hedl-test 2.0.0

Shared test fixtures and utilities for HEDL format converters
Documentation

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 = "2.0"

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