tests.rs

24.3 kB · rust · 642 lines

1use crate::model::*;2use crate::parse::{self, Files};3use crate::report;4use crate::resolve::{self, Built};5use crate::{cli, js, py};6use std::path::{Path, PathBuf};78fn build(lib: &str) -> Built {9    let files: Files = [("lib.rs".to_string(), lib.to_string())]10        .into_iter()11        .collect();12    resolve::build(&files, "0.0.0").expect("the source builds")13}1415fn manifest(lib: &str) -> Manifest {16    build(lib).manifest17}1819fn function<'a>(m: &'a Manifest, path: &str) -> &'a Function {20    let paths: Vec<&str> = m.functions.iter().map(|f| f.path.as_str()).collect();21    m.functions22        .iter()23        .find(|f| f.path == path)24        .unwrap_or_else(|| panic!("no {path} in {paths:?}"))25}2627fn reason(m: &Manifest, path: &str) -> String {28    match &function(m, path).cross {29        Cross::Skip { reason } => reason.clone(),30        other => panic!("{path} is {other:?}"),31    }32}3334fn kind<'a>(m: &'a Manifest, path: &str) -> &'a TypeCross {35    let paths: Vec<&str> = m.types.iter().map(|t| t.path.as_str()).collect();36    &m.types37        .iter()38        .find(|t| t.path == path)39        .unwrap_or_else(|| panic!("no {path} in {paths:?}"))40        .cross41}4243fn scratch(name: &str, lib: &str, units: &str) -> PathBuf {44    let root = std::env::temp_dir().join(format!("bridge-{name}-{}", std::process::id()));45    let _ = std::fs::remove_dir_all(&root);46    std::fs::create_dir_all(root.join("bridge")).expect("a scratch root");47    std::fs::write(root.join("bridge/units.txt"), units).expect("units.txt");48    let m = manifest(lib);49    cli::write(&m, &root).expect("the cli writes");50    py::write(&m, &root).expect("the python bridge writes");51    js::write(&m, &root).expect("the js bridge writes");52    root53}5455fn read(root: &Path, file: &str) -> String {56    std::fs::read_to_string(root.join(file)).unwrap_or_else(|_| panic!("no {file}"))57}5859const ERROR: &str = "pub mod core { pub mod error { pub enum Error { Value(String) } pub type Result<T> = std::result::Result<T, Error>; } pub use error::{Error, Result}; }";6061#[test]62fn scalars_and_strings_cross() {63    let m = manifest(64        "pub fn f(a: bool, b: u8, c: f64, d: String, e: &str, g: char, h: f32, i: i8) -> u64 { 0 }",65    );66    let f = function(&m, "f");67    assert_eq!(f.cross, Cross::Ok);68    assert_eq!(f.params[3].ty, Ty::String);69    assert_eq!(f.params[4].ty, Ty::Str);70    assert_eq!(71        f.params[5].ty,72        Ty::Scalar {73            name: "char".into()74        }75    );76    assert_eq!(f.ret, Ty::Scalar { name: "u64".into() });77}7879#[test]80fn wide_integers_and_code_cross() {81    let m = manifest(82        "pub struct Code(pub(crate) u128); pub fn f(a: u128, b: i128, c: Code) -> Code { c }",83    );84    let f = function(&m, "f");85    assert_eq!(f.cross, Cross::Ok);86    assert_eq!(f.params[0].ty, Ty::U128);87    assert_eq!(f.params[1].ty, Ty::I128);88    assert_eq!(f.ret, Ty::Code);89}9091#[test]92fn containers_and_results_cross() {93    let src = format!("{ERROR} use core::Result; pub fn f(a: Vec<u8>, b: &[u64], c: Option<usize>, d: (u8, u8), e: [u8; 4]) -> Result<Vec<u8>> {{ Ok(a) }}");94    let m = manifest(&src);95    let f = function(&m, "f");96    assert_eq!(f.cross, Cross::Ok);97    let u8 = || Box::new(Ty::Scalar { name: "u8".into() });98    assert_eq!(f.params[0].ty, Ty::Vec { item: u8() });99    assert_eq!(100        f.params[1].ty,101        Ty::Slice {102            mutable: false,103            item: Box::new(Ty::Scalar { name: "u64".into() })104        }105    );106    assert_eq!(107        f.params[2].ty,108        Ty::Option {109            item: Box::new(Ty::Scalar {110                name: "usize".into()111            })112        }113    );114    assert_eq!(115        f.params[3].ty,116        Ty::Tuple {117            items: vec![*u8(), *u8()]118        }119    );120    assert_eq!(f.params[4].ty, Ty::Array { item: u8(), len: 4 });121    assert_eq!(122        f.ret,123        Ty::Result {124            item: Box::new(Ty::Vec { item: u8() })125        }126    );127}128129#[test]130fn json_maps_and_sets_cross() {131    let m = manifest("pub use serde_json::Value as Json; pub fn f(v: &Json, m: std::collections::HashMap<u8, Vec<u8>>) -> std::collections::BTreeSet<u8> { todo!() }");132    let f = function(&m, "f");133    assert_eq!(f.cross, Cross::Ok);134    assert_eq!(135        f.params[0].ty,136        Ty::Ref {137            mutable: false,138            lifetime: None,139            item: Box::new(Ty::Json)140        }141    );142    assert!(matches!(f.params[1].ty, Ty::Map { .. }));143    assert!(matches!(f.ret, Ty::Set { .. }));144}145146#[test]147fn hand_methods_export_as_free_functions_self_first() {148    let m = manifest("pub mod core { pub mod tensor { pub struct Tensor { pub shape: Vec<usize> } impl Tensor { pub fn new(shape: Vec<usize>) -> Tensor { Tensor { shape } } pub fn size(&self) -> usize { 0 } } } pub use tensor::Tensor; }");149    assert_eq!(150        kind(&m, "core::Tensor"),151        &TypeCross::Hand {152            name: "Tensor".into()153        }154    );155    let new = function(&m, "core::tensor::new");156    assert_eq!(157        (new.owner.as_deref(), new.self_kind, new.module.as_str()),158        (Some("core::Tensor"), None, "core::tensor")159    );160    let size = function(&m, "core::tensor::size");161    assert_eq!(162        (size.self_kind, &size.cross),163        (Some(SelfKind::Ref), &Cross::Ok)164    );165}166167#[test]168fn a_hand_type_in_a_private_module_exports_at_the_nearest_public_module() {169    let m = manifest("pub mod six { mod models { pub struct Cell6d { pub start: u8 } impl Cell6d { pub fn width(&self) -> usize { 0 } } } pub use models::Cell6d; }");170    let f = function(&m, "six::width");171    assert_eq!((f.module.as_str(), &f.cross), ("six", &Cross::Ok));172}173174#[test]175fn rng_crosses_by_hand_and_is_passed_mutably() {176    let m = manifest("pub struct Rng { s: u64 } pub fn draw(rng: &mut Rng) -> u64 { 0 }");177    assert_eq!(kind(&m, "Rng"), &TypeCross::Hand { name: "Rng".into() });178    let f = function(&m, "draw");179    assert_eq!(f.cross, Cross::Ok);180    assert_eq!(181        f.params[0].ty,182        Ty::Ref {183            mutable: true,184            lifetime: None,185            item: Box::new(Ty::Hand {186                name: "Rng".into(),187                dim: None188            })189        }190    );191}192193#[test]194fn a_serde_struct_without_self_methods_is_plain_and_keeps_its_constructors() {195    let m = manifest("pub mod gen { #[derive(Serialize, Deserialize)] pub struct Tile { pub side: usize } impl Tile { pub fn new(side: usize) -> Tile { Tile { side } } } }");196    assert_eq!(kind(&m, "gen::Tile"), &TypeCross::Plain);197    let f = function(&m, "gen::Tile::new");198    assert_eq!(199        (f.owner.as_deref(), f.module.as_str(), &f.cross),200        (Some("gen::Tile"), "gen", &Cross::Ok)201    );202}203204#[test]205fn a_serde_skipped_field_makes_a_class() {206    let m = manifest("pub mod gen { #[derive(Serialize, Deserialize)] pub struct File { pub width: usize, #[serde(default, skip_serializing_if = \"Vec::is_empty\")] pub tags: Vec<u8>, #[serde(skip)] pub png: Vec<u8> } #[derive(Serialize, Deserialize)] pub struct Tile { #[serde(default, skip_serializing_if = \"Option::is_none\")] pub side: Option<usize> } }");207    assert_eq!(kind(&m, "gen::File"), &TypeCross::Class);208    assert_eq!(kind(&m, "gen::Tile"), &TypeCross::Plain);209    let file = m.types.iter().find(|t| t.path == "gen::File").expect("File is listed");210    let skips: Vec<bool> = file.fields.iter().map(|f| f.serde_skip).collect();211    assert_eq!(skips, [false, false, true]);212}213214#[test]215fn a_struct_with_a_self_method_is_a_class() {216    let m = manifest("pub mod life { #[derive(Serialize, Deserialize)] pub struct Life { pub time: usize } impl Life { pub fn step(&mut self) {} } }");217    assert_eq!(kind(&m, "life::Life"), &TypeCross::Class);218    let f = function(&m, "life::Life::step");219    assert_eq!((f.self_kind, &f.cross), (Some(SelfKind::Mut), &Cross::Ok));220}221222#[test]223fn a_fieldless_serde_enum_is_a_string_even_with_methods() {224    let m = manifest("#[derive(Serialize, Deserialize)] pub enum Dtype { U8, U16 } impl Dtype { pub fn max(self) -> i64 { 0 } }");225    assert_eq!(226        kind(&m, "Dtype"),227        &TypeCross::Enum {228            named: false,229            words: vec![]230        }231    );232    let f = function(&m, "Dtype::max");233    assert_eq!((f.self_kind, &f.cross), (Some(SelfKind::Value), &Cross::Ok));234}235236#[test]237fn a_named_enum_records_its_words_all_and_name() {238    let m = manifest("pub mod life { named_enum! { #[derive(Serialize, Deserialize)] pub enum Fate { Dead => \"dead\", Alive => \"alive\", } } }");239    assert_eq!(240        kind(&m, "life::Fate"),241        &TypeCross::Enum {242            named: true,243            words: vec!["dead".into(), "alive".into()]244        }245    );246    let all = function(&m, "life::Fate::all");247    assert_eq!(248        (all.source, all.constant, &all.cross),249        (Source::NamedEnum, true, &Cross::Ok)250    );251    assert_eq!(252        all.ret,253        Ty::Array {254            item: Box::new(Ty::Enum {255                path: "life::Fate".into()256            }),257            len: 2258        }259    );260    let name = function(&m, "life::Fate::name");261    assert_eq!(262        (name.source, &name.cross),263        (264            Source::NamedEnum,265            &Cross::Skip {266                reason: "&'static return".into()267            }268        )269    );270}271272#[test]273fn const_generic_functions_dispatch_on_two_and_three() {274    let m = manifest("pub mod cell { pub struct CellNd<const N: usize> { pub n: u8 } pub type Cell2d = CellNd<2>; pub fn fills<const N: usize>(cell: &CellNd<N>) -> usize { 0 } impl CellNd<2> { pub fn rotate(self) -> Cell2d { self } } }");275    let fills = function(&m, "cell::fills");276    assert_eq!(277        (fills.dims.as_slice(), &fills.cross),278        ([2, 3].as_slice(), &Cross::Ok)279    );280    let rotate = function(&m, "cell::rotate");281    assert_eq!(rotate.dims, vec![2]);282    assert_eq!(283        rotate.ret,284        Ty::Hand {285            name: "CellNd".into(),286            dim: Some(2)287        }288    );289    let alias = m290        .types291        .iter()292        .find(|t| t.path == "cell::Cell2d")293        .expect("the alias");294    assert_eq!(295        (alias.kind, &alias.alias),296        (297            TypeKind::Alias,298            &Some(Ty::Hand {299                name: "CellNd".into(),300                dim: Some(2)301            })302        )303    );304}305306#[test]307fn a_trait_generic_is_skipped() {308    let m = manifest("pub fn uniform<T: PartialEq>(items: &[T]) -> bool { true }");309    assert_eq!(reason(&m, "uniform"), "generic over T");310}311312#[test]313fn a_closure_parameter_is_skipped() {314    let m = manifest("pub fn render<F>(rule: F) -> u8 where F: Fn(&[u8]) -> bool { 0 }");315    assert_eq!(reason(&m, "render"), "closure parameter F");316}317318#[test]319fn a_static_return_is_skipped() {320    let m = manifest("pub fn genus() -> &'static str { \"iso\" }");321    assert_eq!(reason(&m, "genus"), "&'static return");322}323324#[test]325fn a_lifetime_bearing_return_is_skipped() {326    let m = manifest("pub fn first<'a>(items: &'a [u8]) -> &'a u8 { &items[0] }");327    assert_eq!(reason(&m, "first"), "lifetime-bearing return");328}329330#[test]331fn an_elided_borrow_return_is_copied_out() {332    let m = manifest("pub struct Cell { pub types: Vec<u8> } impl Cell { pub fn shape(&self) -> &[u8] { &self.types } }");333    assert_eq!(function(&m, "shape").cross, Cross::Ok);334}335336#[test]337fn a_mutable_borrow_return_is_skipped() {338    let m = manifest("pub struct Tensor { pub data: Vec<u8> } impl Tensor { pub fn bytes_mut(&mut self) -> &mut [u8] { &mut self.data } }");339    assert_eq!(reason(&m, "bytes_mut"), "mutable borrow return");340}341342#[test]343fn a_serde_helper_in_a_private_module_is_private() {344    let m = manifest("mod counts { pub fn serialize<S: serde::Serializer>(counts: &u8, serializer: S) -> Result<S::Ok, S::Error> { todo!() } }");345    assert_eq!(function(&m, "counts::serialize").cross, Cross::Private);346}347348#[test]349fn an_error_constructor_is_skipped() {350    let src = format!("{ERROR} pub mod other {{}} impl core::error::Error {{}} pub mod core2 {{}}");351    let m = manifest(&format!("pub mod core {{ pub mod error {{ pub enum Error {{ Value(String) }} pub type Result<T> = std::result::Result<T, Error>; pub fn value_error<T>(message: impl Into<String>) -> Result<T> {{ todo!() }} }} }} {}", &src[src.len()..]));352    assert_eq!(reason(&m, "core::error::value_error"), "error constructor");353}354355#[test]356fn an_impl_trait_argument_is_skipped() {357    let m = manifest("pub fn new(birth: impl Into<u8>) -> u8 { 0 }");358    assert!(reason(&m, "new").starts_with("birth: impl Trait argument"));359}360361#[test]362fn an_iterator_return_is_skipped() {363    let m = manifest("pub fn each() -> impl Iterator<Item = u8> { std::iter::empty() }");364    assert_eq!(reason(&m, "each"), "returns iterator return");365}366367#[test]368fn a_private_type_in_a_signature_is_skipped() {369    let m = manifest("type Rotation = fn() -> u8; struct Point; pub fn create(rotation: Rotation) {} pub fn corners() -> Point { Point }");370    assert_eq!(reason(&m, "create"), "rotation: unknown type Rotation");371    assert_eq!(reason(&m, "corners"), "returns unknown type Point");372}373374#[test]375fn a_type_without_serde_or_methods_is_uncrossable() {376    let m = manifest("pub mod two { pub enum Shape { Square } pub fn png(shape: Shape) {} }");377    assert!(matches!(378        kind(&m, "two::Shape"),379        TypeCross::Uncrossable { .. }380    ));381    assert_eq!(reason(&m, "two::png"), "shape: uncrossable type two::Shape");382}383384#[test]385fn a_class_from_another_unit_is_skipped() {386    let m = manifest("pub mod core { pub struct Colorizer { pub n: u8 } impl Colorizer { pub fn color(&self) -> u8 { 0 } } } pub mod math { pub fn render(c: &crate::core::Colorizer) -> u8 { 0 } }");387    assert_eq!(388        reason(&m, "math::render"),389        "class core::Colorizer lives in unit core"390    );391}392393#[test]394fn mutating_plain_data_in_place_is_skipped() {395    let m = manifest("pub fn push(row: &mut String) {} pub fn fft(re: &mut [f64]) {}");396    assert_eq!(reason(&m, "push"), "row: mutates plain data in place");397    assert_eq!(reason(&m, "fft"), "re: mutable slice argument");398}399400#[test]401fn the_shortest_public_path_wins_and_ties_go_to_the_definition() {402    let m = manifest("pub mod a { pub mod b { pub fn f() {} } pub use b::f; } pub mod cell { pub fn edges() {} } pub mod two { pub use crate::cell::edges; }");403    assert_eq!(function(&m, "a::f").module, "a");404    assert_eq!(function(&m, "cell::edges").cross, Cross::Ok);405    assert!(m.functions.iter().all(|f| f.path != "two::edges"));406}407408#[test]409fn re_exports_resolve_groups_renames_and_globs() {410    let m = manifest("mod one { pub fn x() {} } mod two { pub fn y() {} } mod three { pub fn z() {} } pub mod g { pub use super::one::*; } pub mod h { pub use crate::two::y as ey; pub use crate::three::{z}; }");411    for path in ["g::x", "h::ey", "h::z"] {412        assert_eq!(function(&m, path).cross, Cross::Ok, "{path}");413    }414}415416#[test]417fn an_item_reachable_only_through_a_private_module_is_private() {418    let m = manifest(419        "mod hidden { pub fn f() {} pub struct S; } pub mod shown { mod deep { pub fn g() {} } }",420    );421    assert_eq!(function(&m, "hidden::f").cross, Cross::Private);422    assert_eq!(function(&m, "shown::deep::g").cross, Cross::Private);423    assert!(m.types.is_empty());424}425426#[test]427fn a_name_collision_fails_the_run() {428    let built = build("pub mod core { pub struct Tensor { pub n: u8 } impl Tensor { pub fn new() -> Tensor { todo!() } } pub struct Cell { pub n: u8 } impl Cell { pub fn new() -> Cell { todo!() } } }");429    assert_eq!(430        built.collisions,431        vec!["collision: core::new at lib.rs:1 and lib.rs:1"]432    );433    let built =434        build("pub mod census { pub fn census() {} pub fn extra() {} } pub use census::census;");435    assert_eq!(436        built.collisions,437        vec!["collision: module census and fn census at lib.rs:1"]438    );439}440441#[test]442fn skip_txt_must_name_every_skipped_function_and_nothing_else() {443    let m = manifest("pub fn genus() -> &'static str { \"iso\" } pub fn fine() {}");444    assert_eq!(report::skip_lines(&m), vec!["genus &'static return"]);445    assert_eq!(446        report::check_skip(&m, ""),447        vec!["skip.txt is missing genus"]448    );449    assert_eq!(450        report::check_skip(&m, "fine a stale line\n"),451        vec!["skip.txt is missing genus", "skip.txt names nothing: fine"]452    );453    assert!(report::check_skip(&m, "genus a decision\n").is_empty());454}455456#[test]457fn consts_are_listed_with_their_type() {458    let m = manifest("pub mod font { pub const FPS: usize = 25; pub type Pen = (char, &'static [&'static str]); pub const UPPERS: &[Pen] = &[]; }");459    let fps = m460        .consts461        .iter()462        .find(|c| c.path == "font::FPS")463        .expect("fps");464    assert_eq!(465        (&fps.ty, &fps.cross),466        (467            &Ty::Scalar {468                name: "usize".into()469            },470            &Cross::Ok471        )472    );473    let uppers = m474        .consts475        .iter()476        .find(|c| c.path == "font::UPPERS")477        .expect("uppers");478    assert_eq!(479        uppers.cross,480        Cross::Skip {481            reason: "&'static const".into()482        }483    );484}485486#[test]487fn a_public_trait_adds_its_methods_to_every_implementing_type() {488    let m = manifest("pub mod name { pub trait Named: Sized { const KIND: &'static str; #[doc = \" Folds.\"] fn checked(self) -> Result<Self, u8>; fn to_json(&self) -> String { String::new() } fn from_json(text: &str) -> Result<Self, u8> { todo!() } } pub mod word { #[derive(Serialize, Deserialize)] pub struct Word { pub n: u8 } impl super::Named for Word { const KIND: &'static str = \"word\"; fn checked(self) -> Result<Self, u8> { Ok(self) } } } }");489    assert_eq!(kind(&m, "name::word::Word"), &TypeCross::Class);490    let checked = function(&m, "name::word::Word::checked");491    assert_eq!(492        (checked.self_kind, checked.source, checked.via.as_deref()),493        (Some(SelfKind::Value), Source::Trait, Some("name::Named"))494    );495    assert_eq!(checked.docs, vec!["Folds."]);496    assert!(checked.defined_at.starts_with("lib.rs:"));497    let from = function(&m, "name::word::Word::from_json");498    assert_eq!(499        (from.self_kind, from.owner.as_deref()),500        (None, Some("name::word::Word"))501    );502    assert!(503        matches!(from.cross, Cross::Skip { .. }),504        "Result<Self, u8> is not the crate Result"505    );506    assert_eq!(function(&m, "name::word::Word::to_json").ret, Ty::String);507    assert!(m.functions.iter().all(|f| f.name != "KIND"));508}509510#[test]511fn a_public_class_field_gets_a_setter_unless_it_holds_a_borrow() {512    let root = scratch("setter", "pub mod life { #[derive(Clone, Copy, Serialize, Deserialize)] pub enum Boundary { Constant, Wrap } #[derive(Clone, Serialize, Deserialize)] pub struct Config { pub boundary: Boundary, pub padding: usize, pub name: &'static str } impl Config { pub fn budget(&self) -> usize { 0 } } }", "life\n");513    let rust = read(&root, "pkgs/mrlypy/src/gen.rs");514    assert!(rust.contains("pub fn set_boundary(&mut self, value: PySerde<mrlyrs::life::Boundary>) -> PyResult<()> {"));515    assert!(rust.contains("self.0.padding = value;"));516    assert!(!rust.contains("set_name"));517    assert!(read(&root, "pkgs/mrlypy/python/mrlypy/life/__init__.pyi").contains("@boundary.setter"));518    let wasm = read(&root, "pkgs/mrlyjs/units/life/src/lib.rs");519    assert!(wasm.contains("pub fn set_boundary(&mut self, value: JsValue) -> Result<(), JsValue> {"));520    assert!(wasm.contains("self.inner.padding = value;"));521    assert!(!wasm.contains("set_name"));522    let dts = read(&root, "pkgs/mrlyjs/life.d.ts");523    assert!(dts.contains("set boundary(value: Boundary);"));524    assert!(dts.contains("readonly name: string;"));525    std::fs::remove_dir_all(root).ok();526}527528#[test]529fn default_crosses_as_a_static_on_the_type_and_an_alias_fixes_n() {530    let lib = "pub mod gen { #[derive(Clone, Default, Serialize, Deserialize)] pub struct Paint { pub n: u8 } #[derive(Clone, Serialize, Deserialize)] pub struct Field { pub n: u8 } impl Field { pub fn size(&self) -> u8 { 0 } } impl Default for Field { fn default() -> Field { Field { n: 1 } } } #[derive(Clone, Serialize, Deserialize)] pub struct ConfigNd<const N: usize> { pub n: u8 } impl<const N: usize> Default for ConfigNd<N> { fn default() -> Self { ConfigNd { n: 0 } } } pub type Config2d = ConfigNd<2>; } pub mod core { #[derive(Clone, Default, Serialize, Deserialize)] pub struct Rng { s: u64 } }";531    let m = manifest(lib);532    let paint = function(&m, "gen::Paint::default");533    assert_eq!(534        (paint.source, paint.owner.as_deref(), paint.self_kind, &paint.cross),535        (Source::Default, Some("gen::Paint"), None, &Cross::Ok)536    );537    assert_eq!(paint.ret, Ty::Plain { path: "gen::Paint".into() });538    assert_eq!(539        function(&m, "gen::Field::default").ret,540        Ty::Class { path: "gen::Field".into(), dim: None }541    );542    assert_eq!(543        function(&m, "gen::Config2d::default").ret,544        Ty::Plain { path: "gen::ConfigNd".into() }545    );546    let defaults: Vec<&str> = m547        .functions548        .iter()549        .filter(|f| f.source == Source::Default)550        .map(|f| f.path.as_str())551        .collect();552    assert_eq!(defaults, ["gen::Config2d::default", "gen::Field::default", "gen::Paint::default"]);553    let root = scratch("default", lib, "gen\n");554    assert!(read(&root, "pkgs/mrlypy/python/mrlypy/gen/__init__.pyi").contains("class Config2d:\n    @staticmethod\n    def default() -> dict[str, Any]:"));555    assert!(read(&root, "pkgs/mrlyjs/gen.d.ts").contains("static default(): Field;"));556    assert!(read(&root, "pkgs/mrlyrs/src/bin/mrly.rs").contains("(\"gen.Config2d.default\", \"() -> gen.ConfigNd\""));557    std::fs::remove_dir_all(root).ok();558}559560#[test]561fn the_hand_rng_declares_choice_and_shuffle_in_both_bridges() {562    let root = scratch("rng", "pub mod core { pub struct Rng { s: u64 } impl Rng { pub fn new(seed: u64) -> Rng { Rng { s: seed } } pub fn choice<'a, T>(&mut self, items: &'a [T]) -> &'a T { &items[0] } pub fn shuffle<T>(&mut self, seq: &mut [T]) {} } }", "core\n");563    let stub = read(&root, "pkgs/mrlypy/python/mrlypy/core/__init__.pyi");564    assert!(stub.contains("    def choice(self, seq: Any) -> Any:"));565    assert!(stub.contains("    def shuffle(self, seq: list[Any]) -> None:"));566    let dts = read(&root, "pkgs/mrlyjs/core.d.ts");567    assert!(dts.contains("    choice<T>(items: ArrayLike<T>): T;"));568    assert!(dts.contains("    shuffle<T>(items: T[]): void;"));569    std::fs::remove_dir_all(root).ok();570}571572fn crate_files() -> Files {573    parse::load(Path::new(concat!(574        env!("CARGO_MANIFEST_DIR"),575        "/../pkgs/mrlyrs/src"576    )))577    .expect("the crate loads")578}579580#[test]581fn the_crate_parses_the_same_twice() {582    let files = crate_files();583    let a = resolve::build(&files, "0").expect("first").manifest;584    let b = resolve::build(&files, "0").expect("second").manifest;585    assert_eq!(a, b);586    assert_eq!(587        serde_json::to_string(&a).unwrap(),588        serde_json::to_string(&b).unwrap()589    );590}591592#[test]593fn function_entries_match_the_pub_fn_grep() {594    let files = crate_files();595    let built = resolve::build(&files, "0").expect("the crate builds");596    let grep: usize = files597        .values()598        .map(|t| t.lines().filter(|l| parse::is_pub_fn_line(l)).count())599        .sum();600    let fns = &built.manifest.functions;601    let written = fns602        .iter()603        .filter(|f| f.source == Source::Written && !f.constant)604        .count();605    let constant = fns606        .iter()607        .filter(|f| f.source == Source::Written && f.constant)608        .count();609    let generated = fns.iter().filter(|f| f.source == Source::NamedEnum).count();610    let traits = fns.iter().filter(|f| f.source == Source::Trait).count();611    let defaults = fns.iter().filter(|f| f.source == Source::Default).count();612    let text = &files["math/name/mod.rs"];613    let start = text.find("pub trait Named").expect("the trait");614    let end = start + text[start..].find("\n}\n").expect("its end");615    let trait_fns = text[start..end]616        .lines()617        .filter(|l| l.starts_with("    fn "))618        .count();619    let impls: usize = files620        .values()621        .map(|t| t.matches("impl Named for ").count())622        .sum();623    assert_eq!(624        traits,625        impls * trait_fns,626        "every impl Named carries every trait fn"627    );628    let named = built629        .manifest630        .types631        .iter()632        .filter(|t| matches!(t.cross, TypeCross::Enum { named: true, .. }))633        .count();634    assert_eq!(635        written + built.macro_body_fns,636        grep,637        "every grep line is a written pub fn or sits inside macro_rules"638    );639    assert_eq!(generated, 2 * named, "named_enum! writes all and name");640    assert_eq!(fns.len(), written + constant + generated + traits + defaults);641    assert!(built.collisions.is_empty(), "{:?}", built.collisions);642}