mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 10:16:03 -10:00
feat(run): pytest run configuration type (#274)
Add Configuration had no pytest template although detected and implicit pytest configurations already used the pytest kind. A pytest type now offers Command, Runner arguments, Target and pytest options, and opens existing pytest configurations in that form: pytest itself, `uv/poetry run pytest`, `python -m pytest` (also behind an interpreter prefix such as `uv run python -m pytest`), venv `bin/pytest` and Windows `Scripts\\pytest.exe`. Fields are translated. Fixes #266 Co-authored-by: a-rookie-of-C-language <[email protected]>
This commit is contained in:
1 parent
5217f8570b
commit
1602e4b530
4 files changed
+164
-1
No files matched your search
@@ -16,6 +16,7 @@ import { typeRows } from "./AddConfigPopup";
|
||||
import { runBeforeLaunch } from "./beforeLaunch";
|
||||
import { buildRows } from "./ConfigChooser";
|
||||
import { compoundChildren, compoundCycle, formatEnv, parseEnv, validateConfig } from "./configs";
|
||||
import { contextConfig } from "./context";
|
||||
import { EditConfigsOverlay, moveConfig, treeRows } from "./EditConfigsDialog";
|
||||
import { pasteRows } from "./EnvDialog";
|
||||
import { CURRENT_FILE, resetRuns, selectConfig, sessionsOf, startCompound, startRun, useRun } from "./store";
|
||||
@@ -101,6 +102,87 @@ describe("configuration types", () => {
|
||||
expect(godot.paramsFrom!(cfg("x", { command: "cargo" }))).toBeNull();
|
||||
});
|
||||
|
||||
it("pytest round-trips commandLine / paramsFrom and opens implicit context configs in the form", () => {
|
||||
const pytest = typeFor("pytest")!;
|
||||
expect(pytest.debugAdapter).toBe("debugpy");
|
||||
expect(newConfigOfType(pytest, [])).toMatchObject({
|
||||
kind: "pytest",
|
||||
command: "pytest",
|
||||
args: [],
|
||||
debugAdapter: "debugpy",
|
||||
params: { command: "pytest" },
|
||||
});
|
||||
|
||||
const cases: Array<{
|
||||
command: string;
|
||||
args: string[];
|
||||
params: { command: string; runnerArgs: string; target: string; options: string };
|
||||
}> = [
|
||||
{
|
||||
command: "pytest",
|
||||
args: ["tests", "-x"],
|
||||
params: { command: "pytest", runnerArgs: "", target: "tests", options: "-x" },
|
||||
},
|
||||
{
|
||||
command: "uv",
|
||||
args: ["run", "pytest", "tests/test_a.py::test_x", "-q"],
|
||||
params: { command: "uv", runnerArgs: "run pytest", target: "tests/test_a.py::test_x", options: "-q" },
|
||||
},
|
||||
{
|
||||
command: "python",
|
||||
args: ["-m", "pytest"],
|
||||
params: { command: "python", runnerArgs: "-m pytest", target: "", options: "" },
|
||||
},
|
||||
{
|
||||
command: ".venv/bin/pytest",
|
||||
args: [],
|
||||
params: { command: ".venv/bin/pytest", runnerArgs: "", target: "", options: "" },
|
||||
},
|
||||
{
|
||||
command: ".venv\\Scripts\\pytest.exe",
|
||||
args: [],
|
||||
params: { command: ".venv\\Scripts\\pytest.exe", runnerArgs: "", target: "", options: "" },
|
||||
},
|
||||
];
|
||||
|
||||
for (const { command, args, params } of cases) {
|
||||
const c = cfg("pytest", { kind: "pytest", command, args, detected: true });
|
||||
expect(pytest.paramsFrom!(c)).toEqual(params);
|
||||
expect(formParams(c)).toEqual(params);
|
||||
expect(pytest.commandLine(params)).toEqual({ command, args });
|
||||
expect(withParams(c, params)).toMatchObject({ command, args });
|
||||
}
|
||||
|
||||
const implicit = contextConfig("worker/test_worker.py", [])!;
|
||||
expect(implicit).toMatchObject({
|
||||
kind: "pytest",
|
||||
command: "python3",
|
||||
args: ["-m", "pytest", "worker/test_worker.py"],
|
||||
});
|
||||
expect(formParams(implicit)).toEqual({
|
||||
command: "python3",
|
||||
runnerArgs: "-m pytest",
|
||||
target: "worker/test_worker.py",
|
||||
options: "",
|
||||
});
|
||||
|
||||
const uvPy = [cfg("py", { kind: "python", command: "uv", args: ["run", "python", "-m", "app"] })];
|
||||
const uvImplicit = contextConfig("worker/test_worker.py", uvPy)!;
|
||||
expect(uvImplicit).toMatchObject({
|
||||
kind: "pytest",
|
||||
command: "uv",
|
||||
args: ["run", "python", "-m", "pytest", "worker/test_worker.py"],
|
||||
});
|
||||
expect(formParams(uvImplicit)).toEqual({
|
||||
command: "uv",
|
||||
runnerArgs: "run python -m pytest",
|
||||
target: "worker/test_worker.py",
|
||||
options: "",
|
||||
});
|
||||
|
||||
expect(pytest.paramsFrom!(cfg("pytest", { kind: "pytest", command: "tox", args: [] }))).toBeNull();
|
||||
});
|
||||
|
||||
it("shell script files and text derive the command line", () => {
|
||||
const c = cfg("s");
|
||||
expect(
|
||||
|
||||
@@ -296,6 +296,75 @@ const compound: RunConfigTypeContribution = {
|
||||
commandLine: () => null,
|
||||
};
|
||||
|
||||
/**
|
||||
* pytest: what comes after the `pytest` invocation — a target (directory, file,
|
||||
* test id) and options. The command itself is one word (`pytest`, `uv`,
|
||||
* `.venv/bin/pytest`, `python`), the runner args its leading arguments
|
||||
* (`run pytest`, `-m pytest`, none).
|
||||
*/
|
||||
const pytest: RunConfigTypeContribution = {
|
||||
id: "pytest",
|
||||
label: "pytest",
|
||||
icon: "file-python",
|
||||
description: "Run pytest tests",
|
||||
defaults: { command: "pytest" },
|
||||
debugAdapter: "debugpy",
|
||||
fields: [
|
||||
{
|
||||
key: "command",
|
||||
label: "Command",
|
||||
mono: true,
|
||||
placeholder: "pytest",
|
||||
required: true,
|
||||
hint: "e.g. pytest, uv, poetry, .venv/bin/pytest, python",
|
||||
},
|
||||
{
|
||||
key: "runnerArgs",
|
||||
label: "Runner arguments",
|
||||
mono: true,
|
||||
placeholder: "run pytest",
|
||||
hint: "e.g. `run pytest` for uv/poetry, `-m pytest` for python; empty calls the command itself",
|
||||
},
|
||||
{
|
||||
key: "target",
|
||||
label: "Target",
|
||||
mono: true,
|
||||
placeholder: "tests",
|
||||
hint: "Directory, file or test id; empty runs the whole suite",
|
||||
},
|
||||
{ key: "options", label: "pytest options", mono: true, placeholder: "e.g. -x -q --tb=short" },
|
||||
],
|
||||
commandLine(p) {
|
||||
const args = parseArgs(p.runnerArgs ?? "");
|
||||
if (p.target?.trim()) args.push(...parseArgs(p.target.trim()));
|
||||
args.push(...parseArgs(p.options ?? ""));
|
||||
return { command: p.command?.trim() || "pytest", args };
|
||||
},
|
||||
paramsFrom(c) {
|
||||
if (c.kind !== "pytest") return null;
|
||||
// The runner part (`run pytest`, `-m pytest`, none when the command is
|
||||
// pytest itself); what follows is the target and pytest options.
|
||||
const m = c.args.indexOf("-m");
|
||||
const runner =
|
||||
c.args[0] === "run" && c.args[1] === "pytest"
|
||||
? 2
|
||||
: m >= 0 && c.args[m + 1] === "pytest"
|
||||
? m + 2
|
||||
: baseName(c.command).replace(/\.exe$/i, "") === "pytest"
|
||||
? 0
|
||||
: null;
|
||||
if (runner === null) return null;
|
||||
const rest = c.args.slice(runner);
|
||||
const [target, ...options] = rest;
|
||||
return {
|
||||
command: c.command,
|
||||
runnerArgs: formatArgs(c.args.slice(0, runner)),
|
||||
target: target && !target.startsWith("-") ? formatArgs([target]) : "",
|
||||
options: formatArgs(target && !target.startsWith("-") ? options : rest),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const BUILTIN_TYPES: readonly RunConfigTypeContribution[] = [
|
||||
shell,
|
||||
program,
|
||||
@@ -303,6 +372,7 @@ export const BUILTIN_TYPES: readonly RunConfigTypeContribution[] = [
|
||||
gradle,
|
||||
maven,
|
||||
godot,
|
||||
pytest,
|
||||
applicationType,
|
||||
springBootType,
|
||||
compound,
|
||||
|
||||
@@ -4747,5 +4747,13 @@
|
||||
"Cannot delete agent tasks": "无法删除 Agent 任务",
|
||||
"Cannot archive agent tasks": "无法归档 Agent 任务",
|
||||
"Archive ({count})": "归档({count})",
|
||||
"Delete ({count})…": "删除({count})…"
|
||||
"Delete ({count})…": "删除({count})…",
|
||||
"Run pytest tests": "运行 pytest 测试",
|
||||
"e.g. pytest, uv, poetry, .venv/bin/pytest, python": "例如 pytest、uv、poetry、.venv/bin/pytest、python",
|
||||
"Runner arguments": "运行器实参",
|
||||
"e.g. `run pytest` for uv/poetry, `-m pytest` for python; empty calls the command itself": "例如 uv/poetry 使用 `run pytest`,python 使用 `-m pytest`;留空则直接调用命令本身",
|
||||
"Target": "目标",
|
||||
"Directory, file or test id; empty runs the whole suite": "目录、文件或测试 ID;留空则运行整个测试套件",
|
||||
"pytest options": "pytest 选项",
|
||||
"e.g. -x -q --tb=short": "例如 -x -q --tb=short"
|
||||
}
|
||||
@@ -128,6 +128,7 @@ const UNTRANSLATED = new Set<string>([
|
||||
"Spring Boot",
|
||||
"PostgreSQL",
|
||||
"Pyright",
|
||||
"pytest",
|
||||
"Rust",
|
||||
"Rustfmt",
|
||||
"SingleStore",
|
||||
@@ -195,7 +196,9 @@ const UNTRANSLATED = new Set<string>([
|
||||
"PascalCase",
|
||||
"pyproject.toml, .git",
|
||||
"ruff server",
|
||||
"run pytest",
|
||||
"snake_case",
|
||||
"tests",
|
||||
'{ "section": { "key": "value" } }',
|
||||
// Identifiers that only look like text: icon names, error codes, categories, console output
|
||||
"app",
|
||||
|
||||
Reference in new issue
Block a user