use super::*;
use std::fs;
#[test]
fn code_selector_narrows_the_plan_to_the_named_diagnostic() {
let temp = tempfile::TempDir::new().unwrap();
let script = temp.path().join("mixed.harn");
fs::write(
&script,
"fn helper() {\n let unchanged = 1\n println(\"hi\")\n return unchanged\n}\n",
)
.unwrap();
let everything = build_plan_with_options(&script, None, &FixOptions::default()).unwrap();
let codes = everything
.repairs
.iter()
.map(|repair| repair.diagnostic_code.as_str())
.collect::<BTreeSet<_>>();
assert!(
codes.len() > 1,
"fixture must offer more than one code to narrow: {codes:?}"
);
let wanted = Code::LintMutableNeverReassigned;
assert!(codes.contains(wanted.as_str()), "{codes:?}");
let narrowed = build_plan_with_options(
&script,
None,
&FixOptions {
codes: BTreeSet::from([wanted]),
..FixOptions::default()
},
)
.unwrap();
assert!(!narrowed.repairs.is_empty());
assert!(
narrowed
.repairs
.iter()
.all(|repair| repair.diagnostic_code == wanted.as_str()),
"{:?}",
narrowed.repairs
);
for repair in &narrowed.repairs {
assert_eq!(
narrowed.diagnostics[repair.diagnostic_index].code,
repair.diagnostic_code
);
}
}
#[test]
fn code_selector_does_not_defer_to_an_unselected_whole_program_pass() {
let temp = tempfile::TempDir::new().unwrap();
let script = temp.path().join("lib.harn");
fs::write(
&script,
concat!(
"fn main(harness: Harness) {\n",
" read_config(harness.fs)\n",
" publish(harness)\n",
"}\n",
"\n",
"fn read_config(harness: HarnessFs) {\n",
" return harness.read_text(\"harn.toml\")\n",
"}\n",
"\n",
"fn publish(harness: Harness) {\n",
" harness.fs.write_text(\"out.txt\", \"done\")\n",
"}\n",
),
)
.unwrap();
let rename = Code::LintCapabilityParameterName;
let unselected = build_plan_with_options(
&script,
Some(RepairSafety::SurfaceChanging),
&FixOptions::default(),
)
.unwrap();
assert!(
unselected
.diagnostics
.iter()
.any(|diagnostic| diagnostic.code == Code::LintBroadHarnessParameter.as_str()),
"fixture must give the whole-program pass work to defer to: {:?}",
unselected.diagnostics
);
let narrowed = build_plan_with_options(
&script,
Some(RepairSafety::SurfaceChanging),
&FixOptions {
codes: BTreeSet::from([rename]),
..FixOptions::default()
},
)
.unwrap();
assert!(
narrowed
.repairs
.iter()
.any(|repair| repair.diagnostic_code == rename.as_str() && !repair.edits.is_empty()),
"the selected rename must be planned, not deferred to an excluded pass: {:?}",
narrowed.repairs
);
}
#[test]
fn apply_leaves_an_unformatted_file_unformatted() {
let temp = tempfile::TempDir::new().unwrap();
let script = temp.path().join("loose.harn");
let original = "fn main(harness: Harness) {\n const value = 1\n}\n";
fs::write(&script, original).unwrap();
let start = original.find('1').unwrap();
apply_file_edits(
&script,
&[FixEditWire {
span: SpanWire::from(Span::with_offsets(start, start + 1, 2, 1)),
replacement: "2".to_string(),
}],
)
.unwrap();
assert_eq!(
fs::read_to_string(&script).unwrap(),
"fn main(harness: Harness) {\n const value = 2\n}\n",
"only the repaired span may differ"
);
}
#[test]
fn apply_keeps_a_formatted_file_formatted() {
let temp = tempfile::TempDir::new().unwrap();
let script = temp.path().join("canonical.harn");
let original = "fn main(harness: Harness) {\n const value = 1\n}\n";
fs::write(&script, original).unwrap();
let start = original.find("const").unwrap();
apply_file_edits(
&script,
&[FixEditWire {
span: SpanWire::from(Span::with_offsets(
start,
start + "const value = 1".len(),
2,
3,
)),
replacement: "const value = 1".to_string(),
}],
)
.unwrap();
assert_eq!(
fs::read_to_string(&script).unwrap(),
original,
"a canonical file must come back canonical"
);
}
#[test]
fn edit_group_key_collapses_relative_and_absolute_spellings() {
let temp = tempfile::TempDir::new().unwrap();
let script = temp.path().join("workflow.harn");
fs::write(&script, "const value = 1\n").unwrap();
let previous = std::env::current_dir().unwrap();
std::env::set_current_dir(temp.path()).unwrap();
let relative = edit_group_key("./workflow.harn");
std::env::set_current_dir(previous).unwrap();
let absolute = edit_group_key(&script.to_string_lossy());
assert_eq!(
relative, absolute,
"both spellings of one file must group together"
);
}
#[test]
fn edit_group_key_keeps_unresolvable_paths_verbatim() {
let missing = "./does/not/exist.harn";
assert_eq!(
edit_group_key(missing),
missing,
"an unresolvable path keeps its spelling so the later read reports it"
);
}
#[test]
fn apply_file_edits_refuses_to_write_unparseable_source() {
let temp = tempfile::TempDir::new().unwrap();
let script = temp.path().join("broken.harn");
let original = "fn main(harness: Harness) {\n const value = 1\n}\n";
fs::write(&script, original).unwrap();
let edits = vec![FixEditWire {
span: SpanWire::from(Span::with_offsets(10, 10, 1, 11)),
replacement: "!!(".to_string(),
}];
let error = apply_file_edits(&script, &edits)
.expect_err("a candidate that does not parse must be rejected");
assert!(
error.contains("invalid Harn syntax"),
"error should name the syntax failure: {error}"
);
assert!(
error.contains("applied edits:"),
"error should list the edits so the bad span is visible: {error}"
);
assert_eq!(
fs::read_to_string(&script).unwrap(),
original,
"the file must be left exactly as it was"
);
}