Cross-Language Generator Test Implementation

Date: 2025-07-02
Author: Claude Code
Type: Implementation Report

Summary

Successfully implemented and completed unified cross-language generator tests for netencode across Python, Haskell, Rust, and Nix implementations. All languages now use proper generator functions (not direct type constructors), have correct test expectations verified with actual netencode tools, and produce byte-identical output for equivalent operations.

Background

The netencode project implements a binary serialization format across multiple languages with the goal of producing identical output for the same logical operations. However, testing was inconsistent:

This inconsistency made it difficult to ensure cross-language compatibility and catch regressions.

Implementation Approach

1. Specification-Driven Testing

Created tests/GENERATOR_TEST_SPEC.md as the authoritative test specification:

2. Language-Specific Implementations

Python (lib-python/test_generator_spec.py)

Key test examples:

def test_record_alphabetical_sort(self):
    """Record fields sorted alphabetically."""
    record = ne.record({"b": ne.text("2"), "a": ne.text("1")})
    assert record == b"{20:<1:a|t1:1,<1:b|t1:2,}"

def test_deeply_nested_structures(self):
    """Deeply nested records and lists."""
    inner_record = ne.record({"value": ne.text("deep")})
    middle_list = ne.list([inner_record])
    outer_record = ne.record({"nested": middle_list})
    assert outer_record == b"{37:<6:nested|[22:{17:<5:value|t4:deep,}]}"

Haskell (lib-haskell/test/GeneratorSpec.hs)

Key test examples:

it "record alphabetical sort" $ do
  let recordMap = NEMap.fromList (("b", text "2") :| [("a", text "1")])
  (netencodeEncodeStable (record recordMap) & Builder.toLazyByteString & toStrictBytes) 
    `shouldBe` "{20:<1:a|t1:1,<1:b|t1:2,}"

it "text UTF-8 accented" $
  (netencodeEncodeStable (text "café") & Builder.toLazyByteString & toStrictBytes) 
    `shouldBe` "t5:caf\xc3\xa9,"

Rust (lib-rust/tests/simple_generator_test.rs)

Key test examples:

#[test]
fn test_record_alphabetical_sort() {
    let record = T::record(vec![("b", T::text("2")), ("a", T::text("1"))]);
    assert_eq!(record.encode(), b"{20:<1:a|t1:1,<1:b|t1:2,}");
}

#[test]
fn test_unicode_complex() {
    assert_eq!(
        T::text("Hello 世界 🌍").encode(), 
        b"t17:Hello \\xe4\\xb8\\x96\\xe7\\x95\\x8c \\xf0\\x9f\\x8c\\x8d,"
    );
}

Nix (lib-nix/test-gen.nix)

3. Test Expectation Corrections

During implementation, we discovered incorrect test expectations in the original specification. Using actual netencode tools for verification:

# Verified correct outputs
echo '{"b": "2", "a": "1"}' | json-to-netencode | netencode-pretty
# Result: {20:<1:a|t1:1,<1:b|t1:2,} (not {18:...})

# Complex nested structure
echo '{"nested": [{"value": "deep"}]}' | json-to-netencode | netencode-pretty  
# Result: {37:<6:nested|[22:{17:<5:value|t4:deep,}]} (not {33:...})

Corrections made:

Test Coverage

Basic Types (22 tests across languages)

Composite Types (12 tests across languages)

Complex Scenarios (11 tests across languages)

Error Cases (4 tests where applicable)

Build System Integration

Updated default.nix with test targets:

# Individual language test suites
netencode-python-tests    # Python tests in lib-python/
netencode-haskell-tests   # Haskell HSpec test suite  
netencode-rust-tests      # Rust test suite
netencode-nix-tests       # Nix evaluation-time assertions

# Combined test runner
test-all-generators       # Runs all language tests

Results

Test Metrics

Cross-Language Verification

All implementations produce byte-identical output:

# Python
ne.text("café") == b"t5:caf\xc3\xa9,"

# Haskell  
netencodeEncodeStable (text "café") == "t5:caf\xc3\xa9,"

# Rust
T::text("café").encode() == b"t5:caf\xc3\xa9,"

# Nix
gen.text "café" == "t5:café,"

Usage

Running Tests

# All cross-language generator tests
nix-build -A test-all-generators

# Individual language tests
nix-build -A netencode-tests --arg testFiles '"test_generator_spec.py"'
cabal test generator-tests  # Haskell
cargo test                 # Rust (in lib-rust/)
nix-instantiate --eval --attr netencode-nix-tests.success default.nix

Adding New Test Cases

  1. Update specification: Add test case to tests/GENERATOR_TEST_SPEC.md
  2. Update implementations: Add corresponding test in each language
  3. Verify with tools: Use actual netencode tools to verify expected output
  4. Run full suite: nix-build -A test-all-generators

Benefits Achieved

1. Cross-Language Consistency

2. Comprehensive Coverage

3. Maintainable Architecture

4. Quality Assurance

Technical Implementation Details

Generator Function Patterns

All languages follow consistent patterns:

// Basic types
unit() -> "u,"
natural(42) -> "n:42,"  
integer(-42) -> "i:-42,"
boolean(true) -> "<4:true|u,"
text("hello") -> "t5:hello,"
binary(b"data") -> "b4:data,"

// Composite types  
tag("name", value) -> "<4:name|{value},"
record([("key", value)]) -> "{N:<3:key|{value},}"
list([value1, value2]) -> "[N:{value1},{value2},]"

Length Calculation Verification

The most critical discovery was correcting length calculations:

UTF-8 Handling

Consistent UTF-8 encoding across all languages:

Future Enhancements

Potential Improvements

Integration Opportunities

Conclusion

The cross-language generator test implementation establishes a robust foundation for netencode development. By creating unified specifications and comprehensive tests in each language, we ensure consistency, prevent regressions, and maintain compatibility across the multi-language codebase.

The architecture supports confident development of new features while maintaining backward compatibility and cross-language consistency. The test suite serves as both verification and documentation, demonstrating correct usage patterns across all supported languages.


Files Modified/Created:

Key Achievements: