#[cfg(test)]
mod target_gate_tests {
use super::*;
fn config_with_core_aggregate(directory: &std::path::Path, alef_toml: &str) -> ResolvedCrateConfig {
let core_dir = directory.join("crates").join("demo");
std::fs::create_dir_all(&core_dir).expect("create core crate dir");
std::fs::write(
core_dir.join("Cargo.toml"),
"[package]\nname = \"demo\"\n\n[features]\ndefault = []\nmobile-target = [\"decoder\"]\ndecoder = []\n",
)
.expect("write core Cargo.toml");
let raw: crate::core::config::NewAlefConfig = toml::from_str(alef_toml).expect("fixture config parses");
let mut config = raw.resolve().expect("fixture config resolves").remove(0);
config.workspace_root = Some(directory.to_path_buf());
config
}
fn config_with_core_defaults(directory: &std::path::Path, defaults: &str, alef_toml: &str) -> ResolvedCrateConfig {
let core_dir = directory.join("crates").join("demo");
std::fs::create_dir_all(&core_dir).expect("create core crate dir");
std::fs::write(
core_dir.join("Cargo.toml"),
format!("[package]\nname = \"demo\"\n\n[features]\ndefault = [{defaults}]\ndecoder = []\n"),
)
.expect("write core Cargo.toml");
let raw: crate::core::config::NewAlefConfig = toml::from_str(alef_toml).expect("fixture config parses");
let mut config = raw.resolve().expect("fixture config resolves").remove(0);
config.workspace_root = Some(directory.to_path_buf());
config
}
fn gated_function() -> crate::core::ir::FunctionDef {
crate::core::ir::FunctionDef {
name: "decoder_details".into(),
rust_path: "demo::decoder::decoder_details".into(),
return_type: TypeRef::String,
cfg: Some("feature = \"decoder\"".into()),
..Default::default()
}
}
fn api_with(functions: Vec<crate::core::ir::FunctionDef>) -> ApiSurface {
ApiSurface {
crate_name: "demo".into(),
version: "0.1.0".into(),
functions,
..Default::default()
}
}
#[test]
fn target_override_naming_a_core_aggregate_keeps_its_member_gated_shims() {
let directory = tempfile::tempdir().expect("tempdir");
let config = config_with_core_aggregate(
directory.path(),
r#"
[workspace]
languages = ["kotlin_android", "jni"]
[[crates]]
name = "demo"
sources = ["crates/demo/src/lib.rs"]
[crates.kotlin_android]
package = "dev.sample_crate"
namespace = "dev.sample_crate"
features = ["full"]
[[crates.jni.target_dep_overrides]]
cfg = 'target_os = "android"'
features = ["mobile-target"]
"#,
);
let content = emit_lib_rs(&api_with(vec![gated_function()]), &config);
assert!(
content.contains("core_crate::decoder::decoder_details()"),
"the aggregate enables `decoder`, so its shim must be emitted at all: {content}"
);
assert!(
!content.contains("#[cfg(not(any(target_os = \"android\")))]"),
"the aggregate enables `decoder` on the override target too, so the shim must NOT be \
excluded from it: {content}"
);
}
#[test]
fn target_override_still_excludes_a_leaf_its_aggregate_does_not_enable() {
let directory = tempfile::tempdir().expect("tempdir");
let config = config_with_core_aggregate(
directory.path(),
r#"
[workspace]
languages = ["kotlin_android", "jni"]
[[crates]]
name = "demo"
sources = ["crates/demo/src/lib.rs"]
[crates.kotlin_android]
package = "dev.sample_crate"
namespace = "dev.sample_crate"
features = ["full"]
[[crates.jni.target_dep_overrides]]
cfg = 'target_os = "android"'
features = ["mobile-target"]
"#,
);
let desktop_only = crate::core::ir::FunctionDef {
name: "render_preview".into(),
rust_path: "demo::preview::render_preview".into(),
return_type: TypeRef::String,
cfg: Some("feature = \"preview\"".into()),
..Default::default()
};
let content = emit_lib_rs(&api_with(vec![desktop_only]), &config);
assert!(
content.contains("#[cfg(not(any(target_os = \"android\")))]"),
"`preview` is not a member of `mobile-target`, so its shim must stay gated off the \
override target: {content}"
);
}
#[test]
fn the_default_branch_counts_the_core_crates_own_default_features() {
let directory = tempfile::tempdir().expect("tempdir");
let config = config_with_core_defaults(
directory.path(),
"\"decoder\"",
r#"
[workspace]
languages = ["kotlin_android", "jni"]
[[crates]]
name = "demo"
sources = ["crates/demo/src/lib.rs"]
[crates.kotlin_android]
package = "dev.sample_crate"
namespace = "dev.sample_crate"
features = []
"#,
);
let content = emit_lib_rs(&api_with(vec![gated_function()]), &config);
assert!(
content.contains("core_crate::decoder::decoder_details()"),
"the core crate enables `decoder` by default and this branch never passes \
`default-features = false`, so the shim must be emitted: {content}"
);
}
#[test]
fn an_override_that_keeps_default_features_counts_the_core_crates_defaults() {
let directory = tempfile::tempdir().expect("tempdir");
let config = config_with_core_defaults(
directory.path(),
"\"decoder\"",
r#"
[workspace]
languages = ["kotlin_android", "jni"]
[[crates]]
name = "demo"
sources = ["crates/demo/src/lib.rs"]
[crates.kotlin_android]
package = "dev.sample_crate"
namespace = "dev.sample_crate"
features = []
[[crates.jni.target_dep_overrides]]
cfg = 'target_os = "android"'
features = []
default_features = true
"#,
);
let content = emit_lib_rs(&api_with(vec![gated_function()]), &config);
assert!(
content.contains("core_crate::decoder::decoder_details()"),
"the shim must be emitted at all: {content}"
);
assert!(
!content.contains("#[cfg(not(any(target_os = \"android\")))]"),
"the override keeps the core crate's defaults, which include `decoder`, so the shim \
must NOT be excluded from the override target: {content}"
);
}
#[test]
fn an_override_without_default_features_does_not_get_the_core_crates_defaults() {
let directory = tempfile::tempdir().expect("tempdir");
let config = config_with_core_defaults(
directory.path(),
"\"decoder\"",
r#"
[workspace]
languages = ["kotlin_android", "jni"]
[[crates]]
name = "demo"
sources = ["crates/demo/src/lib.rs"]
[crates.kotlin_android]
package = "dev.sample_crate"
namespace = "dev.sample_crate"
features = []
[[crates.jni.target_dep_overrides]]
cfg = 'target_os = "android"'
features = []
default_features = false
"#,
);
let content = emit_lib_rs(&api_with(vec![gated_function()]), &config);
assert!(
content.contains("#[cfg(not(any(target_os = \"android\")))]"),
"this override passes `default-features = false` and names no features, so nothing \
satisfies `decoder` on that target and the shim must stay gated off it: {content}"
);
}
}