Skip to content
This repository has been archived by the owner on Jan 26, 2022. It is now read-only.

Commit

Permalink
Merge pull request #457 from jtpio/kernelspec-debugger
Browse files Browse the repository at this point in the history
Use the kernel spec metadata['debugger'] to check if a kernel supports debugging
  • Loading branch information
afshin authored Jun 12, 2020
2 parents fc27b02 + ac89b36 commit beffb9e
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 20 deletions.
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@ const service: JupyterFrontEndPlugin<IDebugger> = {
id: '@jupyterlab/debugger:service',
autoStart: true,
provides: IDebugger,
activate: () => new DebuggerService()
activate: (app: JupyterFrontEnd) =>
new DebuggerService({ specsManager: app.serviceManager.kernelspecs })
};

/**
Expand Down
33 changes: 27 additions & 6 deletions src/service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { Session } from '@jupyterlab/services';
import { Session, KernelSpec } from '@jupyterlab/services';

import { IDisposable } from '@lumino/disposable';

Expand All @@ -25,14 +25,17 @@ import { VariablesModel } from './variables/model';
export class DebuggerService implements IDebugger, IDisposable {
/**
* Instantiate a new DebuggerService.
*
* @param options The instantiation options for a DebuggerService.
*/
constructor() {
constructor(options: DebuggerService.IOptions) {
// Avoids setting session with invalid client
// session should be set only when a notebook or
// a console get the focus.
// TODO: also checks that the notebook or console
// runs a kernel with debugging ability
this._session = null;
this._specsManager = options.specsManager;
this._model = new DebuggerModel();
}

Expand Down Expand Up @@ -129,14 +132,15 @@ export class DebuggerService implements IDebugger, IDisposable {
* @param connection The session connection.
*/
async isAvailable(connection: Session.ISessionConnection): Promise<boolean> {
await this._specsManager.ready;
const kernel = connection?.kernel;
if (!kernel) {
return false;
}
const info =
(((await kernel.info) as unknown) as IDebugger.ISession.IInfoReply) ??
null;
return !!(info?.debugger ?? false);
const name = kernel.name;
return !!(
this._specsManager.specs.kernelspecs[name].metadata?.['debugger'] ?? false
);
}

/**
Expand Down Expand Up @@ -623,11 +627,28 @@ export class DebuggerService implements IDebugger, IDisposable {
private _modelChanged = new Signal<IDebugger, IDebugger.IModel>(this);
private _eventMessage = new Signal<IDebugger, IDebugger.ISession.Event>(this);

private _specsManager: KernelSpec.IManager;

private _hashMethod: (code: string) => string;
private _tmpFilePrefix: string;
private _tmpFileSuffix: string;
}

/**
* A namespace for `DebuggerService` statics.
*/
export namespace DebuggerService {
/**
* Instantiation options for a `DebuggerService`.
*/
export interface IOptions {
/**
* The kernel specs manager.
*/
specsManager: KernelSpec.IManager;
}
}

/**
* A namespace for module private data.
*/
Expand Down
21 changes: 13 additions & 8 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ export class DebugSession implements IDebugger.ISession {
}

this._connection.iopubMessage.connect(this._handleEvent, this);

this._ready = new PromiseDelegate<void>();
const future = this.connection.kernel?.requestDebug({
type: 'request',
seq: 0,
command: 'debugInfo'
});
future.onReply = (msg: KernelMessage.IDebugReplyMsg): void => {
this._ready.resolve();
future.dispose();
};
}

/**
Expand Down Expand Up @@ -141,7 +152,7 @@ export class DebugSession implements IDebugger.ISession {
command: K,
args: IDebugger.ISession.Request[K]
): Promise<IDebugger.ISession.Response[K]> {
await this._ready();
await this._ready.promise;
const message = await this._sendDebugMessage({
type: 'request',
seq: this._seq++,
Expand Down Expand Up @@ -192,14 +203,8 @@ export class DebugSession implements IDebugger.ISession {
return reply.promise;
}

/**
* A promise that resolves when the kernel is ready.
*/
private _ready(): Promise<KernelMessage.IInfoReply> {
return this._connection?.kernel?.info;
}

private _seq = 0;
private _ready = new PromiseDelegate<void>();
private _connection: Session.ISessionConnection;
private _isDisposed = false;
private _isStarted = false;
Expand Down
5 changes: 4 additions & 1 deletion test/debugger.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {

import { JupyterServer } from '@jupyterlab/testutils';

import { KernelSpecManager } from '@jupyterlab/services';

import { CommandRegistry } from '@lumino/commands';

import { Debugger } from '../src/debugger';
Expand All @@ -28,7 +30,8 @@ afterAll(async () => {
});

describe('Debugger', () => {
const service = new DebuggerService();
const specsManager = new KernelSpecManager();
const service = new DebuggerService({ specsManager });
const registry = new CommandRegistry();
const factoryService = new CodeMirrorEditorFactory();
const mimeTypeService = new CodeMirrorMimeTypeService();
Expand Down
42 changes: 38 additions & 4 deletions test/service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Session } from '@jupyterlab/services';
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { Session, KernelSpecManager, KernelSpec } from '@jupyterlab/services';

import {
createSession,
signalToPromise,
JupyterServer
} from '@jupyterlab/testutils';

import { UUID } from '@lumino/coreutils';
import { UUID, JSONExt } from '@lumino/coreutils';

import { DebuggerModel } from '../src/model';

Expand All @@ -16,6 +19,25 @@ import { DebugSession } from '../src/session';

import { IDebugger } from '../src/tokens';

import { KERNELSPECS, handleRequest } from './utils';

/**
* A Test class to mock a KernelSpecManager
*/
class TestKernelSpecManager extends KernelSpecManager {
intercept: KernelSpec.ISpecModels | null = null;

/**
* Request the kernel specs
*/
protected async requestSpecs(): Promise<void> {
if (this.intercept) {
handleRequest(this, 200, this.intercept);
}
return super.requestSpecs();
}
}

const server = new JupyterServer();

beforeAll(async () => {
Expand All @@ -28,7 +50,10 @@ afterAll(async () => {
});

describe('Debugging support', () => {
const service = new DebuggerService();
const specs = JSONExt.deepCopy(KERNELSPECS) as KernelSpec.ISpecModels;

let specsManager: TestKernelSpecManager;
let service: DebuggerService;
let xpython: Session.ISessionConnection;
let ipykernel: Session.ISessionConnection;

Expand All @@ -39,16 +64,24 @@ describe('Debugging support', () => {
path: UUID.uuid4()
});
await xpython.changeKernel({ name: 'xpython' });

ipykernel = await createSession({
name: '',
type: 'test',
path: UUID.uuid4()
});
await ipykernel.changeKernel({ name: 'python3' });

specsManager = new TestKernelSpecManager({ standby: 'never' });
specsManager.intercept = specs;
await specsManager.refreshSpecs();
service = new DebuggerService({ specsManager });
});

afterAll(async () => {
await Promise.all([xpython.shutdown(), ipykernel.shutdown()]);
service.dispose();
specsManager.dispose();
});

describe('#isAvailable', () => {
Expand All @@ -65,6 +98,7 @@ describe('Debugging support', () => {
});

describe('DebuggerService', () => {
const specsManager = new KernelSpecManager();
let connection: Session.ISessionConnection;
let model: DebuggerModel;
let session: IDebugger.ISession;
Expand All @@ -79,7 +113,7 @@ describe('DebuggerService', () => {
await connection.changeKernel({ name: 'xpython' });
session = new DebugSession({ connection });
model = new DebuggerModel();
service = new DebuggerService();
service = new DebuggerService({ specsManager });
});

afterEach(async () => {
Expand Down
85 changes: 85 additions & 0 deletions test/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

// Utils from: https://github.com/jupyterlab/jupyterlab/blob/b1e2b83047421bf7196bec5f2a94d0616dcb2329/packages/services/test/utils.ts

import { ServerConnection } from '@jupyterlab/services';

import { JSONObject } from '@lumino/coreutils';

export const KERNELSPECS: JSONObject = {
default: 'xpython',
kernelspecs: {
python3: {
name: 'Python',
spec: {
language: 'python',
argv: [],
// eslint-disable-next-line @typescript-eslint/camelcase
display_name: 'Python 3',
env: {}
},
resources: {}
},
xpython: {
name: 'xpython',
spec: {
language: 'python',
argv: [],
// eslint-disable-next-line @typescript-eslint/camelcase
display_name: 'xpython',
env: {},
metadata: { debugger: true }
},
resources: {}
}
}
};

/**
* Create new server connection settings.
*
* @param settings The server connection settings.
*/
export function makeSettings(
settings?: Partial<ServerConnection.ISettings>
): ServerConnection.ISettings {
return ServerConnection.makeSettings(settings);
}

/**
* An interface for a service that has server settings.
*/
export interface IService {
readonly serverSettings: ServerConnection.ISettings;
}

/**
* Handle a single request with a mock response.
*
* @param item The service.
* @param status The status code for the response.
* @param body The body for the response.
*/
export function handleRequest(item: IService, status: number, body: any): void {
// Store the existing fetch function.
const oldFetch = item.serverSettings.fetch;

// A single use callback.
const temp = (info: RequestInfo, init: RequestInit): Promise<void> => {
// Restore fetch.
(item.serverSettings as any).fetch = oldFetch;

// Normalize the body.
if (typeof body !== 'string') {
body = JSON.stringify(body);
}

// Create the response and return it as a promise.
const response = new Response(body, { status });
return Promise.resolve(response as any);
};

// Override the fetch function.
(item.serverSettings as any).fetch = temp;
}

0 comments on commit beffb9e

Please sign in to comment.