Skip to content

Commit

Permalink
Initial Positron extension API integration tests (#3615)
Browse files Browse the repository at this point in the history
This PR adds a single rather basic test for the Positron extension API:
registering and then disposing a language runtime manager. (I noticed
that there may actually be a bug in the disposing part, see the TODO in
`runtime.test.ts`.)

We can decide whether we're happy with this approach before investing
too much in writing these tests.

The test will run as part of the existing `vscode-api-tests` extension
tests which are already a part of the `test-integration.sh` script. For
convenience, I've also added a `test-positron-api.sh` script that _only_
runs Positron API tests, so you should be able to do:

```sh
./scripts/test-positron-api.sh
```

from the root directory.

We'll likely need to fill out `TestLanguageRuntimeSession` as we add
more tests. I suspect it may end up looking a lot like the Zed and
JavaScript runtime session classes.

Co-authored-by: seem <[email protected]>
  • Loading branch information
wesm and seeM authored Jun 25, 2024
1 parent 74812c9 commit 893521e
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 1 deletion.
6 changes: 6 additions & 0 deletions extensions/vscode-api-tests/src/singlefolder-tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
}
};
}
// --- Start Positron ---
// Allow running a subset of tests.
if (process.env.VSCODE_MOCHA_GREP) {
options.grep = process.env.VSCODE_MOCHA_GREP;
}
// --- End Positron ---

testRunner.configure(options);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
*--------------------------------------------------------------------------------------------*/

import * as positron from 'positron';
import * as vscode from 'vscode';
import { assertNoRpcFromEntry, disposeAll, poll } from '../../utils';
import { Disposable } from 'vscode';
import assert = require('assert');

class TestLanguageRuntimeSession implements positron.LanguageRuntimeSession {
private readonly _onDidReceiveRuntimeMessage = new vscode.EventEmitter<positron.LanguageRuntimeMessage>();
private readonly _onDidChangeRuntimeState = new vscode.EventEmitter<positron.RuntimeState>();
private readonly _onDidEndSession = new vscode.EventEmitter<positron.LanguageRuntimeExit>();

onDidReceiveRuntimeMessage: vscode.Event<positron.LanguageRuntimeMessage> = this._onDidReceiveRuntimeMessage.event;
onDidChangeRuntimeState: vscode.Event<positron.RuntimeState> = this._onDidChangeRuntimeState.event;
onDidEndSession: vscode.Event<positron.LanguageRuntimeExit> = this._onDidEndSession.event;

readonly dynState = {
inputPrompt: `T>`,
continuationPrompt: 'T+',
};

constructor(
readonly runtimeMetadata: positron.LanguageRuntimeMetadata,
readonly metadata: positron.RuntimeSessionMetadata
) { }

execute(_code: string, _id: string, _mode: positron.RuntimeCodeExecutionMode, _errorBehavior: positron.RuntimeErrorBehavior): void {
throw new Error('Not implemented.');
}

async isCodeFragmentComplete(_code: string): Promise<positron.RuntimeCodeFragmentStatus> {
throw new Error('Not implemented.');
}

async createClient(_id: string, _type: positron.RuntimeClientType, _params: any, _metadata?: any): Promise<void> {
throw new Error('Not implemented.');
}

async listClients(_type?: positron.RuntimeClientType | undefined): Promise<Record<string, string>> {
throw new Error('Not implemented.');
}

removeClient(_id: string): void {
throw new Error('Not implemented.');
}

sendClientMessage(_client_id: string, _message_id: string, _message: any): void {
throw new Error('Not implemented.');
}

replyToPrompt(_id: string, _reply: string): void {
throw new Error('Not implemented.');
}

async start(): Promise<positron.LanguageRuntimeInfo> {
throw new Error('Not implemented.');
}

async interrupt(): Promise<void> {
throw new Error('Not implemented.');
}

async restart(): Promise<void> {
throw new Error('Not implemented.');
}

async shutdown(_exitReason: positron.RuntimeExitReason): Promise<void> {
throw new Error('Not implemented.');
}

async forceQuit(): Promise<void> {
throw new Error('Not implemented.');
}

dispose() {
}
}

function testLanguageRuntimeMetadata(): positron.LanguageRuntimeMetadata {
const languageVersion = '0.0.1';
const runtimeShortName = languageVersion;
return {
base64EncodedIconSvg: '',
extraRuntimeData: {},
languageId: 'test',
languageName: 'Test',
languageVersion,
runtimeId: '00000000-0000-0000-0000-100000000000',
runtimeName: `Test ${runtimeShortName}`,
runtimePath: '/test',
runtimeShortName,
runtimeSource: 'Test',
runtimeVersion: '0.0.1',
sessionLocation: positron.LanguageRuntimeSessionLocation.Browser,
startupBehavior: positron.LanguageRuntimeStartupBehavior.Implicit,
};
}

class TestLanguageRuntimeManager implements positron.LanguageRuntimeManager {
readonly onDidDiscoverRuntimeEmitter = new vscode.EventEmitter<positron.LanguageRuntimeMetadata>();

onDidDiscoverRuntime = this.onDidDiscoverRuntimeEmitter.event;

async* discoverRuntimes(): AsyncGenerator<positron.LanguageRuntimeMetadata> {
yield testLanguageRuntimeMetadata();
}

async createSession(
runtimeMetadata: positron.LanguageRuntimeMetadata,
sessionMetadata: positron.RuntimeSessionMetadata
): Promise<positron.LanguageRuntimeSession> {
return new TestLanguageRuntimeSession(runtimeMetadata, sessionMetadata);
}
}

suite('positron API - runtime', () => {

let disposables: Disposable[];
setup(() => {
disposables = [];
});

teardown(async function () {
assertNoRpcFromEntry([positron, 'positron']);
disposeAll(disposables);
});

test('register a runtime manager', async () => {
const getRegisteredRuntimes = async () =>
(await positron.runtime.getRegisteredRuntimes())
.filter(runtime => runtime.languageId === 'test');

assert.deepStrictEqual(
await getRegisteredRuntimes(),
[],
'no test runtimes should be registered');

// Register a manager.
const manager = new TestLanguageRuntimeManager();
const managerDisposable = positron.runtime.registerLanguageRuntimeManager(manager);

// The manager's runtimes should eventually be registered.
await poll(
getRegisteredRuntimes,
(runtimes) => runtimes.length > 0,
'runtimes should be registered',
);

managerDisposable.dispose();

// TODO: Unregistering a manager unregisters its runtimes, but doesn't remove them from
// the list returned by positron.runtime.getRegisteredRuntimes. Is that a bug?
// It also means that this test will currently fail if run out of order.
// await poll(
// getRegisteredRuntimes,
// (runtimes) => runtimes.length === 0,
// 'test runtimes should be unregistered',
// );
});

});
6 changes: 5 additions & 1 deletion extensions/vscode-api-tests/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"include": [
"src/**/*",
"../../src/vscode-dts/vscode.d.ts",
"../../src/vscode-dts/vscode.proposed.*.d.ts"
// --- Start Positron ---
// Add positron.d.ts.
"../../src/vscode-dts/vscode.proposed.*.d.ts",
"../../src/positron-dts/positron.d.ts",
// --- End Positron ---
]
}
58 changes: 58 additions & 0 deletions scripts/test-positron-api.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# This script runs only the Positron API integration tests defined at extensions/vscode-api-tests/src.
# It's largely copied from scripts/test-integration.sh, but with all other tests removed.

set -e

if [[ "$OSTYPE" == "darwin"* ]]; then
realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; }
ROOT=$(dirname $(dirname $(realpath "$0")))
else
ROOT=$(dirname $(dirname $(readlink -f $0)))
# --disable-dev-shm-usage: when run on docker containers where size of /dev/shm
# partition < 64MB which causes OOM failure for chromium compositor that uses the partition for shared memory
LINUX_EXTRA_ARGS="--disable-dev-shm-usage"
fi

VSCODEUSERDATADIR=`mktemp -d 2>/dev/null`
VSCODECRASHDIR=$ROOT/.build/crashes
VSCODELOGSDIR=$ROOT/.build/logs/integration-tests

cd $ROOT

# Figure out which Electron to use for running tests
if [ -z "$INTEGRATION_TEST_ELECTRON_PATH" ]
then
INTEGRATION_TEST_ELECTRON_PATH="./scripts/code.sh"

echo "Running integration tests out of sources."
else
export VSCODE_CLI=1
export ELECTRON_ENABLE_LOGGING=1

echo "Running integration tests with '$INTEGRATION_TEST_ELECTRON_PATH' as build."
fi

echo "Storing crash reports into '$VSCODECRASHDIR'."
echo "Storing log files into '$VSCODELOGSDIR'."


# Tests in the extension host

API_TESTS_EXTRA_ARGS="--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=$VSCODECRASHDIR --logsPath=$VSCODELOGSDIR --no-cached-data --disable-updates --use-inmemory-secretstorage --disable-extensions --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR"

if [ -z "$INTEGRATION_TEST_APP_NAME" ]; then
kill_app() { true; }
else
kill_app() { killall $INTEGRATION_TEST_APP_NAME || true; }
fi

echo
echo "### Positron API tests (folder)"
echo
VSCODE_MOCHA_GREP='positron API' "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests $API_TESTS_EXTRA_ARGS
kill_app

# Cleanup

rm -rf $VSCODEUSERDATADIR

0 comments on commit 893521e

Please sign in to comment.