|
| 1 | +# TUI test methods and outcome — 0.5.15 |
| 2 | + |
| 3 | +**Status:** accepted engineering guidance. This note covers test maintenance, |
| 4 | +not Airlock runtime or operator performance. |
| 5 | + |
| 6 | +## Question and boundary |
| 7 | + |
| 8 | +Slice 94 characterized Textual TUI tests, and Slice 95 tested one narrow |
| 9 | +change: whether eight pure Phase4 composition/navigation tests should use the |
| 10 | +existing explicit `AirlockApp(test_harness=True)` mode. This is not a claim |
| 11 | +about end-user TUI speed, proxy throughput, or the whole test suite. |
| 12 | + |
| 13 | +Historical CI wall times are deliberately excluded from before/after results. |
| 14 | +Successful runs did not retain per-test JUnit timing and ran in different CI |
| 15 | +contexts, so they cannot establish a TUI cause. |
| 16 | + |
| 17 | +## Test-mode policy |
| 18 | + |
| 19 | +Textual's [testing guide](https://textual.textualize.io/guide/testing/) describes |
| 20 | +`run_test()` as headless app execution; it does not turn lifecycle behavior off. |
| 21 | +The [Pilot API](https://textual.textualize.io/api/pilot/) defines `pause()` as a |
| 22 | +way to wait for pending messages and CPU-idle settlement. It is not a generic |
| 23 | +delay to delete. The [Worker API](https://textual.textualize.io/api/worker/) |
| 24 | +documents worker state and cancellation as lifecycle behavior to test directly. |
| 25 | + |
| 26 | +Use `AirlockApp(test_harness=True)` explicitly and only for tests that assert |
| 27 | +the production pane tree, static rendering, or deterministic navigation while |
| 28 | +not asserting lifecycle effects. Keep normal `AirlockApp()` mode for worker and |
| 29 | +timer lifecycle, cancellation, shutdown, stale callbacks, JSONL and MCP |
| 30 | +integration, and actual refresh behavior. Do not introduce a global harness |
| 31 | +fixture, remove `pause()` without a deterministic replacement, add xdist, |
| 32 | +retries, or relaxed timeouts merely to reduce duration. |
| 33 | + |
| 34 | +## Evidence and coverage mapping |
| 35 | + |
| 36 | +The baseline at `eb75a44` had 61 `run_test()` contexts: 10 explicit harness |
| 37 | +uses, 51 normal-mode mounts, and 48 `pilot.pause()` calls. The Slice 95 tree |
| 38 | +has 62 contexts, 18 explicit harness uses, 44 normal mounts, and the same 48 |
| 39 | +pauses. The delta is a new normal-mode regression plus eight deliberate |
| 40 | +per-test harness choices. |
| 41 | + |
| 42 | +The harness migrations are: |
| 43 | + |
| 44 | +- `TestTUIBasic.test_overview_is_default` |
| 45 | +- `TestTUIBasic.test_overview_has_widgets` |
| 46 | +- `TestTUIBasic.test_guards_screen_exists` |
| 47 | +- `TestTUIBasic.test_logs_screen_exists` |
| 48 | +- `TestTUIBasic.test_config_screen_exists` |
| 49 | +- `TestTUIBasic.test_test_screen_exists` |
| 50 | +- `TestTUINavigation.test_navigation_by_number_keys` |
| 51 | +- `TestTUINavigation.test_all_five_views_accessible` |
| 52 | + |
| 53 | +`TestTUIBasic.test_app_instantiates` remains an unchanged non-mount fixture. |
| 54 | +The new normal-mode |
| 55 | +`test_overview_stale_refresh_callback_is_ignored` captures the raw refresh |
| 56 | +worker callback, proves the pane remains mounted, makes only the providers |
| 57 | +table lookup raise `NoMatches`, and verifies the callback returns safely. Its |
| 58 | +RED proof on the parent of the guard change raised `NoMatches`; current GREEN |
| 59 | +coverage protects the existing teardown race. The named normal-mode contract |
| 60 | +`test_default_app_keeps_mount_lifecycle_enabled` remains in place, alongside |
| 61 | +existing lifecycle, JSONL/MCP, refresh, cancellation, and shutdown coverage. |
| 62 | + |
| 63 | +## Measurement and outcome |
| 64 | + |
| 65 | +The experiment used Python 3.12.3, Textual 6.2.1, pytest 9.1.1, Linux |
| 66 | +7.0.0-28-generic x86_64, the locked dependencies, and base `eb75a44`. Its only |
| 67 | +test delta is the stable Phase4 patch ID |
| 68 | +`558a8f37a11f8d7ca3698febcb387ac281fc9c4a`. One warm-up per side preceded |
| 69 | +three recorded A/B/B/A/A/B runs of: |
| 70 | + |
| 71 | +```bash |
| 72 | +timeout --preserve-status 30s uv run python -m pytest \ |
| 73 | + tests/harness/test_phase4_tui.py -q --durations=0 --durations-min=0 |
| 74 | +``` |
| 75 | + |
| 76 | +All six retained runs passed nine tests without a timeout: |
| 77 | + |
| 78 | +| Mode | Wall times (s) | Median (s) | Range (s) | |
| 79 | +| --- | --- | ---: | --- | |
| 80 | +| normal control | 14.33, 14.74, 13.65 | 14.33 | 13.65–14.74 | |
| 81 | +| explicit harness | 16.39, 14.65, 13.25 | 14.65 | 13.25–16.39 | |
| 82 | + |
| 83 | +The explicit-harness median was 0.32s (about 2.2%) slower and had a wider |
| 84 | +range. The navigation tests and fixture setup dominated both modes. The result |
| 85 | +therefore validates only the safety and coverage mapping; it is **inconclusive |
| 86 | +for performance**. It authorizes neither a bulk harness migration nor a |
| 87 | +whole-suite speed claim. |
| 88 | + |
| 89 | +CPU model, memory/load state, and raw historical CI duration logs were not |
| 90 | +retained. A new timing run can check whether this focused command still works, |
| 91 | +but cannot make past measurements reproducible. Any new candidate must have |
| 92 | +its own test-by-test coverage decision and controlled comparison. |
0 commit comments