a73x

35c92e24

Add synthetic hidden-freeze regression test

a73x   2026-04-16 11:21

Commit message
Add synthetic hidden-freeze regression test

Exercises the hide → pty-flood → show sequence through the FrameLoop
state machine. Verifies no canRender() call returns true while hidden,
and that normal pacing resumes on show.

src/frame_loop.zig
Old New
@@ -369,3 +369,44 @@ test "deinit destroys any pending callback" {
369 369
370 try std.testing.expectEqual(@as(usize, 1), mock.callbacks_destroyed); 370 try std.testing.expectEqual(@as(usize, 1), mock.callbacks_destroyed);
371 } 371 }
372
373 test "hidden-freeze regression: pty flood while hidden never blocks" {
374 var mock = try Mock.init();
375 defer mock.deinit();
376 var ts = try TestState.init(std.testing.allocator);
377 defer ts.deinit();
378
379 var loop = FrameLoop.init(mock.ops(), ts.view());
380 defer loop.deinit();
381
382 // Initial render.
383 try loop.commitRender();
384 const first_tok = loop.pending_token.?;
385 loop.onFrameCallbackDone(first_tok);
386
387 // Hide the surface: tracker leaves all outputs.
388 ts.tracker.leaveOutput(1);
389 loop.onSurfaceHidden();
390 try std.testing.expect(!loop.canRender());
391
392 // Simulate 100 iterations of "pty wrote more data, we want to render".
393 // Under the gate, canRender is false every iteration and we never call
394 // commitRender — so no Vulkan work, no blocking.
395 var i: usize = 0;
396 while (i < 100) : (i += 1) {
397 try std.testing.expect(!loop.canRender());
398 }
399
400 // Show again: tracker re-enters an output.
401 try ts.tracker.enterOutput(1);
402 loop.onSurfaceShown();
403
404 try std.testing.expect(loop.canRender());
405
406 // Normal render resumes.
407 try loop.commitRender();
408 try std.testing.expect(loop.pending_token != null);
409 const tok = loop.pending_token.?;
410 loop.onFrameCallbackDone(tok);
411 try std.testing.expect(loop.armed);
412 }