Skip to content

Commit e5893ee

Browse files
Don't return null types from AssemblyEnumerator when ReflectionTypeLoadException is encountered by @Youssef1313 in #6276 (backport to rel/3.10) (#6278)
Co-authored-by: Youssef1313 <youssefvictor00@gmail.com>
1 parent 080e007 commit e5893ee

File tree

4 files changed

+20
-15
lines changed

4 files changed

+20
-15
lines changed

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,6 @@ internal AssemblyEnumerationResult EnumerateAssembly(string assemblyFileName)
106106

107107
foreach (Type type in types)
108108
{
109-
if (type == null)
110-
{
111-
continue;
112-
}
113-
114109
List<UnitTestElement> testsInType = DiscoverTestsInType(assemblyFileName, type, warnings, discoverInternals,
115110
dataSourcesUnfoldingStrategy, testIdGenerationStrategy, fixturesTests);
116111
tests.AddRange(testsInType);
@@ -150,7 +145,10 @@ internal static Type[] GetTypes(Assembly assembly, string assemblyFileName, ICol
150145
}
151146
}
152147

153-
return ex.Types!;
148+
// We already logged a warning or error.
149+
// So don't return types that failed to load.
150+
// The intent of the catch is to gracefully run the types that we could load.
151+
return ex.Types is null ? [] : [.. ex.Types.Where(t => t is not null)!];
154152
}
155153
}
156154

src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -422,18 +422,25 @@ private TestAssemblyInfo GetAssemblyInfo(Assembly assembly)
422422
assemblyInfo.AssemblyCleanupMethodTimeoutMilliseconds = @this.TryGetTimeoutInfo(methodInfo, FixtureKind.AssemblyCleanup);
423423
}
424424

425-
if (methodInfo is { IsPublic: true, IsStatic: true, IsGenericMethod: false, DeclaringType.IsGenericType: false, DeclaringType.IsPublic: true } &&
426-
methodInfo.GetParameters() is { } parameters && parameters.Length == 1 && parameters[0].ParameterType == typeof(TestContext) &&
427-
methodInfo.IsValidReturnType())
425+
bool isGlobalTestInitialize = @this._reflectionHelper.IsAttributeDefined<GlobalTestInitializeAttribute>(methodInfo, inherit: true);
426+
bool isGlobalTestCleanup = @this._reflectionHelper.IsAttributeDefined<GlobalTestCleanupAttribute>(methodInfo, inherit: true);
427+
428+
if (isGlobalTestInitialize || isGlobalTestCleanup)
428429
{
429-
bool isGlobalTestInitialize = @this._reflectionHelper.IsAttributeDefined<GlobalTestInitializeAttribute>(methodInfo, inherit: true);
430-
bool isGlobalTestCleanup = @this._reflectionHelper.IsAttributeDefined<GlobalTestCleanupAttribute>(methodInfo, inherit: true);
431-
if (isGlobalTestInitialize)
430+
// Only try to validate the method if it already has the needed attribute.
431+
// This avoids potential type load exceptions when the return type cannot be resolved.
432+
// NOTE: Users tend to load assemblies in AssemblyInitialize after finishing the discovery.
433+
// We want to avoid loading types early as much as we can.
434+
bool isValid = methodInfo is { IsSpecialName: false, IsPublic: true, IsStatic: true, IsGenericMethod: false, DeclaringType.IsGenericType: false, DeclaringType.IsPublic: true } &&
435+
methodInfo.GetParameters() is { } parameters && parameters.Length == 1 && parameters[0].ParameterType == typeof(TestContext) &&
436+
methodInfo.IsValidReturnType();
437+
438+
if (isValid && isGlobalTestInitialize)
432439
{
433440
assemblyInfo.GlobalTestInitializations.Add((methodInfo, @this.TryGetTimeoutInfo(methodInfo, FixtureKind.TestInitialize)));
434441
}
435442

436-
if (isGlobalTestCleanup)
443+
if (isValid && isGlobalTestCleanup)
437444
{
438445
assemblyInfo.GlobalTestCleanups.Add((methodInfo, @this.TryGetTimeoutInfo(methodInfo, FixtureKind.TestCleanup)));
439446
}

test/IntegrationTests/MSTest.Acceptance.IntegrationTests/TestDiscoveryWarningsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public async Task DiscoverTests_ShowsWarningsForTestsThatFailedToDiscover(string
3535

3636
TestHostResult testHostResult = await testHost.ExecuteAsync("--list-tests");
3737

38-
testHostResult.AssertExitCodeIsNot(ExitCodes.Success);
38+
testHostResult.AssertExitCodeIs(ExitCodes.Success);
3939
testHostResult.AssertOutputContains("System.IO.FileNotFoundException: Could not load file or assembly 'TestDiscoveryWarningsBaseClass");
4040
}
4141

test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/AssemblyEnumeratorTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public void GetTypesShouldReturnReflectionTypeLoadExceptionTypesOnException()
120120
Type[] types = AssemblyEnumerator.GetTypes(mockAssembly.Object, string.Empty, _warnings);
121121

122122
Verify(types is not null);
123-
Verify(reflectedTypes.Equals(types));
123+
Verify(reflectedTypes.SequenceEqual(types));
124124
}
125125

126126
public void GetTypesShouldLogWarningsWhenReflectionFailsWithLoaderExceptions()

0 commit comments

Comments
 (0)