From 8230626815ff47d4f8539e2f19adf6549d07d232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Sat, 11 Jul 2026 14:09:17 +0200 Subject: [PATCH] Fix MSTEST0063 to detect invalid constructors on derived TestClass attributes TestClassConstructorShouldBeValidAnalyzer used an exact-match check (SymbolEqualityComparer.Default.Equals) for TestClassAttribute, so derived attributes like [STATestClass] or custom [MyTestClass] were silently skipped. MSTest discovers such classes at runtime via Inherits() logic, so a private/internal constructor causes a runtime failure but no analyzer warning. Switch the guard to IsTestClass() (which uses Inherits()) to match the runtime discovery logic, and add four tests covering derived and custom TestClass attributes. Fixes #9836 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d1590213-39c9-4b3d-9fa4-1d10ac4961cc --- ...stClassConstructorShouldBeValidAnalyzer.cs | 2 +- ...ssConstructorShouldBeValidAnalyzerTests.cs | 94 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/Analyzers/MSTest.Analyzers/TestClassConstructorShouldBeValidAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/TestClassConstructorShouldBeValidAnalyzer.cs index e2e1ba256c..70bf91d22f 100644 --- a/src/Analyzers/MSTest.Analyzers/TestClassConstructorShouldBeValidAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/TestClassConstructorShouldBeValidAnalyzer.cs @@ -60,7 +60,7 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo if (namedTypeSymbol.TypeKind != TypeKind.Class || namedTypeSymbol.IsAbstract || namedTypeSymbol.IsStatic - || !namedTypeSymbol.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, testClassAttributeSymbol))) + || !namedTypeSymbol.IsTestClass(testClassAttributeSymbol)) { return; } diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/TestClassConstructorShouldBeValidAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/TestClassConstructorShouldBeValidAnalyzerTests.cs index 37af3eda2b..f3d5564915 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/TestClassConstructorShouldBeValidAnalyzerTests.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/TestClassConstructorShouldBeValidAnalyzerTests.cs @@ -343,4 +343,98 @@ public static class MyTestClass await VerifyCS.VerifyCodeFixAsync(code, code); } + + [TestMethod] + public async Task WhenDerivedTestClassAttributeHasPrivateConstructor_Diagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [STATestClass] + public class {|#0:MyTestClass|} + { + private MyTestClass() + { + } + } + """; + + await VerifyCS.VerifyCodeFixAsync( + code, + VerifyCS.Diagnostic(TestClassConstructorShouldBeValidAnalyzer.TestClassConstructorShouldBeValidRule) + .WithLocation(0) + .WithArguments("MyTestClass"), + code); + } + + [TestMethod] + public async Task WhenDerivedTestClassAttributeHasPublicParameterlessConstructor_NoDiagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [STATestClass] + public class MyTestClass + { + public MyTestClass() + { + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + + [TestMethod] + public async Task WhenCustomDerivedTestClassAttributeHasInternalConstructor_Diagnostic() + { + string code = """ + using System; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [AttributeUsage(AttributeTargets.Class)] + public class MyTestClassAttribute : TestClassAttribute + { + } + + [MyTestClass] + public class {|#0:MyTestClass|} + { + internal MyTestClass() + { + } + } + """; + + await VerifyCS.VerifyCodeFixAsync( + code, + VerifyCS.Diagnostic(TestClassConstructorShouldBeValidAnalyzer.TestClassConstructorShouldBeValidRule) + .WithLocation(0) + .WithArguments("MyTestClass"), + code); + } + + [TestMethod] + public async Task WhenCustomDerivedTestClassAttributeHasPublicConstructor_NoDiagnostic() + { + string code = """ + using System; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [AttributeUsage(AttributeTargets.Class)] + public class MyTestClassAttribute : TestClassAttribute + { + } + + [MyTestClass] + public class MyTestClass + { + public MyTestClass() + { + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } }