From c120757091ad1daeefe8b743723ade3628a2f90e Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Tue, 6 Jan 2026 13:45:23 -0300 Subject: [PATCH] refactor: extract inline tests to dedicated `*_tests.rs` files --- crates/plotnik-core/src/interner.rs | 100 ----------- crates/plotnik-core/src/interner_tests.rs | 96 +++++++++++ crates/plotnik-core/src/lib.rs | 84 +-------- crates/plotnik-core/src/lib_tests.rs | 73 ++++++++ crates/plotnik-core/src/utils.rs | 50 ------ crates/plotnik-core/src/utils_tests.rs | 46 +++++ crates/plotnik-langs/src/lib.rs | 159 +----------------- crates/plotnik-langs/src/lib_tests.rs | 152 +++++++++++++++++ crates/plotnik-lib/src/analyze/mod.rs | 2 + crates/plotnik-lib/src/analyze/refs.rs | 47 ------ crates/plotnik-lib/src/analyze/refs_tests.rs | 43 +++++ .../src/analyze/type_check/context.rs | 96 +---------- .../src/analyze/type_check/context_tests.rs | 90 ++++++++++ .../plotnik-lib/src/analyze/type_check/mod.rs | 8 +- .../src/analyze/type_check/symbol.rs | 22 --- .../src/analyze/type_check/symbol_tests.rs | 18 ++ .../src/analyze/type_check/unify.rs | 67 -------- .../src/analyze/type_check/unify_tests.rs | 65 +++++++ crates/plotnik-lib/src/bytecode/effects.rs | 47 ------ .../plotnik-lib/src/bytecode/effects_tests.rs | 43 +++++ crates/plotnik-lib/src/bytecode/entrypoint.rs | 10 -- .../src/bytecode/entrypoint_tests.rs | 6 + crates/plotnik-lib/src/bytecode/format.rs | 49 ------ .../plotnik-lib/src/bytecode/format_tests.rs | 45 +++++ crates/plotnik-lib/src/bytecode/header.rs | 75 --------- .../plotnik-lib/src/bytecode/header_tests.rs | 71 ++++++++ crates/plotnik-lib/src/bytecode/mod.rs | 14 ++ crates/plotnik-lib/src/bytecode/nav.rs | 55 ------ crates/plotnik-lib/src/bytecode/nav_tests.rs | 51 ++++++ crates/plotnik-lib/src/bytecode/sections.rs | 11 -- .../src/bytecode/sections_tests.rs | 7 + crates/plotnik-lib/src/bytecode/type_meta.rs | 54 ------ .../src/bytecode/type_meta_tests.rs | 50 ++++++ crates/plotnik-lib/src/query/mod.rs | 2 + crates/plotnik-lib/src/query/source_map.rs | 112 +----------- .../plotnik-lib/src/query/source_map_tests.rs | 106 ++++++++++++ crates/plotnik-lib/src/type_system/arity.rs | 21 --- .../src/type_system/arity_tests.rs | 17 ++ crates/plotnik-lib/src/type_system/kind.rs | 89 ---------- .../plotnik-lib/src/type_system/kind_tests.rs | 85 ++++++++++ crates/plotnik-lib/src/type_system/mod.rs | 9 + .../plotnik-lib/src/type_system/primitives.rs | 29 ---- .../src/type_system/primitives_tests.rs | 25 +++ .../plotnik-lib/src/type_system/quantifier.rs | 26 --- .../src/type_system/quantifier_tests.rs | 22 +++ 45 files changed, 1157 insertions(+), 1192 deletions(-) create mode 100644 crates/plotnik-core/src/interner_tests.rs create mode 100644 crates/plotnik-core/src/lib_tests.rs create mode 100644 crates/plotnik-core/src/utils_tests.rs create mode 100644 crates/plotnik-langs/src/lib_tests.rs create mode 100644 crates/plotnik-lib/src/analyze/refs_tests.rs create mode 100644 crates/plotnik-lib/src/analyze/type_check/context_tests.rs create mode 100644 crates/plotnik-lib/src/analyze/type_check/symbol_tests.rs create mode 100644 crates/plotnik-lib/src/analyze/type_check/unify_tests.rs create mode 100644 crates/plotnik-lib/src/bytecode/effects_tests.rs create mode 100644 crates/plotnik-lib/src/bytecode/entrypoint_tests.rs create mode 100644 crates/plotnik-lib/src/bytecode/format_tests.rs create mode 100644 crates/plotnik-lib/src/bytecode/header_tests.rs create mode 100644 crates/plotnik-lib/src/bytecode/nav_tests.rs create mode 100644 crates/plotnik-lib/src/bytecode/sections_tests.rs create mode 100644 crates/plotnik-lib/src/bytecode/type_meta_tests.rs create mode 100644 crates/plotnik-lib/src/query/source_map_tests.rs create mode 100644 crates/plotnik-lib/src/type_system/arity_tests.rs create mode 100644 crates/plotnik-lib/src/type_system/kind_tests.rs create mode 100644 crates/plotnik-lib/src/type_system/primitives_tests.rs create mode 100644 crates/plotnik-lib/src/type_system/quantifier_tests.rs diff --git a/crates/plotnik-core/src/interner.rs b/crates/plotnik-core/src/interner.rs index 36ad50cd..361c83a1 100644 --- a/crates/plotnik-core/src/interner.rs +++ b/crates/plotnik-core/src/interner.rs @@ -132,103 +132,3 @@ impl Interner { (blob, offsets) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn intern_deduplicates() { - let mut interner = Interner::new(); - - let a = interner.intern("foo"); - let b = interner.intern("foo"); - let c = interner.intern("bar"); - - assert_eq!(a, b); - assert_ne!(a, c); - assert_eq!(interner.len(), 2); - } - - #[test] - fn resolve_roundtrip() { - let mut interner = Interner::new(); - - let sym = interner.intern("hello"); - assert_eq!(interner.resolve(sym), "hello"); - } - - #[test] - fn intern_owned_avoids_clone_on_hit() { - let mut interner = Interner::new(); - - let a = interner.intern("test"); - let b = interner.intern_owned("test".to_string()); - - assert_eq!(a, b); - assert_eq!(interner.len(), 1); - } - - #[test] - fn symbols_are_copy() { - let mut interner = Interner::new(); - let sym = interner.intern("x"); - - let copy = sym; - assert_eq!(sym, copy); - } - - #[test] - fn symbol_ordering_is_insertion_order() { - let mut interner = Interner::new(); - - let z = interner.intern("z"); - let a = interner.intern("a"); - - // z was inserted first, so z < a by insertion order - assert!(z < a); - } - - #[test] - fn to_blob_produces_correct_format() { - let mut interner = Interner::new(); - interner.intern("id"); - interner.intern("foo"); - - let (blob, offsets) = interner.to_blob(); - - assert_eq!(blob, b"idfoo"); - assert_eq!(offsets, vec![0, 2, 5]); - - // Verify we can reconstruct strings - let s0 = &blob[offsets[0] as usize..offsets[1] as usize]; - let s1 = &blob[offsets[1] as usize..offsets[2] as usize]; - assert_eq!(s0, b"id"); - assert_eq!(s1, b"foo"); - } - - #[test] - fn to_blob_empty() { - let interner = Interner::new(); - let (blob, offsets) = interner.to_blob(); - - assert!(blob.is_empty()); - assert_eq!(offsets, vec![0]); // just the sentinel - } - - #[test] - fn iter_yields_all_strings() { - let mut interner = Interner::new(); - let a = interner.intern("alpha"); - let b = interner.intern("beta"); - - let items: Vec<_> = interner.iter().collect(); - assert_eq!(items, vec![(a, "alpha"), (b, "beta")]); - } - - #[test] - fn symbol_from_raw_roundtrip() { - let sym = Symbol::from_raw(42); - assert_eq!(sym.as_u32(), 42); - } -} diff --git a/crates/plotnik-core/src/interner_tests.rs b/crates/plotnik-core/src/interner_tests.rs new file mode 100644 index 00000000..2cb06e06 --- /dev/null +++ b/crates/plotnik-core/src/interner_tests.rs @@ -0,0 +1,96 @@ +use crate::{Interner, Symbol}; + +#[test] +fn intern_deduplicates() { + let mut interner = Interner::new(); + + let a = interner.intern("foo"); + let b = interner.intern("foo"); + let c = interner.intern("bar"); + + assert_eq!(a, b); + assert_ne!(a, c); + assert_eq!(interner.len(), 2); +} + +#[test] +fn resolve_roundtrip() { + let mut interner = Interner::new(); + + let sym = interner.intern("hello"); + assert_eq!(interner.resolve(sym), "hello"); +} + +#[test] +fn intern_owned_avoids_clone_on_hit() { + let mut interner = Interner::new(); + + let a = interner.intern("test"); + let b = interner.intern_owned("test".to_string()); + + assert_eq!(a, b); + assert_eq!(interner.len(), 1); +} + +#[test] +fn symbols_are_copy() { + let mut interner = Interner::new(); + let sym = interner.intern("x"); + + let copy = sym; + assert_eq!(sym, copy); +} + +#[test] +fn symbol_ordering_is_insertion_order() { + let mut interner = Interner::new(); + + let z = interner.intern("z"); + let a = interner.intern("a"); + + // z was inserted first, so z < a by insertion order + assert!(z < a); +} + +#[test] +fn to_blob_produces_correct_format() { + let mut interner = Interner::new(); + interner.intern("id"); + interner.intern("foo"); + + let (blob, offsets) = interner.to_blob(); + + assert_eq!(blob, b"idfoo"); + assert_eq!(offsets, vec![0, 2, 5]); + + // Verify we can reconstruct strings + let s0 = &blob[offsets[0] as usize..offsets[1] as usize]; + let s1 = &blob[offsets[1] as usize..offsets[2] as usize]; + assert_eq!(s0, b"id"); + assert_eq!(s1, b"foo"); +} + +#[test] +fn to_blob_empty() { + let interner = Interner::new(); + let (blob, offsets) = interner.to_blob(); + + assert!(blob.is_empty()); + assert_eq!(offsets, vec![0]); // just the sentinel +} + +#[test] +fn iter_yields_all_strings() { + let mut interner = Interner::new(); + let a = interner.intern("alpha"); + let b = interner.intern("beta"); + + let items: Vec<_> = interner.iter().collect(); + assert_eq!(items, vec![(a, "alpha"), (b, "beta")]); +} + +#[test] +fn symbol_from_raw_roundtrip() { + let sym = Symbol::from_raw(42); + assert_eq!(sym.as_u32(), 42); +} diff --git a/crates/plotnik-core/src/lib.rs b/crates/plotnik-core/src/lib.rs index 8d3b8927..c3eb9db9 100644 --- a/crates/plotnik-core/src/lib.rs +++ b/crates/plotnik-core/src/lib.rs @@ -17,6 +17,13 @@ mod interner; mod invariants; pub mod utils; +#[cfg(test)] +mod interner_tests; +#[cfg(test)] +mod lib_tests; +#[cfg(test)] +mod utils_tests; + pub use interner::{Interner, Symbol}; /// Raw node definition from `node-types.json`. @@ -548,80 +555,3 @@ impl NodeTypes for DynamicNodeTypes { self.valid_child_types(node_type_id).contains(&child) } } - -#[cfg(test)] -mod tests { - use super::*; - - const SAMPLE_JSON: &str = r#"[ - { - "type": "expression", - "named": true, - "subtypes": [ - {"type": "identifier", "named": true}, - {"type": "number", "named": true} - ] - }, - { - "type": "function_declaration", - "named": true, - "fields": { - "name": { - "multiple": false, - "required": true, - "types": [{"type": "identifier", "named": true}] - }, - "body": { - "multiple": false, - "required": true, - "types": [{"type": "block", "named": true}] - } - } - }, - { - "type": "program", - "named": true, - "root": true, - "fields": {}, - "children": { - "multiple": true, - "required": false, - "types": [{"type": "statement", "named": true}] - } - }, - { - "type": "comment", - "named": true, - "extra": true - }, - { - "type": "identifier", - "named": true - }, - { - "type": "+", - "named": false - } - ]"#; - - #[test] - fn parse_raw_nodes() { - let nodes = parse_node_types(SAMPLE_JSON).unwrap(); - assert_eq!(nodes.len(), 6); - - let expr = nodes.iter().find(|n| n.type_name == "expression").unwrap(); - assert!(expr.named); - assert!(expr.subtypes.is_some()); - assert_eq!(expr.subtypes.as_ref().unwrap().len(), 2); - - let func = nodes - .iter() - .find(|n| n.type_name == "function_declaration") - .unwrap(); - assert!(func.fields.contains_key("name")); - assert!(func.fields.contains_key("body")); - - let plus = nodes.iter().find(|n| n.type_name == "+").unwrap(); - assert!(!plus.named); - } -} diff --git a/crates/plotnik-core/src/lib_tests.rs b/crates/plotnik-core/src/lib_tests.rs new file mode 100644 index 00000000..2e577376 --- /dev/null +++ b/crates/plotnik-core/src/lib_tests.rs @@ -0,0 +1,73 @@ +use crate::parse_node_types; + +const SAMPLE_JSON: &str = r#"[ + { + "type": "expression", + "named": true, + "subtypes": [ + {"type": "identifier", "named": true}, + {"type": "number", "named": true} + ] + }, + { + "type": "function_declaration", + "named": true, + "fields": { + "name": { + "multiple": false, + "required": true, + "types": [{"type": "identifier", "named": true}] + }, + "body": { + "multiple": false, + "required": true, + "types": [{"type": "block", "named": true}] + } + } + }, + { + "type": "program", + "named": true, + "root": true, + "fields": {}, + "children": { + "multiple": true, + "required": false, + "types": [{"type": "statement", "named": true}] + } + }, + { + "type": "comment", + "named": true, + "extra": true + }, + { + "type": "identifier", + "named": true + }, + { + "type": "+", + "named": false + } +]"#; + +#[test] +fn parse_raw_nodes() { + let nodes = parse_node_types(SAMPLE_JSON).unwrap(); + assert_eq!(nodes.len(), 6); + + let expr = nodes.iter().find(|n| n.type_name == "expression").unwrap(); + assert!(expr.named); + assert!(expr.subtypes.is_some()); + assert_eq!(expr.subtypes.as_ref().unwrap().len(), 2); + + let func = nodes + .iter() + .find(|n| n.type_name == "function_declaration") + .unwrap(); + assert!(func.fields.contains_key("name")); + assert!(func.fields.contains_key("body")); + + let plus = nodes.iter().find(|n| n.type_name == "+").unwrap(); + assert!(!plus.named); +} diff --git a/crates/plotnik-core/src/utils.rs b/crates/plotnik-core/src/utils.rs index b8cdd1d4..5bf3a214 100644 --- a/crates/plotnik-core/src/utils.rs +++ b/crates/plotnik-core/src/utils.rs @@ -63,53 +63,3 @@ pub fn to_snake_case(s: &str) -> String { } result } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn pascal_case_from_snake() { - assert_eq!(to_pascal_case("foo_bar"), "FooBar"); - assert_eq!(to_pascal_case("foo"), "Foo"); - assert_eq!(to_pascal_case("_foo"), "Foo"); - assert_eq!(to_pascal_case("foo_"), "Foo"); - } - - #[test] - fn pascal_case_normalizes() { - assert_eq!(to_pascal_case("FOO_BAR"), "FooBar"); - assert_eq!(to_pascal_case("FOO"), "Foo"); - assert_eq!(to_pascal_case("FOOBAR"), "Foobar"); - } - - #[test] - fn pascal_case_idempotent() { - assert_eq!(to_pascal_case("FooBar"), "FooBar"); - assert_eq!(to_pascal_case("QRow"), "QRow"); - assert_eq!(to_pascal_case("Q"), "Q"); - } - - #[test] - fn pascal_case_from_kebab() { - assert_eq!(to_pascal_case("foo-bar"), "FooBar"); - assert_eq!(to_pascal_case("foo-bar-baz"), "FooBarBaz"); - } - - #[test] - fn pascal_case_from_dotted() { - assert_eq!(to_pascal_case("foo.bar"), "FooBar"); - } - - #[test] - fn snake_case_from_pascal() { - assert_eq!(to_snake_case("FooBar"), "foo_bar"); - assert_eq!(to_snake_case("Foo"), "foo"); - } - - #[test] - fn snake_case_from_camel() { - assert_eq!(to_snake_case("fooBar"), "foo_bar"); - assert_eq!(to_snake_case("fooBarBaz"), "foo_bar_baz"); - } -} diff --git a/crates/plotnik-core/src/utils_tests.rs b/crates/plotnik-core/src/utils_tests.rs new file mode 100644 index 00000000..c06cb4ab --- /dev/null +++ b/crates/plotnik-core/src/utils_tests.rs @@ -0,0 +1,46 @@ +use crate::utils::{to_pascal_case, to_snake_case}; + +#[test] +fn pascal_case_from_snake() { + assert_eq!(to_pascal_case("foo_bar"), "FooBar"); + assert_eq!(to_pascal_case("foo"), "Foo"); + assert_eq!(to_pascal_case("_foo"), "Foo"); + assert_eq!(to_pascal_case("foo_"), "Foo"); +} + +#[test] +fn pascal_case_normalizes() { + assert_eq!(to_pascal_case("FOO_BAR"), "FooBar"); + assert_eq!(to_pascal_case("FOO"), "Foo"); + assert_eq!(to_pascal_case("FOOBAR"), "Foobar"); +} + +#[test] +fn pascal_case_idempotent() { + assert_eq!(to_pascal_case("FooBar"), "FooBar"); + assert_eq!(to_pascal_case("QRow"), "QRow"); + assert_eq!(to_pascal_case("Q"), "Q"); +} + +#[test] +fn pascal_case_from_kebab() { + assert_eq!(to_pascal_case("foo-bar"), "FooBar"); + assert_eq!(to_pascal_case("foo-bar-baz"), "FooBarBaz"); +} + +#[test] +fn pascal_case_from_dotted() { + assert_eq!(to_pascal_case("foo.bar"), "FooBar"); +} + +#[test] +fn snake_case_from_pascal() { + assert_eq!(to_snake_case("FooBar"), "foo_bar"); + assert_eq!(to_snake_case("Foo"), "foo"); +} + +#[test] +fn snake_case_from_camel() { + assert_eq!(to_snake_case("fooBar"), "foo_bar"); + assert_eq!(to_snake_case("fooBarBaz"), "foo_bar_baz"); +} diff --git a/crates/plotnik-langs/src/lib.rs b/crates/plotnik-langs/src/lib.rs index 3438616d..3a697dbe 100644 --- a/crates/plotnik-langs/src/lib.rs +++ b/crates/plotnik-langs/src/lib.rs @@ -8,6 +8,9 @@ use plotnik_core::{Cardinality, NodeFieldId, NodeTypeId, NodeTypes, StaticNodeTy pub mod builtin; pub mod dynamic; +#[cfg(test)] +mod lib_tests; + pub use builtin::*; /// User-facing language type. Works with any language (static or dynamic). @@ -211,159 +214,3 @@ impl LangImpl for LangInner { self.node_types.is_valid_child_type(node_type_id, child) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[cfg(feature = "lang-javascript")] - fn lang_from_name() { - assert_eq!(from_name("js").unwrap().name(), "javascript"); - assert_eq!(from_name("JavaScript").unwrap().name(), "javascript"); - assert!(from_name("unknown").is_none()); - } - - #[test] - #[cfg(feature = "lang-go")] - fn lang_from_name_golang() { - assert_eq!(from_name("go").unwrap().name(), "go"); - assert_eq!(from_name("golang").unwrap().name(), "go"); - assert_eq!(from_name("GOLANG").unwrap().name(), "go"); - } - - #[test] - #[cfg(feature = "lang-javascript")] - fn lang_from_extension() { - assert_eq!(from_ext("js").unwrap().name(), "javascript"); - assert_eq!(from_ext("mjs").unwrap().name(), "javascript"); - } - - #[test] - #[cfg(all(feature = "lang-typescript", feature = "lang-tsx"))] - fn typescript_and_tsx() { - assert_eq!(typescript().name(), "typescript"); - assert_eq!(tsx().name(), "tsx"); - assert_eq!(from_ext("ts").unwrap().name(), "typescript"); - assert_eq!(from_ext("tsx").unwrap().name(), "tsx"); - } - - #[test] - fn all_returns_enabled_langs() { - let langs = all(); - assert!(!langs.is_empty()); - for lang in &langs { - assert!(!lang.name().is_empty()); - } - } - - #[test] - #[cfg(feature = "lang-javascript")] - fn resolve_node_and_field() { - let lang = javascript(); - - let func_id = lang.resolve_named_node("function_declaration"); - assert!(func_id.is_some()); - - let unknown = lang.resolve_named_node("nonexistent_node_type"); - assert!(unknown.is_none()); - - let name_field = lang.resolve_field("name"); - assert!(name_field.is_some()); - - let unknown_field = lang.resolve_field("nonexistent_field"); - assert!(unknown_field.is_none()); - } - - #[test] - #[cfg(feature = "lang-javascript")] - fn supertype_via_lang_trait() { - let lang = javascript(); - - let expr_id = lang.resolve_named_node("expression").unwrap(); - assert!(lang.is_supertype(expr_id)); - - let subtypes = lang.subtypes(expr_id); - assert!(!subtypes.is_empty()); - - let func_id = lang.resolve_named_node("function_declaration").unwrap(); - assert!(!lang.is_supertype(func_id)); - } - - #[test] - #[cfg(feature = "lang-javascript")] - fn field_validation_via_trait() { - let lang = javascript(); - - let func_id = lang.resolve_named_node("function_declaration").unwrap(); - let name_field = lang.resolve_field("name").unwrap(); - let body_field = lang.resolve_field("body").unwrap(); - - assert!(lang.has_field(func_id, name_field)); - assert!(lang.has_field(func_id, body_field)); - - let identifier_id = lang.resolve_named_node("identifier").unwrap(); - assert!(lang.is_valid_field_type(func_id, name_field, identifier_id)); - - let statement_block_id = lang.resolve_named_node("statement_block").unwrap(); - assert!(lang.is_valid_field_type(func_id, body_field, statement_block_id)); - } - - #[test] - #[cfg(feature = "lang-javascript")] - fn root_via_trait() { - let lang = javascript(); - let root_id = lang.root(); - assert!(root_id.is_some()); - - let program_id = lang.resolve_named_node("program"); - assert_eq!(root_id, program_id); - } - - #[test] - #[cfg(feature = "lang-javascript")] - fn unresolved_returns_none() { - let lang = javascript(); - - assert!(lang.resolve_named_node("nonexistent_node_type").is_none()); - assert!(lang.resolve_field("nonexistent_field").is_none()); - } - - #[test] - #[cfg(feature = "lang-rust")] - fn rust_lang_works() { - let lang = rust(); - let func_id = lang.resolve_named_node("function_item"); - assert!(func_id.is_some()); - } - - #[test] - #[cfg(feature = "lang-javascript")] - fn resolve_nonexistent_nodes() { - let lang = javascript(); - - // Non-existent nodes return None - assert!(lang.resolve_named_node("end").is_none()); - assert!(lang.resolve_named_node("fake_named").is_none()); - assert!(lang.resolve_anonymous_node("totally_fake_node").is_none()); - - // Field resolution - assert!(lang.resolve_field("name").is_some()); - assert!(lang.resolve_field("fake_field").is_none()); - } - - /// Verifies that languages with "end" keyword assign it a non-zero ID. - #[test] - #[cfg(all(feature = "lang-ruby", feature = "lang-lua"))] - fn end_keyword_resolves() { - // Ruby has "end" keyword for blocks, methods, classes, etc. - let ruby = ruby(); - let ruby_end = ruby.resolve_anonymous_node("end"); - assert!(ruby_end.is_some(), "Ruby should have 'end' keyword"); - - // Lua has "end" keyword for blocks, functions, etc. - let lua = lua(); - let lua_end = lua.resolve_anonymous_node("end"); - assert!(lua_end.is_some(), "Lua should have 'end' keyword"); - } -} diff --git a/crates/plotnik-langs/src/lib_tests.rs b/crates/plotnik-langs/src/lib_tests.rs new file mode 100644 index 00000000..db12849c --- /dev/null +++ b/crates/plotnik-langs/src/lib_tests.rs @@ -0,0 +1,152 @@ +use super::*; + +#[test] +#[cfg(feature = "lang-javascript")] +fn lang_from_name() { + assert_eq!(from_name("js").unwrap().name(), "javascript"); + assert_eq!(from_name("JavaScript").unwrap().name(), "javascript"); + assert!(from_name("unknown").is_none()); +} + +#[test] +#[cfg(feature = "lang-go")] +fn lang_from_name_golang() { + assert_eq!(from_name("go").unwrap().name(), "go"); + assert_eq!(from_name("golang").unwrap().name(), "go"); + assert_eq!(from_name("GOLANG").unwrap().name(), "go"); +} + +#[test] +#[cfg(feature = "lang-javascript")] +fn lang_from_extension() { + assert_eq!(from_ext("js").unwrap().name(), "javascript"); + assert_eq!(from_ext("mjs").unwrap().name(), "javascript"); +} + +#[test] +#[cfg(all(feature = "lang-typescript", feature = "lang-tsx"))] +fn typescript_and_tsx() { + assert_eq!(typescript().name(), "typescript"); + assert_eq!(tsx().name(), "tsx"); + assert_eq!(from_ext("ts").unwrap().name(), "typescript"); + assert_eq!(from_ext("tsx").unwrap().name(), "tsx"); +} + +#[test] +fn all_returns_enabled_langs() { + let langs = all(); + assert!(!langs.is_empty()); + for lang in &langs { + assert!(!lang.name().is_empty()); + } +} + +#[test] +#[cfg(feature = "lang-javascript")] +fn resolve_node_and_field() { + let lang = javascript(); + + let func_id = lang.resolve_named_node("function_declaration"); + assert!(func_id.is_some()); + + let unknown = lang.resolve_named_node("nonexistent_node_type"); + assert!(unknown.is_none()); + + let name_field = lang.resolve_field("name"); + assert!(name_field.is_some()); + + let unknown_field = lang.resolve_field("nonexistent_field"); + assert!(unknown_field.is_none()); +} + +#[test] +#[cfg(feature = "lang-javascript")] +fn supertype_via_lang_trait() { + let lang = javascript(); + + let expr_id = lang.resolve_named_node("expression").unwrap(); + assert!(lang.is_supertype(expr_id)); + + let subtypes = lang.subtypes(expr_id); + assert!(!subtypes.is_empty()); + + let func_id = lang.resolve_named_node("function_declaration").unwrap(); + assert!(!lang.is_supertype(func_id)); +} + +#[test] +#[cfg(feature = "lang-javascript")] +fn field_validation_via_trait() { + let lang = javascript(); + + let func_id = lang.resolve_named_node("function_declaration").unwrap(); + let name_field = lang.resolve_field("name").unwrap(); + let body_field = lang.resolve_field("body").unwrap(); + + assert!(lang.has_field(func_id, name_field)); + assert!(lang.has_field(func_id, body_field)); + + let identifier_id = lang.resolve_named_node("identifier").unwrap(); + assert!(lang.is_valid_field_type(func_id, name_field, identifier_id)); + + let statement_block_id = lang.resolve_named_node("statement_block").unwrap(); + assert!(lang.is_valid_field_type(func_id, body_field, statement_block_id)); +} + +#[test] +#[cfg(feature = "lang-javascript")] +fn root_via_trait() { + let lang = javascript(); + let root_id = lang.root(); + assert!(root_id.is_some()); + + let program_id = lang.resolve_named_node("program"); + assert_eq!(root_id, program_id); +} + +#[test] +#[cfg(feature = "lang-javascript")] +fn unresolved_returns_none() { + let lang = javascript(); + + assert!(lang.resolve_named_node("nonexistent_node_type").is_none()); + assert!(lang.resolve_field("nonexistent_field").is_none()); +} + +#[test] +#[cfg(feature = "lang-rust")] +fn rust_lang_works() { + let lang = rust(); + let func_id = lang.resolve_named_node("function_item"); + assert!(func_id.is_some()); +} + +#[test] +#[cfg(feature = "lang-javascript")] +fn resolve_nonexistent_nodes() { + let lang = javascript(); + + // Non-existent nodes return None + assert!(lang.resolve_named_node("end").is_none()); + assert!(lang.resolve_named_node("fake_named").is_none()); + assert!(lang.resolve_anonymous_node("totally_fake_node").is_none()); + + // Field resolution + assert!(lang.resolve_field("name").is_some()); + assert!(lang.resolve_field("fake_field").is_none()); +} + +/// Verifies that languages with "end" keyword assign it a non-zero ID. +#[test] +#[cfg(all(feature = "lang-ruby", feature = "lang-lua"))] +fn end_keyword_resolves() { + // Ruby has "end" keyword for blocks, methods, classes, etc. + let ruby = ruby(); + let ruby_end = ruby.resolve_anonymous_node("end"); + assert!(ruby_end.is_some(), "Ruby should have 'end' keyword"); + + // Lua has "end" keyword for blocks, functions, etc. + let lua = lua(); + let lua_end = lua.resolve_anonymous_node("end"); + assert!(lua_end.is_some(), "Lua should have 'end' keyword"); +} diff --git a/crates/plotnik-lib/src/analyze/mod.rs b/crates/plotnik-lib/src/analyze/mod.rs index 93a9c445..5d460da3 100644 --- a/crates/plotnik-lib/src/analyze/mod.rs +++ b/crates/plotnik-lib/src/analyze/mod.rs @@ -23,6 +23,8 @@ mod dependencies_tests; #[cfg(all(test, feature = "plotnik-langs"))] mod link_tests; #[cfg(test)] +mod refs_tests; +#[cfg(test)] mod symbol_table_tests; pub use dependencies::DependencyAnalysis; diff --git a/crates/plotnik-lib/src/analyze/refs.rs b/crates/plotnik-lib/src/analyze/refs.rs index 43f84a2f..155c97c7 100644 --- a/crates/plotnik-lib/src/analyze/refs.rs +++ b/crates/plotnik-lib/src/analyze/refs.rs @@ -23,50 +23,3 @@ pub fn contains_ref(expr: &Expr, name: &str) -> bool { .filter_map(|r| r.name()) .any(|tok| tok.text() == name) } - -#[cfg(test)] -mod tests { - use super::*; - use crate::Query; - - #[test] - fn collect_refs_from_simple_ref() { - let q = Query::expect("Q = (Foo)"); - let expr = q.symbol_table.get("Q").unwrap(); - let refs = ref_names(expr); - assert_eq!(refs.len(), 1); - assert!(refs.contains("Foo")); - } - - #[test] - fn collect_refs_from_nested() { - let q = Query::expect("Q = (x (Foo) (Bar))"); - let expr = q.symbol_table.get("Q").unwrap(); - let refs = ref_names(expr); - assert_eq!(refs.len(), 2); - assert!(refs.contains("Foo")); - assert!(refs.contains("Bar")); - } - - #[test] - fn collect_refs_deduplicates() { - let q = Query::expect("Q = {(Foo) (Foo)}"); - let expr = q.symbol_table.get("Q").unwrap(); - let refs = ref_names(expr); - assert_eq!(refs.len(), 1); - } - - #[test] - fn contains_ref_positive() { - let q = Query::expect("Q = (x (Foo))"); - let expr = q.symbol_table.get("Q").unwrap(); - assert!(contains_ref(expr, "Foo")); - } - - #[test] - fn contains_ref_negative() { - let q = Query::expect("Q = (x (Foo))"); - let expr = q.symbol_table.get("Q").unwrap(); - assert!(!contains_ref(expr, "Bar")); - } -} diff --git a/crates/plotnik-lib/src/analyze/refs_tests.rs b/crates/plotnik-lib/src/analyze/refs_tests.rs new file mode 100644 index 00000000..adf8c277 --- /dev/null +++ b/crates/plotnik-lib/src/analyze/refs_tests.rs @@ -0,0 +1,43 @@ +use super::refs::{contains_ref, ref_names}; +use crate::Query; + +#[test] +fn collect_refs_from_simple_ref() { + let q = Query::expect("Q = (Foo)"); + let expr = q.symbol_table.get("Q").unwrap(); + let refs = ref_names(expr); + assert_eq!(refs.len(), 1); + assert!(refs.contains("Foo")); +} + +#[test] +fn collect_refs_from_nested() { + let q = Query::expect("Q = (x (Foo) (Bar))"); + let expr = q.symbol_table.get("Q").unwrap(); + let refs = ref_names(expr); + assert_eq!(refs.len(), 2); + assert!(refs.contains("Foo")); + assert!(refs.contains("Bar")); +} + +#[test] +fn collect_refs_deduplicates() { + let q = Query::expect("Q = {(Foo) (Foo)}"); + let expr = q.symbol_table.get("Q").unwrap(); + let refs = ref_names(expr); + assert_eq!(refs.len(), 1); +} + +#[test] +fn contains_ref_positive() { + let q = Query::expect("Q = (x (Foo))"); + let expr = q.symbol_table.get("Q").unwrap(); + assert!(contains_ref(expr, "Foo")); +} + +#[test] +fn contains_ref_negative() { + let q = Query::expect("Q = (x (Foo))"); + let expr = q.symbol_table.get("Q").unwrap(); + assert!(!contains_ref(expr, "Bar")); +} diff --git a/crates/plotnik-lib/src/analyze/type_check/context.rs b/crates/plotnik-lib/src/analyze/type_check/context.rs index 630a9584..72722a83 100644 --- a/crates/plotnik-lib/src/analyze/type_check/context.rs +++ b/crates/plotnik-lib/src/analyze/type_check/context.rs @@ -6,7 +6,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; -use crate::parser::ast::Expr; +use crate::parser::Expr; use super::symbol::{DefId, Interner, Symbol}; use super::types::{ @@ -247,97 +247,3 @@ impl TypeContext { self.type_names.iter().map(|(&id, &sym)| (id, sym)) } } - -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - - use super::*; - - #[test] - fn builtin_types_have_correct_ids() { - let ctx = TypeContext::new(); - - assert_eq!(ctx.get_type(TYPE_VOID), Some(&TypeShape::Void)); - assert_eq!(ctx.get_type(TYPE_NODE), Some(&TypeShape::Node)); - assert_eq!(ctx.get_type(TYPE_STRING), Some(&TypeShape::String)); - } - - #[test] - fn type_interning_deduplicates() { - let mut ctx = TypeContext::new(); - - let id1 = ctx.intern_type(TypeShape::Node); - let id2 = ctx.intern_type(TypeShape::Node); - - assert_eq!(id1, id2); - assert_eq!(id1, TYPE_NODE); - } - - #[test] - fn struct_types_intern_correctly() { - let mut ctx = TypeContext::new(); - let mut interner = Interner::new(); - - let x_sym = interner.intern("x"); - let mut fields = BTreeMap::new(); - fields.insert(x_sym, FieldInfo::required(TYPE_NODE)); - - let id1 = ctx.intern_type(TypeShape::Struct(fields.clone())); - let id2 = ctx.intern_type(TypeShape::Struct(fields)); - - assert_eq!(id1, id2); - } - - #[test] - fn symbol_interning_works() { - let mut interner = Interner::new(); - - let a = interner.intern("foo"); - let b = interner.intern("foo"); - let c = interner.intern("bar"); - - assert_eq!(a, b); - assert_ne!(a, c); - assert_eq!(interner.resolve(a), "foo"); - assert_eq!(interner.resolve(c), "bar"); - } - - #[test] - fn def_type_by_name() { - let mut ctx = TypeContext::new(); - let mut interner = Interner::new(); - - ctx.set_def_type_by_name(&mut interner, "Query", TYPE_NODE); - assert_eq!( - ctx.get_def_type_by_name(&interner, "Query"), - Some(TYPE_NODE) - ); - assert_eq!(ctx.get_def_type_by_name(&interner, "Missing"), None); - } - - #[test] - fn register_def_returns_stable_id() { - let mut ctx = TypeContext::new(); - let mut interner = Interner::new(); - - let id1 = ctx.register_def(&mut interner, "Foo"); - let id2 = ctx.register_def(&mut interner, "Bar"); - let id3 = ctx.register_def(&mut interner, "Foo"); // duplicate - - assert_eq!(id1, id3); - assert_ne!(id1, id2); - assert_eq!(ctx.def_name(&interner, id1), "Foo"); - assert_eq!(ctx.def_name(&interner, id2), "Bar"); - } - - #[test] - fn def_id_lookup() { - let mut ctx = TypeContext::new(); - let mut interner = Interner::new(); - - ctx.register_def(&mut interner, "Query"); - assert!(ctx.get_def_id(&interner, "Query").is_some()); - assert!(ctx.get_def_id(&interner, "Missing").is_none()); - } -} diff --git a/crates/plotnik-lib/src/analyze/type_check/context_tests.rs b/crates/plotnik-lib/src/analyze/type_check/context_tests.rs new file mode 100644 index 00000000..f5277553 --- /dev/null +++ b/crates/plotnik-lib/src/analyze/type_check/context_tests.rs @@ -0,0 +1,90 @@ +use std::collections::BTreeMap; + +use super::*; + +#[test] +fn builtin_types_have_correct_ids() { + let ctx = TypeContext::new(); + + assert_eq!(ctx.get_type(TYPE_VOID), Some(&TypeShape::Void)); + assert_eq!(ctx.get_type(TYPE_NODE), Some(&TypeShape::Node)); + assert_eq!(ctx.get_type(TYPE_STRING), Some(&TypeShape::String)); +} + +#[test] +fn type_interning_deduplicates() { + let mut ctx = TypeContext::new(); + + let id1 = ctx.intern_type(TypeShape::Node); + let id2 = ctx.intern_type(TypeShape::Node); + + assert_eq!(id1, id2); + assert_eq!(id1, TYPE_NODE); +} + +#[test] +fn struct_types_intern_correctly() { + let mut ctx = TypeContext::new(); + let mut interner = Interner::new(); + + let x_sym = interner.intern("x"); + let mut fields = BTreeMap::new(); + fields.insert(x_sym, FieldInfo::required(TYPE_NODE)); + + let id1 = ctx.intern_type(TypeShape::Struct(fields.clone())); + let id2 = ctx.intern_type(TypeShape::Struct(fields)); + + assert_eq!(id1, id2); +} + +#[test] +fn symbol_interning_works() { + let mut interner = Interner::new(); + + let a = interner.intern("foo"); + let b = interner.intern("foo"); + let c = interner.intern("bar"); + + assert_eq!(a, b); + assert_ne!(a, c); + assert_eq!(interner.resolve(a), "foo"); + assert_eq!(interner.resolve(c), "bar"); +} + +#[test] +fn def_type_by_name() { + let mut ctx = TypeContext::new(); + let mut interner = Interner::new(); + + ctx.set_def_type_by_name(&mut interner, "Query", TYPE_NODE); + assert_eq!( + ctx.get_def_type_by_name(&interner, "Query"), + Some(TYPE_NODE) + ); + assert_eq!(ctx.get_def_type_by_name(&interner, "Missing"), None); +} + +#[test] +fn register_def_returns_stable_id() { + let mut ctx = TypeContext::new(); + let mut interner = Interner::new(); + + let id1 = ctx.register_def(&mut interner, "Foo"); + let id2 = ctx.register_def(&mut interner, "Bar"); + let id3 = ctx.register_def(&mut interner, "Foo"); // duplicate + + assert_eq!(id1, id3); + assert_ne!(id1, id2); + assert_eq!(ctx.def_name(&interner, id1), "Foo"); + assert_eq!(ctx.def_name(&interner, id2), "Bar"); +} + +#[test] +fn def_id_lookup() { + let mut ctx = TypeContext::new(); + let mut interner = Interner::new(); + + ctx.register_def(&mut interner, "Query"); + assert!(ctx.get_def_id(&interner, "Query").is_some()); + assert!(ctx.get_def_id(&interner, "Missing").is_none()); +} diff --git a/crates/plotnik-lib/src/analyze/type_check/mod.rs b/crates/plotnik-lib/src/analyze/type_check/mod.rs index fda92fb2..fd79a41d 100644 --- a/crates/plotnik-lib/src/analyze/type_check/mod.rs +++ b/crates/plotnik-lib/src/analyze/type_check/mod.rs @@ -9,8 +9,14 @@ mod symbol; pub(crate) mod types; mod unify; +#[cfg(test)] +mod context_tests; +#[cfg(test)] +mod symbol_tests; #[cfg(test)] mod tests; +#[cfg(test)] +mod unify_tests; pub use context::TypeContext; pub use symbol::{DefId, Interner, Symbol}; @@ -25,7 +31,7 @@ use indexmap::IndexMap; use crate::analyze::dependencies::DependencyAnalysis; use crate::analyze::symbol_table::{SymbolTable, UNNAMED_DEF}; use crate::diagnostics::Diagnostics; -use crate::parser::ast::Root; +use crate::parser::Root; use crate::query::source_map::SourceId; /// Run type inference on all definitions. diff --git a/crates/plotnik-lib/src/analyze/type_check/symbol.rs b/crates/plotnik-lib/src/analyze/type_check/symbol.rs index e0fe3a3e..2b90d07d 100644 --- a/crates/plotnik-lib/src/analyze/type_check/symbol.rs +++ b/crates/plotnik-lib/src/analyze/type_check/symbol.rs @@ -23,25 +23,3 @@ impl DefId { self.0 as usize } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn def_id_roundtrip() { - let id = DefId::from_raw(42); - assert_eq!(id.as_u32(), 42); - assert_eq!(id.index(), 42); - } - - #[test] - fn def_id_equality() { - let a = DefId::from_raw(1); - let b = DefId::from_raw(1); - let c = DefId::from_raw(2); - - assert_eq!(a, b); - assert_ne!(a, c); - } -} diff --git a/crates/plotnik-lib/src/analyze/type_check/symbol_tests.rs b/crates/plotnik-lib/src/analyze/type_check/symbol_tests.rs new file mode 100644 index 00000000..86e7d350 --- /dev/null +++ b/crates/plotnik-lib/src/analyze/type_check/symbol_tests.rs @@ -0,0 +1,18 @@ +use super::*; + +#[test] +fn def_id_roundtrip() { + let id = DefId::from_raw(42); + assert_eq!(id.as_u32(), 42); + assert_eq!(id.index(), 42); +} + +#[test] +fn def_id_equality() { + let a = DefId::from_raw(1); + let b = DefId::from_raw(1); + let c = DefId::from_raw(2); + + assert_eq!(a, b); + assert_ne!(a, c); +} diff --git a/crates/plotnik-lib/src/analyze/type_check/unify.rs b/crates/plotnik-lib/src/analyze/type_check/unify.rs index 444e88ec..9d5b3ce5 100644 --- a/crates/plotnik-lib/src/analyze/type_check/unify.rs +++ b/crates/plotnik-lib/src/analyze/type_check/unify.rs @@ -139,70 +139,3 @@ fn unify_type_ids(a: TypeId, b: TypeId, field: Symbol) -> Result { - let fields = ctx.get_struct_fields(id).unwrap(); - assert!(fields.get(&x).unwrap().optional); - } - _ => panic!("expected Bubble"), - } - } - - #[test] - fn unify_bubble_merge() { - let mut ctx = TypeContext::new(); - let mut interner = Interner::new(); - let x = interner.intern("x"); - let y = interner.intern("y"); - - let a_id = ctx.intern_single_field(x, FieldInfo::required(TYPE_NODE)); - - let mut b_fields = BTreeMap::new(); - b_fields.insert(x, FieldInfo::required(TYPE_NODE)); - b_fields.insert(y, FieldInfo::required(TYPE_NODE)); - let b_id = ctx.intern_struct(b_fields); - - let result = unify_flow(&mut ctx, TypeFlow::Bubble(a_id), TypeFlow::Bubble(b_id)).unwrap(); - - match result { - TypeFlow::Bubble(id) => { - let fields = ctx.get_struct_fields(id).unwrap(); - // x is in both, so required - assert!(!fields.get(&x).unwrap().optional); - // y only in b, so optional - assert!(fields.get(&y).unwrap().optional); - } - _ => panic!("expected Bubble"), - } - } - - #[test] - fn unify_scalar_error() { - let mut ctx = TypeContext::new(); - let result = unify_flow(&mut ctx, TypeFlow::Scalar(TYPE_NODE), TypeFlow::Void); - assert!(matches!(result, Err(UnifyError::ScalarInUntagged))); - } -} diff --git a/crates/plotnik-lib/src/analyze/type_check/unify_tests.rs b/crates/plotnik-lib/src/analyze/type_check/unify_tests.rs new file mode 100644 index 00000000..bf1fbef1 --- /dev/null +++ b/crates/plotnik-lib/src/analyze/type_check/unify_tests.rs @@ -0,0 +1,65 @@ +use std::collections::BTreeMap; + +use super::*; +use crate::analyze::type_check::TYPE_NODE; +use plotnik_core::Interner; + +#[test] +fn unify_void_void() { + let mut ctx = TypeContext::new(); + let result = unify_flow(&mut ctx, TypeFlow::Void, TypeFlow::Void); + assert!(matches!(result, Ok(TypeFlow::Void))); +} + +#[test] +fn unify_void_bubble() { + let mut ctx = TypeContext::new(); + let mut interner = Interner::new(); + let x = interner.intern("x"); + let struct_id = ctx.intern_single_field(x, FieldInfo::required(TYPE_NODE)); + + let result = unify_flow(&mut ctx, TypeFlow::Void, TypeFlow::Bubble(struct_id)).unwrap(); + + match result { + TypeFlow::Bubble(id) => { + let fields = ctx.get_struct_fields(id).unwrap(); + assert!(fields.get(&x).unwrap().optional); + } + _ => panic!("expected Bubble"), + } +} + +#[test] +fn unify_bubble_merge() { + let mut ctx = TypeContext::new(); + let mut interner = Interner::new(); + let x = interner.intern("x"); + let y = interner.intern("y"); + + let a_id = ctx.intern_single_field(x, FieldInfo::required(TYPE_NODE)); + + let mut b_fields = BTreeMap::new(); + b_fields.insert(x, FieldInfo::required(TYPE_NODE)); + b_fields.insert(y, FieldInfo::required(TYPE_NODE)); + let b_id = ctx.intern_struct(b_fields); + + let result = unify_flow(&mut ctx, TypeFlow::Bubble(a_id), TypeFlow::Bubble(b_id)).unwrap(); + + match result { + TypeFlow::Bubble(id) => { + let fields = ctx.get_struct_fields(id).unwrap(); + // x is in both, so required + assert!(!fields.get(&x).unwrap().optional); + // y only in b, so optional + assert!(fields.get(&y).unwrap().optional); + } + _ => panic!("expected Bubble"), + } +} + +#[test] +fn unify_scalar_error() { + let mut ctx = TypeContext::new(); + let result = unify_flow(&mut ctx, TypeFlow::Scalar(TYPE_NODE), TypeFlow::Void); + assert!(matches!(result, Err(UnifyError::ScalarInUntagged))); +} diff --git a/crates/plotnik-lib/src/bytecode/effects.rs b/crates/plotnik-lib/src/bytecode/effects.rs index fb98c50b..a78cd13b 100644 --- a/crates/plotnik-lib/src/bytecode/effects.rs +++ b/crates/plotnik-lib/src/bytecode/effects.rs @@ -65,50 +65,3 @@ impl EffectOp { raw.to_le_bytes() } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn roundtrip_with_payload() { - let op = EffectOp { - opcode: EffectOpcode::Set, - payload: 42, - }; - let bytes = op.to_bytes(); - let decoded = EffectOp::from_bytes(bytes); - assert_eq!(decoded.opcode, EffectOpcode::Set); - assert_eq!(decoded.payload, 42); - } - - #[test] - fn roundtrip_no_payload() { - let op = EffectOp { - opcode: EffectOpcode::Node, - payload: 0, - }; - let bytes = op.to_bytes(); - let decoded = EffectOp::from_bytes(bytes); - assert_eq!(decoded.opcode, EffectOpcode::Node); - assert_eq!(decoded.payload, 0); - } - - #[test] - fn max_payload() { - let op = EffectOp { - opcode: EffectOpcode::Enum, - payload: 1023, - }; - let bytes = op.to_bytes(); - let decoded = EffectOp::from_bytes(bytes); - assert_eq!(decoded.payload, 1023); - } - - #[test] - #[should_panic(expected = "invalid effect opcode")] - fn invalid_opcode_panics() { - let bytes = [0xFF, 0xFF]; // opcode would be 63, which is invalid - EffectOp::from_bytes(bytes); - } -} diff --git a/crates/plotnik-lib/src/bytecode/effects_tests.rs b/crates/plotnik-lib/src/bytecode/effects_tests.rs new file mode 100644 index 00000000..720be22c --- /dev/null +++ b/crates/plotnik-lib/src/bytecode/effects_tests.rs @@ -0,0 +1,43 @@ +use super::*; + +#[test] +fn roundtrip_with_payload() { + let op = EffectOp { + opcode: EffectOpcode::Set, + payload: 42, + }; + let bytes = op.to_bytes(); + let decoded = EffectOp::from_bytes(bytes); + assert_eq!(decoded.opcode, EffectOpcode::Set); + assert_eq!(decoded.payload, 42); +} + +#[test] +fn roundtrip_no_payload() { + let op = EffectOp { + opcode: EffectOpcode::Node, + payload: 0, + }; + let bytes = op.to_bytes(); + let decoded = EffectOp::from_bytes(bytes); + assert_eq!(decoded.opcode, EffectOpcode::Node); + assert_eq!(decoded.payload, 0); +} + +#[test] +fn max_payload() { + let op = EffectOp { + opcode: EffectOpcode::Enum, + payload: 1023, + }; + let bytes = op.to_bytes(); + let decoded = EffectOp::from_bytes(bytes); + assert_eq!(decoded.payload, 1023); +} + +#[test] +#[should_panic(expected = "invalid effect opcode")] +fn invalid_opcode_panics() { + let bytes = [0xFF, 0xFF]; // opcode would be 63, which is invalid + EffectOp::from_bytes(bytes); +} diff --git a/crates/plotnik-lib/src/bytecode/entrypoint.rs b/crates/plotnik-lib/src/bytecode/entrypoint.rs index 979cba00..a7e05424 100644 --- a/crates/plotnik-lib/src/bytecode/entrypoint.rs +++ b/crates/plotnik-lib/src/bytecode/entrypoint.rs @@ -17,13 +17,3 @@ pub struct Entrypoint { } const _: () = assert!(std::mem::size_of::() == 8); - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn entrypoint_size() { - assert_eq!(std::mem::size_of::(), 8); - } -} diff --git a/crates/plotnik-lib/src/bytecode/entrypoint_tests.rs b/crates/plotnik-lib/src/bytecode/entrypoint_tests.rs new file mode 100644 index 00000000..df74970e --- /dev/null +++ b/crates/plotnik-lib/src/bytecode/entrypoint_tests.rs @@ -0,0 +1,6 @@ +use super::*; + +#[test] +fn entrypoint_size() { + assert_eq!(std::mem::size_of::(), 8); +} diff --git a/crates/plotnik-lib/src/bytecode/format.rs b/crates/plotnik-lib/src/bytecode/format.rs index 3996eb0c..04d2557b 100644 --- a/crates/plotnik-lib/src/bytecode/format.rs +++ b/crates/plotnik-lib/src/bytecode/format.rs @@ -293,52 +293,3 @@ pub fn format_effect(effect: &EffectOp) -> String { EffectOpcode::SuppressEnd => "SuppressEnd".to_string(), } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_symbol_format() { - assert_eq!(Symbol::EMPTY.format(), " "); - assert_eq!(Symbol::EPSILON.format(), " ε "); - assert_eq!(nav_symbol(Nav::StayExact).format(), " ! "); - assert_eq!(nav_symbol(Nav::Down).format(), " ▽ "); - assert_eq!(nav_symbol(Nav::DownSkip).format(), " !▽ "); - assert_eq!(nav_symbol(Nav::DownExact).format(), "!!▽ "); - assert_eq!(nav_symbol(Nav::Next).format(), " ▷ "); - assert_eq!(nav_symbol(Nav::NextSkip).format(), " !▷ "); - assert_eq!(nav_symbol(Nav::NextExact).format(), "!!▷ "); - assert_eq!(nav_symbol(Nav::Up(1)).format(), " △ "); - assert_eq!(nav_symbol(Nav::Up(2)).format(), " △² "); - assert_eq!(nav_symbol(Nav::UpSkipTrivia(1)).format(), " !△ "); - assert_eq!(nav_symbol(Nav::UpExact(1)).format(), "!!△ "); - } - - #[test] - fn test_width_for_count() { - assert_eq!(width_for_count(0), 1); - assert_eq!(width_for_count(1), 1); - assert_eq!(width_for_count(10), 1); - assert_eq!(width_for_count(11), 2); - assert_eq!(width_for_count(100), 2); - assert_eq!(width_for_count(101), 3); - } - - #[test] - fn test_truncate_text() { - assert_eq!(truncate_text("hello", 10), "hello"); - assert_eq!(truncate_text("hello world", 10), "hello wor…"); - assert_eq!(truncate_text("abc", 3), "abc"); - assert_eq!(truncate_text("abcd", 3), "ab…"); - } - - #[test] - fn test_superscript() { - assert_eq!(superscript(0), "⁰"); - assert_eq!(superscript(1), "¹"); - assert_eq!(superscript(9), "⁹"); - assert_eq!(superscript(10), "¹⁰"); - assert_eq!(superscript(12), "¹²"); - } -} diff --git a/crates/plotnik-lib/src/bytecode/format_tests.rs b/crates/plotnik-lib/src/bytecode/format_tests.rs new file mode 100644 index 00000000..5258f861 --- /dev/null +++ b/crates/plotnik-lib/src/bytecode/format_tests.rs @@ -0,0 +1,45 @@ +use super::*; + +#[test] +fn test_symbol_format() { + assert_eq!(Symbol::EMPTY.format(), " "); + assert_eq!(Symbol::EPSILON.format(), " ε "); + assert_eq!(nav_symbol(Nav::StayExact).format(), " ! "); + assert_eq!(nav_symbol(Nav::Down).format(), " ▽ "); + assert_eq!(nav_symbol(Nav::DownSkip).format(), " !▽ "); + assert_eq!(nav_symbol(Nav::DownExact).format(), "!!▽ "); + assert_eq!(nav_symbol(Nav::Next).format(), " ▷ "); + assert_eq!(nav_symbol(Nav::NextSkip).format(), " !▷ "); + assert_eq!(nav_symbol(Nav::NextExact).format(), "!!▷ "); + assert_eq!(nav_symbol(Nav::Up(1)).format(), " △ "); + assert_eq!(nav_symbol(Nav::Up(2)).format(), " △² "); + assert_eq!(nav_symbol(Nav::UpSkipTrivia(1)).format(), " !△ "); + assert_eq!(nav_symbol(Nav::UpExact(1)).format(), "!!△ "); +} + +#[test] +fn test_width_for_count() { + assert_eq!(width_for_count(0), 1); + assert_eq!(width_for_count(1), 1); + assert_eq!(width_for_count(10), 1); + assert_eq!(width_for_count(11), 2); + assert_eq!(width_for_count(100), 2); + assert_eq!(width_for_count(101), 3); +} + +#[test] +fn test_truncate_text() { + assert_eq!(truncate_text("hello", 10), "hello"); + assert_eq!(truncate_text("hello world", 10), "hello wor…"); + assert_eq!(truncate_text("abc", 3), "abc"); + assert_eq!(truncate_text("abcd", 3), "ab…"); +} + +#[test] +fn test_superscript() { + assert_eq!(superscript(0), "⁰"); + assert_eq!(superscript(1), "¹"); + assert_eq!(superscript(9), "⁹"); + assert_eq!(superscript(10), "¹⁰"); + assert_eq!(superscript(12), "¹²"); +} diff --git a/crates/plotnik-lib/src/bytecode/header.rs b/crates/plotnik-lib/src/bytecode/header.rs index 8b9686df..ccdd8919 100644 --- a/crates/plotnik-lib/src/bytecode/header.rs +++ b/crates/plotnik-lib/src/bytecode/header.rs @@ -154,78 +154,3 @@ impl Header { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn header_size() { - assert_eq!(std::mem::size_of::
(), 64); - } - - #[test] - fn header_default() { - let h = Header::default(); - assert!(h.validate_magic()); - assert!(h.validate_version()); - assert_eq!(h.total_size, 0); - } - - #[test] - fn header_roundtrip() { - let h = Header { - magic: MAGIC, - version: VERSION, - checksum: 0x12345678, - total_size: 1024, - str_blob_offset: 64, - str_table_offset: 128, - node_types_offset: 192, - node_fields_offset: 256, - trivia_offset: 320, - type_meta_offset: 384, - entrypoints_offset: 448, - transitions_offset: 512, - str_table_count: 10, - node_types_count: 20, - node_fields_count: 5, - trivia_count: 2, - entrypoints_count: 1, - transitions_count: 15, - ..Default::default() - }; - - let bytes = h.to_bytes(); - assert_eq!(bytes.len(), 64); - - let decoded = Header::from_bytes(&bytes); - assert_eq!(decoded, h); - } - - #[test] - fn header_linked_flag() { - let mut h = Header::default(); - assert!(!h.is_linked()); - - h.set_linked(true); - assert!(h.is_linked()); - assert_eq!(h.flags, flags::LINKED); - - h.set_linked(false); - assert!(!h.is_linked()); - assert_eq!(h.flags, 0); - } - - #[test] - fn header_flags_roundtrip() { - let mut h = Header::default(); - h.set_linked(true); - - let bytes = h.to_bytes(); - let decoded = Header::from_bytes(&bytes); - - assert!(decoded.is_linked()); - assert_eq!(decoded.flags, flags::LINKED); - } -} diff --git a/crates/plotnik-lib/src/bytecode/header_tests.rs b/crates/plotnik-lib/src/bytecode/header_tests.rs new file mode 100644 index 00000000..3e65675f --- /dev/null +++ b/crates/plotnik-lib/src/bytecode/header_tests.rs @@ -0,0 +1,71 @@ +use super::*; + +#[test] +fn header_size() { + assert_eq!(std::mem::size_of::
(), 64); +} + +#[test] +fn header_default() { + let h = Header::default(); + assert!(h.validate_magic()); + assert!(h.validate_version()); + assert_eq!(h.total_size, 0); +} + +#[test] +fn header_roundtrip() { + let h = Header { + magic: MAGIC, + version: VERSION, + checksum: 0x12345678, + total_size: 1024, + str_blob_offset: 64, + str_table_offset: 128, + node_types_offset: 192, + node_fields_offset: 256, + trivia_offset: 320, + type_meta_offset: 384, + entrypoints_offset: 448, + transitions_offset: 512, + str_table_count: 10, + node_types_count: 20, + node_fields_count: 5, + trivia_count: 2, + entrypoints_count: 1, + transitions_count: 15, + ..Default::default() + }; + + let bytes = h.to_bytes(); + assert_eq!(bytes.len(), 64); + + let decoded = Header::from_bytes(&bytes); + assert_eq!(decoded, h); +} + +#[test] +fn header_linked_flag() { + let mut h = Header::default(); + assert!(!h.is_linked()); + + h.set_linked(true); + assert!(h.is_linked()); + assert_eq!(h.flags, flags::LINKED); + + h.set_linked(false); + assert!(!h.is_linked()); + assert_eq!(h.flags, 0); +} + +#[test] +fn header_flags_roundtrip() { + let mut h = Header::default(); + h.set_linked(true); + + let bytes = h.to_bytes(); + let decoded = Header::from_bytes(&bytes); + + assert!(decoded.is_linked()); + assert_eq!(decoded.flags, flags::LINKED); +} diff --git a/crates/plotnik-lib/src/bytecode/mod.rs b/crates/plotnik-lib/src/bytecode/mod.rs index 828f8556..8c88e1e4 100644 --- a/crates/plotnik-lib/src/bytecode/mod.rs +++ b/crates/plotnik-lib/src/bytecode/mod.rs @@ -56,9 +56,23 @@ pub use ir::{ TrampolineIR, }; +#[cfg(test)] +mod effects_tests; +#[cfg(test)] +mod entrypoint_tests; +#[cfg(test)] +mod format_tests; +#[cfg(test)] +mod header_tests; #[cfg(test)] mod instructions_tests; #[cfg(test)] mod ir_tests; #[cfg(test)] mod module_tests; +#[cfg(test)] +mod nav_tests; +#[cfg(test)] +mod sections_tests; +#[cfg(test)] +mod type_meta_tests; diff --git a/crates/plotnik-lib/src/bytecode/nav.rs b/crates/plotnik-lib/src/bytecode/nav.rs index b9bb80f3..2ab8c4d9 100644 --- a/crates/plotnik-lib/src/bytecode/nav.rs +++ b/crates/plotnik-lib/src/bytecode/nav.rs @@ -100,58 +100,3 @@ impl Nav { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn nav_standard_roundtrip() { - for nav in [ - Nav::Stay, - Nav::StayExact, - Nav::Next, - Nav::NextSkip, - Nav::NextExact, - Nav::Down, - Nav::DownSkip, - Nav::DownExact, - ] { - assert_eq!(Nav::from_byte(nav.to_byte()), nav); - } - } - - #[test] - fn nav_up_roundtrip() { - let nav = Nav::Up(5); - assert_eq!(Nav::from_byte(nav.to_byte()), nav); - - let nav = Nav::UpSkipTrivia(10); - assert_eq!(Nav::from_byte(nav.to_byte()), nav); - - let nav = Nav::UpExact(63); - assert_eq!(Nav::from_byte(nav.to_byte()), nav); - } - - #[test] - fn nav_byte_encoding() { - assert_eq!(Nav::Stay.to_byte(), 0b00_000000); - assert_eq!(Nav::StayExact.to_byte(), 0b00_000001); - assert_eq!(Nav::Down.to_byte(), 0b00_000101); - assert_eq!(Nav::Up(5).to_byte(), 0b01_000101); - assert_eq!(Nav::UpSkipTrivia(3).to_byte(), 0b10_000011); - assert_eq!(Nav::UpExact(1).to_byte(), 0b11_000001); - } - - #[test] - #[should_panic(expected = "invalid nav standard")] - fn nav_invalid_standard_panics() { - Nav::from_byte(0b00_111111); - } - - #[test] - #[should_panic(expected = "invalid nav up level")] - fn nav_invalid_up_zero_panics() { - Nav::from_byte(0b01_000000); - } -} diff --git a/crates/plotnik-lib/src/bytecode/nav_tests.rs b/crates/plotnik-lib/src/bytecode/nav_tests.rs new file mode 100644 index 00000000..10fe2f3c --- /dev/null +++ b/crates/plotnik-lib/src/bytecode/nav_tests.rs @@ -0,0 +1,51 @@ +use super::*; + +#[test] +fn nav_standard_roundtrip() { + for nav in [ + Nav::Stay, + Nav::StayExact, + Nav::Next, + Nav::NextSkip, + Nav::NextExact, + Nav::Down, + Nav::DownSkip, + Nav::DownExact, + ] { + assert_eq!(Nav::from_byte(nav.to_byte()), nav); + } +} + +#[test] +fn nav_up_roundtrip() { + let nav = Nav::Up(5); + assert_eq!(Nav::from_byte(nav.to_byte()), nav); + + let nav = Nav::UpSkipTrivia(10); + assert_eq!(Nav::from_byte(nav.to_byte()), nav); + + let nav = Nav::UpExact(63); + assert_eq!(Nav::from_byte(nav.to_byte()), nav); +} + +#[test] +fn nav_byte_encoding() { + assert_eq!(Nav::Stay.to_byte(), 0b00_000000); + assert_eq!(Nav::StayExact.to_byte(), 0b00_000001); + assert_eq!(Nav::Down.to_byte(), 0b00_000101); + assert_eq!(Nav::Up(5).to_byte(), 0b01_000101); + assert_eq!(Nav::UpSkipTrivia(3).to_byte(), 0b10_000011); + assert_eq!(Nav::UpExact(1).to_byte(), 0b11_000001); +} + +#[test] +#[should_panic(expected = "invalid nav standard")] +fn nav_invalid_standard_panics() { + Nav::from_byte(0b00_111111); +} + +#[test] +#[should_panic(expected = "invalid nav up level")] +fn nav_invalid_up_zero_panics() { + Nav::from_byte(0b01_000000); +} diff --git a/crates/plotnik-lib/src/bytecode/sections.rs b/crates/plotnik-lib/src/bytecode/sections.rs index 3d84ade1..0fff5f40 100644 --- a/crates/plotnik-lib/src/bytecode/sections.rs +++ b/crates/plotnik-lib/src/bytecode/sections.rs @@ -48,14 +48,3 @@ pub struct FieldSymbol { pub struct TriviaEntry { pub node_type: u16, } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn slice_range() { - let slice = Slice { ptr: 5, len: 3 }; - assert_eq!(slice.range(), 5..8); - } -} diff --git a/crates/plotnik-lib/src/bytecode/sections_tests.rs b/crates/plotnik-lib/src/bytecode/sections_tests.rs new file mode 100644 index 00000000..fe2a3b01 --- /dev/null +++ b/crates/plotnik-lib/src/bytecode/sections_tests.rs @@ -0,0 +1,7 @@ +use super::*; + +#[test] +fn slice_range() { + let slice = Slice { ptr: 5, len: 3 }; + assert_eq!(slice.range(), 5..8); +} diff --git a/crates/plotnik-lib/src/bytecode/type_meta.rs b/crates/plotnik-lib/src/bytecode/type_meta.rs index 3a39aaf2..904f50d9 100644 --- a/crates/plotnik-lib/src/bytecode/type_meta.rs +++ b/crates/plotnik-lib/src/bytecode/type_meta.rs @@ -152,57 +152,3 @@ pub struct TypeMember { } const _: () = assert!(std::mem::size_of::() == 4); - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn type_meta_header_size() { - assert_eq!(std::mem::size_of::(), 8); - } - - #[test] - fn type_meta_header_roundtrip() { - let header = TypeMetaHeader { - type_defs_count: 42, - type_members_count: 100, - type_names_count: 5, - ..Default::default() - }; - let bytes = header.to_bytes(); - let decoded = TypeMetaHeader::from_bytes(&bytes); - assert_eq!(decoded, header); - } - - #[test] - fn type_def_size() { - assert_eq!(std::mem::size_of::(), 4); - } - - #[test] - fn type_member_size() { - assert_eq!(std::mem::size_of::(), 4); - } - - #[test] - fn type_name_size() { - assert_eq!(std::mem::size_of::(), 4); - } - - #[test] - fn type_kind_is_wrapper() { - assert!(TypeKind::Optional.is_wrapper()); - assert!(TypeKind::ArrayZeroOrMore.is_wrapper()); - assert!(TypeKind::ArrayOneOrMore.is_wrapper()); - assert!(!TypeKind::Struct.is_wrapper()); - assert!(!TypeKind::Enum.is_wrapper()); - } - - #[test] - fn type_kind_aliases() { - // Test bytecode-friendly aliases - assert_eq!(TypeKind::ARRAY_STAR, TypeKind::ArrayZeroOrMore); - assert_eq!(TypeKind::ARRAY_PLUS, TypeKind::ArrayOneOrMore); - } -} diff --git a/crates/plotnik-lib/src/bytecode/type_meta_tests.rs b/crates/plotnik-lib/src/bytecode/type_meta_tests.rs new file mode 100644 index 00000000..d289233f --- /dev/null +++ b/crates/plotnik-lib/src/bytecode/type_meta_tests.rs @@ -0,0 +1,50 @@ +use super::*; + +#[test] +fn type_meta_header_size() { + assert_eq!(std::mem::size_of::(), 8); +} + +#[test] +fn type_meta_header_roundtrip() { + let header = TypeMetaHeader { + type_defs_count: 42, + type_members_count: 100, + type_names_count: 5, + ..Default::default() + }; + let bytes = header.to_bytes(); + let decoded = TypeMetaHeader::from_bytes(&bytes); + assert_eq!(decoded, header); +} + +#[test] +fn type_def_size() { + assert_eq!(std::mem::size_of::(), 4); +} + +#[test] +fn type_member_size() { + assert_eq!(std::mem::size_of::(), 4); +} + +#[test] +fn type_name_size() { + assert_eq!(std::mem::size_of::(), 4); +} + +#[test] +fn type_kind_is_wrapper() { + assert!(TypeKind::Optional.is_wrapper()); + assert!(TypeKind::ArrayZeroOrMore.is_wrapper()); + assert!(TypeKind::ArrayOneOrMore.is_wrapper()); + assert!(!TypeKind::Struct.is_wrapper()); + assert!(!TypeKind::Enum.is_wrapper()); +} + +#[test] +fn type_kind_aliases() { + // Test bytecode-friendly aliases + assert_eq!(TypeKind::ARRAY_STAR, TypeKind::ArrayZeroOrMore); + assert_eq!(TypeKind::ARRAY_PLUS, TypeKind::ArrayOneOrMore); +} diff --git a/crates/plotnik-lib/src/query/mod.rs b/crates/plotnik-lib/src/query/mod.rs index ea209151..f0b47dbf 100644 --- a/crates/plotnik-lib/src/query/mod.rs +++ b/crates/plotnik-lib/src/query/mod.rs @@ -16,3 +16,5 @@ pub use crate::analyze::SymbolTable; mod printer_tests; #[cfg(test)] mod query_tests; +#[cfg(test)] +mod source_map_tests; diff --git a/crates/plotnik-lib/src/query/source_map.rs b/crates/plotnik-lib/src/query/source_map.rs index bda95f7b..3c95eefa 100644 --- a/crates/plotnik-lib/src/query/source_map.rs +++ b/crates/plotnik-lib/src/query/source_map.rs @@ -5,7 +5,7 @@ /// Lightweight handle to a source in a compilation session. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)] -pub struct SourceId(u32); +pub struct SourceId(pub(crate) u32); /// Describes the origin of a source. #[derive(Clone, PartialEq, Eq, Debug)] @@ -148,113 +148,3 @@ impl SourceMap { id } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn single_one_liner() { - let map = SourceMap::one_liner("hello world"); - let id = SourceId(0); - - assert_eq!(map.content(id), "hello world"); - assert_eq!(map.kind(id), &SourceKind::OneLiner); - assert_eq!(map.len(), 1); - } - - #[test] - fn stdin_source() { - let mut map = SourceMap::new(); - let id = map.add_stdin("from stdin"); - - assert_eq!(map.content(id), "from stdin"); - assert_eq!(map.kind(id), &SourceKind::Stdin); - } - - #[test] - fn file_source() { - let mut map = SourceMap::new(); - let id = map.add_file("main.ptk", "Foo = (bar)"); - - assert_eq!(map.content(id), "Foo = (bar)"); - assert_eq!(map.kind(id), &SourceKind::File("main.ptk".to_owned())); - } - - #[test] - fn multiple_sources() { - let mut map = SourceMap::new(); - let a = map.add_file("a.ptk", "content a"); - let b = map.add_file("b.ptk", "content b"); - let c = map.add_one_liner("inline"); - let d = map.add_stdin("piped"); - - assert_eq!(map.len(), 4); - assert_eq!(map.content(a), "content a"); - assert_eq!(map.content(b), "content b"); - assert_eq!(map.content(c), "inline"); - assert_eq!(map.content(d), "piped"); - - assert_eq!(map.kind(a), &SourceKind::File("a.ptk".to_owned())); - assert_eq!(map.kind(b), &SourceKind::File("b.ptk".to_owned())); - assert_eq!(map.kind(c), &SourceKind::OneLiner); - assert_eq!(map.kind(d), &SourceKind::Stdin); - } - - #[test] - fn iteration() { - let mut map = SourceMap::new(); - map.add_file("a.ptk", "aaa"); - map.add_one_liner("bbb"); - - let items: Vec<_> = map.iter().collect(); - assert_eq!(items.len(), 2); - assert_eq!(items[0].id, SourceId(0)); - assert_eq!(items[0].kind, &SourceKind::File("a.ptk".to_owned())); - assert_eq!(items[0].content, "aaa"); - assert_eq!(items[1].id, SourceId(1)); - assert_eq!(items[1].kind, &SourceKind::OneLiner); - assert_eq!(items[1].content, "bbb"); - } - - #[test] - fn get_source() { - let mut map = SourceMap::new(); - let id = map.add_file("test.ptk", "hello"); - - let source = map.get(id); - assert_eq!(source.id, id); - assert_eq!(source.kind, &SourceKind::File("test.ptk".to_owned())); - assert_eq!(source.content, "hello"); - assert_eq!(source.as_str(), "hello"); - } - - #[test] - fn display_name() { - assert_eq!(SourceKind::OneLiner.display_name(), ""); - assert_eq!(SourceKind::Stdin.display_name(), ""); - assert_eq!( - SourceKind::File("foo.ptk".to_owned()).display_name(), - "foo.ptk" - ); - } - - #[test] - #[should_panic(expected = "invalid SourceId")] - fn invalid_id_panics() { - let map = SourceMap::new(); - let _ = map.content(SourceId(999)); - } - - #[test] - fn multiple_stdin_sources() { - let mut map = SourceMap::new(); - let a = map.add_stdin("first stdin"); - let b = map.add_stdin("second stdin"); - - assert_eq!(map.content(a), "first stdin"); - assert_eq!(map.content(b), "second stdin"); - assert_eq!(map.kind(a), &SourceKind::Stdin); - assert_eq!(map.kind(b), &SourceKind::Stdin); - } -} diff --git a/crates/plotnik-lib/src/query/source_map_tests.rs b/crates/plotnik-lib/src/query/source_map_tests.rs new file mode 100644 index 00000000..adab45da --- /dev/null +++ b/crates/plotnik-lib/src/query/source_map_tests.rs @@ -0,0 +1,106 @@ +use super::source_map::{SourceId, SourceKind, SourceMap}; + +#[test] +fn single_one_liner() { + let map = SourceMap::one_liner("hello world"); + let id = SourceId(0); + + assert_eq!(map.content(id), "hello world"); + assert_eq!(map.kind(id), &SourceKind::OneLiner); + assert_eq!(map.len(), 1); +} + +#[test] +fn stdin_source() { + let mut map = SourceMap::new(); + let id = map.add_stdin("from stdin"); + + assert_eq!(map.content(id), "from stdin"); + assert_eq!(map.kind(id), &SourceKind::Stdin); +} + +#[test] +fn file_source() { + let mut map = SourceMap::new(); + let id = map.add_file("main.ptk", "Foo = (bar)"); + + assert_eq!(map.content(id), "Foo = (bar)"); + assert_eq!(map.kind(id), &SourceKind::File("main.ptk".to_owned())); +} + +#[test] +fn multiple_sources() { + let mut map = SourceMap::new(); + let a = map.add_file("a.ptk", "content a"); + let b = map.add_file("b.ptk", "content b"); + let c = map.add_one_liner("inline"); + let d = map.add_stdin("piped"); + + assert_eq!(map.len(), 4); + assert_eq!(map.content(a), "content a"); + assert_eq!(map.content(b), "content b"); + assert_eq!(map.content(c), "inline"); + assert_eq!(map.content(d), "piped"); + + assert_eq!(map.kind(a), &SourceKind::File("a.ptk".to_owned())); + assert_eq!(map.kind(b), &SourceKind::File("b.ptk".to_owned())); + assert_eq!(map.kind(c), &SourceKind::OneLiner); + assert_eq!(map.kind(d), &SourceKind::Stdin); +} + +#[test] +fn iteration() { + let mut map = SourceMap::new(); + map.add_file("a.ptk", "aaa"); + map.add_one_liner("bbb"); + + let items: Vec<_> = map.iter().collect(); + assert_eq!(items.len(), 2); + assert_eq!(items[0].id, SourceId(0)); + assert_eq!(items[0].kind, &SourceKind::File("a.ptk".to_owned())); + assert_eq!(items[0].content, "aaa"); + assert_eq!(items[1].id, SourceId(1)); + assert_eq!(items[1].kind, &SourceKind::OneLiner); + assert_eq!(items[1].content, "bbb"); +} + +#[test] +fn get_source() { + let mut map = SourceMap::new(); + let id = map.add_file("test.ptk", "hello"); + + let source = map.get(id); + assert_eq!(source.id, id); + assert_eq!(source.kind, &SourceKind::File("test.ptk".to_owned())); + assert_eq!(source.content, "hello"); + assert_eq!(source.as_str(), "hello"); +} + +#[test] +fn display_name() { + assert_eq!(SourceKind::OneLiner.display_name(), ""); + assert_eq!(SourceKind::Stdin.display_name(), ""); + assert_eq!( + SourceKind::File("foo.ptk".to_owned()).display_name(), + "foo.ptk" + ); +} + +#[test] +#[should_panic(expected = "invalid SourceId")] +fn invalid_id_panics() { + let map = SourceMap::new(); + let _ = map.content(SourceId(999)); +} + +#[test] +fn multiple_stdin_sources() { + let mut map = SourceMap::new(); + let a = map.add_stdin("first stdin"); + let b = map.add_stdin("second stdin"); + + assert_eq!(map.content(a), "first stdin"); + assert_eq!(map.content(b), "second stdin"); + assert_eq!(map.kind(a), &SourceKind::Stdin); + assert_eq!(map.kind(b), &SourceKind::Stdin); +} diff --git a/crates/plotnik-lib/src/type_system/arity.rs b/crates/plotnik-lib/src/type_system/arity.rs index 029ba90a..61886382 100644 --- a/crates/plotnik-lib/src/type_system/arity.rs +++ b/crates/plotnik-lib/src/type_system/arity.rs @@ -33,24 +33,3 @@ impl Arity { self == Self::Many } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn combine_arities() { - assert_eq!(Arity::One.combine(Arity::One), Arity::One); - assert_eq!(Arity::One.combine(Arity::Many), Arity::Many); - assert_eq!(Arity::Many.combine(Arity::One), Arity::Many); - assert_eq!(Arity::Many.combine(Arity::Many), Arity::Many); - } - - #[test] - fn is_one_and_many() { - assert!(Arity::One.is_one()); - assert!(!Arity::One.is_many()); - assert!(!Arity::Many.is_one()); - assert!(Arity::Many.is_many()); - } -} diff --git a/crates/plotnik-lib/src/type_system/arity_tests.rs b/crates/plotnik-lib/src/type_system/arity_tests.rs new file mode 100644 index 00000000..b5c7a4eb --- /dev/null +++ b/crates/plotnik-lib/src/type_system/arity_tests.rs @@ -0,0 +1,17 @@ +use super::*; + +#[test] +fn combine_arities() { + assert_eq!(Arity::One.combine(Arity::One), Arity::One); + assert_eq!(Arity::One.combine(Arity::Many), Arity::Many); + assert_eq!(Arity::Many.combine(Arity::One), Arity::Many); + assert_eq!(Arity::Many.combine(Arity::Many), Arity::Many); +} + +#[test] +fn is_one_and_many() { + assert!(Arity::One.is_one()); + assert!(!Arity::One.is_many()); + assert!(!Arity::Many.is_one()); + assert!(Arity::Many.is_many()); +} diff --git a/crates/plotnik-lib/src/type_system/kind.rs b/crates/plotnik-lib/src/type_system/kind.rs index 398a8f07..cf4418f0 100644 --- a/crates/plotnik-lib/src/type_system/kind.rs +++ b/crates/plotnik-lib/src/type_system/kind.rs @@ -93,92 +93,3 @@ impl TypeKind { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn from_u8_valid() { - assert_eq!(TypeKind::from_u8(0), Some(TypeKind::Void)); - assert_eq!(TypeKind::from_u8(1), Some(TypeKind::Node)); - assert_eq!(TypeKind::from_u8(2), Some(TypeKind::String)); - assert_eq!(TypeKind::from_u8(3), Some(TypeKind::Optional)); - assert_eq!(TypeKind::from_u8(4), Some(TypeKind::ArrayZeroOrMore)); - assert_eq!(TypeKind::from_u8(5), Some(TypeKind::ArrayOneOrMore)); - assert_eq!(TypeKind::from_u8(6), Some(TypeKind::Struct)); - assert_eq!(TypeKind::from_u8(7), Some(TypeKind::Enum)); - assert_eq!(TypeKind::from_u8(8), Some(TypeKind::Alias)); - } - - #[test] - fn from_u8_invalid() { - assert_eq!(TypeKind::from_u8(9), None); - assert_eq!(TypeKind::from_u8(255), None); - } - - #[test] - fn is_primitive() { - assert!(TypeKind::Void.is_primitive()); - assert!(TypeKind::Node.is_primitive()); - assert!(TypeKind::String.is_primitive()); - assert!(!TypeKind::Optional.is_primitive()); - assert!(!TypeKind::Struct.is_primitive()); - } - - #[test] - fn is_wrapper() { - assert!(TypeKind::Optional.is_wrapper()); - assert!(TypeKind::ArrayZeroOrMore.is_wrapper()); - assert!(TypeKind::ArrayOneOrMore.is_wrapper()); - assert!(!TypeKind::Struct.is_wrapper()); - assert!(!TypeKind::Enum.is_wrapper()); - assert!(!TypeKind::Alias.is_wrapper()); - assert!(!TypeKind::Void.is_wrapper()); - } - - #[test] - fn is_composite() { - assert!(!TypeKind::Optional.is_composite()); - assert!(!TypeKind::ArrayZeroOrMore.is_composite()); - assert!(!TypeKind::ArrayOneOrMore.is_composite()); - assert!(TypeKind::Struct.is_composite()); - assert!(TypeKind::Enum.is_composite()); - assert!(!TypeKind::Alias.is_composite()); - assert!(!TypeKind::Node.is_composite()); - } - - #[test] - fn is_array() { - assert!(!TypeKind::Optional.is_array()); - assert!(TypeKind::ArrayZeroOrMore.is_array()); - assert!(TypeKind::ArrayOneOrMore.is_array()); - assert!(!TypeKind::Struct.is_array()); - assert!(!TypeKind::Enum.is_array()); - assert!(!TypeKind::Alias.is_array()); - } - - #[test] - fn array_is_non_empty() { - assert!(!TypeKind::ArrayZeroOrMore.array_is_non_empty()); - assert!(TypeKind::ArrayOneOrMore.array_is_non_empty()); - } - - #[test] - fn is_alias() { - assert!(!TypeKind::Optional.is_alias()); - assert!(!TypeKind::ArrayZeroOrMore.is_alias()); - assert!(!TypeKind::ArrayOneOrMore.is_alias()); - assert!(!TypeKind::Struct.is_alias()); - assert!(!TypeKind::Enum.is_alias()); - assert!(TypeKind::Alias.is_alias()); - } - - #[test] - fn primitive_name() { - assert_eq!(TypeKind::Void.primitive_name(), Some("Void")); - assert_eq!(TypeKind::Node.primitive_name(), Some("Node")); - assert_eq!(TypeKind::String.primitive_name(), Some("String")); - assert_eq!(TypeKind::Struct.primitive_name(), None); - } -} diff --git a/crates/plotnik-lib/src/type_system/kind_tests.rs b/crates/plotnik-lib/src/type_system/kind_tests.rs new file mode 100644 index 00000000..34ee7f02 --- /dev/null +++ b/crates/plotnik-lib/src/type_system/kind_tests.rs @@ -0,0 +1,85 @@ +use super::*; + +#[test] +fn from_u8_valid() { + assert_eq!(TypeKind::from_u8(0), Some(TypeKind::Void)); + assert_eq!(TypeKind::from_u8(1), Some(TypeKind::Node)); + assert_eq!(TypeKind::from_u8(2), Some(TypeKind::String)); + assert_eq!(TypeKind::from_u8(3), Some(TypeKind::Optional)); + assert_eq!(TypeKind::from_u8(4), Some(TypeKind::ArrayZeroOrMore)); + assert_eq!(TypeKind::from_u8(5), Some(TypeKind::ArrayOneOrMore)); + assert_eq!(TypeKind::from_u8(6), Some(TypeKind::Struct)); + assert_eq!(TypeKind::from_u8(7), Some(TypeKind::Enum)); + assert_eq!(TypeKind::from_u8(8), Some(TypeKind::Alias)); +} + +#[test] +fn from_u8_invalid() { + assert_eq!(TypeKind::from_u8(9), None); + assert_eq!(TypeKind::from_u8(255), None); +} + +#[test] +fn is_primitive() { + assert!(TypeKind::Void.is_primitive()); + assert!(TypeKind::Node.is_primitive()); + assert!(TypeKind::String.is_primitive()); + assert!(!TypeKind::Optional.is_primitive()); + assert!(!TypeKind::Struct.is_primitive()); +} + +#[test] +fn is_wrapper() { + assert!(TypeKind::Optional.is_wrapper()); + assert!(TypeKind::ArrayZeroOrMore.is_wrapper()); + assert!(TypeKind::ArrayOneOrMore.is_wrapper()); + assert!(!TypeKind::Struct.is_wrapper()); + assert!(!TypeKind::Enum.is_wrapper()); + assert!(!TypeKind::Alias.is_wrapper()); + assert!(!TypeKind::Void.is_wrapper()); +} + +#[test] +fn is_composite() { + assert!(!TypeKind::Optional.is_composite()); + assert!(!TypeKind::ArrayZeroOrMore.is_composite()); + assert!(!TypeKind::ArrayOneOrMore.is_composite()); + assert!(TypeKind::Struct.is_composite()); + assert!(TypeKind::Enum.is_composite()); + assert!(!TypeKind::Alias.is_composite()); + assert!(!TypeKind::Node.is_composite()); +} + +#[test] +fn is_array() { + assert!(!TypeKind::Optional.is_array()); + assert!(TypeKind::ArrayZeroOrMore.is_array()); + assert!(TypeKind::ArrayOneOrMore.is_array()); + assert!(!TypeKind::Struct.is_array()); + assert!(!TypeKind::Enum.is_array()); + assert!(!TypeKind::Alias.is_array()); +} + +#[test] +fn array_is_non_empty() { + assert!(!TypeKind::ArrayZeroOrMore.array_is_non_empty()); + assert!(TypeKind::ArrayOneOrMore.array_is_non_empty()); +} + +#[test] +fn is_alias() { + assert!(!TypeKind::Optional.is_alias()); + assert!(!TypeKind::ArrayZeroOrMore.is_alias()); + assert!(!TypeKind::ArrayOneOrMore.is_alias()); + assert!(!TypeKind::Struct.is_alias()); + assert!(!TypeKind::Enum.is_alias()); + assert!(TypeKind::Alias.is_alias()); +} + +#[test] +fn primitive_name() { + assert_eq!(TypeKind::Void.primitive_name(), Some("Void")); + assert_eq!(TypeKind::Node.primitive_name(), Some("Node")); + assert_eq!(TypeKind::String.primitive_name(), Some("String")); + assert_eq!(TypeKind::Struct.primitive_name(), None); +} diff --git a/crates/plotnik-lib/src/type_system/mod.rs b/crates/plotnik-lib/src/type_system/mod.rs index d64a746b..39af1b28 100644 --- a/crates/plotnik-lib/src/type_system/mod.rs +++ b/crates/plotnik-lib/src/type_system/mod.rs @@ -10,6 +10,15 @@ mod kind; mod primitives; mod quantifier; +#[cfg(test)] +mod arity_tests; +#[cfg(test)] +mod kind_tests; +#[cfg(test)] +mod primitives_tests; +#[cfg(test)] +mod quantifier_tests; + pub use arity::Arity; pub use kind::TypeKind; pub use primitives::{PrimitiveType, TYPE_CUSTOM_START, TYPE_NODE, TYPE_STRING, TYPE_VOID}; diff --git a/crates/plotnik-lib/src/type_system/primitives.rs b/crates/plotnik-lib/src/type_system/primitives.rs index 1a6f747d..c5f7ff19 100644 --- a/crates/plotnik-lib/src/type_system/primitives.rs +++ b/crates/plotnik-lib/src/type_system/primitives.rs @@ -63,32 +63,3 @@ impl PrimitiveType { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn primitive_indices() { - assert_eq!(PrimitiveType::Void.index(), 0); - assert_eq!(PrimitiveType::Node.index(), 1); - assert_eq!(PrimitiveType::String.index(), 2); - } - - #[test] - fn from_index() { - assert_eq!(PrimitiveType::from_index(0), Some(PrimitiveType::Void)); - assert_eq!(PrimitiveType::from_index(1), Some(PrimitiveType::Node)); - assert_eq!(PrimitiveType::from_index(2), Some(PrimitiveType::String)); - assert_eq!(PrimitiveType::from_index(3), None); - } - - #[test] - fn is_builtin() { - assert!(PrimitiveType::is_builtin(0)); - assert!(PrimitiveType::is_builtin(1)); - assert!(PrimitiveType::is_builtin(2)); - assert!(!PrimitiveType::is_builtin(3)); - assert!(!PrimitiveType::is_builtin(100)); - } -} diff --git a/crates/plotnik-lib/src/type_system/primitives_tests.rs b/crates/plotnik-lib/src/type_system/primitives_tests.rs new file mode 100644 index 00000000..3b29bb53 --- /dev/null +++ b/crates/plotnik-lib/src/type_system/primitives_tests.rs @@ -0,0 +1,25 @@ +use super::*; + +#[test] +fn primitive_indices() { + assert_eq!(PrimitiveType::Void.index(), 0); + assert_eq!(PrimitiveType::Node.index(), 1); + assert_eq!(PrimitiveType::String.index(), 2); +} + +#[test] +fn from_index() { + assert_eq!(PrimitiveType::from_index(0), Some(PrimitiveType::Void)); + assert_eq!(PrimitiveType::from_index(1), Some(PrimitiveType::Node)); + assert_eq!(PrimitiveType::from_index(2), Some(PrimitiveType::String)); + assert_eq!(PrimitiveType::from_index(3), None); +} + +#[test] +fn is_builtin() { + assert!(PrimitiveType::is_builtin(0)); + assert!(PrimitiveType::is_builtin(1)); + assert!(PrimitiveType::is_builtin(2)); + assert!(!PrimitiveType::is_builtin(3)); + assert!(!PrimitiveType::is_builtin(100)); +} diff --git a/crates/plotnik-lib/src/type_system/quantifier.rs b/crates/plotnik-lib/src/type_system/quantifier.rs index 14652b27..dfa5d6df 100644 --- a/crates/plotnik-lib/src/type_system/quantifier.rs +++ b/crates/plotnik-lib/src/type_system/quantifier.rs @@ -32,29 +32,3 @@ impl QuantifierKind { matches!(self, Self::Optional | Self::ZeroOrMore) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn requires_row_capture() { - assert!(!QuantifierKind::Optional.requires_row_capture()); - assert!(QuantifierKind::ZeroOrMore.requires_row_capture()); - assert!(QuantifierKind::OneOrMore.requires_row_capture()); - } - - #[test] - fn is_non_empty() { - assert!(!QuantifierKind::Optional.is_non_empty()); - assert!(!QuantifierKind::ZeroOrMore.is_non_empty()); - assert!(QuantifierKind::OneOrMore.is_non_empty()); - } - - #[test] - fn can_be_empty() { - assert!(QuantifierKind::Optional.can_be_empty()); - assert!(QuantifierKind::ZeroOrMore.can_be_empty()); - assert!(!QuantifierKind::OneOrMore.can_be_empty()); - } -} diff --git a/crates/plotnik-lib/src/type_system/quantifier_tests.rs b/crates/plotnik-lib/src/type_system/quantifier_tests.rs new file mode 100644 index 00000000..4dcf3e43 --- /dev/null +++ b/crates/plotnik-lib/src/type_system/quantifier_tests.rs @@ -0,0 +1,22 @@ +use super::*; + +#[test] +fn requires_row_capture() { + assert!(!QuantifierKind::Optional.requires_row_capture()); + assert!(QuantifierKind::ZeroOrMore.requires_row_capture()); + assert!(QuantifierKind::OneOrMore.requires_row_capture()); +} + +#[test] +fn is_non_empty() { + assert!(!QuantifierKind::Optional.is_non_empty()); + assert!(!QuantifierKind::ZeroOrMore.is_non_empty()); + assert!(QuantifierKind::OneOrMore.is_non_empty()); +} + +#[test] +fn can_be_empty() { + assert!(QuantifierKind::Optional.can_be_empty()); + assert!(QuantifierKind::ZeroOrMore.can_be_empty()); + assert!(!QuantifierKind::OneOrMore.can_be_empty()); +}