Skip to content
Merged
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 @@ -35,6 +35,11 @@

<ItemGroup>
<CompilerVisibleProperty Include="IsMSTestTestAdapterReferenced" />
<!-- Surfaced so analyzers can warn about features that are unsupported under ahead-of-time
compilation (e.g. MSTEST0072 for [AssemblyFixtureProvider]): PublishAot (Native AOT) and
RunAOTCompilation (Blazor WebAssembly AOT). -->
<CompilerVisibleProperty Include="PublishAot" />
<CompilerVisibleProperty Include="RunAOTCompilation" />
<!-- Selects which MSTest source-generation strategy emits when a source generator is referenced:
'ReflectionFree' (default) materializes attributes and emits delegate-based invokers so the
adapter runs without runtime reflection (best for trimming/Native AOT); 'Rooting' only
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Security;
Expand All @@ -16,6 +16,57 @@ internal sealed partial class TypeCache
{
private static void DiscoverFixturesFromProviders(Assembly currentAssembly, TestAssemblyInfo assemblyInfo, TypeCache @this)
{
#if NET && !WINDOWS_UWP
Comment thread
Evangelink marked this conversation as resolved.
// [AssemblyFixtureProvider] discovery walks the runtime assembly reference graph
// (Assembly.GetReferencedAssemblies + assembly loading by name), which is not supported when
// the runtime cannot generate dynamic code (Native AOT, Mono iOS AOT, Blazor WASM AOT).
// Skipping the feature there keeps behavior predictable and lets the trimmer statically remove
// the reflection path (so no IL2026/IL3050 is produced).
if (!RuntimeFeature.IsDynamicCodeSupported)
Comment thread
Evangelink marked this conversation as resolved.
Comment thread
Evangelink marked this conversation as resolved.
{
// The compile-time MSTEST0072 analyzer covers the referenced-provider case at build time
// (it can read referenced assemblies' metadata). At run time under AOT we cannot walk the
// reference graph (Assembly.GetReferencedAssemblies + load-by-name is the very dynamic-code
// path this guard avoids), so a referenced provider that has not been loaded yet is not
// detectable here. What we can reliably and AOT-safely surface is a marker on an
// already-loaded assembly — in particular the test assembly itself when it self-applies the
// attribute (a documented usage). Emit a best-effort warning for every loaded assembly that
// carries the marker so those cases are not silent.
if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsWarningEnabled)
{
foreach (Assembly loaded in AppDomain.CurrentDomain.GetAssemblies())
{
if (loaded.IsDynamic)
{
continue;
}

bool hasMarker;
try
{
// Metadata-only probe. Isolate per-assembly failures (unresolvable custom-attribute
// metadata on an unrelated assembly must not abort discovery), matching the normal
// discovery path's handling.
hasMarker = HasAssemblyFixtureProviderMarker(loaded);
}
catch (Exception)
{
continue;
}

if (hasMarker)
{
PlatformServiceProvider.Instance.AdapterTraceLogger.Warning(
"TypeCache: [AssemblyFixtureProvider] is not supported when the runtime cannot generate dynamic code (Native AOT, Mono iOS AOT, Blazor WebAssembly AOT). The AssemblyInitialize/AssemblyCleanup methods it exposes from assembly {0} will not run.",
SafeGetAssemblyName(loaded));
}
}
}

return;
}
#endif

// Snapshot which slots were filled by the in-assembly pass. Local declarations are
// authoritative — never let a provider overwrite or even consider those slots, so the
// provider pass stays silent when the test assembly already declared a fixture method.
Expand Down
6 changes: 6 additions & 0 deletions src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
MSTEST0072 | Usage | Warning | AssemblyFixtureProviderNotSupportedWithNativeAotAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0072)
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Immutable;

using Analyzer.Utilities.Extensions;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

using MSTest.Analyzers.Helpers;

namespace MSTest.Analyzers;

/// <summary>
/// MSTEST0072: <inheritdoc cref="Resources.AssemblyFixtureProviderNotSupportedWithNativeAotTitle"/>.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class AssemblyFixtureProviderNotSupportedWithNativeAotAnalyzer : DiagnosticAnalyzer
Comment thread
Evangelink marked this conversation as resolved.
{
private static readonly LocalizableResourceString Title = new(nameof(Resources.AssemblyFixtureProviderNotSupportedWithNativeAotTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.AssemblyFixtureProviderNotSupportedWithNativeAotMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString Description = new(nameof(Resources.AssemblyFixtureProviderNotSupportedWithNativeAotDescription), Resources.ResourceManager, typeof(Resources));

/// <inheritdoc cref="Resources.AssemblyFixtureProviderNotSupportedWithNativeAotTitle" />
public static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
DiagnosticIds.AssemblyFixtureProviderNotSupportedWithNativeAotRuleId,
Title,
MessageFormat,
Description,
Category.Usage,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create(Rule);

/// <inheritdoc />
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterCompilationAction(AnalyzeCompilation);
}

private static void AnalyzeCompilation(CompilationAnalysisContext context)
{
// [AssemblyFixtureProvider] discovery walks the runtime assembly reference graph, which is only
// supported when the runtime can generate dynamic code. Under ahead-of-time compilation the
// feature is skipped at run time, so warn the user that the attribute has no effect there.
// Report when the project opts into an AOT flavor detectable at build time: Native AOT
// (PublishAot) or Blazor WebAssembly AOT (RunAOTCompilation).
if (!(IsBuildPropertyTrue(context, "build_property.PublishAot")
|| IsBuildPropertyTrue(context, "build_property.RunAOTCompilation")))
Comment thread
Evangelink marked this conversation as resolved.
{
return;
}

INamedTypeSymbol? assemblyFixtureProviderAttributeSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingAssemblyFixtureProviderAttribute);
if (assemblyFixtureProviderAttributeSymbol is null)
{
return;
}

// Attributes applied in the compilation being built have a source location we can point at.
foreach (AttributeData attribute in context.Compilation.Assembly.GetAttributes()
.Where(attribute => SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, assemblyFixtureProviderAttributeSymbol)))
{
if (attribute.ApplicationSyntaxReference is null)
{
context.ReportNoLocationDiagnostic(Rule);
}
else
{
context.ReportDiagnostic(attribute.ApplicationSyntaxReference.CreateDiagnostic(Rule, context.CancellationToken));
}
}

// The documented/default usage places [AssemblyFixtureProvider] on a referenced fixture library.
// Those attributes are declared outside this compilation (no source location), but the referencing
// Native AOT test project is exactly where the runtime guard silently skips discovery, so report a
// no-location diagnostic when any referenced assembly carries the marker.
if (context.Compilation.SourceModule.ReferencedAssemblySymbols
.Any(referencedAssembly => referencedAssembly.GetAttributes()
.Any(attribute => SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, assemblyFixtureProviderAttributeSymbol))))
{
context.ReportNoLocationDiagnostic(Rule);
Comment thread
Evangelink marked this conversation as resolved.
}
}

private static bool IsBuildPropertyTrue(CompilationAnalysisContext context, string propertyName)
=> context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue(propertyName, out string? value)
&& bool.TryParse(value, out bool parsed)
&& parsed;
}
3 changes: 2 additions & 1 deletion src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace MSTest.Analyzers.Helpers;
Expand Down Expand Up @@ -76,4 +76,5 @@ internal static class DiagnosticIds
// public const string InheritedTestClassAttributeWithSourceGeneratorRuleId = "MSTEST0069"; - // Reserved. Owned by MSTest.SourceGeneration analyzer; don't reuse this ID.
public const string MemberConditionShouldBeValidRuleId = "MSTEST0070";
public const string RedundantTestMethodDisplayNameRuleId = "MSTEST0071";
public const string AssemblyFixtureProviderNotSupportedWithNativeAotRuleId = "MSTEST0072";
Comment thread
Evangelink marked this conversation as resolved.
}
3 changes: 2 additions & 1 deletion src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace MSTest.Analyzers.Helpers;
Expand All @@ -7,6 +7,7 @@ namespace MSTest.Analyzers.Helpers;
internal static class WellKnownTypeNames
{
public const string MicrosoftVisualStudioTestToolsUnitTestingAssemblyCleanupAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.AssemblyCleanupAttribute";
public const string MicrosoftVisualStudioTestToolsUnitTestingAssemblyFixtureProviderAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.AssemblyFixtureProviderAttribute";
Comment thread
Evangelink marked this conversation as resolved.
public const string MicrosoftVisualStudioTestToolsUnitTestingAssemblyInitializeAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.AssemblyInitializeAttribute";
public const string MicrosoftVisualStudioTestToolsUnitTestingAssert = "Microsoft.VisualStudio.TestTools.UnitTesting.Assert";
public const string MicrosoftVisualStudioTestToolsUnitTestingAssertFailedException = "Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException";
Expand Down
12 changes: 12 additions & 0 deletions src/Analyzers/MSTest.Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -996,4 +996,16 @@ The type declaring these methods should also respect the following rules:
<value>Setting a 'DisplayName' on a test method to a value that is identical to the method name is redundant because the method name is already used as the display name by default. The redundant 'DisplayName' can be removed without changing how the test is reported.</value>
<comment>{Locked="DisplayName"}</comment>
</data>
<data name="AssemblyFixtureProviderNotSupportedWithNativeAotTitle" xml:space="preserve">
<value>'[AssemblyFixtureProvider]' is not supported with ahead-of-time compilation</value>
<comment>{Locked="[AssemblyFixtureProvider]"}</comment>
</data>
<data name="AssemblyFixtureProviderNotSupportedWithNativeAotMessageFormat" xml:space="preserve">
<value>'[AssemblyFixtureProvider]' is not supported with ahead-of-time compilation (such as Native AOT or Blazor WebAssembly AOT) and will be ignored at run time</value>
<comment>{Locked="[AssemblyFixtureProvider]"}{Locked="Native AOT"}{Locked="Blazor WebAssembly AOT"}</comment>
</data>
<data name="AssemblyFixtureProviderNotSupportedWithNativeAotDescription" xml:space="preserve">
<value>'[AssemblyFixtureProvider]' discovery relies on walking the runtime assembly reference graph, which is not supported when the runtime cannot generate dynamic code (for example under Native AOT or Blazor WebAssembly AOT). The attribute is ignored in such builds, so the provided assembly initialize and cleanup methods will not run. Declare the '[AssemblyInitialize]' and '[AssemblyCleanup]' methods directly in the test assembly instead.</value>
<comment>{Locked="[AssemblyFixtureProvider]"}{Locked="[AssemblyInitialize]"}{Locked="[AssemblyCleanup]"}{Locked="Native AOT"}{Locked="Blazor WebAssembly AOT"}</comment>
</data>
</root>
15 changes: 15 additions & 0 deletions src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ Typ deklarující tyto metody by měl také respektovat následující pravidla:
<target state="translated">Metody AssemblyCleanup musí mít platné rozložení</target>
<note>{Locked="AssemblyCleanup"}</note>
</trans-unit>
<trans-unit id="AssemblyFixtureProviderNotSupportedWithNativeAotDescription">
<source>'[AssemblyFixtureProvider]' discovery relies on walking the runtime assembly reference graph, which is not supported when the runtime cannot generate dynamic code (for example under Native AOT or Blazor WebAssembly AOT). The attribute is ignored in such builds, so the provided assembly initialize and cleanup methods will not run. Declare the '[AssemblyInitialize]' and '[AssemblyCleanup]' methods directly in the test assembly instead.</source>
<target state="new">'[AssemblyFixtureProvider]' discovery relies on walking the runtime assembly reference graph, which is not supported when the runtime cannot generate dynamic code (for example under Native AOT or Blazor WebAssembly AOT). The attribute is ignored in such builds, so the provided assembly initialize and cleanup methods will not run. Declare the '[AssemblyInitialize]' and '[AssemblyCleanup]' methods directly in the test assembly instead.</target>
<note>{Locked="[AssemblyFixtureProvider]"}{Locked="[AssemblyInitialize]"}{Locked="[AssemblyCleanup]"}{Locked="Native AOT"}{Locked="Blazor WebAssembly AOT"}</note>
</trans-unit>
<trans-unit id="AssemblyFixtureProviderNotSupportedWithNativeAotMessageFormat">
<source>'[AssemblyFixtureProvider]' is not supported with ahead-of-time compilation (such as Native AOT or Blazor WebAssembly AOT) and will be ignored at run time</source>
<target state="new">'[AssemblyFixtureProvider]' is not supported with ahead-of-time compilation (such as Native AOT or Blazor WebAssembly AOT) and will be ignored at run time</target>
<note>{Locked="[AssemblyFixtureProvider]"}{Locked="Native AOT"}{Locked="Blazor WebAssembly AOT"}</note>
</trans-unit>
<trans-unit id="AssemblyFixtureProviderNotSupportedWithNativeAotTitle">
<source>'[AssemblyFixtureProvider]' is not supported with ahead-of-time compilation</source>
<target state="new">'[AssemblyFixtureProvider]' is not supported with ahead-of-time compilation</target>
<note>{Locked="[AssemblyFixtureProvider]"}</note>
</trans-unit>
<trans-unit id="AssemblyInitializeShouldBeValidDescription">
<source>Methods marked with '[AssemblyInitialize]' should follow the following layout to be valid:
-it can't be declared on a generic class
Expand Down
15 changes: 15 additions & 0 deletions src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ Der Typ, der diese Methoden deklariert, sollte auch die folgenden Regeln beachte
<target state="translated">AssemblyCleanup-Methoden müssen über ein gültiges Layout verfügen.</target>
<note>{Locked="AssemblyCleanup"}</note>
</trans-unit>
<trans-unit id="AssemblyFixtureProviderNotSupportedWithNativeAotDescription">
<source>'[AssemblyFixtureProvider]' discovery relies on walking the runtime assembly reference graph, which is not supported when the runtime cannot generate dynamic code (for example under Native AOT or Blazor WebAssembly AOT). The attribute is ignored in such builds, so the provided assembly initialize and cleanup methods will not run. Declare the '[AssemblyInitialize]' and '[AssemblyCleanup]' methods directly in the test assembly instead.</source>
<target state="new">'[AssemblyFixtureProvider]' discovery relies on walking the runtime assembly reference graph, which is not supported when the runtime cannot generate dynamic code (for example under Native AOT or Blazor WebAssembly AOT). The attribute is ignored in such builds, so the provided assembly initialize and cleanup methods will not run. Declare the '[AssemblyInitialize]' and '[AssemblyCleanup]' methods directly in the test assembly instead.</target>
<note>{Locked="[AssemblyFixtureProvider]"}{Locked="[AssemblyInitialize]"}{Locked="[AssemblyCleanup]"}{Locked="Native AOT"}{Locked="Blazor WebAssembly AOT"}</note>
</trans-unit>
<trans-unit id="AssemblyFixtureProviderNotSupportedWithNativeAotMessageFormat">
<source>'[AssemblyFixtureProvider]' is not supported with ahead-of-time compilation (such as Native AOT or Blazor WebAssembly AOT) and will be ignored at run time</source>
<target state="new">'[AssemblyFixtureProvider]' is not supported with ahead-of-time compilation (such as Native AOT or Blazor WebAssembly AOT) and will be ignored at run time</target>
<note>{Locked="[AssemblyFixtureProvider]"}{Locked="Native AOT"}{Locked="Blazor WebAssembly AOT"}</note>
</trans-unit>
<trans-unit id="AssemblyFixtureProviderNotSupportedWithNativeAotTitle">
<source>'[AssemblyFixtureProvider]' is not supported with ahead-of-time compilation</source>
<target state="new">'[AssemblyFixtureProvider]' is not supported with ahead-of-time compilation</target>
<note>{Locked="[AssemblyFixtureProvider]"}</note>
</trans-unit>
<trans-unit id="AssemblyInitializeShouldBeValidDescription">
<source>Methods marked with '[AssemblyInitialize]' should follow the following layout to be valid:
-it can't be declared on a generic class
Expand Down
Loading
Loading