Skip to content

Commit f0f82e1

Browse files
Haofeiclaude
andcommitted
test: cover native-plugin loader binding mapping (was 0%)
Unit-tests try_build_binding/shim_type/shim_return: scalar bindings, mut-param index recording, Result + nested List/Option mapping, missing-return = plain Unit, and rejection of unsupported param/return types. The build+dlopen half is OS plumbing left to a gated e2e; this is the bug-prone type mapping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8181487 commit f0f82e1

1 file changed

Lines changed: 83 additions & 0 deletions

File tree

crates/rsscript/src/native_plugin/loader.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,86 @@ fn library_file_name(crate_name: &str) -> String {
216216
format!("lib{crate_name}.so")
217217
}
218218
}
219+
220+
#[cfg(test)]
221+
mod tests {
222+
//! The native-plugin loader's *signature → shim binding* mapping. The build +
223+
//! `dlopen` half is OS plumbing (libloading) exercised by the gated e2e test;
224+
//! the bug-prone logic is this type mapping, which was previously 0% covered.
225+
//! Inputs are parsed from tiny interface snippets so we don't hand-build spans.
226+
use super::*;
227+
use crate::syntax::ast::Item;
228+
use crate::syntax::parse_source;
229+
230+
fn sig(src: &str) -> (Vec<Param>, Option<TypeRef>) {
231+
let program = parse_source("t.rssi", src);
232+
for item in program.items {
233+
if let Item::Function(decl) = item {
234+
return (decl.params, decl.return_ty);
235+
}
236+
}
237+
panic!("interface snippet declared no function");
238+
}
239+
240+
#[test]
241+
fn builds_plain_scalar_binding() {
242+
let (params, ret) =
243+
sig("native fn Adder.add(a: Int, b: Int) -> Int\n effects(native)\n");
244+
let binding = try_build_binding("Adder.add", "adder::add", &params, ret.as_ref())
245+
.expect("scalar binding should build");
246+
assert_eq!(binding.symbol, "Adder.add");
247+
assert_eq!(binding.rust_path, "adder::add");
248+
assert_eq!(binding.params, vec![ShimType::Int, ShimType::Int]);
249+
assert_eq!(binding.ret, ShimReturn::Plain(ShimType::Int));
250+
assert!(binding.mut_indices.is_empty());
251+
}
252+
253+
#[test]
254+
fn records_mut_param_positions() {
255+
let (params, ret) =
256+
sig("native fn Buf.fill(buffer: mut Bytes, value: Int) -> Unit\n effects(native)\n");
257+
let binding = try_build_binding("Buf.fill", "buf::fill", &params, ret.as_ref())
258+
.expect("mut binding should build");
259+
assert_eq!(binding.mut_indices, vec![0]);
260+
assert_eq!(binding.params, vec![ShimType::Bytes, ShimType::Int]);
261+
}
262+
263+
#[test]
264+
fn maps_result_and_nested_container_types() {
265+
let (params, ret) = sig(
266+
"native fn P.run(items: read List<String>) -> Result<Option<Int>, String>\n effects(native)\n",
267+
);
268+
let binding = try_build_binding("P.run", "p::run", &params, ret.as_ref())
269+
.expect("container binding should build");
270+
assert_eq!(
271+
binding.params,
272+
vec![ShimType::List(Box::new(ShimType::String))]
273+
);
274+
assert_eq!(
275+
binding.ret,
276+
ShimReturn::Result(ShimType::Option(Box::new(ShimType::Int)))
277+
);
278+
}
279+
280+
#[test]
281+
fn missing_return_type_is_plain_unit() {
282+
let (params, ret) = sig("native fn X.g(a: Int)\n effects(native)\n");
283+
let binding = try_build_binding("X.g", "x::g", &params, ret.as_ref())
284+
.expect("unit-return binding should build");
285+
assert_eq!(binding.ret, ShimReturn::Plain(ShimType::Unit));
286+
}
287+
288+
#[test]
289+
fn rejects_unsupported_param_type() {
290+
// A user type the value bridge can't represent → the binding is skipped
291+
// (the VM reports "no host binding" only if the program actually calls it).
292+
let (params, ret) = sig("native fn X.f(cfg: read Config) -> Unit\n effects(native)\n");
293+
assert!(try_build_binding("X.f", "x::f", &params, ret.as_ref()).is_none());
294+
}
295+
296+
#[test]
297+
fn rejects_unsupported_return_type() {
298+
let (params, ret) = sig("native fn X.h(a: Int) -> Config\n effects(native)\n");
299+
assert!(try_build_binding("X.h", "x::h", &params, ret.as_ref()).is_none());
300+
}
301+
}

0 commit comments

Comments
 (0)