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 classPrefer '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)