use super::*;
use crate::core::config::e2e::{ArgMapping, CallOverride};
use crate::core::config::{ResolvedCrateConfig, TraitBridgeConfig};
use crate::core::ir::{MethodDef, ReceiverKind, TypeDef, TypeRef};
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::Fixture;
fn trait_bridge_method() -> MethodDef {
MethodDef {
name: "probe".to_string(),
params: vec![],
return_type: TypeRef::Primitive(crate::core::ir::PrimitiveType::U64),
is_async: false,
is_static: false,
error_type: None,
doc: String::new(),
receiver: Some(ReceiverKind::Ref),
cfg: None,
sanitized: false,
trait_source: None,
returns_ref: false,
returns_cow: false,
return_newtype_wrapper: None,
has_default_impl: false,
binding_excluded: false,
binding_exclusion_reason: None,
version: Default::default(),
}
}
fn register_fixture() -> Fixture {
Fixture {
id: "register_test_backend_trait_bridge".into(),
description: "register_test_backend: trait bridge".into(),
args: vec![ArgMapping {
name: "backend".to_string(),
field: "backend".to_string(),
arg_type: "test_backend".to_string(),
optional: false,
owned: false,
element_type: None,
go_type: None,
vec_inner_is_ref: false,
trait_name: Some("TestTrait".to_string()),
}],
assertions: vec![crate::e2e::fixture::Assertion {
assertion_type: "not_error".into(),
..Default::default()
}],
..Fixture::default()
}
}
fn base_e2e_config(function_name: &str, zig_override: CallOverride) -> (E2eConfig, ResolvedCrateConfig, Vec<TypeDef>) {
let mut e2e = E2eConfig::default();
e2e.call.function = function_name.to_string();
e2e.call.overrides.insert("zig".into(), zig_override);
let mut config = ResolvedCrateConfig::default();
config.trait_bridges.push(TraitBridgeConfig {
trait_name: "TestTrait".to_string(),
super_trait: None,
register_fn: Some(function_name.to_string()),
..Default::default()
});
let type_defs = vec![TypeDef {
name: "TestTrait".to_string(),
methods: vec![trait_bridge_method()],
is_trait: true,
..Default::default()
}];
(e2e, config, type_defs)
}
#[test]
fn register_style_call_checks_return_code_and_frees_error_message() {
let fixture = register_fixture();
let (e2e, config, type_defs) = base_e2e_config(
"register_test_backend",
CallOverride {
function: Some("register_test_backend".to_string()),
returns_result: Some(false),
..Default::default()
},
);
let rendered = render_test_file(
"plugin_api",
&[&fixture],
&e2e,
"register_test_backend",
"result",
&[],
"sample",
"sample",
&config,
&type_defs,
&[],
crate::e2e::codegen::call_ir::CallIr::default(),
&[],
);
assert!(
!rendered.contains("_ = sample.register_test_backend("),
"return code must not be discarded via `_ = `, got:\n{rendered}"
);
assert!(
rendered.contains("const _rc = sample.register_test_backend(") && rendered.contains("if (_rc != 0) {"),
"expected the i32 return code to be captured and checked, got:\n{rendered}"
);
assert!(
rendered.contains("register_test_backend failed:"),
"failure message must name the failing call, got:\n{rendered}"
);
assert!(
rendered.contains("return error.TestUnexpectedResult;"),
"a non-zero return code must fail the test, got:\n{rendered}"
);
assert!(
rendered.contains("sample._free_string(_m)"),
"out_error message must be freed on failure, got:\n{rendered}"
);
assert!(
!rendered.contains("try sample.register_test_backend("),
"register-fn shape returns i32, not an error union; `try` would not compile:\n{rendered}"
);
}
#[test]
fn error_union_style_call_keeps_using_try() {
let fixture = register_fixture();
let (e2e, config, type_defs) = base_e2e_config(
"clear_test_backend",
CallOverride {
function: Some("clear_test_backend".to_string()),
..Default::default()
},
);
let rendered = render_test_file(
"plugin_api",
&[&fixture],
&e2e,
"clear_test_backend",
"result",
&[],
"sample",
"sample",
&config,
&type_defs,
&[],
crate::e2e::codegen::call_ir::CallIr::default(),
&[],
);
assert!(
rendered.contains("_ = try sample.clear_test_backend("),
"an error-union-returning call must still use `try`, got:\n{rendered}"
);
assert!(
!rendered.contains("const _rc ="),
"the out_error return-code check must not apply to an error-union call, got:\n{rendered}"
);
}