session.spec.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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. ].join('\n');
  99. const breakpoints: DebugProtocol.SourceBreakpoint[] = [
  100. { line: 3 },
  101. { line: 5 }
  102. ];
  103. let connection: Session.ISessionConnection;
  104. let debugSession: Debugger.Session;
  105. let threadId = 1;
  106. beforeEach(async () => {
  107. const path = UUID.uuid4();
  108. connection = await createSession({
  109. name: '',
  110. type: 'test',
  111. path
  112. });
  113. await connection.changeKernel({ name: 'xpython' });
  114. debugSession = new Debugger.Session({
  115. connection
  116. });
  117. await debugSession.start();
  118. const stoppedFuture = new PromiseDelegate<void>();
  119. debugSession.eventMessage.connect(
  120. (sender: Debugger.Session, event: IDebugger.ISession.Event) => {
  121. switch (event.event) {
  122. case 'thread': {
  123. const msg = event as DebugProtocol.ThreadEvent;
  124. threadId = msg.body.threadId;
  125. break;
  126. }
  127. case 'stopped':
  128. stoppedFuture.resolve();
  129. break;
  130. default:
  131. break;
  132. }
  133. }
  134. );
  135. const reply = await debugSession.sendRequest('dumpCell', {
  136. code
  137. });
  138. await debugSession.sendRequest('setBreakpoints', {
  139. breakpoints,
  140. source: { path: reply.body.sourcePath },
  141. sourceModified: false
  142. });
  143. await debugSession.sendRequest('configurationDone', {});
  144. // trigger an execute_request
  145. connection.kernel.requestExecute({ code });
  146. // wait for the first stopped event
  147. await stoppedFuture.promise;
  148. });
  149. afterEach(async () => {
  150. await debugSession.stop();
  151. debugSession.dispose();
  152. await connection.shutdown();
  153. connection.dispose();
  154. });
  155. describe('#debugInfo', () => {
  156. it('should return the state of the current debug session', async () => {
  157. const reply = await debugSession.sendRequest('debugInfo', {});
  158. expect(reply.body.isStarted).toBe(true);
  159. const breakpoints = reply.body.breakpoints;
  160. // breakpoints are in the same file
  161. expect(breakpoints.length).toEqual(1);
  162. const breakpointsInfo = breakpoints[0];
  163. const breakpointLines = breakpointsInfo.breakpoints.map(bp => {
  164. return bp.line;
  165. });
  166. expect(breakpointLines).toEqual([3, 5]);
  167. });
  168. });
  169. describe('#stackTrace', () => {
  170. it('should return the correct stackframes', async () => {
  171. const reply = await debugSession.sendRequest('stackTrace', {
  172. threadId
  173. });
  174. expect(reply.success).toBe(true);
  175. const stackFrames = reply.body.stackFrames;
  176. expect(stackFrames.length).toEqual(1);
  177. const frame = stackFrames[0];
  178. // first breakpoint
  179. expect(frame.line).toEqual(3);
  180. });
  181. });
  182. describe('#scopes', () => {
  183. it('should return the correct scopes', async () => {
  184. const stackFramesReply = await debugSession.sendRequest('stackTrace', {
  185. threadId
  186. });
  187. const frameId = stackFramesReply.body.stackFrames[0].id;
  188. const scopesReply = await debugSession.sendRequest('scopes', {
  189. frameId
  190. });
  191. const scopes = scopesReply.body.scopes;
  192. expect(scopes.length).toEqual(1);
  193. expect(scopes[0].name).toEqual('Locals');
  194. });
  195. });
  196. const getVariables = async (
  197. start?: number,
  198. count?: number
  199. ): Promise<DebugProtocol.Variable[]> => {
  200. const stackFramesReply = await debugSession.sendRequest('stackTrace', {
  201. threadId
  202. });
  203. const frameId = stackFramesReply.body.stackFrames[0].id;
  204. const scopesReply = await debugSession.sendRequest('scopes', {
  205. frameId
  206. });
  207. const scopes = scopesReply.body.scopes;
  208. const variablesReference = scopes[0].variablesReference;
  209. const variablesReply = await debugSession.sendRequest('variables', {
  210. variablesReference,
  211. start,
  212. count
  213. });
  214. return variablesReply.body.variables;
  215. };
  216. describe('#variables', () => {
  217. it('should return the variables and their values', async () => {
  218. const variables = await getVariables();
  219. expect(variables.length).toBeGreaterThan(0);
  220. const i = find(variables, variable => variable.name === 'i');
  221. expect(i).toBeDefined();
  222. expect(i.type).toEqual('int');
  223. expect(i.value).toEqual('1');
  224. });
  225. });
  226. describe('#variablesPagination', () => {
  227. it('should return the amount of variables requested', async () => {
  228. await debugSession.sendRequest('continue', { threadId });
  229. const variables = await getVariables(1, 1);
  230. const integers = variables.filter(variable => variable.type === 'int');
  231. expect(integers).toBeDefined();
  232. expect(integers.length).toEqual(1);
  233. });
  234. });
  235. describe('#continue', () => {
  236. it('should proceed to the next breakpoint', async () => {
  237. const [first, second] = signalToPromises(debugSession.eventMessage, 2);
  238. await debugSession.sendRequest('continue', { threadId });
  239. // wait for debug events
  240. const [, continued] = await first;
  241. expect(continued.event).toEqual('continued');
  242. const [, stopped] = await second;
  243. expect(stopped.event).toEqual('stopped');
  244. const variables = await getVariables();
  245. const i = find(variables, variable => variable.name === 'i');
  246. expect(i).toBeDefined();
  247. expect(i.type).toEqual('int');
  248. expect(i.value).toEqual('2');
  249. const j = find(variables, variable => variable.name === 'j');
  250. expect(j).toBeDefined();
  251. expect(j.type).toEqual('int');
  252. expect(j.value).toEqual('4');
  253. });
  254. });
  255. describe('#loadedSources', () => {
  256. it('should *not* retrieve the list of loaded sources', async () => {
  257. // `loadedSources` is not supported at the moment "unknown command"
  258. const reply = await debugSession.sendRequest('loadedSources', {});
  259. expect(reply.success).toBe(false);
  260. });
  261. });
  262. describe('#source', () => {
  263. it('should retrieve the source of the dumped code cell', async () => {
  264. const stackFramesReply = await debugSession.sendRequest('stackTrace', {
  265. threadId
  266. });
  267. const frame = stackFramesReply.body.stackFrames[0];
  268. const source = frame.source;
  269. const reply = await debugSession.sendRequest('source', {
  270. source: { path: source.path },
  271. sourceReference: source.sourceReference
  272. });
  273. const sourceCode = reply.body.content;
  274. expect(sourceCode).toEqual(code);
  275. });
  276. });
  277. describe('#evaluate', () => {
  278. it('should evaluate the code sent to the kernel', async () => {
  279. const stackFramesReply = await debugSession.sendRequest('stackTrace', {
  280. threadId
  281. });
  282. const frameId = stackFramesReply.body.stackFrames[0].id;
  283. const reply = await debugSession.sendRequest('evaluate', {
  284. frameId,
  285. context: 'repl',
  286. expression: 'k = 123',
  287. format: {}
  288. });
  289. expect(reply.success).toBe(true);
  290. const variables = await getVariables();
  291. const k = find(variables, variable => variable.name === 'k');
  292. expect(k).toBeDefined();
  293. expect(k.type).toEqual('int');
  294. expect(k.value).toEqual('123');
  295. });
  296. });
  297. });