Skip to content

Commit fa09971

Browse files
committed
test: improve changed validation routing
1 parent eadcca8 commit fa09971

7 files changed

Lines changed: 207 additions & 118 deletions

File tree

.agents/skills/labkit-test-planner/SKILL.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,24 @@ another skill already read shared AGENTS context, do not reread it.
2525

2626
Use the smallest source-aligned validation set that covers the touched
2727
boundary. `docs/testing.md` owns the stable build-task names, CI scope, and
28-
command examples. Build tasks are broad discovery-driven entry points; use
28+
command examples. Keep the public build-task set small: improve changed-file
29+
planner routing, representative selectors, printed plan reasons, or focused
30+
runner selectors before adding a new public task.
31+
32+
Use the fast changed-file build task for tight local iteration when git state
33+
is available, and the conservative changed-file build task before handoff.
34+
These tasks inspect the current diff and print why each selected scope is
35+
being run. Use
2936
`runLabKitTests("Suites", ...)` for component, app-family, or focused GUI
30-
iteration. For local GUI edits that only touch one app, prefer the app-level
31-
GUI folder, for example a `Suites` value such as
37+
diagnosis after a scope is known. For local GUI edits that only touch one app,
38+
prefer the app-level GUI folder, for example a `Suites` value such as
3239
`gui/apps/image_measurement/batch_crop` with `IncludeGui=true` and
3340
`GuiMode="hidden"`.
3441

3542
For long focused suites, `runLabKitTests` can split a selected suite with
3643
zero-based `ShardCount` and `ShardIndex`. Use shards only when every shard is
3744
run with a distinct `RunName` and the combined shards cover the same selected
38-
suite; keep `buildtool changed` or the relevant broad build task as the
45+
suite; keep the conservative changed-file or relevant broad build task as the
3946
handoff gate for substantive changes.
4047

4148
For a dirty worktree, route through the changed-file validation planner before
@@ -49,9 +56,9 @@ instead of rerunning narrower tests for ceremony.
4956
After a planned run fails, do not rerun the planner just to discover the same
5057
scope again. Fix the root cause and rerun the narrowest failed suite or test
5158
directly, for example `runLabKitTests("Suites", "project")` after a project
52-
guardrail failure. Escalate back to `buildtool changed`, `headless`, or `gui`
53-
only when the fix touches additional areas, changes validation routing, or the
54-
user explicitly asks for a broader/release gate.
59+
guardrail failure. Escalate back to the changed-file, headless, or GUI build
60+
task only when the fix touches additional areas, changes validation routing,
61+
or the user explicitly asks for a broader/release gate.
5562

5663
```text
5764
project startup, architecture, package surface, sample-data hygiene

docs/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ the app catalog; maintainer references are grouped separately.
1414

1515
- [apps.md](apps.md): app file shape, app ownership, and new-app guidance.
1616
- [architecture.md](architecture.md): package boundaries and extraction rules.
17-
- [testing.md](testing.md): supported build tasks, CI scope, fixtures, and GUI
18-
validation limits.
17+
- [testing.md](testing.md): compact build-task entry points, automatic
18+
changed-file routing, CI scope, fixtures, and GUI validation limits.
1919
- [release.md](release.md): version selection, tag naming, and GitHub release
2020
note format.
2121
- [workflow-assets.md](workflow-assets.md): command-line generation of real app

docs/testing.md

Lines changed: 32 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# Testing
22

3-
Use this page to choose the smallest supported build task that proves the
4-
change you made.
3+
Use this page to choose the smallest supported validation entry point. The
4+
public build-task set is intentionally small; changed-file tasks inspect the
5+
current git diff and print why each selected scope is being run.
56

67
## Default Check
78

8-
Run the default non-GUI build task for broad local validation:
9+
Run the default broad non-GUI check when you are not sure which focused scope
10+
applies:
911

1012
```bash
1113
buildtool headless
@@ -39,10 +41,10 @@ buildtool listTasks
3941

4042
| Task | Use it for |
4143
| --- | --- |
42-
| `changed` | Fast local validation selected from changed and untracked files. |
43-
| `changedFast` | Faster changed-file validation for local iteration; uses representative GUI workflow/layout coverage when broad app GUI coverage would otherwise run. |
44+
| `changedFast` | Tight local iteration from the current diff; substitutes representative GUI coverage for expensive broad GUI scopes. |
45+
| `changed` | Conservative pre-handoff validation from the current diff. |
4446
| `headless` | Full non-GUI validation. |
45-
| `gui` | Noninteractive GUI launch, layout, callback, workflow, and gesture checks. GUI windows are hidden by default. |
47+
| `gui` | Full automated GUI validation with hidden figures. |
4648

4749
Report and discovery tasks:
4850

@@ -53,34 +55,28 @@ Report and discovery tasks:
5355

5456
## Choosing A Task
5557

56-
Use build tasks directly for local iteration. The `changed` task is the
57-
default focused choice before committing: it inspects changed and untracked
58-
files and runs a conservative serial validation plan inside one MATLAB
59-
process. It requires git and a git checkout. Use `headless` in exported
60-
source trees, packaged copies, or environments without git state.
61-
Use `changedFast` during tight edit cycles when a shared UI or broad app-GUI
62-
change would otherwise run the full downstream app GUI suite. It keeps
63-
reusable UI coverage and representative downstream app checks, but it is
64-
not a substitute for the conservative `changed` task before handoff.
65-
Fast validation plans may combine suite routing with test-name selectors for
66-
expensive GUI coverage. Keep those selectors small and contract-oriented:
67-
choose tests that cover startup, declarative shell behavior, debug trace
68-
plumbing, hidden synthetic workflows, and representative downstream app
69-
layouts.
58+
Prefer automatic routing before hand-picking suites:
7059

7160
Common choices:
7261

7362
| Change area | Build task |
7463
| --- | --- |
75-
| Changed source, tests, or docs before commit | `buildtool changed` |
76-
| Local iteration after a broad UI or GUI-adjacent edit | `buildtool changedFast` |
64+
| Local iteration while files are changing | `buildtool changedFast` |
65+
| Before commit, PR, or handoff | `buildtool changed` |
7766
| Full broad non-GUI validation | `buildtool headless` |
78-
| Any GUI launch, layout, callback, or gesture change | `buildtool gui` |
79-
| Architecture, docs, package surface, hygiene | `buildtool headless` |
67+
| Full automated GUI validation | `buildtool gui` |
68+
| Coverage report | `buildtool coverage` |
8069

81-
Focused runner suite targets include `labkit/image` for the reusable image
82-
facade, `labkit/thermal` for thermal parsing and rendering, and
83-
`apps/image_measurement` for downstream image apps.
70+
`changedFast` and `changed` require a git checkout. In exported source trees,
71+
packaged copies, or environments without git state, use `headless` or an
72+
explicit `runLabKitTests` suite selection instead.
73+
74+
The changed-file planner routes by source ownership. For example, a single app
75+
change maps to that app family plus its GUI folder when one exists; reusable
76+
UI changes map to reusable UI coverage plus downstream GUI coverage; docs,
77+
AGENTS, tools, and runner support files map to project guardrails unless their
78+
own tests require broader coverage. The printed plan includes the selected
79+
suites, test-name selectors, GUI mode, and reason for each step.
8480

8581
## CI Scope
8682

@@ -137,43 +133,29 @@ is an implementation detail used by the buildfile.
137133

138134
## Focused And Parallel Iteration
139135

140-
For tight local diagnosis, run the smallest suite directly through the lower-level
141-
runner after adding `tests` to the MATLAB path:
136+
When diagnosing one failure, rerun the smallest matching suite directly after
137+
adding `tests` to the MATLAB path:
142138

143139
```matlab
144140
addpath("tests")
145141
runLabKitTests("Suites", "gui/apps/image_measurement/batch_crop", ...
146142
"IncludeGui", true, "GuiMode", "hidden", "HtmlReport", false)
147143
```
148144

149-
For long GUI or broad component suites, the runner can split a selected suite
150-
into deterministic zero-based shards. Start each shard in a separate MATLAB
151-
process and give each shard a distinct run name:
145+
For long GUI or broad component suites, split the same selected suite into
146+
deterministic zero-based shards. Start each shard in a separate MATLAB process
147+
and give each shard a distinct run name:
152148

153149
```matlab
154150
runLabKitTests("Suites", "gui", "IncludeGui", true, "GuiMode", "hidden", ...
155151
"ShardCount", 3, "ShardIndex", 0, "RunName", "gui_shard_0", ...
156152
"HtmlReport", false)
157153
```
158154

159-
Repeat with `ShardIndex` 1 and 2 for the remaining shards. Sharding is useful
160-
for local agent or maintainer iteration when MATLAB licenses and host resources
161-
allow multiple processes. Use the same suite, test-name filters, tags, and GUI
162-
settings for every shard; vary only `ShardIndex` and `RunName`. Keep final
163-
handoff validation on the official build task unless the sharded runs together
164-
cover the same selected suite and all shards passed.
165-
166-
App GUI tests live at:
167-
168-
```text
169-
tests/cases/gui/apps/<family>/<app_slug>/
170-
```
171-
172-
When a change affects one app, `buildtool changed` maps the touched app
173-
folder to the matching GUI test folder when one exists. Shared UI, launcher,
174-
runner, or broad documentation changes map to broader build-task selections.
175-
Maintainer diagnostics under `tools/` map to project guardrails rather than the
176-
full non-GUI suite unless their tests or shared runner code also changed.
155+
Repeat with `ShardIndex` 1 and 2 for the remaining shards. Use the same suite,
156+
test-name filters, tags, and GUI settings for every shard; vary only
157+
`ShardIndex` and `RunName`. A sharded result is equivalent only when all shards
158+
passed and together cover the same selected suite.
177159

178160
## GUI Validation
179161

tests/AGENTS.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ Tests mirror source ownership. Do not create a parallel runner framework unless
2525
- Do not add a separate custom runner or direct pass/fail test tree. Build
2626
tasks are the human and CI entry points; `tests/runLabKitTests.m` is the
2727
lower-level implementation behind those tasks.
28+
- Do not add public build tasks for every timing strategy. Keep the public
29+
build-task set compact and improve changed-file planner routing, printed
30+
plan reasons, representative selectors, or sharding support instead.
2831
- CI may shard non-GUI validation across multiple GitHub Actions jobs for
2932
wall-clock speed. Keep those shards as CI-only buildfile tasks invoked with
3033
`matlab-actions/run-build`; workflow YAML must not call
@@ -112,15 +115,17 @@ Tests mirror source ownership. Do not create a parallel runner framework unless
112115

113116
## Validation Scope Discipline
114117

115-
- Use the changed-file planner once to choose the initial affected scope for a
116-
dirty worktree. If that planned run fails, fix the specific failure and
117-
rerun the narrowest failing scope or failing suite directly; do not rerun
118-
`buildtool changed` just to rediscover the same plan.
119-
- Use the fast changed-file task for tight local iteration when shared UI or
120-
broad GUI-adjacent edits would trigger full downstream app GUI coverage. It
121-
is an iteration gate, not the final handoff gate; run the conservative changed
122-
task or the relevant broad task before pushing substantive
123-
validation-routing changes.
118+
- Use the fast changed-file build task for local iteration and the
119+
conservative changed-file build task for pre-handoff validation when git
120+
state is available. These tasks should route from the current diff and print
121+
why each selected scope is being run.
122+
- If a changed-file plan fails, fix the specific failure and rerun the
123+
narrowest failing scope or suite directly; do not rerun the changed-file
124+
planner just to rediscover the same plan.
125+
- For new timing strategies, first improve planner routing or representative
126+
selectors. Add a new public task only when the workflow cannot be expressed
127+
through the compact task set listed in `docs/testing.md` or a focused
128+
`runLabKitTests` invocation.
124129
- Prefer `runLabKitTests("Suites", "...")` for rerunning a failed suite such
125130
as `project`, `labkit/ui`, `labkit/image`, `labkit/thermal`, or
126131
`apps/image_measurement`. Rerun broader

tests/cases/contract/project/build/BuildTaskEfficiencyGuardrailTest.m

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,28 @@ function changedFastValidationPlanUsesRepresentativeAppGuiCoverage(testCase)
2929
"+labkit/+ui/+app/runBusy.m", "Mode", "fast");
3030
signatures = validationStepSignatures(steps);
3131
tests = validationStepTestSelectors(steps);
32+
reasons = validationStepReasons(steps);
3233

3334
testCase.verifyTrue(any(signatures == "labkit/ui|false"), ...
3435
"Fast changed UI validation should keep reusable UI non-GUI coverage.");
3536
testCase.verifyTrue(any(signatures == "gui/labkit/ui|true"), ...
3637
"Fast changed UI validation should keep reusable UI GUI coverage.");
38+
testCase.verifyTrue(any(signatures == "gui/gesture/labkit/ui|true"), ...
39+
"Fast changed UI validation should keep representative UI gesture coverage.");
3740
testCase.verifyFalse(any(signatures == "gui/apps|true"), ...
3841
"Fast changed UI validation should avoid the full downstream app GUI suite.");
3942
testCase.verifyTrue(any(signatures == ...
4043
"gui/apps/image_measurement/image_enhance,gui/apps/image_measurement/batch_crop|true"), ...
4144
"Fast changed UI validation should run representative downstream app GUI coverage.");
4245
testCase.verifyTrue(any(contains(tests, "test_gui_layout_ui_declarative_app") & ...
46+
contains(tests, "test_gui_layout_ui_scale_bar_tool") & ...
4347
contains(tests, "test_gui_layout_ui_debug_trace")), ...
4448
"Fast changed UI validation should use reusable UI test-name selectors.");
49+
testCase.verifyTrue(any(contains(tests, ...
50+
"referenceEditAndPlacementEmitStructuredTrace")), ...
51+
"Fast changed UI validation should include representative scale-bar gesture coverage.");
52+
testCase.verifyTrue(all(strlength(reasons) > 0), ...
53+
"Changed validation plan steps should explain why each scope was selected.");
4554

4655
appGuiSelectors = [ ...
4756
"image_enhance_workflow_applies_tool_and_exports", ...
@@ -52,6 +61,14 @@ function changedFastValidationPlanUsesRepresentativeAppGuiCoverage(testCase)
5261
verifySelectorsMatchTests(testCase, appGuiSelectors, ...
5362
["gui/apps/image_measurement/image_enhance", ...
5463
"gui/apps/image_measurement/batch_crop"]);
64+
verifySelectorsMatchTests(testCase, [ ...
65+
"test_gui_layout_ui_declarative_app", ...
66+
"test_gui_layout_ui_scale_bar_tool", ...
67+
"test_gui_layout_ui_debug_trace"], ...
68+
"gui/labkit/ui");
69+
verifySelectorsMatchTests(testCase, ...
70+
"referenceEditAndPlacementEmitStructuredTrace", ...
71+
"gui/gesture/labkit/ui");
5572
end
5673
end
5774
end
@@ -73,6 +90,15 @@ function changedFastValidationPlanUsesRepresentativeAppGuiCoverage(testCase)
7390
end
7491
end
7592

93+
function reasons = validationStepReasons(steps)
94+
reasons = strings(1, numel(steps));
95+
for k = 1:numel(steps)
96+
if isfield(steps(k), "Reason")
97+
reasons(k) = string(steps(k).Reason);
98+
end
99+
end
100+
end
101+
76102
function verifySelectorsMatchTests(testCase, selectors, suites)
77103
output = listLabKitTestsQuietly( ...
78104
"Suites", suites, ...

tests/runLabKitTests.m

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,10 @@ function ensurePlanHasNoExplicitSelectors(opts)
196196

197197
function printValidationPlanSteps(steps)
198198
for k = 1:numel(steps)
199-
fprintf(" %d. %s includeGui=%d suites=%s tests=%s\n", ...
199+
fprintf(" %d. %s includeGui=%d suites=%s tests=%s reason=%s\n", ...
200200
k, steps(k).RunNameSuffix, steps(k).IncludeGui, ...
201-
stepSuitesLabel(steps(k)), stepTestsLabel(steps(k)));
201+
stepSuitesLabel(steps(k)), stepTestsLabel(steps(k)), ...
202+
stepReasonLabel(steps(k)));
202203
end
203204
end
204205

@@ -217,6 +218,14 @@ function printValidationPlanSteps(steps)
217218
end
218219
end
219220

221+
function label = stepReasonLabel(step)
222+
if ~isfield(step, "Reason") || strlength(string(step.Reason)) == 0
223+
label = "<unspecified>";
224+
else
225+
label = char(step.Reason);
226+
end
227+
end
228+
220229
function steps = namedValidationPlan(planName)
221230
switch planName
222231
case "ui"

0 commit comments

Comments
 (0)