// E2E tests for the SDL3 GPU backend. // // Unlike the terminal e2e suite, this harness validates the rendered framebuffer: // the SDL app runs its real GPU/font/shader path and writes the latest frame as // a PPM image in a temp capture directory. These assertions are intentionally // visual/pixel-level, not retained-grid text assertions. const std = @import("std"); const h = @import("e2e_harness_sdl.zig"); const config = @import("config"); const Harness = h.SdlHarness; const Image = h.Image; fn expectPixelSize(hs: *Harness, width: u32, height: u32, msg: []const u8) !void { const img = hs.image orelse { hs.dump(msg); return error.ExpectFailed; }; if (img.width != width or img.height != height) { hs.dump(msg); return error.ExpectFailed; } } fn expectChangedPixels(hs: *Harness, before: Image, min_changed: usize, msg: []const u8) !void { const img = hs.image orelse { hs.dump(msg); return error.ExpectFailed; }; if (img.changedPixels(before, 30) < min_changed) { hs.dump(msg); return error.ExpectFailed; } } pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; const args = try init.minimal.args.toSlice(init.arena.allocator()); if (args.len != 2) { std.debug.print("usage: {s} \n", .{args[0]}); return error.BadArgs; } const exe = args[1]; var hs = try Harness.init(gpa, io, exe.ptr, 24, 80); defer hs.deinit(); hs.trace = config.trace; try hs.expectWaitForImage(8000, "SDL: app never produced a rendered frame"); try hs.pump(2000); try hs.expectGridPixels("SDL: initial rendered frame is not aligned to the pty grid"); const cw = hs.cellWidth(); const ch = hs.cellHeight(); try hs.expectBrightRows(0, 1, 80, "SDL: top bar text did not render as bright glyph pixels"); try hs.expectBrightRows(2, 20, 3000, "SDL: terminal body did not render directory-list glyph pixels"); // Resize must change the actual framebuffer dimensions, not just retained // state. This catches the real SDL window/swapchain path. try hs.resize(30, 100); const before_resize = hs.imageHash(); try hs.expectWaitForChange(before_resize, 8000, "SDL: resize did not produce a new rendered frame"); try expectPixelSize(&hs, cw * 100, ch * 30, "SDL: resized framebuffer does not match 100x30 cells"); try hs.expectBrightRows(0, 1, 80, "SDL: top bar disappeared after resize"); // TTY toggle should visibly change the active pane tag/body because prompts // become visible and the mode tag flips. var before = try hs.imageCopy(); defer before.deinit(gpa); try hs.send("\x02"); try hs.expectWaitForChange(before.hash(), 6000, "SDL: Ctrl-b did not visibly enter tty mode"); try expectChangedPixels(&hs, before, 100, "SDL: tty mode visual delta was too small"); // Shell output should show up in the real rendered framebuffer. before.deinit(gpa); before = try hs.imageCopy(); try hs.send("echo SDL_E2E_MARKER_42\r"); try hs.expectWaitForChange(before.hash(), 8000, "SDL: shell command did not visibly update rendered output"); try expectChangedPixels(&hs, before, 100, "SDL: shell output visual delta was too small"); // Leaving tty should hide prompt/command rows again, producing another real // rendered change. before.deinit(gpa); before = try hs.imageCopy(); try hs.send("\x02"); try hs.expectWaitForChange(before.hash(), 6000, "SDL: Ctrl-b did not visibly return to normal mode"); try expectChangedPixels(&hs, before, 100, "SDL: normal-mode visual delta was too small"); // Global tagbar mouse execution: middle-click Tutor should visibly add a // file-like tutor pane. before.deinit(gpa); before = try hs.imageCopy(); try hs.send("\x1b[<1;14;1M\x1b[<1;14;1m"); try hs.expectWaitForChange(before.hash(), 10000, "SDL: Tutor tagbar action did not visibly change the render"); try expectChangedPixels(&hs, before, 1000, "SDL: Tutor visual delta was too small"); try hs.expectBrightRows(1, 8, 300, "SDL: Tutor pane did not render bright text in the top rows"); // Fresh app: terminal char-mode overlay is visible, can be edited, hides in // raw tty, and returns after leaving tty. Every step is asserted through // rendered pixels. { var hs2 = try Harness.init(gpa, io, exe.ptr, 24, 80); defer hs2.deinit(); hs2.trace = config.trace; try hs2.expectWaitForImage(8000, "SDL overlay: no initial frame"); try hs2.send("\x1b[<0;5;8M\x1b[<0;5;8m"); try hs2.pump(400); try hs2.send("i"); try hs2.pump(300); var base = try hs2.imageCopy(); defer base.deinit(gpa); try hs2.send("ZZSDLINSXYZ"); try hs2.expectWaitForChange(base.hash(), 5000, "SDL: typed overlay did not render"); try expectChangedPixels(&hs2, base, 80, "SDL: typed overlay visual delta was too small"); base.deinit(gpa); base = try hs2.imageCopy(); try hs2.send("\x7f\x7f\x7f"); try hs2.expectWaitForChange(base.hash(), 5000, "SDL: overlay backspace did not render"); try hs2.send("\x1b"); try hs2.pump(300); base.deinit(gpa); base = try hs2.imageCopy(); try hs2.send("\x02"); try hs2.expectWaitForChange(base.hash(), 6000, "SDL: tty mode did not hide overlay visually"); try expectChangedPixels(&hs2, base, 40, "SDL: tty-hide overlay delta was too small"); base.deinit(gpa); base = try hs2.imageCopy(); try hs2.send("\x02"); try hs2.expectWaitForChange(base.hash(), 6000, "SDL: leaving tty did not restore overlay visually"); } // CLI mode parity: --tty starts as a raw shell; typed shell input must // visibly alter the framebuffer. { var hs3 = try Harness.initArgs(gpa, io, exe.ptr, 24, 80, "--tty"); defer hs3.deinit(); hs3.trace = config.trace; try hs3.expectWaitForImage(8000, "SDL --tty: no initial frame"); before.deinit(gpa); before = try hs3.imageCopy(); try hs3.send("i=0; while [ $i -lt 30 ]; do echo ZZSDLTTYFLAG_$i; i=$((i+1)); done\r"); try hs3.pump(2500); try expectChangedPixels(&hs3, before, 80, "SDL --tty: shell visual delta was too small"); try hs3.expectBrightRows(18, 23, 350, "SDL --tty: lower terminal rows were not rendered"); } // Configurable tty toggle: Ctrl-g should visibly enter tty mode when the // toggle is moved off Ctrl-b. { var hs4 = try Harness.initArgs(gpa, io, exe.ptr, 24, 80, "--tty-toggle=g"); defer hs4.deinit(); hs4.trace = config.trace; try hs4.expectWaitForImage(8000, "SDL --tty-toggle=g: no initial frame"); before.deinit(gpa); before = try hs4.imageCopy(); try hs4.send("\x07"); try hs4.expectWaitForChange(before.hash(), 6000, "SDL --tty-toggle=g: Ctrl-g did not visibly enter tty mode"); try expectChangedPixels(&hs4, before, 80, "SDL --tty-toggle=g: visual delta was too small"); } std.debug.print("SDL rendered-image e2e: all tests passed\n", .{}); }