use super::CallIr;
use crate::e2e::config::CallConfig;
pub(super) fn is_fallible(call: &CallConfig, ir: CallIr<'_>) -> bool {
call.core_lookup_name("c")
.as_deref()
.and_then(|name| ir.signature(name))
.and_then(|signature| signature.error_type)
.is_some()
}
pub(super) fn render_call_line(
function_name: &str,
args: &str,
result_var: &str,
expects_error: bool,
is_fallible: bool,
) -> String {
if is_fallible {
crate::e2e::template_env::render(
"c/snippet_status_call.jinja",
minijinja::context! {
result_var => result_var,
function_name => function_name,
args => args,
expects_error => expects_error,
},
)
} else {
crate::e2e::template_env::render(
"c/snippet_void_call.jinja",
minijinja::context! { function_name => function_name, args => args },
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::{FunctionDef, TypeRef};
fn call_named(function: &str) -> CallConfig {
CallConfig {
function: function.to_string(),
..CallConfig::default()
}
}
#[test]
fn a_result_unit_error_function_is_fallible() {
let functions = vec![FunctionDef {
name: "reset_cache".into(),
return_type: TypeRef::Unit,
error_type: Some("SampleError".into()),
..FunctionDef::default()
}];
let ir = CallIr {
functions: &functions,
type_defs: &[],
};
assert!(is_fallible(&call_named("reset_cache"), ir));
}
#[test]
fn a_plain_unit_function_is_not_fallible() {
let functions = vec![FunctionDef {
name: "log_ping".into(),
return_type: TypeRef::Unit,
error_type: None,
..FunctionDef::default()
}];
let ir = CallIr {
functions: &functions,
type_defs: &[],
};
assert!(!is_fallible(&call_named("log_ping"), ir));
}
#[test]
fn an_absent_ir_is_not_fallible() {
assert!(!is_fallible(&call_named("reset_cache"), CallIr::default()));
}
#[test]
fn fallible_call_line_captures_and_asserts_the_status() {
let rendered = render_call_line("sample_reset_cache", "", "result", false, true);
assert_eq!(
rendered,
"int32_t result = sample_reset_cache();\nassert(result == 0 && \"expected call to succeed\");\n"
);
}
#[test]
fn fallible_call_line_flips_polarity_when_the_fixture_expects_an_error() {
let rendered = render_call_line("sample_reset_cache", "", "result", true, true);
assert_eq!(
rendered,
"int32_t result = sample_reset_cache();\nassert(result != 0 && \"expected call to fail\");\n"
);
}
#[test]
fn non_fallible_call_line_discards_the_call_as_before() {
let rendered = render_call_line("sample_log_ping", "", "result", false, false);
assert_eq!(rendered, "sample_log_ping();\n");
}
#[test]
fn returns_void_fallible_call_captures_and_asserts_the_discarded_status() {
use crate::core::config::ResolvedCrateConfig;
use crate::core::config::e2e::E2eConfig;
use crate::e2e::fixture::Fixture;
let fixture = Fixture {
id: "reset_cache".into(),
description: "Reset the on-disk cache".into(),
input: serde_json::json!({}),
..Fixture::default()
};
let mut e2e = E2eConfig::default();
e2e.call.function = "reset_cache".into();
e2e.call.returns_void = true;
e2e.call.overrides.insert(
"c".into(),
crate::core::config::e2e::CallOverride {
header: Some("sample_ffi.h".into()),
function: Some("sample_reset_cache".into()),
..Default::default()
},
);
let functions = [FunctionDef {
name: "reset_cache".into(),
return_type: TypeRef::Unit,
error_type: Some("SampleError".into()),
..FunctionDef::default()
}];
let config = ResolvedCrateConfig {
name: "sample".into(),
..ResolvedCrateConfig::default()
};
let rendered = super::super::render_c_snippet(&fixture, &e2e, &config, &[], &functions)
.expect("a fallible returns_void call must still render");
assert!(
rendered.contains("int32_t") && rendered.contains("sample_reset_cache()"),
"the discarded FFI status must be captured into a typed variable:\n{rendered}"
);
assert!(
rendered.contains("assert(") && rendered.contains("== 0"),
"a not_error fixture on a fallible void call must assert the status reports \
success, not render an unchecked call:\n{rendered}"
);
super::super::snippet_regressions::compile_snippet(
&rendered,
"sample_ffi.h",
concat!("#include <stdint.h>\n", "int32_t sample_reset_cache(void);\n"),
);
}
}