session.spec.ts 11 KB

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