Integration coverage for the convention features - #27
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 — forInAssemblyOf— 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 withCS0261— 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. NowEntryModelUtil.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.
MetadataCandidateUtilitychecked 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
Features —
AsSelf·AsSelfWithInterfacesincluding theSystem.*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 ·InAssemblyOfagainst 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 · anOnlyRealmmodule · 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 · aninternalimplementation 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 withAsSelf.The referenced-assembly tests
SecondarySutProjectgains 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 isinternal, pinning the visibility difference.Verification
dotnet build -c Release --no-incremental— 0 warnings./scripts/coverage.sh 85— 633 tests pass, 87.5% coverage./scripts/verify-packages.sh— clean🤖 Generated with Claude Code
https://claude.ai/code/session_01C56x6Vv6HJ6ArfqwKsuSb9