Skip to content

fix: handle nested types and constructor parameters in UID filter matching#4669

Merged
thomhurst merged 5 commits intomainfrom
claude/fix-test-explorer-issue-bhmyR
Feb 5, 2026
Merged

fix: handle nested types and constructor parameters in UID filter matching#4669
thomhurst merged 5 commits intomainfrom
claude/fix-test-explorer-issue-bhmyR

Conversation

@thomhurst
Copy link
Owner

The CouldMatchUidFilter method was failing to match tests when using
VS Test Explorer because:

  1. For nested types (e.g., Outer+Inner), Type.Name only returns the
    innermost class name, but UIDs contain the full path with '+' separators

  2. For classes with constructor parameters, UIDs contain '(' after the
    class name (e.g., MyClass(String).0.0.Method), but the code only
    checked for '.' or '<'

This fix:

  • Adds BuildClassNameForMatching() to construct the full nested type
    hierarchy matching TestIdentifierService.WriteTypeNameWithGenerics
  • Validates the character after the class name prefix is a valid
    boundary: '.', '<', or '('
  • Maintains protection against substring matching (ABCV vs ABCVC)

Fixes #4656 (follow-up issue after PR #4659)

https://claude.ai/code/session_016KqnWq2pDNM7sDLSn6LvRe

…ching

The CouldMatchUidFilter method was failing to match tests when using
VS Test Explorer because:

1. For nested types (e.g., Outer+Inner), Type.Name only returns the
   innermost class name, but UIDs contain the full path with '+' separators

2. For classes with constructor parameters, UIDs contain '(' after the
   class name (e.g., MyClass(String).0.0.Method), but the code only
   checked for '.' or '<'

This fix:
- Adds BuildClassNameForMatching() to construct the full nested type
  hierarchy matching TestIdentifierService.WriteTypeNameWithGenerics
- Validates the character after the class name prefix is a valid
  boundary: '.', '<', or '('
- Maintains protection against substring matching (ABCV vs ABCVC)

Fixes #4656 (follow-up issue after PR #4659)

https://claude.ai/code/session_016KqnWq2pDNM7sDLSn6LvRe
@claude
Copy link
Contributor

claude bot commented Feb 5, 2026

Code Review - Two Issues Found

1. Allocation in Hot Path (CLAUDE.md Violation)

The new BuildClassNameForMatching() method allocates a List in test discovery (hot path #1). Lines 283-306 allocate List and use string.Join().

CLAUDE.md Rule: Performance First - Minimize allocations in hot paths (test discovery, execution)

See:

4. **Performance First** - Minimize allocations in hot paths. Cache reflection. Use `ValueTask` for potentially-sync operations.

The established pattern in TestIdentifierService.WriteTypeNameWithGenerics (lines 118-184) uses ValueListBuilder for the same operation.

Suggested fix: Use ValueListBuilder instead of List to match the established pattern.


2. Critical Bug: Nested Generic Types Not Handled

BuildClassNameForMatching() does not include generic type arguments for outer types in nested hierarchies. This causes incorrect filtering.

Example for Outer.Inner:

  • TestIdentifierService generates: MyNamespace.Outer<System.Int32>+Inner.0.0.Method
  • BuildClassNameForMatching generates: MyNamespace.Outer+Inner
  • Result: StartsWith check fails

Root cause: Lines 293-300 strip the backtick suffix but dont append generic type arguments like TestIdentifierService does (lines 143-155).

This breaks TUnits dual-mode architecture by creating inconsistency between test identification and filtering.

Suggested fix: Include generic type arguments for ALL types in the hierarchy, matching the format in TestIdentifierService.WriteTypeNameWithGenerics lines 143-155.

@thomhurst thomhurst disabled auto-merge February 5, 2026 13:02
Address code review feedback:

1. Performance: Replace List<string> with ValueListBuilder<string> and
   ValueStringBuilder to avoid allocations in the hot path during test
   discovery.

2. Bug fix: Include generic type arguments for all types in the nested
   hierarchy. For Outer<int>.Inner, the UID contains
   Outer<System.Int32>+Inner, not Outer+Inner.

The implementation now exactly mirrors TestIdentifierService.WriteTypeNameWithGenerics
to ensure consistent UID format matching.

https://claude.ai/code/session_016KqnWq2pDNM7sDLSn6LvRe
Add test fixtures and integration tests covering:
- Nested classes (Outer+Inner format)
- Deeply nested classes (Outer+Inner+Deep format)
- Overlapping class names (FilterTest vs FilterTestHelper)
- Method name boundary matching (Test vs TestMethod)
- Original issue regression (ABCV vs ABCVC)

Test fixtures in TUnit.TestProject/Bugs/4656/UidFilterMatchingTests.cs
Integration tests in TUnit.Engine.Tests/UidFilterMatchingTests.cs

https://claude.ai/code/session_016KqnWq2pDNM7sDLSn6LvRe
- Fix source generator crash (TUNIT0999) for non-generic types nested
  inside generic outer types by adding Arity > 0 guard in
  GloballyQualified() and skipping such types in test metadata generation
- Move [Arguments] from method level to class level for test classes
  with constructor parameters (fixes TUnit0038/TUnit0050 errors)
- Fix engine test tree node filters to use innermost class name instead
  of nested type path format (OuterClass+InnerClass -> InnerClass)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@thomhurst
Copy link
Owner Author

Code Review

No issues found. Checked for bugs and CLAUDE.md compliance.

Add [GenerateGenericTest] attributes to open generic test classes so they
can be instantiated with concrete type arguments. Remove DeeplyNestedClass
(3-level nesting fails in net8.0 AOT) and InnerNonGenericClass (nested
inside open generic, can't be source-generated).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@thomhurst thomhurst enabled auto-merge (squash) February 5, 2026 20:12
@thomhurst thomhurst merged commit 315ee81 into main Feb 5, 2026
12 of 13 checks passed
@thomhurst thomhurst deleted the claude/fix-test-explorer-issue-bhmyR branch February 5, 2026 20:25
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.

[Bug]: [After(TestDiscovery)] TestDiscoveryContext.AllTests is incorrect

2 participants