Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Adversarial Tests

The adversarial test suite validates TeaLeaf’s error handling and robustness using crafted malformed inputs, binary corruption, compression edge cases, and large-corpus stress tests. All tests are isolated in the adversarial-tests/ directory to avoid touching core project files.

Current count: 58 tests across 9 categories.

Running Tests

# Run all adversarial tests
cd adversarial-tests/core-harness
cargo test --test adversarial

# With output
cargo test --test adversarial -- --nocapture

# Run via script (PowerShell)
./adversarial-tests/scripts/run_core_harness.ps1

# CLI adversarial tests
./adversarial-tests/scripts/run_cli_adversarial.ps1

# .NET adversarial harness
./adversarial-tests/scripts/run_dotnet_harness.ps1

Test Input Files

TeaLeaf Format (.tl) — 13 files

Crafted .tl files testing parser error paths:

FileError TestedExpected
bad_unclosed_string.tlUnclosed string literal ("Alice)Parse error
bad_missing_colon.tlMissing colon in key-value pairParse error
bad_invalid_escape.tlInvalid escape sequence (\q)Parse error
bad_number_overflow.tlNumber exceeding u64 boundsSee note below
bad_table_wrong_arity.tlTable row with wrong field countParse error
bad_schema_unclosed.tlUnclosed @struct definitionParse error
bad_unicode_escape_short.tlIncomplete \u escape (\u12)Parse error
bad_unicode_escape_invalid_hex.tlInvalid hex in \uZZZZParse error
bad_unicode_escape_surrogate.tlUnicode surrogate pair (\uD800)Parse error
bad_unterminated_multiline.tlUnterminated """ multiline stringParse error
invalid_utf8.tlInvalid UTF-8 byte sequenceParse error

Note: bad_number_overflow.tl does not cause a parse error. Numbers exceeding i64/u64 range are stored as Value::JsonNumber (exact decimal string), not rejected.

Edge cases that should succeed:

FileWhat It TestsExpected
deep_nesting.tl7 levels of nested arrays ([[[[[[[1]]]]]]])Parse OK
empty_doc.tlEmpty documentParse OK

JSON Format (.json) — 6 files

Files testing from_json error and edge-case paths:

FileWhat It TestsExpected
invalid_json_trailing.jsonTrailing comma or contentParse error
invalid_json_unclosed.jsonUnclosed object or arrayParse error
large_number.jsonNumber overflowing f64Stored as JsonNumber
deep_array.jsonDeeply nested arraysParse OK
empty_object.jsonEmpty JSON object {}Parse OK
root_array.jsonRoot-level array [1,2,3]Preserved as array

Binary Format (.tlbx) — 4 files (unused)

These fixture files exist but are not referenced by any test. All binary adversarial tests generate malformed data inline using tempfile::tempdir(). These files are only used by the CLI adversarial scripts in results/cli/.

FileContent
bad_magic.tlbxInvalid magic bytes
bad_version.tlbxInvalid version field
random_garbage.tlbxRandom bytes
truncated_header.tlbxIncomplete header

Test Functions

Parse Error Tests (10 tests)

FunctionInputAssertion
parse_invalid_syntax_unclosed_stringname: "Aliceis_err()
parse_invalid_escape_sequencename: "Alice\q"is_err()
parse_missing_colonname "Alice"is_err()
parse_schema_unclosedUnclosed @structis_err()
parse_table_wrong_arity3 fields for 2-field schemais_err()
parse_unicode_escape_short\u12is_err()
parse_unicode_escape_invalid_hex\uZZZZis_err()
parse_unicode_escape_surrogate\uD800is_err()
parse_unterminated_multiline_string"""unterminatedis_err()
from_json_invalid{"a":1,} (trailing comma)is_err()

Success / Edge-Case Parse Tests (3 tests)

FunctionInputAssertion
parse_number_overflow_falls_to_json_number18446744073709551616Parse succeeds; stored as Value::JsonNumber
parse_deep_nesting_ok[[[[[[[1]]]]]]]Parse succeeds; get("root") returns value
from_json_root_array_is_preserved[1,2,3]Stored under "root" key as Value::Array

Error Variant Coverage (5 tests)

Tests that exercise specific Error enum variants for code coverage:

FunctionWhat It TestsAssertion
parse_unknown_struct_in_table@table nonexistent references undefined structis_err(); message contains struct name
parse_unexpected_eof_unclosed_braceobj: {x: 1,is_err(); message indicates EOF
parse_unexpected_eof_unclosed_bracketarr: [1, 2,is_err()
reader_missing_fieldreader.get("nonexistent") on valid binaryis_err(); message contains key name
from_json_large_number_falls_to_json_number{"big": 18446744073709551616}Parsed as Value::JsonNumber

Type Coercion Tests (2 tests)

Validates spec §2.5 best-effort numeric coercion during binary compilation:

FunctionInputAssertion
writer_int_overflow_coerces_to_zeroint8 field with value 999Binary roundtrip produces Value::Int(0)
writer_uint_negative_coerces_to_zerouint8 field with value -1Binary roundtrip produces Value::UInt(0)

Binary Reader Tests (4 tests)

FunctionInputAssertion
reader_rejects_bad_magic[0x58, 0x58, 0x58, 0x58]Reader::open().is_err()
reader_rejects_bad_versionValid magic + version 3Reader::open().is_err()
load_invalid_file_errors.tl file with bad syntaxTeaLeaf::load().is_err()
load_invalid_utf8_errors[0xFF, 0xFE, 0xFA]TeaLeaf::load().is_err()

Binary Corruption Tests (12 tests)

Tests that take valid binary output, corrupt specific bytes, and verify the reader does not panic:

FunctionWhat It Corrupts
reader_corrupted_magic_byteFlips first magic byte
reader_corrupted_string_table_offsetPoints string table offset past EOF
reader_truncated_string_tableTruncates file right after header
reader_oversized_string_countSets string count to u32::MAX
reader_oversized_section_countSets section count to u32::MAX
reader_corrupted_schema_countSets schema count to u32::MAX
reader_flipped_bytes_in_section_dataFlips bytes in last 10 bytes of section data
reader_truncated_compressed_dataRemoves last 20 bytes from compressed file
reader_invalid_zlib_streamOverwrites data section with 0xBA bytes
reader_zero_length_fileEmpty Vec<u8>
reader_just_magic_no_headerOnly b"TLBX" (4 bytes, no header)
reader_corrupted_type_codeReplaces a type code byte with 0xFE

All corruption tests assert no panic. Most also verify that Reader::from_bytes() or reader.get() either returns an error or handles the corruption gracefully.

Compression Stress Tests (4 tests)

FunctionWhat It Tests
compression_at_threshold_boundaryData just over 64 bytes triggers compression attempt; roundtrip OK
compression_skipped_when_not_beneficialHigh-entropy data: compressed file not much larger than raw
compression_all_identical_bytes10K zeros: compressed size < half of raw; roundtrip OK
compression_below_threshold_stored_rawSmall data with compress=true: stored raw (same size as uncompressed)

Soak / Large-Corpus Tests (8 tests)

Stress tests for parser, writer, and reader with large inputs:

FunctionScaleWhat It Tests
soak_deeply_nested_arrays200 levels deepParser handles deep nesting without stack overflow
soak_wide_object10,000 fieldsParser and Value::Object handle wide objects
soak_large_array100,000 integersParser handles large arrays; first/last element correct
soak_large_array_binary_roundtrip100,000 integersCompile + read roundtrip with compression
soak_many_sections5,000 top-level keysBinary writer/reader handles many sections
soak_many_schemas500 @struct definitionsSchema table handles large schema counts
soak_string_deduplication15,000 strings (5K dupes)String dedup in binary writer; roundtrip correct
soak_long_string1 MB stringBinary writer/reader handles large string values

Memory-Mapped Reader Tests (10 tests)

Validates Reader::open_mmap() produces identical results to Reader::open() and Reader::from_bytes():

FunctionWhat It Tests
mmap_roundtrip_all_primitive_typesInt, float, bool, string, timestamp via mmap
mmap_roundtrip_containersArrays, objects, nested arrays via mmap
mmap_roundtrip_schemas@struct + @table data via mmap
mmap_roundtrip_compressed500-element compressed array via mmap
mmap_vs_open_equivalenceAll keys: open_mmap values == open values
mmap_vs_from_bytes_equivalenceAll keys: open_mmap values == from_bytes values
mmap_large_file50,000-element array via mmap
mmap_nonexistent_fileopen_mmap on missing path returns error
mmap_multiple_sections100 sections via mmap; boundary keys correct
mmap_string_dedup100 identical string values via mmap; dedup preserved

Directory Structure

adversarial-tests/
├── inputs/
│   ├── tl/              # 13 crafted .tl files (11 error + 2 success)
│   ├── json/            # 6 crafted .json files
│   └── tlbx/            # 4 .tlbx files (used by CLI scripts, not Rust tests)
├── core-harness/
│   ├── tests/
│   │   └── adversarial.rs   # 58 Rust integration tests
│   └── Cargo.toml
├── dotnet-harness/          # C# harness using TeaLeaf bindings
├── scripts/
│   ├── run_core_harness.ps1
│   ├── run_cli_adversarial.ps1
│   └── run_dotnet_harness.ps1
├── results/                 # CLI test logs and outputs
└── README.md

Adding New Tests

1. Add an Inline Test (preferred)

Most adversarial tests generate their inputs inline. This avoids stale fixture files and keeps the test self-contained:

#![allow(unused)]
fn main() {
#[test]
fn parse_new_error_case() {
    assert_parse_err("malformed: input here");
}
}

The assert_parse_err helper asserts that TeaLeaf::parse(input).is_err().

2. For Binary Tests

Use the make_valid_binary helper to produce valid bytes, then corrupt them:

#![allow(unused)]
fn main() {
#[test]
fn reader_new_corruption_case() {
    let mut data = make_valid_binary("val: 42", false);
    data[0] ^= 0xFF; // corrupt something
    let result = Reader::from_bytes(data);
    // Assert no panic; error or graceful handling OK
    if let Ok(r) = result {
        let _ = r.get("val");
    }
}
}

3. Input File Tests (for CLI scripts)

Place malformed input in the appropriate subdirectory for CLI adversarial testing:

adversarial-tests/inputs/tl/bad_new_case.tl

The CLI script run_cli_adversarial.ps1 exercises these files through the tealeaf CLI binary and logs results to results/cli/.