Testing
This guide covers testing strategies for applications using compressionz.
Running Library Tests
Section titled “Running Library Tests”Run the full compressionz test suite:
# Run all testszig build test
# Run tests with optimizations (faster)zig build test -Doptimize=ReleaseFast
# Run specific test filezig test src/lz4/block.zigRound-Trip Testing
Section titled “Round-Trip Testing”The most important test for compression: data survives the round trip.
const std = @import("std");const cz = @import("compressionz");const testing = std.testing;
test "round trip preserves data" { const original = "Hello, compressionz!";
const compressed = try cz.compress(.zstd, original, testing.allocator); defer testing.allocator.free(compressed);
const decompressed = try cz.decompress(.zstd, compressed, testing.allocator); defer testing.allocator.free(decompressed);
try testing.expectEqualStrings(original, decompressed);}Testing All Codecs
Section titled “Testing All Codecs”Verify your data works with every codec:
test "all codecs round trip" { const codecs = [_]cz.Codec{ .lz4, .lz4_raw, .snappy, .zstd, .gzip, .zlib, .deflate, .brotli, };
const test_data = "Test data for compression round trip verification";
inline for (codecs) |codec| { const compressed = try cz.compress(codec, test_data, testing.allocator); defer testing.allocator.free(compressed);
const decompressed = try cz.decompress(codec, compressed, testing.allocator); defer testing.allocator.free(decompressed);
try testing.expectEqualStrings(test_data, decompressed); }}Edge Case Testing
Section titled “Edge Case Testing”Empty Data
Section titled “Empty Data”test "handles empty input" { const empty: []const u8 = "";
const compressed = try cz.compress(.zstd, empty, testing.allocator); defer testing.allocator.free(compressed);
const decompressed = try cz.decompress(.zstd, compressed, testing.allocator); defer testing.allocator.free(decompressed);
try testing.expectEqual(@as(usize, 0), decompressed.len);}Single Byte
Section titled “Single Byte”test "handles single byte" { const single = "x";
const compressed = try cz.compress(.lz4, single, testing.allocator); defer testing.allocator.free(compressed);
const decompressed = try cz.decompress(.lz4, compressed, testing.allocator); defer testing.allocator.free(decompressed);
try testing.expectEqualStrings(single, decompressed);}Large Data
Section titled “Large Data”test "handles large data" { // 10 MB of test data const size = 10 * 1024 * 1024; const data = try testing.allocator.alloc(u8, size); defer testing.allocator.free(data);
// Fill with pattern for (data, 0..) |*byte, i| { byte.* = @truncate(i); }
const compressed = try cz.compress(.zstd, data, testing.allocator); defer testing.allocator.free(compressed);
const decompressed = try cz.decompress(.zstd, compressed, testing.allocator); defer testing.allocator.free(decompressed);
try testing.expectEqualSlices(u8, data, decompressed);}Binary Data
Section titled “Binary Data”test "handles binary data with nulls" { const binary = &[_]u8{ 0x00, 0xFF, 0x00, 0xAB, 0x00, 0xCD };
const compressed = try cz.compress(.snappy, binary, testing.allocator); defer testing.allocator.free(compressed);
const decompressed = try cz.decompress(.snappy, compressed, testing.allocator); defer testing.allocator.free(decompressed);
try testing.expectEqualSlices(u8, binary, decompressed);}Highly Compressible Data
Section titled “Highly Compressible Data”test "compresses repetitive data well" { // 1 MB of repeated 'a' const data = try testing.allocator.alloc(u8, 1024 * 1024); defer testing.allocator.free(data); @memset(data, 'a');
const compressed = try cz.compress(.zstd, data, testing.allocator); defer testing.allocator.free(compressed);
// Should compress very well try testing.expect(compressed.len < data.len / 100);
const decompressed = try cz.decompress(.zstd, compressed, testing.allocator); defer testing.allocator.free(decompressed);
try testing.expectEqualSlices(u8, data, decompressed);}Incompressible Data
Section titled “Incompressible Data”test "handles random data" { var prng = std.Random.DefaultPrng.init(12345); const random = prng.random();
const data = try testing.allocator.alloc(u8, 10000); defer testing.allocator.free(data); random.bytes(data);
const compressed = try cz.compress(.lz4, data, testing.allocator); defer testing.allocator.free(compressed);
// Random data may expand slightly try testing.expect(compressed.len <= data.len + 1000);
const decompressed = try cz.decompress(.lz4, compressed, testing.allocator); defer testing.allocator.free(decompressed);
try testing.expectEqualSlices(u8, data, decompressed);}Error Handling Tests
Section titled “Error Handling Tests”Corrupted Data
Section titled “Corrupted Data”test "detects corrupted data" { const original = "Valid data to compress";
const compressed = try cz.compress(.zstd, original, testing.allocator); defer testing.allocator.free(compressed);
// Corrupt the compressed data var corrupted = try testing.allocator.dupe(u8, compressed); defer testing.allocator.free(corrupted); corrupted[compressed.len / 2] ^= 0xFF;
// Should fail to decompress const result = cz.decompress(.zstd, corrupted, testing.allocator); try testing.expectError(error.InvalidData, result);}Invalid Magic Bytes
Section titled “Invalid Magic Bytes”test "rejects invalid format" { const garbage = &[_]u8{ 0x00, 0x01, 0x02, 0x03, 0x04 };
const result = cz.decompress(.zstd, garbage, testing.allocator); try testing.expectError(error.InvalidData, result);}Truncated Data
Section titled “Truncated Data”test "detects truncated data" { const original = "Some data to compress for truncation test";
const compressed = try cz.compress(.gzip, original, testing.allocator); defer testing.allocator.free(compressed);
// Truncate to half const truncated = compressed[0 .. compressed.len / 2];
const result = cz.decompress(.gzip, truncated, testing.allocator); try testing.expectError(error.UnexpectedEof, result);}Streaming Tests
Section titled “Streaming Tests”test "streaming compression matches one-shot" { const data = "Test data for streaming comparison";
// One-shot compression const oneshot = try cz.compress(.gzip, data, testing.allocator); defer testing.allocator.free(oneshot);
// Streaming compression var output = std.ArrayList(u8).init(testing.allocator); defer output.deinit();
var comp = try cz.compressor(.gzip, testing.allocator, output.writer(), .{}); defer comp.deinit();
try comp.writer().writeAll(data); try comp.finish();
// Decompress both and compare const from_oneshot = try cz.decompress(.gzip, oneshot, testing.allocator); defer testing.allocator.free(from_oneshot);
const from_stream = try cz.decompress(.gzip, output.items, testing.allocator); defer testing.allocator.free(from_stream);
try testing.expectEqualStrings(from_oneshot, from_stream);}Codec Detection Tests
Section titled “Codec Detection Tests”test "detects codec from compressed data" { const data = "Data for codec detection test";
const codecs = [_]cz.Codec{ .zstd, .gzip, .lz4 };
inline for (codecs) |codec| { const compressed = try cz.compress(codec, data, testing.allocator); defer testing.allocator.free(compressed);
const detected = cz.detect(compressed); try testing.expectEqual(codec, detected); }}Dictionary Tests
Section titled “Dictionary Tests”test "dictionary improves small data compression" { const dictionary = \\{"type":"event","timestamp":0,"user":"","action":""} ;
const samples = [_][]const u8{ \\{"type":"event","timestamp":1234,"user":"alice","action":"login"} , \\{"type":"event","timestamp":1235,"user":"bob","action":"view"} , };
for (samples) |sample| { // Without dictionary const without = try cz.compress(.zstd, sample, testing.allocator); defer testing.allocator.free(without);
// With dictionary const with = try cz.compressWithOptions(.zstd, sample, testing.allocator, .{ .dictionary = dictionary, }); defer testing.allocator.free(with);
// Dictionary should help try testing.expect(with.len <= without.len);
// Verify decompression const decompressed = try cz.decompressWithOptions(.zstd, with, testing.allocator, .{ .dictionary = dictionary, }); defer testing.allocator.free(decompressed);
try testing.expectEqualStrings(sample, decompressed); }}Fuzz Testing
Section titled “Fuzz Testing”For thorough testing, use Zig’s built-in fuzzer:
test "fuzz compression round trip" { // This test is run with: zig build test -Dfuzz const input = std.testing.fuzzInput();
const compressed = cz.compress(.zstd, input, testing.allocator) catch return; defer testing.allocator.free(compressed);
const decompressed = cz.decompress(.zstd, compressed, testing.allocator) catch return; defer testing.allocator.free(decompressed);
try testing.expectEqualSlices(u8, input, decompressed);}Run the fuzzer:
zig build test -Dfuzz -- --fuzzMemory Leak Testing
Section titled “Memory Leak Testing”Use Zig’s testing allocator to detect leaks:
test "no memory leaks" { // testing.allocator automatically detects leaks const data = "Test for memory leaks";
const compressed = try cz.compress(.zstd, data, testing.allocator); defer testing.allocator.free(compressed);
const decompressed = try cz.decompress(.zstd, compressed, testing.allocator); defer testing.allocator.free(decompressed);
// If we forget to free, the test will fail with: // "memory leak detected"}Performance Testing
Section titled “Performance Testing”test "compression meets performance requirements" { const size = 1024 * 1024; // 1 MB const data = try testing.allocator.alloc(u8, size); defer testing.allocator.free(data); @memset(data, 'x');
var timer = try std.time.Timer.start();
const compressed = try cz.compress(.lz4, data, testing.allocator); defer testing.allocator.free(compressed);
const elapsed_ns = timer.read(); const throughput_gbps = @as(f64, @floatFromInt(size)) / @as(f64, @floatFromInt(elapsed_ns));
// LZ4 should compress at > 1 GB/s even in debug mode try testing.expect(throughput_gbps > 0.1);}Cross-Codec Compatibility
Section titled “Cross-Codec Compatibility”Test that data compressed with external tools can be decompressed:
test "decompress externally compressed data" { // Data compressed with `gzip` command line tool const external_gzip = @embedFile("testdata/external.gz"); const expected = @embedFile("testdata/original.txt");
const decompressed = try cz.decompress(.gzip, external_gzip, testing.allocator); defer testing.allocator.free(decompressed);
try testing.expectEqualSlices(u8, expected, decompressed);}Test Organization
Section titled “Test Organization”Organize tests in your project:
myproject/├── src/│ └── main.zig├── tests/│ ├── compression_test.zig│ ├── streaming_test.zig│ └── testdata/│ ├── sample.txt│ ├── sample.gz│ └── sample.zst└── build.zigIn build.zig:
const tests = b.addTest(.{ .root_source_file = .{ .path = "tests/compression_test.zig" }, .target = target, .optimize = optimize,});tests.root_module.addImport("compressionz", compressionz_module);
const run_tests = b.addRunArtifact(tests);const test_step = b.step("test", "Run tests");test_step.dependOn(&run_tests.step);CI Integration
Section titled “CI Integration”Example GitHub Actions workflow:
name: Testson: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Setup Zig uses: goto-bus-stop/setup-zig@v2 with: version: 0.15.0
- name: Run tests run: zig build test
- name: Run tests (optimized) run: zig build test -Doptimize=ReleaseFastSummary
Section titled “Summary”- Always test round trips — data in equals data out
- Test edge cases — empty, single byte, large, binary
- Test error handling — corrupted, truncated, invalid data
- Use testing.allocator — catches memory leaks automatically
- Test all codecs — verify your data works everywhere
- Fuzz test — find edge cases you didn’t think of