use super::{classify_param_type, emit_param_conversion};
use crate::core::ir::TypeRef;
#[test]
fn sync_pyo3_free_fn_releases_gil_around_core_call() {
use crate::codegen::generators::gen_function;
use crate::core::ir::{FunctionDef, ParamDef};
let func = FunctionDef {
name: "count_words".to_owned(),
rust_path: "sample_core::count_words".to_owned(),
params: vec![ParamDef {
name: "text".to_owned(),
ty: TypeRef::String,
optional: false,
default: None,
..ParamDef::default()
}],
return_type: TypeRef::Primitive(crate::core::ir::PrimitiveType::U64),
is_async: false,
error_type: None,
..FunctionDef::default()
};
let mapper = crate::backends::pyo3::type_map::Pyo3Mapper::new();
let cfg = crate::backends::pyo3::gen_bindings::config::binding_config("sample_core", true);
let adapter_bodies = ahash::AHashMap::new();
let opaque_types = ahash::AHashSet::new();
let output = gen_function(&func, &mapper, &cfg, &adapter_bodies, &opaque_types);
assert!(
output.contains("py: Python<'_>"),
"expected injected `py: Python<'_>` handle on sync free function:\n{output}"
);
assert!(
output.contains("py.detach(|| sample_core::count_words("),
"expected core call wrapped in `py.detach(|| ...)`:\n{output}"
);
}
#[test]
fn classify_param_type_returns_plain_for_named() {
let ty = TypeRef::Named("Foo".to_string());
let result = classify_param_type(&ty);
assert!(result.is_some());
let (name, _) = result.unwrap();
assert_eq!(name, "Foo");
}
#[test]
fn classify_param_type_returns_none_for_primitive() {
let ty = TypeRef::Primitive(crate::core::ir::PrimitiveType::Bool);
assert!(classify_param_type(&ty).is_none());
}
#[test]
fn emit_param_conversion_guards_optional() {
let mut out = String::new();
emit_param_conversion(&mut out, "_rust_x", "x", "convert(x)", true);
assert!(out.contains("if x is not None else None"));
}
#[test]
fn emit_param_conversion_direct_when_required() {
let mut out = String::new();
emit_param_conversion(&mut out, "_rust_x", "x", "convert(x)", false);
assert!(!out.contains("if x is not None"));
assert!(out.contains("_rust_x = convert(x)"));
}
#[test]
fn async_pyo3_functions_place_bindings_inside_async_block() {}
#[test]
fn pyo3_usize_return_is_not_cast_to_f64() {
use crate::codegen::generators::gen_function;
use crate::core::ir::{FunctionDef, PrimitiveType};
let func = FunctionDef {
name: "wide_value".to_owned(),
rust_path: "sample_core::wide_value".to_owned(),
params: vec![],
return_type: TypeRef::Primitive(PrimitiveType::Usize),
is_async: false,
error_type: None,
..FunctionDef::default()
};
let mapper = crate::backends::pyo3::type_map::Pyo3Mapper::new();
let cfg = crate::backends::pyo3::gen_bindings::config::binding_config("sample_core", true);
let adapter_bodies = ahash::AHashMap::new();
let opaque_types = ahash::AHashSet::new();
let output = gen_function(&func, &mapper, &cfg, &adapter_bodies, &opaque_types);
assert!(
!output.contains("as f64"),
"pyo3 usize return must not be cast to f64:\n{output}"
);
assert!(
output.contains("-> usize"),
"pyo3 signature should keep usize:\n{output}"
);
}
#[test]
fn pyo3_mut_dto_param_returns_the_updated_value() {
use crate::codegen::generators::gen_function;
use crate::core::ir::{FunctionDef, ParamDef};
let func = FunctionDef {
name: "tag_record".to_owned(),
rust_path: "sample_core::tag_record".to_owned(),
params: vec![ParamDef {
name: "record".to_owned(),
ty: TypeRef::Named("Record".to_owned()),
is_ref: true,
is_mut: true,
..ParamDef::default()
}],
return_type: TypeRef::Unit,
is_async: false,
error_type: None,
..FunctionDef::default()
};
let mapper = crate::backends::pyo3::type_map::Pyo3Mapper::new();
let cfg = crate::backends::pyo3::gen_bindings::config::binding_config("sample_core", true);
let adapter_bodies = ahash::AHashMap::new();
let opaque_types = ahash::AHashSet::new();
let output = gen_function(&func, &mapper, &cfg, &adapter_bodies, &opaque_types);
assert!(
output.contains("-> Record"),
"expected the binding to return the mutated DTO type instead of `()`:\n{output}"
);
assert!(
!output.contains("-> ()"),
"must not still advertise a unit return:\n{output}"
);
assert!(
output.contains("py.detach(|| sample_core::tag_record(&mut record_core))"),
"expected the core call to still pass `&mut record_core`:\n{output}"
);
assert!(
output.contains("record_core.into()"),
"expected the mutated intermediate to be returned:\n{output}"
);
}
#[test]
fn pyo3_immutable_dto_param_keeps_unit_return() {
use crate::codegen::generators::gen_function;
use crate::core::ir::{FunctionDef, ParamDef};
let func = FunctionDef {
name: "read_record".to_owned(),
rust_path: "sample_core::read_record".to_owned(),
params: vec![ParamDef {
name: "record".to_owned(),
ty: TypeRef::Named("Record".to_owned()),
is_ref: true,
is_mut: false,
..ParamDef::default()
}],
return_type: TypeRef::Unit,
is_async: false,
error_type: None,
..FunctionDef::default()
};
let mapper = crate::backends::pyo3::type_map::Pyo3Mapper::new();
let cfg = crate::backends::pyo3::gen_bindings::config::binding_config("sample_core", true);
let adapter_bodies = ahash::AHashMap::new();
let opaque_types = ahash::AHashSet::new();
let output = gen_function(&func, &mapper, &cfg, &adapter_bodies, &opaque_types);
assert!(
output.contains("-> ()"),
"immutable borrow must keep unit return:\n{output}"
);
assert!(
!output.contains("record_core.into()"),
"immutable borrow must not gain a write-back tail:\n{output}"
);
}
#[test]
fn pyo3_owned_dto_param_unaffected_by_writeback() {
use crate::codegen::generators::gen_function;
use crate::core::ir::{FunctionDef, ParamDef};
let func = FunctionDef {
name: "consume_record".to_owned(),
rust_path: "sample_core::consume_record".to_owned(),
params: vec![ParamDef {
name: "record".to_owned(),
ty: TypeRef::Named("Record".to_owned()),
is_ref: false,
is_mut: false,
..ParamDef::default()
}],
return_type: TypeRef::Unit,
is_async: false,
error_type: None,
..FunctionDef::default()
};
let mapper = crate::backends::pyo3::type_map::Pyo3Mapper::new();
let cfg = crate::backends::pyo3::gen_bindings::config::binding_config("sample_core", true);
let adapter_bodies = ahash::AHashMap::new();
let opaque_types = ahash::AHashSet::new();
let output = gen_function(&func, &mapper, &cfg, &adapter_bodies, &opaque_types);
assert!(output.contains("-> ()"), "owned param must keep unit return:\n{output}");
assert!(
output.contains("py.detach(|| sample_core::consume_record(record_core))"),
"owned param call must be unaffected:\n{output}"
);
assert!(
!output.contains("record_core.into()"),
"owned param must not gain a write-back tail:\n{output}"
);
}
#[test]
fn pyo3_async_mut_dto_param_returns_the_updated_value() {
use crate::codegen::generators::gen_function;
use crate::core::ir::{FunctionDef, ParamDef};
let func = FunctionDef {
name: "tag_record_async".to_owned(),
rust_path: "sample_core::tag_record_async".to_owned(),
params: vec![ParamDef {
name: "record".to_owned(),
ty: TypeRef::Named("Record".to_owned()),
is_ref: true,
is_mut: true,
..ParamDef::default()
}],
return_type: TypeRef::Unit,
is_async: true,
error_type: None,
..FunctionDef::default()
};
let mapper = crate::backends::pyo3::type_map::Pyo3Mapper::new();
let cfg = crate::backends::pyo3::gen_bindings::config::binding_config("sample_core", true);
let adapter_bodies = ahash::AHashMap::new();
let opaque_types = ahash::AHashSet::new();
let output = gen_function(&func, &mapper, &cfg, &adapter_bodies, &opaque_types);
assert!(
output.contains("sample_core::tag_record_async(&mut record_core).await"),
"expected the core call to still be awaited with `&mut record_core`:\n{output}"
);
assert!(
output.contains("Ok(record_core.into())"),
"expected the future to resolve to the mutated intermediate:\n{output}"
);
assert!(
!output.contains("Ok(())"),
"must not resolve the future to unit and drop the mutated value:\n{output}"
);
}
#[test]
fn pyo3_async_immutable_dto_param_unaffected_by_writeback() {
use crate::codegen::generators::gen_function;
use crate::core::ir::{FunctionDef, ParamDef};
let func = FunctionDef {
name: "read_record_async".to_owned(),
rust_path: "sample_core::read_record_async".to_owned(),
params: vec![ParamDef {
name: "record".to_owned(),
ty: TypeRef::Named("Record".to_owned()),
is_ref: true,
is_mut: false,
..ParamDef::default()
}],
return_type: TypeRef::Unit,
is_async: true,
error_type: None,
..FunctionDef::default()
};
let mapper = crate::backends::pyo3::type_map::Pyo3Mapper::new();
let cfg = crate::backends::pyo3::gen_bindings::config::binding_config("sample_core", true);
let adapter_bodies = ahash::AHashMap::new();
let opaque_types = ahash::AHashSet::new();
let output = gen_function(&func, &mapper, &cfg, &adapter_bodies, &opaque_types);
assert!(
!output.contains("record_core.into()"),
"immutable borrow must not gain a write-back tail:\n{output}"
);
}
#[test]
fn pyo3_async_owned_dto_param_unaffected_by_writeback() {
use crate::codegen::generators::gen_function;
use crate::core::ir::{FunctionDef, ParamDef};
let func = FunctionDef {
name: "consume_record_async".to_owned(),
rust_path: "sample_core::consume_record_async".to_owned(),
params: vec![ParamDef {
name: "record".to_owned(),
ty: TypeRef::Named("Record".to_owned()),
is_ref: false,
is_mut: false,
..ParamDef::default()
}],
return_type: TypeRef::Unit,
is_async: true,
error_type: None,
..FunctionDef::default()
};
let mapper = crate::backends::pyo3::type_map::Pyo3Mapper::new();
let cfg = crate::backends::pyo3::gen_bindings::config::binding_config("sample_core", true);
let adapter_bodies = ahash::AHashMap::new();
let opaque_types = ahash::AHashSet::new();
let output = gen_function(&func, &mapper, &cfg, &adapter_bodies, &opaque_types);
assert!(
!output.contains("record_core.into()"),
"owned param must not gain a write-back tail:\n{output}"
);
}
#[test]
fn pyo3_async_mut_dto_param_with_error_returns_the_updated_value() {
use crate::codegen::generators::gen_function;
use crate::core::ir::{FunctionDef, ParamDef};
let func = FunctionDef {
name: "tag_record_async".to_owned(),
rust_path: "sample_core::tag_record_async".to_owned(),
params: vec![ParamDef {
name: "record".to_owned(),
ty: TypeRef::Named("Record".to_owned()),
is_ref: true,
is_mut: true,
..ParamDef::default()
}],
return_type: TypeRef::Unit,
is_async: true,
error_type: Some("ProbeError".to_owned()),
..FunctionDef::default()
};
let mapper = crate::backends::pyo3::type_map::Pyo3Mapper::new();
let cfg = crate::backends::pyo3::gen_bindings::config::binding_config("sample_core", true);
let adapter_bodies = ahash::AHashMap::new();
let opaque_types = ahash::AHashSet::new();
let output = gen_function(&func, &mapper, &cfg, &adapter_bodies, &opaque_types);
assert!(
output.contains("-> PyResult<Bound<'py, PyAny>>"),
"pyo3 async functions always declare a PyResult<Bound> outer signature:\n{output}"
);
assert!(
output.contains("sample_core::tag_record_async(&mut record_core).await"),
"expected the core call to still be awaited with `&mut record_core`:\n{output}"
);
assert!(
output.contains("Ok(record_core.into())"),
"expected the fallible future to resolve to the mutated intermediate on success:\n{output}"
);
}