use prebindgen_registry::Conversions;
use super::*;
fn callback_snapshot_pipeline() -> (String, std::collections::BTreeMap<String, String>) {
use prebindgen::SourceLocation;
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_thing_name(this_: &ZThing) -> String {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_thing_sub(
cb: impl Fn(ZThing) + Send + Sync + 'static,
on_close: impl Fn() + Send + Sync + 'static,
) {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_other_sub(cb: impl Fn(ZOther) + Send + Sync + 'static) {
unimplemented!()
}
)),
loc.clone(),
),
];
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("thing")
.class(
crate::ptr_class!(ZThing)
.method(prebindgen_registry::fun!(z_thing_name).name("name")),
)
.class(crate::ptr_class!(ZOther))
.fun(prebindgen_registry::fun!(z_thing_sub))
.fun(prebindgen_registry::fun!(z_other_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZThing)
.field_self()
.field(prebindgen_registry::fun!(z_thing_name)),
);
let dir = unique_test_dir("jnigen_cb_snap");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let kdir = dir.join("kotlin");
let paths = gen.write_kotlin(&kdir).expect("write_kotlin");
let mut kotlin = std::collections::BTreeMap::new();
for p in &paths {
let name = p.file_name().unwrap().to_string_lossy().to_string();
kotlin.insert(name, std::fs::read_to_string(p).unwrap());
}
(rust, kotlin)
}
#[test]
fn callback_snapshot_rust_side() {
let (rust, _) = callback_snapshot_pipeline();
let rc: String = rust.split_whitespace().collect();
assert!(rc.contains(r#""run""#), "{rust}");
assert!(!rc.contains(r#""invoke""#), "{rust}");
assert!(rc.contains(r#""(JLjava/lang/String;)V""#), "{rust}");
assert!(rc.contains(r#""()V""#), "{rust}");
assert!(rc.contains(r#""(J)V""#), "{rust}");
assert!(!rc.contains(r#""close""#), "{rust}");
assert!(!rc.contains("io/test/jni/thing/ZOther"), "{rust}");
assert!(rc.contains("attach_current_thread_as_daemon"), "{rust}");
assert!(rc.contains("push_local_frame"), "{rust}");
assert!(rc.contains("pop_local_frame"), "{rust}");
assert!(rc.contains("jni::sys::jvalue{j:"), "{rust}");
assert!(!rc.contains("io/test/jni/thing/ZThing"), "{rust}");
assert!(rc.contains("myflat::z_thing_name"), "{rust}");
}
#[test]
fn callback_snapshot_kotlin_side() {
let (_, kotlin) = callback_snapshot_pipeline();
let names: Vec<&String> = kotlin.keys().collect();
let native: String = kotlin
.values()
.find(|v| v.contains("object JNINative"))
.map(|v| v.split_whitespace().collect())
.unwrap_or_else(|| {
panic!("no generated file contains `object JNINative`; files: {names:?}")
});
assert!(native.contains("cb:Any"), "{native}");
assert!(native.contains("onClose:Any"), "{native}");
let all: String = kotlin
.values()
.cloned()
.collect::<Vec<_>>()
.join("\n")
.split_whitespace()
.collect();
assert!(
all.contains("funinterfaceZThingCallback{publicfunrun(handle:ZThing,name:String)"),
"{all}"
);
assert!(
all.contains("funinterfaceZOtherCallback{publicfunrun(zOther:ZOther)"),
"{all}"
);
assert!(
all.contains("funinterfaceVoidCallback{publicfunrun()"),
"{all}"
);
assert!(
all.contains("funinterfaceZThingCallbackRaw{publicfunrun(handle:Long,name:String)"),
"{all}"
);
assert!(
all.contains("funZThingCallback.asRaw():ZThingCallbackRaw=ZThingCallbackRaw{handle,name->run(ZThing(handle),name)}"),
"{all}"
);
assert!(
all.contains("funinterfaceZOtherCallbackRaw{publicfunrun(zOther:Long)"),
"{all}"
);
assert!(all.contains("val__own0=ZOther(zOther)"), "{all}");
assert!(all.contains("finally{__own0.close()}"), "{all}");
assert!(!all.contains("VoidCallbackRaw"), "{all}");
let pkg = kotlin
.values()
.find(|v| v.contains("public fun zThingSub"))
.cloned()
.unwrap_or_default();
let pc: String = pkg.split_whitespace().collect();
assert!(pc.contains("cb:ZThingCallback"), "{pkg}");
assert!(pc.contains("cb.asRaw()"), "{pkg}");
assert!(pc.contains("onClose:VoidCallback"), "{pkg}");
assert!(pc.contains("cb:ZOtherCallback"), "{pkg}");
}
#[test]
fn callback_root_identity_moved_after_nested_borrow() {
use prebindgen::SourceLocation;
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_parent_child(this_: &ZParent) -> &ZChild {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_child_name(this_: &ZChild) -> String {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_parent_sub(
cb: impl Fn(ZParent) + Send + Sync + 'static,
on_close: impl Fn() + Send + Sync + 'static,
) {
unimplemented!()
}
)),
loc.clone(),
),
];
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("thing")
.class(
crate::ptr_class!(ZChild)
.method(prebindgen_registry::fun!(z_child_name).name("name")),
)
.class(
crate::ptr_class!(ZParent)
.method(prebindgen_registry::fun!(z_parent_child).name("child")),
)
.fun(prebindgen_registry::fun!(z_parent_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZChild)
.field_self()
.field(prebindgen_registry::fun!(z_child_name)),
)
.expand(
prebindgen_registry::expand_return!(ZParent)
.field(prebindgen_registry::fun!(z_parent_child))
.field_self(),
);
let dir = unique_test_dir("jnigen_root_id_order");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let rc: String = rust.split_whitespace().collect();
let move_pos = rc
.find("Box::new(__cb_arg0")
.unwrap_or_else(|| panic!("root identity move not found in:\n{rust}"));
let last_borrow = rc
.rfind("z_parent_child(&__cb_arg0")
.unwrap_or_else(|| panic!("nested child borrow not found in:\n{rust}"));
assert!(
last_borrow < move_pos,
"root identity move must follow every borrow of the owned arg\n{rust}"
);
}
#[test]
fn callback_double_option_unwrap_pipeline() {
use prebindgen::SourceLocation;
let loc = myflat_loc();
let fns: &[&str] = &[
"pub fn z_reply_zid(r: &ZReply) -> Option<ZId> { unimplemented!() }",
"pub fn z_reply_is_ok(r: &ZReply) -> bool { unimplemented!() }",
"pub fn z_reply_sample(r: &ZReply) -> Option<&ZSample> { unimplemented!() }",
"pub fn z_reply_err(r: &ZReply) -> Option<&ZErr> { unimplemented!() }",
"pub fn z_sample_key_expr(s: &ZSample) -> &ZKeyExpr { unimplemented!() }",
"pub fn z_sample_timestamp(s: &ZSample) -> Option<&ZTs> { unimplemented!() }",
"pub fn z_ts_ntp64(t: &ZTs) -> i64 { unimplemented!() }",
"pub fn z_keyexpr_as_str(ke: &ZKeyExpr) -> &str { unimplemented!() }",
"pub fn z_err_payload(e: &ZErr) -> Vec<u8> { unimplemented!() }",
];
let mut items: Vec<(syn::Item, SourceLocation)> = fns
.iter()
.map(|src| {
let f: syn::ItemFn = syn::parse_str(src).expect("parse fn");
(syn::Item::Fn(f), loc.clone())
})
.collect();
items.push((
syn::Item::Struct(syn::parse_quote!(
pub struct ZId {
pub hi: i64,
pub lo: i64,
}
)),
loc.clone(),
));
items.push((
syn::Item::Fn(syn::parse_quote!(
pub fn z_get(cb: impl Fn(ZReply) + Send + Sync + 'static) {
unimplemented!()
}
)),
loc.clone(),
));
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("query")
.class(crate::data_class!(ZId))
.class(
crate::ptr_class!(ZKeyExpr)
.method(prebindgen_registry::fun!(z_keyexpr_as_str).name("asStr")),
)
.class(
crate::ptr_class!(ZTs)
.method(prebindgen_registry::fun!(z_ts_ntp64).name("ntp64")),
)
.class(
crate::ptr_class!(ZSample)
.method(prebindgen_registry::fun!(z_sample_key_expr).name("keyExpr"))
.method(prebindgen_registry::fun!(z_sample_timestamp).name("timestamp")),
)
.class(
crate::ptr_class!(ZErr)
.method(prebindgen_registry::fun!(z_err_payload).name("payload")),
)
.class(
crate::ptr_class!(ZReply)
.method(prebindgen_registry::fun!(z_reply_zid).name("zid"))
.method(prebindgen_registry::fun!(z_reply_is_ok).name("isOk"))
.method(prebindgen_registry::fun!(z_reply_sample).name("sample"))
.method(prebindgen_registry::fun!(z_reply_err).name("err")),
)
.fun(prebindgen_registry::fun!(z_get)),
)
.expand(
prebindgen_registry::expand_return!(ZKeyExpr)
.field_self()
.field(prebindgen_registry::fun!(z_keyexpr_as_str)),
)
.expand(
prebindgen_registry::expand_return!(ZTs).field(prebindgen_registry::fun!(z_ts_ntp64)),
)
.expand(
prebindgen_registry::expand_return!(ZSample)
.field(prebindgen_registry::fun!(z_sample_key_expr))
.field(prebindgen_registry::fun!(z_sample_timestamp)),
)
.expand(
prebindgen_registry::expand_return!(ZErr)
.field(prebindgen_registry::fun!(z_err_payload)),
)
.expand(
prebindgen_registry::expand_return!(ZReply)
.field(prebindgen_registry::fun!(z_reply_zid))
.field(prebindgen_registry::fun!(z_reply_is_ok))
.field(prebindgen_registry::fun!(z_reply_sample))
.field(prebindgen_registry::fun!(z_reply_err)),
);
let dir = unique_test_dir("jnigen_double_opt");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let gen = jni.build_with(registry).expect("resolve");
let rust_path = gen.write_rust(dir.join("gen.rs")).expect("write_rust");
let rust = std::fs::read_to_string(&rust_path).unwrap();
let rc: String = rust.split_whitespace().collect();
assert!(rc.contains("matchmyflat::z_reply_sample("), "{rust}");
assert!(rc.contains("matchmyflat::z_sample_timestamp("), "{rust}");
assert!(rc.contains("myflat::z_ts_ntp64(__n1)"), "{rust}");
assert!(
!rc.contains("myflat::z_ts_ntp64(myflat::z_sample_timestamp("),
"{rust}"
);
assert!(
!rc.contains("myflat::z_sample_key_expr(myflat::z_reply_sample("),
"{rust}"
);
assert!(rc.contains("myflat::z_sample_key_expr(__n0)"), "{rust}");
assert!(rc.contains("jni::objects::JObject::null()"), "{rust}");
assert!(rc.contains("myflat::z_reply_zid(&__cb_arg0)"), "{rust}");
assert!(!rc.contains("matchmyflat::z_reply_zid("), "{rust}");
assert!(
rc.contains(
"\"(Lio/test/jni/query/ZId;ZLjava/lang/Long;Ljava/lang/String;Ljava/lang/Long;[B)V\""
),
"{rust}"
);
assert!(rc.contains("jni::sys::jvalue{z:"), "{rust}");
let kdir = dir.join("kotlin");
let paths = gen.write_kotlin(&kdir).expect("write_kotlin");
let iface_file = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.find(|v| v.contains("fun interface ZReplyCallback"))
.unwrap_or_default();
let iface = iface_file
.split("fun interface ZReplyCallback")
.nth(1)
.and_then(|s| s.split_once('}').map(|(b, _)| b.to_string()))
.unwrap_or_default();
let ic: String = iface.split_whitespace().collect();
assert!(ic.contains("isOk:Boolean"), "{iface}");
assert!(ic.contains("sample__keyExpr:ZKeyExpr?"), "{iface}");
assert!(ic.contains(":Long?"), "{iface}");
assert!(ic.contains(":ZId?"), "{iface}");
let pkg = paths
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.find(|v| v.contains("public fun zGet"))
.unwrap_or_default();
let pc: String = pkg.split_whitespace().collect();
assert!(pc.contains("cb:ZReplyCallback"), "{pkg}");
assert!(pc.contains("JNINative.zGet(cb.asRaw(),"), "{pkg}");
}
#[test]
fn iface_spec_memo_shares_one_derivation() {
use prebindgen::SourceLocation;
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_thing_name(this_: &ZThing) -> String {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_things_all() -> Vec<ZThing> {
unimplemented!()
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_thing_sub(cb: impl Fn(ZThing) + Send + Sync + 'static) {
unimplemented!()
}
)),
loc.clone(),
),
];
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!("thing")
.class(
crate::ptr_class!(ZThing)
.method(prebindgen_registry::fun!(z_thing_name).name("name")),
)
.fun(prebindgen_registry::fun!(z_things_all))
.fun(prebindgen_registry::fun!(z_thing_sub)),
)
.expand(
prebindgen_registry::expand_return!(ZThing)
.field_self()
.field(prebindgen_registry::fun!(z_thing_name)),
);
let gen = jni.build_with(registry).expect("resolve");
let (ext, registry) = (gen.declarations(), gen.registry());
let a = ext
.iface_spec(registry, &SpecKey::JniErrorHandler)
.expect("global handler spec");
let b = ext
.iface_spec(registry, &SpecKey::JniErrorHandler)
.expect("global handler spec");
assert!(Arc::ptr_eq(&a, &b), "one derivation per identity");
let plan = registry
.unfold_plans()
.get(&syn::parse_str::<syn::Ident>("z_things_all").unwrap())
.expect("fold plan");
let via_plan = folder_iface_for_plan(ext, registry, plan).expect("folder spec");
let decon = plan.decon.clone().expect("record-built fold");
let direct = ext
.iface_spec(registry, &SpecKey::Folder(decon))
.expect("folder spec");
assert!(Arc::ptr_eq(&via_plan, &direct), "folder identity shared");
let args = vec![registry
.reading_of(&syn::parse_quote!(ZThing))
.expect("ZThing is interned")];
let cb1 = ext
.iface_spec(registry, &SpecKey::callback(&args))
.expect("callback spec");
let cb2 = ext
.iface_spec(registry, &SpecKey::callback(&args))
.expect("callback spec");
assert!(Arc::ptr_eq(&cb1, &cb2), "callback identity shared");
assert_eq!(cb1.descr, "(JLjava/lang/String;)V");
}
#[test]
fn fn_plan_memo_shares_one_derivation() {
use prebindgen::SourceLocation;
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Fn(syn::parse_quote!(
pub fn z_do_thing(x: i64) -> i64 {
unimplemented!()
}
)),
loc.clone(),
)];
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(crate::package!("thing").fun(prebindgen_registry::fun!(z_do_thing)));
let gen = jni.build_with(registry).expect("resolve");
let (ext, registry) = (gen.declarations(), gen.registry());
let f = registry.flat().function("z_do_thing").expect("indexed");
let a = ext.fn_plan(registry, f).expect("plan");
let b = ext.fn_plan(registry, f).expect("plan");
assert!(std::rc::Rc::ptr_eq(&a, &b), "one plan per function ident");
assert_eq!(a.native_symbol, b.native_symbol);
let fresh = JniFunctionPlan::build(ext, registry, f).expect("fresh build");
assert_eq!(a.native_symbol, fresh.native_symbol);
assert_eq!(a.jni_method, fresh.jni_method);
}
#[test]
fn a_callback_identity_is_the_same_from_the_reading_or_the_syntax() {
use prebindgen::SourceLocation;
let loc = myflat_loc();
let items: Vec<(syn::Item, SourceLocation)> = vec![(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sub(cb: impl Fn(ZThing) + Send + Sync + 'static) {
unimplemented!()
}
)),
loc.clone(),
)];
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::ptr_class!(ZThing))
.fun(prebindgen_registry::fun!(z_sub)),
);
let gen = jni.build_with(registry).expect("resolve");
let registry = gen.registry();
let f = registry.flat().function("z_sub").expect("the declared fn");
let cb = f
.params
.iter()
.find_map(|p| match p.ty.kind() {
prebindgen_registry::flat::TypeKind::Callback { args } => Some((p, args)),
_ => None,
})
.expect("z_sub takes a callback");
let (param, arg_readings) = cb;
let from_reading = SpecKey::callback(arg_readings);
let from_syntax = SpecKey::Callback(
prebindgen_registry::flat::extract_fn_trait_args(
&prebindgen_registry::Emit::for_test().spell_ty(¶m.ty),
)
.expect("the param is an impl Fn")
.iter()
.map(prebindgen_registry::TypeKey::from_type)
.collect(),
);
assert_eq!(
from_reading, from_syntax,
"a callback keyed off its readings must be the SAME memo identity as one \
keyed off the signature's syntax — otherwise the memo silently emits two \
interfaces for one callback"
);
}
#[test]
fn a_wrapped_borrow_callback_arg_declines() {
use prebindgen::SourceLocation;
let loc = myflat_loc();
let build = |argty: syn::Type| -> Result<String, String> {
let items: Vec<(syn::Item, SourceLocation)> = vec![
(
syn::Item::Struct(syn::parse_quote!(
pub struct ZThing {
pub v: i64,
}
)),
loc.clone(),
),
(
syn::Item::Fn(syn::parse_quote!(
pub fn z_sub(cb: impl Fn(#argty) + Send + Sync + 'static) {
unimplemented!()
}
)),
loc.clone(),
),
];
let registry =
crate::test_util::reg_from_items(declare_referenced(items)).expect("index items");
let jni = JniGenBuilder::new()
.set_package_prefix("io.test.jni")
.package(
crate::package!()
.class(crate::ptr_class!(ZThing))
.fun(prebindgen_registry::fun!(z_sub)),
);
let dir = unique_test_dir("jnigen_wrapped_cb");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
match jni.build_with(registry) {
Ok(g) => Ok(std::fs::read_to_string(
g.write_rust(dir.join("g.rs")).expect("write_rust"),
)
.expect("read rust")),
Err(e) => Err(format!("{e}")),
}
};
let plain = build(syn::parse_quote!(&ZThing)).expect("a plain borrow resolves");
assert!(
plain.contains("__cb_arg0: &myflat::ZThing"),
"the trampoline takes the borrow as written:\n{plain}"
);
let err = build(syn::parse_quote!(Box<&ZThing>))
.expect_err("a wrapped borrow callback arg must not resolve");
assert!(
err.contains("could not be resolved"),
"the refusal names the type: {err}"
);
}