Skip to content

Integration coverage for the convention features - #27

Merged
ipjohnson merged 3 commits into
mainfrom
test/convention-integration
Aug 9, 2026
Merged

Integration coverage for the convention features#27
ipjohnson merged 3 commits into
mainfrom
test/convention-integration

Conversation

@ipjohnson

@ipjohnson ipjohnson commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Thirty-five integration tests for convention registration, compiled by the analyzer through MSBuild rather than driven in memory — plus three defects they surfaced.

The generator unit tests cannot show that the analyzer loads from an ordinary ProjectReference, that both analyzer packages contribute to one partial module without colliding, or — for InAssemblyOf — that a genuine compile-time assembly reference is what gets scanned.

Three defects, none reachable from the unit tests

A decorator file declared a record module a class, and so did an interceptor file. Four writers contribute to a module's partial. Two carried their own copy of the record rewrite and two did not, so a compilation with a record module and a [Decorator] failed with CS0261 — and separately with an [Intercept]. Each missing copy only breaks a compilation that has both a record module and whatever makes that writer emit, which is why finding two instances took two separate discoveries. Now EntryModelUtil.ApplyRecordDeclaration, called by all four, so the next writer cannot get it wrong. That one-line public API addition is the whole API snapshot diff.

A metadata scan re-registered a package's own services. MetadataCandidateUtility checked only that a type was a constructible public class, where the syntax path also excludes types carrying a service attribute. An assembly whose types carry those attributes has its own module and registers them itself, so scanning it as well registers everything twice, possibly under a different lifetime — and composing that module is what the developer should be doing instead. [Decorator] is excluded for the same reason it is in the compilation being built.

What is covered

FeaturesAsSelf · AsSelfWithInterfaces including the System.* exclusion · AlsoAsSelf · WithAttribute · WithName · RegisterAll() selecting a type that implements nothing · AsMatchingInterface · As<T> · WithKey · Using(Try) · a type filling two roles with a lifetime each · one convention registering every closing · an open generic decorator over every convention-registered handler · InAssemblyOf against a real referenced assembly.

Combinations — a convention-registered service that is also intercepted · two decorators nesting by Order · a keyed registration decorated in place · records, nested types and primary constructors as candidates · an OnlyRealm module · module composition carrying its conventions · environment conditions on candidates in both environments · registrations not leaking between modules.

Corners — module-level [Decorate] · two modules scanning one interface, composed together · namespace filters reaching nested namespaces, the exact form refusing to, and the negative form · an open generic resolved at two closings · scoped actually scoping · disposal with the provider · an internal implementation as a candidate, which it is in this compilation and is not across an assembly boundary · a decorator resolving its own convention-registered dependencies · a filtered metadata scan reshaped with AsSelf.

The referenced-assembly tests

SecondarySutProject gains three policy types and no attributes. It does not reference the conventions analyzer, so they are inert until something scans them — which is what a third-party package looks like. One is internal, pinning the visibility difference.

Verification

  • dotnet build -c Release --no-incremental0 warnings
  • ./scripts/coverage.sh 85633 tests pass, 87.5% coverage
  • ./scripts/verify-packages.sh — clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01C56x6Vv6HJ6ArfqwKsuSb9

Ian Johnson and others added 3 commits August 9, 2026 12:18
Fifteen integration tests for the selection, shape and decoration features,
compiled by the analyzer through MSBuild rather than driven in memory. The unit
tests cannot show that the analyzer loads from an ordinary ProjectReference, that
both analyzer packages contribute to one partial module without colliding, or —
for InAssemblyOf — that a genuine compile-time assembly reference is what gets
scanned.

Two defects surfaced while writing them, neither reachable from the unit tests.

**A decorator file declared a record module a class.** DecoratorFileWriter did
not apply the record rewrite that DependencyFileWriter and DependencyModuleWriter
both do, so a compilation holding a record module and any decorator failed with
CS0261. It went unnoticed because a decorator file is only emitted once something
in the compilation carries [Decorator], and nothing in the integration project
did until now.

**A metadata scan re-registered a package's own services.** MetadataCandidateUtility
checked only that a type was a constructible public class, where the syntax path
also excludes types carrying a service attribute. An assembly whose types carry
those attributes has its own module and registers them itself, so scanning it as
well registers everything twice, possibly under a different lifetime — and
composing that module is what the developer should be doing instead. [Decorator]
is excluded for the same reason it is in the compilation being built.

The referenced-assembly test scans SecondarySutProject, which does not reference
the conventions analyzer, so its new policy types are inert until something scans
them. Its internal policy proves the visibility difference: only public types
cross an assembly boundary.

613 tests pass, 0 warnings, 87.4% coverage, verify-packages.sh clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C56x6Vv6HJ6ArfqwKsuSb9
Nine more integration tests, for the combinations rather than the features.
Each half works alone; nobody had run them together. Interception and decoration
do not know a service was registered by convention, conventions do not know a
candidate is intercepted, and the types people write are not all plain classes.

Covered: a convention-registered service that is also intercepted; two decorators
nesting by declared order over a convention registration; a keyed convention
registration decorated in place; records, nested types and primary constructors
as candidates, with one convention registration injected into another; an
OnlyRealm module taking its own convention registrations; composing a module
bringing its conventions with it; environment conditions on convention
candidates in both environments; and registrations not leaking between modules.

Writing them found the record-declaration bug a third time, in
InterceptorRegistrationWriter, surfaced by adding the first [Intercept] to a
project that contains a record module. Two writers had their own copy of the
rewrite and two did not, and each missing one only breaks a compilation that has
both a record module and whatever makes that writer emit — which is why this took
three separate discoveries. The rewrite is now EntryModelUtil.ApplyRecordDeclaration
and all four writers call it, so the next writer cannot get it wrong.

622 tests pass, 0 warnings, 87.5% coverage, verify-packages.sh clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C56x6Vv6HJ6ArfqwKsuSb9
Eleven more integration tests, for the shapes that are easy to get wrong and
cheap to leave untested.

Module-level [Decorate] over a convention registration, which is a different code
path from [Decorator] on the class and the form to use when the decorator or the
service comes from an assembly you do not control. Two modules scanning one
interface and composed into the same application, where realms are what keeps
both rather than one displacing the other. A namespace filter reaching nested
namespaces, the exact form refusing to, and the negative form. A generic
implementation closing nothing, registered open and resolved at two closings. A
declared scoped lifetime actually scoping across two scopes. A convention
registered singleton disposed with the provider. An internal implementation as a
candidate, which it is in the compilation being built and is not across an
assembly boundary. A decorator resolving its own convention-registered
dependencies, which works because decoration passes the wrapped instance
positionally and resolves the rest from the container. And a metadata scan
narrowed by a name filter and reshaped with AsSelf, rather than the plain form.

All eleven passed first time, which is the answer to whether the combinations
were already sound rather than merely untried.

633 tests pass, 0 warnings, 87.5% coverage, verify-packages.sh clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C56x6Vv6HJ6ArfqwKsuSb9
@ipjohnson
ipjohnson merged commit 57d79c4 into main Aug 9, 2026
2 checks passed
@ipjohnson
ipjohnson deleted the test/convention-integration branch August 9, 2026 17:09
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