service.spec.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { Session, KernelSpecManager, KernelSpec } from '@jupyterlab/services';
  4. import {
  5. createSession,
  6. signalToPromise,
  7. JupyterServer
  8. } from '@jupyterlab/testutils';
  9. import { UUID, JSONExt } from '@lumino/coreutils';
  10. import { Debugger } from '../src/debugger';
  11. import { IDebugger } from '../src/tokens';
  12. import { KERNELSPECS, handleRequest } from './utils';
  13. /**
  14. * A Test class to mock a KernelSpecManager
  15. */
  16. class TestKernelSpecManager extends KernelSpecManager {
  17. intercept: KernelSpec.ISpecModels | null = null;
  18. /**
  19. * Request the kernel specs
  20. */
  21. protected async requestSpecs(): Promise<void> {
  22. if (this.intercept) {
  23. handleRequest(this, 200, this.intercept);
  24. }
  25. return super.requestSpecs();
  26. }
  27. }
  28. const server = new JupyterServer();
  29. beforeAll(async () => {
  30. jest.setTimeout(20000);
  31. await server.start();
  32. });
  33. afterAll(async () => {
  34. await server.shutdown();
  35. });
  36. describe('Debugging support', () => {
  37. const specs = JSONExt.deepCopy(KERNELSPECS) as KernelSpec.ISpecModels;
  38. let specsManager: TestKernelSpecManager;
  39. let service: Debugger.Service;
  40. let config: IDebugger.IConfig;
  41. let xpython: Session.ISessionConnection;
  42. let ipykernel: Session.ISessionConnection;
  43. beforeAll(async () => {
  44. xpython = await createSession({
  45. name: '',
  46. type: 'test',
  47. path: UUID.uuid4()
  48. });
  49. await xpython.changeKernel({ name: 'xpython' });
  50. ipykernel = await createSession({
  51. name: '',
  52. type: 'test',
  53. path: UUID.uuid4()
  54. });
  55. await ipykernel.changeKernel({ name: 'python3' });
  56. specsManager = new TestKernelSpecManager({ standby: 'never' });
  57. specsManager.intercept = specs;
  58. await specsManager.refreshSpecs();
  59. config = new Debugger.Config();
  60. service = new Debugger.Service({ specsManager, config });
  61. });
  62. afterAll(async () => {
  63. await Promise.all([xpython.shutdown(), ipykernel.shutdown()]);
  64. service.dispose();
  65. specsManager.dispose();
  66. });
  67. describe('#isAvailable', () => {
  68. it('should return true for kernels that have support for debugging', async () => {
  69. const enabled = await service.isAvailable(xpython);
  70. expect(enabled).toBe(true);
  71. });
  72. it('should return false for kernels that do not have support for debugging', async () => {
  73. const enabled = await service.isAvailable(ipykernel);
  74. expect(enabled).toBe(false);
  75. });
  76. });
  77. });
  78. describe('DebuggerService', () => {
  79. const specsManager = new KernelSpecManager();
  80. let connection: Session.ISessionConnection;
  81. let model: Debugger.Model;
  82. let config: IDebugger.IConfig;
  83. let session: IDebugger.ISession;
  84. let service: IDebugger;
  85. beforeEach(async () => {
  86. connection = await createSession({
  87. name: '',
  88. type: 'test',
  89. path: UUID.uuid4()
  90. });
  91. await connection.changeKernel({ name: 'xpython' });
  92. session = new Debugger.Session({ connection });
  93. model = new Debugger.Model();
  94. config = new Debugger.Config();
  95. service = new Debugger.Service({ specsManager, config });
  96. });
  97. afterEach(async () => {
  98. await connection.shutdown();
  99. connection.dispose();
  100. session.dispose();
  101. (service as Debugger.Service).dispose();
  102. });
  103. describe('#constructor()', () => {
  104. it('should create a new instance', () => {
  105. expect(service).toBeInstanceOf(Debugger.Service);
  106. });
  107. });
  108. describe('#start()', () => {
  109. it('should start the service if the session is set', async () => {
  110. service.session = session;
  111. await service.start();
  112. expect(service.isStarted).toEqual(true);
  113. });
  114. it('should throw an error if the session is not set', async () => {
  115. await expect(service.start()).rejects.toThrow(
  116. "Cannot read property 'start' of null"
  117. );
  118. });
  119. });
  120. describe('#stop()', () => {
  121. it('should stop the service if the session is set', async () => {
  122. service.session = session;
  123. await service.start();
  124. await service.stop();
  125. expect(service.isStarted).toEqual(false);
  126. });
  127. });
  128. describe('#session', () => {
  129. it('should emit the sessionChanged signal when setting the session', () => {
  130. const sessionChangedEvents: IDebugger.ISession[] = [];
  131. service.sessionChanged.connect((_, newSession) => {
  132. sessionChangedEvents.push(newSession);
  133. });
  134. service.session = session;
  135. expect(sessionChangedEvents.length).toEqual(1);
  136. expect(sessionChangedEvents[0]).toEqual(session);
  137. });
  138. });
  139. describe('#model', () => {
  140. it('should emit the modelChanged signal when setting the model', () => {
  141. const modelChangedEvents: Debugger.Model[] = [];
  142. service.modelChanged.connect((_, newModel) => {
  143. modelChangedEvents.push(newModel as Debugger.Model);
  144. });
  145. service.model = model;
  146. expect(modelChangedEvents.length).toEqual(1);
  147. expect(modelChangedEvents[0]).toEqual(model);
  148. });
  149. });
  150. describe('protocol', () => {
  151. const code = [
  152. 'i = 0',
  153. 'i += 1',
  154. 'i += 1',
  155. 'j = i**2',
  156. 'j += 1',
  157. 'print(i, j)'
  158. ].join('\n');
  159. let breakpoints: IDebugger.IBreakpoint[];
  160. let sourceId: string;
  161. beforeEach(async () => {
  162. service.session = session;
  163. service.model = model;
  164. await service.restoreState(true);
  165. const breakpointLines: number[] = [3, 5];
  166. sourceId = service.getCodeId(code);
  167. breakpoints = breakpointLines.map((l: number, index: number) => {
  168. return {
  169. id: index,
  170. line: l,
  171. active: true,
  172. verified: true,
  173. source: {
  174. path: sourceId
  175. }
  176. };
  177. });
  178. await service.updateBreakpoints(code, breakpoints);
  179. });
  180. describe('#updateBreakpoints', () => {
  181. it('should update the breakpoints', () => {
  182. const bpList = model.breakpoints.getBreakpoints(sourceId);
  183. expect(bpList).toEqual(breakpoints);
  184. });
  185. });
  186. describe('#restoreState', () => {
  187. it('should restore the breakpoints', async () => {
  188. model.breakpoints.restoreBreakpoints(
  189. new Map<string, IDebugger.IBreakpoint[]>()
  190. );
  191. const bpList1 = model.breakpoints.getBreakpoints(sourceId);
  192. expect(bpList1.length).toEqual(0);
  193. await service.restoreState(true);
  194. const bpList2 = model.breakpoints.getBreakpoints(sourceId);
  195. expect(bpList2).toEqual(breakpoints);
  196. });
  197. });
  198. describe('#restart', () => {
  199. it('should restart the debugger and send the breakpoints again', async () => {
  200. await service.restart();
  201. model.breakpoints.restoreBreakpoints(
  202. new Map<string, IDebugger.IBreakpoint[]>()
  203. );
  204. await service.restoreState(true);
  205. const bpList = model.breakpoints.getBreakpoints(sourceId);
  206. breakpoints[0].id = 2;
  207. breakpoints[1].id = 3;
  208. expect(bpList).toEqual(breakpoints);
  209. });
  210. });
  211. describe('#hasStoppedThreads', () => {
  212. it('should return false if the model is null', () => {
  213. service.model = null;
  214. const hasStoppedThreads = service.hasStoppedThreads();
  215. expect(hasStoppedThreads).toBe(false);
  216. });
  217. it('should return true when the execution has stopped', async () => {
  218. const variablesChanged = signalToPromise(model.variables.changed);
  219. // trigger a manual execute request
  220. connection.kernel.requestExecute({ code });
  221. // wait for the first stopped event and variables changed
  222. await variablesChanged;
  223. const hasStoppedThreads = service.hasStoppedThreads();
  224. expect(hasStoppedThreads).toBe(true);
  225. await service.restart();
  226. });
  227. });
  228. });
  229. });