about summary refs log tree commit diff
path: root/src/tools/rust-analyzer/editors/code/tests
diff options
context:
space:
mode:
authorJosh McKinney <joshka@users.noreply.github.com>2024-03-28 20:39:21 -0700
committerJosh McKinney <joshka@users.noreply.github.com>2024-03-28 20:42:44 -0700
commit29d28f801cf7cac6a2b80dadfd60f3f6823bc3cf (patch)
tree3c05166c97442ea82947c2dcaea6b0bd03992a41 /src/tools/rust-analyzer/editors/code/tests
parentab10eea62ec59bc676a946ae8be562edcaccb950 (diff)
Make `cargo run` always available for binaries
Previously, items for `cargo test` and `cargo check` would appear as in
the `Select Runnable` quick pick that appears when running
`rust-analyzer: Run`, but `run` would only appear as a runnable if a
`main`` function was selected in the editor. This change adds `cargo
run` as an always available runnable command for binary packages.

This makes it easier to develop cli / tui applications, as now users can
run application from anywhere in their codebase.
Diffstat (limited to 'src/tools/rust-analyzer/editors/code/tests')
-rw-r--r--src/tools/rust-analyzer/editors/code/tests/runTests.ts43
-rw-r--r--src/tools/rust-analyzer/editors/code/tests/unit/index.ts87
-rw-r--r--src/tools/rust-analyzer/editors/code/tests/unit/launch_config.test.ts99
-rw-r--r--src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts121
-rw-r--r--src/tools/rust-analyzer/editors/code/tests/unit/settings.test.ts63
5 files changed, 413 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/editors/code/tests/runTests.ts b/src/tools/rust-analyzer/editors/code/tests/runTests.ts
new file mode 100644
index 00000000000..08632ec3b49
--- /dev/null
+++ b/src/tools/rust-analyzer/editors/code/tests/runTests.ts
@@ -0,0 +1,43 @@
+import * as path from "path";
+import * as fs from "fs";
+
+import { runTests } from "@vscode/test-electron";
+
+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", extensionDevelopmentPath];
+
+    // 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);
+});
diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/index.ts b/src/tools/rust-analyzer/editors/code/tests/unit/index.ts
new file mode 100644
index 00000000000..8ad46546abd
--- /dev/null
+++ b/src/tools/rust-analyzer/editors/code/tests/unit/index.ts
@@ -0,0 +1,87 @@
+import * as assert from "node:assert/strict";
+import { readdir } from "fs/promises";
+import * as path from "path";
+
+class Test {
+    readonly name: string;
+    readonly promise: Promise<void>;
+
+    constructor(name: string, promise: Promise<void>) {
+        this.name = name;
+        this.promise = promise;
+    }
+}
+
+class Suite {
+    tests: Test[];
+
+    constructor() {
+        this.tests = [];
+    }
+
+    public addTest(name: string, f: () => Promise<void>): void {
+        const test = new Test(name, f());
+        this.tests.push(test);
+    }
+
+    public async run(): Promise<void> {
+        let failed = 0;
+        for (const test of this.tests) {
+            try {
+                await test.promise;
+                ok(`  ✔ ${test.name}`);
+            } catch (e) {
+                assert.ok(e instanceof Error);
+                error(`  ✖︎ ${test.name}\n  ${e.stack}`);
+                failed += 1;
+            }
+        }
+        if (failed) {
+            const plural = failed > 1 ? "s" : "";
+            throw new Error(`${failed} failed test${plural}`);
+        }
+    }
+}
+
+export class Context {
+    public async suite(name: string, f: (ctx: Suite) => void): Promise<void> {
+        const ctx = new Suite();
+        f(ctx);
+        try {
+            ok(`⌛︎ ${name}`);
+            await ctx.run();
+            ok(`✔ ${name}`);
+        } catch (e) {
+            assert.ok(e instanceof Error);
+            error(`✖︎ ${name}\n  ${e.stack}`);
+            throw e;
+        }
+    }
+}
+
+export async function run(): Promise<void> {
+    const context = new Context();
+
+    const testFiles = (await readdir(path.resolve(__dirname))).filter((name) =>
+        name.endsWith(".test.js"),
+    );
+    for (const testFile of testFiles) {
+        try {
+            const testModule = require(path.resolve(__dirname, testFile));
+            await testModule.getTests(context);
+        } catch (e) {
+            error(`${e}`);
+            throw e;
+        }
+    }
+}
+
+function ok(message: string): void {
+    // eslint-disable-next-line no-console
+    console.log(`\x1b[32m${message}\x1b[0m`);
+}
+
+function error(message: string): void {
+    // eslint-disable-next-line no-console
+    console.error(`\x1b[31m${message}\x1b[0m`);
+}
diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/launch_config.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/launch_config.test.ts
new file mode 100644
index 00000000000..eeb8608a42d
--- /dev/null
+++ b/src/tools/rust-analyzer/editors/code/tests/unit/launch_config.test.ts
@@ -0,0 +1,99 @@
+import * as assert from "assert";
+import { Cargo } from "../../src/toolchain";
+import type { Context } from ".";
+
+export async function getTests(ctx: Context) {
+    await ctx.suite("Launch configuration/Lens", (suite) => {
+        suite.addTest("A binary", async () => {
+            const args = Cargo.artifactSpec([
+                "build",
+                "--package",
+                "pkg_name",
+                "--bin",
+                "pkg_name",
+            ]);
+
+            assert.deepStrictEqual(args.cargoArgs, [
+                "build",
+                "--package",
+                "pkg_name",
+                "--bin",
+                "pkg_name",
+                "--message-format=json",
+            ]);
+            assert.deepStrictEqual(args.filter, undefined);
+        });
+
+        suite.addTest("One of Multiple Binaries", async () => {
+            const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "bin1"]);
+
+            assert.deepStrictEqual(args.cargoArgs, [
+                "build",
+                "--package",
+                "pkg_name",
+                "--bin",
+                "bin1",
+                "--message-format=json",
+            ]);
+            assert.deepStrictEqual(args.filter, undefined);
+        });
+
+        suite.addTest("A test", async () => {
+            const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib", "--no-run"]);
+
+            assert.deepStrictEqual(args.cargoArgs, [
+                "test",
+                "--package",
+                "pkg_name",
+                "--lib",
+                "--no-run",
+                "--message-format=json",
+            ]);
+            assert.notDeepStrictEqual(args.filter, undefined);
+        });
+    });
+
+    await ctx.suite("Launch configuration/QuickPick", (suite) => {
+        suite.addTest("A binary", async () => {
+            const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "pkg_name"]);
+
+            assert.deepStrictEqual(args.cargoArgs, [
+                "build",
+                "--package",
+                "pkg_name",
+                "--bin",
+                "pkg_name",
+                "--message-format=json",
+            ]);
+            assert.deepStrictEqual(args.filter, undefined);
+        });
+
+        suite.addTest("One of Multiple Binaries", async () => {
+            const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "bin2"]);
+
+            assert.deepStrictEqual(args.cargoArgs, [
+                "build",
+                "--package",
+                "pkg_name",
+                "--bin",
+                "bin2",
+                "--message-format=json",
+            ]);
+            assert.deepStrictEqual(args.filter, undefined);
+        });
+
+        suite.addTest("A test", async () => {
+            const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib"]);
+
+            assert.deepStrictEqual(args.cargoArgs, [
+                "test",
+                "--package",
+                "pkg_name",
+                "--lib",
+                "--message-format=json",
+                "--no-run",
+            ]);
+            assert.notDeepStrictEqual(args.filter, undefined);
+        });
+    });
+}
diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts
new file mode 100644
index 00000000000..b1407ce0193
--- /dev/null
+++ b/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts
@@ -0,0 +1,121 @@
+import * as assert from "assert";
+import { prepareEnv } from "../../src/run";
+import type { RunnableEnvCfg } from "../../src/config";
+import type { Context } from ".";
+import type * as ra from "../../src/lsp_ext";
+
+function makeRunnable(label: string): ra.Runnable {
+    return {
+        label,
+        kind: "cargo",
+        args: {
+            cargoArgs: [],
+            executableArgs: [],
+            cargoExtraArgs: [],
+        },
+    };
+}
+
+function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<string, string> {
+    const runnable = makeRunnable(runnableName);
+    return prepareEnv(runnable, config);
+}
+
+export async function getTests(ctx: Context) {
+    await ctx.suite("Runnable env", (suite) => {
+        suite.addTest("Global config works", async () => {
+            const binEnv = fakePrepareEnv("run project_name", { GLOBAL: "g" });
+            assert.strictEqual(binEnv["GLOBAL"], "g");
+
+            const testEnv = fakePrepareEnv("test some::mod::test_name", { GLOBAL: "g" });
+            assert.strictEqual(testEnv["GLOBAL"], "g");
+        });
+
+        suite.addTest("null mask works", async () => {
+            const config = [
+                {
+                    env: { DATA: "data" },
+                },
+            ];
+            const binEnv = fakePrepareEnv("run project_name", config);
+            assert.strictEqual(binEnv["DATA"], "data");
+
+            const testEnv = fakePrepareEnv("test some::mod::test_name", config);
+            assert.strictEqual(testEnv["DATA"], "data");
+        });
+
+        suite.addTest("order works", async () => {
+            const config = [
+                {
+                    env: { DATA: "data" },
+                },
+                {
+                    env: { DATA: "newdata" },
+                },
+            ];
+            const binEnv = fakePrepareEnv("run project_name", config);
+            assert.strictEqual(binEnv["DATA"], "newdata");
+
+            const testEnv = fakePrepareEnv("test some::mod::test_name", config);
+            assert.strictEqual(testEnv["DATA"], "newdata");
+        });
+
+        suite.addTest("mask works", async () => {
+            const config = [
+                {
+                    env: { DATA: "data" },
+                },
+                {
+                    mask: "^run",
+                    env: { DATA: "rundata" },
+                },
+                {
+                    mask: "special_test$",
+                    env: { DATA: "special_test" },
+                },
+            ];
+            const binEnv = fakePrepareEnv("run project_name", config);
+            assert.strictEqual(binEnv["DATA"], "rundata");
+
+            const testEnv = fakePrepareEnv("test some::mod::test_name", config);
+            assert.strictEqual(testEnv["DATA"], "data");
+
+            const specialTestEnv = fakePrepareEnv("test some::mod::special_test", config);
+            assert.strictEqual(specialTestEnv["DATA"], "special_test");
+        });
+
+        suite.addTest("exact test name works", async () => {
+            const config = [
+                {
+                    env: { DATA: "data" },
+                },
+                {
+                    mask: "some::mod::test_name",
+                    env: { DATA: "test special" },
+                },
+            ];
+            const testEnv = fakePrepareEnv("test some::mod::test_name", config);
+            assert.strictEqual(testEnv["DATA"], "test special");
+
+            const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
+            assert.strictEqual(specialTestEnv["DATA"], "data");
+        });
+
+        suite.addTest("test mod name works", async () => {
+            const config = [
+                {
+                    env: { DATA: "data" },
+                },
+                {
+                    mask: "some::mod",
+                    env: { DATA: "mod special" },
+                },
+            ];
+            const testEnv = fakePrepareEnv("test some::mod::test_name", config);
+            assert.strictEqual(testEnv["DATA"], "mod special");
+
+            const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
+            assert.strictEqual(specialTestEnv["DATA"], "mod special");
+        });
+    });
+}
diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/settings.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/settings.test.ts
new file mode 100644
index 00000000000..bafb9569a1c
--- /dev/null
+++ b/src/tools/rust-analyzer/editors/code/tests/unit/settings.test.ts
@@ -0,0 +1,63 @@
+import * as assert from "assert";
+import type { Context } from ".";
+import { substituteVariablesInEnv } from "../../src/config";
+
+export async function getTests(ctx: Context) {
+    await ctx.suite("Server Env Settings", (suite) => {
+        suite.addTest("Replacing Env Variables", async () => {
+            const envJson = {
+                USING_MY_VAR: "${env:MY_VAR} test ${env:MY_VAR}",
+                MY_VAR: "test",
+            };
+            const expectedEnv = {
+                USING_MY_VAR: "test test test",
+                MY_VAR: "test",
+            };
+            const actualEnv = await substituteVariablesInEnv(envJson);
+            assert.deepStrictEqual(actualEnv, expectedEnv);
+        });
+
+        suite.addTest("Circular dependencies remain as is", async () => {
+            const envJson = {
+                A_USES_B: "${env:B_USES_A}",
+                B_USES_A: "${env:A_USES_B}",
+                C_USES_ITSELF: "${env:C_USES_ITSELF}",
+                D_USES_C: "${env:C_USES_ITSELF}",
+                E_IS_ISOLATED: "test",
+                F_USES_E: "${env:E_IS_ISOLATED}",
+            };
+            const expectedEnv = {
+                A_USES_B: "${env:B_USES_A}",
+                B_USES_A: "${env:A_USES_B}",
+                C_USES_ITSELF: "${env:C_USES_ITSELF}",
+                D_USES_C: "${env:C_USES_ITSELF}",
+                E_IS_ISOLATED: "test",
+                F_USES_E: "test",
+            };
+            const actualEnv = await substituteVariablesInEnv(envJson);
+            assert.deepStrictEqual(actualEnv, expectedEnv);
+        });
+
+        suite.addTest("Should support external variables", async () => {
+            process.env["TEST_VARIABLE"] = "test";
+            const envJson = {
+                USING_EXTERNAL_VAR: "${env:TEST_VARIABLE} test ${env:TEST_VARIABLE}",
+            };
+            const expectedEnv = {
+                USING_EXTERNAL_VAR: "test test test",
+            };
+
+            const actualEnv = await substituteVariablesInEnv(envJson);
+            assert.deepStrictEqual(actualEnv, expectedEnv);
+            delete process.env["TEST_VARIABLE"];
+        });
+
+        suite.addTest("should support VSCode variables", async () => {
+            const envJson = {
+                USING_VSCODE_VAR: "${workspaceFolderBasename}",
+            };
+            const actualEnv = await substituteVariablesInEnv(envJson);
+            assert.deepStrictEqual(actualEnv["USING_VSCODE_VAR"], "code");
+        });
+    });
+}