session.spec.ts 10 KB

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