bynk/test.rs
1//! `bynk test` — discover and run a project's test declarations.
2//!
3//! Delegates to the `bynkc` the driver resolves (`BYNK_BYNKC` → PATH →
4//! sibling-of-`bynk`) and shells `bynkc test`, forwarding every flag verbatim
5//! (v0.138, #487). Unlike `check`/`fmt`, `test` delegates *always*, not only
6//! under an override: it orchestrates external `tsc`/`node`, so it is a
7//! subprocess regardless, and routing it through the driver's resolution is the
8//! whole point — an editor or developer inherits that resolution instead of
9//! locating `bynkc` themselves (the fix direction for #486). The trade-off (a
10//! `bynkc` binary must be present, and the driver↔compiler skew surface stays)
11//! is accepted; the driver at least resolves it more richly than any editor.
12//! #1675: and it acts on that skew before delegating — see
13//! [`crate::compiler::skew_gate`].
14
15use std::ffi::OsString;
16use std::process::ExitCode;
17
18use bynk_driver::test_runner::TestArgs;
19
20use crate::compiler::{Compiler, SkewAsker, skew_gate};
21
22/// Run `bynk test` by shelling the resolved `bynkc`. When no `bynkc` could be
23/// located, point the developer at `bynk doctor` rather than emitting a raw
24/// spawn error.
25///
26/// Wave 5 §5.4 (findings #40/#72): `args` is the one contract `bynkc test`
27/// and `bynk test` both flatten (`bynk_driver::test_runner::TestArgs`) —
28/// this function's whole body is re-spelling its fields back out as argv
29/// tokens for the shell-out, so a field added there needs no matching change
30/// here beyond this list.
31pub fn run(compiler: &Compiler, args: TestArgs, allow_skew: bool) -> ExitCode {
32 let Some(bynkc) = compiler.path.as_deref() else {
33 eprintln!(
34 "bynk test: no `bynkc` compiler found (looked at $BYNK_BYNKC, PATH, and next to `bynk`)."
35 );
36 eprintln!(" Run `bynk doctor --only test` for the exact remedy.");
37 return ExitCode::FAILURE;
38 };
39 // #1675: `test` always runs a second compiler, so act on its skew from the
40 // driver: a minor skew warns, a major one refuses unless allowed.
41 let asker = SkewAsker {
42 command: Some("test"),
43 has_flag: true,
44 };
45 if !skew_gate(compiler, asker, allow_skew) {
46 return ExitCode::FAILURE;
47 }
48
49 let mut argv: Vec<OsString> = vec!["test".into(), args.input.into_os_string()];
50 if let Some(output) = args.output {
51 argv.push("--output".into());
52 argv.push(output.into_os_string());
53 }
54 if args.no_run {
55 argv.push("--no-run".into());
56 }
57 argv.push("--format".into());
58 argv.push(args.format.as_bynkc_arg().into());
59 if args.inspect {
60 argv.push("--inspect".into());
61 }
62 if let Some(seed) = args.seed {
63 argv.push("--seed".into());
64 argv.push(seed.into());
65 }
66 if let Some(case) = args.case {
67 argv.push("--case".into());
68 argv.push(case.into());
69 }
70 if args.coverage {
71 argv.push("--coverage".into());
72 }
73
74 crate::shell::delegate(bynkc, argv)
75}