use super::*;
fn test_backend_arg(trait_name: &str) -> crate::e2e::config::ArgMapping {
crate::e2e::config::ArgMapping {
name: "backend".into(),
field: "backend".into(),
arg_type: "test_backend".into(),
optional: false,
owned: false,
element_type: None,
go_type: None,
vec_inner_is_ref: false,
trait_name: Some(trait_name.to_string()),
}
}
#[test]
#[should_panic(expected = "test-backend emitter is unimplemented")]
fn registered_test_backend_trait_panics_because_c_backend_is_unimplemented() {
use crate::core::config::TraitBridgeConfig;
let bridge = TraitBridgeConfig {
trait_name: "SampleBackend".into(),
..TraitBridgeConfig::default()
};
let config = ResolvedCrateConfig {
trait_bridges: vec![bridge],
..ResolvedCrateConfig::default()
};
let fixture = Fixture {
id: "register_sample_backend".into(),
..Fixture::default()
};
let args = vec![test_backend_arg("SampleBackend")];
let _ = build_args_string_c(
&fixture.input,
&args,
&HashMap::new(),
&config,
&[],
&fixture,
"register_sample_backend",
TargetParams::IrAbsent,
);
}
#[test]
#[should_panic(expected = "no `[[crates.trait_bridges]]` entry")]
fn unregistered_test_backend_trait_panics_instead_of_falling_back_to_null() {
let config = ResolvedCrateConfig::default();
let fixture = Fixture {
id: "register_sample_backend".into(),
..Fixture::default()
};
let args = vec![test_backend_arg("SampleBackend")];
let _ = build_args_string_c(
&fixture.input,
&args,
&HashMap::new(),
&config,
&[],
&fixture,
"register_sample_backend",
TargetParams::IrAbsent,
);
}
#[test]
fn should_emit_empty_parens_when_args_unconfigured_and_target_takes_no_parameters() {
let fixture = Fixture {
id: "list_ocr_backends".into(),
input: serde_json::json!({"cache_dir": "/tmp/sample_cache"}),
..Fixture::default()
};
let config = ResolvedCrateConfig::default();
let result = build_args_string_c(
&fixture.input,
&[],
&HashMap::new(),
&config,
&[],
&fixture,
"list_ocr_backends",
TargetParams::Known(&[]),
)
.expect("a genuinely zero-argument target must not fail generation");
assert_eq!(
result, "",
"a zero-argument call must emit `()`, not a fabricated literal"
);
}
#[test]
fn should_refuse_when_args_unconfigured_and_target_takes_a_typed_parameter() {
let fixture = Fixture {
id: "pack_configure_defaults".into(),
input: serde_json::json!({"cache_dir": "/tmp/sample_cache"}),
..Fixture::default()
};
let config = ResolvedCrateConfig::default();
let params = [ParamDef {
name: "config".into(),
..ParamDef::default()
}];
let error = build_args_string_c(
&fixture.input,
&[],
&HashMap::new(),
&config,
&[],
&fixture,
"ts_pack_configure",
TargetParams::Known(¶ms),
)
.expect_err("a known non-empty parameter list must not be papered over with a JSON literal")
.to_string();
assert!(
!error.contains("cache_dir"),
"must not leak the fixture JSON into a diagnostic that replaces splicing it: {error}"
);
assert!(error.contains("ts_pack_configure"), "must name the call: {error}");
assert!(error.contains("config"), "must name the unfilled parameter: {error}");
assert!(error.contains("args"), "must point at the `args` config knob: {error}");
}
#[test]
fn should_refuse_when_args_unconfigured_and_target_signature_is_unresolvable() {
let fixture = Fixture {
id: "mystery_call".into(),
input: serde_json::json!({"cache_dir": "/tmp/sample_cache"}),
..Fixture::default()
};
let config = ResolvedCrateConfig::default();
let error = build_args_string_c(
&fixture.input,
&[],
&HashMap::new(),
&config,
&[],
&fixture,
"mystery_fn",
TargetParams::Unresolvable,
)
.expect_err("an unresolvable signature must not fall back to guessing")
.to_string();
assert!(error.contains("mystery_fn"), "must name the call: {error}");
assert!(error.contains("args"), "must point at the `args` config knob: {error}");
}
#[test]
fn should_keep_prior_behaviour_when_there_is_no_ir_to_consult() {
let fixture = Fixture {
id: "no_ir".into(),
input: serde_json::json!({"cache_dir": "/tmp/sample_cache"}),
..Fixture::default()
};
let config = ResolvedCrateConfig::default();
let rendered = build_args_string_c(
&fixture.input,
&[],
&HashMap::new(),
&config,
&[],
&fixture,
"sample_fn",
TargetParams::IrAbsent,
)
.expect("an absent IR must not fail generation on a path that never had a signature");
assert_eq!(
rendered,
json_to_c(&fixture.input),
"with no IR consulted the emitter must render exactly what it rendered before"
);
}
#[test]
fn should_still_emit_configured_args_unchanged_when_args_are_present() {
let fixture = Fixture {
id: "chat_basic".into(),
input: serde_json::json!({"text": "hello"}),
..Fixture::default()
};
let config = ResolvedCrateConfig::default();
let args = vec![crate::e2e::config::ArgMapping {
name: "text".into(),
field: "text".into(),
arg_type: "string".into(),
optional: false,
owned: false,
element_type: None,
go_type: None,
vec_inner_is_ref: false,
trait_name: None,
}];
let result = build_args_string_c(
&fixture.input,
&args,
&HashMap::new(),
&config,
&[],
&fixture,
"chat",
TargetParams::Unresolvable,
)
.expect("configured args must still render");
assert_eq!(
result, "\"hello\"",
"a configured string arg must still emit its real typed literal"
);
}
fn string_arg(name: &str, field: &str) -> crate::e2e::config::ArgMapping {
crate::e2e::config::ArgMapping {
name: name.into(),
field: field.into(),
arg_type: "string".into(),
optional: false,
owned: false,
element_type: None,
go_type: None,
vec_inner_is_ref: false,
trait_name: None,
}
}
#[test]
fn should_refuse_a_string_literal_configured_against_a_handle_parameter() {
let fixture = Fixture {
id: "configure_cache_dir".into(),
input: serde_json::json!({"config": {"cache_dir": "/tmp/sample_cache"}}),
..Fixture::default()
};
let config = ResolvedCrateConfig::default();
let args = vec![string_arg("config", "config")];
let params = [ParamDef {
name: "config".into(),
ty: TypeRef::Named("SampleConfig".into()),
..ParamDef::default()
}];
let type_defs = [TypeDef {
name: "SampleConfig".into(),
has_serde: true,
..TypeDef::default()
}];
let error = build_args_string_c(
&fixture.input,
&args,
&HashMap::new(),
&config,
&type_defs,
&fixture,
"sample_configure",
TargetParams::Known(¶ms),
)
.expect_err("a JSON object must not be lowered into a handle parameter")
.to_string();
assert!(error.contains("sample_configure"), "must name the call: {error}");
assert!(error.contains("`config`"), "must name the parameter: {error}");
assert!(
error.contains("AlefHandle"),
"must name the parameter's C type: {error}"
);
assert!(
error.contains("cache_dir"),
"must quote the offending value so the operator can find the entry: {error}"
);
assert!(
error.contains("json_object"),
"must name the configuration that constructs the handle: {error}"
);
}
#[test]
fn should_not_refuse_a_json_literal_against_a_vec_parameter() {
let fixture = Fixture {
id: "rank_documents".into(),
input: serde_json::json!({"documents": ["alpha", "beta"]}),
..Fixture::default()
};
let config = ResolvedCrateConfig::default();
let args = vec![string_arg("documents", "documents")];
let params = [ParamDef {
name: "documents".into(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("Document".into()))),
..ParamDef::default()
}];
let type_defs = [TypeDef {
name: "Document".into(),
has_serde: true,
..TypeDef::default()
}];
let rendered = build_args_string_c(
&fixture.input,
&args,
&HashMap::new(),
&config,
&type_defs,
&fixture,
"sample_rank",
TargetParams::Known(¶ms),
)
.expect("a JSON-string parameter must keep rendering its literal");
assert_eq!(
rendered,
json_to_c(&fixture.input["documents"]),
"a `Vec<T>` parameter crosses as a JSON `const char *`, so the literal is correct"
);
}
#[test]
fn should_not_refuse_a_named_parameter_the_ir_carries_no_type_def_for() {
let fixture = Fixture {
id: "set_level".into(),
input: serde_json::json!({"level": "debug"}),
..Fixture::default()
};
let config = ResolvedCrateConfig::default();
let args = vec![string_arg("level", "level")];
let params = [ParamDef {
name: "level".into(),
ty: TypeRef::Named("LogLevel".into()),
..ParamDef::default()
}];
let rendered = build_args_string_c(
&fixture.input,
&args,
&HashMap::new(),
&config,
&[],
&fixture,
"sample_set_level",
TargetParams::Known(¶ms),
)
.expect("a name with no `TypeDef` behind it licenses no claim about the C type");
assert_eq!(rendered, "\"debug\"", "the rendering must be left exactly as it was");
}