Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Aug 12, 2025

When using the [InheritsTests] attribute to inherit test methods from base classes in different projects, the categories on those inherited test methods were lost during source generation mode. This worked correctly in reflection mode, creating an inconsistency between the two execution modes.

Problem

Consider this scenario:

// In TUnit.TestProject.Library
public abstract class BaseTests
{
    [Test]
    [Category("BaseCategory")]
    public void BaseTest() { }
}

// In TUnit.TestProject  
[InheritsTests]
public class DerivedTests : BaseTests { }

In source generation mode, the BaseTest method would lose its BaseCategory attribute when inherited, but in reflection mode it would retain it correctly.

Root Cause

The issue was in AttributeWriter.WriteAttributes() where attributes without an ApplicationSyntaxReference were being skipped entirely. This occurs for attributes from different assemblies/projects during compilation, which is exactly the case for inherited test methods from base classes in different projects.

Solution

  • Enhanced attribute filtering: Modified WriteAttributes() to include attributes from other assemblies that have a valid AttributeClass
  • Dual processing paths: Updated WriteAttribute() to handle both syntax-based attributes (current compilation) and metadata-based attributes (cross-assembly)
  • Improved cross-assembly support: Enhanced WriteAttributeWithoutSyntax() to properly reconstruct attributes using compilation metadata

The fix ensures that a CategoryAttribute("BaseCategory") from an inherited method now correctly generates:

new global::TUnit.Core.CategoryAttribute("BaseCategory")

This maintains behavioral parity between source generation and reflection modes, ensuring inherited test categories are preserved regardless of which execution mode is used.

Fixes #2880.


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

Copilot AI and others added 3 commits August 12, 2025 18:46
Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
…ation

Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] Inherited tests across projects do not retain their categories Fix inherited test categories issue in source generation mode Aug 12, 2025
Copilot finished work on behalf of thomhurst August 12, 2025 18:59
@Copilot Copilot AI requested a review from thomhurst August 12, 2025 18:59
@thomhurst thomhurst marked this pull request as ready for review August 12, 2025 19:04
@thomhurst
Copy link
Owner

@copilot In InheritedCategoryTestValidation , let's do something like

await Assert.That(TestContext.Current!.TestDetails.Categories.Contains("ExpectedCategory");

Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Aug 12, 2025

@copilot In InheritedCategoryTestValidation , let's do something like

await Assert.That(TestContext.Current!.TestDetails.Categories.Contains("ExpectedCategory");

Updated InheritedCategoryTestValidation to use the suggested assertion pattern. The tests now properly verify that inherited categories are preserved using await Assert.That(TestContext.Current!.TestDetails.Categories.Contains("ExpectedCategory")). Commit aabc5f0.

Copilot finished work on behalf of thomhurst August 12, 2025 19:14
@thomhurst thomhurst enabled auto-merge (squash) August 12, 2025 19:16
Copy link
Contributor

claude bot commented Aug 12, 2025

Claude finished @Copilot's task —— View job


Pull Request Review

Reviewing PR #2881 - Fix inherited test categories issue in source generation mode

Todo List:

  • Read and analyze changed files
  • Review core fix in AttributeWriter.cs
  • Analyze test coverage and validation
  • Check for potential issues and edge cases
  • Provide comprehensive feedback

Review Summary

This PR successfully addresses the issue where inherited test methods from base classes in different projects lose their category attributes during source generation mode. The fix is well-implemented and maintains parity between source generation and reflection modes.

Code Quality & Best Practices

Excellent Implementation:

  • Clean separation of concerns: The fix properly separates syntax-based attribute processing (current compilation) from metadata-based processing (cross-assembly) in AttributeWriter.cs:43-54
  • Robust filtering logic: The enhanced filtering in WriteAttributes() at lines 19-21 correctly includes attributes from other assemblies with valid AttributeClass
  • Consistent method structure: WriteAttributeWithoutSyntax() mirrors the structure of the syntax-based approach, maintaining code consistency

Strong Error Handling:

  • Proper null checks for ApplicationSyntaxReference and AttributeClass
  • Safe navigation patterns throughout the implementation
  • Graceful degradation when syntax information is unavailable

Technical Correctness

Core Fix Analysis:

  • Root cause properly addressed: The issue of skipping attributes without ApplicationSyntaxReference is resolved by the enhanced filtering logic
  • Dual processing paths: Lines 43-54 in WriteAttribute() correctly route to either syntax-based or metadata-based processing
  • Cross-assembly attribute reconstruction: WriteAttributeWithoutSyntax() properly reconstructs attributes using TypedConstantParser.GetRawTypedConstantValue() for both constructor and named arguments

Generated Code Quality:

  • Correctly generates new global::TUnit.Core.CategoryAttribute("BaseCategory") as verified in tests
  • Maintains fully qualified type names to prevent namespace conflicts
  • Handles both single and multiple category attributes properly

Test Coverage

Comprehensive Test Strategy:

  • Source generator tests: InheritedTestsFromDifferentProjectTests.cs validates correct code generation with explicit string matching
  • Runtime validation tests: InheritedCategoryTestValidation.cs verifies categories are available at runtime using TestContext.Current
  • Multiple scenarios covered: Single categories, multiple categories, and cross-project inheritance

Test Quality:

  • Tests follow the established pattern using await Assert.That(condition)
  • Clear test names that describe the scenario being validated
  • Appropriate separation between generation-time and runtime validation

Performance Considerations

Efficient Implementation:

  • Minimal overhead: Only processes attributes that can actually be written (filtering at lines 19-21)
  • No redundant processing: Early returns in WriteAttributeWithoutSyntax() when no additional properties needed
  • Reuses existing infrastructure: Leverages TypedConstantParser and GloballyQualified() extension methods

Security Considerations

No Security Concerns Identified:

  • No user input processing or external data access
  • Uses compiler-provided metadata exclusively
  • Generated code uses fully qualified type names preventing injection attacks

🔍 Minor Observations

Code Clarity:

  • The comment at lines 45-46 could be more specific: "For attributes from other assemblies (like inherited methods from base classes in different projects)"
  • The method WriteDataSourceGeneratorPropertiesWithoutSyntax() duplicates logic from WriteDataSourceGeneratorProperties() - consider extracting common functionality

Edge Case Handling:

  • The implementation correctly handles edge cases like attributes with only named arguments, only constructor arguments, or both
  • Data source generator properties are properly handled for cross-assembly attributes

🎯 Recommendations

  1. Consider refactoring: Extract common data source generator property writing logic to reduce duplication between lines 172-204 and 264-296
  2. Documentation: Consider adding XML documentation to WriteAttributeWithoutSyntax() to explain when it's used
  3. Integration testing: The current tests are excellent - no additional testing needed

📋 Final Assessment

This is a high-quality fix that:

  • ✅ Correctly identifies and solves the root cause
  • ✅ Maintains backward compatibility
  • ✅ Includes comprehensive test coverage
  • ✅ Follows established patterns and conventions
  • ✅ Has no performance or security concerns

The implementation demonstrates deep understanding of the Roslyn API and properly handles the complexity of cross-assembly attribute processing during source generation.

Recommendation: APPROVE


…ojectTests.cs

Co-authored-by: Stuart Lang <stuart.b.lang@gmail.com>
This was referenced Aug 19, 2025
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.

Inherited tests across projects do not retain their categories
3 participants