Skip to content

🐛 Lock Ceedling work by build root; keep same-named files' coverage counters (Ceedling 1.2.1 validation) - #27

Merged
mkarlesky merged 2 commits into
next_versionfrom
fix/build-root-lock-and-namesake-counters
Oct 24, 2026
Merged

mkarlesky merged 2 commits into
next_versionfrom
fix/build-root-lock-and-namesake-counters

Conversation

@mkarlesky

@mkarlesky mkarlesky commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Fixes from validating next_version against a Ceedling 1.2.1 dev build (with the fixes for Ceedling #1360, #1361, #1362), plus the real-Ceedling gates on 1.0.0, 1.1.0, 1.1.10 and 1.2.0.

Fixes

  1. Projects sharing a build root collided (every Ceedling version). The lock was keyed by project directory. Ceedling's shared state lives in the build root: build/vendor rewrites before 1.2.1, the CppUnit report, Cobertura reports and gcov counters. Projects in different directories that share one (e.g. a mixin in ceedlingExplorer.mixins setting :project: :build_root:) built at once and read each other's reports. Reproduced with real Ceedling 1.2.1: 4 tests errored from another project's report. Every Ceedling command now runs inside exclusively(), which holds directory: and build: keys through the new KeyedLock.runAll(). The keys are deduplicated and taken in sorted order, so callers can't deadlock.
  2. A --test-case collision fallback cleared same-named files' counters (reachable from 1.2.1). clearTestCounters() matched any counter directory named for the file. 1.2.1 lays out gcov/out/<test dir>/<name>/…, so unit/test_dup.c lost its coverage when integration/test_dup.c's fallback ran. Reproduced: a passing test showed 0 hits. The new testBuildName() names each test's build directory exactly as Ceedling does. It mirrors Ceedling's PathMirror.relative_subdir over the resolved :paths: :test:, with +:/-: decorators and globs stripped, and the longest root holding the file wins. The fallback now clears only that directory's counters, case-folded on Windows. executableSubpath() uses the same name for debug executables, replacing its "newest build" guess. Before 1.2.0 the name is flat (nestedTestBuilds capability).
  3. --force-test-rerun stays on 1.2.1. A real-Ceedling probe showed that a counter reset without the flag gives an all-zero report on 1.2.1 too. The comments now give that reason, so nobody drops the flag for 1.2.1.
  4. Docs.
    • The README coverageCommandArgs row was stale for 1.2.1.
    • The README, KnownIssues and the engine warning now say what each version does with an ambiguous name. 1.2.0 stops with Ambiguous file reference; earlier versions may run the other file.
    • README support range through 1.2.1.
    • CHANGELOG entries.
    • Development.md: the lock and counters, plus 1.2.1 and a same-named coverage check added to the manual plan.

Verification

  • TDD: 6 new or changed engine tests and 2 lock tests, each confirmed failing before its fix.
  • just test: 458 unit and 18 integration tests. just lint: 0 findings. just coverage: ceedlingEngine.ts and concurrency.ts at 100% lines, and branch coverage at the baseline.
  • Real Ceedling, compiled engine:
    • shared-build-root stress: 0 violations, down from 4;
    • same-named counters: hits kept;
    • 1.2.1 gated-feature suite: 0 violations;
    • S2 and S4 stress on 1.2.1 and 1.0.0: 0 violations, rerun after the exact build-name change;
    • multi-root layout (+:test/**, +:extra/**) on 1.2.0 and 1.2.1: exact executables and counters.

Observed in Ceedling (not this extension)

  • Ceedling 1.2.0 and 1.2.1 can't build some same-named test files. One case is test/unit/test_calc.c under +:test/unit/** beside extra/unit/test_calc.c under +:extra/**, where one test's build name (test_calc) ends the other's path. Every build stops with EXCEPTION: Ambiguous file reference 'test_calc.c'. Only such layouts could tell the old trailing-directory heuristic from the exact rule. Layouts Ceedling builds, e.g. +:test/** and +:extra/** holding test/unit/test_calc.c and extra/integration/test_calc.c, pass with real Ceedling 1.2.0 and 1.2.1: exact debug executables, and a collision fallback that keeps the other file's coverage.

Review findings deferred

  • A project no discovery has loaded yet holds only its directory lock, so before 1.2.1 its first dumpconfig can rewrite a shared build root's vendored files under another project's build. That happens only for a project added during a sibling's run. Locking every known build root would make all discovery wait behind runs. It's documented in code and Development.md.

🤖 Generated with Claude Code

mkarlesky and others added 2 commits October 24, 2026 10:56
…ge counters

Projects in different directories sharing a build root, e.g. through a common mixin
setting :project: :build_root:, ran builds at once and read each other's reports.
Every Ceedling command now holds a lock keyed by the resolved build root as well as
the project directory, taken in sorted order through KeyedLock.runAll().

A --test-case collision's whole-file fallback cleared the counters of every test file
with the same name. It now clears only the counters beneath the file's own directory
within its test path, as Ceedling 1.2.0 and later lay them out.

The --force-test-rerun comments give the reason that holds on Ceedling 1.2.1: delta
builds ignore deleted coverage counters. The same-named-file coverage warning, README,
KnownIssues, and CHANGELOG say what each Ceedling version does with an ambiguous name,
and name 1.2.1 as supported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From Ceedling 1.2.0 a test builds under its file's directory within the longest test path
holding it, e.g. unit/test_foo for test/unit/test_foo.c under +:test/**. testBuildName()
mirrors Ceedling's PathMirror over the resolved :paths: :test:, with decorators and globs
stripped, so a coverage fallback clears exactly its test's counters and a debug build
finds exactly its executable, instead of matching trailing directories or taking the
newest build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mkarlesky
mkarlesky merged commit 80e200f into next_version Oct 24, 2026
3 checks passed
@mkarlesky
mkarlesky deleted the fix/build-root-lock-and-namesake-counters branch October 24, 2026 18:06
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.

1 participant