-
-
Notifications
You must be signed in to change notification settings - Fork 108
fix: TestExecutorAttribute respects scope hierarchy (method > class > assembly) #4374
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
… assembly) Add IScopedAttribute to TestExecutorAttribute, STAThreadExecutorAttribute, and CultureAttribute to enable proper scope-based filtering. This ensures that method-level executor attributes take precedence over class-level, which take precedence over assembly-level. Previously, all executor attributes from all scopes were invoked and each would call SetTestExecutor(), with the last one winning (typically assembly-level due to attribute collection order). Now, the existing ScopedAttributeFilter mechanism properly filters executor attributes to keep only the first occurrence (method-level due to method -> class -> assembly collection order). Fixes #4351 Co-Authored-By: Claude Opus 4.5 <[email protected]>
SummaryFixes executor attribute scope precedence by implementing IScopedAttribute on TestExecutorAttribute, STAThreadExecutorAttribute, and CultureAttribute. Critical IssuesNone found ✅ AnalysisThis PR correctly addresses issue #4351 by implementing the IScopedAttribute interface on all executor-related attributes. The fix is sound: Root Cause: Without IScopedAttribute, all executor attributes from assembly/class/method scopes were being collected and processed. Since each would call SetTestExecutor(), they would overwrite each other non-deterministically based on processing order. Solution: By implementing IScopedAttribute with ScopeType = typeof(ITestExecutor), the existing ScopedAttributeFilter.FilterScopedAttributes() mechanism now correctly filters executors using TryAdd semantics (first occurrence wins). Since attributes are collected method → class → assembly, method-level executors now correctly take precedence. Dual-Mode Compatibility: ✅ The source generator copies attributes verbatim (AttributeWriter.cs:TUnit.Core.SourceGenerator/CodeGenerators/Writers/AttributeWriter.cs:73), so the IScopedAttribute interface implementation is preserved in generated code. The reflection-based engine handles filtering via ScopedAttributeFilter (TUnit.Engine/Extensions/TestContextExtensions.cs:154). AOT Compatibility: ✅ The existing [DynamicallyAccessedMembers] annotation on the non-generic TestExecutorAttribute is preserved (TUnit.Core/Attributes/Executors/TestExecutorAttribute.cs:19 in the diff). Snapshot Tests: ✅ All .verified.txt files correctly updated, no .received.txt files committed. Consistency: ✅ All three executor-related attributes (TestExecutorAttribute, STAThreadExecutorAttribute, CultureAttribute) now implement IScopedAttribute consistently. SuggestionsConsider adding a regression test to verify executor scope precedence works correctly (method > class > assembly). The issue #4351 includes a complete reproduction case that could be adapted into a test in TUnit.TestProject. Verdict✅ APPROVE - No critical issues. The implementation is correct, follows TUnit's dual-mode architecture, maintains AOT compatibility, and properly updates public API snapshots. |
Add tests to verify that TestExecutorAttribute respects scope hierarchy: - Method-level executor overrides class-level - Class-level executor is used when no method-level exists - Tests both generic and non-generic TestExecutor attributes Verifies fix for #4351 Co-Authored-By: Claude Opus 4.5 <[email protected]>
SummaryFixes executor attribute scope hierarchy by implementing IScopedAttribute on TestExecutorAttribute, STAThreadExecutorAttribute, and CultureAttribute to ensure method > class > assembly precedence. Critical IssuesNone found ✅ AnalysisThis PR correctly addresses issue #4351 by implementing the Root Cause Confirmed: Without Solution Verified: By implementing Dual-Mode Compatibility: ✅ Both reflection (TUnit.Engine) and source generation (TUnit.Core.SourceGenerator) paths work correctly:
AOT Compatibility: ✅ The existing Snapshot Tests: ✅ All .verified.txt files correctly updated for all target frameworks, no .received.txt files committed. Test Coverage: ✅ Comprehensive tests added in TUnit.TestProject/TestExecutorScopeHierarchyTests.cs covering:
Consistency: ✅ All three executor-related attributes ( Previous Review StatusPrevious comment from thomhurst (2026-01-13) already approved this PR with similar findings. The author has addressed all concerns by adding comprehensive tests. Verdict✅ APPROVE - No critical issues. The implementation correctly fixes the scope hierarchy bug, maintains dual-mode compatibility, preserves AOT compatibility, properly updates public API snapshots, and includes excellent test coverage. |
Summary
IScopedAttributetoTestExecutorAttribute,STAThreadExecutorAttribute, andCultureAttributetypeof(ITestExecutor)as the scope type to group all executor-setting attributes togetherScopedAttributeFiltermechanism to properly filter executors by scope precedenceRoot Cause
TestExecutorAttributedid not implementIScopedAttribute, so all executor attributes from all scopes (assembly, class, method) were being invoked. Each would callSetTestExecutor(), overwriting the previous one. Since attributes are collected in method → class → assembly order, the assembly-level executor would be invoked last and incorrectly win.Solution
By implementing
IScopedAttributewithScopeType => typeof(ITestExecutor), all executor attributes are now properly filtered by theScopedAttributeFilterwhich usesTryAddsemantics (first occurrence wins). Since attributes are collected method-first, method-level executors now correctly take precedence.Test plan
Fixes #4351
🤖 Generated with Claude Code