Summary
In reflection mode, every test resolves data-source-injected properties twice, and the second injection overwrites the property with a fresh instance whose IAsyncInitializer.InitializeAsync is never called. The bug is masked today by ObjectLifecycleService re-walking the live graph at execution time, but it surfaces immediately if that walk is removed (see closed PR #5748).
Root cause
Two interacting issues:
-
RegisterTestArgumentsAsync is called twice per test:
-
PropertyInjector.InjectReflectionPropertyAsync lacks the existingValue != null → skip guard that InjectSourceGeneratedPropertyAsync (PropertyInjector.cs:257-265) has.
Combined effect:
- 1st injection — instance A — gets tracked + initialised.
- 2nd injection — instance B — overwrites the property reference. Never tracked, never initialised.
- Test method reads the property → sees instance B, in uninitialised state.
Source-gen mode is unaffected because of the existing skip-if-set guard.
Why it's invisible today
ObjectLifecycleService.InitializeObjectWithSpanAsync calls InitializeNestedObjectsForExecutionAsync(obj) on the live graph at execution time, re-initialising whatever instance is currently bound. This masks the latent bug.
Reproduction (after removing the masking walk)
Apply the change from PR #5748 and run in reflection mode:
Bugs._2955.InheritedDataSourceTests.Test_DirectDataSource_WorksCorrectly
CombinedDataSourceTests.CombinedDataSource_WithNestedPropertyInjectionAndMultipleIAsyncInitializers
Both fail because the test reads an uninitialised second-instance property.
Proposed fix
Either or both:
- Apply the source-gen "skip if set" guard to
InjectReflectionPropertyAsync so a second injection is a no-op.
- De-dupe the two
RegisterTestArgumentsAsync call sites (TestBuilder + TestFilterService), or make the second one idempotent.
Related
Summary
In reflection mode, every test resolves data-source-injected properties twice, and the second injection overwrites the property with a fresh instance whose
IAsyncInitializer.InitializeAsyncis never called. The bug is masked today byObjectLifecycleServicere-walking the live graph at execution time, but it surfaces immediately if that walk is removed (see closed PR #5748).Root cause
Two interacting issues:
RegisterTestArgumentsAsyncis called twice per test:TestBuilder.BuildTestAsync(TestBuilder.cs:942)TestFilterService.RegisterTest(TestFilterService.cs:107)PropertyInjector.InjectReflectionPropertyAsynclacks theexistingValue != null → skipguard thatInjectSourceGeneratedPropertyAsync(PropertyInjector.cs:257-265) has.Combined effect:
Source-gen mode is unaffected because of the existing skip-if-set guard.
Why it's invisible today
ObjectLifecycleService.InitializeObjectWithSpanAsynccallsInitializeNestedObjectsForExecutionAsync(obj)on the live graph at execution time, re-initialising whatever instance is currently bound. This masks the latent bug.Reproduction (after removing the masking walk)
Apply the change from PR #5748 and run in reflection mode:
Bugs._2955.InheritedDataSourceTests.Test_DirectDataSource_WorksCorrectlyCombinedDataSourceTests.CombinedDataSource_WithNestedPropertyInjectionAndMultipleIAsyncInitializersBoth fail because the test reads an uninitialised second-instance property.
Proposed fix
Either or both:
InjectReflectionPropertyAsyncso a second injection is a no-op.RegisterTestArgumentsAsynccall sites (TestBuilder+TestFilterService), or make the second one idempotent.Related