session.spec.ts 9.0 KB

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