hedl-test

Crates.iohedl-test
lib.rshedl-test
version1.2.0
created_at2026-01-08 11:13:24.13309+00
updated_at2026-01-21 02:59:23.834813+00
descriptionShared test fixtures and utilities for HEDL format converters
homepagehttps://dweve.com
repositoryhttps://github.com/dweve/hedl
max_upload_size
id2030002
size235,941
(marcflp)

documentation

README

hedl-test

Shared test fixtures and utilities for HEDL—comprehensive test data, builders, and error cases for consistent testing across all crates.

Testing format converters requires representative documents. Building test cases by hand is tedious and error-prone. Common test scenarios should be reusable across crates. Edge cases need systematic coverage. Error conditions must be validated consistently.

hedl-test provides 15 pre-built fixtures covering all HEDL features, 4 builder types for programmatic document construction, comprehensive error fixtures (15 invalid HEDL, 15 invalid expressions, 8 semantic violations), and edge case generators (deep nesting, wide documents, long strings, many references).

What's Implemented

Comprehensive test infrastructure:

  1. 15 Pre-Built Fixtures: Scalars, special strings, references, expressions, tensors, named values, user list, mixed type list, with references, with nest, deep nest, edge cases, comprehensive, blog, empty
  2. 4 Builder Types: DocumentBuilder, MatrixListBuilder, NodeBuilder, ValueBuilder
  3. Error Fixtures: 15 invalid HEDL samples, 15 invalid expressions, 8 semantically invalid documents
  4. Edge Case Generators: deeply_nested_document, wide_document, long_string_document, many_references_document
  5. Counting Utilities: count_nodes, count_references
  6. Expression Helpers: expr, try_expr, expr_value, try_expr_value with ExprError type

Installation

[dev-dependencies]
hedl-test = { workspace = true }

Or specify version directly:

[dev-dependencies]
hedl-test = "1.2"

Pre-Built Fixtures

Basic Fixtures

use hedl_test::fixtures;

// Scalar types (all primitives)
let doc = fixtures::scalars();
// Contains: int, float, bool, null, string examples

// Special strings (escapes, Unicode, etc.)
let doc = fixtures::special_strings();
// Contains: quotes, escapes, newlines, Unicode

// References (qualified and local)
let doc = fixtures::references();
// Contains: local references (@id) and typed references (@Type:id)

// Tensors (matrix literals)
let doc = fixtures::tensors();
// Contains: 1D, 2D, 3D tensors and empty tensor

Complex Fixtures

use hedl_test::fixtures;

// User list (simple matrix)
let doc = fixtures::user_list();
// 3 users: alice, bob, charlie with id, name, email

// Mixed type list
let doc = fixtures::mixed_type_list();
// Items with int, float, bool, null, and string fields

// With references
let doc = fixtures::with_references();
// Users and Posts with author references

// With NEST hierarchy
let doc = fixtures::with_nest();
// Users with nested posts (parent-child via NEST)

// Deep NEST hierarchy
let doc = fixtures::deep_nest();
// Organization → Department → Employee (3 levels)

// Blog (nested structure)
let doc = fixtures::blog();
// Complex blog platform: users, categories, tags, posts, comments, reactions, post_tags, followers

Comprehensive Fixture

All HEDL features in one document:

use hedl_test::fixtures;

let doc = fixtures::comprehensive();
// Contains:
// - Scalar values (bool, string, int, float)
// - Expressions (function calls, identifiers, literals)
// - Tensors
// - Users list with nested posts (via NEST)
// - Comments and Tags lists
// - References between entities
// - Multiple STRUCT definitions
// - NEST directive

Builder Types

DocumentBuilder

Programmatically construct documents:

use hedl_test::fixtures::builders::DocumentBuilder;
use hedl_core::Value;

let doc = DocumentBuilder::new()
    .version(1, 0)
    .struct_def("User", vec!["id".to_string(), "name".to_string(), "email".to_string()])
    .alias("api_url", "https://api.example.com")
    .nest("User", "Post")
    .scalar("app_name", Value::String("MyApp".to_string().into()))
    .scalar("debug", Value::Bool(true))
    .build();

Methods:

  • version(major, minor) - Set document version
  • struct_def(name, fields) - Add struct definition
  • alias(alias, target) - Add alias
  • nest(parent, child) - Add NEST relationship
  • scalar(name, value) - Add scalar value to root
  • list(name, list) - Add MatrixList to root
  • item(name, item) - Add Item to root
  • build() - Build the Document

MatrixListBuilder

Build typed entity lists:

use hedl_test::fixtures::builders::MatrixListBuilder;
use hedl_core::{Node, Value};

let list = MatrixListBuilder::new("User")
    .schema(vec!["id".to_string(), "name".to_string(), "age".to_string()])
    .row(Node::new("User", "alice", vec![
        Value::String("alice".to_string().into()),
        Value::String("Alice Smith".to_string().into()),
        Value::Int(30),
    ]))
    .row(Node::new("User", "bob", vec![
        Value::String("bob".to_string().into()),
        Value::String("Bob Jones".to_string().into()),
        Value::Int(25),
    ]))
    .build();

Methods:

  • schema(fields) - Set schema (all field names)
  • field(field) - Add single field to schema
  • row(node) - Add a row (node)
  • rows(nodes) - Add multiple rows
  • count_hint(count) - Set count hint
  • build() - Build the MatrixList

NodeBuilder

Build individual entities:

use hedl_test::fixtures::builders::NodeBuilder;
use hedl_core::Value;

let node = NodeBuilder::new("User", "alice")
    .field(Value::String("alice".to_string().into()))
    .field(Value::String("Alice Smith".to_string().into()))
    .field(Value::Int(30))
    .build();

Methods:

  • field(value) - Add a field value
  • fields(values) - Add multiple field values
  • children(rel_name, nodes) - Add child nodes under a relationship
  • child(rel_name, node) - Add single child node
  • child_count(count) - Set child count hint
  • build() - Build the Node

ValueBuilder

Static helper methods for creating values:

use hedl_test::fixtures::builders::ValueBuilder;

// Scalar values
let v = ValueBuilder::null();
let v = ValueBuilder::bool_val(true);
let v = ValueBuilder::int(42);
let v = ValueBuilder::float(3.14);
let v = ValueBuilder::string("Hello");

// References
let v = ValueBuilder::reference("User", "alice");  // Qualified reference
let v = ValueBuilder::local_ref("some_id");        // Local reference

// Tensors
let v = ValueBuilder::tensor_1d(vec![1.0, 2.0, 3.0]);
let v = ValueBuilder::tensor_2d(vec![vec![1.0, 2.0], vec![3.0, 4.0]]);

Methods:

  • null() - Create null value
  • bool_val(value) - Create boolean value
  • int(value) - Create integer value
  • float(value) - Create float value
  • string(value) - Create string value
  • reference(type_name, id) - Create qualified reference
  • local_ref(id) - Create local reference
  • tensor_1d(values) - Create 1D tensor
  • tensor_2d(rows) - Create 2D tensor

Error Fixtures

Invalid HEDL Syntax

use hedl_test::fixtures::errors;

// Get all invalid HEDL samples as (name, hedl_text) pairs
let samples = errors::invalid_hedl_samples();
for (name, hedl_text) in samples {
    // Test parser with invalid input
    // Each should fail to parse
}

Available samples (15 total):

  • empty - Empty document
  • whitespace_only - Only whitespace
  • invalid_directive - Invalid directive name
  • missing_separator - Missing --- separator
  • invalid_version - Invalid version number
  • malformed_struct - Malformed STRUCT directive
  • unclosed_string - Unclosed string literal
  • invalid_escape - Invalid escape sequence
  • malformed_reference - Malformed reference
  • invalid_tensor - Malformed tensor syntax
  • mismatched_brackets - Mismatched brackets
  • invalid_number - Invalid number format
  • malformed_expression - Malformed expression
  • invalid_identifier - Invalid identifier
  • duplicate_directive - Duplicate directive

Invalid Expressions

use hedl_test::fixtures::errors;

// Get all invalid expression samples as (description, expr_string) pairs
let samples = errors::invalid_expression_samples();
for (desc, expr_str) in samples {
    // Test expression parser with invalid input
    // Each should fail to parse
}

Available samples (15 total):

  • empty - Empty expression
  • whitespace_only - Only whitespace
  • unclosed_paren - Unclosed parenthesis
  • unclosed_string - Unclosed string literal
  • invalid_chars - Invalid characters
  • double_dot - Double dot in path
  • trailing_dot - Trailing dot
  • leading_dot - Leading dot
  • mismatched_parens - Mismatched parentheses
  • empty_call - Empty function call
  • invalid_identifier - Invalid identifier
  • special_chars_only - Only special characters
  • nested_unclosed - Nested unclosed parentheses
  • comma_without_args - Comma without arguments
  • trailing_comma - Trailing comma

Semantic Errors

Valid syntax but semantic violations:

use hedl_test::fixtures::errors;

// Get all semantically invalid documents as (name, document) pairs
let docs = errors::semantically_invalid_docs();
for (name, doc) in docs {
    // Test validation logic with semantically invalid documents
    // Each should fail validation but parse correctly
}

Available documents (8 total):

  • undefined_struct - MatrixList references undefined struct
  • undefined_nest - NEST directive references non-existent child type
  • circular_nest - Circular NEST references (TypeA → TypeB → TypeA)
  • dangling_reference - Reference points to non-existent node
  • mismatched_schema - MatrixList schema doesn't match struct definition
  • empty_type_name - MatrixList has empty type name
  • duplicate_ids - Multiple nodes with same ID in a list
  • invalid_alias - Alias references non-existent item

Edge Case Generators

Deeply Nested Document

Test parser depth limits:

use hedl_test::fixtures::errors;

let doc = errors::deeply_nested_document(1000);
// Creates 1000 levels of nested children via NEST

Use Case: Verify recursion depth limits and stack safety

Wide Document

Test handling of many entities:

use hedl_test::fixtures::errors;

let doc = errors::wide_document(1000);
// Creates a MatrixList with 1000 rows

Use Case: Verify large list handling and memory efficiency

Long Strings

Test string length limits:

use hedl_test::fixtures::errors;

let doc = errors::long_string_document(1024 * 1024);
// Creates a document with a 1 MB string value

Use Case: Verify string buffer limits and allocation safety

Many References

Test reference resolution performance:

use hedl_test::fixtures::errors;

let doc = errors::many_references_document(10_000);
// Creates 10K target nodes and 10K references

Use Case: Verify reference resolution performance and correctness

Test Utilities

Counting Utilities

use hedl_test::{count_nodes, count_references};

// Count total nodes (entities) in document
let count = count_nodes(&doc);

// Count total references in document
let ref_count = count_references(&doc);

Available functions:

  • count_nodes(&doc) - Count all nodes in the document (including nested children)
  • count_references(&doc) - Count all reference values in the document

Expression Utilities

use hedl_test::{expr, try_expr, expr_value, try_expr_value, ExprError};

// Create Expression from string (panics on error)
let e = expr("42");
let e = expr("now()");
let e = expr("user.name");

// Create Expression safely (returns Result)
let result = try_expr("invalid!!!");
match result {
    Ok(e) => println!("Parsed: {:?}", e),
    Err(ExprError::ParseFailed { source, input }) => {
        println!("Failed to parse '{}': {}", input, source);
    }
    Err(ExprError::EmptyInput) => println!("Empty input"),
    Err(ExprError::Missing) => println!("Missing expression"),
}

// Create Value::Expression from string
let v = expr_value("42");        // Panics on error
let v = try_expr_value("42")?;   // Returns Result

Available functions:

  • expr(s) - Parse expression string, panic on error
  • try_expr(s) - Parse expression string, return Result
  • expr_value(s) - Create Value::Expression, panic on error
  • try_expr_value(s) - Create Value::Expression, return Result

Error type:

  • ExprError::EmptyInput - Expression string is empty
  • ExprError::ParseFailed { source, input } - Parse failed with lexical error
  • ExprError::Missing - Expression is missing or null

Quick Builder Helpers

The fixtures::builders::quick module provides convenient helper functions for common patterns:

use hedl_test::fixtures::builders::quick;

// Create document with simple scalar fields
let doc = quick::simple_scalars(vec![
    ("name", "Alice"),
    ("city", "NYC"),
]);

// Create document with simple user list
let doc = quick::simple_user_list(vec![
    ("alice", "Alice Smith", "alice@example.com"),
    ("bob", "Bob Jones", "bob@example.com"),
]);

// Create document with references
let doc = quick::with_references(
    vec![("alice", "Alice"), ("bob", "Bob")],  // users
    vec![("post1", "Title", "alice")],          // posts with author refs
);

Usage Patterns

Testing Parsers

use hedl_test::fixtures;

#[test]
fn test_parse_user_list() {
    let doc = fixtures::user_list();
    assert_eq!(doc.version, (1, 0));

    if let Some(hedl_core::Item::List(list)) = doc.root.get("users") {
        assert_eq!(list.rows.len(), 3);
        assert_eq!(list.type_name, "User");
    }
}

Testing Format Converters

use hedl_test::fixtures;

#[test]
fn test_all_fixtures() {
    for (name, fixture_fn) in fixtures::all() {
        let doc = fixture_fn();

        // Test your converter with each fixture
        let json = to_json(&doc).unwrap();
        let back = from_json(&json).unwrap();

        // Verify roundtrip
        assert_eq!(doc, back, "Fixture {} failed roundtrip", name);
    }
}

Testing Error Handling

use hedl_test::fixtures::errors;

#[test]
fn test_invalid_hedl_samples() {
    for (name, hedl_text) in errors::invalid_hedl_samples() {
        let result = parse(hedl_text);
        assert!(result.is_err(), "Sample {} should fail to parse", name);
    }
}

#[test]
fn test_semantic_errors() {
    for (name, doc) in errors::semantically_invalid_docs() {
        let result = validate(&doc);
        assert!(result.is_err(), "Document {} should fail validation", name);
    }
}

Testing Edge Cases

use hedl_test::fixtures::errors;
use hedl_test::count_nodes;

#[test]
fn test_deeply_nested() {
    let doc = errors::deeply_nested_document(100);
    let count = count_nodes(&doc);

    // Verify deep nesting is handled correctly
    assert!(count > 0);
}

#[test]
fn test_many_references() {
    let doc = errors::many_references_document(1000);

    // Test reference resolution performance
    let result = resolve_references(&doc);
    assert!(result.is_ok());
}

What This Crate Doesn't Do

Property-Based Testing: Fixtures are hand-crafted examples. For property-based testing, use proptest or quickcheck with custom generators.

Performance Testing: Fixtures designed for correctness testing. For performance, use hedl-bench with realistic workloads.

Fuzz Testing: No fuzzing infrastructure. For fuzz testing, use cargo-fuzz with custom fuzz targets.

Test Data Generation: Fixtures are static. For dynamic test data, write custom generators using builders.

Dependencies

  • hedl-core - Core HEDL types and parsing
  • hedl-c14n - Canonicalization for HEDL documents
  • smallvec - Optimized small vector implementation

License

Apache-2.0

Commit count: 0

cargo fmt