Browse Source

Backport PR #11015: Make Test Server Configurable (#11127)

Co-authored-by: Frédéric Collonval <fcollonval@gmail.com>
MeeseeksMachine 3 years ago
parent
commit
6f0dbe9228

+ 1 - 0
testutils/package.json

@@ -51,6 +51,7 @@
     "@lumino/properties": "^1.2.3",
     "@lumino/signaling": "^1.4.3",
     "child_process": "~1.0.2",
+    "deepmerge": "^4.2.2",
     "fs-extra": "^9.0.1",
     "identity-obj-proxy": "^3.0.0",
     "jest": "^26.4.2",

+ 68 - 25
testutils/src/start_jupyter_server.ts

@@ -1,11 +1,12 @@
 // Copyright (c) Jupyter Development Team.
 
 import { ChildProcess, spawn } from 'child_process';
+import merge from 'deepmerge';
 import * as fs from 'fs';
 import * as path from 'path';
 
 import { PageConfig, URLExt } from '@jupyterlab/coreutils';
-import { PromiseDelegate, UUID } from '@lumino/coreutils';
+import { JSONObject, PromiseDelegate, UUID } from '@lumino/coreutils';
 import { sleep } from './common';
 
 /**
@@ -39,15 +40,15 @@ export class JupyterServer {
    *
    * @throws Error if another server is still running.
    */
-  async start(): Promise<string> {
+  async start(options: Partial<JupyterServer.IOptions> = {}): Promise<string> {
     if (Private.child !== null) {
       throw Error('Previous server was not disposed');
     }
     const startDelegate = new PromiseDelegate<string>();
 
     const env = {
-      JUPYTER_CONFIG_DIR: Private.handleConfig(),
-      JUPYTER_DATA_DIR: Private.handleData(),
+      JUPYTER_CONFIG_DIR: Private.handleConfig(options),
+      JUPYTER_DATA_DIR: Private.handleData(options),
       JUPYTER_RUNTIME_DIR: Private.mktempDir('jupyter_runtime'),
       IPYTHONDIR: Private.mktempDir('ipython'),
       PATH: process.env.PATH
@@ -114,6 +115,29 @@ export class JupyterServer {
   }
 }
 
+/**
+ * A namespace for JupyterServer static values.
+ */
+export namespace JupyterServer {
+  /**
+   * Options used to create a new JupyterServer instance.
+   */
+  export interface IOptions {
+    /**
+     * Additional Page Config values.
+     */
+    pageConfig: { [name: string]: string };
+    /**
+     * Additional traitlet config data.
+     */
+    configData: JSONObject;
+    /**
+     * Map of additional kernelspec names to kernel.json dictionaries
+     */
+    additionalKernelSpecs: JSONObject;
+  }
+}
+
 /**
  * A namespace for module private data.
  */
@@ -140,6 +164,7 @@ namespace Private {
     const specDir = path.join(dataDir, 'kernels', name);
     fs.mkdirSync(specDir, { recursive: true });
     fs.writeFileSync(path.join(specDir, 'kernel.json'), JSON.stringify(spec));
+    PageConfig.setOption(`__kernelSpec_${name}`, JSON.stringify(spec));
   }
 
   /**
@@ -200,12 +225,20 @@ namespace Private {
   /**
    * Handle configuration.
    */
-  export function handleConfig(): string {
+  export function handleConfig(
+    options: Partial<JupyterServer.IOptions>
+  ): string {
     // Set up configuration.
     const token = UUID.uuid4();
     PageConfig.setOption('token', token);
     PageConfig.setOption('terminalsAvailable', 'true');
 
+    if (options.pageConfig) {
+      Object.keys(options.pageConfig).forEach(key => {
+        PageConfig.setOption(key, options.pageConfig![key]);
+      });
+    }
+
     const configDir = mktempDir('config');
     const configPath = path.join(configDir, 'jupyter_server_config.json');
     const root_dir = createNotebookDir();
@@ -214,26 +247,30 @@ namespace Private {
     const user_settings_dir = mktempDir('settings');
     const workspaces_dir = mktempDir('workspaces');
 
-    const configData = {
-      LabApp: {
-        user_settings_dir,
-        workspaces_dir,
-        app_dir,
-        open_browser: false,
-        log_level: 'DEBUG'
-      },
-      ServerApp: {
-        token,
-        root_dir,
-        log_level: 'DEBUG'
-      },
-      MultiKernelManager: {
-        default_kernel_name: 'echo'
+    const configData = merge(
+      {
+        LabApp: {
+          user_settings_dir,
+          workspaces_dir,
+          app_dir,
+          open_browser: false,
+          log_level: 'DEBUG'
+        },
+        ServerApp: {
+          token,
+          root_dir,
+          log_level: 'DEBUG'
+        },
+        MultiKernelManager: {
+          default_kernel_name: 'echo'
+        },
+        KernelManager: {
+          shutdown_wait_time: 1.0
+        }
       },
-      KernelManager: {
-        shutdown_wait_time: 1.0
-      }
-    };
+      options.configData || {}
+    );
+    PageConfig.setOption('__configData', JSON.stringify(configData));
     fs.writeFileSync(configPath, JSON.stringify(configData));
     return configDir;
   }
@@ -241,7 +278,7 @@ namespace Private {
   /**
    * Handle data.
    */
-  export function handleData(): string {
+  export function handleData(options: Partial<JupyterServer.IOptions>): string {
     const dataDir = mktempDir('data');
 
     // Install custom specs.
@@ -262,6 +299,12 @@ namespace Private {
       display_name: 'Python 3',
       language: 'python'
     });
+
+    if (options.additionalKernelSpecs) {
+      Object.keys(options.additionalKernelSpecs).forEach(key => {
+        installSpec(dataDir, key, options.additionalKernelSpecs![key]);
+      });
+    }
     return dataDir;
   }
 

+ 34 - 1
testutils/test/start_jupyter_server.spec.ts

@@ -4,7 +4,7 @@
 const fetch = require('node-fetch');
 
 import { JupyterServer } from '../src';
-import { URLExt } from '@jupyterlab/coreutils';
+import { PageConfig, URLExt } from '@jupyterlab/coreutils';
 
 describe('JupyterServer', () => {
   it('should start the server', async () => {
@@ -14,4 +14,37 @@ describe('JupyterServer', () => {
     await fetch(URLExt.join(url, 'api'));
     await server.shutdown();
   });
+
+  it('should accept options', async () => {
+    jest.setTimeout(20000);
+    const pageConfig = { foo: 'bar', fizz: 'buzz' };
+    const configData = {
+      FakeTrait: { fake_prop: 1 },
+      OtherTrait: { other_prop: 'hello' },
+      KernelManager: {
+        shutdown_wait_time: 1.11
+      }
+    };
+    const additionalKernelSpecs = {
+      foo: {
+        argv: ['python', '-m', 'ipykernel_launcher', '-f', '{connection_file}'],
+        display_name: 'Test Python',
+        language: 'python'
+      }
+    };
+    const server = new JupyterServer();
+    const url = await server.start({
+      pageConfig,
+      configData,
+      additionalKernelSpecs
+    });
+    await fetch(URLExt.join(url, 'api'));
+    expect(PageConfig.getOption('foo')).toEqual('bar');
+    expect(PageConfig.getOption('fizz')).toEqual('buzz');
+    expect(PageConfig.getOption('__configData')).toContain('FakeTrait');
+    expect(PageConfig.getOption('__configData')).toContain('OtherTrait');
+    expect(PageConfig.getOption('__configData')).toContain('1.11');
+    expect(PageConfig.getOption('__kernelSpec_foo')).toContain('Test Python');
+    await expect(server.shutdown()).resolves.not.toThrow();
+  });
 });