session.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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. JupyterServer,
  9. signalToPromises
  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. const msg = event as DebugProtocol.StoppedEvent;
  137. threadId = msg.body.threadId!;
  138. stoppedFuture.resolve();
  139. break;
  140. default:
  141. break;
  142. }
  143. }
  144. );
  145. const reply = await debugSession.sendRequest('dumpCell', {
  146. code
  147. });
  148. await debugSession.sendRequest('setBreakpoints', {
  149. breakpoints,
  150. source: { path: reply.body.sourcePath },
  151. sourceModified: false
  152. });
  153. await debugSession.sendRequest('configurationDone', {});
  154. // trigger an execute_request
  155. connection!.kernel!.requestExecute({ code });
  156. // wait for the first stopped event
  157. await stoppedFuture.promise;
  158. });
  159. afterEach(async () => {
  160. await debugSession.stop();
  161. debugSession.dispose();
  162. await connection.shutdown();
  163. connection.dispose();
  164. });
  165. describe('#debugInfo', () => {
  166. it('should return the state of the current debug session', async () => {
  167. const reply = await debugSession.sendRequest('debugInfo', {});
  168. expect(reply.body.isStarted).toBe(true);
  169. const breakpoints = reply.body.breakpoints;
  170. // breakpoints are in the same file
  171. expect(breakpoints.length).toEqual(1);
  172. const breakpointsInfo = breakpoints[0];
  173. const breakpointLines = breakpointsInfo.breakpoints.map(bp => {
  174. return bp.line;
  175. });
  176. expect(breakpointLines).toEqual([3, 5]);
  177. });
  178. });
  179. describe('#stackTrace', () => {
  180. it('should return the correct stackframes', async () => {
  181. const reply = await debugSession.sendRequest('stackTrace', {
  182. threadId
  183. });
  184. expect(reply.success).toBe(true);
  185. const stackFrames = reply.body.stackFrames;
  186. expect(stackFrames.length).toEqual(1);
  187. const frame = stackFrames[0];
  188. // first breakpoint
  189. expect(frame.line).toEqual(3);
  190. });
  191. });
  192. describe('#scopes', () => {
  193. it('should return the correct scopes', async () => {
  194. const stackFramesReply = await debugSession.sendRequest('stackTrace', {
  195. threadId
  196. });
  197. const frameId = stackFramesReply.body.stackFrames[0].id;
  198. const scopesReply = await debugSession.sendRequest('scopes', {
  199. frameId
  200. });
  201. const scopes = scopesReply.body.scopes;
  202. expect(scopes.length).toBeGreaterThanOrEqual(1);
  203. const locals = scopes.find(scope => scope.name === 'Locals');
  204. expect(locals).toBeTruthy();
  205. });
  206. });
  207. const getVariables = async (
  208. start?: number,
  209. count?: number
  210. ): Promise<DebugProtocol.Variable[]> => {
  211. const stackFramesReply = await debugSession.sendRequest('stackTrace', {
  212. threadId
  213. });
  214. const frameId = stackFramesReply.body.stackFrames[0].id;
  215. const scopesReply = await debugSession.sendRequest('scopes', {
  216. frameId
  217. });
  218. const scopes = scopesReply.body.scopes;
  219. const variablesReference = scopes[0].variablesReference;
  220. const variablesReply = await debugSession.sendRequest('variables', {
  221. variablesReference,
  222. start,
  223. count
  224. });
  225. return variablesReply.body.variables;
  226. };
  227. describe('#variables', () => {
  228. it('should return the variables and their values', async () => {
  229. const variables = await getVariables();
  230. expect(variables.length).toBeGreaterThan(0);
  231. const i = find(variables, variable => variable.name === 'i');
  232. expect(i).toBeDefined();
  233. expect(i!.type).toEqual('int');
  234. expect(i!.value).toEqual('1');
  235. });
  236. });
  237. describe('#variablesPagination', () => {
  238. it.skip('should return the amount of variables requested', async () => {
  239. await debugSession.sendRequest('continue', { threadId });
  240. const variables = await getVariables(1, 1);
  241. const integers = variables.filter(variable => variable.type === 'int');
  242. expect(integers).toBeDefined();
  243. expect(integers.length).toEqual(1);
  244. });
  245. });
  246. describe('#continue', () => {
  247. it('should proceed to the next breakpoint', async () => {
  248. const [first, second] = signalToPromises(debugSession.eventMessage, 2);
  249. await debugSession.sendRequest('continue', { threadId });
  250. // wait for debug events
  251. const [, continued] = await first;
  252. expect(continued.event).toEqual('continued');
  253. const [, stopped] = await second;
  254. expect(stopped.event).toEqual('stopped');
  255. const variables = await getVariables();
  256. const i = find(variables, variable => variable.name === 'i');
  257. expect(i).toBeDefined();
  258. expect(i!.type).toEqual('int');
  259. expect(i!.value).toEqual('2');
  260. const j = find(variables, variable => variable.name === 'j');
  261. expect(j).toBeDefined();
  262. expect(j!.type).toEqual('int');
  263. expect(j!.value).toEqual('4');
  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. });