comm.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. // Copyright (c) Jupyter Development Team.
  2. import 'jest';
  3. import { PromiseDelegate } from '@lumino/coreutils';
  4. import { KernelMessage, Kernel, KernelManager } from '../../src';
  5. import {
  6. isFulfilled,
  7. JupyterServer,
  8. flakyIt as it
  9. } from '@jupyterlab/testutils';
  10. import { init } from '../utils';
  11. // Initialize fetch override.
  12. init();
  13. const BLIP = `
  14. from ipykernel.comm import Comm
  15. comm = Comm(target_name="test", data="hello")
  16. comm.send(data="hello")
  17. comm.close("goodbye")
  18. `;
  19. const RECEIVE = `
  20. def create_recv(comm):
  21. def _recv(msg):
  22. data = msg["content"]["data"]
  23. if data == "quit":
  24. comm.close("goodbye")
  25. else:
  26. comm.send(data)
  27. return _recv
  28. `;
  29. const SEND = `
  30. ${RECEIVE}
  31. from ipykernel.comm import Comm
  32. comm = Comm(target_name="test", data="hello")
  33. comm.send(data="hello")
  34. comm.on_msg(create_recv(comm))
  35. `;
  36. const TARGET = `
  37. ${RECEIVE}
  38. def target_func(comm, msg):
  39. comm.on_msg(create_recv(comm))
  40. get_ipython().kernel.comm_manager.register_target("test", target_func)
  41. `;
  42. const server = new JupyterServer();
  43. beforeAll(async () => {
  44. await server.start();
  45. });
  46. afterAll(async () => {
  47. await server.shutdown();
  48. });
  49. describe('jupyter.services - Comm', () => {
  50. let kernelManager: KernelManager;
  51. let kernel: Kernel.IKernelConnection;
  52. beforeAll(async () => {
  53. jest.setTimeout(20000);
  54. kernelManager = new KernelManager();
  55. kernel = await kernelManager.startNew({ name: 'ipython' });
  56. });
  57. afterEach(() => {
  58. // A no-op comm target.
  59. kernel.registerCommTarget('test', (comm, msg) => {
  60. /* no op */
  61. });
  62. });
  63. afterAll(async () => {
  64. await kernel.shutdown();
  65. });
  66. describe('Kernel', () => {
  67. describe('#createComm()', () => {
  68. it('should create an instance of IComm', () => {
  69. const comm = kernel.createComm('test');
  70. expect(comm.targetName).toBe('test');
  71. expect(typeof comm.commId).toBe('string');
  72. });
  73. it('should use the given id', () => {
  74. const comm = kernel.createComm('test', '1234');
  75. expect(comm.targetName).toBe('test');
  76. expect(comm.commId).toBe('1234');
  77. });
  78. it('should throw an error if there is an existing comm', () => {
  79. expect(() => kernel.createComm('test', '1234')).toThrowError();
  80. });
  81. it('should throw an error when the kernel does not handle comms', async () => {
  82. const kernel2 = await kernelManager.startNew(
  83. { name: 'ipython' },
  84. { handleComms: false }
  85. );
  86. expect(kernel2.handleComms).toBe(false);
  87. expect(() => kernel2.createComm('test', '1234')).toThrowError();
  88. });
  89. });
  90. describe('#hasComm()', () => {
  91. it('should test if there is a registered comm', () => {
  92. expect(kernel.hasComm('test comm')).toBe(false);
  93. const comm = kernel.createComm('test', 'test comm');
  94. expect(kernel.hasComm('test comm')).toBe(true);
  95. comm.dispose();
  96. expect(kernel.hasComm('test comm')).toBe(false);
  97. });
  98. });
  99. describe('#registerCommTarget()', () => {
  100. it('should call the provided callback', async () => {
  101. const promise = new PromiseDelegate<
  102. [Kernel.IComm, KernelMessage.ICommOpenMsg]
  103. >();
  104. const hook = (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => {
  105. promise.resolve([comm, msg]);
  106. };
  107. kernel.registerCommTarget('test', hook);
  108. // Request the comm creation.
  109. await kernel.requestExecute({ code: SEND }, true).done;
  110. // Get the comm.
  111. const [comm, msg] = await promise.promise;
  112. expect(msg.content.data).toBe('hello');
  113. // Clean up
  114. kernel.removeCommTarget('test', hook);
  115. comm.dispose();
  116. });
  117. it('should do nothing if the kernel does not handle comms', async () => {
  118. const promise = new PromiseDelegate<
  119. [Kernel.IComm, KernelMessage.ICommOpenMsg]
  120. >();
  121. const hook = (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => {
  122. promise.resolve([comm, msg]);
  123. };
  124. const kernel2 = await kernelManager.startNew(
  125. { name: 'ipython' },
  126. { handleComms: false }
  127. );
  128. kernel2.registerCommTarget('test', hook);
  129. // Request the comm creation.
  130. await kernel2.requestExecute({ code: SEND }, true).done;
  131. // The promise above should not be fulfilled, even after a short delay.
  132. expect(await isFulfilled(promise.promise, 300)).toBe(false);
  133. // The kernel comm has not been closed - we just completely ignored it.
  134. const reply = await kernel2.requestExecute(
  135. { code: `assert comm._closed is False` },
  136. true
  137. ).done;
  138. // If the assert was false, we would get an 'error' status
  139. expect(reply!.content.status).toBe('ok');
  140. // Clean up
  141. kernel2.removeCommTarget('test', hook);
  142. });
  143. });
  144. describe('#commInfo()', () => {
  145. it('should get the comm info', async () => {
  146. const commPromise = new PromiseDelegate<Kernel.IComm>();
  147. const hook = (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => {
  148. commPromise.resolve(comm);
  149. };
  150. kernel.registerCommTarget('test', hook);
  151. // Request the comm creation.
  152. await kernel.requestExecute({ code: SEND }, true).done;
  153. // Get the comm.
  154. const comm = await commPromise.promise;
  155. // Ask the kernel for the list of current comms.
  156. const msg = await kernel.requestCommInfo({});
  157. if (msg.content.status !== 'ok') {
  158. throw new Error('Message error');
  159. }
  160. // Test to make sure the comm we just created is listed.
  161. const comms = msg.content.comms;
  162. expect(comms[comm.commId].target_name).toBe('test');
  163. // Clean up
  164. kernel.removeCommTarget('test', hook);
  165. comm.dispose();
  166. });
  167. it('should allow an optional target name', async () => {
  168. await kernel.requestExecute({ code: SEND }, true).done;
  169. const msg = await kernel.requestCommInfo({ target_name: 'test' });
  170. if (msg.content.status !== 'ok') {
  171. throw new Error('Message error');
  172. }
  173. const comms = msg.content.comms;
  174. for (const id in comms) {
  175. expect(comms[id].target_name).toBe('test');
  176. }
  177. });
  178. });
  179. describe('#isDisposed', () => {
  180. it('should be true after we dispose of the comm', () => {
  181. const comm = kernel.createComm('test');
  182. expect(comm.isDisposed).toBe(false);
  183. comm.dispose();
  184. expect(comm.isDisposed).toBe(true);
  185. });
  186. it('should be safe to call multiple times', () => {
  187. const comm = kernel.createComm('test');
  188. expect(comm.isDisposed).toBe(false);
  189. expect(comm.isDisposed).toBe(false);
  190. comm.dispose();
  191. expect(comm.isDisposed).toBe(true);
  192. expect(comm.isDisposed).toBe(true);
  193. });
  194. });
  195. describe('#dispose()', () => {
  196. it('should dispose of the resources held by the comm', () => {
  197. const comm = kernel.createComm('foo');
  198. comm.dispose();
  199. expect(comm.isDisposed).toBe(true);
  200. });
  201. });
  202. });
  203. describe('IComm', () => {
  204. let comm: Kernel.IComm;
  205. beforeEach(() => {
  206. comm = kernel.createComm('test');
  207. });
  208. describe('#id', () => {
  209. it('should be a string', () => {
  210. expect(typeof comm.commId).toBe('string');
  211. });
  212. });
  213. describe('#name', () => {
  214. it('should be a string', () => {
  215. expect(comm.targetName).toBe('test');
  216. });
  217. });
  218. describe('#onClose', () => {
  219. it('should be readable and writable function', async () => {
  220. expect(comm.onClose).toBeUndefined();
  221. let called = false;
  222. comm.onClose = msg => {
  223. called = true;
  224. };
  225. await comm.close().done;
  226. expect(called).toBe(true);
  227. });
  228. it('should be called when the server side closes', async () => {
  229. const promise = new PromiseDelegate<void>();
  230. kernel.registerCommTarget('test', (comm, msg) => {
  231. comm.onClose = data => {
  232. promise.resolve(void 0);
  233. };
  234. comm.send('quit');
  235. });
  236. await kernel.requestExecute({ code: SEND }, true).done;
  237. await promise.promise;
  238. });
  239. });
  240. describe('#onMsg', () => {
  241. it('should be readable and writable function', async () => {
  242. let called = false;
  243. comm.onMsg = msg => {
  244. called = true;
  245. };
  246. expect(typeof comm.onMsg).toBe('function');
  247. const msg = KernelMessage.createMessage({
  248. msgType: 'comm_msg',
  249. channel: 'iopub',
  250. username: kernel.username,
  251. session: kernel.clientId,
  252. content: {
  253. comm_id: 'abcd',
  254. data: {}
  255. }
  256. });
  257. await comm.onMsg(msg);
  258. expect(called).toBe(true);
  259. });
  260. it('should be called when the server side sends a message', async () => {
  261. let called = false;
  262. kernel.registerCommTarget('test', (comm, msg) => {
  263. comm.onMsg = msg => {
  264. expect(msg.content.data).toBe('hello');
  265. called = true;
  266. };
  267. });
  268. await kernel.requestExecute({ code: BLIP }, true).done;
  269. expect(called).toBe(true);
  270. });
  271. });
  272. describe('#open()', () => {
  273. it('should send a message to the server', async () => {
  274. const future = kernel.requestExecute({ code: TARGET });
  275. await future.done;
  276. const encoder = new TextEncoder();
  277. const data = encoder.encode('hello');
  278. const future2 = comm.open({ foo: 'bar' }, { fizz: 'buzz' }, [
  279. data,
  280. data.buffer
  281. ]);
  282. await future2.done;
  283. });
  284. });
  285. describe('#send()', () => {
  286. it('should send a message to the server', async () => {
  287. await comm.open().done;
  288. const future = comm.send({ foo: 'bar' }, { fizz: 'buzz' });
  289. await future.done;
  290. });
  291. it('should pass through a buffers field', async () => {
  292. await comm.open().done;
  293. const future = comm.send({ buffers: 'bar' });
  294. await future.done;
  295. });
  296. });
  297. describe('#close()', () => {
  298. it('should send a message to the server', async () => {
  299. await comm.open().done;
  300. const encoder = new TextEncoder();
  301. const data = encoder.encode('hello');
  302. const future = comm.close({ foo: 'bar' }, {}, [data, data.buffer]);
  303. await future.done;
  304. });
  305. it('should trigger an onClose', async () => {
  306. await comm.open().done;
  307. let called = false;
  308. comm.onClose = (msg: KernelMessage.ICommCloseMsg) => {
  309. expect(msg.content.data).toEqual({ foo: 'bar' });
  310. called = true;
  311. };
  312. const future = comm.close({ foo: 'bar' });
  313. await future.done;
  314. expect(called).toBe(true);
  315. });
  316. it('should not send subsequent messages', async () => {
  317. await comm.open().done;
  318. await comm.close({ foo: 'bar' }).done;
  319. expect(() => {
  320. comm.send('test');
  321. }).toThrowError();
  322. });
  323. });
  324. });
  325. });