@@ -28,7 +28,7 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting
2828 [System.AttributeUsage(System.AttributeTargets.Class)]
2929 public class TestClassAttribute : System.Attribute { }
3030
31- [System.AttributeUsage(System.AttributeTargets.Method)]
31+ [System.AttributeUsage(System.AttributeTargets.Method, Inherited = false )]
3232 public class TestMethodAttribute : System.Attribute
3333 {
3434 public TestMethodAttribute() { }
@@ -59,7 +59,7 @@ public class ParallelizeAttribute : System.Attribute
5959 public string? Scope { get; set; }
6060 }
6161
62- [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = true)]
62+ [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = true, Inherited = false )]
6363 public class DataRowAttribute : System.Attribute
6464 {
6565 public DataRowAttribute(object? data1) { }
@@ -571,6 +571,9 @@ public virtual void Run() { }
571571 [TestClass]
572572 public class DerivedTests : BaseTests
573573 {
574+ // [TestMethod] is re-applied here because the real attribute is declared
575+ // with AttributeUsage(Inherited = false) and would not be inherited.
576+ [TestMethod]
574577 public override void Run() { }
575578 }
576579 }
@@ -583,9 +586,6 @@ public override void Run() { }
583586 runEntries . Should ( ) . Be ( 1 , "the derived override must replace the base entry (not duplicate it)" ) ;
584587 registry . Should ( ) . Contain ( "((global::Sample.DerivedTests)instance!).Run();" ) ;
585588 registry . Should ( ) . NotContain ( "((global::Sample.BaseTests)instance!).Run();" ) ;
586-
587- // The override does NOT re-apply [TestMethod] but the attribute must still be visible
588- // via the override chain — matching the runtime semantics of GetCustomAttributes(inherit: true).
589589 registry . Should ( ) . Contain ( "global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute" ) ;
590590 }
591591
@@ -1032,6 +1032,162 @@ public void Test(string? value) { }
10321032 registry . Should ( ) . Contain ( "new object?[] { (object)null! }" ) ;
10331033 }
10341034
1035+ [ TestMethod ]
1036+ public void Generator_SkipsProtectedAndPrivateProtectedMembers ( )
1037+ {
1038+ // Generated invokers live in a separate static helper class (not a derived type),
1039+ // so 'protected' and 'private protected' members are not callable from the emitted
1040+ // code. They MUST be excluded from the registry to keep the generated source compiling.
1041+ const string userCode = """
1042+ using Microsoft.VisualStudio.TestTools.UnitTesting;
1043+
1044+ namespace Sample
1045+ {
1046+ public class BaseTests
1047+ {
1048+ [TestMethod]
1049+ protected void ProtectedTest() { }
1050+
1051+ [TestMethod]
1052+ private protected void PrivateProtectedTest() { }
1053+ }
1054+
1055+ [TestClass]
1056+ public class DerivedTests : BaseTests
1057+ {
1058+ [TestMethod]
1059+ public void PublicTest() { }
1060+ }
1061+ }
1062+ """ ;
1063+
1064+ Compilation outputCompilation = RunGeneratorAndGetCompilation ( MinimalMSTestStub , userCode ) ;
1065+ IEnumerable < Diagnostic > errors = outputCompilation
1066+ . GetDiagnostics ( )
1067+ . Where ( d => d . Severity == DiagnosticSeverity . Error ) ;
1068+ errors . Should ( ) . BeEmpty ( "the generated registry MUST NOT reference protected / private protected members" ) ;
1069+
1070+ string registry = outputCompilation
1071+ . SyntaxTrees
1072+ . Single ( t => t . FilePath . EndsWith ( "MSTestReflectionMetadata.Registry.g.cs" , System . StringComparison . Ordinal ) )
1073+ . ToString ( ) ;
1074+
1075+ registry . Should ( ) . Contain ( "Name = \" PublicTest\" " ) ;
1076+ registry . Should ( ) . NotContain ( "Name = \" ProtectedTest\" " ) ;
1077+ registry . Should ( ) . NotContain ( "Name = \" PrivateProtectedTest\" " ) ;
1078+ }
1079+
1080+ [ TestMethod ]
1081+ public void Generator_KeepsAllowMultipleAttributes_AcrossOverrideChain ( )
1082+ {
1083+ // [TestCategory] is AllowMultiple=true. Collecting attributes across the override chain
1084+ // MUST keep every instance instead of collapsing them by type, otherwise the inherited
1085+ // categories disappear from the registry.
1086+ const string userCode = """
1087+ using Microsoft.VisualStudio.TestTools.UnitTesting;
1088+
1089+ namespace Sample
1090+ {
1091+ public class BaseTests
1092+ {
1093+ [TestMethod]
1094+ [TestCategory("BaseCat")]
1095+ public virtual void Run() { }
1096+ }
1097+
1098+ [TestClass]
1099+ public class DerivedTests : BaseTests
1100+ {
1101+ [TestCategory("DerivedCat")]
1102+ public override void Run() { }
1103+ }
1104+ }
1105+ """ ;
1106+
1107+ string registry = GetRegistry ( RunGenerator ( MinimalMSTestStub , userCode ) ) ;
1108+
1109+ registry . Should ( ) . Contain ( "\" BaseCat\" " ) ;
1110+ registry . Should ( ) . Contain ( "\" DerivedCat\" " ) ;
1111+ }
1112+
1113+ [ TestMethod ]
1114+ public void Generator_DistinguishesGenericArity_BetweenSameNamedMethods ( )
1115+ {
1116+ // Methods that differ only in generic arity (e.g. M() vs M<T>()) MUST be treated as
1117+ // distinct in the per-class dedup key, otherwise the generator drops one of them.
1118+ const string userCode = """
1119+ using Microsoft.VisualStudio.TestTools.UnitTesting;
1120+
1121+ namespace Sample
1122+ {
1123+ [TestClass]
1124+ public class GenericArityTests
1125+ {
1126+ [TestMethod]
1127+ public void Run() { }
1128+
1129+ [TestMethod]
1130+ public void Run<T>() { }
1131+
1132+ [TestMethod]
1133+ public void Run<T, U>() { }
1134+ }
1135+ }
1136+ """ ;
1137+
1138+ Compilation outputCompilation = RunGeneratorAndGetCompilation ( MinimalMSTestStub , userCode ) ;
1139+ IEnumerable < Diagnostic > errors = outputCompilation
1140+ . GetDiagnostics ( )
1141+ . Where ( d => d . Severity == DiagnosticSeverity . Error ) ;
1142+ errors . Should ( ) . BeEmpty ( ) ;
1143+
1144+ string registry = outputCompilation
1145+ . SyntaxTrees
1146+ . Single ( t => t . FilePath . EndsWith ( "MSTestReflectionMetadata.Registry.g.cs" , System . StringComparison . Ordinal ) )
1147+ . ToString ( ) ;
1148+
1149+ int occurrences = System . Text . RegularExpressions . Regex
1150+ . Matches ( registry , "Name = \" Run\" " )
1151+ . Count ;
1152+ occurrences . Should ( ) . Be ( 3 ) ;
1153+ }
1154+
1155+ [ TestMethod ]
1156+ public void Generator_DoesNotInherit_TestMethodOrDataRow_OntoOverride ( )
1157+ {
1158+ // [TestMethod] and [DataRow] are declared with AttributeUsage(Inherited = false) in
1159+ // the real MSTest assembly. An override that does NOT re-apply [TestMethod] is not a
1160+ // test, and inherited [DataRow]s must not leak from the base method onto the override.
1161+ const string userCode = """
1162+ using Microsoft.VisualStudio.TestTools.UnitTesting;
1163+
1164+ namespace Sample
1165+ {
1166+ public class BaseTests
1167+ {
1168+ [TestMethod]
1169+ [DataRow(1)]
1170+ [DataRow(2)]
1171+ public virtual void Run(int x) { }
1172+ }
1173+
1174+ [TestClass]
1175+ public class DerivedTests : BaseTests
1176+ {
1177+ // Override deliberately does not re-apply [TestMethod] or any [DataRow].
1178+ public override void Run(int x) { }
1179+ }
1180+ }
1181+ """ ;
1182+
1183+ string registry = GetRegistry ( RunGenerator ( MinimalMSTestStub , userCode ) ) ;
1184+
1185+ // Neither the [TestMethod] nor the inherited [DataRow]s should propagate onto the override.
1186+ registry . Should ( ) . NotContain ( "Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()" ) ;
1187+ registry . Should ( ) . NotContain ( "Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute(" ) ;
1188+ registry . Should ( ) . NotContain ( "new DataRowModel(" ) ;
1189+ }
1190+
10351191 private static string GetRegistry ( GeneratorRunResult result )
10361192 => result . GeneratedSources
10371193 . Single ( s => s . HintName == "MSTestReflectionMetadata.Registry.g.cs" )
0 commit comments