prebindgen-jni 0.5.0

JNI / Kotlin binding generator for prebindgen
Documentation
//! Shared helpers for this crate's unit tests. Compiled only under
//! `cfg(test)`; not part of any public API. Copied (and re-pathed to
//! `prebindgen::`) from `prebindgen`'s own `api/test_util.rs` — only the
//! functions the moved jnigen tests actually call.

use std::{
    path::PathBuf,
    sync::atomic::{AtomicUsize, Ordering},
};

use prebindgen_registry::{Registry, RegistryBuilder};

/// Build a `Registry` from an item stream, the way `Registry::from_items` used
/// to before reading captured output became `FlatBuilder`'s job alone.
///
/// Test-only sugar: the two steps are one line each in a build script, but they
/// appear in dozens of fixtures here.
pub(crate) fn reg_from_items<M, I>(
    items: I,
) -> Result<RegistryBuilder<M>, prebindgen_registry::ScanError>
where
    I: IntoIterator<Item = (syn::Item, prebindgen::SourceLocation)>,
{
    let flat = prebindgen_registry::Flat::builder().items(items).build()?;
    Registry::builder(flat)
}

/// Append a marked type alias for every nominal type the stream names but never
/// declares, so a fixture satisfies the flat API's self-sufficiency rule.
///
/// A fixture that is *about* a handle's treatment already declares it; this covers
/// the ones where the handle is incidental — `reg_with(&["fn get(s: &Storage) -> Payload"])`
/// is testing an unfold plan, not what `Storage` is. Declaring them as
/// [`Extern`](prebindgen_registry::flat::Extern)s is exactly what a real source crate does
/// for a foreign handle, and it is inert for the registry either way: a type alias
/// lands in no registry map.
///
/// Runs to a fixed point, since a declaration can only ever resolve more references.
/// It cannot help a **path-qualified** name (`std::time::Duration`), which no
/// declaration can name — such a fixture has to spell the type bare and declare it.
pub(crate) fn declare_referenced<I>(items: I) -> Vec<(syn::Item, prebindgen::SourceLocation)>
where
    I: IntoIterator<Item = (syn::Item, prebindgen::SourceLocation)>,
{
    use prebindgen_registry::flat::{Flat, ItemError};

    let mut items: Vec<(syn::Item, prebindgen::SourceLocation)> = items.into_iter().collect();

    loop {
        let flat = Flat::builder()
            .items(items.iter().cloned())
            .build()
            .expect("fixture parses");
        // A set: the same name is reported once per referencing item.
        let missing: std::collections::BTreeSet<String> = flat
            .unsupported()
            .filter_map(|u| match &*u.error {
                // Skip a name the stream already holds. Refusal is transitive, so a
                // declared struct whose own field is undeclared reports as
                // unresolved too — declaring an alias for it would collide. Adding
                // the root name resolves it on the next round.
                ItemError::UnresolvedType { name }
                    if !name.contains("::") && flat.element(name).is_none() =>
                {
                    Some(name.clone())
                }
                _ => None,
            })
            .collect();
        if missing.is_empty() {
            return items;
        }
        for name in missing {
            let ident = quote::format_ident!("{name}");
            let alias: syn::Item = syn::parse_quote!(
                pub type #ident = __fixture::#ident;
            );
            items.push((alias, prebindgen::SourceLocation::default()));
        }
    }
}

/// One type as the **model** reads it, for a test that needs a `TypeRef` and has
/// only a spelling.
///
/// Minting is sealed to `core` (#280), and rightly — a hand-assembled
/// reading could pair a `kind` with a disagreeing `syntax`, which is the one
/// thing holding a `TypeRef` is supposed to rule out. So this does not reach
/// around the seal: it puts the spelling in a parameter position, runs the real
/// parse, and hands back the reading the model produced. A spelling the grammar
/// refuses panics here rather than yielding something weaker.
pub(crate) fn reading(ty: syn::Type) -> prebindgen_registry::flat::TypeRef {
    let item: syn::Item = syn::parse_quote!(
        pub fn __probe(v: #ty) {
            unimplemented!()
        }
    );
    let flat = prebindgen_registry::Flat::builder()
        .items(declare_referenced(vec![(
            item,
            prebindgen::SourceLocation::default(),
        )]))
        .build()
        .expect("the probe parses");
    flat.function("__probe")
        .expect("the probe is indexed")
        .params[0]
        .ty
        .clone()
}

/// A process-unique temp directory for a test that writes files. Keyed by
/// pid + a monotonic counter so tests that share a helper and run on
/// separate threads never clobber each other's output dir.
pub(crate) fn unique_test_dir(prefix: &str) -> PathBuf {
    static SEQ: AtomicUsize = AtomicUsize::new(0);
    let seq = SEQ.fetch_add(1, Ordering::Relaxed);
    std::env::temp_dir().join(format!("{prefix}_{}_{}", std::process::id(), seq))
}