test: differential fuzzing of the terminal core against Ghostty's Zig code
Adds tools/difftest: a Zig driver built against a copy of Ghostty 1.3.1's own src/terminal (Zig 0.15.2, set up under target/ by tools/difftest/setup.sh) and a Rust twin on ghostty-terminal, both printing a canonical dump of every scrollback/active row (cells, graphemes, styles, hyperlinks, row flags), cursor, modes, regions, tabstops and colors. A seeded generator produces escape-heavy streams (UTF-8/wide/combining/emoji, C0, CSI with edge params, SGR, modes, ESC, OSC, DCS, margins, insert/delete); `just difftest` runs it with Zig assertions on (ReleaseSafe vs debug Rust) and off (ReleaseFast vs production Rust), with minimizers and a triage script. Divergences found and fixed in Rust (each with a regression test): - ill-formed UTF-8 decoding follows the SIMD path a default Ghostty build uses (not Zig's scalar fallback) - PageList::erase_rows keeps Zig's row-flag behavior on regrow - grapheme move on wrap with left/right margins uses the original cell Upstream bugs found and deliberately NOT replicated (per the project rule): a stale cell pointer after page growth in Terminal.zig:482/539 that drops a grapheme, plus several Zig assertion failures/segfaults; documented with minimized repros in docs/rewrite/notes/difftest-DF.md. Final run: 390k cases across many geometries, 0 divergences (Zig-abort cases, ~0.1-0.5%, are not compared). Test Plan: - just difftest 20000 "1 2 3" - cargo test -p ghostty-terminal (regression tests)
This commit is contained in:
1 parent
6def1f3b5d
commit
4eb03f637d
19 files changed
+2033
-165
No files matched your search
@@ -904,6 +904,17 @@ impl Terminal {
|
||||
// This is like print_cell but without clearing the
|
||||
// grapheme data from the cell, so we can move it
|
||||
// later.
|
||||
// Zig keeps a raw pointer (`prev.cell`) to this cell;
|
||||
// remember its page cell index, because the wrap below
|
||||
// may scroll (rows move, cell memory does not) and the
|
||||
// grapheme move must still use THIS cell.
|
||||
let prev_idx = {
|
||||
let scr = self.screen_mut();
|
||||
let (x, pin) = (scr.cursor.x, scr.cursor_pin());
|
||||
scr.page(pin.node)
|
||||
.cell_index(usize::from(x), usize::from(pin.y))
|
||||
.expect("prev cell")
|
||||
};
|
||||
{
|
||||
let cell = self.screen_mut().cursor_cell_mut();
|
||||
cell.set_wide(if row_wrap {
|
||||
@@ -929,8 +940,11 @@ impl Terminal {
|
||||
|
||||
if new_pin.node == old_pin.node {
|
||||
let page = self.screen_mut().page_mut(new_pin.node);
|
||||
let old_idx = page.cell_index(old_x, old_y).expect("old cell");
|
||||
let new_idx = page.cell_index(new_x, new_y).expect("new cell");
|
||||
// Zig moves from `prev.cell`, NOT from `old_pin`
|
||||
// (they differ when margins keep the row from
|
||||
// scrolling the cell out of `old_pin`).
|
||||
let old_idx = prev_idx;
|
||||
page.move_grapheme(old_idx, new_idx)?;
|
||||
page.cells[old_idx].set_content_tag(ContentTag::Codepoint);
|
||||
page.cells[new_idx].set_content_tag(ContentTag::CodepointGrapheme);
|
||||
|
||||
Reference in new issue
Block a user