use crate::helpers::*;
use serde_json::json;
use std::fs;
const TWO_EVALS_ONE_ISOLATED: &str = r#"{ "skill_name": "mr-review", "evals": [
{ "id": "e1", "prompt": "p1", "expected_output": "o", "files": ["a.txt"] },
{ "id": "e2", "prompt": "p2", "expected_output": "o", "files": ["b.txt"], "isolation": "isolated" } ] }"#;
fn write_fixtures(skill_dir: &std::path::Path) {
fs::write(skill_dir.join("mr-review/evals/a.txt"), "AAA").unwrap();
fs::write(skill_dir.join("mr-review/evals/b.txt"), "BBB").unwrap();
}
#[test]
fn single_group_emits_groups_key_and_per_condition_envs() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), DEFAULT_EVALS);
skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args(["--skill", "mr-review", "--mode", "new-skill", "--dry-run"])
.assert()
.success();
assert!(cli_env_dir(&cwd, "g1", "with_skill").exists());
assert!(cli_env_dir(&cwd, "g1", "without_skill").exists());
let dispatch = read_json(&iteration_dir(&cwd).join("dispatch.json"));
let groups = dispatch["groups"]
.as_array()
.expect("groups summary present even for a single group");
assert_eq!(groups.len(), 1);
assert_eq!(groups[0]["id"], "g1");
for task in dispatch["tasks"].as_array().unwrap() {
assert!(task.get("group").is_none(), "single group: no tag: {task}");
let cond = task["condition"].as_str().unwrap();
let eval_root = task["eval_root"].as_str().expect("task carries eval_root");
assert!(
eval_root.ends_with(&format!("env-g1-{cond}")),
"eval_root points at the per-condition env: {eval_root}"
);
}
}
#[test]
fn cli_single_group_emits_groups_and_splits_env_per_condition() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), DEFAULT_EVALS);
skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args([
"--skill",
"mr-review",
"--mode",
"new-skill",
"--harness",
"claude-code",
"--dry-run",
])
.assert()
.success();
let with_env = cli_env_dir(&cwd, "g1", "with_skill");
let without_env = cli_env_dir(&cwd, "g1", "without_skill");
assert!(
with_env
.join(".claude/skills/slow-powers-eval-1-with_skill__mr-review")
.exists()
);
assert!(
!without_env
.join(".claude/skills/slow-powers-eval-1-with_skill__mr-review")
.exists(),
"the control arm's env contains no staged skill"
);
let dispatch = read_json(&iteration_dir(&cwd).join("dispatch.json"));
let groups = dispatch["groups"]
.as_array()
.expect("groups summary present");
assert_eq!(groups.len(), 1);
assert_eq!(groups[0]["id"], "g1");
assert_eq!(groups[0]["evals"], json!(["e1"]));
for task in dispatch["tasks"].as_array().unwrap() {
assert!(task.get("group").is_none(), "single group: no tag: {task}");
let eval_root = task["eval_root"]
.as_str()
.expect("Cli task carries eval_root");
let cond = task["condition"].as_str().unwrap();
assert!(
eval_root.ends_with(&format!("env-g1-{cond}")),
"eval_root points at the per-condition env: {eval_root}"
);
}
}
#[test]
fn isolated_hint_splits_into_two_groups() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), TWO_EVALS_ONE_ISOLATED);
write_fixtures(&skill_dir);
skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args(["--skill", "mr-review", "--mode", "new-skill", "--dry-run"])
.assert()
.success();
let dispatch = read_json(&iteration_dir(&cwd).join("dispatch.json"));
let groups = dispatch["groups"]
.as_array()
.expect("groups summary present");
assert_eq!(groups.len(), 2, "the isolated eval forms its own group");
assert_eq!(groups[0]["evals"], json!(["e1"]));
assert_eq!(groups[1]["evals"], json!(["e2"]));
assert!(
groups[1]["rationale"]
.as_str()
.unwrap()
.contains("isolated"),
"second group's rationale names the hint: {}",
groups[1]["rationale"]
);
let e2_task = dispatch["tasks"]
.as_array()
.unwrap()
.iter()
.find(|t| t["eval_id"] == "e2" && t["condition"] == "with_skill")
.unwrap();
assert_eq!(e2_task["group"], "g2");
assert_eq!(
read_str(&cli_env_dir(&cwd, "g1", "with_skill").join("a.txt")),
"AAA"
);
assert!(
!cli_env_dir(&cwd, "g1", "with_skill").join("b.txt").exists(),
"the isolated group's fixture is not staged into g1's env"
);
assert_eq!(
read_str(&cli_env_dir(&cwd, "g2", "with_skill").join("b.txt")),
"BBB"
);
assert!(
!cli_env_dir(&cwd, "g2", "with_skill").join("a.txt").exists(),
"g1's fixture is not staged into the isolated group's env"
);
}
#[test]
fn isolated_hint_splits_into_separate_envs_cli() {
let tmp = tempfile::TempDir::new().unwrap();
let (skill_dir, cwd) = setup(tmp.path(), TWO_EVALS_ONE_ISOLATED);
write_fixtures(&skill_dir);
skill_eval()
.current_dir(&cwd)
.args(["run", "--skill-dir"])
.arg(&skill_dir)
.args(["--skill", "mr-review", "--harness", "codex", "--dry-run"])
.assert()
.success();
assert_eq!(
read_str(&cli_env_dir(&cwd, "g1", "with_skill").join("a.txt")),
"AAA"
);
assert!(!cli_env_dir(&cwd, "g1", "with_skill").join("b.txt").exists());
assert_eq!(
read_str(&cli_env_dir(&cwd, "g2", "with_skill").join("b.txt")),
"BBB"
);
assert!(!cli_env_dir(&cwd, "g2", "with_skill").join("a.txt").exists());
let dispatch = read_json(&iteration_dir(&cwd).join("dispatch.json"));
assert_eq!(dispatch["groups"].as_array().unwrap().len(), 2);
let e2_task = dispatch["tasks"]
.as_array()
.unwrap()
.iter()
.find(|t| t["eval_id"] == "e2" && t["condition"] == "with_skill")
.unwrap();
assert_eq!(e2_task["group"], "g2");
assert!(
e2_task["eval_root"]
.as_str()
.unwrap()
.ends_with("env-g2-with_skill")
);
}