Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ public AssemblyEnumerator(MSTestSettings settings) =>
/// </summary>
/// <param name="assemblyFileName">The assembly file name.</param>
/// <param name="mustSerialize">Flag set to true when parameterized test data must be serialized.</param>
/// <param name="useGeneratedDescriptors">Whether native MTP discovery may consume complete source-generated descriptors.</param>
/// <returns>A collection of Test Elements.</returns>
internal AssemblyEnumerationResult EnumerateAssembly(string assemblyFileName, bool mustSerialize)
internal AssemblyEnumerationResult EnumerateAssembly(string assemblyFileName, bool mustSerialize, bool useGeneratedDescriptors = false)
{
List<string> warnings = [];
DebugEx.Assert(!StringEx.IsNullOrWhiteSpace(assemblyFileName), "Invalid assembly file name.");
Expand Down Expand Up @@ -97,7 +98,7 @@ internal AssemblyEnumerationResult EnumerateAssembly(string assemblyFileName, bo
foreach (Type type in types)
{
List<UnitTestElement> testsInType = DiscoverTestsInType(assemblyFileName, type, warnings, discoverInternals,
dataSourcesUnfoldingStrategy, mustSerialize);
dataSourcesUnfoldingStrategy, mustSerialize, useGeneratedDescriptors);
tests.AddRange(testsInType);
}

Expand Down Expand Up @@ -161,7 +162,8 @@ private List<UnitTestElement> DiscoverTestsInType(
List<string> warningMessages,
bool discoverInternals,
TestDataSourceUnfoldingStrategy dataSourcesUnfoldingStrategy,
bool mustSerialize)
bool mustSerialize,
bool useGeneratedDescriptors)
{
string? typeFullName = null;
var tests = new List<UnitTestElement>();
Expand All @@ -170,7 +172,9 @@ private List<UnitTestElement> DiscoverTestsInType(
{
typeFullName = type.FullName;
TypeEnumerator testTypeEnumerator = GetTypeEnumerator(type, assemblyFileName, discoverInternals);
List<UnitTestElement>? unitTestCases = testTypeEnumerator.Enumerate(warningMessages);
List<UnitTestElement>? unitTestCases = useGeneratedDescriptors

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ternary branch is redundant — just pass the value directly (Code Structure).

List<UnitTestElement>? unitTestCases = useGeneratedDescriptors
    ? testTypeEnumerator.Enumerate(warningMessages, useGeneratedDescriptors: true)
    : testTypeEnumerator.Enumerate(warningMessages);

Since the two-arg overload simply delegates to the same EnumerateCore, this can be simplified to a single call:

List<UnitTestElement>? unitTestCases = testTypeEnumerator.Enumerate(warningMessages, useGeneratedDescriptors);

The overload Enumerate(List<string> warnings, bool useGeneratedDescriptors) already exists and handles both paths.

? testTypeEnumerator.Enumerate(warningMessages, useGeneratedDescriptors: true)
: testTypeEnumerator.Enumerate(warningMessages);

if (unitTestCases != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private static AssemblyEnumerationResult GetTestsInIsolation(string fullFilePath
// of strings normally (by reference), and we were mutating that collection in the appdomain.
// But this does not mutate the collection outside of appdomain, so we lost all warnings that happened inside.
bool mustSerialize = !isMTP || isolationHost is TestSourceHost { UsesAppDomain: true };
return assemblyEnumerator.EnumerateAssembly(fullFilePath, mustSerialize);
return assemblyEnumerator.EnumerateAssembly(fullFilePath, mustSerialize, useGeneratedDescriptors: isMTP);
}

private static bool IsManagedAssembly(string fileName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
/// <param name="warnings"> Contains warnings if any, that need to be passed back to the caller. </param>
/// <returns> list of test cases.</returns>
internal virtual List<UnitTestElement>? Enumerate(List<string> warnings)
=> EnumerateCore(warnings, useGeneratedDescriptors: false);

internal virtual List<UnitTestElement>? Enumerate(List<string> warnings, bool useGeneratedDescriptors)
=> EnumerateCore(warnings, useGeneratedDescriptors);

private List<UnitTestElement>? EnumerateCore(List<string> warnings, bool useGeneratedDescriptors)
{
if (!_typeValidator.IsValidTestClass(_type, warnings))
{
Expand All @@ -66,7 +72,13 @@
#endif

// If test class is valid, then get the tests
return GetTests(warnings);
return useGeneratedDescriptors
&& PlatformServiceProvider.Instance.ReflectionOperations.TryGetTestMethodDescriptors(
_type,
out MethodInfo[]? descriptorMethods,
out bool areAllTestMethodsSupported)
? GetTests(warnings, descriptorMethods, areAllTestMethodsSupported)
: GetTests(warnings);
}

/// <summary>
Expand All @@ -75,26 +87,46 @@
/// <param name="warnings"> Contains warnings if any, that need to be passed back to the caller. </param>
/// <returns> List of Valid Tests. </returns>
internal List<UnitTestElement> GetTests(List<string> warnings)
=> GetTests(warnings, [], areAllTestMethodsSupported: false);

private List<UnitTestElement> GetTests(List<string> warnings, MethodInfo[] descriptorMethods, bool areAllTestMethodsSupported)
{
bool foundDuplicateTests = false;
var foundTests = new HashSet<string>();
var tests = new List<UnitTestElement>();
var tests = new List<UnitTestElement>(descriptorMethods.Length);
HashSet<MethodInfo>? descriptorMethodSet = descriptorMethods.Length == 0
? null
: new HashSet<MethodInfo>(descriptorMethods);

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0028: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0028: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0028: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028)

Check failure on line 99 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs#L99

src/Adapter/MSTestAdapter.PlatformServices/Discovery/TypeEnumerator.cs(99,15): error IDE0306: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0306)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 IDE0306/IDE0028 — Collection initialization can be simplified; use a collection expression instead of the HashSet<T> constructor with an argument, matching the analyzer rule enforced as an error in this build.

Suggested change
: new HashSet<MethodInfo>(descriptorMethods);
: [.. descriptorMethods];


// Instead of asking reflect helper to query the type for every method we have, we ask once for the type.
bool classDisablesParallelization = _reflectHelper.IsAttributeDefined<DoNotParallelizeAttribute>(_type);

// Test class is already valid. Verify methods.
// PERF: GetRuntimeMethods is used here to get all methods, including non-public, and static methods.
// if we rely on analyzers to identify all invalid methods on build, we can change this to fit the current settings.
foreach (MethodInfo method in PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethods(_type))
foreach (MethodInfo method in descriptorMethods)
{
foundDuplicateTests = foundDuplicateTests || !foundTests.Add(method.ToString() ?? method.Name);
tests.Add(GetTestFromMethod(method, classDisablesParallelization, warnings, isFromGeneratedDescriptor: true));
}

if (!areAllTestMethodsSupported)
{
if (_testMethodValidator.IsValidTestMethod(method, _type, warnings))
foreach (MethodInfo method in PlatformServiceProvider.Instance.ReflectionOperations.GetRuntimeMethods(_type))
{
// ToString() outputs method name and its signature. This is necessary for overloaded methods to be recognized as distinct tests.
foundDuplicateTests = foundDuplicateTests || !foundTests.Add(method.ToString() ?? method.Name);
UnitTestElement testMethod = GetTestFromMethod(method, classDisablesParallelization, warnings);
if (descriptorMethodSet?.Contains(method) == true)
{
continue;
}

tests.Add(testMethod);
if (_testMethodValidator.IsValidTestMethod(method, _type, warnings))
{
// ToString() outputs method name and its signature. This is necessary for overloaded methods to be recognized as distinct tests.
foundDuplicateTests = foundDuplicateTests || !foundTests.Add(method.ToString() ?? method.Name);
UnitTestElement testMethod = GetTestFromMethod(method, classDisablesParallelization, warnings);

tests.Add(testMethod);
}
}
}

Expand Down Expand Up @@ -134,8 +166,9 @@
/// <param name="method">The reflected method.</param>
/// <param name="classDisablesParallelization">Whether the test class disables parallelization.</param>
/// <param name="warnings">Contains warnings if any, that need to be passed back to the caller.</param>
/// <param name="isFromGeneratedDescriptor">Whether native MTP discovery selected this method from generated metadata.</param>
/// <returns> Returns a UnitTestElement.</returns>
internal UnitTestElement GetTestFromMethod(MethodInfo method, bool classDisablesParallelization, ICollection<string> warnings)
internal UnitTestElement GetTestFromMethod(MethodInfo method, bool classDisablesParallelization, ICollection<string> warnings, bool isFromGeneratedDescriptor = false)
{
// null if the current instance represents a generic type parameter.
DebugEx.Assert(_type.AssemblyQualifiedName != null, "AssemblyQualifiedName for method is null.");
Expand All @@ -157,6 +190,7 @@
IReflectionOperations reflectionOperations = PlatformServiceProvider.Instance.ReflectionOperations;
var testElement = new UnitTestElement(testMethod)
{
IsFromGeneratedDescriptor = isFromGeneratedDescriptor,
TestCategory = reflectionOperations.GetTestCategories(method, _type),
DoNotParallelize = classDisablesParallelization || _reflectHelper.IsAttributeDefined<DoNotParallelizeAttribute>(method),
ResourceLocks = MergeResourceLocks(GetClassResourceLocks(), ReadResourceLocks(method)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ internal interface IReflectionOperations
/// <returns>A constructor invoker delegate, or <see langword="null"/> in reflection mode.</returns>
Func<object?[]?, object>? GetConstructorInvoker(Type type);

/// <summary>
/// Gets source-generated test descriptors for native discovery.
/// </summary>
/// <param name="type">The test class to inspect.</param>
/// <param name="methods">Methods with complete generated discovery metadata.</param>
/// <param name="areAllTestMethodsSupported">Whether the generated set is authoritative for the class.</param>
/// <returns><see langword="true"/> when generated descriptor metadata exists for the class.</returns>
bool TryGetTestMethodDescriptors(Type type, [NotNullWhen(true)] out MethodInfo[]? methods, out bool areAllTestMethodsSupported);

/// <summary>
/// Gets a delegate that assigns <paramref name="property"/> directly (without
/// <see cref="PropertyInfo.SetValue(object, object)"/>), or <see langword="null"/> when no
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ public UnitTestElement(TestMethod testMethod)
#endif
internal Guid? CachedTestNodeUid { get; set; }

/// <summary>
/// Gets or sets a value indicating whether native MTP discovery materialized this element from
/// a complete source-generated descriptor instead of the legacy runtime-method scan.
/// </summary>
#if NETFRAMEWORK
[field: NonSerialized]
#endif
internal bool IsFromGeneratedDescriptor { get; set; }

internal UnitTestElement Clone()
{
var clone = (UnitTestElement)MemberwiseClone();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#nullable enable
static Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.SourceGeneration.ReflectionMetadataHook.Register(System.Reflection.Assembly! assembly, System.Type![]! types, System.Collections.Generic.IReadOnlyDictionary<System.Type!, System.Reflection.MethodInfo![]!>! testMethods, System.Collections.Generic.IReadOnlyDictionary<System.Type!, System.Attribute![]!>! typeAttributes, object![]! assemblyAttributes, System.Collections.Generic.IReadOnlyDictionary<System.Reflection.MethodInfo!, System.Attribute![]!>! methodAttributes, System.Collections.Generic.IReadOnlyDictionary<System.Reflection.MethodInfo!, System.Func<object?, object?[]?, object?>!>! methodInvokers, System.Collections.Generic.IReadOnlyDictionary<System.Type!, Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.SourceGeneration.ConstructorInvokerInfo[]!>! constructorInvokers, System.Collections.Generic.IReadOnlyDictionary<System.Reflection.PropertyInfo!, System.Action<object?, object?>!>! propertySetters) -> void
static Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.SourceGeneration.ReflectionMetadataHook.Register(System.Reflection.Assembly! assembly, System.Type![]! types, System.Collections.Generic.IReadOnlyDictionary<System.Type!, System.Reflection.MethodInfo![]!>! testMethods, System.Collections.Generic.IReadOnlyDictionary<System.Type!, System.Attribute![]!>! typeAttributes, object![]! assemblyAttributes, System.Collections.Generic.IReadOnlyDictionary<System.Reflection.MethodInfo!, System.Attribute![]!>! methodAttributes, System.Collections.Generic.IReadOnlyDictionary<System.Reflection.MethodInfo!, System.Func<object?, object?[]?, object?>!>! methodInvokers, System.Collections.Generic.IReadOnlyDictionary<System.Type!, Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.SourceGeneration.ConstructorInvokerInfo[]!>! constructorInvokers, System.Collections.Generic.IReadOnlyDictionary<System.Reflection.PropertyInfo!, System.Action<object?, object?>!>! propertySetters, System.Collections.Generic.IReadOnlyDictionary<System.Type!, System.Reflection.MethodInfo![]!>! descriptorTestMethods, System.Type![]! descriptorCompleteTypes) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ public MethodInfo[] GetRuntimeMethods(Type type)

public Func<object?[]?, object>? GetConstructorInvoker(Type type) => null;

public bool TryGetTestMethodDescriptors(Type type, [NotNullWhen(true)] out MethodInfo[]? methods, out bool areAllTestMethodsSupported)
{
methods = null;
areAllTestMethodsSupported = false;
return false;
}

public Action<object?, object?>? GetPropertySetter(PropertyInfo property) => null;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ private static SourceGeneratedReflectionDataProvider BuildMergedSnapshot(IReadOn
var typeConstructorsInvoker = new Dictionary<Type, ConstructorInvoker[]>();
var typeMethodInvokers = new Dictionary<MethodInfo, Func<object?, object?[]?, object?>>();
var typePropertySetters = new Dictionary<PropertyInfo, Action<object?, object?>>();
var descriptorTestMethods = new Dictionary<Type, MethodInfo[]>();
var descriptorCompleteTypes = new Dictionary<Type, bool>();

foreach (SourceGeneratedReflectionDataProvider provider in providers)
{
Expand All @@ -203,6 +205,8 @@ private static SourceGeneratedReflectionDataProvider BuildMergedSnapshot(IReadOn
MergeInto(typeConstructorsInvoker, provider.TypeConstructorsInvoker);
MergeInto(typeMethodInvokers, provider.TypeMethodInvokers);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge semantics for DescriptorCompleteTypes may silently regress completeness (Algorithmic Correctness).

MergeInto uses last-writer-wins (target[key] = value). If two source-generated providers register the same type — one with true (complete) and one with false (incomplete) — the final result depends on iteration order. The unit test TryGetTestMethodDescriptors_RetainsPerMethodFallbackWhenRegistrationIsIncomplete passes only because the false provider is added second.

The correct merge semantic for completeness is logical AND: a type is only complete if all providers declare it complete. Similarly, the MethodInfo[] for DescriptorTestMethods should be merged (union), not overwritten.

Consider replacing MergeInto here with a specialized merge:

if (target.TryGetValue(kvp.Key, out bool existing))
    target[kvp.Key] = existing && kvp.Value;
else
    target[kvp.Key] = kvp.Value;

MergeInto(typePropertySetters, provider.TypePropertySetters);
MergeInto(descriptorTestMethods, provider.DescriptorTestMethods);
MergeInto(descriptorCompleteTypes, provider.DescriptorCompleteTypes);
}

return new SourceGeneratedReflectionDataProvider
Expand All @@ -219,6 +223,8 @@ private static SourceGeneratedReflectionDataProvider BuildMergedSnapshot(IReadOn
TypeConstructorsInvoker = typeConstructorsInvoker,
TypeMethodInvokers = typeMethodInvokers,
TypePropertySetters = typePropertySetters,
DescriptorTestMethods = descriptorTestMethods,
DescriptorCompleteTypes = descriptorCompleteTypes,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,42 @@ public static void Register(
IReadOnlyDictionary<MethodInfo, Func<object?, object?[]?, object?>> methodInvokers,
IReadOnlyDictionary<Type, ConstructorInvokerInfo[]> constructorInvokers,
IReadOnlyDictionary<PropertyInfo, Action<object?, object?>> propertySetters)
=> Register(
assembly,
types,
testMethods,
typeAttributes,
assemblyAttributes,
methodAttributes,
methodInvokers,
constructorInvokers,
propertySetters,
EmptyDescriptorTestMethods,
[]);

/// <summary>
/// <b>Infrastructure.</b> Publishes reflection metadata and the deliberately constrained test
/// descriptors that native Microsoft.Testing.Platform discovery may consume directly.
/// </summary>
/// <remarks>
/// Descriptor methods must have complete, compatibility-safe metadata. A type listed in
/// <paramref name="descriptorCompleteTypes"/> declares that every test method on that type is
/// represented in <paramref name="descriptorTestMethods"/>; omitted types retain per-method
/// fallback to legacy discovery. This API is exclusively for generated code.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public static void Register(
Assembly assembly,
Type[] types,
IReadOnlyDictionary<Type, MethodInfo[]> testMethods,
IReadOnlyDictionary<Type, Attribute[]> typeAttributes,
object[] assemblyAttributes,
IReadOnlyDictionary<MethodInfo, Attribute[]> methodAttributes,
IReadOnlyDictionary<MethodInfo, Func<object?, object?[]?, object?>> methodInvokers,
IReadOnlyDictionary<Type, ConstructorInvokerInfo[]> constructorInvokers,
IReadOnlyDictionary<PropertyInfo, Action<object?, object?>> propertySetters,
IReadOnlyDictionary<Type, MethodInfo[]> descriptorTestMethods,
Type[] descriptorCompleteTypes)
{
if (assembly is null)
{
Expand Down Expand Up @@ -236,6 +272,16 @@ public static void Register(
throw new ArgumentNullException(nameof(propertySetters));
}

if (descriptorTestMethods is null)
{
throw new ArgumentNullException(nameof(descriptorTestMethods));
}

if (descriptorCompleteTypes is null)
{
throw new ArgumentNullException(nameof(descriptorCompleteTypes));
}

// Ownership transfer (see the remarks on this method): the source generator hands over
// freshly-built, throwaway collections and never mutates them after the call, so we store
// the passed arrays and read-only dictionaries directly instead of copying them.
Expand Down Expand Up @@ -273,6 +319,13 @@ public static void Register(
}
}

var descriptorCompleteTypeSet = new HashSet<Type>(descriptorCompleteTypes);
var descriptorCompleteness = new Dictionary<Type, bool>(descriptorTestMethods.Count);
foreach (Type type in descriptorTestMethods.Keys)
{
descriptorCompleteness[type] = descriptorCompleteTypeSet.Contains(type);
}

var provider = new SourceGeneratedReflectionDataProvider
{
Assembly = assembly,
Expand All @@ -286,6 +339,8 @@ public static void Register(
TypeMethodInvokers = methodInvokers,
TypeConstructorsInvoker = constructorInvokersMap,
TypePropertySetters = propertySetters,
DescriptorTestMethods = descriptorTestMethods,
DescriptorCompleteTypes = descriptorCompleteness,
};

lock (Lock)
Expand All @@ -311,4 +366,6 @@ public static void Register(
private static readonly Dictionary<Type, ConstructorInvokerInfo[]> EmptyConstructorInvokers = [];

private static readonly Dictionary<PropertyInfo, Action<object?, object?>> EmptyPropertySetters = [];

private static readonly Dictionary<Type, MethodInfo[]> EmptyDescriptorTestMethods = [];
}
Loading
Loading