123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import {
- Dialog,
- ISessionContext,
- SessionContext,
- sessionContextDialogs
- } from '@jupyterlab/apputils';
- import {
- KernelManager,
- KernelSpecManager,
- SessionAPI,
- SessionManager
- } from '@jupyterlab/services';
- import {
- acceptDialog,
- dismissDialog,
- flakyIt as it,
- JupyterServer,
- testEmission
- } from '@jupyterlab/testutils';
- import { PromiseDelegate, UUID } from '@lumino/coreutils';
- const server = new JupyterServer();
- beforeAll(async () => {
- await server.start();
- });
- afterAll(async () => {
- await server.shutdown();
- });
- describe('@jupyterlab/apputils', () => {
- describe('SessionContext', () => {
- let kernelManager: KernelManager;
- let sessionManager: SessionManager;
- let specsManager: KernelSpecManager;
- let path = '';
- let sessionContext: SessionContext;
- beforeAll(async () => {
- jest.setTimeout(20000);
- kernelManager = new KernelManager();
- sessionManager = new SessionManager({ kernelManager });
- specsManager = new KernelSpecManager();
- await Promise.all([
- sessionManager.ready,
- kernelManager.ready,
- specsManager.ready
- ]);
- });
- beforeEach(async () => {
- Dialog.flush();
- path = UUID.uuid4();
- sessionContext = new SessionContext({
- path,
- sessionManager,
- specsManager,
- kernelPreference: { name: specsManager.specs?.default }
- });
- });
- afterEach(async () => {
- Dialog.flush();
- try {
- if (sessionContext.session) {
- await sessionContext.shutdown();
- }
- } catch (error) {
- console.warn('Session shutdown failed.', error);
- }
- sessionContext.dispose();
- });
- describe('#constructor()', () => {
- it('should create a session context', () => {
- expect(sessionContext).toBeInstanceOf(SessionContext);
- });
- });
- describe('#disposed', () => {
- it('should be emitted when the session context is disposed', async () => {
- sessionContext.kernelPreference = { canStart: false };
- await sessionContext.initialize();
- let called = false;
- sessionContext.disposed.connect((sender, args) => {
- expect(sender).toBe(sessionContext);
- expect(args).toBeUndefined();
- called = true;
- });
- sessionContext.dispose();
- expect(called).toBe(true);
- });
- });
- describe('#kernelChanged', () => {
- it('should be emitted when the kernel changes', async () => {
- let called = false;
- sessionContext.kernelChanged.connect(
- (sender, { oldValue, newValue }) => {
- if (oldValue !== null) {
- return;
- }
- expect(sender).toBe(sessionContext);
- expect(oldValue).toBeNull();
- expect(newValue).toBe(sessionContext.session?.kernel || null);
- called = true;
- }
- );
- await sessionContext.initialize();
- expect(called).toBe(true);
- });
- });
- describe('#sessionChanged', () => {
- it('should be emitted when the session changes', async () => {
- let called = false;
- sessionContext.sessionChanged.connect(
- (sender, { oldValue, newValue }) => {
- if (oldValue !== null) {
- return;
- }
- expect(sender).toBe(sessionContext);
- expect(oldValue).toBeNull();
- expect(newValue).toBe(sessionContext.session);
- called = true;
- }
- );
- await sessionContext.initialize();
- expect(called).toBe(true);
- });
- });
- describe('#statusChanged', () => {
- it('should be emitted when the status changes', async () => {
- let called = false;
- sessionContext.statusChanged.connect((sender, args) => {
- expect(sender).toBe(sessionContext);
- expect(typeof args).toBe('string');
- called = true;
- });
- await sessionContext.initialize();
- await sessionContext.session!.kernel!.info;
- expect(called).toBe(true);
- });
- });
- describe('#iopubMessage', () => {
- it('should be emitted for iopub kernel messages', async () => {
- let called = false;
- sessionContext.iopubMessage.connect((sender, args) => {
- expect(sender).toBe(sessionContext);
- called = true;
- });
- await sessionContext.initialize();
- await sessionContext.session!.kernel!.info;
- expect(called).toBe(true);
- });
- });
- describe('#propertyChanged', () => {
- it('should be emitted when a session path changes', async () => {
- let called = false;
- await sessionContext.initialize();
- sessionContext.propertyChanged.connect((sender, args) => {
- expect(sender).toBe(sessionContext);
- expect(args).toBe('path');
- called = true;
- });
- await sessionContext.session!.setPath('foo');
- expect(called).toBe(true);
- });
- it('should be emitted when a session name changes', async () => {
- let called = false;
- await sessionContext.initialize();
- sessionContext.propertyChanged.connect((sender, args) => {
- expect(sender).toBe(sessionContext);
- expect(args).toBe('name');
- called = true;
- });
- await sessionContext.session!.setName('foo');
- expect(called).toBe(true);
- });
- it('should be emitted when a session type changes', async () => {
- let called = false;
- await sessionContext.initialize();
- sessionContext.propertyChanged.connect((sender, args) => {
- expect(sender).toBe(sessionContext);
- expect(args).toBe('type');
- called = true;
- });
- await sessionContext.session!.setType('foo');
- expect(called).toBe(true);
- });
- });
- describe('#kernel', () => {
- it('should be the current kernel of the the session', async () => {
- expect(sessionContext.session?.kernel).toBeFalsy();
- await sessionContext.initialize();
- expect(sessionContext.session?.kernel).toBeTruthy();
- });
- });
- describe('#kernelPreference', () => {
- it('should be the kernel preference of the session', () => {
- const preference: ISessionContext.IKernelPreference = {
- name: 'foo',
- language: 'bar',
- id: '1234',
- shouldStart: true,
- canStart: true
- };
- sessionContext.kernelPreference = preference;
- expect(sessionContext.kernelPreference).toBe(preference);
- });
- });
- describe('#manager', () => {
- it('should be the session manager used by the session', () => {
- expect(sessionContext.sessionManager).toBe(sessionManager);
- });
- });
- describe('#initialize()', () => {
- it('should start the default kernel', async () => {
- await sessionContext.initialize();
- expect(sessionContext.session?.kernel?.name).toBe(
- specsManager.specs!.default
- );
- });
- it('should connect to an existing session on the path', async () => {
- const other = await sessionManager.startNew({
- name: '',
- path,
- type: 'test'
- });
- await sessionContext.initialize();
- expect(other.kernel?.id).toBeDefined();
- expect(other.kernel?.id).toBe(sessionContext.session?.kernel?.id);
- await other.shutdown();
- other.dispose();
- });
- it('should connect to an existing kernel', async () => {
- // Shut down and dispose the session so it can be re-instantiated.
- await sessionContext.shutdown();
- const other = await sessionManager.startNew({
- name: '',
- path: UUID.uuid4(),
- type: 'test'
- });
- const kernelPreference = { id: other.kernel!.id };
- sessionContext = new SessionContext({
- sessionManager,
- specsManager,
- kernelPreference
- });
- await sessionContext.initialize();
- expect(other.kernel?.id).toBeDefined();
- expect(other.kernel?.id).toBe(sessionContext.session?.kernel?.id);
- // We don't call other.shutdown() here because that
- // is handled by the afterEach() handler above.
- other.dispose();
- });
- it('should yield true if there is no distinct kernel to start', async () => {
- // Remove the kernel preference before initializing.
- sessionContext.kernelPreference = {};
- const result = await sessionContext.initialize();
- expect(result).toBe(true);
- });
- it('should be a no-op if the shouldStart kernelPreference is false', async () => {
- sessionContext.kernelPreference = { shouldStart: false };
- const result = await sessionContext.initialize();
- expect(result).toBe(false);
- expect(sessionContext.session?.kernel).toBeFalsy();
- });
- it('should be a no-op if the canStart kernelPreference is false', async () => {
- sessionContext.kernelPreference = { canStart: false };
- const result = await sessionContext.initialize();
- expect(result).toBe(false);
- expect(sessionContext.session?.kernel).toBeFalsy();
- });
- it('should handle an error during startup', async () => {
- // Give it a mock manager that errors on connectTo
- const mockManager = new SessionManager({ kernelManager });
- sessionContext = new SessionContext({
- path,
- sessionManager: mockManager,
- specsManager,
- kernelPreference: { name: specsManager.specs?.default }
- });
- (mockManager as any).running = () => {
- return [{ path }];
- };
- (mockManager as any).connectTo = () => {
- throw new Error('mock error');
- };
- let caught = false;
- const promise = sessionContext.initialize().catch(() => {
- caught = true;
- });
- await Promise.all([promise, acceptDialog()]);
- expect(caught).toBe(true);
- });
- });
- describe('#kernelDisplayName', () => {
- it('should be the display name of the current kernel', async () => {
- await sessionContext.initialize();
- const spec = await sessionContext.session!.kernel!.spec;
- expect(sessionContext.kernelDisplayName).toBe(spec!.display_name);
- });
- it('should display "No Kernel" when there is no kernel', async () => {
- sessionContext.kernelPreference = {
- canStart: false,
- shouldStart: false
- };
- expect(sessionContext.kernelDisplayName).toBe('No Kernel');
- });
- it('should display the pending kernel name when it looks like we are starting a kernel', async () => {
- sessionContext.kernelPreference = {
- autoStartDefault: true,
- canStart: true,
- shouldStart: true
- };
- expect(sessionContext.kernelDisplayName).toBe('Echo Kernel');
- });
- });
- describe('#kernelDisplayStatus', () => {
- it('should be the status of the current kernel if connected', async () => {
- await sessionContext.initialize();
- await sessionContext.session!.kernel!.info;
- expect(sessionContext.kernelDisplayStatus).toBe(
- sessionContext.session?.kernel?.status
- );
- });
- it('should be the connection status of the current kernel if not connected', async () => {
- await sessionContext.initialize();
- const reconnect = sessionContext.session!.kernel!.reconnect();
- expect(sessionContext.kernelDisplayStatus).toBe(
- sessionContext.session?.kernel?.connectionStatus
- );
- await reconnect;
- });
- it('should be "initializing" if it looks like we are trying to start a kernel', async () => {
- sessionContext.kernelPreference = {};
- expect(sessionContext.kernelDisplayStatus).toBe('initializing');
- });
- it('should be "idle" if there is no current kernel', async () => {
- await sessionContext.initialize();
- await sessionContext.shutdown();
- expect(sessionContext.kernelDisplayStatus).toBe('idle');
- });
- });
- describe('#isDisposed', () => {
- it('should test whether a client session has been disposed', () => {
- expect(sessionContext.isDisposed).toBe(false);
- sessionContext.dispose();
- expect(sessionContext.isDisposed).toBe(true);
- });
- });
- describe('#dispose()', () => {
- it('should dispose the resources held by the client session', () => {
- sessionContext.dispose();
- expect(sessionContext.isDisposed).toBe(true);
- sessionContext.dispose();
- expect(sessionContext.isDisposed).toBe(true);
- });
- it('should not shut down the session by default', async () => {
- await sessionContext.initialize();
- const id = sessionContext.session!.id;
- sessionContext.dispose();
- const sessions = await SessionAPI.listRunning();
- expect(sessions.find(s => s.id === id)).toBeTruthy();
- await SessionAPI.shutdownSession(id);
- });
- it('should shut down the session when shutdownOnDispose is true', async () => {
- sessionContext.kernelPreference = {
- ...sessionContext.kernelPreference,
- shutdownOnDispose: true
- };
- const delegate = new PromiseDelegate();
- await sessionContext.initialize();
- const id = sessionContext.session!.id;
- // Wait for the session to shut down.
- sessionContext.sessionManager.runningChanged.connect((_, sessions) => {
- if (!sessions.find(s => s.id === id)) {
- delegate.resolve(void 0);
- return;
- }
- });
- sessionContext.dispose();
- return delegate.promise;
- });
- });
- describe('#changeKernel()', () => {
- it('should change the current kernel', async () => {
- await sessionContext.initialize();
- const name = sessionContext.session?.kernel?.name;
- const id = sessionContext.session?.kernel?.id;
- const kernel = (await sessionContext.changeKernel({ name }))!;
- expect(kernel.id).not.toBe(id);
- expect(kernel.name).toBe(name);
- });
- it('should still work if called before fully initialized', async () => {
- const initPromise = sessionContext.initialize(); // Start but don't finish init.
- const name = 'echo';
- const kernelPromise = sessionContext.changeKernel({ name });
- let lastKernel = null;
- sessionContext.kernelChanged.connect(() => {
- lastKernel = sessionContext.session?.kernel;
- });
- const results = await Promise.all([kernelPromise, initPromise]);
- const kernel = results[0];
- const shouldSelect = results[1];
- expect(shouldSelect).toBe(false);
- expect(lastKernel).toBe(kernel);
- });
- it('should handle multiple requests', async () => {
- await sessionContext.initialize();
- const name = 'echo';
- const kernelPromise0 = sessionContext.changeKernel({ name });
- // The last launched kernel should win.
- const kernelPromise1 = sessionContext.changeKernel({ name });
- let lastKernel = null;
- sessionContext.kernelChanged.connect(() => {
- lastKernel = sessionContext.session?.kernel;
- });
- const results = await Promise.all([kernelPromise0, kernelPromise1]);
- // We can't know which of the two was launched first, so the result
- // could be either, just make sure it isn't the original kernel.
- expect([results[0], results[1]]).toContain(lastKernel);
- });
- });
- describe('#shutdown', () => {
- it('should kill the kernel and shut down the session', async () => {
- await sessionContext.initialize();
- expect(sessionContext.session?.kernel).toBeTruthy();
- await sessionContext.shutdown();
- expect(sessionContext.session?.kernel).toBeFalsy();
- });
- it('should handle a shutdown during startup', async () => {
- const initPromise = sessionContext.initialize(); // Start but don't finish init.
- const shutdownPromise = sessionContext.shutdown();
- const results = await Promise.all([initPromise, shutdownPromise]);
- expect(results[0]).toBe(false);
- expect(sessionContext.session).toBe(null);
- });
- });
- describe('.getDefaultKernel()', () => {
- it('should return null if no options are given', () => {
- expect(
- SessionContext.getDefaultKernel({
- specs: specsManager.specs,
- preference: {}
- })
- ).toBeNull();
- });
- it('should return a matching name', () => {
- const spec = specsManager.specs!.kernelspecs[
- specsManager.specs!.default
- ]!;
- expect(
- SessionContext.getDefaultKernel({
- specs: specsManager.specs,
- preference: { name: spec.name }
- })
- ).toBe(spec.name);
- });
- it('should return null if no match is found', () => {
- expect(
- SessionContext.getDefaultKernel({
- specs: specsManager.specs,
- preference: { name: 'foo' }
- })
- ).toBeNull();
- });
- it('should return a matching language', () => {
- const spec = specsManager.specs!.kernelspecs[
- specsManager.specs!.default
- ]!;
- const kernelspecs: any = {};
- kernelspecs[spec.name] = spec;
- expect(
- SessionContext.getDefaultKernel({
- specs: {
- default: spec.name,
- kernelspecs
- },
- preference: { language: spec.language }
- })
- ).toBe(spec.name);
- });
- it('should return null if a language matches twice', () => {
- const spec = specsManager.specs!.kernelspecs[
- specsManager.specs!.default
- ]!;
- const kernelspecs: any = {};
- kernelspecs['foo'] = spec;
- kernelspecs['bar'] = spec;
- expect(
- SessionContext.getDefaultKernel({
- specs: {
- default: spec.name,
- kernelspecs
- },
- preference: { language: spec.language }
- })
- ).toBeNull();
- });
- });
- describe('.sessionContextDialogs', () => {
- describe('#selectKernel()', () => {
- it('should select a kernel for the session', async () => {
- await sessionContext.initialize();
- const { id, name } = sessionContext?.session!.kernel!;
- const accept = acceptDialog();
- await sessionContextDialogs.selectKernel(sessionContext);
- await accept;
- const session = sessionContext?.session;
- expect(session!.kernel!.id).not.toBe(id);
- expect(session!.kernel!.name).toBe(name);
- });
- it('should keep the existing kernel if dismissed', async () => {
- await sessionContext.initialize();
- const { id, name } = sessionContext!.session!.kernel!;
- const dismiss = dismissDialog();
- await sessionContextDialogs.selectKernel(sessionContext);
- await dismiss;
- const session = sessionContext.session;
- expect(session!.kernel!.id).toBe(id);
- expect(session!.kernel!.name).toBe(name);
- });
- });
- describe('#restart()', () => {
- it('should restart if the user accepts the dialog', async () => {
- const emission = testEmission(sessionContext.statusChanged, {
- find: (_, args) => args === 'restarting'
- });
- await sessionContext.initialize();
- await sessionContext!.session?.kernel?.info;
- const restart = sessionContextDialogs.restart(sessionContext);
- await acceptDialog();
- expect(await restart).toBe(true);
- await emission;
- });
- it('should not restart if the user rejects the dialog', async () => {
- let called = false;
- await sessionContext.initialize();
- sessionContext.statusChanged.connect((sender, args) => {
- if (args === 'restarting') {
- called = true;
- }
- });
- const restart = sessionContextDialogs.restart(sessionContext);
- await dismissDialog();
- expect(await restart).toBe(false);
- expect(called).toBe(false);
- });
- it('should start the same kernel as the previously started kernel', async () => {
- await sessionContext.initialize();
- await sessionContext.shutdown();
- await sessionContextDialogs.restart(sessionContext);
- expect(sessionContext?.session?.kernel).toBeTruthy();
- });
- });
- });
- });
- });
|