ikernel.spec.ts 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { expect } from 'chai';
  4. import { PageConfig } from '@jupyterlab/coreutils';
  5. import { UUID } from '@phosphor/coreutils';
  6. import { JSONObject, PromiseDelegate } from '@phosphor/coreutils';
  7. import { Signal } from '@phosphor/signaling';
  8. import { Kernel, KernelMessage } from '../../../src/kernel';
  9. import { Cell } from '@jupyterlab/cells';
  10. import {
  11. expectFailure,
  12. KernelTester,
  13. handleRequest,
  14. createMsg,
  15. testEmission
  16. } from '../utils';
  17. describe('Kernel.IKernel', () => {
  18. let defaultKernel: Kernel.IKernel;
  19. let specs: Kernel.ISpecModels;
  20. beforeAll(async () => {
  21. specs = await Kernel.getSpecs();
  22. });
  23. beforeEach(async () => {
  24. defaultKernel = await Kernel.startNew();
  25. await defaultKernel.ready;
  26. });
  27. afterEach(async () => {
  28. await defaultKernel.shutdown();
  29. defaultKernel.dispose();
  30. });
  31. afterAll(async () => {
  32. await Kernel.shutdownAll();
  33. });
  34. describe('#terminated', () => {
  35. it('should be emitted when the kernel is shut down', async () => {
  36. let called = false;
  37. defaultKernel.terminated.connect((sender, args) => {
  38. expect(sender).to.equal(defaultKernel);
  39. expect(args).to.be.undefined;
  40. called = true;
  41. });
  42. await defaultKernel.shutdown();
  43. expect(called).to.equal(true);
  44. });
  45. });
  46. describe('#statusChanged', () => {
  47. it('should be a signal following the Kernel status', async () => {
  48. let called = false;
  49. defaultKernel.statusChanged.connect(() => {
  50. if (defaultKernel.status === 'busy') {
  51. called = true;
  52. }
  53. });
  54. await defaultKernel.requestExecute({ code: 'a=1' }, true).done;
  55. expect(called).to.equal(true);
  56. });
  57. });
  58. describe('#iopubMessage', async () => {
  59. it('should be emitted for an iopub message', async () => {
  60. let called = false;
  61. defaultKernel.iopubMessage.connect((k, msg) => {
  62. called = true;
  63. });
  64. await defaultKernel.requestExecute({ code: 'a=1' }, true).done;
  65. expect(called).to.equal(true);
  66. });
  67. it('should be emitted regardless of the sender', async () => {
  68. const tester = new KernelTester();
  69. const kernel = await tester.start();
  70. const msgId = UUID.uuid4();
  71. const emission = testEmission(kernel.iopubMessage, {
  72. find: (k, msg) => msg.header.msg_id === msgId
  73. });
  74. const msg = KernelMessage.createMessage({
  75. msgType: 'status',
  76. channel: 'iopub',
  77. session: tester.serverSessionId,
  78. msgId
  79. }) as KernelMessage.IStatusMsg;
  80. msg.content.execution_state = 'idle';
  81. tester.send(msg);
  82. await emission;
  83. await tester.shutdown();
  84. tester.dispose();
  85. });
  86. });
  87. describe('#unhandledMessage', () => {
  88. let tester: KernelTester;
  89. beforeEach(() => {
  90. tester = new KernelTester();
  91. });
  92. afterEach(async () => {
  93. await tester.shutdown();
  94. tester.dispose();
  95. });
  96. it('should be emitted for an unhandled message', async () => {
  97. const kernel = await tester.start();
  98. const msgId = UUID.uuid4();
  99. const emission = testEmission(kernel.unhandledMessage, {
  100. find: (k, msg) => msg.header.msg_id === msgId
  101. });
  102. const msg = KernelMessage.createShellMessage({
  103. msgType: 'foo',
  104. channel: 'shell',
  105. session: tester.serverSessionId,
  106. msgId
  107. });
  108. msg.parent_header = { session: kernel.clientId };
  109. tester.send(msg);
  110. await emission;
  111. });
  112. it('should not be emitted for an iopub signal', async () => {
  113. const kernel = await tester.start();
  114. // We'll send two messages, first an iopub message, then a shell message.
  115. // The unhandledMessage signal should only emit once for the shell message.
  116. const msgId = UUID.uuid4();
  117. const emission = testEmission(kernel.unhandledMessage, {
  118. test: (k, msg) => {
  119. expect(msg.header.msg_id).to.equal(msgId);
  120. }
  121. });
  122. // Send an iopub message.
  123. tester.sendStatus(UUID.uuid4(), 'idle');
  124. // Send a shell message.
  125. const msg = KernelMessage.createShellMessage({
  126. msgType: 'foo',
  127. channel: 'shell',
  128. session: tester.serverSessionId,
  129. msgId
  130. });
  131. msg.parent_header = { session: kernel.clientId };
  132. tester.send(msg);
  133. await emission;
  134. });
  135. it('should not be emitted for a different client session', async () => {
  136. const kernel = await tester.start();
  137. // We'll send two messages, first a message with a different session, then
  138. // one with the current client session. The unhandledMessage signal should
  139. // only emit once for the current session message.
  140. const msgId = 'message from right session';
  141. const emission = testEmission(kernel.unhandledMessage, {
  142. test: (k, msg) => {
  143. expect((msg.parent_header as KernelMessage.IHeader).session).to.equal(
  144. kernel.clientId
  145. );
  146. expect(msg.header.msg_id).to.equal(msgId);
  147. }
  148. });
  149. // Send a shell message with the wrong client (parent) session.
  150. const msg1 = KernelMessage.createShellMessage({
  151. msgType: 'foo',
  152. channel: 'shell',
  153. session: tester.serverSessionId,
  154. msgId: 'message from wrong session'
  155. });
  156. msg1.parent_header = { session: 'wrong session' };
  157. tester.send(msg1);
  158. // Send a shell message with the right client (parent) session.
  159. const msg2 = KernelMessage.createShellMessage({
  160. msgType: 'foo',
  161. channel: 'shell',
  162. session: tester.serverSessionId,
  163. msgId: msgId
  164. });
  165. msg2.parent_header = { session: kernel.clientId };
  166. tester.send(msg2);
  167. await emission;
  168. });
  169. });
  170. describe('#anyMessage', () => {
  171. let tester: KernelTester;
  172. beforeEach(() => {
  173. tester = new KernelTester();
  174. });
  175. afterEach(async () => {
  176. await tester.shutdown();
  177. tester.dispose();
  178. });
  179. it('should be emitted for an unhandled message', async () => {
  180. const kernel = await tester.start();
  181. const msgId = UUID.uuid4();
  182. const emission = testEmission(kernel.anyMessage, {
  183. test: (k, args) => {
  184. expect(args.msg.header.msg_id).to.equal(msgId);
  185. expect(args.msg.header.msg_type).to.equal('foo');
  186. expect(args.direction).to.equal('recv');
  187. }
  188. });
  189. const msg = KernelMessage.createShellMessage({
  190. msgType: 'foo',
  191. channel: 'shell',
  192. session: tester.serverSessionId,
  193. msgId
  194. });
  195. msg.parent_header = { session: kernel.clientId };
  196. tester.send(msg);
  197. await emission;
  198. });
  199. it('should be emitted for an iopub message', async () => {
  200. const kernel = await tester.start();
  201. const msgId = 'idle status';
  202. const emission = testEmission(kernel.anyMessage, {
  203. test: (k, args) => {
  204. expect((args.msg.header as any).msg_id).to.equal(msgId);
  205. expect(args.direction).to.equal('recv');
  206. }
  207. });
  208. tester.sendStatus(msgId, 'idle');
  209. await emission;
  210. });
  211. it('should be emitted for an stdin message', async () => {
  212. const kernel = await tester.start();
  213. const emission = testEmission(kernel.anyMessage, {
  214. test: (k, args) => {
  215. expect(args.msg.content.value).to.equal('foo');
  216. expect(args.direction).to.equal('send');
  217. }
  218. });
  219. kernel.sendInputReply({ value: 'foo' });
  220. await emission;
  221. });
  222. });
  223. describe('#id', () => {
  224. it('should be a string', () => {
  225. expect(typeof defaultKernel.id).to.equal('string');
  226. });
  227. });
  228. describe('#name', () => {
  229. it('should be a string', () => {
  230. expect(typeof defaultKernel.name).to.equal('string');
  231. });
  232. });
  233. describe('#model', () => {
  234. it('should be an IModel', () => {
  235. const model = defaultKernel.model;
  236. expect(typeof model.name).to.equal('string');
  237. expect(typeof model.id).to.equal('string');
  238. });
  239. });
  240. describe('#username', () => {
  241. it('should be a string', () => {
  242. expect(typeof defaultKernel.username).to.equal('string');
  243. });
  244. });
  245. describe('#serverSettings', () => {
  246. it('should be the server settings', () => {
  247. expect(defaultKernel.serverSettings.baseUrl).to.equal(
  248. PageConfig.getBaseUrl()
  249. );
  250. });
  251. });
  252. describe('#clientId', () => {
  253. it('should be a string', () => {
  254. expect(typeof defaultKernel.clientId).to.equal('string');
  255. });
  256. });
  257. describe('#status', () => {
  258. it('should get an idle status', async () => {
  259. const emission = testEmission(defaultKernel.statusChanged, {
  260. find: () => defaultKernel.status === 'idle'
  261. });
  262. await defaultKernel.requestExecute({ code: 'a=1' }).done;
  263. await emission;
  264. });
  265. // TODO: seems to be sporadically timing out if we await the restart. See
  266. // https://github.com/jupyter/notebook/issues/3705.
  267. it.skip('should get a restarting status', async () => {
  268. const emission = testEmission(defaultKernel.statusChanged, {
  269. find: () => defaultKernel.status === 'restarting'
  270. });
  271. await defaultKernel.restart();
  272. await emission;
  273. });
  274. it('should get a busy status', async () => {
  275. const emission = testEmission(defaultKernel.statusChanged, {
  276. find: () => defaultKernel.status === 'busy'
  277. });
  278. await defaultKernel.requestExecute({ code: 'a=1' }, true).done;
  279. await emission;
  280. });
  281. it('should get a reconnecting status', async () => {
  282. const tester = new KernelTester();
  283. const kernel = await tester.start();
  284. await kernel.ready;
  285. const emission = testEmission(kernel.statusChanged, {
  286. find: () => kernel.status === 'reconnecting'
  287. });
  288. await tester.close();
  289. await emission;
  290. tester.dispose();
  291. });
  292. it('should get a dead status', async () => {
  293. const tester = new KernelTester();
  294. const kernel = await tester.start();
  295. await kernel.ready;
  296. const dead = testEmission(kernel.statusChanged, {
  297. find: () => kernel.status === 'dead'
  298. });
  299. tester.sendStatus(UUID.uuid4(), 'dead');
  300. await dead;
  301. tester.dispose();
  302. });
  303. it('should not emit an invalid status', async () => {
  304. const tester = new KernelTester();
  305. const kernel = await tester.start();
  306. await kernel.ready;
  307. const emission = testEmission(kernel.statusChanged, {
  308. test: (k, status) => {
  309. expect(status).to.equal('busy');
  310. expect(kernel.status).to.equal('busy');
  311. }
  312. });
  313. // This invalid status is not emitted.
  314. tester.sendStatus(UUID.uuid4(), 'invalid-status' as Kernel.Status);
  315. // This valid status is emitted.
  316. tester.sendStatus(UUID.uuid4(), 'busy');
  317. await emission;
  318. tester.dispose();
  319. });
  320. });
  321. describe('#info', () => {
  322. it('should get the kernel info', () => {
  323. const name = defaultKernel.info.language_info.name;
  324. const defaultSpecs = specs.kernelspecs[specs.default];
  325. expect(name).to.equal(defaultSpecs.language);
  326. });
  327. });
  328. describe('#getSpec()', () => {
  329. it('should resolve with the spec', async () => {
  330. const spec = await defaultKernel.getSpec();
  331. expect(spec.name).to.equal(specs.default);
  332. });
  333. });
  334. describe('#isReady', () => {
  335. it('should test whether the kernel is ready', async () => {
  336. const kernel = await Kernel.startNew();
  337. expect(kernel.isReady).to.equal(false);
  338. await kernel.ready;
  339. expect(kernel.isReady).to.equal(true);
  340. await kernel.shutdown();
  341. });
  342. });
  343. describe('#ready', () => {
  344. it('should resolve when the kernel is ready', async () => {
  345. await defaultKernel.ready;
  346. });
  347. });
  348. describe('#isDisposed', () => {
  349. it('should be true after we dispose of the kernel', () => {
  350. const kernel = Kernel.connectTo(defaultKernel.model);
  351. expect(kernel.isDisposed).to.equal(false);
  352. kernel.dispose();
  353. expect(kernel.isDisposed).to.equal(true);
  354. });
  355. it('should be safe to call multiple times', () => {
  356. const kernel = Kernel.connectTo(defaultKernel.model);
  357. expect(kernel.isDisposed).to.equal(false);
  358. expect(kernel.isDisposed).to.equal(false);
  359. kernel.dispose();
  360. expect(kernel.isDisposed).to.equal(true);
  361. expect(kernel.isDisposed).to.equal(true);
  362. });
  363. });
  364. describe('#dispose()', () => {
  365. it('should dispose of the resources held by the kernel', () => {
  366. const kernel = Kernel.connectTo(defaultKernel.model);
  367. const future = kernel.requestExecute({ code: 'foo' });
  368. expect(future.isDisposed).to.equal(false);
  369. kernel.dispose();
  370. expect(future.isDisposed).to.equal(true);
  371. });
  372. it('should be safe to call twice', () => {
  373. const kernel = Kernel.connectTo(defaultKernel.model);
  374. const future = kernel.requestExecute({ code: 'foo' });
  375. expect(future.isDisposed).to.equal(false);
  376. kernel.dispose();
  377. expect(future.isDisposed).to.equal(true);
  378. expect(kernel.isDisposed).to.equal(true);
  379. kernel.dispose();
  380. expect(future.isDisposed).to.equal(true);
  381. expect(kernel.isDisposed).to.equal(true);
  382. });
  383. });
  384. describe('#sendShellMessage()', () => {
  385. it('should send a message to the kernel', async () => {
  386. const tester = new KernelTester();
  387. const kernel = await tester.start();
  388. const done = new PromiseDelegate<void>();
  389. const msgId = UUID.uuid4();
  390. tester.onMessage(msg => {
  391. try {
  392. expect(msg.header.msg_id).to.equal(msgId);
  393. } catch (e) {
  394. done.reject(e);
  395. throw e;
  396. }
  397. done.resolve(null);
  398. });
  399. const options: KernelMessage.IOptions = {
  400. msgType: 'custom',
  401. channel: 'shell',
  402. username: kernel.username,
  403. session: kernel.clientId,
  404. msgId
  405. };
  406. const msg = KernelMessage.createShellMessage(options);
  407. kernel.sendShellMessage(msg, true);
  408. await done;
  409. await tester.shutdown();
  410. tester.dispose();
  411. });
  412. it('should send a binary message', async () => {
  413. const tester = new KernelTester();
  414. const kernel = await tester.start();
  415. const done = new PromiseDelegate<void>();
  416. const msgId = UUID.uuid4();
  417. tester.onMessage(msg => {
  418. try {
  419. const decoder = new TextDecoder('utf8');
  420. const item = msg.buffers[0] as DataView;
  421. expect(decoder.decode(item)).to.equal('hello');
  422. } catch (e) {
  423. done.reject(e);
  424. throw e;
  425. }
  426. done.resolve(null);
  427. });
  428. const options: KernelMessage.IOptions = {
  429. msgType: 'custom',
  430. channel: 'shell',
  431. username: kernel.username,
  432. session: kernel.clientId,
  433. msgId
  434. };
  435. const encoder = new TextEncoder();
  436. const data = encoder.encode('hello');
  437. const msg = KernelMessage.createShellMessage(options, {}, {}, [
  438. data,
  439. data.buffer
  440. ]);
  441. kernel.sendShellMessage(msg, true);
  442. await done;
  443. await tester.shutdown();
  444. tester.dispose();
  445. });
  446. it('should fail if the kernel is dead', async () => {
  447. const tester = new KernelTester();
  448. const kernel = await tester.start();
  449. // Create a promise that resolves when the kernel's status changes to dead
  450. const dead = testEmission(kernel.statusChanged, {
  451. find: () => kernel.status === 'dead'
  452. });
  453. tester.sendStatus(UUID.uuid4(), 'dead');
  454. await dead;
  455. const options: KernelMessage.IOptions = {
  456. msgType: 'custom',
  457. channel: 'shell',
  458. username: kernel.username,
  459. session: kernel.clientId
  460. };
  461. const msg = KernelMessage.createShellMessage(options);
  462. expect(() => {
  463. kernel.sendShellMessage(msg, true);
  464. }).to.throw(/Kernel is dead/);
  465. await tester.shutdown();
  466. tester.dispose();
  467. });
  468. it('should handle out of order messages', async () => {
  469. // This test that a future.done promise resolves when a status idle and
  470. // reply come through, even if the status comes first.
  471. const tester = new KernelTester();
  472. const kernel = await tester.start();
  473. const options: KernelMessage.IOptions = {
  474. msgType: 'custom',
  475. channel: 'shell',
  476. username: kernel.username,
  477. session: kernel.clientId
  478. };
  479. const msg = KernelMessage.createShellMessage(options);
  480. const future = kernel.sendShellMessage(msg, true);
  481. let newMsg: KernelMessage.IMessage;
  482. tester.onMessage(msg => {
  483. // trigger onDone
  484. options.msgType = 'status';
  485. options.channel = 'iopub';
  486. newMsg = KernelMessage.createMessage(options, {
  487. execution_state: 'idle'
  488. });
  489. newMsg.parent_header = msg.header;
  490. tester.send(newMsg);
  491. future.onIOPub = () => {
  492. options.msgType = 'custom';
  493. options.channel = 'shell';
  494. newMsg = KernelMessage.createShellMessage(options);
  495. newMsg.parent_header = msg.header;
  496. tester.send(newMsg);
  497. };
  498. });
  499. await future.done;
  500. await tester.shutdown();
  501. tester.dispose();
  502. });
  503. });
  504. describe('#interrupt()', () => {
  505. it('should interrupt and resolve with a valid server response', async () => {
  506. const kernel = await Kernel.startNew();
  507. await kernel.ready;
  508. await kernel.interrupt();
  509. await kernel.shutdown();
  510. });
  511. it('should throw an error for an invalid response', async () => {
  512. handleRequest(defaultKernel, 200, {
  513. id: defaultKernel.id,
  514. name: defaultKernel.name
  515. });
  516. const interrupt = defaultKernel.interrupt();
  517. await expectFailure(interrupt, 'Invalid response: 200 OK');
  518. });
  519. it('should throw an error for an error response', async () => {
  520. handleRequest(defaultKernel, 500, {});
  521. const interrupt = defaultKernel.interrupt();
  522. await expectFailure(interrupt, '');
  523. });
  524. it('should fail if the kernel is dead', async () => {
  525. const tester = new KernelTester();
  526. const kernel = await tester.start();
  527. // Create a promise that resolves when the kernel's status changes to dead
  528. const dead = testEmission(kernel.statusChanged, {
  529. find: () => kernel.status === 'dead'
  530. });
  531. tester.sendStatus(UUID.uuid4(), 'dead');
  532. await dead;
  533. await expectFailure(kernel.interrupt(), 'Kernel is dead');
  534. tester.dispose();
  535. });
  536. });
  537. describe('#restart()', () => {
  538. // TODO: seems to be sporadically timing out if we await the restart. See
  539. // https://github.com/jupyter/notebook/issues/3705.
  540. it.skip('should restart and resolve with a valid server response', async () => {
  541. await defaultKernel.restart();
  542. await defaultKernel.ready;
  543. });
  544. it('should fail if the kernel does not restart', async () => {
  545. handleRequest(defaultKernel, 500, {});
  546. const restart = defaultKernel.restart();
  547. await expectFailure(restart, '');
  548. });
  549. it('should throw an error for an invalid response', async () => {
  550. const kernel = defaultKernel;
  551. handleRequest(kernel, 205, { id: kernel.id, name: kernel.name });
  552. await expectFailure(
  553. kernel.restart(),
  554. 'Invalid response: 205 Reset Content'
  555. );
  556. });
  557. it('should throw an error for an error response', async () => {
  558. handleRequest(defaultKernel, 500, {});
  559. const restart = defaultKernel.restart();
  560. await expectFailure(restart);
  561. });
  562. it('should throw an error for an invalid id', async () => {
  563. handleRequest(defaultKernel, 200, {});
  564. const restart = defaultKernel.restart();
  565. await expectFailure(restart);
  566. });
  567. // TODO: seems to be sporadically timing out if we await the restart. See
  568. // https://github.com/jupyter/notebook/issues/3705.
  569. it.skip('should dispose of existing comm and future objects', async () => {
  570. const kernel = defaultKernel;
  571. const comm = kernel.connectToComm('test');
  572. const future = kernel.requestExecute({ code: 'foo' });
  573. kernel.restart();
  574. await kernel.ready;
  575. expect(future.isDisposed).to.equal(true);
  576. expect(comm.isDisposed).to.equal(true);
  577. });
  578. });
  579. describe('#reconnect()', () => {
  580. it('should reconnect the websocket', () => {
  581. return defaultKernel.reconnect();
  582. });
  583. it('should emit `"reconnecting"`, then `"connected"` status', async () => {
  584. let connectedEmission: Promise<void>;
  585. const emission = testEmission(defaultKernel.statusChanged, {
  586. find: () => defaultKernel.status === 'reconnecting',
  587. test: () => {
  588. connectedEmission = testEmission(defaultKernel.statusChanged, {
  589. find: () => defaultKernel.status === 'connected'
  590. });
  591. }
  592. });
  593. await defaultKernel.reconnect();
  594. await emission;
  595. await connectedEmission;
  596. });
  597. });
  598. describe('#shutdown()', () => {
  599. it('should shut down and resolve with a valid server response', async () => {
  600. const kernel = await Kernel.startNew();
  601. await kernel.ready;
  602. await kernel.shutdown();
  603. });
  604. it('should throw an error for an invalid response', async () => {
  605. handleRequest(defaultKernel, 200, {
  606. id: UUID.uuid4(),
  607. name: 'foo'
  608. });
  609. const shutdown = defaultKernel.shutdown();
  610. await expectFailure(shutdown, 'Invalid response: 200 OK');
  611. });
  612. it('should handle a 404 error', async () => {
  613. const kernel = await Kernel.startNew();
  614. handleRequest(kernel, 404, {});
  615. await kernel.shutdown();
  616. });
  617. it('should throw an error for an error response', async () => {
  618. handleRequest(defaultKernel, 500, {});
  619. const shutdown = defaultKernel.shutdown();
  620. await expectFailure(shutdown, '');
  621. });
  622. it('should fail if the kernel is dead', async () => {
  623. const tester = new KernelTester();
  624. const kernel = await tester.start();
  625. // Create a promise that resolves when the kernel's status changes to dead
  626. const dead = testEmission(kernel.statusChanged, {
  627. find: () => kernel.status === 'dead'
  628. });
  629. tester.sendStatus(UUID.uuid4(), 'dead');
  630. await dead;
  631. await expectFailure(kernel.shutdown(), 'Kernel is dead');
  632. tester.dispose();
  633. });
  634. it('should dispose of all kernel instances', async () => {
  635. const kernel0 = await Kernel.startNew();
  636. const kernel1 = Kernel.connectTo(kernel0.model);
  637. await kernel0.ready;
  638. await kernel1.ready;
  639. await kernel0.shutdown();
  640. expect(kernel0.isDisposed).to.equal(true);
  641. expect(kernel1.isDisposed).to.equal(true);
  642. });
  643. });
  644. describe('#requestKernelInfo()', () => {
  645. it('should resolve the promise', async () => {
  646. const msg = await defaultKernel.requestKernelInfo();
  647. const name = msg.content.language_info.name;
  648. expect(name).to.be.ok;
  649. });
  650. });
  651. describe('#requestComplete()', () => {
  652. it('should resolve the promise', async () => {
  653. const options: KernelMessage.ICompleteRequest = {
  654. code: 'hello',
  655. cursor_pos: 4
  656. };
  657. await defaultKernel.requestComplete(options);
  658. });
  659. it('should reject the promise if the kernel is dead', async () => {
  660. const options: KernelMessage.ICompleteRequest = {
  661. code: 'hello',
  662. cursor_pos: 4
  663. };
  664. const tester = new KernelTester();
  665. const kernel = await tester.start();
  666. // Create a promise that resolves when the kernel's status changes to dead
  667. const dead = testEmission(kernel.statusChanged, {
  668. find: () => kernel.status === 'dead'
  669. });
  670. tester.sendStatus(UUID.uuid4(), 'dead');
  671. await dead;
  672. await expectFailure(kernel.requestComplete(options), 'Kernel is dead');
  673. tester.dispose();
  674. });
  675. });
  676. describe('#requestInspect()', () => {
  677. it('should resolve the promise', async () => {
  678. const options: KernelMessage.IInspectRequest = {
  679. code: 'hello',
  680. cursor_pos: 4,
  681. detail_level: 0
  682. };
  683. await defaultKernel.requestInspect(options);
  684. });
  685. });
  686. describe('#requestIsComplete()', () => {
  687. it('should resolve the promise', async () => {
  688. const options: KernelMessage.IIsCompleteRequest = {
  689. code: 'hello'
  690. };
  691. await defaultKernel.requestIsComplete(options);
  692. });
  693. });
  694. describe('#requestHistory()', () => {
  695. it('should resolve the promise', async () => {
  696. const options: KernelMessage.IHistoryRequest = {
  697. output: true,
  698. raw: true,
  699. hist_access_type: 'search',
  700. session: 0,
  701. start: 1,
  702. stop: 2,
  703. n: 1,
  704. pattern: '*',
  705. unique: true
  706. };
  707. await defaultKernel.requestHistory(options);
  708. });
  709. });
  710. describe('#sendInputReply()', () => {
  711. it('should send an input_reply message', async () => {
  712. const tester = new KernelTester();
  713. const kernel = await tester.start();
  714. const done = new PromiseDelegate<void>();
  715. tester.onMessage(msg => {
  716. expect(msg.header.msg_type).to.equal('input_reply');
  717. done.resolve(null);
  718. });
  719. kernel.sendInputReply({ value: 'test' });
  720. await done;
  721. await tester.shutdown();
  722. tester.dispose();
  723. });
  724. it('should fail if the kernel is dead', async () => {
  725. const tester = new KernelTester();
  726. const kernel = await tester.start();
  727. // Create a promise that resolves when the kernel's status changes to dead
  728. const dead = testEmission(kernel.statusChanged, {
  729. find: () => kernel.status === 'dead'
  730. });
  731. tester.sendStatus(UUID.uuid4(), 'dead');
  732. await dead;
  733. expect(() => {
  734. kernel.sendInputReply({ value: 'test' });
  735. }).to.throw(/Kernel is dead/);
  736. tester.dispose();
  737. });
  738. });
  739. describe('#requestExecute()', () => {
  740. it('should send and handle incoming messages', async () => {
  741. let newMsg: KernelMessage.IMessage;
  742. const content: KernelMessage.IExecuteRequest = {
  743. code: 'test',
  744. silent: false,
  745. store_history: true,
  746. user_expressions: {},
  747. allow_stdin: false,
  748. stop_on_error: false
  749. };
  750. const options: KernelMessage.IOptions = {
  751. msgType: 'custom',
  752. channel: 'shell',
  753. username: defaultKernel.username,
  754. session: defaultKernel.clientId
  755. };
  756. let future: Kernel.IFuture;
  757. const tester = new KernelTester();
  758. tester.onMessage(msg => {
  759. expect(msg.channel).to.equal('shell');
  760. // send a reply
  761. options.channel = 'shell';
  762. newMsg = KernelMessage.createMessage(options);
  763. newMsg.parent_header = msg.header;
  764. tester.send(newMsg);
  765. future.onReply = () => {
  766. // trigger onStdin
  767. options.channel = 'stdin';
  768. newMsg = KernelMessage.createMessage(options);
  769. newMsg.parent_header = msg.header;
  770. tester.send(newMsg);
  771. };
  772. future.onStdin = () => {
  773. // trigger onIOPub with a 'stream' message
  774. options.channel = 'iopub';
  775. options.msgType = 'stream';
  776. const streamContent: JSONObject = { name: 'stdout', text: '' };
  777. newMsg = KernelMessage.createMessage(options, streamContent);
  778. newMsg.parent_header = msg.header;
  779. tester.send(newMsg);
  780. };
  781. future.onIOPub = ioMsg => {
  782. if (ioMsg.header.msg_type === 'stream') {
  783. // trigger onDone
  784. options.msgType = 'status';
  785. newMsg = KernelMessage.createMessage(options, {
  786. execution_state: 'idle'
  787. });
  788. newMsg.parent_header = msg.header;
  789. tester.send(newMsg);
  790. }
  791. };
  792. });
  793. const kernel = await tester.start();
  794. future = kernel.requestExecute(content);
  795. await future.done;
  796. expect(future.isDisposed).to.equal(true);
  797. await tester.shutdown();
  798. tester.dispose();
  799. });
  800. it('should not dispose of KernelFuture when disposeOnDone=false', async () => {
  801. const options: KernelMessage.IExecuteRequest = {
  802. code: 'test',
  803. silent: false,
  804. store_history: true,
  805. user_expressions: {},
  806. allow_stdin: false,
  807. stop_on_error: false
  808. };
  809. const future = defaultKernel.requestExecute(options, false);
  810. await future.done;
  811. expect(future.isDisposed).to.equal(false);
  812. future.dispose();
  813. expect(future.isDisposed).to.equal(true);
  814. });
  815. });
  816. describe('#checkExecuteMetadata()', () => {
  817. it('should accept cell metadata as part of request', async () => {
  818. let options: KernelMessage.IExecuteRequest = {
  819. code: 'test',
  820. silent: false,
  821. store_history: true,
  822. user_expressions: {},
  823. allow_stdin: false,
  824. stop_on_error: false
  825. };
  826. let metadata = { cellId: 'test' };
  827. let future = defaultKernel.requestExecute(options, false, metadata);
  828. await future.done;
  829. expect((future.msg.metadata = metadata));
  830. });
  831. });
  832. describe('#registerMessageHook()', () => {
  833. it('should have the most recently registered hook run first', async () => {
  834. const options: KernelMessage.IExecuteRequest = {
  835. code: 'test',
  836. silent: false,
  837. store_history: true,
  838. user_expressions: {},
  839. allow_stdin: false,
  840. stop_on_error: false
  841. };
  842. const calls: string[] = [];
  843. let future: Kernel.IFuture;
  844. let kernel: Kernel.IKernel;
  845. const tester = new KernelTester();
  846. tester.onMessage(message => {
  847. // send a reply
  848. const parentHeader = message.header;
  849. const msg = createMsg('shell', parentHeader);
  850. tester.send(msg);
  851. future.onReply = () => {
  852. // trigger onIOPub with a 'stream' message
  853. const msgStream = createMsg('iopub', parentHeader);
  854. msgStream.header.msg_type = 'stream';
  855. msgStream.content = { name: 'stdout', text: 'foo' };
  856. tester.send(msgStream);
  857. // trigger onDone
  858. const msgDone = createMsg('iopub', parentHeader);
  859. msgDone.header.msg_type = 'status';
  860. (msgDone as KernelMessage.IStatusMsg).content.execution_state =
  861. 'idle';
  862. tester.send(msgDone);
  863. };
  864. kernel.registerMessageHook(parentHeader.msg_id, async msg => {
  865. // Make this hook call asynchronous
  866. await calls.push('last');
  867. return true;
  868. });
  869. kernel.registerMessageHook(parentHeader.msg_id, msg => {
  870. calls.push('first');
  871. // not returning should also continue handling
  872. return void 0;
  873. });
  874. future.onIOPub = () => {
  875. calls.push('iopub');
  876. };
  877. });
  878. kernel = await tester.start();
  879. future = kernel.requestExecute(options, false);
  880. await future.done;
  881. // the last hook was called for the stream and the status message.
  882. expect(calls).to.deep.equal([
  883. 'first',
  884. 'last',
  885. 'iopub',
  886. 'first',
  887. 'last',
  888. 'iopub'
  889. ]);
  890. await tester.shutdown();
  891. tester.dispose();
  892. });
  893. it('should abort processing if a hook returns false, but the done logic should still work', async () => {
  894. const options: KernelMessage.IExecuteRequest = {
  895. code: 'test',
  896. silent: false,
  897. store_history: true,
  898. user_expressions: {},
  899. allow_stdin: false,
  900. stop_on_error: false
  901. };
  902. const calls: string[] = [];
  903. const tester = new KernelTester();
  904. let future: Kernel.IFuture;
  905. let kernel: Kernel.IKernel;
  906. tester.onMessage(message => {
  907. // send a reply
  908. const parentHeader = message.header;
  909. const msg = createMsg('shell', parentHeader);
  910. tester.send(msg);
  911. future.onReply = () => {
  912. // trigger onIOPub with a 'stream' message
  913. const msgStream = createMsg('iopub', parentHeader);
  914. msgStream.header.msg_type = 'stream';
  915. msgStream.content = { name: 'stdout', text: 'foo' };
  916. tester.send(msgStream);
  917. // trigger onDone
  918. const msgDone = createMsg('iopub', parentHeader);
  919. msgDone.header.msg_type = 'status';
  920. (msgDone as KernelMessage.IStatusMsg).content.execution_state =
  921. 'idle';
  922. tester.send(msgDone);
  923. };
  924. kernel.registerMessageHook(parentHeader.msg_id, msg => {
  925. calls.push('last');
  926. return true;
  927. });
  928. kernel.registerMessageHook(parentHeader.msg_id, msg => {
  929. calls.push('first');
  930. return false;
  931. });
  932. future.onIOPub = async () => {
  933. await calls.push('iopub');
  934. };
  935. });
  936. kernel = await tester.start();
  937. future = kernel.requestExecute(options, false);
  938. await future.done;
  939. // the last hook was called for the stream and the status message.
  940. expect(calls).to.deep.equal(['first', 'first']);
  941. await tester.shutdown();
  942. tester.dispose();
  943. });
  944. it('should process additions on the next run', async () => {
  945. const options: KernelMessage.IExecuteRequest = {
  946. code: 'test',
  947. silent: false,
  948. store_history: true,
  949. user_expressions: {},
  950. allow_stdin: false,
  951. stop_on_error: false
  952. };
  953. const calls: string[] = [];
  954. const tester = new KernelTester();
  955. let future: Kernel.IFuture;
  956. let kernel: Kernel.IKernel;
  957. tester.onMessage(message => {
  958. // send a reply
  959. const parentHeader = message.header;
  960. const msg = createMsg('shell', parentHeader);
  961. tester.send(msg);
  962. future.onReply = () => {
  963. // trigger onIOPub with a 'stream' message
  964. const msgStream = createMsg('iopub', parentHeader);
  965. msgStream.header.msg_type = 'stream';
  966. msgStream.content = { name: 'stdout', text: 'foo' };
  967. tester.send(msgStream);
  968. // trigger onDone
  969. const msgDone = createMsg('iopub', parentHeader);
  970. msgDone.header.msg_type = 'status';
  971. (msgDone as KernelMessage.IStatusMsg).content.execution_state =
  972. 'idle';
  973. tester.send(msgDone);
  974. };
  975. kernel.registerMessageHook(parentHeader.msg_id, msg => {
  976. calls.push('last');
  977. kernel.registerMessageHook(parentHeader.msg_id, msg => {
  978. calls.push('first');
  979. return true;
  980. });
  981. return true;
  982. });
  983. future.onIOPub = () => {
  984. calls.push('iopub');
  985. };
  986. });
  987. kernel = await tester.start();
  988. future = kernel.requestExecute(options, false);
  989. await future.done;
  990. expect(calls).to.deep.equal(['last', 'iopub', 'first', 'last', 'iopub']);
  991. await tester.shutdown();
  992. tester.dispose();
  993. });
  994. it('should deactivate a hook immediately on removal', async () => {
  995. const options: KernelMessage.IExecuteRequest = {
  996. code: 'test',
  997. silent: false,
  998. store_history: true,
  999. user_expressions: {},
  1000. allow_stdin: false,
  1001. stop_on_error: false
  1002. };
  1003. const calls: string[] = [];
  1004. const tester = new KernelTester();
  1005. let future: Kernel.IFuture;
  1006. let kernel: Kernel.IKernel;
  1007. tester.onMessage(message => {
  1008. // send a reply
  1009. const parentHeader = message.header;
  1010. const msg = createMsg('shell', parentHeader);
  1011. tester.send(msg);
  1012. future.onReply = () => {
  1013. // trigger onIOPub with a 'stream' message
  1014. const msgStream = createMsg('iopub', parentHeader);
  1015. msgStream.header.msg_type = 'stream';
  1016. msgStream.content = { name: 'stdout', text: 'foo' };
  1017. tester.send(msgStream);
  1018. // trigger onDone
  1019. const msgDone = createMsg('iopub', parentHeader);
  1020. msgDone.header.msg_type = 'status';
  1021. (msgDone as KernelMessage.IStatusMsg).content.execution_state =
  1022. 'idle';
  1023. tester.send(msgDone);
  1024. };
  1025. const toDelete = (msg: KernelMessage.IIOPubMessage) => {
  1026. calls.push('delete');
  1027. return true;
  1028. };
  1029. kernel.registerMessageHook(parentHeader.msg_id, toDelete);
  1030. kernel.registerMessageHook(parentHeader.msg_id, msg => {
  1031. if (calls.length > 0) {
  1032. // delete the hook the second time around
  1033. kernel.removeMessageHook(parentHeader.msg_id, toDelete);
  1034. }
  1035. calls.push('first');
  1036. return true;
  1037. });
  1038. future.onIOPub = () => {
  1039. calls.push('iopub');
  1040. };
  1041. });
  1042. kernel = await tester.start();
  1043. future = kernel.requestExecute(options, false);
  1044. await future.done;
  1045. expect(calls).to.deep.equal([
  1046. 'first',
  1047. 'delete',
  1048. 'iopub',
  1049. 'first',
  1050. 'iopub'
  1051. ]);
  1052. await tester.shutdown();
  1053. tester.dispose();
  1054. });
  1055. });
  1056. describe('handles messages asynchronously', () => {
  1057. // TODO: Also check that messages are canceled appropriately. In particular, when
  1058. // a kernel is restarted, then a message is sent for a comm open from the
  1059. // old session, the comm open should be canceled.
  1060. it('should run handlers in order', async () => {
  1061. const options: KernelMessage.IExecuteRequest = {
  1062. code: 'test',
  1063. silent: false,
  1064. store_history: true,
  1065. user_expressions: {},
  1066. allow_stdin: true,
  1067. stop_on_error: false
  1068. };
  1069. const tester = new KernelTester();
  1070. const kernel = await tester.start();
  1071. const future = kernel.requestExecute(options, false);
  1072. // The list of emissions from the anyMessage signal.
  1073. const msgSignal: string[][] = [];
  1074. const msgSignalExpected: string[][] = [];
  1075. // The list of message processing calls
  1076. const calls: string[][] = [];
  1077. const callsExpected: string[][] = [];
  1078. function pushIopub(msgId: string) {
  1079. callsExpected.push([msgId, 'future hook a']);
  1080. callsExpected.push([msgId, 'future hook b']);
  1081. callsExpected.push([msgId, 'kernel hook a']);
  1082. callsExpected.push([msgId, 'kernel hook b']);
  1083. callsExpected.push([msgId, 'iopub']);
  1084. msgSignalExpected.push([msgId, 'iopub']);
  1085. }
  1086. function pushCommOpen(msgId: string) {
  1087. pushIopub(msgId);
  1088. callsExpected.push([msgId, 'comm open']);
  1089. }
  1090. function pushCommMsg(msgId: string) {
  1091. pushIopub(msgId);
  1092. callsExpected.push([msgId, 'comm msg']);
  1093. }
  1094. function pushCommClose(msgId: string) {
  1095. pushIopub(msgId);
  1096. callsExpected.push([msgId, 'comm close']);
  1097. }
  1098. function pushStdin(msgId: string) {
  1099. callsExpected.push([msgId, 'stdin']);
  1100. msgSignalExpected.push([msgId, 'stdin']);
  1101. }
  1102. function pushReply(msgId: string) {
  1103. callsExpected.push([msgId, 'reply']);
  1104. msgSignalExpected.push([msgId, 'shell']);
  1105. }
  1106. const anyMessageDone = new PromiseDelegate();
  1107. const handlingBlock = new PromiseDelegate();
  1108. tester.onMessage(message => {
  1109. tester.onMessage(() => {
  1110. return;
  1111. });
  1112. tester.parentHeader = message.header;
  1113. pushIopub(tester.sendStatus('busy', 'busy'));
  1114. pushIopub(tester.sendStream('stdout', { name: 'stdout', text: 'foo' }));
  1115. pushCommOpen(
  1116. tester.sendCommOpen('comm open', {
  1117. target_name: 'commtarget',
  1118. comm_id: 'commid',
  1119. data: {}
  1120. })
  1121. );
  1122. pushIopub(
  1123. tester.sendDisplayData('display 1', { data: {}, metadata: {} })
  1124. );
  1125. pushCommMsg(
  1126. tester.sendCommMsg('comm 1', { comm_id: 'commid', data: {} })
  1127. );
  1128. pushCommMsg(
  1129. tester.sendCommMsg('comm 2', { comm_id: 'commid', data: {} })
  1130. );
  1131. pushCommClose(
  1132. tester.sendCommClose('comm close', { comm_id: 'commid', data: {} })
  1133. );
  1134. pushStdin(
  1135. tester.sendInputRequest('stdin', { prompt: '', password: false })
  1136. );
  1137. pushIopub(
  1138. tester.sendDisplayData('display 2', {
  1139. data: {},
  1140. metadata: {},
  1141. transient: { display_id: 'displayid' }
  1142. })
  1143. );
  1144. pushIopub(
  1145. tester.sendUpdateDisplayData('update display', {
  1146. data: {},
  1147. metadata: {},
  1148. transient: { display_id: 'displayid' }
  1149. })
  1150. );
  1151. pushIopub(
  1152. tester.sendExecuteResult('execute result', {
  1153. execution_count: 1,
  1154. data: {},
  1155. metadata: {}
  1156. })
  1157. );
  1158. pushIopub(tester.sendStatus('idle', 'idle'));
  1159. pushReply(tester.sendExecuteReply('execute reply', {}));
  1160. tester.parentHeader = undefined;
  1161. });
  1162. kernel.anyMessage.connect((k, args) => {
  1163. msgSignal.push([args.msg.header.msg_id, args.msg.channel]);
  1164. if (args.msg.header.msg_id === 'execute reply') {
  1165. anyMessageDone.resolve(undefined);
  1166. }
  1167. });
  1168. kernel.registerMessageHook(future.msg.header.msg_id, async msg => {
  1169. // Make this hook call asynchronous
  1170. await calls.push([msg.header.msg_id, 'kernel hook b']);
  1171. return true;
  1172. });
  1173. kernel.registerMessageHook(future.msg.header.msg_id, async msg => {
  1174. calls.push([msg.header.msg_id, 'kernel hook a']);
  1175. return true;
  1176. });
  1177. kernel.registerCommTarget('commtarget', async (comm, msg) => {
  1178. await calls.push([msg.header.msg_id, 'comm open']);
  1179. comm.onMsg = async msg => {
  1180. await calls.push([msg.header.msg_id, 'comm msg']);
  1181. };
  1182. comm.onClose = async msg => {
  1183. await calls.push([msg.header.msg_id, 'comm close']);
  1184. };
  1185. });
  1186. future.registerMessageHook(async msg => {
  1187. await calls.push([msg.header.msg_id, 'future hook b']);
  1188. return true;
  1189. });
  1190. future.registerMessageHook(async msg => {
  1191. // Delay processing until after we've checked the anyMessage results.
  1192. await handlingBlock.promise;
  1193. await calls.push([msg.header.msg_id, 'future hook a']);
  1194. return true;
  1195. });
  1196. future.onIOPub = async msg => {
  1197. await calls.push([msg.header.msg_id, 'iopub']);
  1198. };
  1199. future.onStdin = async msg => {
  1200. await calls.push([msg.header.msg_id, 'stdin']);
  1201. };
  1202. future.onReply = async msg => {
  1203. await calls.push([msg.header.msg_id, 'reply']);
  1204. };
  1205. // Give the kernel time to receive and queue up the messages.
  1206. await anyMessageDone.promise;
  1207. // At this point, the synchronous anyMessage signal should have been
  1208. // emitted for every message, but no actual message handling should have
  1209. // happened.
  1210. expect(msgSignal).to.deep.equal(msgSignalExpected);
  1211. expect(calls).to.deep.equal([]);
  1212. // Release the lock on message processing.
  1213. handlingBlock.resolve(undefined);
  1214. await future.done;
  1215. expect(calls).to.deep.equal(callsExpected);
  1216. await tester.shutdown();
  1217. tester.dispose();
  1218. });
  1219. });
  1220. });