diff --git a/TestFx.sln b/TestFx.sln index dbe03a18d8..38570909eb 100644 --- a/TestFx.sln +++ b/TestFx.sln @@ -181,7 +181,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest.Analyzers.CodeFixes" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest.Analyzers.Package", "src\Analyzers\MSTest.Analyzers.Package\MSTest.Analyzers.Package.csproj", "{DC068986-7549-4B75-8EFC-A9958FD5CF88}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest.Analyzers.Test", "test\UnitTests\MSTest.Analyzers.Test\MSTest.Analyzers.Test.csproj", "{1FF35C23-C128-4C95-B3F8-67B1B4C51E4D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest.Analyzers.UnitTests", "test\UnitTests\MSTest.Analyzers.UnitTests\MSTest.Analyzers.UnitTests.csproj", "{1FF35C23-C128-4C95-B3F8-67B1B4C51E4D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest.Sdk", "src\Package\MSTest.Sdk\MSTest.Sdk.csproj", "{10930CFD-EDF9-4486-B0A3-49230B5A6798}" EndProject diff --git a/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md b/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md index ea1e9ddc08..78baf7989c 100644 --- a/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md @@ -5,4 +5,6 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- -MSTEST0001 | Performance | Warning | UseParallelizeAttributeAnalyzer \ No newline at end of file +MSTEST0001 | Performance | Warning | UseParallelizeAttributeAnalyzer, [Documentation](https://github.com/microsoft/testfx/blob/main/docs/analyzers/MSTEST0001.md) +MSTEST0002 | Usage | Warning | TestClassShouldBePublicAnalyzer, [Documentation](https://github.com/microsoft/testfx/blob/main/docs/analyzers/MSTEST0002.md) +MSTEST0003 | Usage | Warning | TestMethodShouldBePublicAnalyzer, [Documentation](https://github.com/microsoft/testfx/blob/main/docs/analyzers/MSTEST0003.md) diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/Categories.cs b/src/Analyzers/MSTest.Analyzers/Helpers/Categories.cs index 91b6103349..0b7c512b90 100644 --- a/src/Analyzers/MSTest.Analyzers/Helpers/Categories.cs +++ b/src/Analyzers/MSTest.Analyzers/Helpers/Categories.cs @@ -6,4 +6,5 @@ namespace MSTest.Analyzers; internal static class Categories { public const string Performance = nameof(Performance); + public const string Usage = nameof(Usage); } diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs index 5a772bb084..d8a5b3d3e0 100644 --- a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs +++ b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs @@ -6,4 +6,6 @@ namespace MSTest.Analyzers; internal static class DiagnosticIds { public const string UseParallelizedAttributeRuleId = "MSTEST0001"; + public const string TestClassShouldBePublicRuleId = "MSTEST0002"; + public const string TestMethodShouldBePublicRuleId = "MSTEST0003"; } diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs b/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs index f517c295aa..70cc4be803 100644 --- a/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs +++ b/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs @@ -6,8 +6,10 @@ namespace MSTest.Analyzers; // IMPORTANT: Keep this file sorted alphabetically. internal static class WellKnownTypeNames { - public const string MicrosoftVisualStudioTestToolsUnitTestingParallelizeAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.ParallelizeAttribute"; public const string MicrosoftVisualStudioTestToolsUnitTestingDoNotParallelizeAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.DoNotParallelizeAttribute"; + public const string MicrosoftVisualStudioTestToolsUnitTestingParallelizeAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.ParallelizeAttribute"; + public const string MicrosoftVisualStudioTestToolsUnitTestingTestClassAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute"; + public const string MicrosoftVisualStudioTestToolsUnitTestingTestMethodAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute"; public const string SystemThreadingTasksTask1 = "System.Threading.Tasks.Task`1"; } diff --git a/src/Analyzers/MSTest.Analyzers/MSTest.Analyzers.csproj b/src/Analyzers/MSTest.Analyzers/MSTest.Analyzers.csproj index 14f84be56a..60561a9b8b 100644 --- a/src/Analyzers/MSTest.Analyzers/MSTest.Analyzers.csproj +++ b/src/Analyzers/MSTest.Analyzers/MSTest.Analyzers.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Analyzers/MSTest.Analyzers/Resources.Designer.cs b/src/Analyzers/MSTest.Analyzers/Resources.Designer.cs index b0ce07dddd..51d206e281 100644 --- a/src/Analyzers/MSTest.Analyzers/Resources.Designer.cs +++ b/src/Analyzers/MSTest.Analyzers/Resources.Designer.cs @@ -60,6 +60,60 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to MSTest only considers public classes marked with the '[TestClass]' attribute as test classes.. + /// + internal static string TestClassShouldBePublicDescription { + get { + return ResourceManager.GetString("TestClassShouldBePublicDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Test class '{0}' should be public. + /// + internal static string TestClassShouldBePublicMessageFormat { + get { + return ResourceManager.GetString("TestClassShouldBePublicMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Classes marked with '[TestClass]' should be public. + /// + internal static string TestClassShouldBePublicTitle { + get { + return ResourceManager.GetString("TestClassShouldBePublicTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods.. + /// + internal static string TestMethodShouldBePublicDescription { + get { + return ResourceManager.GetString("TestMethodShouldBePublicDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Test method '{0}' should be public. + /// + internal static string TestMethodShouldBePublicMessageFormat { + get { + return ResourceManager.GetString("TestMethodShouldBePublicMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Methods marked with '[TestMethod]' should be public. + /// + internal static string TestMethodShouldBePublicTitle { + get { + return ResourceManager.GetString("TestMethodShouldBePublicTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'.. /// diff --git a/src/Analyzers/MSTest.Analyzers/Resources.resx b/src/Analyzers/MSTest.Analyzers/Resources.resx index e1ab95352c..769a173907 100644 --- a/src/Analyzers/MSTest.Analyzers/Resources.resx +++ b/src/Analyzers/MSTest.Analyzers/Resources.resx @@ -117,6 +117,24 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + Test class '{0}' should be public + + + Classes marked with '[TestClass]' should be public + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + Test method '{0}' should be public + + + Methods marked with '[TestMethod]' should be public + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/.editorconfig b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/.editorconfig index cbd58c5433..7b0db641e0 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/.editorconfig +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/.editorconfig @@ -20,6 +20,7 @@ dotnet_diagnostic.SA1214.severity = none dotnet_diagnostic.SA1311.severity = none dotnet_diagnostic.SA1314.severity = none dotnet_diagnostic.SA1405.severity = none +dotnet_diagnostic.SA1413.severity = none dotnet_diagnostic.SA1502.severity = none dotnet_diagnostic.SA1512.severity = none dotnet_diagnostic.SA1516.severity = none diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/DiagnosticExtensions.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/DiagnosticExtensions.cs new file mode 100644 index 0000000000..2b9618f30c --- /dev/null +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/DiagnosticExtensions.cs @@ -0,0 +1,282 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Threading; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Analyzer.Utilities.Extensions +{ + internal static class DiagnosticExtensions + { + public static Diagnostic CreateDiagnostic( + this SyntaxNode node, + DiagnosticDescriptor rule, + params object[] args) + => node.CreateDiagnostic(rule, properties: null, args); + + public static Diagnostic CreateDiagnostic( + this SyntaxNode node, + DiagnosticDescriptor rule, + ImmutableDictionary? properties, + params object[] args) + => node.CreateDiagnostic(rule, additionalLocations: ImmutableArray.Empty, properties, args); + + public static Diagnostic CreateDiagnostic( + this SyntaxNode node, + DiagnosticDescriptor rule, + ImmutableArray additionalLocations, + ImmutableDictionary? properties, + params object[] args) + => node + .GetLocation() + .CreateDiagnostic( + rule: rule, + additionalLocations: additionalLocations, + properties: properties, + args: args); + + public static Diagnostic CreateDiagnostic( + this IOperation operation, + DiagnosticDescriptor rule, + params object[] args) + => operation.CreateDiagnostic(rule, properties: null, args); + + public static Diagnostic CreateDiagnostic( + this IOperation operation, + DiagnosticDescriptor rule, + ImmutableDictionary? properties, + params object[] args) + { + return operation.Syntax.CreateDiagnostic(rule, properties, args); + } + + public static Diagnostic CreateDiagnostic( + this IOperation operation, + DiagnosticDescriptor rule, + ImmutableArray additionalLocations, + ImmutableDictionary? properties, + params object[] args) + { + return operation.Syntax.CreateDiagnostic(rule, additionalLocations, properties, args); + } + + public static Diagnostic CreateDiagnostic( + this SyntaxToken token, + DiagnosticDescriptor rule, + params object[] args) + { + return token.GetLocation().CreateDiagnostic(rule, args); + } + + public static Diagnostic CreateDiagnostic( + this ISymbol symbol, + DiagnosticDescriptor rule, + params object[] args) + { + return symbol.Locations.CreateDiagnostic(rule, args); + } + + public static Diagnostic CreateDiagnostic( + this ISymbol symbol, + DiagnosticDescriptor rule, + ImmutableDictionary? properties, + params object[] args) + { + return symbol.Locations.CreateDiagnostic(rule, properties, args); + } + + public static Diagnostic CreateDiagnostic( + this Location location, + DiagnosticDescriptor rule, + params object[] args) + => location + .CreateDiagnostic( + rule: rule, + properties: ImmutableDictionary.Empty, + args: args); + + public static Diagnostic CreateDiagnostic( + this Location location, + DiagnosticDescriptor rule, + ImmutableDictionary? properties, + params object[] args) + => location.CreateDiagnostic(rule, ImmutableArray.Empty, properties, args); + + public static Diagnostic CreateDiagnostic( + this Location location, + DiagnosticDescriptor rule, + ImmutableArray additionalLocations, + ImmutableDictionary? properties, + params object[] args) + { + if (!location.IsInSource) + { + location = Location.None; + } + + return Diagnostic.Create( + descriptor: rule, + location: location, + additionalLocations: additionalLocations, + properties: properties, + messageArgs: args); + } + + public static Diagnostic CreateDiagnostic( + this IEnumerable locations, + DiagnosticDescriptor rule, + params object[] args) + { + return locations.CreateDiagnostic(rule, null, args); + } + + public static Diagnostic CreateDiagnostic( + this IEnumerable locations, + DiagnosticDescriptor rule, + ImmutableDictionary? properties, + params object[] args) + { + IEnumerable inSource = locations.Where(l => l.IsInSource); + if (!inSource.Any()) + { + return Diagnostic.Create(rule, null, args); + } + + return Diagnostic.Create(rule, + location: inSource.First(), + additionalLocations: inSource.Skip(1), + properties: properties, + messageArgs: args); + } + + /// + /// TODO: Revert this reflection based workaround once we move to Microsoft.CodeAnalysis version 3.0 + /// + private static readonly PropertyInfo? s_syntaxTreeDiagnosticOptionsProperty = + typeof(SyntaxTree).GetTypeInfo().GetDeclaredProperty("DiagnosticOptions"); + + private static readonly PropertyInfo? s_compilationOptionsSyntaxTreeOptionsProviderProperty = + typeof(CompilationOptions).GetTypeInfo().GetDeclaredProperty("SyntaxTreeOptionsProvider"); + + public static void ReportNoLocationDiagnostic( + this CompilationAnalysisContext context, + DiagnosticDescriptor rule, + params object[] args) + => context.Compilation.ReportNoLocationDiagnostic(rule, context.ReportDiagnostic, properties: null, args); + + public static void ReportNoLocationDiagnostic( + this SyntaxNodeAnalysisContext context, + DiagnosticDescriptor rule, + params object[] args) + => context.Compilation.ReportNoLocationDiagnostic(rule, context.ReportDiagnostic, properties: null, args); + + public static void ReportNoLocationDiagnostic( + this Compilation compilation, + DiagnosticDescriptor rule, + Action addDiagnostic, + ImmutableDictionary? properties, + params object[] args) + { + var effectiveSeverity = GetEffectiveSeverity(); + if (!effectiveSeverity.HasValue) + { + // Disabled rule + return; + } + + if (effectiveSeverity.Value != rule.DefaultSeverity) + { +#pragma warning disable RS0030 // The symbol 'DiagnosticDescriptor.DiagnosticDescriptor.#ctor' is banned in this project: Use 'DiagnosticDescriptorHelper.Create' instead + rule = new DiagnosticDescriptor(rule.Id, rule.Title, rule.MessageFormat, rule.Category, + effectiveSeverity.Value, rule.IsEnabledByDefault, rule.Description, rule.HelpLinkUri, rule.CustomTags.ToArray()); +#pragma warning restore RS0030 + } + + var diagnostic = Diagnostic.Create(rule, Location.None, properties, args); + addDiagnostic(diagnostic); + return; + + DiagnosticSeverity? GetEffectiveSeverity() + { + // Microsoft.CodeAnalysis version >= 3.7 exposes options through 'CompilationOptions.SyntaxTreeOptionsProvider.TryGetDiagnosticValue' + // Microsoft.CodeAnalysis version 3.3 - 3.7 exposes options through 'SyntaxTree.DiagnosticOptions'. This API is deprecated in 3.7. + + var syntaxTreeOptionsProvider = s_compilationOptionsSyntaxTreeOptionsProviderProperty?.GetValue(compilation.Options); + var syntaxTreeOptionsProviderTryGetDiagnosticValueMethod = syntaxTreeOptionsProvider?.GetType().GetRuntimeMethods().FirstOrDefault(m => m.Name == "TryGetDiagnosticValue"); + if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod == null && s_syntaxTreeDiagnosticOptionsProperty == null) + { + return rule.DefaultSeverity; + } + + ReportDiagnostic? overriddenSeverity = null; + foreach (var tree in compilation.SyntaxTrees) + { + ReportDiagnostic? configuredValue = null; + + // Prefer 'CompilationOptions.SyntaxTreeOptionsProvider', if available. + if (s_compilationOptionsSyntaxTreeOptionsProviderProperty != null) + { + if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod != null) + { + // public abstract bool TryGetDiagnosticValue(SyntaxTree tree, string diagnosticId, out ReportDiagnostic severity); + // public abstract bool TryGetDiagnosticValue(SyntaxTree tree, string diagnosticId, CancellationToken cancellationToken, out ReportDiagnostic severity); + object?[] parameters; + if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod.GetParameters().Length == 3) + { + parameters = new object?[] { tree, rule.Id, null }; + } + else + { + parameters = new object?[] { tree, rule.Id, CancellationToken.None, null }; + } + + if (syntaxTreeOptionsProviderTryGetDiagnosticValueMethod.Invoke(syntaxTreeOptionsProvider, parameters) is true && + parameters.Last() is ReportDiagnostic value) + { + configuredValue = value; + } + } + } + else + { + RoslynDebug.Assert(s_syntaxTreeDiagnosticOptionsProperty != null); + var options = (ImmutableDictionary)s_syntaxTreeDiagnosticOptionsProperty.GetValue(tree)!; + if (options.TryGetValue(rule.Id, out var value)) + { + configuredValue = value; + } + } + + if (configuredValue == null) + { + continue; + } + + if (configuredValue == ReportDiagnostic.Suppress) + { + // Any suppression entry always wins. + return null; + } + + if (overriddenSeverity == null) + { + overriddenSeverity = configuredValue; + } + else if (overriddenSeverity.Value.IsLessSevereThan(configuredValue.Value)) + { + // Choose the most severe value for conflicts. + overriddenSeverity = configuredValue; + } + } + + return overriddenSeverity.HasValue ? overriddenSeverity.Value.ToDiagnosticSeverity() : rule.DefaultSeverity; + } + } + } +} diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ReportDiagnosticExtensions.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ReportDiagnosticExtensions.cs new file mode 100644 index 0000000000..5984f2ead1 --- /dev/null +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ReportDiagnosticExtensions.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.CodeAnalysis +{ + internal static class ReportDiagnosticExtensions + { + public static string ToAnalyzerConfigString(this ReportDiagnostic reportDiagnostic) + { + return reportDiagnostic switch + { + ReportDiagnostic.Error => "error", + ReportDiagnostic.Warn => "warning", + ReportDiagnostic.Info => "suggestion", + ReportDiagnostic.Hidden => "silent", + ReportDiagnostic.Suppress => "none", + _ => throw new NotImplementedException(), + }; + } + + public static DiagnosticSeverity? ToDiagnosticSeverity(this ReportDiagnostic reportDiagnostic) + { + return reportDiagnostic switch + { + ReportDiagnostic.Error => DiagnosticSeverity.Error, + ReportDiagnostic.Warn => DiagnosticSeverity.Warning, + ReportDiagnostic.Info => DiagnosticSeverity.Info, + ReportDiagnostic.Hidden => DiagnosticSeverity.Hidden, + ReportDiagnostic.Suppress => null, + ReportDiagnostic.Default => null, + _ => throw new NotImplementedException(), + }; + } + + public static bool IsLessSevereThan(this ReportDiagnostic current, ReportDiagnostic other) + { + return current switch + { + ReportDiagnostic.Error => false, + + ReportDiagnostic.Warn => + other switch + { + ReportDiagnostic.Error => true, + _ => false + }, + + ReportDiagnostic.Info => + other switch + { + ReportDiagnostic.Error => true, + ReportDiagnostic.Warn => true, + _ => false + }, + + ReportDiagnostic.Hidden => + other switch + { + ReportDiagnostic.Error => true, + ReportDiagnostic.Warn => true, + ReportDiagnostic.Info => true, + _ => false + }, + + ReportDiagnostic.Suppress => true, + + _ => false + }; + } + } +} diff --git a/src/Analyzers/MSTest.Analyzers/TestClassShouldBePublicAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/TestClassShouldBePublicAnalyzer.cs new file mode 100644 index 0000000000..df37c30fd7 --- /dev/null +++ b/src/Analyzers/MSTest.Analyzers/TestClassShouldBePublicAnalyzer.cs @@ -0,0 +1,62 @@ +// 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; + +[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] +public sealed class TestClassShouldBePublicAnalyzer : DiagnosticAnalyzer +{ + private static readonly LocalizableResourceString Title = new(nameof(Resources.TestClassShouldBePublicTitle), Resources.ResourceManager, typeof(Resources)); + private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.TestClassShouldBePublicMessageFormat), Resources.ResourceManager, typeof(Resources)); + private static readonly LocalizableResourceString Description = new(nameof(Resources.TestClassShouldBePublicDescription), Resources.ResourceManager, typeof(Resources)); + internal static readonly DiagnosticDescriptor Rule = new( + DiagnosticIds.TestClassShouldBePublicRuleId, + Title, + MessageFormat, + Categories.Usage, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + Description, + $"https://github.com/microsoft/testfx/blob/main/docs/analyzers/{DiagnosticIds.TestClassShouldBePublicRuleId}.md"); + + public override ImmutableArray SupportedDiagnostics { get; } + = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(context => + { + if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestClassAttribute, out var testClassAttributeSymbol)) + { + context.RegisterSymbolAction(context => AnalyzeSymbol(context, testClassAttributeSymbol), SymbolKind.NamedType); + } + }); + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol testClassAttributeSymbol) + { + var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; + if (namedTypeSymbol.TypeKind != TypeKind.Class + || namedTypeSymbol.GetResultantVisibility() == SymbolVisibility.Public) + { + return; + } + + if (namedTypeSymbol.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, testClassAttributeSymbol))) + { + context.ReportDiagnostic(namedTypeSymbol.CreateDiagnostic(Rule, namedTypeSymbol.Name)); + } + } +} diff --git a/src/Analyzers/MSTest.Analyzers/TestMethodShouldBePublicAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/TestMethodShouldBePublicAnalyzer.cs new file mode 100644 index 0000000000..c6ce87b4b3 --- /dev/null +++ b/src/Analyzers/MSTest.Analyzers/TestMethodShouldBePublicAnalyzer.cs @@ -0,0 +1,62 @@ +// 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; + +[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] +public sealed class TestMethodShouldBePublicAnalyzer : DiagnosticAnalyzer +{ + private static readonly LocalizableResourceString Title = new(nameof(Resources.TestMethodShouldBePublicTitle), Resources.ResourceManager, typeof(Resources)); + private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.TestMethodShouldBePublicMessageFormat), Resources.ResourceManager, typeof(Resources)); + private static readonly LocalizableResourceString Description = new(nameof(Resources.TestMethodShouldBePublicDescription), Resources.ResourceManager, typeof(Resources)); + internal static readonly DiagnosticDescriptor Rule = new( + DiagnosticIds.TestMethodShouldBePublicRuleId, + Title, + MessageFormat, + Categories.Usage, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + Description, + $"https://github.com/microsoft/testfx/blob/main/docs/analyzers/{DiagnosticIds.TestMethodShouldBePublicRuleId}.md"); + + public override ImmutableArray SupportedDiagnostics { get; } + = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(context => + { + if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestMethodAttribute, out var testMethodAttributeSymbol)) + { + context.RegisterSymbolAction(context => AnalyzeSymbol(context, testMethodAttributeSymbol), SymbolKind.Method); + } + }); + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol testMethodAttributeSymbol) + { + var methodSymbol = (IMethodSymbol)context.Symbol; + if (methodSymbol.MethodKind != MethodKind.Ordinary + || (methodSymbol.GetResultantVisibility() == SymbolVisibility.Public && methodSymbol.DeclaredAccessibility == Accessibility.Public)) + { + return; + } + + if (methodSymbol.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, testMethodAttributeSymbol))) + { + context.ReportDiagnostic(methodSymbol.CreateDiagnostic(Rule, methodSymbol.Name)); + } + } +} diff --git a/src/Analyzers/MSTest.Analyzers/UseParallelizeAttributeAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/UseParallelizeAttributeAnalyzer.cs index 7c66290b22..16b7564282 100644 --- a/src/Analyzers/MSTest.Analyzers/UseParallelizeAttributeAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/UseParallelizeAttributeAnalyzer.cs @@ -3,6 +3,8 @@ using System.Collections.Immutable; +using Analyzer.Utilities.Extensions; + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -61,7 +63,7 @@ private static void AnalyzeCompilation(CompilationAnalysisContext context) if (!hasParallelizeAttribute && !hasDoNotParallelizeAttribute) { // We cannot provide any good location for assembly level missing attributes - context.ReportDiagnostic(Diagnostic.Create(Rule, Location.None)); + context.ReportNoLocationDiagnostic(Rule); } } } diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf index 1981f36988..62cd3cebd0 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. Ve výchozím nastavení spouští MSTest testy sekvenčně, což může vést k závažným omezením výkonu. Doporučuje se povolit atribut sestavení Parallelize nebo explicitně použít atribut DoNotParallelize na úrovni sestavení, pokud je známo, že sestavení není paralelizovatelné. diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf index 72ac5b80e5..5159975d81 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. Standardmäßig führt MSTest Tests sequentiell aus, was zu erheblichen Leistungseinschränkungen führen kann. Es wird empfohlen, das Assembly-Attribut "Parallelize" zu aktivieren oder, wenn bekannt ist, dass die Assembly nicht parallelisierbar ist, explizit das Assembly-Attribut "DoNotParallelize" zu verwenden. diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf index 0df91502b2..dc10027477 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. De manera predeterminada, MSTest ejecuta pruebas secuencialmente, lo que puede provocar limitaciones graves del servidor. Se recomienda habilitar el atributo de ensamblado ''Parallelize'' o, si se sabe que el ensamblado no se puede paralelizar, usar explícitamente el atributo de nivel de ensamblado ''DoNotParallelize''. diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf index 66900bbb7d..51146f796c 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. Par défaut, MSTest exécute des tests séquentiels, ce qui peut entraîner de graves limitations de performances. Il est recommandé d’activer l’attribut d’assemblée « Parallelize » ou, si l’assemblée est connu pour ne pas être parallélisable, d’utiliser explicitement l’attribut de niveau assemblée « DoNotParallelize ». diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf index fb112810b1..976c030769 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. Per impostazione predefinita, MSTest esegue i test in sequenza, il che può causare gravi limitazioni delle prestazioni. È consigliabile abilitare l'attributo di assembly 'Parallelize' o, se l'assembly non è parallelizzabile, usare in modo esplicito l'attributo a livello di assembly 'DoNotParallelize'. diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf index 8dfd317c94..c5726f3bc5 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. 既定では、MSTest はテストを順番に実行するため、重大なパフォーマンス制限が生じる可能性があります。アセンブリ属性 'Parallelize' を有効にするか、アセンブリが並列化できないことがわかっている場合は、アセンブリ レベル属性 'DoNotParallelize' を明示的に使用することをお勧めします。 diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf index 93a349f92b..aaf3283f04 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. 기본적으로 MSTest는 순차적으로 테스트를 실행하므로 심각한 성능 제한이 발생할 수 있습니다. 어셈블리 특성 'Parallelize'를 사용하거나, 병렬화할 수 없는 것으로 알려진 어셈블리의 경우 어셈블리 수준 특성 'DoNotParallelize'를 명시적으로 사용하는 것이 좋습니다. diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf index ec2ff833f2..9977b127c0 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. Domyślnie narzędzie MSTest uruchamia testy przy zachowaniu kolejności, co może prowadzić do poważnego ograniczenia wydajności. Zaleca się włączenie atrybutu zestawu „Parallelize” lub jeśli zestaw nie może być równoległy, aby użyć jawnie atrybutu poziomu zestawu „DoNotParallelize”. diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf index 15789612f4..2f853e1ea3 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. Por padrão, o MSTest executa testes sequencialmente, o que pode levar a graves limitações de desempenho. É recomendável habilitar o atributo de assembly "Parallelize" ou se o assembly for conhecido por não ser paralelizável, usar explicitamente o atributo de nível de assembly "DoNotParallelize". diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf index a8f4bff37a..fc27e31971 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. По умолчанию MSTest выполняет тесты последовательно, что может привести к серьезному ограничению производительности. Рекомендуется включить атрибут сборки "Parallelize" или явно использовать атрибут уровня сборки "DoNotParallelize", если известно, что сборка не поддерживает параллелизацию. diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf index 792a957d47..1719a60dad 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. Varsayılan olarak, MSTest hizmeti testleri sırayla çalıştırır ve bu durum ciddi performans sınırlamalarına yol açabilir. ‘Parallelize’ derleme özniteliğini etkinleştirmeniz önerilir veya derlemenin paralelleştirilebilir olduğu biliniyorsa, doğrudan ‘DoNotParallelize’ derleme düzeyi özniteliğini kullanmanız önerilir. diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf index 55085d15ec..ef77ffdd34 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. 默认情况下,MSTest 会按顺序运行测试,这可能会导致严重性能限制。建议启用程序集属性“Parallelize”,或者如果已知程序集不可并行,则显式使用程序集级别属性“DoNotParallelize”。 diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf index 5983c34262..67d921aa3d 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf @@ -2,6 +2,36 @@ + + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + MSTest only considers public classes marked with the '[TestClass]' attribute as test classes. + + + + Test class '{0}' should be public + Test class '{0}' should be public + + + + Classes marked with '[TestClass]' should be public + Classes marked with '[TestClass]' should be public + + + + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + MSTest only considers public methods marked with the '[TestMethod]' attribute as test methods. + + + + Test method '{0}' should be public + Test method '{0}' should be public + + + + Methods marked with '[TestMethod]' should be public + Methods marked with '[TestMethod]' should be public + + By default, MSTest runs tests sequentially which can lead to severe performance limitations. It is recommended to enable assembly attribute 'Parallelize' or if the assembly is known to not be parallelizable, to use explicitly the assembly level attribute 'DoNotParallelize'. 根據預設,MSTest 依順序執行測試,這可能會導致嚴重的效能限制。建議啟用組件屬性 'Parallelize',或者如果已知組件不可平行處理,則明確使用組件層級的屬性 'DoNotParallelize'。 diff --git a/test/UnitTests/MSTest.Analyzers.Test/MSTest.Analyzers.Test.csproj b/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj similarity index 50% rename from test/UnitTests/MSTest.Analyzers.Test/MSTest.Analyzers.Test.csproj rename to test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj index ef327e4f80..e1026fbbea 100644 --- a/test/UnitTests/MSTest.Analyzers.Test/MSTest.Analyzers.Test.csproj +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj @@ -2,9 +2,12 @@ net6.0 - + Exe + false + false true true + true @@ -12,13 +15,27 @@ + + - - - + + + + + + + + PreserveNewest + + + PreserveNewest + + + Always + diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.launcher.config.json b/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.launcher.config.json new file mode 100644 index 0000000000..96aed3c657 --- /dev/null +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.launcher.config.json @@ -0,0 +1,3 @@ +{ + "program": "MSTest.Analyzers.UnitTests.exe" +} diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.testingplatformconfig.json b/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.testingplatformconfig.json new file mode 100644 index 0000000000..519fa1ca12 --- /dev/null +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.testingplatformconfig.json @@ -0,0 +1,8 @@ +{ + "testingplatform": { + "telemetry": { + "isDevelopmentRepository": true + }, + "exitProcessOnUnhandledException": false + } +} diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/Program.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/Program.cs new file mode 100644 index 0000000000..dc46bf47e3 --- /dev/null +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/Program.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Testing.Framework; +using Microsoft.Testing.Platform.Builder; +using Microsoft.Testing.Platform.CommandLine; +using Microsoft.Testing.Platform.Extensions; +using Microsoft.Testing.Platform.Extensions.TestHost; +using Microsoft.Testing.Platform.Services; +using Microsoft.Testing.TestInfrastructure; + +// DebuggerUtility.AttachVSToCurrentProcess(); +ITestApplicationBuilder builder = await TestApplication.CreateBuilderAsync(args); +builder.TestHost.AddTestApplicationLifecycleCallbacks(sp => new GlobalTasks(sp.GetCommandLineOptions())); +builder.AddTestFramework(new MSTest.Analyzers.UnitTests.SourceGeneratedTestNodesBuilder()); + +// Custom suite tools +CompositeExtensionFactory slowestTestCompositeServiceFactory = new(_ => new SlowestTestsConsumer()); +builder.TestHost.AddDataConsumer(slowestTestCompositeServiceFactory); +builder.TestHost.AddTestSessionLifetimeHandle(slowestTestCompositeServiceFactory); +ITestApplication app = await builder.BuildAsync(); +return await app.RunAsync(); + +internal sealed class GlobalTasks : ITestApplicationLifecycleCallbacks +{ + private readonly ICommandLineOptions _commandLineOptions; + + public GlobalTasks(ICommandLineOptions commandLineOptions) + { + _commandLineOptions = commandLineOptions; + } + + public string Uid => nameof(GlobalTasks); + + public string Version => "1.0.0"; + + public string DisplayName => string.Empty; + + public string Description => string.Empty; + + public Task IsEnabledAsync() => Task.FromResult(true); + + public async Task AfterRunAsync(int returnValue, CancellationToken cancellationToken) + { + // Check if any tests are missing that were supposed to run. + TestsRunWatchDog.BaselineFile = Path.Combine(AppContext.BaseDirectory, "testsbaseline.txt"); + await TestsRunWatchDog.Verify(skip: _commandLineOptions.IsServerMode(), fixBaseLine: true); + } + + public Task BeforeRunAsync(CancellationToken cancellationToken) => Task.CompletedTask; +} diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/TestClassShouldBePublicAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/TestClassShouldBePublicAnalyzerTests.cs new file mode 100644 index 0000000000..59424ae2ba --- /dev/null +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/TestClassShouldBePublicAnalyzerTests.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Testing.Framework; +using Microsoft.Testing.TestInfrastructure; + +using VerifyCS = MSTest.Analyzers.Test.CSharpCodeFixVerifier< + MSTest.Analyzers.TestClassShouldBePublicAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace MSTest.Analyzers.Test; + +[TestGroup] +public sealed class TestClassShouldBePublicAnalyzerTests(ITestExecutionContext testExecutionContext) : TestBase(testExecutionContext) +{ + public async Task WhenClassIsPublicAndTestClass_NoDiagnostic() + { + var code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + public async Task WhenClassIsInternalAndTestClass_Diagnostic() + { + var code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + internal class [|MyTestClass|] + { + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + [Arguments("private")] + [Arguments("internal")] + public async Task WhenClassIsInnerAndNotPublicTestClass_Diagnostic(string accessibility) + { + var code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public class OuterClass + { + [TestClass] + {{accessibility}} class [|MyTestClass|] + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + public async Task WhenClassIsInternalAndNotTestClass_NoDiagnostic() + { + var code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + internal class MyTestClass + { + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + public async Task WhenClassIsPublicAndTestClassAsInnerOfInternalClass_Diagnostic() + { + var code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + internal class OuterClass + { + [TestClass] + public class [|MyTestClass|] + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } +} diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/TestMethodShouldBePublicAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/TestMethodShouldBePublicAnalyzerTests.cs new file mode 100644 index 0000000000..1b1b1ba66b --- /dev/null +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/TestMethodShouldBePublicAnalyzerTests.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Testing.Framework; +using Microsoft.Testing.TestInfrastructure; + +using VerifyCS = MSTest.Analyzers.Test.CSharpCodeFixVerifier< + MSTest.Analyzers.TestMethodShouldBePublicAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace MSTest.Analyzers.Test; + +[TestGroup] +public sealed class TestMethodShouldBePublicAnalyzerTests(ITestExecutionContext testExecutionContext) : TestBase(testExecutionContext) +{ + public async Task WhenMethodIsPublicAndTestMethod_NoDiagnostic() + { + var code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + public void MyTestMethod() + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + [Arguments("protected")] + [Arguments("internal")] + [Arguments("internal protected")] + [Arguments("private")] + public async Task WhenMethodIsNotPublicAndTestMethod_Diagnostic(string accessibility) + { + var code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + [TestMethod] + {{accessibility}} void [|MyTestMethod|]() + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + public async Task WhenMethodIsNotPublicAndNotTestMethod_NoDiagnostic() + { + var code = $$""" + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MyTestClass + { + private void PrivateMethod() + { + } + + protected void ProtectedMethod() + { + } + + internal protected void InternalProtectedMethod() + { + } + + internal void InternalMethod() + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } +} diff --git a/test/UnitTests/MSTest.Analyzers.Test/UseParallelizeAttributeAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/UseParallelizeAttributeAnalyzerTests.cs similarity index 84% rename from test/UnitTests/MSTest.Analyzers.Test/UseParallelizeAttributeAnalyzerTests.cs rename to test/UnitTests/MSTest.Analyzers.UnitTests/UseParallelizeAttributeAnalyzerTests.cs index 2b6099d58f..5f8cc9e3bb 100644 --- a/test/UnitTests/MSTest.Analyzers.Test/UseParallelizeAttributeAnalyzerTests.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/UseParallelizeAttributeAnalyzerTests.cs @@ -1,9 +1,8 @@ // 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.Threading.Tasks; - -using TestFramework.ForTestingMSTest; +using Microsoft.Testing.Framework; +using Microsoft.Testing.TestInfrastructure; using VerifyCS = MSTest.Analyzers.Test.CSharpCodeFixVerifier< MSTest.Analyzers.UseParallelizeAttributeAnalyzer, @@ -11,7 +10,8 @@ namespace MSTest.Analyzers.Test; -public class UseParallelizeAttributeAnalyzerTests : TestContainer +[TestGroup] +public class UseParallelizeAttributeAnalyzerTests(ITestExecutionContext testExecutionContext) : TestBase(testExecutionContext) { public async Task WhenNoAttributeSpecified_Diagnostic() { diff --git a/test/UnitTests/MSTest.Analyzers.Test/Verifiers/CSharpCodeFixVerifier`2+Test.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2+Test.cs similarity index 100% rename from test/UnitTests/MSTest.Analyzers.Test/Verifiers/CSharpCodeFixVerifier`2+Test.cs rename to test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2+Test.cs diff --git a/test/UnitTests/MSTest.Analyzers.Test/Verifiers/CSharpCodeFixVerifier`2.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs similarity index 98% rename from test/UnitTests/MSTest.Analyzers.Test/Verifiers/CSharpCodeFixVerifier`2.cs rename to test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs index 17cd8228d7..27057edaf3 100644 --- a/test/UnitTests/MSTest.Analyzers.Test/Verifiers/CSharpCodeFixVerifier`2.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs @@ -1,9 +1,6 @@ // 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.Threading; -using System.Threading.Tasks; - using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Testing; diff --git a/test/UnitTests/MSTest.Analyzers.Test/Verifiers/CSharpVerifierHelper.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpVerifierHelper.cs similarity index 99% rename from test/UnitTests/MSTest.Analyzers.Test/Verifiers/CSharpVerifierHelper.cs rename to test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpVerifierHelper.cs index 222cab6461..a105504fc7 100644 --- a/test/UnitTests/MSTest.Analyzers.Test/Verifiers/CSharpVerifierHelper.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpVerifierHelper.cs @@ -1,7 +1,6 @@ // 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; using System.Collections.Immutable; using Microsoft.CodeAnalysis; diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/testsbaseline.txt b/test/UnitTests/MSTest.Analyzers.UnitTests/testsbaseline.txt new file mode 100644 index 0000000000..8ee0115810 --- /dev/null +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/testsbaseline.txt @@ -0,0 +1,15 @@ +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestClassShouldBePublicAnalyzerTests.WhenClassIsInnerAndNotPublicTestClass_Diagnostic(string) (accessibility: "private") +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestClassShouldBePublicAnalyzerTests.WhenClassIsInternalAndNotTestClass_NoDiagnostic() +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestClassShouldBePublicAnalyzerTests.WhenClassIsInternalAndTestClass_Diagnostic() +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestClassShouldBePublicAnalyzerTests.WhenClassIsPublicAndTestClass_NoDiagnostic() +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.UseParallelizeAttributeAnalyzerTests.WhenNoAttributeSpecified_Diagnostic() +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.UseParallelizeAttributeAnalyzerTests.WhenParallelizeAttributeSet_NoDiagnostic() +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestClassShouldBePublicAnalyzerTests.WhenClassIsPublicAndTestClassAsInnerOfInternalClass_Diagnostic() +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestClassShouldBePublicAnalyzerTests.WhenClassIsInnerAndNotPublicTestClass_Diagnostic(string) (accessibility: "internal") +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.UseParallelizeAttributeAnalyzerTests.WhenDoNotParallelizeAttributeSet_NoDiagnostic() +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestMethodShouldBePublicAnalyzerTests.WhenMethodIsNotPublicAndTestMethod_Diagnostic(string) (accessibility: "private") +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestMethodShouldBePublicAnalyzerTests.WhenMethodIsNotPublicAndTestMethod_Diagnostic(string) (accessibility: "internal protected") +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestMethodShouldBePublicAnalyzerTests.WhenMethodIsNotPublicAndTestMethod_Diagnostic(string) (accessibility: "protected") +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestMethodShouldBePublicAnalyzerTests.WhenMethodIsNotPublicAndNotTestMethod_NoDiagnostic() +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestMethodShouldBePublicAnalyzerTests.WhenMethodIsPublicAndTestMethod_NoDiagnostic() +MSTest.Analyzers.UnitTests.MSTest.Analyzers.Test.TestMethodShouldBePublicAnalyzerTests.WhenMethodIsNotPublicAndTestMethod_Diagnostic(string) (accessibility: "internal") \ No newline at end of file diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj index 7cc7faeea1..0d286c9768 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj @@ -23,9 +23,9 @@ - + @@ -34,7 +34,7 @@ - +