Skip to content

Commit 910fe75

Browse files
lite3-zigclaude
andcommitted
Add fuzz and property-based tests
- Random set/get round-trip (50 random keys) - JSON decode/re-encode idempotency (6 varied documents) - Oversized key boundary test (255 ok, 256 rejected) - All-types-in-one-object stress test - Context rapid grow/shrink realloc stress test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5ea9fd4 commit 910fe75

1 file changed

Lines changed: 135 additions & 0 deletions

File tree

src/tests.zig

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,3 +1413,138 @@ test "Buffer: keys accept runtime []const u8 slices" {
14131413
try testing.expectEqual(@as(i64, 42), val);
14141414
try testing.expect(try buf.exists(lite3.root, key));
14151415
}
1416+
1417+
// =========================================================================
1418+
// Fuzz / property-based tests
1419+
// =========================================================================
1420+
1421+
test "Fuzz: random set/get round-trip preserves values" {
1422+
// Generate many key-value pairs and verify all survive insertion.
1423+
var mem: [65536]u8 align(4) = undefined;
1424+
var buf = try lite3.Buffer.initObj(&mem);
1425+
1426+
var prng = std.Random.DefaultPrng.init(0xdeadbeef);
1427+
const rand = prng.random();
1428+
1429+
const num_keys = 50;
1430+
var keys: [num_keys][16]u8 = undefined;
1431+
var values: [num_keys]i64 = undefined;
1432+
1433+
for (0..num_keys) |i| {
1434+
// Generate a random 8-char key
1435+
for (&keys[i]) |*byte| {
1436+
byte.* = rand.intRangeAtMost(u8, 'a', 'z');
1437+
}
1438+
values[i] = rand.int(i64);
1439+
try buf.setI64(lite3.root, &keys[i], values[i]);
1440+
}
1441+
1442+
// Verify all values are retrievable
1443+
for (0..num_keys) |i| {
1444+
const val = try buf.getI64(lite3.root, &keys[i]);
1445+
try testing.expectEqual(values[i], val);
1446+
}
1447+
}
1448+
1449+
test "Fuzz: JSON decode of random valid documents" {
1450+
if (!lite3.json_enabled) return;
1451+
1452+
// Test that well-formed JSON documents decode and re-encode consistently.
1453+
const test_jsons = [_][]const u8{
1454+
\\{"a":1,"b":2,"c":3}
1455+
,
1456+
\\{"nested":{"x":true,"y":false},"arr":[1,2,3]}
1457+
,
1458+
\\{"empty_obj":{},"empty_arr":[],"null_val":null}
1459+
,
1460+
\\{"str":"hello \"world\"","num":-42,"float":3.14}
1461+
,
1462+
\\[1,2,3,"four",true,null,{"key":"val"}]
1463+
,
1464+
\\{"unicode":"caf\u00e9","escape":"line\nbreak"}
1465+
,
1466+
};
1467+
1468+
for (test_jsons) |json| {
1469+
var mem: [16384]u8 align(4) = undefined;
1470+
var buf = lite3.Buffer.jsonDecode(&mem, json) catch continue;
1471+
1472+
// Re-encode should succeed
1473+
const encoded = try buf.jsonEncode(lite3.root);
1474+
defer encoded.deinit();
1475+
try testing.expect(encoded.slice().len > 0);
1476+
1477+
// Decode the re-encoded JSON and re-encode again — should be stable
1478+
var mem2: [16384]u8 align(4) = undefined;
1479+
var buf2 = try lite3.Buffer.jsonDecode(&mem2, encoded.slice());
1480+
const encoded2 = try buf2.jsonEncode(lite3.root);
1481+
defer encoded2.deinit();
1482+
1483+
try testing.expectEqualStrings(encoded.slice(), encoded2.slice());
1484+
}
1485+
}
1486+
1487+
test "Fuzz: oversized key returns InvalidArgument" {
1488+
var mem: [8192]u8 align(4) = undefined;
1489+
var buf = try lite3.Buffer.initObj(&mem);
1490+
1491+
// Key exactly at max (255 bytes) should work
1492+
const max_key = "k" ** 255;
1493+
try buf.setI64(lite3.root, max_key, 1);
1494+
try testing.expectEqual(@as(i64, 1), try buf.getI64(lite3.root, max_key));
1495+
1496+
// Key at 256 bytes should fail
1497+
const too_long = "k" ** 256;
1498+
try testing.expectError(lite3.Error.InvalidArgument, buf.setI64(lite3.root, too_long, 2));
1499+
try testing.expectError(lite3.Error.InvalidArgument, buf.getI64(lite3.root, too_long));
1500+
try testing.expectError(lite3.Error.InvalidArgument, buf.exists(lite3.root, too_long));
1501+
}
1502+
1503+
test "Fuzz: many types in single object" {
1504+
// Stress test: insert every type into one object and verify.
1505+
var mem: [65536]u8 align(4) = undefined;
1506+
var buf = try lite3.Buffer.initObj(&mem);
1507+
1508+
try buf.setNull(lite3.root, "null_val");
1509+
try buf.setBool(lite3.root, "bool_val", true);
1510+
try buf.setI64(lite3.root, "i64_val", std.math.minInt(i64));
1511+
try buf.setF64(lite3.root, "f64_val", std.math.floatMax(f64));
1512+
try buf.setStr(lite3.root, "str_val", "hello");
1513+
try buf.setBytes(lite3.root, "bytes_val", &[_]u8{ 0x00, 0xFF, 0x80 });
1514+
const obj = try buf.setObj(lite3.root, "obj_val");
1515+
try buf.setI64(obj, "inner", 42);
1516+
const arr = try buf.setArr(lite3.root, "arr_val");
1517+
try buf.arrAppendI64(arr, 1);
1518+
try buf.arrAppendStr(arr, "two");
1519+
1520+
try testing.expectEqual(lite3.Type.null, try buf.getType(lite3.root, "null_val"));
1521+
try testing.expectEqual(true, try buf.getBool(lite3.root, "bool_val"));
1522+
try testing.expectEqual(std.math.minInt(i64), try buf.getI64(lite3.root, "i64_val"));
1523+
try testing.expectEqual(std.math.floatMax(f64), try buf.getF64(lite3.root, "f64_val"));
1524+
try testing.expectEqualStrings("hello", try buf.getStr(lite3.root, "str_val"));
1525+
try testing.expectEqualSlices(u8, &[_]u8{ 0x00, 0xFF, 0x80 }, try buf.getBytes(lite3.root, "bytes_val"));
1526+
try testing.expectEqual(@as(i64, 42), try buf.getI64(obj, "inner"));
1527+
try testing.expectEqual(@as(i64, 1), try buf.arrGetI64(arr, 0));
1528+
try testing.expectEqualStrings("two", try buf.arrGetStr(arr, 1));
1529+
try testing.expectEqual(@as(u32, 8), try buf.count(lite3.root));
1530+
}
1531+
1532+
test "Fuzz: Context rapid grow/shrink cycle" {
1533+
// Repeatedly add and overwrite keys to exercise Context's realloc path.
1534+
var ctx = try lite3.Context.create();
1535+
defer ctx.destroy();
1536+
try ctx.initObj();
1537+
1538+
for (0..20) |round| {
1539+
// Add many keys with large values to force growth
1540+
for (0..50) |i| {
1541+
var key_buf: [16]u8 = undefined;
1542+
const key = std.fmt.bufPrint(&key_buf, "k{d}_{d}", .{ round, i }) catch unreachable;
1543+
try ctx.setStr(lite3.root, key, "x" ** 200);
1544+
}
1545+
}
1546+
1547+
// Verify the context is still usable
1548+
try ctx.setI64(lite3.root, "final", 999);
1549+
try testing.expectEqual(@as(i64, 999), try ctx.getI64(lite3.root, "final"));
1550+
}

0 commit comments

Comments
 (0)