#![allow(clippy::print_stderr)]
use crate::core::config::ResolvedCrateConfig;
use crate::core::config::e2e::{CallConfig, DependencyMode};
use crate::e2e::codegen::{E2eCodegen, all_generators};
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::{Assertion, Fixture, FixtureGroup};
const NO_MARKABLE_OUTPUT: &[&str] = &["r", "php_ext", "homebrew"];
const REGISTRY_REQUIRES_GITHUB_REPO: &[&str] = &["zig", "r"];
fn sweep_groups() -> Vec<FixtureGroup> {
vec![FixtureGroup {
category: "marker_coverage".to_string(),
fixtures: vec![Fixture {
id: "marker_coverage_case".to_string(),
description: "marker coverage sweep fixture".to_string(),
assertions: vec![Assertion {
assertion_type: "not_error".to_string(),
..Default::default()
}],
..Fixture::default()
}],
}]
}
fn sweep_config(dep_mode: DependencyMode) -> E2eConfig {
let mut config = E2eConfig {
call: CallConfig {
function: "marker_coverage_call".to_string(),
returns_result: true,
..CallConfig::default()
},
dep_mode,
..E2eConfig::default()
};
config.env.insert("ALEF_MARKER_COVERAGE".to_string(), "1".to_string());
config
}
#[test]
fn every_markable_e2e_generated_file_carries_a_provenance_marker() {
let crate_config = ResolvedCrateConfig::default();
let groups = sweep_groups();
let mut unmarked: Vec<String> = Vec::new();
let mut markable_seen = 0usize;
let mut total_seen = 0usize;
let mut contributing: Vec<&str> = Vec::new();
for (mode_label, e2e_config) in [
("local", sweep_config(DependencyMode::Local)),
("registry", sweep_config(DependencyMode::Registry)),
] {
for generator in all_generators() {
let language = generator.language_name();
let files = match generator.generate(&groups, &e2e_config, &crate_config, &[], &[], &[], &[]) {
Ok(files) => files,
Err(error) => {
assert!(
mode_label == "registry"
&& REGISTRY_REQUIRES_GITHUB_REPO.contains(&language)
&& error.to_string().contains("github_repo"),
"{mode_label}/{language}: generator failed unexpectedly: {error}"
);
continue;
}
};
assert!(
!files.is_empty(),
"{mode_label}/{language}: generator emitted no files at all, so this row proves nothing"
);
total_seen += files.len();
let mut markable_here = 0usize;
for file in files {
if crate::cli::pipeline::generate::write::marker_comment_style(&file.path).is_none() {
continue;
}
markable_here += 1;
markable_seen += 1;
if !file.carries_alef_marker() {
unmarked.push(format!("{mode_label}/{language}: {}", file.path.display()));
}
}
if markable_here > 0 && !contributing.contains(&language) {
contributing.push(language);
}
assert!(
markable_here > 0 || NO_MARKABLE_OUTPUT.contains(&language),
"{mode_label}/{language}: emitted no markable file, so it silently contributes \
nothing to this gate -- add it to NO_MARKABLE_OUTPUT with a reason, or fix the sweep"
);
}
}
eprintln!(
"marker sweep: {} generated file(s) across 2 dependency modes, {markable_seen} on a markable \
extension, {} generator(s) contributing",
total_seen,
contributing.len()
);
assert!(
markable_seen >= 60,
"sweep examined only {markable_seen} markable file(s); it is no longer covering the surface \
it was written against ({} generators contributed)",
contributing.len()
);
assert!(
unmarked.is_empty(),
"{} e2e-generated file(s) land on a markable extension but carry no alef provenance marker, \
so the scaffold writer records no ownership entry for them either (alef #474) -- stamp each \
with crate::core::hash::SELF_MARKING_HEADER_LINE{{,_HASH}}:\n {}",
unmarked.len(),
unmarked.join("\n ")
);
}
#[test]
fn a_template_change_to_the_kotlin_android_build_file_is_not_refused_on_the_second_write() {
use crate::core::backend::GeneratedFile;
let temp = tempfile::tempdir().expect("tempdir");
let base = temp.path();
let generated = crate::e2e::codegen::kotlin_android::KotlinAndroidE2eCodegen
.generate(
&sweep_groups(),
&sweep_config(DependencyMode::Local),
&ResolvedCrateConfig::default(),
&[],
&[],
&[],
&[],
)
.expect("kotlin_android e2e generation");
let build_file = generated
.iter()
.find(|file| file.path.ends_with("build.gradle.kts"))
.expect("kotlin_android emits build.gradle.kts");
let first = crate::cli::pipeline::write_scaffold_files_report(std::slice::from_ref(build_file), base, true)
.expect("first write");
assert!(
first.refused_paths.is_empty(),
"the first write creates the file and can never be refused: {:?}",
first.refused_paths
);
let on_disk = base.join(&build_file.path);
assert!(on_disk.is_file(), "first write must create {}", on_disk.display());
let after_template_change = GeneratedFile {
path: build_file.path.clone(),
content: format!(
"{}\n// a later alef release renders one more line\n",
build_file.content
),
generated_header: build_file.generated_header,
};
let second =
crate::cli::pipeline::write_scaffold_files_report(std::slice::from_ref(&after_template_change), base, true)
.expect("second write");
assert!(
second.refused_paths.is_empty(),
"alef #474: the regenerated build.gradle.kts was refused, so the file is frozen -- \
{:?}",
second.refused_paths
);
let written = std::fs::read_to_string(&on_disk).expect("read back");
assert_eq!(
written, after_template_change.content,
"the template change must actually reach disk, not merely avoid a refusal"
);
let unmarked_temp = tempfile::tempdir().expect("tempdir");
let unmarked_base = unmarked_temp.path();
let stripped: String = build_file
.content
.lines()
.filter(|line| !line.contains("Generated by alef"))
.collect::<Vec<_>>()
.join("\n");
assert!(
!crate::core::hash::content_has_alef_marker(&stripped),
"the negative control must actually be unmarked, or it proves nothing"
);
let unmarked = GeneratedFile {
path: build_file.path.clone(),
content: stripped.clone(),
generated_header: false,
};
crate::cli::pipeline::write_scaffold_files_report(std::slice::from_ref(&unmarked), unmarked_base, true)
.expect("first unmarked write");
let unmarked_changed = GeneratedFile {
path: build_file.path.clone(),
content: format!("{stripped}\n// a later alef release renders one more line\n"),
generated_header: false,
};
let refused =
crate::cli::pipeline::write_scaffold_files_report(std::slice::from_ref(&unmarked_changed), unmarked_base, true)
.expect("second unmarked write");
assert_eq!(
refused.refused_paths.len(),
1,
"negative control: an unmarked, unrecorded .kts must be refused on the second write, \
otherwise the positive assertion above passes for some reason other than the marker"
);
}