terminal/snapshot: grid tests

This commit is contained in:
Mitchell Hashimoto
2026-07-30 20:16:46 -07:00
parent 627f343097
commit 13bc78b7f3
5 changed files with 248 additions and 48 deletions

View File

@@ -24,6 +24,7 @@
//! ```
const std = @import("std");
const test_fixture = @import("fixture.zig");
const record = @import("record.zig");
const Blake3 = std.crypto.hash.Blake3;
@@ -110,27 +111,55 @@ pub fn decode(
}
}
test "checkpoint BLAKE3-256 registry" {
const expected = [_]u8{
0x64, 0x37, 0xb3, 0xac, 0x38, 0x46, 0x51, 0x33,
0xff, 0xb6, 0x3b, 0x75, 0x27, 0x3a, 0x8d, 0xb5,
0x48, 0xc5, 0x58, 0x46, 0x5d, 0x79, 0xdb, 0x03,
0xfd, 0x35, 0x9c, 0x6c, 0xd5, 0xbd, 0x9d, 0x85,
};
var actual: Digest = undefined;
Blake3.hash("abc", &actual, .{});
try std.testing.expectEqual(expected, actual);
const test_ready_fixture = test_fixture.parse(
@embedFile("testdata/checkpoint-ready-v1.hex"),
);
test "READY golden encoding and BLAKE3-256 registry" {
const prefix = "abc";
var snapshot: std.Io.Writer.Allocating = .init(std.testing.allocator);
defer snapshot.deinit();
try snapshot.writer.writeAll(prefix);
try encode(.ready, &snapshot);
try test_fixture.expectEqual(
.bytes,
"src/terminal/snapshot/testdata/checkpoint-ready-v1.hex",
"snapshot_fixture-checkpoint-ready-v1.hex",
&test_ready_fixture,
snapshot.written(),
);
// The checked-in payload independently locks the BLAKE3-256 registry.
var actual: Digest = undefined;
Blake3.hash(prefix, &actual, .{});
const payload_offset = prefix.len + record.Header.len;
try std.testing.expectEqualSlices(
u8,
test_ready_fixture[payload_offset..][0..actual.len],
&actual,
);
// Finalizing a streaming hasher neither consumes nor resets it.
var hasher = Blake3.init(.{});
hasher.update("a");
var prefix: Digest = undefined;
Blake3.hash("a", &prefix, .{});
var prefix_digest: Digest = undefined;
Blake3.hash("a", &prefix_digest, .{});
hasher.final(&actual);
try std.testing.expectEqual(prefix, actual);
try std.testing.expectEqual(prefix_digest, actual);
hasher.update("bc");
hasher.final(&actual);
try std.testing.expectEqual(expected, actual);
try std.testing.expectEqualSlices(
u8,
test_ready_fixture[payload_offset..][0..actual.len],
&actual,
);
// Decode the checked-in record rather than the generated candidate.
var source: std.Io.Reader = .fixed(test_ready_fixture[prefix.len..]);
try decode(.ready, actual, &source);
}
test "READY and FINISH checkpoint coverage" {

View File

@@ -105,6 +105,7 @@
//! nonzero base codepoint.
const std = @import("std");
const test_fixture = @import("fixture.zig");
const io = @import("io.zig");
const kitty = @import("../kitty.zig");
const terminal_hyperlink = @import("../hyperlink.zig");
@@ -543,3 +544,164 @@ const CellHeader = struct {
_padding: u5 = 0,
};
};
const test_golden_fixture = test_fixture.parse(
@embedFile("testdata/grid-v1.hex"),
);
test "grid golden encoding and decoding" {
const capacity: terminal_page.Capacity = .{
.cols = 3,
.rows = 2,
.styles = 0,
.hyperlink_bytes = 0,
.grapheme_bytes = 64,
.string_bytes = 0,
};
var source = try TerminalPage.init(capacity);
defer source.deinit();
// The first row covers semantic metadata, a wide-cell pair, and a
// multi-codepoint grapheme.
const wide = source.getRowAndCell(0, 0);
wide.row.semantic_prompt = .prompt;
wide.cell.* = .init('A');
wide.cell.wide = .wide;
wide.cell.protected = true;
wide.cell.semantic_content = .prompt;
const tail = source.getRowAndCell(1, 0);
tail.cell.wide = .spacer_tail;
tail.cell.semantic_content = .input;
const grapheme = source.getRowAndCell(2, 0);
grapheme.cell.* = .init('x');
try source.setGraphemes(
grapheme.row,
grapheme.cell,
&.{ 0x0301, 0x0302 },
);
// The second row covers both background-color kinds and a wrapped spacer
// head with the remaining row semantic value.
const palette = source.getRowAndCell(0, 1);
palette.cell.content_tag = .bg_color_palette;
palette.cell.content = .{ .color_palette = .{ .data = 7 } };
const rgb = source.getRowAndCell(1, 1);
rgb.cell.content_tag = .bg_color_rgb;
rgb.cell.content = .{ .color_rgb = .{
.r = 0xaa,
.g = 0xbb,
.b = 0xcc,
} };
rgb.cell.protected = true;
const head = source.getRowAndCell(2, 1);
head.cell.wide = .spacer_head;
head.row.wrap = true;
head.row.wrap_continuation = true;
head.row.semantic_prompt = .prompt_continuation;
var encoded: [128]u8 = undefined;
var writer: std.Io.Writer = .fixed(&encoded);
try encode(&source, &writer);
try test_fixture.expectEqual(
.bytes,
"src/terminal/snapshot/testdata/grid-v1.hex",
"snapshot_fixture-grid-v1.hex",
&test_golden_fixture,
writer.buffered(),
);
var destination = try TerminalPage.init(capacity);
defer destination.deinit();
var style_remap = StyleRemap.init(std.testing.allocator);
defer style_remap.deinit();
var hyperlink_remap = HyperlinkRemap.init(std.testing.allocator);
defer hyperlink_remap.deinit();
// Decode the checked-in reference through a one-byte reader buffer.
var fixture_reader: std.Io.Reader = .fixed(&test_golden_fixture);
var read_buffer: [1]u8 = undefined;
var limited = fixture_reader.limited(.unlimited, &read_buffer);
try decode(
&destination,
&limited.interface,
&style_remap,
&hyperlink_remap,
);
try destination.verifyIntegrity(std.testing.allocator);
const decoded_wide = destination.getRowAndCell(0, 0);
try std.testing.expectEqual(@as(u21, 'A'), decoded_wide.cell.codepoint());
try std.testing.expectEqual(TerminalCell.Wide.wide, decoded_wide.cell.wide);
try std.testing.expect(decoded_wide.cell.protected);
try std.testing.expectEqual(
TerminalCell.SemanticContent.prompt,
decoded_wide.cell.semantic_content,
);
try std.testing.expectEqual(
TerminalRow.SemanticPrompt.prompt,
decoded_wide.row.semantic_prompt,
);
const decoded_tail = destination.getRowAndCell(1, 0);
try std.testing.expectEqual(
TerminalCell.Wide.spacer_tail,
decoded_tail.cell.wide,
);
try std.testing.expectEqual(
TerminalCell.SemanticContent.input,
decoded_tail.cell.semantic_content,
);
const decoded_grapheme = destination.getRowAndCell(2, 0);
try std.testing.expectEqualSlices(
u21,
&.{ 0x0301, 0x0302 },
destination.lookupGrapheme(decoded_grapheme.cell).?,
);
const decoded_palette = destination.getRowAndCell(0, 1);
try std.testing.expectEqual(
TerminalCell.ContentTag.bg_color_palette,
decoded_palette.cell.content_tag,
);
try std.testing.expectEqual(
@as(u8, 7),
decoded_palette.cell.content.color_palette.data,
);
const decoded_rgb = destination.getRowAndCell(1, 1);
try std.testing.expectEqual(
TerminalCell.ContentTag.bg_color_rgb,
decoded_rgb.cell.content_tag,
);
try std.testing.expectEqual(
TerminalCell.RGB{ .r = 0xaa, .g = 0xbb, .b = 0xcc },
decoded_rgb.cell.content.color_rgb,
);
try std.testing.expect(decoded_rgb.cell.protected);
const decoded_head = destination.getRowAndCell(2, 1);
try std.testing.expectEqual(
TerminalCell.Wide.spacer_head,
decoded_head.cell.wide,
);
try std.testing.expect(decoded_head.row.wrap);
try std.testing.expect(decoded_head.row.wrap_continuation);
try std.testing.expectEqual(
TerminalRow.SemanticPrompt.prompt_continuation,
decoded_head.row.semantic_prompt,
);
// A re-encode proves the decoded native page retains every wire field.
var reencoded: [128]u8 = undefined;
var rewriter: std.Io.Writer = .fixed(&reencoded);
try encode(&destination, &rewriter);
try std.testing.expectEqualStrings(
&test_golden_fixture,
rewriter.buffered(),
);
}

View File

@@ -764,13 +764,6 @@ test "framed PAGE encode and decode a sparse native page" {
try std.testing.expectEqual(null, hyperlink_it.next());
const decoded_first = decoded.getRowAndCell(0, 0);
try std.testing.expectEqual(@as(u21, 'A'), decoded_first.cell.codepoint());
try std.testing.expectEqual(TerminalCell.Wide.wide, decoded_first.cell.wide);
try std.testing.expect(decoded_first.cell.protected);
try std.testing.expectEqual(
TerminalCell.SemanticContent.prompt,
decoded_first.cell.semantic_content,
);
try std.testing.expectEqual(@as(TerminalStyleId, 1), decoded_first.cell.style_id);
try std.testing.expectEqual(
@as(TerminalHyperlinkId, 1),
@@ -778,39 +771,12 @@ test "framed PAGE encode and decode a sparse native page" {
);
const decoded_second = decoded.getRowAndCell(1, 0);
try std.testing.expectEqual(TerminalCell.Wide.spacer_tail, decoded_second.cell.wide);
try std.testing.expectEqual(@as(TerminalStyleId, 2), decoded_second.cell.style_id);
try std.testing.expectEqual(
@as(TerminalHyperlinkId, 2),
decoded.lookupHyperlink(decoded_second.cell).?,
);
const decoded_grapheme = decoded.getRowAndCell(0, 1);
try std.testing.expectEqualSlices(
u21,
&.{ 0x0301, 0x0302 },
decoded.lookupGrapheme(decoded_grapheme.cell).?,
);
const decoded_rgb = decoded.getRowAndCell(1, 1);
try std.testing.expectEqual(TerminalCell.ContentTag.bg_color_rgb, decoded_rgb.cell.content_tag);
try std.testing.expectEqual(
TerminalCell.RGB{ .r = 0xaa, .g = 0xbb, .b = 0xcc },
decoded_rgb.cell.content.color_rgb,
);
const decoded_spacer_head = decoded.getRowAndCell(2, 1);
try std.testing.expectEqual(
TerminalCell.Wide.spacer_head,
decoded_spacer_head.cell.wide,
);
try std.testing.expect(decoded_spacer_head.row.wrap);
try std.testing.expect(decoded_spacer_head.row.wrap_continuation);
try std.testing.expectEqual(
TerminalRow.SemanticPrompt.prompt_continuation,
decoded_spacer_head.row.semantic_prompt,
);
// The first re-encode reflects compacted IDs; subsequent round trips must
// stabilize byte-for-byte.
var reencoded: [512]u8 = undefined;

View File

@@ -0,0 +1,14 @@
# Ghostty snapshot fixture
# Wire version: 1
# Generated by its snapshot test; review before replacing.
# On mismatch, the candidate is copied to the repository root.
# prefix covered by READY
61 62 63
# READY record header: tag, payload length, CRC32C
05 00 20 00 00 00 90 5b 2d 06
# BLAKE3-256 digest of the prefix
64 37 b3 ac 38 46 51 33 ff b6 3b 75 27 3a 8d b5
48 c5 58 46 5d 79 db 03 fd 35 9c 6c d5 bd 9d 85

View File

@@ -0,0 +1,29 @@
# Ghostty snapshot fixture
# Wire version: 1
# Generated by its snapshot test; review before replacing.
# On mismatch, the candidate is copied to the repository root.
# row 0: prompt
04
# row 0, cell 0: protected prompt, wide "A"
00 01 05 00 00 00 00 00 41 00 00 00 00 00 00 00
# row 0, cell 1: input spacer tail
00 02 02 00 00 00 00 00 00 00 00 00 00 00 00 00
# row 0, cell 2: "x" with two grapheme suffixes
00 00 00 00 00 00 00 00 78 00 00 00 02 00 00 00
01 03 00 00 02 03 00 00
# row 1: wrapped prompt continuation
0b
# row 1, cell 0: palette background 7
01 00 00 00 00 00 00 00 07 00 00 00 00 00 00 00
# row 1, cell 1: protected RGB background
02 00 01 00 00 00 00 00 aa bb cc 00 00 00 00 00
# row 1, cell 2: spacer head
00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00