Skip to content

CrossAppGuardCiGateTests anchors on Lite/, so the two Darling.Tests reads of Lite.Tests/ are invisible to it #3067

Closed

Description

@erikdarlingdata

CrossAppGuardCiGateTests requires that when one SKU's test project reads the other's source, that read is reachable by the CI path filter gating its suite. Two such reads exist on dev today and it sees neither, because the otherApp anchor is the app directory and Lite's test project is a sibling of it rather than a child.

The two references

  • Darling/Darling.Tests/QueryHighDopStaleMaxDopParityTests.cs:479 — const string twin = "Lite.Tests/QueryHighDopStaleMaxDopParityTests.cs";, then File.Exists(RepoPath(twin)) and a parse of the file's declared ExpectedGuardCopies.
  • Darling/Darling.Tests/ParameterSensitivityFiringSignatureParityTests.cs:336 — the same shape against Lite.Tests/ParameterSensitivityFiringSignatureParityTests.cs and its ExpectedSignatureCopies.

Both read real files that exist on disk. Both are exactly the cross-app source read this guard exists to require be filter-reachable.

The mechanism

For the Darling.Tests arm the guard passes otherApp: "Lite", and both matchers anchor on otherApp followed by a separator:

Regex.Matches(text, "\"(" + Regex.Escape(otherApp) + "[/\\\\][^\"]+)\"")

Lite.Tests/… does not match Lite/…, so neither reference enters the found set. The reverse arm does not have this problem: otherApp is Darling and Darling's test project is at Darling/Darling.Tests, inside Darling/.

This is a different cause from #3063. That one was the *.cs-only population — project XML was never opened — and it had no instance on dev until #3059 merged. This one is the anchor, and it is live now. #3065 fixed the population and left this deliberately, because it is a decision rather than a repair.

Why this is not a one-line widening

The darling filter names Lite/**/*.cs and Lite/**/*.xaml but not Lite.Tests/**. So the moment the anchor sees these two references, the guard goes red, and the exits are:

(a) Add Lite.Tests/**/!(*.md) to the darling filter. #3059 weighed and rejected exactly this: the darling filter also gates the live-PostgreSQL darling-pg job, so naming Lite's test tree there would stand up a throwaway TimescaleDB cluster for a change to any of ~300 Lite test files. That cost is the reason the entry was declined once already.

(b) Teach the guard that darling-tree-guards coverage counts. That job runs Darling.Tests whenever the build job's Run Darling tests step reports skipped, which is precisely when the darling filter did not fire. So for the Darling.Tests arm, coverage is already total — and an exemption on that basis would make the entire Darling.Tests arm of this guard vacuous, not just these two entries. That is a real argument for it and a real argument against it, which is why it needs a call.

(c) Something narrower — e.g. a per-reference allow-list carrying the darling-tree-guards reasoning as its stated bound, in the shape CommentFilterAdoptionTests (#3059) uses for its own legitimate exceptions. Keeps the arm non-vacuous and keeps the two entries honest, at the cost of a list somebody has to maintain.

Nothing is unguarded today

A Lite.Tests-only change leaves darling-tests reporting skipped, so darling-tree-guards runs the whole Darling suite and both twin guards execute. A change touching Darling/** fires darling and they execute there. Either way they run.

As with #3063, what is missing is the guard's ability to know that, and the failure direction is the same one: this guard fails toward not noticing, and a missed reference is silent.

The measurement that should decide

Between (a) and (c): how often would a Lite.Tests-only change be forced to stand up the darling-pg cluster. That is countable off recent merge history — the darling-tree-guards comment already records that 92 of 100 PRs report there without spinning up .NET at all, so the same window can say how many touched Lite.Tests without touching Darling/**. If the answer is a large fraction, (a) is as expensive as #3059 judged it and (c) wins.

Provenance

Found while fixing #3063 (PR #3065), by measuring the framing #3059's adjacent finding had proposed — that the detector "keys on Lite/ and Darling/ while Lite.Tests is a sibling of Lite". That was not the cause of #3059's own invisibility, which was the population. It is, however, a correct description of a second and currently-live gap, so it is recorded here on its own rather than folded into a population fix.

Activity

  1. erikdarlingdata commented on Sep 6, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling

    Fixed by #3075, merged to dev as 0f888e90c13. Closing by hand — a closing keyword does not fire when the PR targets dev rather than the default branch.

    Resolved via exit (c): a four-entry allow-list compared for set equality, honoured only while WholeTreeBackstopIsIntact. Two corrections to this issue's own record are worth carrying forward:

    • There are four references, not two. CommentFilterAdoptionTests.cs:99 and :106 are the ones this issue missed, and they are the strongest instances — that class enumerates the whole tree.
    • This issue's stated reason for rejecting exit (a) is false. build.yml declares three separate darling: filter blocks (lines 162 build, 673 darling-pg, 854 darling-linux), and darling-pg reads its own; nothing added to the build job's filter can stand up its cluster. darling-tree-guards has no paths filter at all. The same false claim was committed to the tree as Fixes #3052 #3059's stated reason and is corrected in Fixes #3067 #3075.

    The measurement inverted the basis for the decision: over 100 PRs merged to dev (#2919–#3073), six touched Lite.Tests/** without Darling/** and all six already ran the Darling suite — so exit (a) buys zero additional guard execution, paid for by compiling the Darling Viewer on a Lite test-file edit.

    A third distinct spelling is filed as #3076: a cross-app read written as a bare directory name carries no separator and is invisible to the matcher.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions