diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs index 5c53d8cf10..cec9cc15ec 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.Execution.cs @@ -52,6 +52,19 @@ private void ThrowMultipleAttributesException(string attributeName) throw new TypeInspectionException(errorMessage); } + [DoesNotReturn] + private void ThrowMultipleClassAttributesException(string attributeName) + { + // Note: even if the given attribute has AllowMultiple = false, we can + // still reach here if a derived attribute authored by the user re-defines AttributeUsage + string errorMessage = string.Format( + CultureInfo.CurrentCulture, + Resource.UTA_MultipleAttributesOnTestClass, + Parent.ClassType.FullName, + attributeName); + throw new TypeInspectionException(errorMessage); + } + /// /// Execute test without timeout. /// diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs index f9c86c74b6..4d81c5f61f 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs @@ -174,15 +174,45 @@ private TestMethodAttribute GetTestMethodAttribute() } /// - /// Gets the number of retries this test method should make in case of failure. + /// Resolves the retry attribute that applies to this test method, considering both + /// method-level and class-level attributes. /// + /// + /// A method-level retry attribute fully overrides any class-level retry attribute. + /// Class-level retry attributes are always validated (even when the method has its own + /// retry) so that misuse on the test class is reported regardless of method overrides. + /// /// - /// The number of retries, which is always greater than or equal to 1. - /// If RetryAttribute is not present, returns 1. + /// The resolved , or if neither + /// the method nor the declaring class is decorated. /// private RetryBaseAttribute? GetRetryAttribute() { - Attribute[] attributes = PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached(MethodInfo); + RetryBaseAttribute? methodRetry = GetSingleRetryAttribute( + PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached(MethodInfo), + RetryAttributeScope.Method); + + // Always scan the class as well so a misuse there (multiple class-level retry + // attributes) is reported even when the method has its own retry override. + RetryBaseAttribute? classRetry = GetSingleRetryAttribute( + PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached(Parent.ClassType), + RetryAttributeScope.Class); + + // Method-level retry fully overrides class-level retry when present. + return methodRetry ?? classRetry; + } + + /// + /// Returns the single found in , + /// or if none is present. + /// + /// The attribute set to scan (method-level or class-level). + /// Indicates whether comes from a method or a class; only used to pick the right error message when more than one retry attribute is found. + /// + /// Thrown when contains more than one . + /// + private RetryBaseAttribute? GetSingleRetryAttribute(Attribute[] attributes, RetryAttributeScope scope) + { RetryBaseAttribute? result = null; foreach (Attribute attribute in attributes) { @@ -190,7 +220,14 @@ private TestMethodAttribute GetTestMethodAttribute() { if (result is not null) { - ThrowMultipleAttributesException(nameof(RetryBaseAttribute)); + if (scope == RetryAttributeScope.Class) + { + ThrowMultipleClassAttributesException(nameof(RetryBaseAttribute)); + } + else + { + ThrowMultipleAttributesException(nameof(RetryBaseAttribute)); + } } result = retryAttribute; @@ -199,4 +236,10 @@ private TestMethodAttribute GetTestMethodAttribute() return result; } + + private enum RetryAttributeScope + { + Method, + Class, + } } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/Resource.resx b/src/Adapter/MSTestAdapter.PlatformServices/Resources/Resource.resx index 4aa2f48189..838dbd87c5 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/Resource.resx +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/Resource.resx @@ -399,6 +399,9 @@ but received {4} argument(s), with types '{5}'. The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + Error in executing test. No result returned by extension. If using extension of TestMethodAttribute then please contact vendor. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.cs.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.cs.xlf index c98f8cefe8..0a7ea02a71 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.cs.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.cs.xlf @@ -462,6 +462,11 @@ byl však přijat tento počet argumentů: {4} s typy {5}. Metoda {0}.{1} neexistuje. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. Testovací metoda {0}.{1} má definovaných více atributů odvozených od atributu {2}. Povolený je jenom jeden takový atribut. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.de.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.de.xlf index 267a98ece4..a00f78e9b2 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.de.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.de.xlf @@ -462,6 +462,11 @@ aber empfing {4} Argument(e) mit den Typen „{5}“. Die Methode "{0}.{1}" ist nicht vorhanden. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. Für die Testmethode „{0}.{1}“ sind mehrere Attribute definiert, die von „{2}“ abgeleitet sind. Nur ein einziges solches Attribut ist zulässig. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.es.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.es.xlf index 1443052200..439fadb6a5 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.es.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.es.xlf @@ -462,6 +462,11 @@ pero recibió {4} argumentos, con los tipos '{5}'. El método {0}.{1} no existe. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. El método de prueba '{0}.{1}' tiene varios atributos derivados de '{2}' definidos en él. Solo se permite un atributo de este tipo. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.fr.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.fr.xlf index 9ee5036f55..77a9a662ca 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.fr.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.fr.xlf @@ -462,6 +462,11 @@ mais a reçu {4} argument(s), avec les types « {5} ». La méthode {0}.{1} n'existe pas. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. La méthode de test « {0}.{1} » possède plusieurs attributs dérivés de « {2} » qui lui sont définis. Un seul attribut de ce type est autorisé. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.it.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.it.xlf index a8ffd371bb..15736efefe 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.it.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.it.xlf @@ -462,6 +462,11 @@ ma ha ricevuto {4} argomenti, con tipi '{5}'. Il metodo {0}.{1} non esiste. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. Il metodo di test '{0}.{1}' contiene più attributi derivati da '{2}' definito in esso. È consentito solo uno di tali attributi. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ja.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ja.xlf index 5b5a963c0e..75a20b91a9 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ja.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ja.xlf @@ -463,6 +463,11 @@ but received {4} argument(s), with types '{5}'. メソッド {0}.{1} は存在しません。 + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. テスト メソッド '{0}.{1}' には、 '{2}' から派生した属性が複数定義されています。このような属性は 1 つしか許可されません。 diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ko.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ko.xlf index 052ccf9601..ec257d5a8a 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ko.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ko.xlf @@ -462,6 +462,11 @@ but received {4} argument(s), with types '{5}'. {0}.{1} 메서드가 없습니다. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. 테스트 메서드 '{0}.{1}'에 {2}에서 파생된 여러 특성이 정의되어 있습니다. 이러한 특성은 하나만 허용됩니다. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pl.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pl.xlf index 8309926eff..7d9a20fbba 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pl.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pl.xlf @@ -462,6 +462,11 @@ ale odebrał argumenty {4} z typami „{5}”. Metoda {0}.{1} nie istnieje. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. Metoda testowa "{0}.{1}” ma zdefiniowanych wiele atrybutów pochodzących z „{2}”. Dozwolony jest tylko jeden taki atrybut. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pt-BR.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pt-BR.xlf index fd105b2b91..3304dd61a1 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pt-BR.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.pt-BR.xlf @@ -462,6 +462,11 @@ mas {4} argumentos recebidos, com tipos '{5}'. O método {0}.{1} não existe. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. O método de teste '{0}.{1}' tem várias características derivadas de '{2}' definidas nele. Apenas uma dessas características tem permissão. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ru.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ru.xlf index 1f92136321..35b6c65dde 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ru.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.ru.xlf @@ -462,6 +462,11 @@ but received {4} argument(s), with types '{5}'. Метод {0}.{1} не существует. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. У метода тестирования "{0}.{1}" есть несколько атрибутов, производных от заданного в нем "{2}". Допускается только один такой атрибут. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.tr.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.tr.xlf index 9e39ef1f9b..a9cacb267e 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.tr.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.tr.xlf @@ -462,6 +462,11 @@ ancak, '{5}' türünde {4} bağımsız değişken aldı. {0}.{1} metodu yok. + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. “{0}.{1}” test yöntemi, üzerinde tanımlanan “{2}” öğesinden türetilmiş birden fazla öznitelik içeriyor. Bu türde yalnızca bir tane özniteliğe izin verilir. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hans.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hans.xlf index 11c20a8082..1b172ae754 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hans.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hans.xlf @@ -462,6 +462,11 @@ but received {4} argument(s), with types '{5}'. 方法 {0}.{1} 不存在。 + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. 测试方法“{0}.{1}”具有多个在其上定义的“{2}”的派生属性。仅允许一个此类属性。 diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hant.xlf b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hant.xlf index 2e942c2bf9..d47a7ca08d 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hant.xlf +++ b/src/Adapter/MSTestAdapter.PlatformServices/Resources/xlf/Resource.zh-Hant.xlf @@ -462,6 +462,11 @@ but received {4} argument(s), with types '{5}'. 方法 {0}.{1} 不存在。 + + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + The test class '{0}' has multiple attributes derived from '{1}' defined on it. Only one such attribute is allowed. + + The test method '{0}.{1}' has multiple attributes derived from '{2}' defined on it. Only one such attribute is allowed. 測試方法 '{0}.{1}' 具有多個衍生自 '{2}' 的屬性根據其定義。只允許一個此類屬性。 diff --git a/src/Analyzers/MSTest.Analyzers/Resources.resx b/src/Analyzers/MSTest.Analyzers/Resources.resx index d30b0cd2a9..0b3097f38c 100644 --- a/src/Analyzers/MSTest.Analyzers/Resources.resx +++ b/src/Analyzers/MSTest.Analyzers/Resources.resx @@ -583,10 +583,10 @@ The type declaring these methods should also respect the following rules: Do not duplicate 'DataRow' attributes. This is usually a copy/paste error. The attribute indices are '{0}' and '{1}'. - Use retry attribute on test method + Use retry attribute on test method or test class - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class Prefer 'TestMethod' over 'DataTestMethod' diff --git a/src/Analyzers/MSTest.Analyzers/UseRetryWithTestMethodAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/UseRetryWithTestMethodAnalyzer.cs index bd9d74d458..63bab875f6 100644 --- a/src/Analyzers/MSTest.Analyzers/UseRetryWithTestMethodAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/UseRetryWithTestMethodAnalyzer.cs @@ -42,35 +42,43 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(context => { - if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestMethodAttribute, out INamedTypeSymbol? testMethodAttributeSymbol) && - context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingRetryBaseAttribute, out INamedTypeSymbol? retryBaseAttributeSymbol)) + if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestMethodAttribute, out INamedTypeSymbol? testMethodAttributeSymbol) + && context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestClassAttribute, out INamedTypeSymbol? testClassAttributeSymbol) + && context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingRetryBaseAttribute, out INamedTypeSymbol? retryBaseAttributeSymbol)) { context.RegisterSymbolAction( - context => AnalyzeSymbol(context, testMethodAttributeSymbol, retryBaseAttributeSymbol), - SymbolKind.Method); + context => AnalyzeSymbol(context, testMethodAttributeSymbol, testClassAttributeSymbol, retryBaseAttributeSymbol), + SymbolKind.Method, + SymbolKind.NamedType); } }); } - private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol testMethodAttributeSymbol, INamedTypeSymbol retryBaseAttributeSymbol) + private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol testMethodAttributeSymbol, INamedTypeSymbol testClassAttributeSymbol, INamedTypeSymbol retryBaseAttributeSymbol) { bool hasRetryBaseAttribute = false; foreach (AttributeData attribute in context.Symbol.GetAttributes()) { - if (attribute.AttributeClass.Inherits(testMethodAttributeSymbol)) + if (context.Symbol.Kind == SymbolKind.Method && attribute.AttributeClass.Inherits(testMethodAttributeSymbol)) { // We are looking for retry attributes that are not applied to test methods. // If this is already a test method, we just return. return; } + if (context.Symbol.Kind == SymbolKind.NamedType && attribute.AttributeClass.Inherits(testClassAttributeSymbol)) + { + // Similarly, if this is already a test class, we just return. + return; + } + if (attribute.AttributeClass.Inherits(retryBaseAttributeSymbol)) { hasRetryBaseAttribute = true; } } - // We looked all attributes, and we found a retry attribute, but didn't find TestMethodAttribute. + // We looked all attributes, and we found a retry attribute, but didn't find TestMethodAttribute or TestClassAttribute. if (hasRetryBaseAttribute) { context.ReportDiagnostic(context.Symbol.CreateDiagnostic(UseRetryWithTestMethodRule)); diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf index d039aa268f..28077209f1 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf @@ -990,13 +990,13 @@ Typ deklarující tyto metody by měl také respektovat následující pravidla: - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - Atribut, který je odvozen od retryBaseAttribute, lze zadat pouze v testovací metodě. + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + Atribut, který je odvozen od retryBaseAttribute, lze zadat pouze v testovací metodě. - Use retry attribute on test method - Použití atributu opakování u testovací metody + Use retry attribute on test method or test class + Použití atributu opakování u testovací metody diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf index 303c4c40da..d0392c0779 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf @@ -991,13 +991,13 @@ Der Typ, der diese Methoden deklariert, sollte auch die folgenden Regeln beachte - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - Ein von „RetryBaseAttribute“ abgeleitetes Attribut kann nur für eine Testmethode angegeben werden. + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + Ein von „RetryBaseAttribute“ abgeleitetes Attribut kann nur für eine Testmethode angegeben werden. - Use retry attribute on test method - Verwenden des Wiederholungsattributs für die Testmethode + Use retry attribute on test method or test class + Verwenden des Wiederholungsattributs für die Testmethode diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf index 3e2d9f972b..9826583a2e 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf @@ -990,13 +990,13 @@ El tipo que declara estos métodos también debe respetar las reglas siguientes: - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - Un atributo que deriva de 'RetryBaseAttribute' solo se puede especificar en un método de prueba + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + Un atributo que deriva de 'RetryBaseAttribute' solo se puede especificar en un método de prueba - Use retry attribute on test method - Usar el atributo de reintento en el método de prueba + Use retry attribute on test method or test class + Usar el atributo de reintento en el método de prueba diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf index bb47273794..7046be7985 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf @@ -990,13 +990,13 @@ Le type doit être une classe - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - Vous ne pouvez spécifier un attribut dérivant de « RetryBaseAttribute » que sur une méthode de test + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + Vous ne pouvez spécifier un attribut dérivant de « RetryBaseAttribute » que sur une méthode de test - Use retry attribute on test method - Utilisez l’attribut de nouvelle tentative sur la méthode de test + Use retry attribute on test method or test class + Utilisez l’attribut de nouvelle tentative sur la méthode de test diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf index b63f33d308..0992505351 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf @@ -990,13 +990,13 @@ Anche il tipo che dichiara questi metodi deve rispettare le regole seguenti: - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - È possibile specificare un attributo che deriva da ‘RetryBaseAttribute’ solo in un metodo di test + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + È possibile specificare un attributo che deriva da ‘RetryBaseAttribute’ solo in un metodo di test - Use retry attribute on test method - Utilizzare l'attributo di ripetizione nel metodo di test + Use retry attribute on test method or test class + Utilizzare l'attributo di ripetizione nel metodo di test diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf index cab3960df4..0cef2f3bb1 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf @@ -990,13 +990,13 @@ The type declaring these methods should also respect the following rules: - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - 'RetryBaseAttribute' から派生する属性は、テスト メソッドでのみ指定できます + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + 'RetryBaseAttribute' から派生する属性は、テスト メソッドでのみ指定できます - Use retry attribute on test method - テスト メソッドで retry 属性を使用する + Use retry attribute on test method or test class + テスト メソッドで retry 属性を使用する diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf index c9a1d30b28..be776c6e11 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf @@ -990,13 +990,13 @@ The type declaring these methods should also respect the following rules: - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - 'RetryBaseAttribute'에서 파생된 속성은 테스트 메서드에서만 지정할 수 있습니다. + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + 'RetryBaseAttribute'에서 파생된 속성은 테스트 메서드에서만 지정할 수 있습니다. - Use retry attribute on test method - 테스트 메서드에 재시도 속성 사용 + Use retry attribute on test method or test class + 테스트 메서드에 재시도 속성 사용 diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf index 82144e07a5..fd05c18408 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf @@ -990,13 +990,13 @@ Typ deklarujący te metody powinien również przestrzegać następujących regu - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - Atrybut pochodzący od atrybutu „RetryBaseAttribute” można określić tylko w metodzie testowej + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + Atrybut pochodzący od atrybutu „RetryBaseAttribute” można określić tylko w metodzie testowej - Use retry attribute on test method - Użyj atrybutu ponawiania dla metody testowej + Use retry attribute on test method or test class + Użyj atrybutu ponawiania dla metody testowej diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf index 5cb977417b..850de01fc7 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.pt-BR.xlf @@ -990,13 +990,13 @@ O tipo que declara esses métodos também deve respeitar as seguintes regras: - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - Um atributo derivado de “RetryBaseAttribute” só pode ser especificado em um método de teste + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + Um atributo derivado de “RetryBaseAttribute” só pode ser especificado em um método de teste - Use retry attribute on test method - Usar o atributo de repetição no método de teste + Use retry attribute on test method or test class + Usar o atributo de repetição no método de teste diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf index 569d19c064..5768eb930f 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.ru.xlf @@ -1002,13 +1002,13 @@ The type declaring these methods should also respect the following rules: - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - Атрибут, производный от "RetryBaseAttribute", может быть указан только в тестовом методе + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + Атрибут, производный от "RetryBaseAttribute", может быть указан только в тестовом методе - Use retry attribute on test method - Использовать атрибут retry в тестовом методе + Use retry attribute on test method or test class + Использовать атрибут retry в тестовом методе diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf index 2262c5a02f..45e118ae89 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.tr.xlf @@ -992,13 +992,13 @@ Bu yöntemleri bildiren tipin ayrıca aşağıdaki kurallara uyması gerekir: - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - 'RetryBaseAttribute' özniteliğinden türetilen bir öznitelik yalnızca bir test yönteminde belirtilebilir + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + 'RetryBaseAttribute' özniteliğinden türetilen bir öznitelik yalnızca bir test yönteminde belirtilebilir - Use retry attribute on test method - Test yönteminde yeniden deneme özniteliğini kullan + Use retry attribute on test method or test class + Test yönteminde yeniden deneme özniteliğini kullan diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf index 78f3cc5634..a2c2ef6559 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hans.xlf @@ -990,13 +990,13 @@ The type declaring these methods should also respect the following rules: - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - 只能在测试方法上指定派生自 ‘RetryBaseAttribute’ 的属性 + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + 只能在测试方法上指定派生自 ‘RetryBaseAttribute’ 的属性 - Use retry attribute on test method - 对测试方法使用重试属性 + Use retry attribute on test method or test class + 对测试方法使用重试属性 diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf index b93f4e842c..7b6615c170 100644 --- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf +++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.zh-Hant.xlf @@ -990,13 +990,13 @@ The type declaring these methods should also respect the following rules: - An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method - 衍生自 'RetryBaseAttribute' 的屬性只能在測試方法上指定 + An attribute that derives from 'RetryBaseAttribute' can be specified only on a test method or a test class + 衍生自 'RetryBaseAttribute' 的屬性只能在測試方法上指定 - Use retry attribute on test method - 在測試方法上使用重試屬性 + Use retry attribute on test method or test class + 在測試方法上使用重試屬性 diff --git a/src/TestFramework/TestFramework/Attributes/TestMethod/RetryAttribute.cs b/src/TestFramework/TestFramework/Attributes/TestMethod/RetryAttribute.cs index ce0ef338d7..4b6a83c179 100644 --- a/src/TestFramework/TestFramework/Attributes/TestMethod/RetryAttribute.cs +++ b/src/TestFramework/TestFramework/Attributes/TestMethod/RetryAttribute.cs @@ -6,7 +6,13 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting; /// /// This attribute is used to set a retry count on a test method in case of failure. /// -[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] +/// +/// When applied to a test class, the attribute is used as a default for every test method declared on the class. +/// A placed directly on a test method takes precedence over a class-level one +/// (the method-level value fully replaces the class-level value, regardless of whether it is larger or smaller). +/// The attribute is not inherited: applying it to a base test class does not apply it to derived test classes. +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public sealed class RetryAttribute : RetryBaseAttribute { /// diff --git a/src/TestFramework/TestFramework/Attributes/TestMethod/RetryBaseAttribute.cs b/src/TestFramework/TestFramework/Attributes/TestMethod/RetryBaseAttribute.cs index c1b5a6d9c6..23593da332 100644 --- a/src/TestFramework/TestFramework/Attributes/TestMethod/RetryBaseAttribute.cs +++ b/src/TestFramework/TestFramework/Attributes/TestMethod/RetryBaseAttribute.cs @@ -7,7 +7,12 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting; /// An abstract attribute that controls retrying a test method if it failed. It's up to the derived classes to /// define how the retry is done. /// -[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] +/// +/// When applied to a test class, the attribute is used as a default for every test method declared on the class. +/// A retry attribute placed directly on a test method takes precedence over a class-level one. +/// The attribute is not inherited: applying it to a base test class does not apply it to derived test classes. +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] public abstract class RetryBaseAttribute : Attribute { /// diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/RetryTests.cs b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/RetryTests.cs index 3305922e2a..57a3216927 100644 --- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/RetryTests.cs +++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/RetryTests.cs @@ -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 Microsoft.Testing.Platform.Acceptance.IntegrationTests; @@ -146,3 +146,112 @@ public static void ClassCleanup() public TestContext TestContext { get; set; } } + +[TestClass] +public sealed class ClassLevelRetryTests : AcceptanceTestBase +{ + [TestMethod] + public async Task ClassLevelRetry_AppliesToAllTestMethods_AndMethodLevelOverrides() + { + var testHost = TestHost.LocateFrom(AssetFixture.ProjectPath, TestAssetFixture.ProjectName, TargetFrameworks.NetCurrent); + TestHostResult testHostResult = await testHost.ExecuteAsync("--settings my.runsettings", cancellationToken: TestContext.CancellationToken); + + testHostResult.AssertExitCodeIs(ExitCode.AtLeastOneTestFailed); + + // ClassLevelOnly is decorated only by class-level [Retry(3)] => 4 total runs. + // MethodLevelOverride overrides the class-level [Retry(3)] with method-level [Retry(1)] => 2 total runs. + // PassingMethod also has class-level retry but passes on first attempt => 1 total run. + testHostResult.AssertOutputContains(""" + ClassLevelOnly executed 4 times. + MethodLevelOverride executed 2 times. + PassingMethod executed 1 time. + """); + testHostResult.AssertOutputContainsSummary(failed: 2, passed: 1, skipped: 0); + } + + public sealed class TestAssetFixture() : TestAssetFixtureBase() + { + public const string ProjectName = "ClassLevelRetryTests"; + + public string ProjectPath => GetAssetPath(ProjectName); + + public override (string ID, string Name, string Code) GetAssetsToGenerate() => (ProjectName, ProjectName, + SourceCode + .PatchTargetFrameworks(TargetFrameworks.NetCurrent) + .PatchCodeWithReplace("$MSTestVersion$", MSTestVersion)); + + private const string SourceCode = """ +#file ClassLevelRetryTests.csproj + + + + Exe + true + $TargetFrameworks$ + + + + + + + + + + PreserveNewest + + + + +#file UnitTest1.cs +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[TestClass] +[Retry(3)] +public class UnitTest1 +{ + private static int _classLevelOnly; + private static int _methodOverride; + private static int _passing; + + [TestMethod] + public void ClassLevelOnly() + { + _classLevelOnly++; + Assert.Fail("Always failing ClassLevelOnly"); + } + + [TestMethod] + [Retry(1)] + public void MethodLevelOverride() + { + _methodOverride++; + Assert.Fail("Always failing MethodLevelOverride"); + } + + [TestMethod] + public void PassingMethod() + { + _passing++; + } + + [ClassCleanup] + public static void ClassCleanup() + { + Console.WriteLine($"ClassLevelOnly executed {_classLevelOnly} times."); + Console.WriteLine($"MethodLevelOverride executed {_methodOverride} times."); + Console.WriteLine($"PassingMethod executed {_passing} time{(_passing == 1 ? string.Empty : "s")}."); + } +} + +#file my.runsettings + + + false + + +"""; + } + + public TestContext TestContext { get; set; } +} diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/UseRetryWithTestMethodAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/UseRetryWithTestMethodAnalyzerTests.cs index b4ea95243f..253a66395a 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/UseRetryWithTestMethodAnalyzerTests.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/UseRetryWithTestMethodAnalyzerTests.cs @@ -112,4 +112,88 @@ public void M() await VerifyCS.VerifyCodeFixAsync(code, code); } + + [TestMethod] + public async Task WhenTestClassHasRetryAttribute_NoDiagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + internal sealed class MyTestClassAttribute : TestClassAttribute { } + + [TestClass] + [Retry(maxRetryAttempts: 3)] + public class MyTestClass + { + [TestMethod] + public void M() + { + } + } + + [Retry(maxRetryAttempts: 3)] + [TestClass] + public class MyTestClass2 + { + [TestMethod] + public void M() + { + } + } + + [MyTestClass] + [Retry(maxRetryAttempts: 3)] + public class MyTestClass3 + { + [TestMethod] + public void M() + { + } + } + + [Retry(maxRetryAttempts: 3)] + [MyTestClass] + public class MyTestClass4 + { + [TestMethod] + public void M() + { + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + + [TestMethod] + public async Task WhenNonTestClassHasRetryAttribute_Diagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [Retry(maxRetryAttempts: 3)] + public class [|MyClass|] + { + } + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } + + [TestMethod] + public async Task WhenNonTestClassDoesNotHaveRetryAttribute_NoDiagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public class MyClass + { + public void M() + { + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(code, code); + } } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodInfoTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodInfoTests.cs index 0493a3d508..b808744172 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodInfoTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodInfoTests.cs @@ -208,6 +208,67 @@ public void TestMethodInfoCtorShouldThrowWhenMultipleRetryBaseAttributesExist() nameof(RetryBaseAttribute))); } + public void TestMethodInfoCtorShouldUseClassLevelRetryAttributeWhenMethodHasNone() + { + TestMethodInfo testMethodInfo = CreateTestMethodInfoForRetryAttributeTests( + nameof(DummyTestClassWithClassLevelRetry.MethodWithoutRetryAttribute)); + + testMethodInfo.RetryAttribute.Should().BeOfType(); + ((RetryAttribute)testMethodInfo.RetryAttribute!).MaxRetryAttempts.Should().Be(4); + } + + public void TestMethodInfoCtorShouldPreferMethodLevelRetryAttributeOverClassLevel() + { + TestMethodInfo testMethodInfo = CreateTestMethodInfoForRetryAttributeTests( + nameof(DummyTestClassWithClassLevelRetry.MethodWithRetryAttribute)); + + testMethodInfo.RetryAttribute.Should().BeOfType(); + ((RetryAttribute)testMethodInfo.RetryAttribute!).MaxRetryAttempts.Should().Be(7); + } + + public void TestMethodInfoCtorShouldPreferMethodLevelRetryEvenWhenSmallerThanClassLevel() + { + TestMethodInfo testMethodInfo = CreateTestMethodInfoForRetryAttributeTests( + nameof(DummyTestClassWithClassLevelRetry.MethodWithSmallerRetryAttribute)); + + testMethodInfo.RetryAttribute.Should().BeOfType(); + ((RetryAttribute)testMethodInfo.RetryAttribute!).MaxRetryAttempts.Should().Be(1); + } + + public void TestMethodInfoCtorShouldNotInheritRetryAttributeFromBaseClass() + { + TestMethodInfo testMethodInfo = CreateTestMethodInfoForRetryAttributeTests( + nameof(DerivedFromClassWithClassLevelRetry.MethodInDerivedClass)); + + testMethodInfo.RetryAttribute.Should().BeNull(); + } + + public void TestMethodInfoCtorShouldThrowWhenMultipleClassLevelRetryBaseAttributesExist() + { + Action action = () => _ = CreateTestMethodInfoForRetryAttributeTests( + nameof(DummyTestClassWithMultipleClassLevelRetryAttributes.TestMethod)); + + TypeInspectionException exception = action.Should().Throw().Which; + exception.Message.Should().Be(string.Format( + CultureInfo.CurrentCulture, + Resource.UTA_MultipleAttributesOnTestClass, + typeof(DummyTestClassWithMultipleClassLevelRetryAttributes).FullName, + nameof(RetryBaseAttribute))); + } + + public void TestMethodInfoCtorShouldThrowOnMultipleClassLevelRetryEvenWhenMethodHasItsOwnRetry() + { + Action action = () => _ = CreateTestMethodInfoForRetryAttributeTests( + nameof(DummyTestClassWithMultipleClassLevelRetryAttributes.TestMethodWithOwnRetry)); + + TypeInspectionException exception = action.Should().Throw().Which; + exception.Message.Should().Be(string.Format( + CultureInfo.CurrentCulture, + Resource.UTA_MultipleAttributesOnTestClass, + typeof(DummyTestClassWithMultipleClassLevelRetryAttributes).FullName, + nameof(RetryBaseAttribute))); + } + #region TestMethod invoke scenarios public async Task TestMethodInfoInvokeShouldWaitForAsyncTestMethodsToComplete() @@ -1791,8 +1852,11 @@ private static async Task RunWithTestablePlatformService(TestablePlatformService } private TestMethodInfo CreateTestMethodInfoForRetryAttributeTests(string methodName) + => CreateTestMethodInfoForRetryAttributeTests(methodName); + + private TestMethodInfo CreateTestMethodInfoForRetryAttributeTests(string methodName) { - Type classType = typeof(DummyTestClassWithRetryAttributeMethods); + Type classType = typeof(TClass); MethodInfo methodInfo = classType.GetMethod(methodName)!; ConstructorInfo constructorInfo = classType.GetConstructor([])!; var testClassInfo = new TestClassInfo(classType, constructorInfo, isParameterlessConstructor: true, _classAttribute, _testAssemblyInfo); @@ -1996,6 +2060,63 @@ protected internal override Task ExecuteAsync(RetryContext retryCon => throw new NotSupportedException(); } + [Retry(4)] + public class DummyTestClassWithClassLevelRetry + { + [TestMethod] + public void MethodWithoutRetryAttribute() + { + } + + [TestMethod] + [Retry(7)] + public void MethodWithRetryAttribute() + { + } + + [TestMethod] + [Retry(1)] + public void MethodWithSmallerRetryAttribute() + { + } + } + + public class DerivedFromClassWithClassLevelRetry : DummyTestClassWithClassLevelRetry + { + [TestMethod] + public void MethodInDerivedClass() + { + } + } + + [DummyClassLevelRetry1] + [DummyClassLevelRetry2] + public class DummyTestClassWithMultipleClassLevelRetryAttributes + { + [TestMethod] + public void TestMethod() + { + } + + [TestMethod] + [Retry(2)] + public void TestMethodWithOwnRetry() + { + } + } + + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + private sealed class DummyClassLevelRetry1Attribute : RetryBaseAttribute + { + protected internal override Task ExecuteAsync(RetryContext retryContext) => throw new NotSupportedException(); + } + + [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + private sealed class DummyClassLevelRetry2Attribute : RetryBaseAttribute + { + protected internal override Task ExecuteAsync(RetryContext retryContext) => throw new NotSupportedException(); + } + public class DummyTestClassWithParameterizedCtor { public DummyTestClassWithParameterizedCtor(int x)