Skip to main content

Testing

Voltaire uses a strict TDD approach. Tests are first-class citizens, never stubs or placeholders.

Core Principle

Every line correct. No stubs, no commented tests.If a test exists, it passes. If it doesn’t pass, fix it or delete it.

Test Organization

Zig Tests

Inline in source files:
src/primitives/Address/address.zig     # Implementation + tests
src/crypto/Keccak256/keccak256.zig     # Implementation + tests

TypeScript Tests

Separate test files:
src/primitives/Address/Address.test.ts
src/primitives/Address/Address.wasm.test.ts
src/crypto/Keccak256/Keccak256.test.ts

Benchmarks

src/primitives/Address/address.bench.zig   # Zig benchmarks (zbench)
src/primitives/Address/Address.bench.ts    # TS benchmarks (mitata)

Running Tests

Zig Tests

# All Zig tests
zig build test

# Filter by name
zig build -Dtest-filter=Address
zig build -Dtest-filter=keccak
zig build -Dtest-filter="fromHex"

# With debug output
zig build test 2>&1 | head -100

TypeScript Tests

# Watch mode (development)
bun run test

# Single run
bun run test:run

# Filter tests
bun run test -- address
bun run test -- "Address.fromHex"

# Coverage report
bun run test:coverage

# Specific test suites
bun run test:native      # Native FFI
bun run test:wasm        # WASM

TDD Workflow

The Loop

  1. Write failing test
  2. Implement minimal code to pass
  3. Refactor
  4. Run zig build && zig build test
  5. Repeat
# Keep this running in a terminal
while true; do zig build && zig build test && bun run test:run; sleep 2; done

Bug Fixing

Always produce a failing test first:
test "regression: fromHex handles mixed case" {
    // This was failing before the fix
    const addr = try Address.fromHex("0x742D35Cc6634c0532925A3b844Bc9e7595f251E3");
    try testing.expectEqual(@as(u8, 0x74), addr.bytes[0]);
}
Then fix the implementation. The test stays forever.

Zig Test Patterns

Basic Assertion

const testing = std.testing;

test "Address.fromHex valid input" {
    const addr = try Address.fromHex("0x742d35cc6634c0532925a3b844bc9e7595f251e3");
    try testing.expectEqual(@as(u8, 0x74), addr.bytes[0]);
    try testing.expectEqual(@as(u8, 0x2d), addr.bytes[1]);
}

Error Testing

test "Address.fromHex rejects invalid length" {
    const result = Address.fromHex("0x123");
    try testing.expectError(error.InvalidLength, result);
}

test "Address.fromHex rejects invalid characters" {
    const result = Address.fromHex("0xgggggggggggggggggggggggggggggggggggggggg");
    try testing.expectError(error.InvalidHexDigit, result);
}

Slice Comparison

test "Address.toHex output" {
    const addr = Address{ .bytes = [_]u8{0x74} ++ [_]u8{0} ** 19 };
    const hex = addr.toHex();
    try testing.expectEqualSlices(u8, "0x7400000000000000000000000000000000000000", &hex);
}

Debug Output

test "complex operation" {
    testing.log_level = .debug;

    const result = try complexOperation();
    std.log.debug("result: {x}", .{result});

    try testing.expect(result.len > 0);
}

Memory Testing

test "no memory leaks" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const result = try encode(allocator, data);
    defer allocator.free(result);

    // If we get here without leak detection, test passes
}

TypeScript Test Patterns

Basic Test

import { describe, it, expect } from "vitest";
import * as Address from "./index.js";

describe("Address", () => {
  describe("fromHex", () => {
    it("converts valid lowercase hex", () => {
      const addr = Address.fromHex("0x742d35cc6634c0532925a3b844bc9e7595f251e3");
      expect(addr).toBeInstanceOf(Uint8Array);
      expect(addr.length).toBe(20);
    });

    it("converts valid checksummed hex", () => {
      const addr = Address.fromHex("0x742d35Cc6634C0532925a3b844Bc9e7595f251e3");
      expect(addr[0]).toBe(0x74);
    });
  });
});

Error Testing

describe("fromHex", () => {
  it("throws on invalid hex", () => {
    expect(() => Address.fromHex("0xinvalid")).toThrow();
  });

  it("throws InvalidAddressError", () => {
    expect(() => Address.fromHex("0x123")).toThrow(InvalidAddressError);
  });

  it("throws with descriptive message", () => {
    expect(() => Address.fromHex("bad")).toThrow(/invalid.*address/i);
  });
});

Async Testing

describe("async operations", () => {
  it("resolves with valid data", async () => {
    const result = await fetchAddress(id);
    expect(result).toBeDefined();
  });

  it("rejects on network error", async () => {
    await expect(fetchAddress("invalid")).rejects.toThrow();
  });
});

Table-Driven Tests

describe("toChecksummed", () => {
  const cases = [
    { input: "0x742d35cc6634c0532925a3b844bc9e7595f251e3", expected: "0x742d35Cc6634C0532925a3b844Bc9e7595f251e3" },
    { input: "0x0000000000000000000000000000000000000000", expected: "0x0000000000000000000000000000000000000000" },
    { input: "0xffffffffffffffffffffffffffffffffffffffff", expected: "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF" },
  ];

  it.each(cases)("checksums $input", ({ input, expected }) => {
    const addr = Address.fromHex(input);
    expect(Address.toChecksummed(addr)).toBe(expected);
  });
});

Cross-Validation

Validate against known-good vectors (no external libs):
import * as Address from "./index.js";

describe("checksum test vectors", () => {
  const cases = [
    ["0x742d35cc6634c0532925a3b844bc9e7595f251e3", "0x742d35Cc6634C0532925a3b844Bc9e7595f251e3"],
    ["0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000"],
    ["0xffffffffffffffffffffffffffffffffffffffff", "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF"],
  ] as const;

  it.each(cases)("checksums %s", (input, expected) => {
    const ours = Address.toChecksummed(Address.fromHex(input));
    expect(ours).toBe(expected);
  });
});

Test Coverage

Generating Coverage

# TypeScript coverage
bun run test:coverage

# View report
open coverage/index.html

Coverage Goals

  • Core primitives: 100%
  • Crypto functions: 100%
  • Edge cases: exhaustive
  • Error paths: covered

Fuzz Testing

Zig Fuzz Tests

// address.fuzz.zig
const std = @import("std");
const Address = @import("address.zig").Address;

test "fuzz: fromHex roundtrip" {
    var input: [40]u8 = undefined;

    // Generate random hex
    var rng = std.Random.DefaultPrng.init(0);
    for (&input) |*c| {
        const idx = rng.random().int(u4);
        c.* = "0123456789abcdef"[idx];
    }

    const hex = "0x" ++ input;
    const addr = try Address.fromHex(hex);
    const output = addr.toHex();

    try std.testing.expectEqualSlices(u8, &hex, &output);
}

Running Fuzz Tests

# Enable fuzzing
zig build test -Dfuzz=true

Security Testing

# Run security-focused tests
zig build test-security

# Constant-time verification
zig build test -Dtest-filter=secure

What Security Tests Cover

  • Constant-time comparisons
  • No timing leaks
  • Input validation
  • Memory clearing after sensitive ops
  • Edge case handling

Benchmarks

Zig Benchmarks

// address.bench.zig
const zbench = @import("zbench");

pub fn benchFromHex(b: *zbench.Benchmark) void {
    const hex = "0x742d35cc6634c0532925a3b844bc9e7595f251e3";

    b.run(struct {
        fn f() void {
            _ = Address.fromHex(hex) catch unreachable;
        }
    }.f);
}

TypeScript Benchmarks

// Address.bench.ts
import { bench, run } from "mitata";
import * as Address from "./index.js";

bench("Address.fromHex", () => {
  Address.fromHex("0x742d35cc6634c0532925a3b844bc9e7595f251e3");
});

bench("Address.toChecksummed", () => {
  Address.toChecksummed(addr);
});

await run();

Running Benchmarks

# Zig benchmarks
zig build bench -Dwith-benches=true

# TypeScript benchmarks
bun run bench

Common Mistakes

Avoid these testing anti-patterns:
// ❌ Commented out test (remove or implement)
// it("handles edge case", () => {
//   // implement here when ready
// });

// ❌ Empty test
it("does something", () => {});

// ❌ Test with placeholder
it("validates input", () => {
  // add real assertions here
  expect(true).toBe(true);
});

// ❌ Skipped test
it.skip("broken test", () => { ... });
All of these should either be:
  1. Implemented properly
  2. Deleted entirely