дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
feat(run): run Ruby tests from the editor and Project view (-I roots, test-root folders) (#1545)
A Ruby file run from the context menu had no project load path, so require "foo" for lib/foo.rb failed: Sources roots are now passed as -I<root>. Folders under a test root get Run Tests in '<dir>': RSpec on the folder, rake test with TEST=<globs> (two comma-separated globs, since Rake splits TEST at commas), or a detected minitest configuration that loads every *_test.rb / test_*.rb under the folder when the project has neither. Co-authored-by: kjeldahl <[email protected]>
Цей коміт міститься в:
1 джерело
4959b430b1
коміт
f6a1823121
4 files changed
+144
-1
No files matched your search
@@ -0,0 +1,75 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@/platform/projectRoots", () => ({
|
||||
currentRoots: () => [
|
||||
{ path: "lib", kind: "source", detectedFrom: null },
|
||||
{ path: "test", kind: "test", detectedFrom: null },
|
||||
{ path: "spec", kind: "test", detectedFrom: null },
|
||||
{ path: "", kind: "source", detectedFrom: null },
|
||||
],
|
||||
}));
|
||||
|
||||
import type { RunConfig } from "@/platform/ipc";
|
||||
import { produceContext, produceDirectory, type ToolEnv } from "./producers";
|
||||
|
||||
const env: ToolEnv = {
|
||||
tool: (n) => (n === "ruby" ? { path: "/bin/ruby", version: null } : null),
|
||||
compileDir: "/tmp/openidea-run",
|
||||
};
|
||||
const input = (path: string) => ({ path, root: "/work/app", configs: [], content: undefined, line: undefined });
|
||||
|
||||
describe("Ruby file run", () => {
|
||||
it("puts Sources roots on the load path", () => {
|
||||
const p = produceContext(input("/work/app/test/integration/a_test.rb"), env);
|
||||
expect(p?.config?.args).toEqual(["-Ilib", "test/integration/a_test.rb"]);
|
||||
});
|
||||
|
||||
it("adds none for files outside the project", () => {
|
||||
const p = produceContext(input("/elsewhere/a.rb"), env);
|
||||
expect(p?.config?.args).toEqual(["a.rb"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Ruby directory run", () => {
|
||||
const cfg = (over: Partial<RunConfig> & { id: string; kind: string }): RunConfig => ({
|
||||
name: over.id,
|
||||
command: "bundle",
|
||||
args: [],
|
||||
cwd: null,
|
||||
env: {},
|
||||
detected: true,
|
||||
isTest: true,
|
||||
debugAdapter: null,
|
||||
...over,
|
||||
});
|
||||
const dir = (path: string, configs: RunConfig[]) =>
|
||||
produceDirectory({ path: `/work/app/${path}`, root: "/work/app", configs });
|
||||
|
||||
it("RSpec on a nested folder", () => {
|
||||
const p = dir("spec/models/user", [cfg({ id: "rspec", kind: "rspec", args: ["exec", "rspec"] })]);
|
||||
expect(p?.config?.args).toEqual(["exec", "rspec", "spec/models/user"]);
|
||||
expect(p?.name).toBe("Tests in 'user'");
|
||||
});
|
||||
|
||||
it("rake test with a TEST glob", () => {
|
||||
const p = dir("test/unit", [cfg({ id: "rake:test", kind: "rake", args: ["exec", "rake", "test"] })]);
|
||||
expect(p?.config?.args).toEqual(["exec", "rake", "test", "TEST=test/unit/**/*_test.rb,test/unit/**/test_*.rb"]);
|
||||
});
|
||||
|
||||
it("the Minitest runner with Sources roots and the folder", () => {
|
||||
const base = cfg({ id: "minitest", kind: "ruby", args: ["exec", "ruby", "-Itest", "-e", "SCRIPT", "test"] });
|
||||
const p = dir("test/unit", [base]);
|
||||
expect(p?.config?.args).toEqual(["exec", "ruby", "-Itest", "-Ilib", "-e", "SCRIPT", "test/unit"]);
|
||||
});
|
||||
|
||||
it("the test root itself", () => {
|
||||
const p = dir("test", [cfg({ id: "rspec", kind: "rspec", args: ["exec", "rspec"] })]);
|
||||
expect(p?.config?.args).toEqual(["exec", "rspec", "test"]);
|
||||
});
|
||||
|
||||
it("nothing for non-test folders or projects without a Ruby runner", () => {
|
||||
expect(dir("lib/unit", [cfg({ id: "rspec", kind: "rspec" })])).toBeNull();
|
||||
expect(dir("other/unit", [cfg({ id: "rspec", kind: "rspec" })])).toBeNull();
|
||||
expect(dir("test/unit", [])).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -596,6 +596,10 @@ function simple(
|
||||
return { name: s.file, scope: "file", config: fileConfig(s, kind, t.command, args(t.command, t.name), debugAdapter) };
|
||||
}
|
||||
|
||||
/** `-I<dir>` for each Sources root of the project (the project root itself is already the cwd). */
|
||||
const rubyLoadPath = (roots: readonly { path: string; kind: string }[]): string[] =>
|
||||
roots.filter((r) => r.kind === "source" && r.path !== "").map((r) => `-I${r.path}`);
|
||||
|
||||
const hasGoMain = (content?: string) => content === undefined || /^\s*package\s+main\b/m.test(content);
|
||||
const hasRustMain = (content?: string) =>
|
||||
content === undefined || /^(?:pub\s+)?(?:async\s+)?fn\s+main\s*\(/m.test(content);
|
||||
@@ -798,6 +802,8 @@ export function produceFile(input: ContextInput, env: ToolEnv): Produced | null
|
||||
};
|
||||
}
|
||||
case "ruby":
|
||||
// Sources roots go on the load path (`-Ilib`), as `rake test` does; paths are project-relative.
|
||||
return simple(lang, s, env, lang, () => [...(s.inProject ? rubyLoadPath(currentRoots()) : []), s.arg]);
|
||||
case "php":
|
||||
case "perl":
|
||||
case "lua":
|
||||
@@ -1021,6 +1027,33 @@ function cmakeTargets(input: ContextInput): RunConfig[] {
|
||||
|
||||
const TEST_DIR = /(^|\/)(tests?|__tests__|specs?|test_?\w*)$/;
|
||||
|
||||
/**
|
||||
* The tests of a Ruby folder: RSpec on the folder, `rake test` with `TEST=<glob>`, or the
|
||||
* detected Minitest runner given the folder (Sources roots on its load path).
|
||||
*/
|
||||
function rubyDirectory(
|
||||
closest: (kinds: string[]) => RunConfig | undefined,
|
||||
relToCwd: (c: RunConfig) => string,
|
||||
make: (base: RunConfig, label: string, args: string[]) => Produced,
|
||||
label: string,
|
||||
): Produced | null {
|
||||
const rspec = closest(["rspec"]);
|
||||
if (rspec) return make(rspec, label, [...rspec.args, relToCwd(rspec)]);
|
||||
const rake = closest(["rake"]);
|
||||
if (rake && rake.args.at(-1) === "test") {
|
||||
// Rake splits `TEST` at commas into patterns, so no braces: two globs.
|
||||
const dir = relToCwd(rake);
|
||||
return make(rake, label, [...rake.args, `TEST=${dir}/**/*_test.rb,${dir}/**/test_*.rb`]);
|
||||
}
|
||||
const minitest = closest(["ruby"]);
|
||||
if (!minitest) return null;
|
||||
const at = minitest.args.indexOf("-e");
|
||||
const flags = rubyLoadPath(currentRoots()).filter((f) => !minitest.args.includes(f));
|
||||
const args = [...minitest.args.slice(0, -1)];
|
||||
args.splice(at < 0 ? args.length : at, 0, ...flags);
|
||||
return make(minitest, label, [...args, relToCwd(minitest)]);
|
||||
}
|
||||
|
||||
export function produceDirectory(input: ContextInput): Produced | null {
|
||||
const { root } = input;
|
||||
if (!root || !isProjectPath(input) || input.path === root) return null;
|
||||
@@ -1029,8 +1062,8 @@ export function produceDirectory(input: ContextInput): Produced | null {
|
||||
const jvmRoot = /^(?:(.*)\/)?src\/test(?:\/(java|kotlin|scala|groovy))?$/.exec(rel);
|
||||
const jvmPackage = /^(?:(.*)\/)?src\/test\/(?:java|kotlin|scala|groovy)\/(.+)$/.exec(rel);
|
||||
const marked = isTestPath(currentRoots(), rel);
|
||||
if (!TEST_DIR.test(rel) && !jvmRoot && !jvmPackage && !marked && !/(^|\/)src\/test(\/|$)/.test(rel)) return null;
|
||||
const tests = input.configs.filter((c) => c.isTest);
|
||||
if (!TEST_DIR.test(rel) && !jvmRoot && !jvmPackage && !marked && !/(^|\/)src\/test(\/|$)/.test(rel)) return null;
|
||||
const closest = (kinds: string[]) => {
|
||||
const cands = tests.filter((c) => kinds.includes(c.kind));
|
||||
const score = (c: RunConfig) => {
|
||||
@@ -1071,6 +1104,8 @@ export function produceDirectory(input: ContextInput): Produced | null {
|
||||
}
|
||||
}
|
||||
const js = closest(["npm", "bun", "deno"]);
|
||||
const ruby = marked && !js && rubyDirectory(closest, relToCwd, make, testsIn);
|
||||
if (ruby) return ruby;
|
||||
const py = closest(["pytest"]);
|
||||
const django = closest(["django-test"]);
|
||||
const go = closest(["go"]);
|
||||
|
||||
Reference in new issue
Заблокувати користувача