use std::path::{Path, PathBuf};
use std::process::{Command, Output};
fn blue() -> Command {
let mut c = Command::new(env!("CARGO_BIN_EXE_blue"));
c.env_remove("BLUE_PATH")
.env_remove("BLUE_CONFIG")
.env_remove("BLUE_TIER");
c
}
fn project(name: &str, test: &str) -> PathBuf {
let from = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/project-rules")
.join(name);
let to = std::env::temp_dir().join(format!("blue-project-rules-{}-{test}", std::process::id()));
let _ = std::fs::remove_dir_all(&to);
std::fs::create_dir_all(to.join(".git")).expect("mkdir");
for entry in std::fs::read_dir(&from).expect("fixture") {
let p = entry.expect("entry").path();
std::fs::copy(&p, to.join(p.file_name().expect("name"))).expect("copy");
}
to
}
fn check(file: &Path, extra: &[&str]) -> Output {
blue()
.arg("check")
.args(extra)
.arg(file)
.output()
.expect("spawn")
}
fn text(o: &Output) -> String {
String::from_utf8_lossy(&o.stdout).into_owned() + &String::from_utf8_lossy(&o.stderr)
}
#[test]
fn a_project_rule_reports_a_finding_at_its_node() {
let dir = project("ok", "finding");
let o = check(&dir.join("finding.b"), &[]);
let out = text(&o);
assert_eq!(o.status.code(), Some(1), "{out}");
assert!(
out.contains("finding.b:10:3: error[FX0001]: loud(x) is quiet(x)"),
"{out}"
);
assert!(out.contains("project rules: 2"), "{out}");
}
#[test]
fn a_project_finding_is_a_json_diagnostic_with_its_fix() {
let dir = project("ok", "json");
let o = check(&dir.join("finding.b"), &["--format", "json"]);
let line = String::from_utf8_lossy(&o.stdout)
.lines()
.find(|l| l.contains("FX0001"))
.map(str::to_owned)
.unwrap_or_else(|| panic!("no FX0001 line: {}", text(&o)));
let v: serde_json::Value = serde_json::from_str(&line).expect("json");
assert_eq!(v["code"], "FX0001");
assert_eq!(v["slug"], "loud-call");
assert_eq!(v["severity"], "error");
assert_eq!(v["line"], 10);
assert_eq!(v["fixes"][0]["applicability"], "machine-applicable");
assert_eq!(v["fixes"][0]["edits"][0]["original"], "loud(x)");
assert_eq!(v["fixes"][0]["edits"][0]["replacement"], "quiet(x)");
}
#[test]
fn a_waiver_names_a_project_code_like_a_registry_one() {
let dir = project("ok", "waiver");
let o = check(&dir.join("waived.b"), &[]);
let out = text(&o);
assert_eq!(o.status.code(), Some(0), "{out}");
assert!(out.contains("waived: 1"), "{out}");
assert!(
out.contains("waived: loud(x) is quiet(x) (this one must be loud)"),
"{out}"
);
}
#[test]
fn fix_applies_a_project_rules_fix() {
let dir = project("ok", "fix");
let file = dir.join("finding.b");
let o = check(&file, &["--fix"]);
let out = text(&o);
assert_eq!(o.status.code(), Some(0), "{out}");
let after = std::fs::read_to_string(&file).expect("read");
assert!(after.contains("def shout(x)\n quiet(x)\nend"), "{after}");
}
#[test]
fn a_runaway_rule_is_a_rule_failure_not_a_hang() {
let dir = project("runaway", "budget");
let o = check(&dir.join("target.b"), &[]);
let out = text(&o);
assert_eq!(o.status.code(), Some(1), "{out}");
let failures: Vec<&str> = out
.lines()
.filter(|l| l.contains("error[FX0003]"))
.collect();
assert_eq!(failures.len(), 1, "{out}");
assert!(failures[0].contains("ran past its budget"), "{out}");
assert!(failures[0].contains("in `spin`"), "{out}");
assert_eq!(out.matches("error[FX0004]: seen").count(), 2, "{out}");
}
#[test]
fn a_rule_reaching_for_read_file_is_refused_by_the_frame() {
let dir = project("reads", "frame");
let o = check(&dir.join("target.b"), &["--format", "json"]);
let out = text(&o);
assert_eq!(o.status.code(), Some(1), "{out}");
let line = String::from_utf8_lossy(&o.stdout)
.lines()
.find(|l| l.contains("FX0005"))
.map(str::to_owned)
.unwrap_or_else(|| panic!("no FX0005 line: {out}"));
let v: serde_json::Value = serde_json::from_str(&line).expect("json");
assert_eq!(v["slug"], "rule-failure");
assert!(
v["message"]
.as_str()
.is_some_and(|m| m.contains("outside the rule frame: read_file")),
"{line}"
);
}
#[test]
fn a_file_with_no_project_rules_is_checked_as_before() {
let dir = std::env::temp_dir().join(format!("blue-project-rules-{}-none", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(dir.join(".git")).expect("mkdir");
let file = dir.join("plain.b");
std::fs::write(
&file,
"# waive R0001: the root's rule\ndef f()\n 1 == 2\nend\n",
)
.expect("write");
let o = check(&file, &[]);
let out = text(&o);
assert_eq!(o.status.code(), Some(0), "{out}");
assert!(!out.contains("project rules"), "{out}");
}
#[test]
fn a_warning_rule_reports_without_failing() {
let dir = project("ok", "warning");
let o = check(&dir.join("todo.b"), &[]);
let out = text(&o);
assert_eq!(o.status.code(), Some(0), "{out}");
assert!(
out.contains("todo.b:5:1: warning[FX0002]: a todo is left"),
"{out}"
);
assert!(out.contains("help: finish it"), "{out}");
}