use crate::common::{eval_int, eval_string};
#[test]
fn builtins_values_are_category_strings() {
assert_eq!(eval_string(r#"$stryke::builtins{pmap}"#), "parallel");
assert_eq!(eval_string(r#"$stryke::builtins{map}"#), "array / list");
assert_eq!(eval_string(r#"$stryke::builtins{uc}"#), "string");
let empty = eval_int(
r#"
my $n = 0;
for my $k (keys %stryke::builtins) {
$n++ if $stryke::builtins{$k} eq "";
}
$n
"#,
);
assert_eq!(
empty, 0,
"every %builtins value should be a non-empty category"
);
}
#[test]
fn perl_compats_and_extensions_partition_builtins() {
let n_b = eval_int(r#"scalar keys %stryke::builtins"#);
let n_pc = eval_int(r#"scalar keys %stryke::perl_compats"#);
let n_e = eval_int(r#"scalar keys %stryke::extensions"#);
assert_eq!(
n_b,
n_pc + n_e,
"|%builtins|={n_b} but |%perl_compats|+|%extensions|={pc}+{e}={sum} — disjointness broken",
n_b = n_b,
pc = n_pc,
e = n_e,
sum = n_pc + n_e,
);
assert_eq!(
eval_int(r#"exists $stryke::perl_compats{keys} ? 1 : 0"#),
1,
"keys must be in %perl_compats",
);
assert_eq!(
eval_int(r#"exists $stryke::extensions{pmap} ? 1 : 0"#),
1,
"pmap must be in %extensions",
);
assert_eq!(
eval_int(r#"exists $stryke::extensions{keys} ? 1 : 0"#),
0,
"keys is core, must not be in %extensions",
);
}
#[test]
fn subset_values_match_builtins() {
assert_eq!(
eval_string(r#"$stryke::perl_compats{map}"#),
eval_string(r#"$stryke::builtins{map}"#),
);
assert_eq!(
eval_string(r#"$stryke::extensions{pmap}"#),
eval_string(r#"$stryke::builtins{pmap}"#),
);
}
#[test]
fn aliases_resolve_short_form_to_primary() {
assert_eq!(eval_string(r#"$stryke::aliases{tj}"#), "to_json");
assert_eq!(eval_int(r#"exists $stryke::aliases{to_json} ? 1 : 0"#), 0);
let dangling = eval_int(
r#"
my $n = 0;
for my $alias (keys %stryke::aliases) {
my $primary = $stryke::aliases{$alias};
$n++ unless exists $stryke::builtins{$primary};
}
$n
"#,
);
assert_eq!(dangling, 0);
}
#[test]
fn descriptions_cover_documented_names() {
let d = eval_string(r#"$stryke::descriptions{pmap}"#);
assert!(
d.len() > 10,
"%d{{pmap}} should be real sentence, got {:?}",
d
);
assert_eq!(
eval_int(r#"exists $stryke::descriptions{definitely_not_a_builtin_xyz} ? 1 : 0"#),
0,
);
let n_desc = eval_int(r#"scalar keys %stryke::descriptions"#);
let n_all = eval_int(r#"scalar keys %stryke::all"#);
assert!(
n_desc > 0 && n_desc <= n_all,
"%descriptions ({n_desc}) should be between 1 and |%all| ({n_all}) — \
it includes both primaries and aliases when the LSP arm is shared",
);
}
#[test]
fn categories_inverted_index_returns_name_arrayrefs() {
let n_parallel = eval_int(r#"scalar @{ $stryke::categories{parallel} }"#);
assert!(
n_parallel >= 20,
"expected ≥20 parallel ops, got {n_parallel}",
);
let mismatch = eval_int(
r#"
my %from_c = map { $_ => 1 } @{ $stryke::categories{"string"} };
my @from_b = grep { $stryke::builtins{$_} eq "string" } keys %stryke::builtins;
my $n = 0;
for my $k (@from_b) { $n++ unless $from_c{$k}; }
$n += scalar(keys %from_c) - scalar(@from_b);
$n
"#,
);
assert_eq!(
mismatch, 0,
"%categories[string] should match grep {{ $b{{_}} eq 'string' }} keys %b",
);
}
#[test]
fn primaries_inverted_index_returns_alias_arrayrefs() {
let tj_in = eval_int(
r#"
my $aliases = $stryke::primaries{to_json}
my $found = 0
for my $a (@$aliases) { $found = 1 if $a eq "tj"; }
$found
"#,
);
assert_eq!(tj_in, 1, "to_json's aliases should include 'tj'");
let bn_in = eval_int(
r#"
my $aliases = $stryke::primaries{basename}
my $found = 0
for my $a (@$aliases) { $found = 1 if $a eq "bn"; }
$found
"#,
);
assert_eq!(bn_in, 1);
let dangling = eval_int(
r#"
my $n = 0
for my $primary (keys %stryke::primaries) {
$n++ unless exists $stryke::builtins{$primary};
}
$n
"#,
);
assert_eq!(
dangling, 0,
"every %primaries key should be a known builtin"
);
}
#[test]
fn short_aliases_mirror_long_names() {
assert_eq!(eval_string(r#"$b{pmap}"#), "parallel");
assert_eq!(eval_string(r#"$pc{map}"#), "array / list");
assert_eq!(eval_string(r#"$e{pmap}"#), "parallel");
assert_eq!(eval_string(r#"$a{tj}"#), "to_json");
assert!(eval_int(r#"length($d{pmap}) > 0 ? 1 : 0"#) == 1);
assert!(eval_int(r#"scalar @{ $c{parallel} } > 0 ? 1 : 0"#) == 1);
assert!(eval_int(r#"scalar @{ $p{to_json} } > 0 ? 1 : 0"#) == 1);
}
#[test]
fn every_dispatch_primary_is_categorized() {
let out = eval_string(
r#"
my @bad
for my $name (sort keys %stryke::builtins) {
push @bad, $name if $stryke::builtins{$name} eq "uncategorized"
}
join ",", @bad
"#,
);
assert!(
out.is_empty(),
"uncategorized dispatch primaries — add each to a `// ── category ──`\n\
section in parser.rs (is_perl5_core or stryke_extension_name):\n {out}",
);
}
#[test]
fn reflection_hashes_have_reasonable_sizes() {
assert!(eval_int(r#"scalar keys %stryke::builtins"#) >= 200);
assert!(eval_int(r#"scalar keys %stryke::perl_compats"#) >= 80);
assert!(eval_int(r#"scalar keys %stryke::extensions"#) >= 100);
assert!(eval_int(r#"scalar keys %stryke::aliases"#) >= 100);
assert!(eval_int(r#"scalar keys %stryke::descriptions"#) >= 10);
assert!(eval_int(r#"scalar keys %stryke::categories"#) >= 10);
assert!(eval_int(r#"scalar keys %stryke::primaries"#) >= 100);
}