Skip to content
Permalink
Browse files

Initial extension activation (#3)

* Add sandbox for testing out the extension host API

* Start testing out basic command API

* Get message from extension

* Remove superfluous logging

* Remove additional logging

* Move test.js -> test.ts

* Factor out extension host helper

* Don't run extension host helper as a test

* Factor tests to use new ExtensionHost model

* Add test case validating plugin activation

* Get activation test green
  • Loading branch information...
bryphe committed Mar 19, 2019
1 parent d63bb39 commit 7b4a6516cdb90f00e292e6716b5da1307ccc31e6
@@ -0,0 +1,113 @@
const cp = require("child_process");
const fs = require("fs");
const path = require("path");
const rpc = require("vscode-jsonrpc");

let bootstrapForkPath = path.join(__dirname, "..", "out", "bootstrap-fork.js");

// test("extension host process gets initialized", async () => {

let run = async () => {

let childProcess = cp.spawn("node", [bootstrapForkPath, "--type=extensionHost"], {
env: {
...process.env,
"AMD_ENTRYPOINT": "vs/workbench/services/extensions/node/extensionHostProcess",
},
stdio: ["pipe", "pipe", "inherit"]
});

let connection = rpc.createMessageConnection(
new rpc.StreamMessageReader(childProcess.stdout),
new rpc.StreamMessageWriter(childProcess.stdin)
);

let promise = new Promise((c) => {

let testNotification = new rpc.NotificationType('host/msg');
connection.onNotification(testNotification, (msg) => {
console.log("INCOMING MESSAGE: " + JSON.stringify(msg));

let message = msg;
if (message.reqId > 0) {
connection.sendNotification('ext/msg', {
type: 12, /*ReplyOKJSON */
reqId: message.reqId,
payload: null,
});
}

c();
});

});
connection.listen();

let extensionPath = path.join(__dirname, "..", "extensions", "oni-api-tests", "package.json")
let testExtension = JSON.parse(fs.readFileSync(extensionPath));

testExtension.main = path.join(extensionPath, "..", "extension.js");

let extMessage = new rpc.NotificationType('ext/msg');
connection.sendNotification(extMessage, {
type: 0,
reqId: 1,
payload: {
// extensions: [],
extensions: [{
...testExtension,
identifier: "Hello",
extensionLocationPath: extensionPath,
}],
parentPid: process.pid,
environment: {
globalStorageHomePath: require("os").tmpdir(),
},
workspace: {},
logsLocationPath: require("os").tmpdir(),
autoStart: true,
}
});

connection.sendNotification(extMessage, {
type: 4, /* RequestJSONArgs */
reqId: 2,
payload: ["ExtHostConfiguration", "$initializeConfiguration", [{}]],
});

connection.sendNotification(extMessage, {
type: 4, /* RequestJSONArgs */
reqId: 2,
payload: ["ExtHostWorkspace", "$initializeWorkspace", [{
id: "workspace-test",
name: "workspace-test",
configuration: null,
folders: [],
}]],
});

// await promise;
// console.log("HEY");
// setTimeout(() => {
// connection.sendNotification(extMessage, {
// type: 4,
// reqId: 3,
// payload: ["ExtHostCommands", "$executeContributedCommand", ["extension.helloWorld"]],
// });
// }, 1000);


let closePromise = new Promise((c) => {
connection.onClose(() => c());
})

// connection.sendNotification(extMessage, {
// type: 1,
// payload: null,
// });

await closePromise;
};

run();
// }, 10000);
@@ -0,0 +1,44 @@
const cp = require("child_process");
const fs = require("fs");
const path = require("path");
const rpc = require("vscode-jsonrpc");

let bootstrapForkPath = path.join(__dirname, "..", "out", "bootstrap-fork.js");

let extensionPath = path.join(__dirname, "..", "extensions", "oni-api-tests", "package.json");

import * as ExtensionHost from "./ExtensionHost";

describe("initialization", () => {
test("extension host process gets initialized", async () => {

await ExtensionHost.withExtensionHost([], async (api) => {
// expect(true).toBe(true);
await api.start();
return Promise.resolve();
});
});
});

describe("activation", () => {
test("get activation event for '*' activation type", async () => {
await ExtensionHost.withExtensionHost([extensionPath], async (api) => {
let promise = new Promise((c) => {

api.onMessage.subscribe(msg => {
const { payload } = msg;

if(payload.methodName == "$onDidActivateExtension") {
console.dir(payload);
c();
}
});

});

await api.start();

await promise;
});
});
});
@@ -0,0 +1,201 @@
/*
* ExtensionHost.ts
*
* Helper API for testing / exercising an extension host
*/

import * as cp from "child_process";
import * as fs from "fs";
import * as path from "path";
import * as rpc from "vscode-jsonrpc";

let bootstrapForkPath = path.join(__dirname, "..", "out", "bootstrap-fork.js");

export const enum MessageType {
Initialized = 0,
Ready = 1,
InitData = 2,
Terminate = 3,
RequestJSONArgs = 4,
RequestJSONArgsWithCancellation = 5,
RequestMixedArgs = 6,
RequestMixedArgsWithCancellation = 7,
Acknowledged = 8,
Cancel = 9,
ReplyOKEmpty = 10,
ReplyOKBuffer = 11,
ReplyOKJSON = 12,
ReplyErrError = 13,
ReplyErrEmpty = 14,
};

import { EventEmitter } from "events"

export interface IDisposable {
dispose(): void
}

export type DisposeFunction = () => void

export type EventCallback<T> = (value: T) => void

export interface IEvent<T> {
subscribe(callback: EventCallback<T>): IDisposable
}

export class Event<T> implements IEvent<T> {

private _name: string
private _eventObject: EventEmitter = new EventEmitter()

constructor(name?: string) {
this._name = name || "default_event"
}

public subscribe(callback: EventCallback<T>): IDisposable {
this._eventObject.addListener(this._name, callback)

const dispose = () => {
this._eventObject.removeListener(this._name, callback)
}

return { dispose }
}

public dispatch(val?: T): void {
this._eventObject.emit(this._name, val)
}
}

export interface IExtensionHost {

start: () => Promise<void>;

sendNotification: (payload: any) => void;

onMessage: IEvent<any>;
}


type apiFunction = (api: IExtensionHost) => Promise<void>;

export let withExtensionHost = async (extensions: string[], f: apiFunction) => {
let incomingNotification = new rpc.NotificationType<any, any>('host/msg');
let outgoingNotification = new rpc.NotificationType<any, any>('ext/msg');

let requestId = 0;

let onMessageEvent = new Event<any>();

let childProcess = cp.spawn("node", [bootstrapForkPath, "--type=extensionHost"], {
env: {
...process.env,
"AMD_ENTRYPOINT": "vs/workbench/services/extensions/node/extensionHostProcess",
},
stdio: ["pipe", "pipe", "inherit"]
});

let connection = rpc.createMessageConnection(
new rpc.StreamMessageReader(childProcess.stdout),
new rpc.StreamMessageWriter(childProcess.stdin)
);

let promise = new Promise((c) => {
connection.onNotification(incomingNotification, (msg) => {

if(msg.type === MessageType.Ready) {
c();
} else {

/* TODO: Have a way for the user to specify a reply */
connection.sendNotification(outgoingNotification, {
type: MessageType.ReplyOKJSON,
reqId: msg.reqId,
payload: null,
})

onMessageEvent.dispatch(msg);
}
})
});

let start = async () => {
connection.listen();

let extensionInfo = extensions.map(ext => {
let metadata = <any>JSON.parse(fs.readFileSync(ext, "utf8"));

if (metadata.main) {
metadata.main = path.join(ext, "..", metadata.main);
}

let resolvedMetadata = {
...metadata,
identifier: metadata.name,
extensionLocationPath: ext,
};
return resolvedMetadata;
});


connection.sendNotification(outgoingNotification, {
type: 0,
reqId: requestId++,
payload: {
parentPid: process.pid,
extensions: extensionInfo,
environment: {
globalStorageHomePath: require("os").tmpdir(),
},
workspace: {},
logsLocationPath: require("os").tmpdir(),
autoStart: true,
}
});

connection.sendNotification(outgoingNotification, {
type: 4, /* RequestJSONArgs */
reqId: 2,
payload: ["ExtHostConfiguration", "$initializeConfiguration", [{}]],
});

connection.sendNotification(outgoingNotification, {
type: 4, /* RequestJSONArgs */
reqId: 2,
payload: ["ExtHostWorkspace", "$initializeWorkspace", [{
id: "workspace-test",
name: "workspace-test",
configuration: null,
folders: [],
}]],
});

await promise;
};

let sendNotification = (payload) => connection.sendNotification(outgoingNotification, {
type: MessageType.RequestJSONArgs,
reqId: requestId++,
payload,
});

let extHost = {
start,
sendNotification,
onMessage: onMessageEvent,
};

await f(extHost);

let closePromise = new Promise((c) => {
connection.onClose(() => c());
})

connection.sendNotification(outgoingNotification, {
reqId: 4,
type: 3, /* terminate */
payload: null,
});

await closePromise;
};

0 comments on commit 7b4a651

Please sign in to comment.
You can’t perform that action at this time.