lib/trace/src/properties/store.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const hypothesis = @import("hypothesis");
  3 const sys = @import("sys");
  4 const trace = @import("trace");
  5 
  6 const Allocator = std.mem.Allocator;
  7 const event = trace.event;
  8 const TraceReader = trace.TraceReader;
  9 const TraceReaderLimits = trace.TraceReaderLimits;
 10 const TraceReaderStorage = trace.TraceReaderStorage;
 11 const TraceWriter = trace.TraceWriter;
 12 const TraceWriterLimits = trace.TraceWriterLimits;
 13 const TraceWriterStorage = trace.TraceWriterStorage;
 14 
 15 const text_values = [_][]const u8{
 16     "",
 17     "run",
 18     "env.TEST",
 19     "checkpoint",
 20     "quote\"value",
 21     "slash\\path",
 22     "line\nbreak",
 23     "tab\tvalue",
 24     "\x00control",
 25 };
 26 
 27 const payload_values = [_][]const u8{
 28     "",
 29     "value",
 30     "tiny\x00trace",
 31     "\x00\x01\x02\xfe\xff",
 32     "{\"protocol\":\"tiny.trace/v1\",\"elapsed_ns\":7}",
 33 };
 34 
 35 const u64_values = [_]u64{
 36     0,
 37     1,
 38     2,
 39     7,
 40     42,
 41     std.math.maxInt(i64),
 42     @as(u64, @intCast(std.math.maxInt(i64))) + 1,
 43     std.math.maxInt(u64),
 44 };
 45 
 46 const i64_values = [_]i64{
 47     std.math.minInt(i64),
 48     -1,
 49     0,
 50     1,
 51     42,
 52     std.math.maxInt(i64),
 53 };
 54 
 55 const alignment_values = [_]u32{
 56     0,
 57     1,
 58     2,
 59     8,
 60     16,
 61     4096,
 62     std.math.maxInt(u32),
 63 };
 64 
 65 pub fn settings() hypothesis.Settings {
 66     return hypothesis.Settings.quick()
 67         .withSeed(0x5452_4143_4550_4254)
 68         .withDatabase("zig-out/hypothesis-failures/trace");
 69 }
 70 
 71 fn drawUsize(
 72     conjecture: *hypothesis.ConjectureData,
 73     min: usize,
 74     max: usize,
 75     shrink_towards: usize,
 76 ) !usize {
 77     return @intCast(try conjecture.drawInteger(
 78         @intCast(min),
 79         @intCast(max),
 80         @intCast(shrink_towards),
 81     ));
 82 }
 83 
 84 fn drawU64(conjecture: *hypothesis.ConjectureData) !u64 {
 85     return u64_values[try drawUsize(conjecture, 0, u64_values.len - 1, 0)];
 86 }
 87 
 88 fn drawI64(conjecture: *hypothesis.ConjectureData) !i64 {
 89     return i64_values[try drawUsize(conjecture, 0, i64_values.len - 1, 2)];
 90 }
 91 
 92 fn drawText(conjecture: *hypothesis.ConjectureData) ![]const u8 {
 93     return text_values[try drawUsize(conjecture, 0, text_values.len - 1, 1)];
 94 }
 95 
 96 fn drawPayload(conjecture: *hypothesis.ConjectureData) ![]const u8 {
 97     return payload_values[try drawUsize(conjecture, 0, payload_values.len - 1, 1)];
 98 }
 99 
100 fn drawTimepoint(
101     conjecture: *hypothesis.ConjectureData,
102     fallback_seq: usize,
103 ) !event.Timepoint {
104     return .{
105         .epoch = try drawU64(conjecture),
106         .thread_id = @intCast(try drawUsize(conjecture, 0, 4, 1)),
107         .seq = if (try conjecture.drawBoolean())
108             @intCast(fallback_seq + 1)
109         else
110             try drawU64(conjecture),
111     };
112 }
113 
114 fn drawSafepoint(conjecture: *hypothesis.ConjectureData) !event.Safepoint {
115     return .{
116         .function_id = try drawU64(conjecture),
117         .site_id = try drawU64(conjecture),
118         .stack_map_id = try drawU64(conjecture),
119     };
120 }
121 
122 fn drawEvent(
123     conjecture: *hypothesis.ConjectureData,
124     index: usize,
125 ) !event.Event {
126     const timepoint = try drawTimepoint(conjecture, index);
127     return switch (try drawUsize(conjecture, 0, 10, 4)) {
128         0 => event.Event.sessionStart(timepoint, try drawText(conjecture)),
129         1 => event.Event.sessionEnd(timepoint, try drawI64(conjecture)),
130         2 => event.Event.functionEnter(timepoint, try drawSafepoint(conjecture)),
131         3 => event.Event.functionExit(timepoint, try drawSafepoint(conjecture)),
132         4 => event.Event.safepointReached(timepoint, try drawSafepoint(conjecture)),
133         5 => event.Event.boundaryBytes(timepoint, try drawText(conjecture), try drawPayload(conjecture)),
134         6 => event.Event.allocation(
135             timepoint,
136             try drawU64(conjecture),
137             try drawU64(conjecture),
138             alignment_values[try drawUsize(conjecture, 0, alignment_values.len - 1, 3)],
139             if (try conjecture.drawBoolean()) try drawText(conjecture) else null,
140         ),
141         7 => event.Event.free(timepoint, try drawU64(conjecture)),
142         8 => event.Event.checkpoint(timepoint, try drawText(conjecture), try drawPayload(conjecture)),
143         9 => event.Event.checkpointRestore(timepoint, try drawText(conjecture)),
144         else => event.Event.user(timepoint, try drawText(conjecture), try drawPayload(conjecture)),
145     };
146 }
147 
148 fn drawEvents(
149     conjecture: *hypothesis.ConjectureData,
150     property_allocator: Allocator,
151 ) ![]event.Event {
152     const count = try drawUsize(conjecture, 0, 24, 4);
153     const events = try property_allocator.alloc(event.Event, count);
154     for (events, 0..) |*item, index| {
155         item.* = try drawEvent(conjecture, index);
156     }
157     return events;
158 }
159 
160 fn expectJsonRoundtrip(
161     allocator: Allocator,
162     original: event.Event,
163 ) !void {
164     var out = std.Io.Writer.Allocating.init(allocator);
165     defer out.deinit();
166     try original.writeJsonLine(&out.writer);
167     const bytes = try out.toOwnedSlice();
168     defer allocator.free(bytes);
169 
170     var parsed = try event.Event.fromJsonLine(allocator, std.mem.trimEnd(u8, bytes, "\n"));
171     defer parsed.deinit(allocator);
172 
173     try std.testing.expect(original.eqlForReplay(parsed));
174 }
175 
176 pub const TracePersistenceProperty = struct {
177     pub fn property(
178         conjecture: *hypothesis.ConjectureData,
179         property_allocator: Allocator,
180     ) !void {
181         const events = try drawEvents(conjecture, property_allocator);
182         defer property_allocator.free(events);
183 
184         for (events) |item| {
185             try expectJsonRoundtrip(property_allocator, item);
186         }
187 
188         var tmp = std.testing.tmpDir(.{});
189         defer tmp.cleanup();
190 
191         const tmp_root = try tmp.parent_dir.realPathFileAlloc(
192             sys.fs.debugIo(),
193             tmp.sub_path[0..],
194             property_allocator,
195         );
196         defer property_allocator.free(tmp_root);
197 
198         const trace_path = try sys.path.join(property_allocator, &.{ tmp_root, "property.trace" });
199         defer property_allocator.free(trace_path);
200 
201         const manifest = trace.Manifest{ .build_id = "trace-pbt" };
202         const limits = trace.TraceLimits{
203             .max_event_bytes = 4096,
204             .max_chunk_bytes = 16 * 1024,
205         };
206         var writer_storage = try TraceWriterStorage.init(
207             property_allocator,
208             TraceWriterLimits.fromOpen(trace_path, manifest, limits),
209         );
210         defer writer_storage.deinit(property_allocator);
211         writer_storage.activate();
212         var trace_writer: TraceWriter = .{};
213         try trace_writer.open(&writer_storage, trace_path, manifest, limits);
214         defer trace_writer.deinit();
215         for (events) |item| try trace_writer.append(item);
216         try trace_writer.finish();
217 
218         var reader_storage = try TraceReaderStorage.init(
219             property_allocator,
220             TraceReaderLimits.fromOpen(trace_path, limits),
221         );
222         defer reader_storage.deinit(property_allocator);
223         reader_storage.activate();
224         const verified = try trace.store.verify(&reader_storage, trace_path);
225         try std.testing.expectEqual(@as(u64, @intCast(events.len)), verified.event_count);
226 
227         var trace_reader: TraceReader = .{};
228         try trace_reader.open(&reader_storage, trace_path);
229         defer trace_reader.deinit();
230         const source = trace_reader.source();
231         var index: usize = 0;
232         while (try source.peek()) |item| {
233             try std.testing.expect(index < events.len);
234             try std.testing.expect(events[index].eqlForReplay(item.*));
235             source.advance();
236             index += 1;
237         }
238         try std.testing.expectEqual(events.len, index);
239     }
240 };
241 
242 test "property: trace events persist with replay identity" {
243     try hypothesis.checkNamed(
244         TracePersistenceProperty,
245         "trace-persistence",
246         settings(),
247     );
248 }