Merge pull request #4538 from vsrs/vscode_tests

vscode client side tests
This commit is contained in:
Aleksey Kladov 2020-05-23 16:39:04 +02:00 committed by GitHub
commit f4f5fca101
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1018 additions and 33 deletions

View file

@ -97,7 +97,13 @@ jobs:
typescript: typescript:
name: TypeScript name: TypeScript
runs-on: ubuntu-latest strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -111,10 +117,19 @@ jobs:
working-directory: ./editors/code working-directory: ./editors/code
- run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; } - run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; }
if: runner.os == 'Linux'
working-directory: ./editors/code working-directory: ./editors/code
- run: npm run lint - run: npm run lint
working-directory: ./editors/code working-directory: ./editors/code
- name: Run vscode tests
uses: GabrielBB/xvfb-action@v1.2
env:
VSCODE_CLI: 1
with:
run: npm --prefix ./editors/code test
# working-directory: ./editors/code # does not work: https://github.com/GabrielBB/xvfb-action/issues/8
- run: npm run package --scripts-prepend-node-path - run: npm run package --scripts-prepend-node-path
working-directory: ./editors/code working-directory: ./editors/code

34
.vscode/launch.json vendored
View file

@ -70,6 +70,28 @@
"__RA_LSP_SERVER_DEBUG": "${workspaceFolder}/target/release/rust-analyzer" "__RA_LSP_SERVER_DEBUG": "${workspaceFolder}/target/release/rust-analyzer"
} }
}, },
{
// Used for testing the extension with a local build of the LSP server (in `target/release`)
// with all other extendions loaded.
"name": "Run With Extensions",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--disable-extension", "matklad.rust-analyzer",
"--extensionDevelopmentPath=${workspaceFolder}/editors/code"
],
"outFiles": [
"${workspaceFolder}/editors/code/out/**/*.js"
],
"preLaunchTask": "Build Server (Release) and Extension",
"skipFiles": [
"<node_internals>/**/*.js"
],
"env": {
"__RA_LSP_SERVER_DEBUG": "${workspaceFolder}/target/release/rust-analyzer"
}
},
{ {
// Used to attach LLDB to a running LSP server. // Used to attach LLDB to a running LSP server.
// NOTE: Might require root permissions. For this run: // NOTE: Might require root permissions. For this run:
@ -87,5 +109,17 @@
"rust" "rust"
] ]
}, },
{
"name": "Run Unit Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/editors/code",
"--extensionTestsPath=${workspaceFolder}/editors/code/out/tests/unit" ],
"sourceMaps": true,
"outFiles": [ "${workspaceFolder}/editors/code/out/tests/unit/**/*.js" ],
"preLaunchTask": "Pretest"
}
] ]
} }

12
.vscode/tasks.json vendored
View file

@ -40,6 +40,18 @@
"command": "cargo build --release --package rust-analyzer", "command": "cargo build --release --package rust-analyzer",
"problemMatcher": "$rustc" "problemMatcher": "$rustc"
}, },
{
"label": "Pretest",
"group": "build",
"isBackground": false,
"type": "npm",
"script": "pretest",
"path": "editors/code/",
"problemMatcher": {
"base": "$tsc",
"fileLocation": ["relative", "${workspaceFolder}/editors/code/"]
}
},
{ {
"label": "Build Server and Extension", "label": "Build Server and Extension",

View file

@ -1,5 +1,5 @@
** **
!out/main.js !out/src/main.js
!package.json !package.json
!package-lock.json !package-lock.json
!ra_syntax_tree.tmGrammar.json !ra_syntax_tree.tmGrammar.json

File diff suppressed because it is too large Load diff

View file

@ -29,8 +29,10 @@
"package": "vsce package -o rust-analyzer.vsix", "package": "vsce package -o rust-analyzer.vsix",
"build": "tsc", "build": "tsc",
"watch": "tsc --watch", "watch": "tsc --watch",
"lint": "tsfmt --verify && eslint -c .eslintrc.js --ext ts ./src", "lint": "tsfmt --verify && eslint -c .eslintrc.js --ext ts ./src ./tests",
"fix": " tsfmt -r && eslint -c .eslintrc.js --ext ts ./src --fix" "fix": " tsfmt -r && eslint -c .eslintrc.js --ext ts ./src ./tests --fix",
"pretest": "npm run build",
"test": "node ./out/tests/runTests.js"
}, },
"dependencies": { "dependencies": {
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
@ -39,17 +41,22 @@
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^11.1.0", "@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-node-resolve": "^7.1.3", "@rollup/plugin-node-resolve": "^7.1.3",
"@types/glob": "^7.1.1",
"@types/mocha": "^7.0.2",
"@types/node": "^12.12.39", "@types/node": "^12.12.39",
"@types/node-fetch": "^2.5.7", "@types/node-fetch": "^2.5.7",
"@types/vscode": "^1.44.0", "@types/vscode": "^1.44.0",
"@typescript-eslint/eslint-plugin": "^2.33.0", "@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0", "@typescript-eslint/parser": "^2.33.0",
"eslint": "^6.8.0", "eslint": "^6.8.0",
"glob": "^7.1.6",
"mocha": "^7.1.2",
"rollup": "^2.10.0", "rollup": "^2.10.0",
"tslib": "^1.12.0", "tslib": "^1.12.0",
"typescript": "^3.9.2", "typescript": "^3.9.2",
"typescript-formatter": "^7.2.2", "typescript-formatter": "^7.2.2",
"vsce": "^1.75.0" "vsce": "^1.75.0",
"vscode-test": "^1.3.0"
}, },
"activationEvents": [ "activationEvents": [
"onLanguage:rust", "onLanguage:rust",
@ -57,7 +64,7 @@
"onCommand:rust-analyzer.collectGarbage", "onCommand:rust-analyzer.collectGarbage",
"workspaceContains:**/Cargo.toml" "workspaceContains:**/Cargo.toml"
], ],
"main": "./out/main", "main": "./out/src/main",
"contributes": { "contributes": {
"taskDefinitions": [ "taskDefinitions": [
{ {

View file

@ -6,7 +6,7 @@ import nodeBuiltins from 'builtin-modules';
/** @type { import('rollup').RollupOptions } */ /** @type { import('rollup').RollupOptions } */
export default { export default {
input: 'out/main.js', input: 'out/src/main.js',
plugins: [ plugins: [
resolve({ resolve({
preferBuiltins: true preferBuiltins: true
@ -20,7 +20,7 @@ export default {
], ],
external: [...nodeBuiltins, 'vscode'], external: [...nodeBuiltins, 'vscode'],
output: { output: {
file: './out/main.js', file: './out/src/main.js',
format: 'cjs', format: 'cjs',
exports: 'named' exports: 'named'
} }

View file

@ -12,14 +12,44 @@ interface CompilationArtifact {
isTest: boolean; isTest: boolean;
} }
export interface ArtifactSpec {
cargoArgs: string[];
filter?: (artifacts: CompilationArtifact[]) => CompilationArtifact[];
}
export function artifactSpec(args: readonly string[]): ArtifactSpec {
const cargoArgs = [...args, "--message-format=json"];
// arguments for a runnable from the quick pick should be updated.
// see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens
switch (cargoArgs[0]) {
case "run": cargoArgs[0] = "build"; break;
case "test": {
if (!cargoArgs.includes("--no-run")) {
cargoArgs.push("--no-run");
}
break;
}
}
const result: ArtifactSpec = { cargoArgs: cargoArgs };
if (cargoArgs[0] === "test") {
// for instance, `crates\rust-analyzer\tests\heavy_tests\main.rs` tests
// produce 2 artifacts: {"kind": "bin"} and {"kind": "test"}
result.filter = (artifacts) => artifacts.filter(it => it.isTest);
}
return result;
}
export class Cargo { export class Cargo {
constructor(readonly rootFolder: string, readonly output: OutputChannel) { } constructor(readonly rootFolder: string, readonly output: OutputChannel) { }
private async artifactsFromArgs(cargoArgs: string[]): Promise<CompilationArtifact[]> { private async getArtifacts(spec: ArtifactSpec): Promise<CompilationArtifact[]> {
const artifacts: CompilationArtifact[] = []; const artifacts: CompilationArtifact[] = [];
try { try {
await this.runCargo(cargoArgs, await this.runCargo(spec.cargoArgs,
message => { message => {
if (message.reason === 'compiler-artifact' && message.executable) { if (message.reason === 'compiler-artifact' && message.executable) {
const isBinary = message.target.crate_types.includes('bin'); const isBinary = message.target.crate_types.includes('bin');
@ -43,30 +73,11 @@ export class Cargo {
throw new Error(`Cargo invocation has failed: ${err}`); throw new Error(`Cargo invocation has failed: ${err}`);
} }
return artifacts; return spec.filter?.(artifacts) ?? artifacts;
} }
async executableFromArgs(args: readonly string[]): Promise<string> { async executableFromArgs(args: readonly string[]): Promise<string> {
const cargoArgs = [...args, "--message-format=json"]; const artifacts = await this.getArtifacts(artifactSpec(args));
// arguments for a runnable from the quick pick should be updated.
// see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens
switch (cargoArgs[0]) {
case "run": cargoArgs[0] = "build"; break;
case "test": {
if (cargoArgs.indexOf("--no-run") === -1) {
cargoArgs.push("--no-run");
}
break;
}
}
let artifacts = await this.artifactsFromArgs(cargoArgs);
if (cargoArgs[0] === "test") {
// for instance, `crates\rust-analyzer\tests\heavy_tests\main.rs` tests
// produce 2 artifacts: {"kind": "bin"} and {"kind": "test"}
artifacts = artifacts.filter(a => a.isTest);
}
if (artifacts.length === 0) { if (artifacts.length === 0) {
throw new Error('No compilation artifacts'); throw new Error('No compilation artifacts');

View file

@ -0,0 +1,43 @@
import * as path from 'path';
import * as fs from 'fs';
import { runTests } from 'vscode-test';
async function main() {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
// Minimum supported version.
const jsonData = fs.readFileSync(path.join(extensionDevelopmentPath, 'package.json'));
const json = JSON.parse(jsonData.toString());
let minimalVersion: string = json.engines.vscode;
if (minimalVersion.startsWith('^')) minimalVersion = minimalVersion.slice(1);
const launchArgs = ["--disable-extensions"];
// All test suites (either unit tests or integration tests) should be in subfolders.
const extensionTestsPath = path.resolve(__dirname, './unit/index');
// Run tests using the minimal supported version.
await runTests({
version: minimalVersion,
launchArgs,
extensionDevelopmentPath,
extensionTestsPath
});
// and the latest one
await runTests({
version: 'stable',
launchArgs,
extensionDevelopmentPath,
extensionTestsPath
});
}
main().catch(err => {
// eslint-disable-next-line no-console
console.error('Failed to run tests', err);
process.exit(1);
});

View file

@ -0,0 +1,38 @@
import * as path from 'path';
import Mocha from 'mocha';
import glob from 'glob';
export function run(): Promise<void> {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
color: true
});
const testsRoot = __dirname;
return new Promise((resolve, reject) => {
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
if (err) {
return reject(err);
}
// Add files to the test suite
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
try {
// Run the mocha test
mocha.timeout(100000);
mocha.run(failures => {
if (failures > 0) {
reject(new Error(`${failures} tests failed.`));
} else {
resolve();
}
});
} catch (err) {
reject(err);
}
});
});
}

View file

@ -0,0 +1,52 @@
import * as assert from 'assert';
import * as cargo from '../../src/cargo';
suite('Launch configuration', () => {
suite('Lens', () => {
test('A binary', async () => {
const args = cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "pkg_name"]);
assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]);
assert.deepEqual(args.filter, undefined);
});
test('One of Multiple Binaries', async () => {
const args = cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "bin1"]);
assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin1", "--message-format=json"]);
assert.deepEqual(args.filter, undefined);
});
test('A test', async () => {
const args = cargo.artifactSpec(["test", "--package", "pkg_name", "--lib", "--no-run"]);
assert.deepEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--no-run", "--message-format=json"]);
assert.notDeepEqual(args.filter, undefined);
});
});
suite('QuickPick', () => {
test('A binary', async () => {
const args = cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "pkg_name"]);
assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]);
assert.deepEqual(args.filter, undefined);
});
test('One of Multiple Binaries', async () => {
const args = cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "bin2"]);
assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin2", "--message-format=json"]);
assert.deepEqual(args.filter, undefined);
});
test('A test', async () => {
const args = cargo.artifactSpec(["test", "--package", "pkg_name", "--lib"]);
assert.deepEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--message-format=json", "--no-run"]);
assert.notDeepEqual(args.filter, undefined);
});
});
});

View file

@ -9,7 +9,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"sourceMap": true, "sourceMap": true,
"rootDir": "src", "rootDir": ".",
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
@ -18,6 +18,11 @@
"newLine": "LF" "newLine": "LF"
}, },
"exclude": [ "exclude": [
"node_modules" "node_modules",
".vscode-test"
],
"include": [
"src",
"tests"
] ]
} }