Skip to content

Testing

This guide covers testing strategies for applications using compressionz.

Run the full compressionz test suite:

Terminal window
# Run all tests
zig build test
# Run tests with optimizations (faster)
zig build test -Doptimize=ReleaseFast
# Run specific test file
zig test src/lz4/block.zig

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);
}

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);
}
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
}
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);
}
}

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:

Terminal window
zig build test -Dfuzz -- --fuzz

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"
}
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);
}

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);
}

Organize tests in your project:

myproject/
├── src/
│ └── main.zig
├── tests/
│ ├── compression_test.zig
│ ├── streaming_test.zig
│ └── testdata/
│ ├── sample.txt
│ ├── sample.gz
│ └── sample.zst
└── build.zig

In 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);

Example GitHub Actions workflow:

name: Tests
on: [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=ReleaseFast
  • 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