mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-31 04:39:01 +00:00
terminal/snapshot: grid tests
This commit is contained in:
@@ -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" {
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
14
src/terminal/snapshot/testdata/checkpoint-ready-v1.hex
vendored
Normal file
14
src/terminal/snapshot/testdata/checkpoint-ready-v1.hex
vendored
Normal 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
|
||||
29
src/terminal/snapshot/testdata/grid-v1.hex
vendored
Normal file
29
src/terminal/snapshot/testdata/grid-v1.hex
vendored
Normal 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
|
||||
Reference in New Issue
Block a user