Refactor TestMethod type metadata to a single semantic class identity#8213
Conversation
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
|
/review |
|
❌ Expert Code Review (command) failed. Please review the logs for details. |
There was a problem hiding this comment.
Pull request overview
Consolidates TestMethod's type metadata so that ManagedTypeName is computed from FullClassName rather than stored as a separate field. The constructor's managedTypeName parameter is removed, and discovery/rehydration paths and tests are updated accordingly. The interface comments are clarified to reflect the new mapping.
Changes:
TestMethod.ManagedTypeNamebecomes a derived property (=> FullClassName); the main constructor drops themanagedTypeNameparameter, andTypeEnumeratorno longer captures it fromManagedNameHelper.TestCaseExtensions.ToUnitTestElementWithUpdatedSourcenow prefers the managed-type property (semantic type) over the legacyTestClassNamePropertywhen constructing theTestMethod.ITestMethoddoc comments clarify thatFullClassNameis the semantic class name and thatManagedTypeNamemaps to it; tests updated for new constructor and added coverage for managed-type preference and equivalence.
Show a summary per file
| File | Description |
|---|---|
| src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/TestMethod.cs | Removes managedTypeName constructor parameter and field; ManagedTypeName now returns FullClassName. |
| src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ObjectModel/ITestMethod.cs | Clarifies semantic meaning of FullClassName and notes that ManagedTypeName maps to it. |
| src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs | Discards managedType from ManagedNameHelper and constructs TestMethod without it. |
| src/Adapter/MSTestAdapter.PlatformServices/Extensions/TestCaseExtensions.cs | Uses GetManagedType() (falling back to TestClassNameProperty) as the semantic class name for the new TestMethod. |
| test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModel/UnitTestElementTests.cs | Adds test asserting ManagedTypeProperty is set from FullClassName. |
| test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Extensions/TestCaseExtensionsTests.cs | Adds test verifying managed type is preferred over TestClassNameProperty. |
| test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TypeCacheTests.cs | Updates helper to new constructor signature. |
| test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestExecutionManagerTests.cs | Updates GetTestCase to new constructor signature. |
Copilot's findings
- Files reviewed: 8/8 changed files
- Comments generated: 0
| DebugEx.Assert(_type.AssemblyQualifiedName != null, "AssemblyQualifiedName for method is null."); | ||
|
|
||
| ManagedNameHelper.GetManagedNameAndHierarchy(method, out string managedType, out string managedMethod, out string?[] hierarchyValues); | ||
| ManagedNameHelper.GetManagedNameAndHierarchy(method, out _, out string managedMethod, out string?[] hierarchyValues); |
There was a problem hiding this comment.
Please keep the out string managedType and instead drop the _type.FullName!.
managedType handles generic cases more properly, I think.
There was a problem hiding this comment.
Consider also finding a unit or E2E test that captures the difference in behavior.
There was a problem hiding this comment.
Addressed in d56a41d. After extensive discussion (see Evangelink's MAJOR finding), we cannot drop _type.FullName! for FullClassName because TypeCache.LoadType calls assembly.GetType(FullClassName) which requires the closed-generic CLR name to instantiate the type — passing the open-generic managed name (e.g. MyClass1) would return an uninstantiable open-generic Type`.
The current resolution:
FullClassNamekeeps_type.FullName!(closed-generic, required for type loading).ManagedTypeNameis derived fromFullClassNameby stripping the generic argument list ([...]), producing the open-generic managed name expected by VSTest'sManagedTypeproperty.- The unused
managedTypeout parameter is now discarded without _and an explanatory comment is added.
Coverage exists in TypeEnumeratorTests.GetTestFromMethodShouldUseClosedFullClassNameAndOpenManagedTypeNameForGenericTypes (verifies both values for DummyGenericTestClass<int>, then round-trips through ToTestCase and TypeCache.GetTestMethodInfo to prove type loading still works) and TestCaseExtensionsTests.ToUnitTestElementShouldParseLegacyClosedGenericFullyQualifiedNameWhenManagedTypeIsOpenGeneric (verifies legacy closed-generic FQN rehydration).
|
@copilot address review comments |
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
The PR removed the leading `managedTypeName` parameter from the `TestMethod` constructor (it is now derived from `FullClassName`), but two test helper methods and one new test still used the old 8-argument signature, breaking the Windows build: - `UnitTestDiscovererTests.CreateTestMethod` and `UnitTestRunnerTests.CreateTestMethod`: passed `typeFullName` twice (once as the now-removed `managedTypeName` and once as `fullClassName`). Drop the leading argument so they target the new 7-arg overload. - `TypeEnumeratorTests.GetTestFromMethodShouldUseManagedTypeNameForGenericTypes`: used `nameof(DummyGenericTestClass<int>.GenericTestMethod)` which triggers IDE0340 (Use unbound generic type). Replace with `nameof(DummyGenericTestClass<>.GenericTestMethod)`. Also merges the latest `main` into the PR branch to pick up build infrastructure changes that the previous CI run did not have. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review comment #3240781680: explain why ToUnitTestElementWithUpdatedSource still falls back to TestClassNameProperty when GetManagedType() returns null. The fallback is required for TestCases produced by older runners that pre-date the managed-type properties (UnitTestElement.ToTestCase does not write the managed-type pair when ManagedMethodName is unknown). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Expert Code Review (command) completed successfully! Expert-reviewer launched in background for PR #8213. It will post the review directly. |
Evangelink
left a comment
There was a problem hiding this comment.
Review Summary
This PR has a clear goal — eliminate the redundant managedTypeName constructor parameter and derive ManagedTypeName from FullClassName — and the non-generic path is clean. However, the change introduces two behavioural regressions for generic test classes that need to be addressed before merging.
Verdict table
| # | Dimension | Finding | Severity |
|---|---|---|---|
| 1 | Algorithmic Correctness | TypeEnumerator now passes managedType (open-generic name) as fullClassName. TypeCache.LoadType uses this to call assembly.GetType(typeName), which returns an uninstantiable open-generic Type for closed-generic test classes. |
MAJOR |
| 2 | Backward Compatibility | GetTestName strips the class-name prefix from the FQN using string comparison. Old TestCases produced before this change carry a closed-generic FQN; the new code supplies the open-generic managed type as testClassName. StartsWith fails → method name is set to the entire FQN, breaking re-execution. |
MAJOR |
| 3 | API Contract | ManagedTypeName is now always non-null. HasManagedMethodAndTypeProperties / [MemberNotNullWhen] implicitly relied on either half being nullable to gate ManagedTypeProperty writes. The widened write-set is undocumented. |
MODERATE |
| 4 | Test Completeness | New generic-type test only validates property values; it does not round-trip through ToTestCase + TypeCache resolution, which is the only path where the bug would surface. |
MODERATE |
| 5 | Assertion Quality | GetPropertyValue without type arg returns object?; inconsistent with the rest of the file. |
NIT |
| 6 | Threading/Concurrency | N/A — no shared mutable state introduced. | — |
| 7 | Security/IPC | N/A — no wire-format changes. | — |
| 8 | Public API | All changed members are internal; no PublicAPI.Unshipped.txt impact. |
— |
| 9 | Performance | N/A — ManagedTypeName => FullClassName is a single property read, no allocation. |
— |
| 10 | Cross-TFM | N/A — no TFM-specific APIs introduced. | — |
| 11–21 | Remaining dimensions | No findings. | — |
Recommended fixes
-
TypeEnumerator.cs: KeepfullClassName = _type.FullName!(the exact runtime type name for closed generics) and passmanagedTypeonly where a managed-type identifier is expected (i.e.ManagedMethodName's counterpart). If the intent is truly to unify them, add a test proving thatTypeCachecan resolve and instantiate the resulting type. -
TestCaseExtensions.cs: After changingtestClassNameto prefer the managed type, add a test that re-hydrates a TestCase whose FQN was generated under the old convention (closed-generic form) and verifies the method name is parsed correctly. -
TestMethod.cs(HasManagedMethodAndTypeProperties): Add a comment or rename the property to reflect that it now only guards theManagedMethodNamebeing present (sinceManagedTypeNamecan never be null).
Generated by Expert Code Review (command) for issue #8213 · ● 10.7M
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
Addressed in cb20a82. The fix restores closed-generic |
- TypeEnumerator: discard the unused managedType out variable (use 'out _') and add a comment explaining why FullClassName uses _type.FullName! (closed-generic CLR name needed for TypeCache.LoadType) while ManagedTypeName is derived from it. - TestMethod: drop StringComparison overload of String.IndexOf for net462/uap10.0.16299 compatibility. - TestCaseExtensionsTests: use unbound generic type for nameof to satisfy IDE0340. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
✨ Enhancement
TestMethodwas carrying overlapping type metadata (FullClassName,ManagedTypeName, and historically syntactic/declaring context), which made inheritance scenarios harder to reason about. This change consolidates semantic type identity to one source and keeps adapter behavior aligned with that model.What does this improve?
TestMethod.FullClassName.Why is this valuable?
Implementation approach:
TestMethodmodel cleanupManagedTypeNameis now derived fromFullClassNameinstead of stored separately.managedTypeNamefrom the mainTestMethodconstructor.TestCaseconversion paths to constructTestMethodwithout a separate managed type argument.TestCaserehydration, semantic type is preferred when present.ITestMethodcomments to reflect that managed type maps to semantic class name (FullClassName).ManagedTypeName/FullClassNameequivalence.