use alef::core::config::NewAlefConfig;
use alef::e2e::codegen::E2eCodegen;
use alef::e2e::codegen::go::GoCodegen;
use alef::e2e::codegen::python::PythonE2eCodegen;
use alef::e2e::codegen::rust::RustE2eCodegen;
use alef::e2e::codegen::typescript::TypeScriptCodegen;
use alef::e2e::fixture::{Assertion, Fixture, FixtureGroup};
use std::path::PathBuf;
const REPEATED_GENERATION_COUNT: usize = 32;
fn build_config(language: &str) -> (alef::e2e::config::E2eConfig, alef::core::config::ResolvedCrateConfig) {
let toml_src = format!(
r#"
[workspace]
languages = ["{language}"]
[[crates]]
name = "demo_widgets"
sources = ["src/lib.rs"]
[crates.e2e]
fixtures = "fixtures"
output = "e2e"
[crates.e2e.env]
ZEBRA_ALLOW_PRIVATE_NETWORK = "z"
ALPHA_ALLOW_PRIVATE_NETWORK = "a"
MIKE_ALLOW_PRIVATE_NETWORK = "m"
BRAVO_ALLOW_PRIVATE_NETWORK = "b"
[crates.e2e.call]
function = "make_widget"
module = "DemoWidgets"
result_var = "result"
async = true
returns_result = true
[[crates.e2e.call.args]]
name = "name"
field = "input.name"
type = "string"
[crates.e2e.calls.secondary]
function = "make_widget_variant"
module = "DemoWidgets"
result_var = "result"
async = true
returns_result = true
[[crates.e2e.calls.secondary.args]]
name = "name"
field = "input.name"
type = "string"
"#,
);
let cfg: NewAlefConfig = toml::from_str(&toml_src).expect("config parses");
let e2e = cfg.crates[0].e2e.clone().unwrap();
let resolved = cfg.resolve().expect("resolves").remove(0);
(e2e, resolved)
}
fn build_fixture_group() -> FixtureGroup {
FixtureGroup {
category: "smoke".to_string(),
fixtures: vec![
Fixture {
id: "smoke_default_call".to_string(),
category: Some("smoke".to_string()),
description: "default call".to_string(),
input: serde_json::json!({ "name": "first" }),
assertions: vec![Assertion {
assertion_type: "not_error".to_string(),
..Assertion::default()
}],
source: "smoke.json".to_string(),
..Fixture::default()
},
Fixture {
id: "smoke_secondary_call".to_string(),
category: Some("smoke".to_string()),
description: "named secondary call".to_string(),
call: Some("secondary".to_string()),
input: serde_json::json!({ "name": "second" }),
assertions: vec![Assertion {
assertion_type: "not_error".to_string(),
..Assertion::default()
}],
source: "smoke.json".to_string(),
..Fixture::default()
},
],
}
}
fn render_once(codegen: &dyn E2eCodegen, language: &str) -> Vec<(PathBuf, String)> {
let (e2e, resolved) = build_config(language);
let groups = vec![build_fixture_group()];
let mut files: Vec<(PathBuf, String)> = codegen
.generate(&groups, &e2e, &resolved, &[], &[], &[], &[])
.unwrap_or_else(|err| panic!("{language} e2e generation must succeed: {err:?}"))
.into_iter()
.map(|f| (f.path, f.content))
.collect();
files.sort_by(|a, b| a.0.cmp(&b.0));
files
}
fn assert_stable_across_repeated_generation(codegen: &dyn E2eCodegen, language: &str) {
let expected = render_once(codegen, language);
assert!(
!expected.is_empty(),
"{language} e2e codegen must emit at least one file"
);
for attempt in 1..REPEATED_GENERATION_COUNT {
let actual = render_once(codegen, language);
assert_eq!(
actual, expected,
"{language} e2e codegen must be byte-identical across repeated generation of the \
same config (mismatch on attempt {attempt} of {REPEATED_GENERATION_COUNT}); a \
`HashMap`/`HashSet` iterating straight into emitted output is randomly seeded per \
process and would fail this assertion intermittently rather than deterministically."
);
}
}
#[test]
fn go_e2e_codegen_is_deterministic_across_repeated_generation() {
assert_stable_across_repeated_generation(&GoCodegen, "go");
}
#[test]
fn rust_e2e_codegen_is_deterministic_across_repeated_generation() {
assert_stable_across_repeated_generation(&RustE2eCodegen, "rust");
}
#[test]
fn python_e2e_codegen_is_deterministic_across_repeated_generation() {
assert_stable_across_repeated_generation(&PythonE2eCodegen, "python");
}
#[test]
fn typescript_e2e_codegen_is_deterministic_across_repeated_generation() {
assert_stable_across_repeated_generation(&TypeScriptCodegen, "node");
}