Skip to documentation
SLOP

tiny.trace.session

Reference tiny.trace session

Defined in tiny.trace.

API (2)

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.tracesession
Static calls · unresolved targets: unknown · external targets: unknown.

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

Definitions1
Public names1
Members0
Version26.7.0
Revisiondaab053ee433