From c13a9bb49ced768d7da3f6aff2e6d31fbed4641e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Mar 2026 06:58:17 -0700 Subject: [PATCH] vt: add tests for write_pty and bell effect callbacks Test that the write_pty callback receives correct DECRQM response data and userdata, that queries are silently ignored without a callback, and that setting null clears the callback. Test that the bell callback fires on single and multiple BEL characters with correct userdata, and that BEL without a callback is safe. --- src/terminal/c/terminal.zig | 151 ++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index 10e5a2960..7a214d5b7 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -864,6 +864,157 @@ test "grid_ref null terminal" { }, &out_ref)); } +test "set write_pty callback" { + var t: Terminal = null; + try testing.expectEqual(Result.success, new( + &lib_alloc.test_allocator, + &t, + .{ + .cols = 80, + .rows = 24, + .max_scrollback = 0, + }, + )); + defer free(t); + + const S = struct { + var last_data: ?[]u8 = null; + var last_userdata: ?*anyopaque = null; + + fn deinit() void { + if (last_data) |d| testing.allocator.free(d); + last_data = null; + last_userdata = null; + } + + fn writePty(_: Terminal, ud: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void { + if (last_data) |d| testing.allocator.free(d); + last_data = testing.allocator.dupe(u8, ptr[0..len]) catch @panic("OOM"); + last_userdata = ud; + } + }; + defer S.deinit(); + + // Set userdata and write_pty callback + var sentinel: u8 = 42; + const ud: ?*anyopaque = @ptrCast(&sentinel); + set(t, .userdata, @ptrCast(&ud)); + const cb: ?Effects.WritePtyFn = &S.writePty; + set(t, .write_pty, @ptrCast(&cb)); + + // DECRQM for wraparound mode (mode 7, set by default) should trigger write_pty + vt_write(t, "\x1B[?7$p", 6); + try testing.expect(S.last_data != null); + try testing.expectEqualStrings("\x1B[?7;1$y", S.last_data.?); + try testing.expectEqual(@as(?*anyopaque, @ptrCast(&sentinel)), S.last_userdata); +} + +test "set write_pty without callback ignores queries" { + var t: Terminal = null; + try testing.expectEqual(Result.success, new( + &lib_alloc.test_allocator, + &t, + .{ + .cols = 80, + .rows = 24, + .max_scrollback = 0, + }, + )); + defer free(t); + + // Without setting a callback, DECRQM should be silently ignored (no crash) + vt_write(t, "\x1B[?7$p", 6); +} + +test "set write_pty null clears callback" { + var t: Terminal = null; + try testing.expectEqual(Result.success, new( + &lib_alloc.test_allocator, + &t, + .{ + .cols = 80, + .rows = 24, + .max_scrollback = 0, + }, + )); + defer free(t); + + const S = struct { + var called: bool = false; + fn writePty(_: Terminal, _: ?*anyopaque, _: [*]const u8, _: usize) callconv(.c) void { + called = true; + } + }; + S.called = false; + + // Set then clear the callback + const cb: ?Effects.WritePtyFn = &S.writePty; + set(t, .write_pty, @ptrCast(&cb)); + set(t, .write_pty, null); + + vt_write(t, "\x1B[?7$p", 6); + try testing.expect(!S.called); +} + +test "set bell callback" { + var t: Terminal = null; + try testing.expectEqual(Result.success, new( + &lib_alloc.test_allocator, + &t, + .{ + .cols = 80, + .rows = 24, + .max_scrollback = 0, + }, + )); + defer free(t); + + const S = struct { + var bell_count: usize = 0; + var last_userdata: ?*anyopaque = null; + + fn bell(_: Terminal, ud: ?*anyopaque) callconv(.c) void { + bell_count += 1; + last_userdata = ud; + } + }; + S.bell_count = 0; + S.last_userdata = null; + + // Set userdata and bell callback + var sentinel: u8 = 99; + const ud: ?*anyopaque = @ptrCast(&sentinel); + set(t, .userdata, @ptrCast(&ud)); + const cb: ?Effects.BellFn = &S.bell; + set(t, .bell, @ptrCast(&cb)); + + // Single BEL + vt_write(t, "\x07", 1); + try testing.expectEqual(@as(usize, 1), S.bell_count); + try testing.expectEqual(@as(?*anyopaque, @ptrCast(&sentinel)), S.last_userdata); + + // Multiple BELs + vt_write(t, "\x07\x07", 2); + try testing.expectEqual(@as(usize, 3), S.bell_count); +} + +test "bell without callback is silent" { + var t: Terminal = null; + try testing.expectEqual(Result.success, new( + &lib_alloc.test_allocator, + &t, + .{ + .cols = 80, + .rows = 24, + .max_scrollback = 0, + }, + )); + defer free(t); + + // BEL without a callback should not crash + vt_write(t, "\x07", 1); +} + test "grid_ref out of bounds" { var t: Terminal = null; try testing.expectEqual(Result.success, new(