tiny.trace.session
Defined in tiny.trace.
API (2)
Types and contracts
Public types and contracts.
Source
Source: lib/trace/src/root.zig:33
zig
pub const session = @import("session.zig");Source: lib/trace/src/session.zig
zig
const std = @import("std");const event = @import("event.zig");const checkpoint = @import("checkpoint.zig");const Allocator = std.mem.Allocator;pub const ReplayProgress = struct { cursor: u64, event_count: u64, remaining_count: u64, pub fn complete(self: ReplayProgress) bool { return self.remaining_count == 0; }};pub const Session = struct { allocator: Allocator, mode: event.Mode, sink: ?event.Sink = null, source: ?event.Source = null, epoch: u64 = 0, next_seq: u64 = 1, sequence_exhausted: bool = false, replay_cursor: u64 = 0, last_checkpoint: ?event.Event = null, pub fn initOff(allocator: Allocator) Session { return .{ .allocator = allocator, .mode = .off }; } pub fn initRecord(allocator: Allocator, sink: event.Sink) Session { return .{ .allocator = allocator, .mode = .record, .sink = sink }; } pub fn initReplay(allocator: Allocator, source: event.Source) Session { return .{ .allocator = allocator, .mode = .replay, .source = source }; } pub fn deinit(self: *Session) void { if (self.last_checkpoint) |*item| item.deinit(self.allocator); self.* = undefined; } pub fn replayProgress(self: *const Session) ReplayProgress { const event_count = if (self.source) |source| source.count() else 0; std.debug.assert(self.replay_cursor <= event_count); return .{ .cursor = self.replay_cursor, .event_count = event_count, .remaining_count = event_count - self.replay_cursor, }; } pub fn sessionStart( self: *Session, thread_id: event.ThreadId, label: []const u8, ) !event.Timepoint { const timepoint = try self.prepareTimepoint(thread_id); return try self.handleEvent(event.Event.sessionStart(timepoint, label)); } pub fn sessionEnd( self: *Session, thread_id: event.ThreadId, status: i64, ) !event.Timepoint { const timepoint = try self.prepareTimepoint(thread_id); return try self.handleEvent(event.Event.sessionEnd(timepoint, status)); } pub fn functionEnter( self: *Session, thread_id: event.ThreadId, site: event.Safepoint, ) !event.Timepoint { const timepoint = try self.prepareTimepoint(thread_id); return try self.handleEvent(event.Event.functionEnter(timepoint, site)); } pub fn functionExit( self: *Session, thread_id: event.ThreadId, site: event.Safepoint, ) !event.Timepoint { const timepoint = try self.prepareTimepoint(thread_id); return try self.handleEvent(event.Event.functionExit(timepoint, site)); } pub fn safepoint( self: *Session, thread_id: event.ThreadId, site: event.Safepoint, ) !event.Timepoint { const timepoint = try self.prepareTimepoint(thread_id); return try self.handleEvent(event.Event.safepointReached(timepoint, site)); } pub fn allocation( self: *Session, thread_id: event.ThreadId, object_id: u64, size: u64, alignment: u32, label: ?[]const u8, ) !event.Timepoint { const timepoint = try self.prepareTimepoint(thread_id); return try self.handleEvent(event.Event.allocation( timepoint, object_id, size, alignment, label, )); } pub fn free( self: *Session, thread_id: event.ThreadId, object_id: u64, ) !event.Timepoint { const timepoint = try self.prepareTimepoint(thread_id); return try self.handleEvent(event.Event.free(timepoint, object_id)); } pub fn boundaryBytesAlloc( self: *Session, thread_id: event.ThreadId, operation: []const u8, recorded_value: ?[]const u8, ) ![]u8 { const timepoint = try self.prepareTimepoint(thread_id); switch (self.mode) { .off => { const source_value = recorded_value orelse return error.MissingBoundaryValue; const value = try self.allocator.dupe(u8, source_value); self.commitTimepoint(timepoint); return value; }, .record => { const value = recorded_value orelse return error.MissingBoundaryValue; const owned = try self.allocator.dupe(u8, value); errdefer self.allocator.free(owned); try self.sink.?.append(event.Event.boundaryBytes(timepoint, operation, value)); self.commitTimepoint(timepoint); return owned; }, .replay => { const source = self.source.?; const actual = (try source.peek()) orelse return error.MissingReplayEvent; const recorded = actual.data orelse return error.ReplayEventMismatch; const expected = event.Event.boundaryBytes(timepoint, operation, recorded); if (!expected.eqlForReplay(actual.*)) return error.ReplayEventMismatch; const next_replay_cursor = try self.prepareReplayCursor(); const value = try self.allocator.dupe(u8, recorded); source.advance(); self.replay_cursor = next_replay_cursor; self.commitTimepoint(timepoint); return value; }, } } pub fn checkpointCapture( self: *Session, thread_id: event.ThreadId, label: []const u8, provider: checkpoint.Provider, ) !event.Timepoint { const timepoint = try self.prepareTimepoint(thread_id); const bytes = try provider.captureAlloc(self.allocator); defer self.allocator.free(bytes); return try self.handleEvent(event.Event.checkpoint(timepoint, label, bytes)); } pub fn checkpointRestore( self: *Session, thread_id: event.ThreadId, label: []const u8, provider: checkpoint.Provider, ) !event.Timepoint { const checkpoint_event = self.last_checkpoint orelse return error.MissingCheckpoint; const checkpoint_bytes = checkpoint_event.data orelse return error.MissingCheckpoint; const timepoint = try self.prepareTimepoint(thread_id); const expected = event.Event.checkpointRestore(timepoint, label); const prepared_replay = if (self.mode == .replay) try self.prepareReplay(expected) else null; const replay_timepoint = if (prepared_replay) |prepared| prepared.actual.timepoint else timepoint; provider.prepareRestoreBytes(checkpoint_bytes) catch |err| { provider.cancelRestore(); return err; }; errdefer provider.cancelRestore(); if (self.mode == .record) try self.sink.?.append(expected); provider.commitRestore(); if (prepared_replay) |prepared| self.commitReplay(prepared); self.commitTimepoint(timepoint); return replay_timepoint; } pub fn userEvent( self: *Session, thread_id: event.ThreadId, label: []const u8, bytes: []const u8, ) !event.Timepoint { const timepoint = try self.prepareTimepoint(thread_id); return try self.handleEvent(event.Event.user(timepoint, label, bytes)); } pub fn verifyReplayComplete(self: *Session) !void { if (self.mode != .replay) return; const source = self.source.?; if (self.replay_cursor != source.count()) return error.ReplayNotComplete; if (try source.peek() != null) return error.ReplayNotComplete; } fn prepareTimepoint(self: *const Session, thread_id: event.ThreadId) !event.Timepoint { if (self.sequence_exhausted) return error.SequenceExhausted; return event.Timepoint{ .epoch = self.epoch, .thread_id = thread_id, .seq = self.next_seq, }; } fn commitTimepoint(self: *Session, timepoint: event.Timepoint) void { std.debug.assert(!self.sequence_exhausted); std.debug.assert(timepoint.epoch == self.epoch); std.debug.assert(timepoint.seq == self.next_seq); if (self.next_seq == std.math.maxInt(u64)) { self.sequence_exhausted = true; } else { self.next_seq += 1; } } fn handleEvent(self: *Session, expected: event.Event) !event.Timepoint { var owned_checkpoint: ?event.Event = null; errdefer if (owned_checkpoint) |*owned| owned.deinit(self.allocator); var replay_timepoint: ?event.Timepoint = null; switch (self.mode) { .off => { if (expected.kind == .checkpoint) { owned_checkpoint = try expected.cloneAlloc(self.allocator); } }, .record => { if (expected.kind == .checkpoint) { owned_checkpoint = try expected.cloneAlloc(self.allocator); } try self.sink.?.append(expected); }, .replay => { const prepared = try self.prepareReplay(expected); replay_timepoint = prepared.actual.timepoint; if (prepared.actual.kind == .checkpoint) { owned_checkpoint = try prepared.actual.cloneAlloc(self.allocator); } self.commitReplay(prepared); }, } self.commitTimepoint(expected.timepoint); if (owned_checkpoint) |owned| { self.replaceCheckpoint(owned); owned_checkpoint = null; } return replay_timepoint orelse expected.timepoint; } const PreparedReplay = struct { source: event.Source, actual: *const event.Event, next_cursor: u64, }; fn prepareReplay(self: *Session, expected: event.Event) !PreparedReplay { const source = self.source.?; const actual = (try source.peek()) orelse return error.MissingReplayEvent; if (!expected.eqlForReplay(actual.*)) return error.ReplayEventMismatch; return .{ .source = source, .actual = actual, .next_cursor = try self.prepareReplayCursor(), }; } fn prepareReplayCursor(self: *const Session) !u64 { return std.math.add(u64, self.replay_cursor, 1) catch return error.ReplayCursorExhausted; } fn commitReplay(self: *Session, prepared: PreparedReplay) void { prepared.source.advance(); self.replay_cursor = prepared.next_cursor; } fn replaceCheckpoint(self: *Session, owned: event.Event) void { if (self.last_checkpoint) |*previous| previous.deinit(self.allocator); self.last_checkpoint = owned; }};const SliceStream = struct { events: []const event.Event, cursor: usize = 0, fn source(self: *SliceStream) event.Source { return .{ .context = self, .peekFn = peek, .advanceFn = advance, .countFn = count }; } fn peek(context: *anyopaque) !?*const event.Event { const self: *SliceStream = @ptrCast(@alignCast(context)); if (self.cursor == self.events.len) return null; return &self.events[self.cursor]; } fn advance(context: *anyopaque) void { const self: *SliceStream = @ptrCast(@alignCast(context)); self.cursor += 1; } fn count(context: *anyopaque) u64 { const self: *SliceStream = @ptrCast(@alignCast(context)); return self.events.len; }};const CaptureSink = struct { timepoints: [8]event.Timepoint = undefined, len: usize = 0, fn sink(self: *CaptureSink) event.Sink { return .{ .context = self, .appendFn = append }; } fn append(context: *anyopaque, item: event.Event) !void { const self: *CaptureSink = @ptrCast(@alignCast(context)); if (self.len == self.timepoints.len) return error.TestSinkFull; self.timepoints[self.len] = item.timepoint; self.len += 1; }};const FailOnceSink = struct { timepoints: [8]event.Timepoint = undefined, len: usize = 0, fail_next: bool = true, fn sink(self: *FailOnceSink) event.Sink { return .{ .context = self, .appendFn = append }; } fn append(context: *anyopaque, item: event.Event) !void { const self: *FailOnceSink = @ptrCast(@alignCast(context)); if (self.fail_next) { self.fail_next = false; return error.InjectedSinkFailure; } if (self.len == self.timepoints.len) return error.TestSinkFull; self.timepoints[self.len] = item.timepoint; self.len += 1; }};const FailOnceStream = struct { events: []const event.Event, cursor: usize = 0, fail_next: bool = true, fn source(self: *FailOnceStream) event.Source { return .{ .context = self, .peekFn = peek, .advanceFn = advance, .countFn = count }; } fn peek(context: *anyopaque) !?*const event.Event { const self: *FailOnceStream = @ptrCast(@alignCast(context)); if (self.fail_next) { self.fail_next = false; return error.InjectedSourceFailure; } if (self.cursor == self.events.len) return null; return &self.events[self.cursor]; } fn advance(context: *anyopaque) void { const self: *FailOnceStream = @ptrCast(@alignCast(context)); self.cursor += 1; } fn count(context: *anyopaque) u64 { const self: *FailOnceStream = @ptrCast(@alignCast(context)); return self.events.len; }};const ProviderProbe = struct { value: [32]u8 = undefined, value_len: usize = 0, staged: [32]u8 = undefined, staged_len: usize = 0, prepared: bool = false, prepare_calls: u64 = 0, commit_calls: u64 = 0, cancel_calls: u64 = 0, fail_prepare: bool = false, fn init(bytes: []const u8) ProviderProbe { var self: ProviderProbe = .{}; self.setValue(bytes); return self; } fn setValue(self: *ProviderProbe, bytes: []const u8) void { std.debug.assert(bytes.len <= self.value.len); @memcpy(self.value[0..bytes.len], bytes); self.value_len = bytes.len; } fn current(self: *const ProviderProbe) []const u8 { return self.value[0..self.value_len]; } fn provider(self: *ProviderProbe) checkpoint.Provider { return .{ .context = self, .capture = capture, .prepare_restore = prepareRestore, .commit_restore = commitRestore, .cancel_restore = cancelRestore, }; } fn capture(context: *anyopaque, allocator: Allocator) ![]u8 { const self: *ProviderProbe = @ptrCast(@alignCast(context)); return try allocator.dupe(u8, self.current()); } fn prepareRestore(context: *anyopaque, bytes: []const u8) !void { const self: *ProviderProbe = @ptrCast(@alignCast(context)); self.prepare_calls += 1; if (self.fail_prepare) return error.InjectedProviderFailure; if (bytes.len > self.staged.len) return error.TestProviderCapacityExceeded; @memcpy(self.staged[0..bytes.len], bytes); self.staged_len = bytes.len; self.prepared = true; } fn commitRestore(context: *anyopaque) void { const self: *ProviderProbe = @ptrCast(@alignCast(context)); std.debug.assert(self.prepared); @memcpy(self.value[0..self.staged_len], self.staged[0..self.staged_len]); self.value_len = self.staged_len; self.prepared = false; self.commit_calls += 1; } fn cancelRestore(context: *anyopaque) void { const self: *ProviderProbe = @ptrCast(@alignCast(context)); self.prepared = false; self.cancel_calls += 1; }};test "recording streams events without retaining copies" { var capture: CaptureSink = .{}; var session = Session.initRecord(std.testing.allocator, capture.sink()); defer session.deinit(); const start = try session.sessionStart(1, "test"); const enter = try session.functionEnter(1, .{ .function_id = 10, .site_id = 1 }); const exit = try session.functionExit(1, .{ .function_id = 10, .site_id = 2 }); try std.testing.expectEqual(@as(u64, 1), start.seq); try std.testing.expectEqual(@as(u64, 2), enter.seq); try std.testing.expectEqual(@as(u64, 3), exit.seq); try std.testing.expectEqual(@as(usize, 3), capture.len);}test "session failure atomicity retries a failed record append without a sequence gap" { var capture: FailOnceSink = .{}; var session = Session.initRecord(std.testing.allocator, capture.sink()); defer session.deinit(); try std.testing.expectError( error.InjectedSinkFailure, session.sessionStart(1, "test"), ); try std.testing.expectEqual(@as(usize, 0), capture.len); const retried = try session.sessionStart(1, "test"); try std.testing.expectEqual(@as(u64, 1), retried.seq); try std.testing.expectEqual(@as(usize, 1), capture.len); try std.testing.expectEqual(@as(u64, 1), capture.timepoints[0].seq);}test "session failure atomicity retries a failed source peek" { const events = [_]event.Event{ event.Event.sessionStart(.{ .thread_id = 1, .seq = 1 }, "test"), }; var stream = FailOnceStream{ .events = &events }; var session = Session.initReplay(std.testing.allocator, stream.source()); defer session.deinit(); try std.testing.expectError( error.InjectedSourceFailure, session.sessionStart(1, "test"), ); try std.testing.expectEqual(@as(usize, 0), stream.cursor); try std.testing.expectEqual(@as(u64, 0), session.replayProgress().cursor); const retried = try session.sessionStart(1, "test"); try std.testing.expectEqual(@as(u64, 1), retried.seq); try session.verifyReplayComplete();}test "replay validates a borrowed stream and exposes numeric progress" { const events = [_]event.Event{ event.Event.sessionStart(.{ .thread_id = 1, .seq = 1 }, "test"), event.Event.safepointReached(.{ .thread_id = 1, .seq = 2 }, .{ .function_id = 20, .site_id = 5 }), }; var stream = SliceStream{ .events = &events }; var session = Session.initReplay(std.testing.allocator, stream.source()); defer session.deinit(); var progress = session.replayProgress(); try std.testing.expectEqual(@as(u64, 0), progress.cursor); try std.testing.expectEqual(@as(u64, 2), progress.remaining_count); _ = try session.sessionStart(1, "test"); progress = session.replayProgress(); try std.testing.expectEqual(@as(u64, 1), progress.cursor); try std.testing.expectEqual(@as(u64, 1), progress.remaining_count); _ = try session.safepoint(1, .{ .function_id = 20, .site_id = 5 }); try session.verifyReplayComplete(); try std.testing.expect(session.replayProgress().complete());}test "session failure atomicity allocates a record boundary before append" { var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0, }); var capture: CaptureSink = .{}; var session = Session.initRecord(failing.allocator(), capture.sink()); defer session.deinit(); try std.testing.expectError( error.OutOfMemory, session.boundaryBytesAlloc(1, "env.TEST", "value"), ); try std.testing.expectEqual(@as(usize, 0), capture.len); failing.fail_index = std.math.maxInt(usize); failing.resize_fail_index = std.math.maxInt(usize); const retried = try session.boundaryBytesAlloc(1, "env.TEST", "value"); defer failing.allocator().free(retried); try std.testing.expectEqualStrings("value", retried); try std.testing.expectEqual(@as(usize, 1), capture.len); try std.testing.expectEqual(@as(u64, 1), capture.timepoints[0].seq);}test "session failure atomicity retries boundary replay after allocation failure" { const events = [_]event.Event{ event.Event.boundaryBytes(.{ .thread_id = 1, .seq = 1 }, "env.TEST", "value"), }; var stream = SliceStream{ .events = &events }; var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0, }); var session = Session.initReplay(failing.allocator(), stream.source()); defer session.deinit(); try std.testing.expectError( error.OutOfMemory, session.boundaryBytesAlloc(1, "env.TEST", null), ); try std.testing.expectEqual(@as(usize, 0), stream.cursor); try std.testing.expectEqual(@as(u64, 0), session.replayProgress().cursor); failing.fail_index = std.math.maxInt(usize); failing.resize_fail_index = std.math.maxInt(usize); const retried = try session.boundaryBytesAlloc(1, "env.TEST", null); defer failing.allocator().free(retried); try std.testing.expectEqualStrings("value", retried); try session.verifyReplayComplete();}test "session failure atomicity rejects noncanonical boundary fields" { var events = [_]event.Event{ event.Event.boundaryBytes(.{ .thread_id = 1, .seq = 1 }, "env.TEST", "value"), }; events[0].label = "unexpected"; var stream = SliceStream{ .events = &events }; var session = Session.initReplay(std.testing.allocator, stream.source()); defer session.deinit(); try std.testing.expectError( error.ReplayEventMismatch, session.boundaryBytesAlloc(1, "env.TEST", null), ); try std.testing.expectEqual(@as(usize, 0), stream.cursor); try std.testing.expectEqual(@as(u64, 0), session.replayProgress().cursor); events[0].label = null; const retried = try session.boundaryBytesAlloc(1, "env.TEST", null); defer std.testing.allocator.free(retried); try std.testing.expectEqualStrings("value", retried); try session.verifyReplayComplete();}test "session failure atomicity requires a live boundary value while off" { var session = Session.initOff(std.testing.allocator); defer session.deinit(); try std.testing.expectError( error.MissingBoundaryValue, session.boundaryBytesAlloc(1, "env.TEST", null), ); const retried = try session.boundaryBytesAlloc(1, "env.TEST", "value"); defer std.testing.allocator.free(retried); try std.testing.expectEqualStrings("value", retried); const next = try session.sessionEnd(1, 0); try std.testing.expectEqual(@as(u64, 2), next.seq);}test "replay mismatch leaves the session retryable" { const events = [_]event.Event{ event.Event.safepointReached(.{ .thread_id = 1, .seq = 1 }, .{ .function_id = 20, .site_id = 5 }), }; var stream = SliceStream{ .events = &events }; var session = Session.initReplay(std.testing.allocator, stream.source()); defer session.deinit(); try std.testing.expectError( error.ReplayEventMismatch, session.safepoint(1, .{ .function_id = 21, .site_id = 5 }), ); try std.testing.expectEqual(@as(usize, 0), stream.cursor); try std.testing.expectEqual(@as(u64, 0), session.replayProgress().cursor); const retried = try session.safepoint(1, .{ .function_id = 20, .site_id = 5 }); try std.testing.expectEqual(@as(u64, 1), retried.seq); try session.verifyReplayComplete();}test "boundary replay copies borrowed bytes before advancing" { const events = [_]event.Event{ event.Event.boundaryBytes(.{ .thread_id = 1, .seq = 1 }, "env.TEST", "hello"), }; var stream = SliceStream{ .events = &events }; var session = Session.initReplay(std.testing.allocator, stream.source()); defer session.deinit(); const replayed = try session.boundaryBytesAlloc(1, "env.TEST", null); defer std.testing.allocator.free(replayed); try std.testing.expectEqualStrings("hello", replayed); try session.verifyReplayComplete();}test "session failure atomicity validates restore before staging provider state" { const events = [_]event.Event{ event.Event.checkpoint(.{ .thread_id = 1, .seq = 1 }, "saved", "checkpoint"), event.Event.checkpointRestore(.{ .thread_id = 1, .seq = 2 }, "restore"), }; var stream = SliceStream{ .events = &events }; var provider_probe = ProviderProbe.init("checkpoint"); var session = Session.initReplay(std.testing.allocator, stream.source()); defer session.deinit(); _ = try session.checkpointCapture(1, "saved", provider_probe.provider()); provider_probe.setValue("live"); try std.testing.expectError( error.ReplayEventMismatch, session.checkpointRestore(1, "wrong", provider_probe.provider()), ); try std.testing.expectEqualStrings("live", provider_probe.current()); try std.testing.expectEqual(@as(u64, 0), provider_probe.prepare_calls); try std.testing.expectEqual(@as(usize, 1), stream.cursor); try std.testing.expectEqual(@as(u64, 1), session.replayProgress().cursor); provider_probe.fail_prepare = true; try std.testing.expectError( error.InjectedProviderFailure, session.checkpointRestore(1, "restore", provider_probe.provider()), ); try std.testing.expectEqualStrings("live", provider_probe.current()); try std.testing.expect(!provider_probe.prepared); try std.testing.expectEqual(@as(u64, 1), provider_probe.cancel_calls); try std.testing.expectEqual(@as(usize, 1), stream.cursor); try std.testing.expectEqual(@as(u64, 1), session.replayProgress().cursor); provider_probe.fail_prepare = false; const restored = try session.checkpointRestore(1, "restore", provider_probe.provider()); try std.testing.expectEqual(@as(u64, 2), restored.seq); try std.testing.expectEqualStrings("checkpoint", provider_probe.current()); try std.testing.expectEqual(@as(u64, 2), provider_probe.prepare_calls); try std.testing.expectEqual(@as(u64, 1), provider_probe.commit_calls); try session.verifyReplayComplete();}test "session failure atomicity preserves a checkpoint when replacement allocation fails" { const events = [_]event.Event{ event.Event.checkpoint(.{ .thread_id = 1, .seq = 1 }, "first", "one"), event.Event.checkpoint(.{ .thread_id = 1, .seq = 2 }, "second", "two"), }; var stream = SliceStream{ .events = &events }; var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{}); var provider_probe = ProviderProbe.init("one"); var session = Session.initReplay(failing.allocator(), stream.source()); defer session.deinit(); _ = try session.checkpointCapture(1, "first", provider_probe.provider()); try std.testing.expectEqualStrings("one", session.last_checkpoint.?.data.?); provider_probe.setValue("two"); failing.fail_index = failing.alloc_index + 1; try std.testing.expectError( error.OutOfMemory, session.checkpointCapture(1, "second", provider_probe.provider()), ); try std.testing.expectEqualStrings("one", session.last_checkpoint.?.data.?); try std.testing.expectEqual(@as(usize, 1), stream.cursor); try std.testing.expectEqual(@as(u64, 1), session.replayProgress().cursor); failing.fail_index = std.math.maxInt(usize); failing.resize_fail_index = std.math.maxInt(usize); const retried = try session.checkpointCapture(1, "second", provider_probe.provider()); try std.testing.expectEqual(@as(u64, 2), retried.seq); try std.testing.expectEqualStrings("two", session.last_checkpoint.?.data.?); try session.verifyReplayComplete();}test "session failure atomicity cancels a staged restore when recording fails" { var capture: FailOnceSink = .{ .fail_next = false }; var provider_probe = ProviderProbe.init("checkpoint"); var session = Session.initRecord(std.testing.allocator, capture.sink()); defer session.deinit(); _ = try session.checkpointCapture(1, "saved", provider_probe.provider()); provider_probe.setValue("live"); capture.fail_next = true; try std.testing.expectError( error.InjectedSinkFailure, session.checkpointRestore(1, "restore", provider_probe.provider()), ); try std.testing.expectEqualStrings("live", provider_probe.current()); try std.testing.expect(!provider_probe.prepared); try std.testing.expectEqual(@as(u64, 1), provider_probe.cancel_calls); try std.testing.expectEqual(@as(usize, 1), capture.len); const restored = try session.checkpointRestore(1, "restore", provider_probe.provider()); try std.testing.expectEqual(@as(u64, 2), restored.seq); try std.testing.expectEqualStrings("checkpoint", provider_probe.current()); try std.testing.expectEqual(@as(usize, 2), capture.len); try std.testing.expectEqual(@as(u64, 2), capture.timepoints[1].seq);}test "checkpoint provider owns committed restore after checkpoint replacement" { var provider_probe = ProviderProbe.init("one"); { var session = Session.initOff(std.testing.allocator); defer session.deinit(); _ = try session.checkpointCapture(1, "first", provider_probe.provider()); provider_probe.setValue("live"); _ = try session.checkpointRestore(1, "restore", provider_probe.provider()); try std.testing.expectEqualStrings("one", provider_probe.current()); _ = try session.checkpointCapture(1, "second", provider_probe.provider()); try std.testing.expectEqualStrings("one", provider_probe.current()); } try std.testing.expectEqualStrings("one", provider_probe.current());}test "session sequence capacity admits the final timepoint exactly once" { var session = Session.initOff(std.testing.allocator); defer session.deinit(); session.next_seq = std.math.maxInt(u64); const final = try session.sessionStart(1, "last"); try std.testing.expectEqual(std.math.maxInt(u64), final.seq); try std.testing.expectError( error.SequenceExhausted, session.sessionEnd(1, 0), );}Audit
| Definitions | 1 |
|---|---|
| Public names | 1 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |