Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 0 additions & 100 deletions crates/plotnik-core/src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
96 changes: 96 additions & 0 deletions crates/plotnik-core/src/interner_tests.rs
Original file line number Diff line number Diff line change
@@ -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);
}
84 changes: 7 additions & 77 deletions crates/plotnik-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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);
}
}
73 changes: 73 additions & 0 deletions crates/plotnik-core/src/lib_tests.rs
Original file line number Diff line number Diff line change
@@ -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);
}
Loading