дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
* fix(sdk): keep the drive letter of a Maven home found on a mapped drive Maven discovery canonicalized mvn on PATH and each Maven home to resolve symlinks (Homebrew's bin/mvn). On Windows, a home on a mapped network drive or a subst drive then became \\?\UNC\server\share\… (or another drive), the Maven home setting picked from the list stored that path, and cmd.exe can't start mvn.cmd from it: every Maven run failed with "The system cannot find the path specified". Discovery now keeps the path as found when canonicalizing would change its drive letter. Refs #822 * fix(sdk): keep the drive letter of a JDK home on a mapped drive A JDK on a mapped network drive was canonicalized to \\?\UNC\server\share\… too, so Maven ran with that JAVA_HOME and mvn.cmd failed with "The system cannot find the path specified" even with a local Maven home. JDK homes (configured, JAVA_HOME, the java on PATH) now keep their drive letter like Maven homes do. * fix(maven): resolve current toolchain settings for each run * fix(sdk): distinguish mapped roots from external launcher links * test(run): preserve explicit Maven choices across save and reload * fix(maven): keep edited nested test launchers when refreshing defaults
324 рядки
10 KiB
TypeScript
324 рядки
10 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { GradleModel, MavenModel, MavenProject, RunConfig } from "@/platform/ipc";
|
|
import {
|
|
expandableKeys,
|
|
goalCompletions,
|
|
gradleChildren,
|
|
gradleRoots,
|
|
gradleTaskConfig,
|
|
isRunnable,
|
|
mavenChildren,
|
|
mavenGoalConfig,
|
|
mavenRoots,
|
|
nodeIcon,
|
|
nodeLabel,
|
|
parseGoalLine,
|
|
parseTaskLine,
|
|
profilesArg,
|
|
splitArgs,
|
|
taskCompletions,
|
|
type BuildNode,
|
|
} from "./model";
|
|
|
|
function project(over: Partial<MavenProject>): MavenProject {
|
|
return {
|
|
pom: "pom.xml",
|
|
dir: "",
|
|
groupId: "demo",
|
|
artifactId: "parent",
|
|
version: "1.0",
|
|
packaging: "pom",
|
|
displayName: "parent",
|
|
parent: null,
|
|
modules: [],
|
|
aggregated: false,
|
|
plugins: [],
|
|
dependencies: [],
|
|
profiles: [],
|
|
javaVersion: 21,
|
|
command: "/p/mvnw",
|
|
options: [],
|
|
env: { JAVA_HOME: "/jdk21" },
|
|
...over,
|
|
};
|
|
}
|
|
|
|
const surefire = {
|
|
groupId: "org.apache.maven.plugins",
|
|
artifactId: "maven-surefire-plugin",
|
|
version: "3.2.5",
|
|
prefix: "surefire",
|
|
goals: ["help", "test"],
|
|
};
|
|
|
|
const maven: MavenModel = {
|
|
projects: [
|
|
project({ modules: ["core/pom.xml", "app/pom.xml"], displayName: "Multi Demo", profiles: ["dev", "ci"] }),
|
|
project({
|
|
pom: "core/pom.xml",
|
|
dir: "core",
|
|
artifactId: "core",
|
|
displayName: "core",
|
|
packaging: "jar",
|
|
aggregated: true,
|
|
plugins: [surefire],
|
|
dependencies: [
|
|
{
|
|
groupId: "org.junit.jupiter",
|
|
artifactId: "junit-jupiter",
|
|
version: "5.10.2",
|
|
type: "jar",
|
|
classifier: null,
|
|
scope: "test",
|
|
optional: false,
|
|
children: [
|
|
{
|
|
groupId: "org.junit.jupiter",
|
|
artifactId: "junit-jupiter-api",
|
|
version: "5.10.2",
|
|
type: "jar",
|
|
classifier: null,
|
|
scope: "test",
|
|
optional: false,
|
|
children: [],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}),
|
|
project({
|
|
pom: "app/pom.xml",
|
|
dir: "app",
|
|
artifactId: "app",
|
|
displayName: "Demo App",
|
|
packaging: "jar",
|
|
aggregated: true,
|
|
}),
|
|
],
|
|
profiles: [
|
|
{ id: "dev", activeByDefault: true },
|
|
{ id: "ci", activeByDefault: false },
|
|
],
|
|
syncedAt: null,
|
|
stale: false,
|
|
syncError: null,
|
|
};
|
|
|
|
const runConfig = (over: Partial<RunConfig>): RunConfig => ({
|
|
id: "x",
|
|
name: "x",
|
|
kind: "maven",
|
|
command: "mvn",
|
|
args: [],
|
|
cwd: null,
|
|
env: {},
|
|
detected: true,
|
|
isTest: false,
|
|
debugAdapter: null,
|
|
...over,
|
|
});
|
|
|
|
const children = (n: BuildNode) => mavenChildren(n, maven, []) ?? [];
|
|
const labels = (nodes: BuildNode[]) => nodes.map(nodeLabel);
|
|
|
|
describe("Maven tree", () => {
|
|
it("shows profiles, then the aggregator with lifecycle, plugins and its modules", () => {
|
|
const roots = mavenRoots(maven);
|
|
expect(labels(roots)).toEqual(["Profiles", "Multi Demo"]);
|
|
expect(labels(children(roots[0]!))).toEqual(["dev", "ci"]);
|
|
const top = children(roots[1]!);
|
|
expect(labels(top)).toEqual(["Lifecycle", "Plugins", "core", "Demo App"]);
|
|
expect(labels(children(top[0]!))).toEqual([
|
|
"clean",
|
|
"validate",
|
|
"compile",
|
|
"test",
|
|
"package",
|
|
"verify",
|
|
"install",
|
|
"site",
|
|
"deploy",
|
|
]);
|
|
expect(nodeIcon(top[2]!)).toBe("maven-module");
|
|
expect(nodeIcon(roots[1]!)).toBe("maven-project");
|
|
});
|
|
|
|
it("expands plugins to goals and dependencies to their transitive ones", () => {
|
|
const core = children(mavenRoots(maven)[1]!)[2]!;
|
|
const kids = children(core);
|
|
expect(labels(kids)).toEqual(["Lifecycle", "Plugins", "Dependencies"]);
|
|
const plugin = children(kids[1]!)[0]!;
|
|
expect(nodeLabel(plugin)).toBe("surefire (org.apache.maven.plugins:maven-surefire-plugin:3.2.5)");
|
|
const goals = children(plugin);
|
|
expect(labels(goals)).toEqual(["surefire:help", "surefire:test"]);
|
|
expect(isRunnable(goals[1]!)).toBe(true);
|
|
const deps = children(kids[2]!);
|
|
expect(labels(deps)).toEqual(["org.junit.jupiter:junit-jupiter:5.10.2"]);
|
|
expect(labels(children(deps[0]!))).toEqual(["org.junit.jupiter:junit-jupiter-api:5.10.2"]);
|
|
expect(isRunnable(deps[0]!)).toBe(false);
|
|
});
|
|
|
|
it("lists the project's maven run configurations", () => {
|
|
const configs = [
|
|
runConfig({ id: "a", name: "core [test]", cwd: "core", detected: false }),
|
|
runConfig({ id: "b", name: "root", cwd: null }),
|
|
runConfig({ id: "c", kind: "npm", cwd: "core" }),
|
|
];
|
|
const top = mavenChildren(mavenRoots(maven)[1]!, maven, configs)!;
|
|
expect(labels(top)).toEqual(["Lifecycle", "Plugins", "Run Configurations", "core", "Demo App"]);
|
|
expect(labels(mavenChildren(top[2]!, maven, configs)!)).toEqual(["root"]);
|
|
const runs = mavenChildren(top[3]!, maven, configs)!.find((n) => n.kind === "runConfigs")!;
|
|
expect(labels(mavenChildren(runs, maven, configs)!)).toEqual(["core [test]"]);
|
|
});
|
|
|
|
it("expand all opens folders but not dependency subtrees", () => {
|
|
const keys = expandableKeys(mavenRoots(maven), (n) => mavenChildren(n, maven, []));
|
|
expect(keys.has("mvn:pom.xml")).toBe(true);
|
|
expect(keys.has("mvn:core/pom.xml:deps")).toBe(true);
|
|
expect([...keys].some((k) => k.includes("deps/"))).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("Maven runs", () => {
|
|
const core = maven.projects[1]!;
|
|
|
|
it("passes goals, offline, skip tests and changed profiles", () => {
|
|
expect(profilesArg(maven.profiles, {})).toBeNull();
|
|
expect(profilesArg(maven.profiles, { ci: true, dev: false })).toBe("!dev,ci");
|
|
const config = mavenGoalConfig(core, ["clean", "test"], maven, {
|
|
offline: true,
|
|
skipTests: false,
|
|
profiles: { ci: true },
|
|
});
|
|
expect(config).toMatchObject({
|
|
name: "core [clean,test]",
|
|
kind: "maven",
|
|
command: "/p/mvnw",
|
|
args: ["clean", "test", "-o", "-Pci"],
|
|
cwd: "core",
|
|
env: { JAVA_HOME: "/jdk21" },
|
|
isTest: true,
|
|
detected: false,
|
|
});
|
|
const skipped = mavenGoalConfig(core, ["test"], maven, { offline: false, skipTests: true, profiles: {} });
|
|
expect(skipped.args).toEqual(["test", "-DskipTests"]);
|
|
expect(skipped.isTest).toBe(false);
|
|
const root = mavenGoalConfig(maven.projects[0]!, ["clean"], maven, {
|
|
offline: false,
|
|
skipTests: false,
|
|
profiles: {},
|
|
});
|
|
expect(root.cwd).toBeNull();
|
|
expect(root.isTest).toBe(false);
|
|
const configured = {
|
|
...core,
|
|
command: "//?/UNC/server/share/maven/bin/mvn.cmd",
|
|
options: ["-s", "/old/settings.xml", "-Dmaven.repo.local=/old/repository"],
|
|
};
|
|
const goal = mavenGoalConfig(configured, ["test"], maven, {
|
|
offline: false,
|
|
skipTests: true,
|
|
profiles: {},
|
|
});
|
|
expect(goal.command).toBe("mvn");
|
|
expect(goal.args).toEqual(["test", "-DskipTests"]);
|
|
expect(
|
|
mavenGoalConfig(configured, ["-s", "/own/settings.xml", "test"], maven, {
|
|
offline: false,
|
|
skipTests: false,
|
|
profiles: {},
|
|
}).args,
|
|
).toEqual(["-s", "/own/settings.xml", "test"]);
|
|
});
|
|
|
|
it("parses typed command lines", () => {
|
|
expect(parseGoalLine("mvn clean install -Dmsg='a b'")).toEqual(["clean", "install", "-Dmsg=a b"]);
|
|
expect(parseGoalLine("./mvnw -q verify")).toEqual(["-q", "verify"]);
|
|
expect(parseTaskLine('./gradlew :app:run --args "x y"')).toEqual([":app:run", "--args", "x y"]);
|
|
expect(splitArgs(`a "" 'b c'`)).toEqual(["a", "", "b c"]);
|
|
});
|
|
|
|
it("completes phases and plugin goals for the last word, history for the whole line", () => {
|
|
const c = goalCompletions("clean te", [surefire], ["clean test -DskipTests"]);
|
|
expect(c.map((x) => x.label)).toEqual(["test", "test-compile"]);
|
|
expect(c[0]!.line).toBe("clean test");
|
|
expect(goalCompletions("sure", [surefire]).map((x) => x.label)).toEqual(["surefire:help", "surefire:test"]);
|
|
expect(goalCompletions("cl", [], ["clean install"]).map((x) => x.label)[0]).toBe("clean install");
|
|
expect(goalCompletions("-D", [surefire])).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("Gradle tree and runs", () => {
|
|
const gradle: GradleModel = {
|
|
dir: "",
|
|
projects: [
|
|
{
|
|
path: ":",
|
|
name: "gradle-demo",
|
|
dir: "",
|
|
buildFile: "build.gradle.kts",
|
|
tasks: [
|
|
{ name: "wrapper", group: "build setup", description: null },
|
|
{ name: "init", group: "build setup", description: null },
|
|
],
|
|
},
|
|
{
|
|
path: ":app",
|
|
name: "app",
|
|
dir: "app",
|
|
buildFile: "app/build.gradle.kts",
|
|
tasks: [
|
|
{ name: "run", group: "application", description: "Runs this project" },
|
|
{ name: "compileJava", group: "other", description: null },
|
|
{ name: "test", group: "verification", description: null },
|
|
{ name: "build", group: "build", description: null },
|
|
],
|
|
},
|
|
{ path: ":libs:core", name: "core", dir: "libs/core", buildFile: null, tasks: [] },
|
|
],
|
|
javaVersion: null,
|
|
command: "/p/gradlew",
|
|
env: {},
|
|
syncedAt: 1,
|
|
stale: false,
|
|
syncError: null,
|
|
};
|
|
const kids = (n: BuildNode) =>
|
|
gradleChildren(n, gradle, [runConfig({ id: "g", name: "app [run]", kind: "gradle" })]) ?? [];
|
|
|
|
it("nests subprojects and groups tasks, other last", () => {
|
|
const roots = gradleRoots(gradle);
|
|
expect(labels(roots)).toEqual(["gradle-demo"]);
|
|
expect(labels(kids(roots[0]!))).toEqual(["Tasks", "Run Configurations", "app", "core"]);
|
|
const app = kids(roots[0]!)[2]!;
|
|
const groups = kids(kids(app)[0]!);
|
|
expect(labels(groups)).toEqual(["application", "build", "verification", "other"]);
|
|
expect(labels(kids(kids(kids(roots[0]!)[0]!)[0]!))).toEqual(["init", "wrapper"]);
|
|
expect(nodeIcon(groups[0]!)).toBe("build-task-group");
|
|
});
|
|
|
|
it("runs tasks by path from the build directory", () => {
|
|
const app = gradle.projects[1]!;
|
|
const config = gradleTaskConfig(gradle, app, ["test"], { offline: true });
|
|
expect(config).toMatchObject({
|
|
name: "app [test]",
|
|
kind: "gradle",
|
|
command: "/p/gradlew",
|
|
args: [":app:test", "--offline"],
|
|
cwd: null,
|
|
isTest: true,
|
|
});
|
|
expect(gradleTaskConfig(gradle, gradle.projects[0]!, ["wrapper"], { offline: false }).args).toEqual(["wrapper"]);
|
|
expect(gradleTaskConfig(gradle, app, [":lib:jar", "--info"], { offline: false }).args).toEqual([
|
|
":lib:jar",
|
|
"--info",
|
|
]);
|
|
});
|
|
|
|
it("completes task names and paths", () => {
|
|
const labelsOf = taskCompletions("ru", gradle).map((c) => c.label);
|
|
expect(labelsOf).toEqual([":app:run"]);
|
|
expect(taskCompletions("clean wr", gradle).map((c) => c.line)).toEqual(["clean wrapper"]);
|
|
});
|
|
});
|