session.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { Session } from '@jupyterlab/services';
  4. import {
  5. createSession,
  6. signalToPromises,
  7. JupyterServer
  8. } from '@jupyterlab/testutils';
  9. import { find } from '@lumino/algorithm';
  10. import { PromiseDelegate, UUID } from '@lumino/coreutils';
  11. import { DebugProtocol } from 'vscode-debugprotocol';
  12. import { Debugger } from '../src/debugger';
  13. import { IDebugger } from '../src/tokens';
  14. const server = new JupyterServer();
  15. beforeAll(async () => {
  16. jest.setTimeout(20000);
  17. await server.start();
  18. });
  19. afterAll(async () => {
  20. await server.shutdown();
  21. });
  22. describe('Debugger.Session', () => {
  23. let connection: Session.ISessionConnection;
  24. beforeEach(async () => {
  25. const path = UUID.uuid4();
  26. connection = await createSession({
  27. name: '',
  28. type: 'test',
  29. path
  30. });
  31. await connection.changeKernel({ name: 'xpython' });
  32. });
  33. afterEach(async () => {
  34. await connection.shutdown();
  35. });
  36. describe('#isDisposed', () => {
  37. it('should return whether the object is disposed', () => {
  38. const debugSession = new Debugger.Session({
  39. connection
  40. });
  41. expect(debugSession.isDisposed).toEqual(false);
  42. debugSession.dispose();
  43. expect(debugSession.isDisposed).toEqual(true);
  44. });
  45. });
  46. describe('#eventMessage', () => {
  47. it('should be emitted when sending debug messages', async () => {
  48. const debugSession = new Debugger.Session({
  49. connection
  50. });
  51. let events: string[] = [];
  52. debugSession.eventMessage.connect((sender, event) => {
  53. events.push(event.event);
  54. });
  55. await debugSession.start();
  56. await debugSession.stop();
  57. expect(events).toEqual(['output', 'initialized', 'process']);
  58. });
  59. });
  60. describe('#sendRequest success', () => {
  61. it('should send debug messages to the kernel', async () => {
  62. const debugSession = new Debugger.Session({
  63. connection
  64. });
  65. await debugSession.start();
  66. const code = 'i=0\ni+=1\ni+=1';
  67. const reply = await debugSession.sendRequest('dumpCell', {
  68. code
  69. });
  70. await debugSession.stop();
  71. expect(reply.body.sourcePath).toContain('.py');
  72. });
  73. });
  74. describe('#sendRequest failure', () => {
  75. it('should handle replies with success false', async () => {
  76. const debugSession = new Debugger.Session({
  77. connection
  78. });
  79. await debugSession.start();
  80. const reply = await debugSession.sendRequest('evaluate', {
  81. expression: 'a'
  82. });
  83. await debugSession.stop();
  84. const { success, message } = reply;
  85. expect(success).toBe(false);
  86. expect(message).toContain('Unable to find thread for evaluation');
  87. });
  88. });
  89. });
  90. describe('protocol', () => {
  91. const code = [
  92. 'i = 0',
  93. 'i += 1',
  94. 'i += 1',
  95. 'j = i**2',
  96. 'j += 1',
  97. 'print(i, j)',
  98. 'for i in range(10):',
  99. ' j = i ** 2',
  100. ' print(j)',
  101. 'print(j)'
  102. ].join('\n');
  103. const breakpoints: DebugProtocol.SourceBreakpoint[] = [
  104. { line: 3 },
  105. { line: 5 }
  106. ];
  107. let connection: Session.ISessionConnection;
  108. let debugSession: Debugger.Session;
  109. let threadId = 1;
  110. beforeEach(async () => {
  111. const path = UUID.uuid4();
  112. connection = await createSession({
  113. name: '',
  114. type: 'test',
  115. path
  116. });
  117. await connection.changeKernel({ name: 'xpython' });
  118. debugSession = new Debugger.Session({
  119. connection
  120. });
  121. await debugSession.start();
  122. const stoppedFuture = new PromiseDelegate<void>();
  123. debugSession.eventMessage.connect(
  124. (sender: Debugger.Session, event: IDebugger.ISession.Event) => {
  125. switch (event.event) {
  126. case 'thread': {
  127. const msg = event as DebugProtocol.ThreadEvent;
  128. threadId = msg.body.threadId;
  129. break;
  130. }
  131. case 'stopped':
  132. stoppedFuture.resolve();
  133. break;
  134. default:
  135. break;
  136. }
  137. }
  138. );
  139. const reply = await debugSession.sendRequest('dumpCell', {
  140. code
  141. });
  142. await debugSession.sendRequest('setBreakpoints', {
  143. breakpoints,
  144. source: { path: reply.body.sourcePath },
  145. sourceModified: false
  146. });
  147. await debugSession.sendRequest('configurationDone', {});
  148. // trigger an execute_request
  149. connection!.kernel!.requestExecute({ code });
  150. // wait for the first stopped event
  151. await stoppedFuture.promise;
  152. });
  153. afterEach(async () => {
  154. await debugSession.stop();
  155. debugSession.dispose();
  156. await connection.shutdown();
  157. connection.dispose();
  158. });
  159. describe('#debugInfo', () => {
  160. it('should return the state of the current debug session', async () => {
  161. const reply = await debugSession.sendRequest('debugInfo', {});
  162. expect(reply.body.isStarted).toBe(true);
  163. const breakpoints = reply.body.breakpoints;
  164. // breakpoints are in the same file
  165. expect(breakpoints.length).toEqual(1);
  166. const breakpointsInfo = breakpoints[0];
  167. const breakpointLines = breakpointsInfo.breakpoints.map(bp => {
  168. return bp.line;
  169. });
  170. expect(breakpointLines).toEqual([3, 5]);
  171. });
  172. });
  173. describe('#stackTrace', () => {
  174. it('should return the correct stackframes', async () => {
  175. const reply = await debugSession.sendRequest('stackTrace', {
  176. threadId
  177. });
  178. expect(reply.success).toBe(true);
  179. const stackFrames = reply.body.stackFrames;
  180. expect(stackFrames.length).toEqual(1);
  181. const frame = stackFrames[0];
  182. // first breakpoint
  183. expect(frame.line).toEqual(3);
  184. });
  185. });
  186. describe('#scopes', () => {
  187. it('should return the correct scopes', async () => {
  188. const stackFramesReply = await debugSession.sendRequest('stackTrace', {
  189. threadId
  190. });
  191. const frameId = stackFramesReply.body.stackFrames[0].id;
  192. const scopesReply = await debugSession.sendRequest('scopes', {
  193. frameId
  194. });
  195. const scopes = scopesReply.body.scopes;
  196. expect(scopes.length).toEqual(1);
  197. expect(scopes[0].name).toEqual('Locals');
  198. });
  199. });
  200. const getVariables = async (
  201. start?: number,
  202. count?: number
  203. ): Promise<DebugProtocol.Variable[]> => {
  204. const stackFramesReply = await debugSession.sendRequest('stackTrace', {
  205. threadId
  206. });
  207. const frameId = stackFramesReply.body.stackFrames[0].id;
  208. const scopesReply = await debugSession.sendRequest('scopes', {
  209. frameId
  210. });
  211. const scopes = scopesReply.body.scopes;
  212. const variablesReference = scopes[0].variablesReference;
  213. const variablesReply = await debugSession.sendRequest('variables', {
  214. variablesReference,
  215. start,
  216. count
  217. });
  218. return variablesReply.body.variables;
  219. };
  220. describe('#variables', () => {
  221. it('should return the variables and their values', async () => {
  222. const variables = await getVariables();
  223. expect(variables.length).toBeGreaterThan(0);
  224. const i = find(variables, variable => variable.name === 'i');
  225. expect(i).toBeDefined();
  226. expect(i!.type).toEqual('int');
  227. expect(i!.value).toEqual('1');
  228. });
  229. });
  230. describe('#variablesPagination', () => {
  231. it.skip('should return the amount of variables requested', async () => {
  232. await debugSession.sendRequest('continue', { threadId });
  233. const variables = await getVariables(1, 1);
  234. const integers = variables.filter(variable => variable.type === 'int');
  235. expect(integers).toBeDefined();
  236. expect(integers.length).toEqual(1);
  237. });
  238. });
  239. describe('#continue', () => {
  240. it('should proceed to the next breakpoint', async () => {
  241. const [first, second] = signalToPromises(debugSession.eventMessage, 2);
  242. await debugSession.sendRequest('continue', { threadId });
  243. // wait for debug events
  244. const [, continued] = await first;
  245. expect(continued.event).toEqual('continued');
  246. const [, stopped] = await second;
  247. expect(stopped.event).toEqual('stopped');
  248. const variables = await getVariables();
  249. const i = find(variables, variable => variable.name === 'i');
  250. expect(i).toBeDefined();
  251. expect(i!.type).toEqual('int');
  252. expect(i!.value).toEqual('2');
  253. const j = find(variables, variable => variable.name === 'j');
  254. expect(j).toBeDefined();
  255. expect(j!.type).toEqual('int');
  256. expect(j!.value).toEqual('4');
  257. });
  258. });
  259. describe('#loadedSources', () => {
  260. it('should *not* retrieve the list of loaded sources', async () => {
  261. // `loadedSources` is not supported at the moment "unknown command"
  262. const reply = await debugSession.sendRequest('loadedSources', {});
  263. expect(reply.success).toBe(false);
  264. });
  265. });
  266. describe('#source', () => {
  267. it('should retrieve the source of the dumped code cell', async () => {
  268. const stackFramesReply = await debugSession.sendRequest('stackTrace', {
  269. threadId
  270. });
  271. const frame = stackFramesReply.body.stackFrames[0];
  272. const source = frame.source;
  273. const reply = await debugSession.sendRequest('source', {
  274. source: { path: source!.path! },
  275. sourceReference: source!.sourceReference!
  276. });
  277. const sourceCode = reply.body.content;
  278. expect(sourceCode).toEqual(code);
  279. });
  280. });
  281. describe('#evaluate', () => {
  282. it('should evaluate the code sent to the kernel', async () => {
  283. const stackFramesReply = await debugSession.sendRequest('stackTrace', {
  284. threadId
  285. });
  286. const frameId = stackFramesReply.body.stackFrames[0].id;
  287. const reply = await debugSession.sendRequest('evaluate', {
  288. frameId,
  289. context: 'repl',
  290. expression: 'k = 123',
  291. format: {}
  292. });
  293. expect(reply.success).toBe(true);
  294. const variables = await getVariables();
  295. const k = find(variables, variable => variable.name === 'k');
  296. expect(k).toBeDefined();
  297. expect(k!.type).toEqual('int');
  298. expect(k!.value).toEqual('123');
  299. });
  300. });
  301. describe('#setBreakpoints with condition', () => {
  302. it('should be hit when the condition is true', async () => {
  303. const reply = await debugSession.sendRequest('dumpCell', {
  304. code
  305. });
  306. await debugSession.sendRequest('setBreakpoints', {
  307. breakpoints: [{ line: 9, condition: 'i == 5' }],
  308. source: { path: reply.body.sourcePath },
  309. sourceModified: false
  310. });
  311. await debugSession.sendRequest('configurationDone', {});
  312. // advance to the conditional breakpoint
  313. await debugSession.sendRequest('continue', { threadId });
  314. const variables = await getVariables();
  315. const j = find(variables, variable => variable.name === 'j');
  316. expect(j).toBeDefined();
  317. expect(j!.value).toEqual('25');
  318. });
  319. });
  320. });