use std::collections::BTreeMap;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
#[cfg(windows)]
use std::time::Duration;
use wvq_runtime::{
default_limits, discover_executor_targets, Executor, ExecutorId, ExecutorRegistry,
PrepareRequest, RuntimeError,
};
#[cfg(windows)]
use wvq_runtime::{ExecutorSpec, ProcessLimits};
fn request(id: &str, extra: BTreeMap<String, String>) -> PrepareRequest {
PrepareRequest {
executor: ExecutorId::new(id).unwrap(),
cwd: std::env::temp_dir(),
filters: Vec::new(),
exact_case: None,
extra,
limits: default_limits(),
cancel: Arc::new(AtomicBool::new(false)),
}
}
#[test]
fn unknown_executor_id_fails() {
let registry = ExecutorRegistry::production().unwrap();
let err = registry
.prepare(request("bash", BTreeMap::new()))
.unwrap_err();
assert!(matches!(err, RuntimeError::UnknownExecutor(id) if id == "bash"));
}
#[test]
fn cargo_test_disables_color_for_machine_readable_case_evidence() {
let prepared = ExecutorRegistry::production()
.unwrap()
.prepare(request("cargo-test", BTreeMap::new()))
.unwrap();
assert_eq!(prepared.program, "cargo");
assert_eq!(
prepared.args,
["test", "--color", "never", "--workspace", "--all-targets"]
);
}
#[test]
fn cargo_test_name_filter_is_a_positional_testname() {
let registry = ExecutorRegistry::production().unwrap();
let mut req = request("cargo-test", BTreeMap::new());
req.filters = vec!["an_unmeasured_axis_never_becomes_clean".into()];
let prepared = registry.prepare(req).unwrap();
assert_eq!(
prepared.args,
[
"test",
"--color",
"never",
"--workspace",
"--all-targets",
"an_unmeasured_axis_never_becomes_clean"
]
);
}
#[test]
fn registered_go_test_gets_frozen_typed_argv() {
let registry = ExecutorRegistry::production().unwrap();
let mut req = request("go-test", BTreeMap::new());
req.filters = vec!["TestAdd".into()];
let prepared = registry.prepare(req).unwrap();
assert_eq!(prepared.program, "go");
assert_eq!(
prepared.args,
[
"test",
"-json",
"-coverprofile=.weavatrix-quality/go-cover.out",
"./...",
"-run",
"TestAdd"
]
);
assert_eq!(
registry
.capabilities(&prepared.executor)
.map(|caps| caps.cases),
Some(true)
);
}
#[test]
fn path_filters_share_one_runner_process_without_becoming_name_patterns() {
let registry = ExecutorRegistry::production().unwrap();
let paths = vec!["tests/alpha.test.ts".into(), "tests/beta.test.ts".into()];
let mut vitest = request("vitest", BTreeMap::new());
vitest.filters.clone_from(&paths);
assert_eq!(
registry.prepare(vitest).unwrap().args,
[
"exec",
"--offline",
"--yes=false",
"--",
"vitest",
"run",
"--reporter=junit",
"--outputFile=.weavatrix-quality/junit.xml",
"tests/alpha.test.ts",
"tests/beta.test.ts"
]
);
let mut storybook = request("storybook-vitest-v8", BTreeMap::new());
storybook.filters = vec!["src/Button.stories.tsx".into()];
assert_eq!(
registry.prepare(storybook).unwrap().args,
[
"exec",
"--offline",
"--yes=false",
"--",
"vitest",
"run",
"--project=storybook",
"--coverage",
"--coverage.reporter=lcov",
"--reporter=junit",
"--outputFile=.weavatrix-quality/junit.xml",
"src/Button.stories.tsx"
]
);
let mut jest = request("jest", BTreeMap::new());
jest.filters = paths;
assert_eq!(
registry.prepare(jest).unwrap().args,
[
"--runInBand",
"--runTestsByPath",
"tests/alpha.test.ts",
"tests/beta.test.ts"
]
);
}
#[test]
fn exact_case_filters_are_frozen_and_regex_escaped() {
let registry = ExecutorRegistry::production().unwrap();
let mut vitest = request("vitest", BTreeMap::new());
vitest.filters = vec!["tests/limit.test.ts".into()];
vitest.exact_case = Some("boundary [five]".into());
assert_eq!(
®istry.prepare(vitest).unwrap().args[8..],
[
"tests/limit.test.ts",
"--testNamePattern",
"^boundary \\[five\\]$"
]
);
let mut go = request("go-test", BTreeMap::new());
go.exact_case = Some("TestAllowed".into());
assert_eq!(
®istry.prepare(go).unwrap().args[4..],
["-run", "^TestAllowed$"]
);
}
#[test]
fn storybook_vitest_is_discovered_as_a_distinct_browser_project() {
let root = std::env::temp_dir().join(format!("wvq-discovery-storybook-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(root.join(".storybook")).unwrap();
std::fs::write(
root.join("package.json"),
r#"{"scripts":{"test":"vitest run"},"devDependencies":{"vitest":"4","@storybook/addon-vitest":"10","@vitest/coverage-v8":"4"}}"#,
)
.unwrap();
let targets = discover_executor_targets(&root).unwrap();
let ids = targets
.iter()
.map(|target| target.executor.as_str())
.collect::<Vec<_>>();
assert_eq!(ids, ["storybook-vitest-v8", "vitest"]);
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn repository_discovery_returns_only_registered_ids() {
let root = std::env::temp_dir().join(format!("wvq-discovery-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(root.join("web")).unwrap();
std::fs::write(root.join("Cargo.toml"), "[workspace]\n").unwrap();
std::fs::write(
root.join("web/package.json"),
r#"{"scripts":{"test":"vitest run"},"devDependencies":{"vitest":"1"}}"#,
)
.unwrap();
let targets = discover_executor_targets(&root).unwrap();
let ids: Vec<&str> = targets
.iter()
.map(|target| target.executor.as_str())
.collect();
assert_eq!(ids, ["cargo-test", "vitest"]);
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn complex_repository_test_script_stays_on_the_generic_npm_boundary() {
let root = std::env::temp_dir().join(format!("wvq-discovery-complex-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).unwrap();
std::fs::write(
root.join("package.json"),
r#"{"scripts":{"test":"node prepare.mjs && vitest run --coverage"},"devDependencies":{"vitest":"4"}}"#,
)
.unwrap();
let targets = discover_executor_targets(&root).unwrap();
assert_eq!(targets[0].executor.as_str(), "npm-test");
let _ = std::fs::remove_dir_all(root);
}
#[test]
fn mcp_command_field_cannot_select_an_executable() {
let registry = ExecutorRegistry::production().unwrap();
let mut extra = BTreeMap::new();
extra.insert("command".into(), "rm -rf /".into());
extra.insert("program".into(), "powershell.exe".into());
let err = registry.prepare(request("vitest", extra)).unwrap_err();
assert!(matches!(err, RuntimeError::InvalidArg(msg) if msg.contains("executable")));
}
#[test]
fn filter_shell_metacharacters_are_rejected() {
let registry = ExecutorRegistry::production().unwrap();
let mut req = request("vitest", BTreeMap::new());
req.filters = vec!["ok | calc.exe".into()];
assert!(matches!(
registry.prepare(req),
Err(RuntimeError::InvalidArg(_))
));
}
#[cfg(windows)]
#[test]
fn deadline_output_and_cancel_limits_are_enforced() {
let mut registry = ExecutorRegistry::new();
registry
.register(ExecutorSpec {
id: ExecutorId::new("wvq-echo").unwrap(),
program: "cmd.exe".into(),
prefix: vec!["/d".into(), "/c".into(), "echo wvq-ok".into()],
filter_flag: None,
capabilities: wvq_runtime::ExecutorCapabilities {
cases: false,
coverage: false,
},
})
.unwrap();
registry
.register(ExecutorSpec {
id: ExecutorId::new("wvq-wait").unwrap(),
program: "cmd.exe".into(),
prefix: vec!["/d".into(), "/c".into(), "ping -n 20 127.0.0.1 >nul".into()],
filter_flag: None,
capabilities: wvq_runtime::ExecutorCapabilities {
cases: false,
coverage: false,
},
})
.unwrap();
let echo = registry
.prepare(request("wvq-echo", BTreeMap::new()))
.unwrap();
let ok = registry.execute(&echo).unwrap();
let text = String::from_utf8_lossy(&ok.stdout);
assert!(text.contains("wvq-ok"), "{text}");
let mut tiny = request("wvq-echo", BTreeMap::new());
tiny.limits = ProcessLimits {
deadline: Duration::from_secs(5),
max_output_bytes: 1,
};
let prepared = registry.prepare(tiny).unwrap();
assert!(matches!(
registry.execute(&prepared),
Err(RuntimeError::OutputLimit { max: 1 })
));
let mut wait = request("wvq-wait", BTreeMap::new());
wait.limits = ProcessLimits {
deadline: Duration::from_millis(80),
max_output_bytes: 64 * 1024,
};
let prepared = registry.prepare(wait).unwrap();
assert!(matches!(
registry.execute(&prepared),
Err(RuntimeError::DeadlineExceeded)
));
let cancelled = request("wvq-echo", BTreeMap::new());
cancelled
.cancel
.store(true, std::sync::atomic::Ordering::SeqCst);
let prepared = registry.prepare(cancelled).unwrap();
assert!(matches!(
registry.execute(&prepared),
Err(RuntimeError::Cancelled)
));
}