-
-
Notifications
You must be signed in to change notification settings - Fork 105
refactor: Consolidate initialisation logic #4006
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…proved test discovery handling
…proved test discovery handling
…proved test discovery handling
…proved test discovery handling
…proved test discovery handling
…proved test discovery handling
…proved test discovery handling
…proved test discovery handling
…proved test discovery handling
…proved test discovery handling
…proved test discovery handling
…s and improved disposal logic
…d objects and enhance error handling during initialization
Pull Request Review: Consolidate Initialization LogicSummaryThis PR addresses issue #3992 by fixing a critical bug where IAsyncInitializer objects (like Docker containers) were being initialized during test discovery instead of test execution. ✅ Strengths1. Correct Bug FixExcellent fix introducing IAsyncDiscoveryInitializer to distinguish discovery-time vs execution-time initialization. This prevents expensive resource initialization during discovery. 2. Excellent ConsolidationSignificantly reduces code duplication with ObjectGraphDiscoverer and helper classes. 3. Performance OptimizationsGood attention to performance with pre-calculated depths, capacity pre-allocation, and lazy initialization. 4. Thread SafetyProper synchronization with Lazy and reference equality. 5. Comprehensive TestingBug recreation test validates the fix correctly.
|
| Category | Rating |
|---|---|
| Bug Fix | ⭐⭐⭐⭐⭐ |
| Code Quality | ⭐⭐⭐⭐☆ |
| Performance | ⭐⭐⭐⭐☆ |
| Thread Safety | ⭐⭐⭐⭐⭐ |
| Documentation | ⭐⭐⭐⭐☆ |
| Test Coverage | ⭐⭐⭐☆☆ |
🎯 Verdict
Conditional Approval - Excellent refactoring but CRITICAL issue must be fixed before merge.
Required:
- Fix ReferenceEqualityComparer compilation issue
- Run dotnet publish -p:PublishAot=true
- Add edge case tests
- Document cleanup requirements
Great work overall! 🎉
…s to internal for encapsulation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR consolidates initialization logic to fix issue #3992 by implementing phase-aware object initialization. The refactoring separates discovery-phase initialization (IAsyncDiscoveryInitializer) from execution-phase initialization (IAsyncInitializer), ensuring resources like Docker containers aren't started until actually needed during test execution.
Key Changes
- Phase-aware initialization: Split initialization into discovery and execution phases with explicit control via
InitializeForDiscoveryAsyncandInitializeAsyncmethods - Consolidated object graph discovery: Created
ObjectGraphDiscovererin TUnit.Core to eliminate duplicate graph traversal logic across multiple services - Performance optimizations: Added caching, removed LINQ allocations in hot paths, and introduced
ParallelTaskHelperfor efficient parallel task execution
Reviewed changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| TUnit.Core/ObjectInitializer.cs | Refactored to use Lazy pattern with phase-aware initialization methods |
| TUnit.Core/Discovery/ObjectGraphDiscoverer.cs | New centralized service consolidating object graph traversal logic |
| TUnit.Core/Services/ObjectInitializationService.cs | New public API service delegating to ObjectInitializer |
| TUnit.Engine/ObjectLifecycleService.cs | Updated to separate property injection from initialization with phase control |
| TUnit.Engine/TestInitializer.cs | Split into PrepareTest and InitializeTestObjectsAsync for phased execution |
| TUnit.Engine/TestExecutor.cs | Modified to initialize test objects after BeforeClass hooks |
| TUnit.Core/Helpers/ParallelTaskHelper.cs | New helper eliminating LINQ allocations in parallel operations |
| TUnit.Core/PropertyInjection/PropertyCacheKeyGenerator.cs | New utility centralizing cache key generation |
| TUnit.Core/Tracking/ObjectTracker.cs | Enhanced with better thread safety and disposal callback handling |
| TUnit.Assertions/Conditions/Helpers/*.cs | New assertion helpers consolidating reflection and type checking logic |
| TUnit.PublicAPI/*.verified.txt | Updated API snapshots reflecting new public interfaces |
| { | ||
| var variable = expression.Substring(startIndex, endIndex - startIndex); | ||
| // Handle lambda expressions like "async () => ..." by returning "value" | ||
| if (variable.Contains("=>") || variable.StartsWith("()", StringComparison.Ordinal)) |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using StringComparison.Ordinal is good practice. However, on line 33, the second Contains call doesn't specify a comparison type. For consistency and to avoid culture-specific issues, consider using Contains("=>", StringComparison.Ordinal) if available in the target framework, or using IndexOf("=>", StringComparison.Ordinal) >= 0.
| if (variable.Contains("=>") || variable.StartsWith("()", StringComparison.Ordinal)) | |
| if (variable.Contains("=>", StringComparison.Ordinal) || variable.StartsWith("()", StringComparison.Ordinal)) |
| foreach (var key in keys) | ||
| { | ||
| if (key > maxDepth) | ||
| { | ||
| maxDepth = key; | ||
| } | ||
| } |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
| foreach (var p in allProps) | ||
| { | ||
| if (p.CanRead && p.GetIndexParameters().Length == 0) | ||
| { | ||
| eligibleCount++; | ||
| } | ||
| } |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
| foreach (var p in allProps) | ||
| { | ||
| if (p.CanRead && p.GetIndexParameters().Length == 0) | ||
| { | ||
| result[i++] = p; | ||
| } | ||
| } |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
| foreach (var obj in kvp.Value) | ||
| { | ||
| if (!alreadyTracked.Contains(obj)) | ||
| { | ||
| newTrackableObjects.Add(obj); | ||
| } |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
| foreach (var obj in kvp.Value) | |
| { | |
| if (!alreadyTracked.Contains(obj)) | |
| { | |
| newTrackableObjects.Add(obj); | |
| } | |
| foreach (var obj in kvp.Value.Where(obj => !alreadyTracked.Contains(obj))) | |
| { | |
| newTrackableObjects.Add(obj); |
| foreach (var arg in arguments) | ||
| { | ||
| if (arg != null && PropertyInjectionCache.HasInjectableProperties(arg.GetType())) | ||
| { | ||
| injectableArgs.Add(arg); | ||
| } | ||
| } |
Copilot
AI
Dec 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
PR Review: Consolidate Initialization LogicSummary: This is a well-structured refactoring that consolidates scattered initialization logic across the codebase. The PR successfully addresses issue #3992 by ensuring IAsyncInitializer objects are not initialized during test discovery, only during test execution. Strengths1. Excellent Consolidation and DRY Principles
2. Strong Separation of Concerns
3. Performance Optimizations
4. Thread Safety
5. Excellent Documentation
6. AOT/Trimming Compatibility
Issues and RecommendationsMEDIUM: Cache Cleanup Strategy in PropertyCacheManagerIssue: PropertyCacheManager.GetCachedProperties (lines 44-72) uses a simple remove-first-half strategy when cache exceeds limit. Location: TUnit.Core/Discovery/PropertyCacheManager.cs:44-72 Concerns:
Recommendation:
MINOR: Dual Visited Set TrackingIssue: StructuralEquivalencyAssertion.CompareObjects now tracks both visitedActual and visitedExpected, which is correct for preventing infinite recursion in both object graphs. Location: TUnit.Assertions/Conditions/StructuralEquivalencyAssertion.cs:90-148 Recommendation: Add a comment explaining why both tracking sets are needed (cycles in both actual and expected graphs can cause infinite recursion). MINOR: Missing Disposal Pattern TestIssue: The bug test (BugRecreation.cs) verifies that DummyContainer is not initialized during discovery but only during execution. However, it does not verify the disposal behavior. Location: TUnit.TestProject/Bugs/3992/BugRecreation.cs Recommendation: Add a test verifying that DisposeAsync is called exactly once after test execution completes. MINOR: Error Collection Without LimitsIssue: ObjectGraphDiscoverer.DiscoveryErrors (line 63) is a ConcurrentBag that grows unbounded until ClearDiscoveryErrors() is called. Location: TUnit.Core/Discovery/ObjectGraphDiscoverer.cs:63 Scenario: In a long-running test session with many property access failures, this could accumulate significant memory. Recommendation:
Checklist VerificationBased on CLAUDE.md requirements:
Test CoverageGood:
Could Improve:
SecurityNo security concerns identified. Overall AssessmentRating: 8.5/10 This is a high-quality refactoring that significantly improves code organization, eliminates duplication, and fixes a real bug (#3992). The code follows modern C# best practices, includes excellent documentation, and demonstrates strong understanding of concurrency and performance. Should Fix:
Nice to Have: Recommendation: Approve and merge. Items 1-2 are minor and could be addressed in a follow-up PR if desired. Great work on this consolidation! The separation of discovery vs. initialization phases is much clearer now, and the fix for #3992 ensures Docker containers and other expensive resources are not spun up during test discovery. |
…proved test discovery handling
…r improved test instance handling
Fixes #3992