wamr-ide: Add vscode extension tests (#2292)
This PR adds tests for #2219 by changing the `compilation_on_android_ubuntu.yml` workflow. The first run will take about two hours, since LLDB is built from scratch. Later, the build is cached and the whole job should not take more than three minutes. Core of the PR is an integration test that boots up vscode and lets it debug a test WASM file.
This commit is contained in:
33
test-tools/wamr-ide/VSCode-Extension/src/test/runTest.ts
Normal file
33
test-tools/wamr-ide/VSCode-Extension/src/test/runTest.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Intel Corporation. All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
import { runTests } from '@vscode/test-electron';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// The folder containing the Extension Manifest package.json
|
||||
// Passed to `--extensionDevelopmentPath`
|
||||
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
|
||||
|
||||
// The path to the extension test script
|
||||
// Passed to --extensionTestsPath
|
||||
const extensionTestsPath = path.resolve(__dirname, './suite/index');
|
||||
|
||||
// Download VS Code, unzip it and run the integration test
|
||||
await runTests({
|
||||
extensionDevelopmentPath,
|
||||
extensionTestsPath,
|
||||
launchArgs: ['--user-data-dir', `${os.tmpdir()}`]
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to run tests');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Intel Corporation. All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
*/
|
||||
|
||||
import {DebugProtocol} from '@vscode/debugprotocol';
|
||||
import {after, before, test, suite} from 'mocha';
|
||||
import {assert} from 'chai';
|
||||
import * as vscode from 'vscode';
|
||||
import * as cp from 'child_process';
|
||||
import * as path from "path";
|
||||
import * as os from 'os';
|
||||
import {WasmDebugConfig, WasmDebugConfigurationProvider} from "../../debugConfigurationProvider";
|
||||
import {EXTENSION_PATH, clearAllBp, setBpAtMarker, compileRustToWasm} from "./utils";
|
||||
import {downloadLldb, isLLDBInstalled} from '../../utilities/lldbUtilities';
|
||||
|
||||
suite('Unit Tests', function () {
|
||||
test('DebugConfigurationProvider init commands', function () {
|
||||
const testExtensionPath = "/test/path/";
|
||||
const provider = new WasmDebugConfigurationProvider(testExtensionPath);
|
||||
|
||||
assert.includeMembers(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
provider.getDebugConfig().initCommands!,
|
||||
[`command script import ${testExtensionPath}/formatters/rust.py`],
|
||||
"Debugger init commands did not contain "
|
||||
);
|
||||
});
|
||||
|
||||
test('DebugConfigurationProvider resolve configuration', function () {
|
||||
const testExtensionPath = "/test/path/";
|
||||
const provider = new WasmDebugConfigurationProvider(testExtensionPath);
|
||||
|
||||
const actual = provider.resolveDebugConfiguration(undefined, {
|
||||
type: "wamr-debug",
|
||||
name: "Attach",
|
||||
request: "attach",
|
||||
initCommands: [],
|
||||
attachCommands: [
|
||||
'process connect -p wasm connect://123.456.789.1:1237',
|
||||
]
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
actual,
|
||||
{
|
||||
type: "wamr-debug",
|
||||
name: "Attach",
|
||||
request: "attach",
|
||||
stopOnEntry: true,
|
||||
initCommands: [],
|
||||
attachCommands: [
|
||||
'process connect -p wasm connect://123.456.789.1:1237',
|
||||
]
|
||||
},
|
||||
"Configuration did not match the expected configuration after calling resolveDebugConfiguration()"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Inegration Tests', function () {
|
||||
let debuggerProcess: cp.ChildProcessWithoutNullStreams;
|
||||
const port = 1239;
|
||||
const downloadTimeout = 60 * 1000;
|
||||
|
||||
before(async function () {
|
||||
// timeout of 20 seconds
|
||||
this.timeout(20 * 1000);
|
||||
// Download LLDB if necessary. Should be available in the CI. Only for local execution.
|
||||
if (!isLLDBInstalled(EXTENSION_PATH)) {
|
||||
this.timeout(downloadTimeout);
|
||||
console.log("Downloading LLDB. This might take a moment...");
|
||||
await downloadLldb(EXTENSION_PATH);
|
||||
assert.isTrue(isLLDBInstalled(EXTENSION_PATH), "LLDB was not installed correctly");
|
||||
}
|
||||
|
||||
compileRustToWasm();
|
||||
|
||||
const platform = os.platform();
|
||||
assert.isTrue(platform === "darwin" || platform === "linux", `Tests do not support your platform: ${platform}`);
|
||||
const iWasmPath = path.resolve(`${EXTENSION_PATH}/../../../product-mini/platforms/${platform}/build/iwasm`);
|
||||
const testWasmFilePath = `${EXTENSION_PATH}/resource/test/test.wasm`;
|
||||
|
||||
debuggerProcess = cp.spawn(
|
||||
iWasmPath,
|
||||
[`-g=127.0.0.1:${port}`, testWasmFilePath],
|
||||
{}
|
||||
);
|
||||
|
||||
debuggerProcess.stderr.on('data', (data) => {
|
||||
console.log(`Error from debugger process: ${data}`);
|
||||
});
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await vscode.debug.stopDebugging();
|
||||
debuggerProcess.kill();
|
||||
});
|
||||
|
||||
test('Rust formatters', async function () {
|
||||
// timeout of 1 minutes
|
||||
this.timeout(60 * 1000);
|
||||
clearAllBp();
|
||||
setBpAtMarker(`${EXTENSION_PATH}/resource/test/test.rs`, "BP_MARKER_1");
|
||||
|
||||
const getVariables = new Promise<DebugProtocol.Variable[]>((resolve, reject) => {
|
||||
vscode.debug.registerDebugAdapterTrackerFactory("wamr-debug", {
|
||||
createDebugAdapterTracker: function () {
|
||||
return {
|
||||
// The debug adapter has sent a Debug Adapter Protocol message to the editor.
|
||||
onDidSendMessage: (message: DebugProtocol.ProtocolMessage) => {
|
||||
if (message.type === "response") {
|
||||
const m = message as DebugProtocol.Response;
|
||||
if (m.command === "variables") {
|
||||
const res = m as DebugProtocol.VariablesResponse;
|
||||
resolve(res.body.variables);
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
reject("An error occurred before vscode reached the breakpoint: " + error);
|
||||
},
|
||||
onExit: (code: number | undefined) => {
|
||||
reject(`Debugger exited before vscode reached the breakpoint with code: ${code}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const config: WasmDebugConfig = {
|
||||
type: "wamr-debug",
|
||||
request: "attach",
|
||||
name: "Attach Debugger",
|
||||
stopOnEntry: false,
|
||||
initCommands: [
|
||||
`command script import ${EXTENSION_PATH}/formatters/rust.py`
|
||||
],
|
||||
attachCommands: [
|
||||
`process connect -p wasm connect://127.0.0.1:${port}`
|
||||
]
|
||||
};
|
||||
|
||||
if (os.platform() === 'win32' || os.platform() === 'darwin') {
|
||||
config.initCommands?.push('platform select remote-linux');
|
||||
}
|
||||
|
||||
try {
|
||||
await vscode.debug.startDebugging(undefined, config);
|
||||
} catch (e) {
|
||||
assert.fail("Could not connect to debug adapter");
|
||||
}
|
||||
|
||||
// wait until vs code has reached breakpoint and has requested the variables.
|
||||
const variables = await getVariables;
|
||||
const namesToVariables = variables.reduce((acc: { [name: string]: DebugProtocol.Variable }, c) => {
|
||||
if (c.evaluateName) {
|
||||
acc[c.evaluateName] = c;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
assert.includeMembers(Object.keys(namesToVariables), ["vector", "map", "string", "slice", "deque", "ref_cell"], "The Debugger did not return all expected debugger variables.");
|
||||
|
||||
// Vector
|
||||
assert.equal(namesToVariables["vector"].value, " (5) vec![1, 2, 3, 4, 12]", "The Vector summary string looks different than expected");
|
||||
|
||||
// Map
|
||||
assert.equal(namesToVariables["map"].value, " size=5, capacity=8", "The Map summary string looks different than expected");
|
||||
|
||||
// String
|
||||
assert.equal(namesToVariables["string"].value, " \"this is a string\"", "The String summary string looks different than expected");
|
||||
|
||||
// Slice
|
||||
assert.equal(namesToVariables["slice"].value, " \"ello\"", "The Slice summary string looks different than expected");
|
||||
|
||||
// Deque
|
||||
assert.equal(namesToVariables["deque"].value, " (5) VecDeque[1, 2, 3, 4, 5]", "The Deque summary string looks different than expected");
|
||||
|
||||
// RefCell
|
||||
assert.equal(namesToVariables["ref_cell"].value, " 5", "The RefCell summary string looks different than expected");
|
||||
});
|
||||
});
|
||||
42
test-tools/wamr-ide/VSCode-Extension/src/test/suite/index.ts
Normal file
42
test-tools/wamr-ide/VSCode-Extension/src/test/suite/index.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Intel Corporation. All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as Mocha from 'mocha';
|
||||
import * as glob from 'glob';
|
||||
|
||||
export function run(): Promise<void> {
|
||||
// Create the mocha test
|
||||
const mocha = new Mocha({
|
||||
ui: 'tdd'
|
||||
});
|
||||
|
||||
const testsRoot = path.resolve(__dirname, '..');
|
||||
|
||||
return new Promise((c, e) => {
|
||||
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
|
||||
if (err) {
|
||||
return e(err);
|
||||
}
|
||||
|
||||
// Add files to the test suite
|
||||
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
|
||||
|
||||
try {
|
||||
// Run the mocha test
|
||||
mocha.run(failures => {
|
||||
if (failures > 0) {
|
||||
e(new Error(`${failures} tests failed.`));
|
||||
} else {
|
||||
c();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
43
test-tools/wamr-ide/VSCode-Extension/src/test/suite/utils.ts
Normal file
43
test-tools/wamr-ide/VSCode-Extension/src/test/suite/utils.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Intel Corporation. All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
*/
|
||||
|
||||
import {assert} from 'chai';
|
||||
import * as vscode from 'vscode';
|
||||
import {Range, SourceBreakpoint} from "vscode";
|
||||
import * as fs from "fs";
|
||||
import path = require('path');
|
||||
import * as cp from 'child_process';
|
||||
|
||||
export const EXTENSION_PATH = path.resolve(`${__dirname}/../../..`);
|
||||
|
||||
// clears all set breakpoints
|
||||
export function clearAllBp(): void {
|
||||
vscode.debug.removeBreakpoints(vscode.debug.breakpoints);
|
||||
}
|
||||
|
||||
// Inserts a breakpoint in a file at the first occurrence of bpMarker
|
||||
export function setBpAtMarker(file: string, bpMarker: string): void {
|
||||
const uri = vscode.Uri.file(file);
|
||||
const data = fs.readFileSync(uri.path, "utf8");
|
||||
const line = data.split("\n").findIndex(line => line.includes(bpMarker));
|
||||
assert.notStrictEqual(line, -1, "Could not find breakpoint marker in source file");
|
||||
const position = new vscode.Position(line, 0);
|
||||
const bp = new SourceBreakpoint(new vscode.Location(uri, new Range(position, position)), true);
|
||||
vscode.debug.addBreakpoints([bp]);
|
||||
}
|
||||
|
||||
// compiles resources/test/test.rs to test.wasm
|
||||
export function compileRustToWasm(): void {
|
||||
const testResourceFolder = `${EXTENSION_PATH}/resource/test`;
|
||||
// compile with debug symbols and no optimization
|
||||
const cmd = `rustc --target wasm32-wasi ${testResourceFolder}/test.rs -g -C opt-level=0 -o ${testResourceFolder}/test.wasm`;
|
||||
|
||||
try {
|
||||
cp.execSync(cmd, {stdio: [null, null, process.stderr]});
|
||||
} catch (e) {
|
||||
assert.fail(`Compilation of example rust file failed with error: ${e}`);
|
||||
}
|
||||
assert.isTrue(fs.existsSync(`${testResourceFolder}/test.wasm`), "Could not find wasm file WASM file to run debugger on.");
|
||||
}
|
||||
Reference in New Issue
Block a user