service.spec.ts 7.9 KB

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