use crate::helpers::skill_eval;
use predicates::prelude::PredicateBooleanExt;
use predicates::str::contains;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn write_project_descriptor(root: &Path, file: &str, contents: &str) {
let dir = root.join(".eval-magic").join("harnesses");
fs::create_dir_all(&dir).unwrap();
fs::write(dir.join(file), contents).unwrap();
}
#[test]
fn project_local_descriptor_registers_a_new_harness() {
let tmp = TempDir::new().unwrap();
write_project_descriptor(tmp.path(), "cool.toml", "label = \"cool-custom-harness\"\n");
skill_eval()
.current_dir(tmp.path())
.args(["aggregate", "--harness", "cool-custom-harness"])
.assert()
.failure()
.stderr(contains("unknown harness").not());
}
#[test]
fn unknown_harness_error_lists_discovered_harnesses() {
let tmp = TempDir::new().unwrap();
write_project_descriptor(tmp.path(), "cool.toml", "label = \"cool-custom-harness\"\n");
skill_eval()
.current_dir(tmp.path())
.args(["aggregate", "--harness", "nonexistent"])
.assert()
.failure()
.stderr(
contains("unknown harness 'nonexistent'")
.and(contains("claude-code"))
.and(contains("cool-custom-harness")),
);
}
#[test]
fn user_global_layer_is_discovered_via_config_dir_env() {
let tmp = TempDir::new().unwrap();
let config_root = tmp.path().join("config");
let dir = config_root.join("harnesses");
fs::create_dir_all(&dir).unwrap();
fs::write(dir.join("cool.toml"), "label = \"cool-custom-harness\"\n").unwrap();
skill_eval()
.current_dir(tmp.path())
.env("EVAL_MAGIC_CONFIG_DIR", &config_root)
.args(["aggregate", "--harness", "cool-custom-harness"])
.assert()
.failure()
.stderr(contains("unknown harness").not());
}
#[test]
fn harness_file_registers_a_one_off_harness() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("cool.toml");
fs::write(&file, "label = \"cool-custom-harness\"\n").unwrap();
skill_eval()
.current_dir(tmp.path())
.arg("aggregate")
.arg("--harness-file")
.arg(&file)
.args(["--harness", "cool-custom-harness"])
.assert()
.failure()
.stderr(contains("unknown harness").not());
}
#[test]
fn broken_project_local_descriptor_warns_but_never_bricks() {
let tmp = TempDir::new().unwrap();
write_project_descriptor(
tmp.path(),
"broken.toml",
"label = \"broken\"\nmystery = 1\n",
);
skill_eval()
.current_dir(tmp.path())
.args(["aggregate", "--harness", "claude-code"])
.assert()
.failure()
.stderr(
contains("skipping harness descriptor")
.and(contains("harness lint"))
.and(contains("unknown harness").not()),
);
}
#[test]
fn broken_harness_file_is_fatal() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("broken.toml");
fs::write(&file, "label = ").unwrap();
skill_eval()
.current_dir(tmp.path())
.arg("aggregate")
.arg("--harness-file")
.arg(&file)
.assert()
.failure()
.stderr(contains("broken.toml").and(contains("invalid TOML")));
}
#[test]
fn harness_list_names_layers_and_enhancements() {
let tmp = TempDir::new().unwrap();
write_project_descriptor(tmp.path(), "cool.toml", "label = \"cool-custom-harness\"\n");
skill_eval()
.current_dir(tmp.path())
.args(["harness", "list"])
.assert()
.success()
.stdout(
contains("claude-code")
.and(contains("(default)"))
.and(contains("built-in"))
.and(contains("opencode"))
.and(contains("cool-custom-harness"))
.and(contains("project"))
.and(contains("baseline"))
.and(contains("transcript")),
);
}
#[test]
fn harness_list_marks_merged_builtins() {
let tmp = TempDir::new().unwrap();
write_project_descriptor(
tmp.path(),
"claude-code.toml",
"label = \"claude-code\"\n\n[model]\nflag = \"--model-x\"\n",
);
skill_eval()
.current_dir(tmp.path())
.args(["harness", "list"])
.assert()
.success()
.stdout(contains("built-in + project"));
}
#[test]
fn harness_show_prints_the_merged_descriptor_as_toml() {
let tmp = TempDir::new().unwrap();
write_project_descriptor(
tmp.path(),
"claude-code.toml",
"label = \"claude-code\"\n\n[model]\nflag = \"--model-x\"\n",
);
skill_eval()
.current_dir(tmp.path())
.args(["harness", "show", "claude-code"])
.assert()
.success()
.stdout(
contains("label = \"claude-code\"")
.and(contains("--model-x"))
.and(contains("harnesses/claude-code.toml (built-in)"))
.and(contains("claude-code.toml (project)"))
.and(contains("skills_dir = \".claude/skills\"")),
);
}
#[test]
fn harness_show_unknown_name_lists_known_harnesses() {
let tmp = TempDir::new().unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "show", "nonexistent"])
.assert()
.failure()
.stderr(contains("unknown harness 'nonexistent'").and(contains("claude-code")));
}
#[test]
fn harness_lint_passes_a_valid_file() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("cool.toml");
fs::write(&file, "label = \"cool-custom-harness\"\n").unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.assert()
.success()
.stdout(contains("✓"));
}
#[test]
fn harness_lint_reports_schema_violations() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("bad.toml");
fs::write(&file, "label = \"bad\"\nmystery = 1\n").unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.assert()
.failure()
.stderr(contains("✗").and(contains("mystery")));
}
#[test]
fn harness_lint_rejects_a_user_guard_block() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("armed.toml");
fs::write(
&file,
r#"
label = "armed"
[guard]
hooks_file = ".armed/hooks.json"
matcher = "Write"
command_template = '"{exe}" guard-hook --harness armed "{marker}"'
hook_entry = '{"matcher":"{matcher}","hooks":[{"type":"command","command":"{command}"}]}'
verdict_template = '{"decision":"block","reason":"{reason}"}'
armed_message = "x"
"#,
)
.unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.assert()
.failure()
.stderr(contains("may not declare [guard]").and(contains("detect-stray-writes")));
}
#[test]
fn harness_lint_reports_invariant_breaks() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("slug.toml");
fs::write(
&file,
"label = \"demo\"\nskills_dir = \".demo/skills\"\nconfig_dirs = [\".demo\"]\n\n\
[staging]\nslug_template = \"{prefix}{iteration}-{condition}\"\n",
)
.unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.assert()
.failure()
.stderr(contains("staging.slug_template").and(contains("{skill_name}")));
}
#[test]
fn harness_lint_checks_a_partial_override_against_its_merge_target() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("claude-code.toml");
fs::write(
&file,
"label = \"claude-code\"\nconfig_dirs = [\".other\"]\n",
)
.unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.assert()
.failure()
.stderr(contains("parent of skills_dir"));
}
#[test]
fn harness_lint_passes_an_extract_descriptor() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("flat.toml");
fs::write(
&file,
r#"label = "flat"
[tools]
write = ["file_write"]
shell = ["shell"]
[transcript]
events_filename = "flat-events.jsonl"
[transcript.extract.tools]
where = { kind = "tool.call" }
name_field = "tool"
[transcript.extract.final_text]
where = { kind = "message" }
field = "text"
"#,
)
.unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.assert()
.success()
.stdout(contains("✓"));
}
#[test]
fn harness_lint_reports_extract_overlaying_a_builtin_parser() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("claude-code.toml");
fs::write(
&file,
"label = \"claude-code\"\n\n[transcript]\nevents_filename = \"events.jsonl\"\n\n\
[transcript.extract.final_text]\nfield = \"result\"\n",
)
.unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.assert()
.failure()
.stderr(contains("exactly one").and(contains("new label")));
}
#[test]
fn harness_lint_accepts_a_registered_harness_name() {
let tmp = TempDir::new().unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint", "claude-code"])
.assert()
.success()
.stdout(contains("✓"));
}
#[test]
fn harness_lint_of_a_name_reports_the_skipped_project_file() {
let tmp = TempDir::new().unwrap();
write_project_descriptor(
tmp.path(),
"broken.toml",
"label = \"broken\"\nmystery = 1\n",
);
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint", "broken"])
.assert()
.failure()
.stderr(contains("✗").and(contains("mystery")));
}
#[test]
fn missing_harness_file_is_fatal() {
let tmp = TempDir::new().unwrap();
skill_eval()
.current_dir(tmp.path())
.arg("aggregate")
.arg("--harness-file")
.arg(tmp.path().join("missing.toml"))
.assert()
.failure()
.stderr(contains("--harness-file").and(contains("missing.toml")));
}
const PROBE_OK_TOML: &str = "label = \"probe-ok\"\n\n\
[dispatch]\n\
exec_template = 'printf \"ok\\n\" > <outputs_dir>/final-message.md'\n";
#[test]
fn harness_lint_probe_recovers_final_message_for_fake_exec_template() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("probe-ok.toml");
fs::write(&file, PROBE_OK_TOML).unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.args(["--probe", "--yes"])
.assert()
.success()
.stdout(contains("✓ live exec template"));
}
#[test]
fn harness_lint_probe_runs_against_a_registered_name() {
let tmp = TempDir::new().unwrap();
write_project_descriptor(tmp.path(), "probe-ok.toml", PROBE_OK_TOML);
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint", "probe-ok", "--probe", "--yes"])
.assert()
.success()
.stdout(contains("✓ live exec template"));
}
#[test]
fn harness_lint_probe_fails_when_final_message_missing() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("probe-bad.toml");
fs::write(
&file,
"label = \"probe-bad\"\n\n[dispatch]\nexec_template = 'true'\n",
)
.unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.args(["--probe", "--yes"])
.assert()
.failure()
.stderr(contains("✗").and(contains("final-message.md")));
}
#[test]
fn harness_lint_probe_renders_parallel_and_judge_templates() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("probe-full.toml");
fs::write(
&file,
"label = \"probe-full\"\n\n\
[model]\nflag = \"-m\"\n\n\
[dispatch]\n\
exec_template = 'printf \"ok\\n\" > <outputs_dir>/final-message.md'\n\
capture_prefix = \"out\"\n\
parallel_command_template = \"agent --cd {cwd} run\"\n\
judge_command_template = \"judge --cd {cwd} $model_arg \\\\\"\n",
)
.unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.args(["--probe", "--yes"])
.assert()
.success()
.stdout(
contains("✓ live exec template")
.and(contains("✓ render: parallel_command_template"))
.and(contains("✓ render: judge_command_template")),
);
}
#[test]
fn harness_lint_probe_aborts_without_yes_on_non_yes_stdin() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("probe-ok.toml");
fs::write(&file, PROBE_OK_TOML).unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.args(["--probe"])
.write_stdin("n\n")
.assert()
.failure()
.stderr(
contains("About to execute")
.and(contains("aborted"))
.and(contains("✓ live exec template").not()),
);
}
#[test]
fn harness_lint_probe_timeout_kills_a_long_command() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("probe-slow.toml");
fs::write(
&file,
"label = \"probe-slow\"\n\n[dispatch]\nexec_template = 'sleep 3'\n",
)
.unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.args(["--probe", "--yes", "--probe-timeout", "1"])
.assert()
.failure()
.stderr(contains("✗").and(contains("timed out")));
}
#[test]
fn harness_lint_probe_without_exec_template_reports_nothing_to_run() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("no-dispatch.toml");
fs::write(&file, "label = \"no-dispatch\"\n").unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.args(["--probe", "--yes"])
.assert()
.failure()
.stderr(contains("✗").and(contains("dispatch.exec_template")));
}
#[test]
fn harness_lint_probe_does_not_run_after_static_checks_fail() {
let tmp = TempDir::new().unwrap();
let file = tmp.path().join("broken.toml");
fs::write(&file, "label = \"broken\"\nmystery = 1\n").unwrap();
skill_eval()
.current_dir(tmp.path())
.args(["harness", "lint"])
.arg(&file)
.args(["--probe", "--yes"])
.assert()
.failure()
.stderr(contains("mystery").and(contains("About to execute").not()));
}
#[test]
fn harness_lint_help_lists_the_probe_flags() {
skill_eval()
.args(["harness", "lint", "--help"])
.assert()
.success()
.stdout(
contains("--probe")
.and(contains("--yes"))
.and(contains("--probe-timeout")),
);
}