use std::path::{Path, PathBuf};
use super::fixture::{
FIXTURE_ALEF_TOML, FIXTURE_CARGO_TOML, FIXTURE_SOURCE, FOREIGN_CRATE_CARGO_TOML, FOREIGN_CRATE_SOURCE,
};
use super::{CARGO, EmittedTree, LaneOutcome, Sabotage, emit_tree, fixture_language_list, resolve_tools, run_tool};
pub(crate) fn write_fixture_workspace(root: &Path) {
std::fs::create_dir_all(root.join("src")).expect("create fixture src directory");
std::fs::write(root.join("src/lib.rs"), FIXTURE_SOURCE.trim_start()).expect("write fixture source");
let foreign_core_dir = write_foreign_crate(root);
let dep_path = foreign_core_dir.display().to_string();
let source_path = foreign_core_dir.join("src/lib.rs").display().to_string();
let cargo_toml = FIXTURE_CARGO_TOML.replace("__FOREIGN_CORE_DEP_PATH__", &dep_path);
std::fs::write(root.join("Cargo.toml"), cargo_toml).expect("write fixture Cargo.toml");
let config = FIXTURE_ALEF_TOML
.replace("__ALEF_VERSION__", env!("CARGO_PKG_VERSION"))
.replace("__LANGUAGES__", &fixture_language_list())
.replace("__FOREIGN_CORE_DEP_PATH__", &dep_path)
.replace("__FOREIGN_CORE_SOURCE_PATH__", &source_path);
std::fs::write(root.join("alef.toml"), config).expect("write fixture alef.toml");
}
fn write_foreign_crate(root: &Path) -> PathBuf {
let dir = root.join("foreign_core");
std::fs::create_dir_all(dir.join("src")).expect("create foreign_core src directory");
std::fs::write(dir.join("src/lib.rs"), FOREIGN_CRATE_SOURCE.trim_start()).expect("write foreign_core source");
std::fs::write(dir.join("Cargo.toml"), FOREIGN_CRATE_CARGO_TOML).expect("write foreign_core Cargo.toml");
dir
}
fn python_crate_dir(tree: &EmittedTree) -> PathBuf {
tree.manifest_dirs()
.into_iter()
.find(|dir| {
dir.file_name()
.is_some_and(|name| name.to_string_lossy().ends_with("-py"))
})
.expect("emitted tree has no `*-py` crate directory; `alef generate` may have changed the pyo3 output layout")
.to_path_buf()
}
fn python_lib_rs(tree: &EmittedTree) -> PathBuf {
python_crate_dir(tree).join("src/lib.rs")
}
fn impl_block_range(source: &str, header: &str) -> (usize, usize) {
let start = source
.find(header)
.unwrap_or_else(|| panic!("emitted pyo3 lib.rs has no `{header}` block:\n{source}"));
let end = source[start..]
.find("\n}")
.map(|offset| start + offset + 2)
.unwrap_or_else(|| panic!("`{header}` block never closes in emitted pyo3 lib.rs"));
(start, end)
}
fn run_python_clippy(tree: &EmittedTree) -> LaneOutcome {
run_tool(CARGO.program, CARGO.check_args, &python_crate_dir(tree))
}
fn drop_binding_to_core_variant_arm(tree: &EmittedTree) {
let path = python_lib_rs(tree);
let source = std::fs::read_to_string(&path).unwrap_or_else(|error| panic!("read {}: {error}", path.display()));
let (start, end) = impl_block_range(&source, "impl From<Swatch> for foreign_core::Swatch {");
let block = &source[start..end];
let target = "Swatch::Accent => Self::Accent,";
assert!(
block.lines().any(|line| line.trim() == target),
"the binding-to-core block has no Accent arm to remove: {block}"
);
let sabotaged_block: String = block
.lines()
.filter(|line| line.trim() != target)
.collect::<Vec<_>>()
.join("\n");
let sabotaged = format!("{}{sabotaged_block}{}", &source[..start], &source[end..]);
std::fs::write(&path, sabotaged).unwrap_or_else(|error| panic!("write {}: {error}", path.display()));
}
fn add_redundant_core_to_binding_catch_all(tree: &EmittedTree) {
let path = python_lib_rs(tree);
let source = std::fs::read_to_string(&path).unwrap_or_else(|error| panic!("read {}: {error}", path.display()));
let (start, end) = impl_block_range(&source, "impl From<foreign_core::Swatch> for Swatch {");
let block = &source[start..end];
assert!(
!block.contains("_ => Default::default(),"),
"the core-to-binding block already has a catch-all, so adding a second one would not \
prove this sabotage examines anything new:\n{block}"
);
let last_arm = block
.lines()
.find(|line| line.trim() == "foreign_core::Swatch::Accent => Self::Accent,")
.unwrap_or_else(|| panic!("core-to-binding block has no `Accent` arm to anchor the sabotage on:\n{block}"));
let indent = &last_arm[..last_arm.len() - last_arm.trim_start().len()];
let sabotaged_block = block.replacen(last_arm, &format!("{last_arm}\n{indent}_ => Default::default(),"), 1);
let sabotaged = format!("{}{sabotaged_block}{}", &source[..start], &source[end..]);
std::fs::write(&path, sabotaged).unwrap_or_else(|error| panic!("write {}: {error}", path.display()));
}
#[test]
#[ignore = "compiles the emitted pyo3 crate; run via the CI gate job"]
fn clippy_lane_catches_a_missing_binding_to_core_variant_arm() {
resolve_tools(&[&CARGO]);
let clean = emit_tree(Sabotage::None);
let control = run_python_clippy(&clean);
assert!(
control.passed,
"the control tree must be green, or the sabotage proves nothing:\n{}",
control.output
);
let sabotaged = emit_tree(Sabotage::None);
drop_binding_to_core_variant_arm(&sabotaged);
let outcome = run_python_clippy(&sabotaged);
assert!(
!outcome.passed,
"dropping a compiled binding-to-core variant arm did not fail `cargo clippy`, so this \
lane is not examining the emitted pyo3 conversion"
);
assert!(
outcome.output.contains("E0004") || outcome.output.contains("non-exhaustive"),
"the build failed, but not with the expected `error[E0004]: non-exhaustive patterns` -- \
this sabotage may be failing for an unrelated reason:\n{}",
outcome.output
);
}
#[test]
#[ignore = "compiles the emitted pyo3 crate; run via the CI gate job"]
fn clippy_lane_catches_a_redundant_foreign_cfg_wrapper_catch_all() {
resolve_tools(&[&CARGO]);
let clean = emit_tree(Sabotage::None);
let control = run_python_clippy(&clean);
assert!(
control.passed,
"the control tree must be green, or the sabotage proves nothing:\n{}",
control.output
);
let sabotaged = emit_tree(Sabotage::None);
add_redundant_core_to_binding_catch_all(&sabotaged);
let outcome = run_python_clippy(&sabotaged);
assert!(
!outcome.passed,
"adding a redundant catch-all to the already-exhaustive core-to-binding match did not \
fail `cargo clippy -- -D warnings`, so this lane is not examining the emitted pyo3 \
conversion"
);
assert!(
outcome.output.contains("unreachable"),
"the build failed, but not with the expected `unreachable_patterns` -- this sabotage may \
be failing for an unrelated reason:\n{}",
outcome.output
);
}
#[test]
fn impl_block_range_stops_at_the_outer_closing_brace() {
let source = "prefix
impl From<A> for B {
fn from(val: A) -> Self {
match val {
A::X => Self::X,
}
}
}
suffix
";
let (start, end) = impl_block_range(source, "impl From<A> for B {");
let block = &source[start..end];
assert!(block.starts_with("impl From<A> for B {"));
assert!(block.ends_with('}'), "block must end at the outer brace: {block:?}");
assert!(!block.contains("suffix"));
assert_eq!(&source[end..], "\nsuffix\n");
}