Skip to content

feat(test): --reuse-build skips injection and mxbuild when nothing changed - #1783

Draft
MendixMau wants to merge 3 commits into
mendixlabs:mainfrom
MendixMau:feature/1782-test-reuse-build
Draft

MendixMau wants to merge 3 commits into
mendixlabs:mainfrom
MendixMau:feature/1782-test-reuse-build

Conversation

@MendixMau

@MendixMau MendixMau commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1782.

What

Adds mxcli test --reuse-build. On a repeat mxcli test run, if the project
tree, the generated test-endpoint/test-microflow MDL, the mxcli executable
and the mxbuild path/flags all hash to the same key as the build currently
on disk (pinned to deployment/model/model.mdp's identity so a build made
by anything else is never mistaken for ours), skip injection and the
mxbuild rebuild and boot straight from the existing deployment/.
--constant values are runtime configuration, not model, so different
constants don't invalidate reuse.

Why

Repeat mxcli test runs during iterative test-writing pay a full
inject+mxbuild+boot cycle every time, even when only a test file changed
and the model is untouched.

Real-app numbers (with denominators)

Measured against a copy of a real ~280MB Mendix app
(~/mta-lab/w/r1test, 14 tests across 3 .test.mdl files), machine load
noted at each measurement (1-minute load average). An earlier pass on this
same app was re-run after it turned out that pass had not recorded load,
and the machine was later found to be running at load 44-58 from unrelated
concurrent sessions during parts of this work — the numbers below are the
re-confirmed ABAB set, captured while load was climbing back from a 13.38
trough (13-32 band throughout, well below the 44-58 band seen elsewhere):

run time load (1-min avg, start)
full rebuild (no --reuse-build) 96s 15.5
full rebuild (forced stale-model rebuild) 66s 24.6
--reuse-build, no build recorded yet (builds) 66s 26.4
--reuse-build steady-state hit 12s 21.7
--reuse-build steady-state hit (repeat) 12s 20.8

5.5x reduction (66s rebuild vs 12s reuse hit) in the "I changed a test, not
the model" loop, 14/14 green on every run.

A real bug found and fixed during this work

The first working version did not exclude theme-cache/ from the hashed
project tree. mxbuild rewrites theme-cache/web/theme.compiled.css on
every build with a non-deterministic cache-busting query string on every
font/asset url() (Atlas_Styling.ttf?<token>), even with zero theme or
model change. Because that file lives inside the project tree, it was
counted as a build input — so the hash key never repeated across two
separate mxcli test invocations on any app using the Atlas Core theme
(effectively all of them), and --reuse-build was a complete no-op on real
apps despite passing synthetic unit tests. Fixed by adding theme-cache to
the skip-dir set; re-verified on the real app (12.15s reuse hit vs the prior
~60-67s miss on every single call).

How it was tested

  • 12 unit tests, cmd/mxcli/testrunner/reusebuild_test.go:
    go test ./cmd/mxcli/testrunner/... -run 'TestHashProjectTree|TestReusableBuild|TestTestBuildKey|TestForgetBuild|TestRecordBuild' -v → all PASS.
  • Real-app A/B timings above.
  • 5 negative controls against the real app (4 applicable to this change's
    scope):
    1. Wrong @expect → red; --reuse-build of that build stays red;
      restoring the file → rebuild, green.
    2. Seeded model bug (an attribute guard removed) → rebuild red on the
      affected test; --reuse-build reuses that red build, stays red;
      reverting the bug → rebuild, green.
    3. Broken model (microflow missing a required return) → build fails
      cleanly, never reports a pass.
    4. Stale model via an independent touch of deployment/model/model.mdp
      → correctly detected as stale (identity mismatch), falls back to a
      full rebuild.
    5. Rollback-leaves-0-rows: not applicable — this flag only decides
      whether to skip inject+build+boot; it never touches
      execution/assertion/rollback behavior.

Known limitation (not a bug)

Interleaving a plain (non---reuse-build) run between two --reuse-build
runs invalidates the chain: the plain run's own inject-then-cleanup
round-trip changes the project tree enough that the following
--reuse-build call correctly (safely) misses and rebuilds — the intended
fail-safe, but it means strict alternation between the two modes never
shows the steady-state number, only sustained --reuse-build use does.

Checklist (CONTRIBUTING.md)

  • make build succeeds
  • make lint passes
  • make test — unit tests for the touched package pass; full-repo
    suite run is in progress (will update before marking ready for
    review)
  • Mendix Studio Pro validation — not applicable: this change affects
    only mxcli's own test-runner build-reuse decision, not generated MDL,
    BSON serialization, or any entity/microflow model shape
  • One concern per PR

Marked as draft until the full make test run is confirmed green and
CI passes.

🤖 Generated with Claude Code

MendixMau and others added 3 commits November 9, 2026 20:06
…ests and mxcli are unchanged

A local test run injects the test microflows, builds with mxbuild and boots.
For an unchanged project, unchanged test files and an unchanged mxcli the
injected model is the same as last time, so the deployment left in
<project>/deployment is already the build this run would produce.

The key is a content hash, never a timestamp: every file of the project
folder (minus output/cache dirs), the generated endpoint and test microflow
text, the mxcli executable and the mxbuild override. The record also pins
the identity of deployment/model/model.mdp, so a build made by anything else
in that folder (run --local, mxbuild by hand) is never taken for ours. Any
doubt means a normal build.
…stale-model refusal

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
 mendixlabs#1782)

mxbuild rewrites theme-cache/web/theme.compiled.css on every build with a
non-deterministic cache-busting query string on each font/asset url(), even
with zero theme or model changes. Without this exclusion the hash key never
repeats across two separate 'mxcli test' invocations on any app using the
Atlas Core theme, so --reuse-build silently rebuilt every time and never
paid for itself. Found by running --reuse-build twice in a row against a
real app and seeing it rebuild both times; re-verified after the fix (reuse
hit: 12.15s vs ~60-67s miss).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: mxcli test --reuse-build — skip injection and mxbuild when nothing changed

1 participant