Skip to content

Commit ead894a

Browse files
committed
refactor(test): consolidate tests into source files following Zig idioms
Moved all UUID-related tests from separate files into src/uuid/uuid.zig. Zig convention is to co-locate tests with implementation code. Added tests: - RFC 9562 compliance (bit layout, version, variant) - Collision safety (single/multiple/multi-threaded scenarios) - Monotonicity guarantees (10K UUID verification) - String format compliance Removed separate test files: - src/test_collision.zig - src/test_rfc_compliance.zig
1 parent db534eb commit ead894a

File tree

3 files changed

+155
-150
lines changed

3 files changed

+155
-150
lines changed

build.zig

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,6 @@ pub fn build(b: *std.Build) void {
1515
const run_tests = b.addRunArtifact(tests);
1616
test_step.dependOn(&run_tests.step);
1717

18-
// Test for collision safety
19-
const test_collision = b.addTest(.{
20-
.root_source_file = b.path("src/test_collision.zig"),
21-
.target = target,
22-
.optimize = optimize,
23-
});
24-
const test_collision_step = b.step("test-collision", "Run collision safety test");
25-
const run_test_collision = b.addRunArtifact(test_collision);
26-
test_collision_step.dependOn(&run_test_collision.step);
27-
2818
// Benchmark for UUID generation (demonstrates per-generator mutex performance)
2919
const bench_uuid = b.addExecutable(.{
3020
.name = "bench_uuid_contention",

src/test_collision.zig

Lines changed: 0 additions & 140 deletions
This file was deleted.

src/uuid/uuid.zig

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,158 @@ test "UUIDv7 format" {
127127

128128
try std.testing.expectEqualStrings(uuid_str_want, uuid_str_got);
129129
}
130+
131+
test "RFC 9562 - UUIDv7 bit layout compliance" {
132+
var gen = initSecure();
133+
const test_uuid = try gen.next();
134+
var bytes: [16]u8 = undefined;
135+
std.mem.writeInt(u128, &bytes, test_uuid, .big);
136+
137+
// Verify version field (bits 48-51, octet 6 high nibble) = 0b0111 (7)
138+
const version = (bytes[6] & 0xF0) >> 4;
139+
try std.testing.expectEqual(@as(u8, 0x7), version);
140+
141+
// Verify variant field (bits 64-65, octet 8 MSBs) = 0b10
142+
const variant = (bytes[8] & 0xC0) >> 6;
143+
try std.testing.expectEqual(@as(u8, 0b10), variant);
144+
145+
// Verify timestamp exists (bits 0-47, octets 0-5)
146+
const timestamp = (@as(u64, bytes[0]) << 40) |
147+
(@as(u64, bytes[1]) << 32) |
148+
(@as(u64, bytes[2]) << 24) |
149+
(@as(u64, bytes[3]) << 16) |
150+
(@as(u64, bytes[4]) << 8) |
151+
(@as(u64, bytes[5]));
152+
try std.testing.expect(timestamp > 0);
153+
}
154+
155+
test "RFC 9562 - monotonicity guarantee" {
156+
var gen = initSecure();
157+
var last = try gen.next();
158+
159+
// Verify monotonic ordering across 10,000 UUIDs
160+
for (0..10_000) |_| {
161+
const current = try gen.next();
162+
try std.testing.expect(current > last);
163+
last = current;
164+
}
165+
}
166+
167+
test "RFC 9562 - string format compliance" {
168+
var gen = initSecure();
169+
const test_uuid = try gen.next();
170+
var buf: [36]u8 = undefined;
171+
const uuid_str = toString(test_uuid, &buf);
172+
173+
// Verify length and dash positions per RFC format:
174+
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
175+
try std.testing.expectEqual(@as(usize, 36), uuid_str.len);
176+
try std.testing.expectEqual(@as(u8, '-'), uuid_str[8]);
177+
try std.testing.expectEqual(@as(u8, '-'), uuid_str[13]);
178+
try std.testing.expectEqual(@as(u8, '-'), uuid_str[18]);
179+
try std.testing.expectEqual(@as(u8, '-'), uuid_str[23]);
180+
181+
// Verify all non-dash characters are hexadecimal
182+
for (uuid_str, 0..) |c, i| {
183+
if (i == 8 or i == 13 or i == 18 or i == 23) continue;
184+
const is_hex = (c >= '0' and c <= '9') or
185+
(c >= 'a' and c <= 'f') or
186+
(c >= 'A' and c <= 'F');
187+
try std.testing.expect(is_hex);
188+
}
189+
}
190+
191+
test "collision safety - single generator" {
192+
var gen = initSecure();
193+
var seen = std.AutoHashMap(Uuid, void).init(std.testing.allocator);
194+
defer seen.deinit();
195+
196+
const count = 100_000;
197+
var last: Uuid = 0;
198+
199+
for (0..count) |_| {
200+
const id = try gen.next();
201+
202+
// Check monotonicity (must always increase)
203+
try std.testing.expect(id > last);
204+
last = id;
205+
206+
// Check for duplicates
207+
const result = try seen.getOrPut(id);
208+
try std.testing.expect(!result.found_existing);
209+
}
210+
}
211+
212+
test "collision safety - multiple generators produce different UUIDs" {
213+
var gen1 = initSecure();
214+
var gen2 = initSecure();
215+
216+
const id1 = try gen1.next();
217+
const id2 = try gen2.next();
218+
219+
// Different generators should produce different UUIDs
220+
// (62 bits of randomness make collisions virtually impossible)
221+
try std.testing.expect(id1 != id2);
222+
}
223+
224+
test "collision safety - multi-threaded generation" {
225+
const ThreadContext = struct {
226+
gen: Generator,
227+
ids: std.ArrayList(Uuid),
228+
229+
fn worker(ctx: *@This()) !void {
230+
for (0..1000) |_| {
231+
const id = try ctx.gen.next();
232+
try ctx.ids.append(id);
233+
}
234+
}
235+
};
236+
237+
const num_threads = 8;
238+
var threads: [num_threads]std.Thread = undefined;
239+
var contexts: [num_threads]ThreadContext = undefined;
240+
241+
// Initialize contexts
242+
for (0..num_threads) |i| {
243+
contexts[i] = .{
244+
.gen = initSecure(),
245+
.ids = std.ArrayList(Uuid).init(std.testing.allocator),
246+
};
247+
}
248+
defer {
249+
for (0..num_threads) |i| {
250+
contexts[i].ids.deinit();
251+
}
252+
}
253+
254+
// Spawn threads
255+
for (0..num_threads) |i| {
256+
threads[i] = try std.Thread.spawn(.{}, ThreadContext.worker, .{&contexts[i]});
257+
}
258+
259+
// Wait for completion
260+
for (0..num_threads) |i| {
261+
threads[i].join();
262+
}
263+
264+
// Check each generator's UUIDs are monotonic
265+
for (contexts) |ctx| {
266+
var last: Uuid = 0;
267+
for (ctx.ids.items) |id| {
268+
try std.testing.expect(id > last);
269+
last = id;
270+
}
271+
}
272+
273+
// Collect all UUIDs and check for duplicates
274+
var all_ids = std.AutoHashMap(Uuid, void).init(std.testing.allocator);
275+
defer all_ids.deinit();
276+
277+
for (contexts) |ctx| {
278+
for (ctx.ids.items) |id| {
279+
const result = try all_ids.getOrPut(id);
280+
try std.testing.expect(!result.found_existing);
281+
}
282+
}
283+
}
284+

0 commit comments

Comments
 (0)