use super::*;
use crate::hook::cargo_output_contract::CargoOutputCapture;
fn strings(words: &[&str]) -> Vec<String> {
words.iter().map(|word| (*word).to_owned()).collect()
}
#[test]
fn quoted_manifest_features_and_target_dir_keep_exact_argument_boundaries() {
for command in [
r#"cargo build --bin 'app' --manifest-path 'crates/space λ/Cargo.toml' --target-dir "target tree" --features 'first second'"#,
r#"cargo build --bin ap''p --manifest-path crates/space\ λ/Cargo.toml --target-dir target\ tree --features "first second""#,
] {
assert_eq!(
build_arguments(CompilationKind::CargoBuild, command).unwrap(),
strings(&[
"--bin",
"app",
"--manifest-path",
"crates/space λ/Cargo.toml",
"--target-dir",
"target tree",
"--features",
"first second",
]),
"{command}"
);
let selected = selection(Some(CompilationKind::CargoBuild), command).unwrap();
assert_eq!(selected.bins, BTreeSet::from(["app".to_owned()]));
}
}
#[test]
fn exec_requoting_round_trips_literal_quotes_backslashes_and_dollar_bytes() {
for manifest in [
"crates/space λ/Cargo.toml",
"crates/it's a package/Cargo.toml",
"crates/double\"quote/Cargo.toml",
"crates/back\\slash/Cargo.toml",
"crates/literal $HOME $(printf not-executed)/Cargo.toml",
"crates/literal [glob]*?;|&<>/Cargo.toml",
] {
let argv = strings(&[
"cargo",
"build",
"--bin=app",
"--manifest-path",
manifest,
"--message-format=json,json-render-diagnostics",
]);
let command = crate::hook::join_exec_command(&argv);
assert_eq!(
build_arguments(CompilationKind::CargoBuild, &command).unwrap(),
argv[2..].to_vec(),
"{command}"
);
let capture =
CargoOutputCapture::for_command(Some(CompilationKind::CargoBuild), &command).unwrap();
assert!(capture.caller_json_supported());
assert_eq!(capture.execution_command(&command), command);
}
}
#[test]
fn quoted_wrapper_values_never_become_cargo_options() {
let command = r#"env -u RUSTFLAGS -- CARGO_TARGET_DIR='target --bin decoy' rustup run nightly '/opt/tool chain/cargo' b '--bin=app' --features 'first --bin decoy' --profile 'lean'"#;
let selected = selection(Some(CompilationKind::CargoBuild), command).unwrap();
assert_eq!(selected.bins, BTreeSet::from(["app".to_owned()]));
assert_eq!(selected.profile, "lean");
let inline = r#"CARGO_TARGET_DIR='target tree' cargo +nightly b --bin "app" --manifest-path 'path --bin decoy/Cargo.toml'"#;
let selected = selection(Some(CompilationKind::CargoBuild), inline).unwrap();
assert_eq!(selected.bins, BTreeSet::from(["app".to_owned()]));
for command in [
"env -C 'another root' cargo build --bin app",
"env --chdir='another root' cargo build --bin app",
"time -p cargo build --bin app",
"rustup run --install nightly cargo build --bin app",
"sh -c 'cargo build --bin app'",
] {
assert!(
selection(Some(CompilationKind::CargoBuild), command).is_none(),
"expanded the wrapper grammar: {command}"
);
}
}
#[test]
fn quoted_named_outputs_keep_the_existing_transfer_policy() {
let plain =
"cargo build --bin app --example demo-lib --profile lean --target x86_64-unknown-linux-gnu";
let quoted = r#"cargo build '--bin=app' --example 'demo-lib' --profile "lean" --target='x86_64-unknown-linux-gnu' --manifest-path 'source tree/Cargo.toml'"#;
let kind = Some(CompilationKind::CargoBuild);
assert_eq!(patterns(kind, Some(plain)), patterns(kind, Some(quoted)));
assert_eq!(
super::super::get_custom_target_artifact_patterns(kind, Some(plain)),
super::super::get_custom_target_artifact_patterns(kind, Some(quoted))
);
assert!(super::super::get_project_artifact_patterns(kind, Some(quoted), true).is_empty());
}
#[test]
fn quoting_does_not_admit_evaluation_comments_or_additional_outputs() {
for command in [
"cargo build --bin \"$APP\"",
"cargo build --bin app --manifest-path \"$ROOT/Cargo.toml\"",
"cargo build --bin app --manifest-path $(printf Cargo.toml)",
"cargo build --bin app --manifest-path `printf Cargo.toml`",
"cargo build --bin app --manifest-path ~/Cargo.toml",
"cargo build --bin app --manifest-path crates/*/Cargo.toml",
"cargo build --bin app; cargo build --bin other",
"cargo build --bin app && cargo build --bin other",
"cargo build --bin app | cat",
"cargo build --bin app > result",
"cargo build --bin app # --lib",
"cargo build --bin app --features '{a,b}'",
"cargo build --bin 'app*'",
"cargo build --bin '../app'",
"cargo build --bin app '--all-targets'",
"cargo build --bin app '--config=build.target=custom.json'",
"cargo build --bin app '--'",
"cargo build --bin app --manifest-path 'unfinished",
"cargo build --bin app --manifest-path unfinished\\",
"cargo build --bin app\ncargo build --bin other",
"cargo build --bin app --manifest-path 'line\nfeed/Cargo.toml'",
"cargo build --bin app --manifest-path 'tab\tpath/Cargo.toml'",
"cargo build --bin app --manifest-path 'carriage\rreturn'",
"cargo build --bin app --manifest-path 'nul\0byte'",
"cargo build --bin app --manifest-path 'control\u{85}byte'",
] {
assert!(
selection(Some(CompilationKind::CargoBuild), command).is_none(),
"admitted unsupported syntax: {command:?}"
);
}
assert!(
selection(
Some(CompilationKind::CargoBuild),
"cargo\tbuild\t--bin\t'app'"
)
.is_some()
);
}
#[test]
fn quoted_opaque_values_cannot_supply_a_missing_target_or_mode() {
for (kind, command) in [
(
CompilationKind::CargoBuild,
"cargo build --features 'first --bin app'",
),
(
CompilationKind::CargoBuild,
"cargo build --bin app --package '--bin decoy'",
),
(
CompilationKind::CargoTest,
"cargo test --test alpha --features '--no-run'",
),
(
CompilationKind::CargoTest,
"cargo test --test alpha --features 'first --no-run'",
),
(
CompilationKind::CargoTest,
"cargo test --test alpha -- '--no-run'",
),
(
CompilationKind::CargoBench,
"cargo bench --bench alpha --features 'first --no-run'",
),
] {
assert!(selection(Some(kind), command).is_none(), "{command}");
}
}
#[test]
fn quoted_commands_retain_byte_and_word_budgets() {
let too_long = format!(
"cargo build --bin app --manifest-path '{}'",
"λ".repeat(32_768)
);
assert!(selection(Some(CompilationKind::CargoBuild), &too_long).is_none());
let too_many = format!("cargo build --bin app {}", "'-q' ".repeat(4093));
assert!(selection(Some(CompilationKind::CargoBuild), &too_many).is_none());
let boundary = format!("cargo build --bin app {}", "'-q' ".repeat(4092));
assert!(selection(Some(CompilationKind::CargoBuild), &boundary).is_some());
}
#[test]
fn quoted_no_run_capture_survives_serialization_without_widening_its_mode() {
for (kind, subcommand, selector) in [
(CompilationKind::CargoTest, "test", "test"),
(CompilationKind::CargoBench, "bench", "bench"),
] {
let command = format!(
"cargo {subcommand} '--no-run' --{selector} 'alpha' --manifest-path 'source λ/Cargo.toml' --message-format 'json,json-render-diagnostics'"
);
let capture = CargoOutputCapture::for_command(Some(kind), &command).unwrap();
assert!(capture.caller_json_supported());
let restored: CargoOutputCapture =
serde_json::from_slice(&serde_json::to_vec(&capture).unwrap()).unwrap();
assert_eq!(restored, capture);
assert_eq!(restored.execution_command(&command), command);
let executable = "/worker/target λ/deps/alpha-0123456789abcdef";
let record = serde_json::json!({
"reason": "compiler-artifact",
"target": {"name": "alpha", "kind": [selector]},
"filenames": [executable], "executable": executable, "fresh": true,
"profile": {"test": true},
});
let receipt = format!("{record}\n{{\"reason\":\"build-finished\",\"success\":true}}\n");
let contract = restored
.parse_receipt(receipt.as_bytes(), Path::new("/worker/target λ"))
.unwrap();
assert_eq!(
contract.required_files,
BTreeSet::from([std::path::PathBuf::from("deps/alpha-0123456789abcdef")])
);
assert!(
CargoOutputCapture::for_command(Some(kind), &command.replace("'--no-run'", ""))
.is_none()
);
assert!(
CargoOutputCapture::for_command(Some(kind), &command.replace("'alpha'", "'alpha*'"))
.is_none()
);
}
}
#[cfg(unix)]
#[tokio::test]
async fn decoded_arguments_agree_with_a_real_shell_without_running_a_compiler() {
use std::process::Stdio;
use std::time::Duration;
use tokio::process::Command;
for (words, suffix) in [
(
r#"cargo build --bin ap''p --manifest-path 'it'\''s λ/Cargo.toml' --features "first second""#,
strings(&[
"--bin",
"app",
"--manifest-path",
"it's λ/Cargo.toml",
"--features",
"first second",
]),
),
(
r#"cargo build '--bin=app' --manifest-path 'literal $HOME $(printf not-executed)/Cargo.toml'"#,
strings(&[
"--bin=app",
"--manifest-path",
"literal $HOME $(printf not-executed)/Cargo.toml",
]),
),
(
r#"cargo build --bin app --manifest-path back\\slash/Cargo.toml --target-dir target\ tree"#,
strings(&[
"--bin",
"app",
"--manifest-path",
"back\\slash/Cargo.toml",
"--target-dir",
"target tree",
]),
),
] {
let script = format!("set -- {words}; shift 2; printf '%s\\0' \"$@\"");
let mut shell = Command::new("/bin/sh");
shell
.arg("-c")
.arg(script)
.env_clear()
.env("PATH", "/usr/bin:/bin")
.stdin(Stdio::null())
.kill_on_drop(true);
let output = tokio::time::timeout(Duration::from_secs(5), shell.output())
.await
.expect("literal argv shell fixture timed out")
.unwrap();
assert!(output.status.success(), "{output:?}");
assert!(output.stderr.is_empty());
let expected: Vec<u8> = suffix
.iter()
.flat_map(|word| word.as_bytes().iter().copied().chain(std::iter::once(0)))
.collect();
assert_eq!(output.stdout, expected, "{words}");
assert_eq!(
build_arguments(CompilationKind::CargoBuild, words).unwrap(),
suffix
);
}
}