diff --git a/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md b/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
index db7d4e4da2..6d67e0d4e4 100644
--- a/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
+++ b/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
@@ -6,3 +6,4 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
MSTEST0058 | Usage | Info | AvoidAssertsInCatchBlocksAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0058)
+MSTEST0059 | Design | Info | AvoidSleepAndDelayInTestsAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0059)
diff --git a/src/Analyzers/MSTest.Analyzers/AvoidSleepAndDelayInTestsAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/AvoidSleepAndDelayInTestsAnalyzer.cs
new file mode 100644
index 0000000000..4b171f26c2
--- /dev/null
+++ b/src/Analyzers/MSTest.Analyzers/AvoidSleepAndDelayInTestsAnalyzer.cs
@@ -0,0 +1,142 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Immutable;
+
+using Analyzer.Utilities.Extensions;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+using MSTest.Analyzers.Helpers;
+
+namespace MSTest.Analyzers;
+
+///
+/// MSTEST0059: .
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
+public sealed class AvoidSleepAndDelayInTestsAnalyzer : DiagnosticAnalyzer
+{
+ private static readonly LocalizableResourceString Title = new(nameof(Resources.AvoidSleepAndDelayInTestsTitle), Resources.ResourceManager, typeof(Resources));
+ private static readonly LocalizableResourceString Description = new(nameof(Resources.AvoidSleepAndDelayInTestsDescription), Resources.ResourceManager, typeof(Resources));
+ private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.AvoidSleepAndDelayInTestsMessageFormat), Resources.ResourceManager, typeof(Resources));
+
+ internal static readonly DiagnosticDescriptor AvoidSleepAndDelayInTestsRule = DiagnosticDescriptorHelper.Create(
+ DiagnosticIds.AvoidSleepAndDelayInTestsRuleId,
+ Title,
+ MessageFormat,
+ Description,
+ Category.Design,
+ DiagnosticSeverity.Info,
+ isEnabledByDefault: false);
+
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; }
+ = ImmutableArray.Create(AvoidSleepAndDelayInTestsRule);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(context =>
+ {
+ // Get the required symbols
+ if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingThread, out INamedTypeSymbol? threadSymbol) ||
+ !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask, out INamedTypeSymbol? taskSymbol) ||
+ !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestMethodAttribute, out INamedTypeSymbol? testMethodAttributeSymbol) ||
+ !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestInitializeAttribute, out INamedTypeSymbol? testInitializeAttributeSymbol) ||
+ !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestCleanupAttribute, out INamedTypeSymbol? testCleanupAttributeSymbol) ||
+ !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingClassInitializeAttribute, out INamedTypeSymbol? classInitializeAttributeSymbol) ||
+ !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingClassCleanupAttribute, out INamedTypeSymbol? classCleanupAttributeSymbol) ||
+ !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingAssemblyInitializeAttribute, out INamedTypeSymbol? assemblyInitializeAttributeSymbol) ||
+ !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingAssemblyCleanupAttribute, out INamedTypeSymbol? assemblyCleanupAttributeSymbol))
+ {
+ return;
+ }
+
+ context.RegisterOperationAction(
+ context => AnalyzeInvocation(context, threadSymbol, taskSymbol, testMethodAttributeSymbol, testInitializeAttributeSymbol, testCleanupAttributeSymbol, classInitializeAttributeSymbol, classCleanupAttributeSymbol, assemblyInitializeAttributeSymbol, assemblyCleanupAttributeSymbol),
+ OperationKind.Invocation);
+ });
+ }
+
+ private static void AnalyzeInvocation(
+ OperationAnalysisContext context,
+ INamedTypeSymbol threadSymbol,
+ INamedTypeSymbol taskSymbol,
+ INamedTypeSymbol testMethodAttributeSymbol,
+ INamedTypeSymbol testInitializeAttributeSymbol,
+ INamedTypeSymbol testCleanupAttributeSymbol,
+ INamedTypeSymbol classInitializeAttributeSymbol,
+ INamedTypeSymbol classCleanupAttributeSymbol,
+ INamedTypeSymbol assemblyInitializeAttributeSymbol,
+ INamedTypeSymbol assemblyCleanupAttributeSymbol)
+ {
+ var invocationOperation = (IInvocationOperation)context.Operation;
+ IMethodSymbol method = invocationOperation.TargetMethod;
+
+ // Check if we're inside a test-related method
+ if (context.ContainingSymbol is not IMethodSymbol containingMethod)
+ {
+ return;
+ }
+
+ // Check if the containing method is a test method or test fixture method
+ if (!IsTestRelatedMethod(containingMethod, testMethodAttributeSymbol, testInitializeAttributeSymbol, testCleanupAttributeSymbol, classInitializeAttributeSymbol, classCleanupAttributeSymbol, assemblyInitializeAttributeSymbol, assemblyCleanupAttributeSymbol))
+ {
+ return;
+ }
+
+ // Check if the invocation is Thread.Sleep
+ if (SymbolEqualityComparer.Default.Equals(method.ContainingType, threadSymbol) && method.Name == "Sleep")
+ {
+ context.ReportDiagnostic(invocationOperation.Syntax.CreateDiagnostic(AvoidSleepAndDelayInTestsRule, "Thread.Sleep"));
+ return;
+ }
+
+ // Check if the invocation is Task.Delay
+ if (SymbolEqualityComparer.Default.Equals(method.ContainingType, taskSymbol) && method.Name == "Delay")
+ {
+ context.ReportDiagnostic(invocationOperation.Syntax.CreateDiagnostic(AvoidSleepAndDelayInTestsRule, "Task.Delay"));
+ return;
+ }
+ }
+
+ private static bool IsTestRelatedMethod(
+ IMethodSymbol method,
+ INamedTypeSymbol testMethodAttributeSymbol,
+ INamedTypeSymbol testInitializeAttributeSymbol,
+ INamedTypeSymbol testCleanupAttributeSymbol,
+ INamedTypeSymbol classInitializeAttributeSymbol,
+ INamedTypeSymbol classCleanupAttributeSymbol,
+ INamedTypeSymbol assemblyInitializeAttributeSymbol,
+ INamedTypeSymbol assemblyCleanupAttributeSymbol)
+ {
+ ImmutableArray attributes = method.GetAttributes();
+ foreach (AttributeData attribute in attributes)
+ {
+ if (attribute.AttributeClass is null)
+ {
+ continue;
+ }
+
+ // Check if the method has any test-related attribute
+ if (attribute.AttributeClass.Inherits(testMethodAttributeSymbol) ||
+ SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, testInitializeAttributeSymbol) ||
+ SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, testCleanupAttributeSymbol) ||
+ SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, classInitializeAttributeSymbol) ||
+ SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, classCleanupAttributeSymbol) ||
+ SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, assemblyInitializeAttributeSymbol) ||
+ SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, assemblyCleanupAttributeSymbol))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
index 15b809366e..08e1918cba 100644
--- a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
+++ b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
@@ -63,4 +63,5 @@ internal static class DiagnosticIds
public const string TestMethodAttributeShouldSetDisplayNameCorrectlyRuleId = "MSTEST0056";
public const string TestMethodAttributeShouldPropagateSourceInformationRuleId = "MSTEST0057";
public const string AvoidAssertsInCatchBlocksRuleId = "MSTEST0058";
+ public const string AvoidSleepAndDelayInTestsRuleId = "MSTEST0059";
}
diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs b/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs
index f2ed45a0f9..4aac2612c6 100644
--- a/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs
+++ b/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs
@@ -56,4 +56,5 @@ internal static class WellKnownTypeNames
public const string SystemThreadingTasksTask1 = "System.Threading.Tasks.Task`1";
public const string SystemThreadingTasksValueTask = "System.Threading.Tasks.ValueTask";
public const string SystemThreadingTasksValueTask1 = "System.Threading.Tasks.ValueTask`1";
+ public const string SystemThreadingThread = "System.Threading.Thread";
}
diff --git a/src/Analyzers/MSTest.Analyzers/Resources.resx b/src/Analyzers/MSTest.Analyzers/Resources.resx
index 85df4da66e..24d2beffbe 100644
--- a/src/Analyzers/MSTest.Analyzers/Resources.resx
+++ b/src/Analyzers/MSTest.Analyzers/Resources.resx
@@ -693,4 +693,13 @@ The type declaring these methods should also respect the following rules:
Using asserts in catch blocks is problematic because the test will pass even if no exception is thrown and the catch block is never executed. Use 'Assert.Throws', 'Assert.ThrowsExactly', 'Assert.ThrowsAsync' or 'Assert.ThrowsExactlyAsync' to verify that an exception is thrown, and then make additional assertions on the caught exception without using the try-catch block.
+
+ Avoid using Thread.Sleep or Task.Delay in test methods
+
+
+ Avoid using '{0}' in test methods as it can cause flakiness. Consider using asynchronous alternatives.
+
+
+ Using 'Thread.Sleep' or 'Task.Delay' in test methods can lead to flaky tests when operations don't complete within the specified time frame. Consider using asynchronous alternatives like 'await Task.Delay' with a cancellation token or 'await task' instead.
+
\ No newline at end of file
diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
index a416844e86..47c64039c0 100644
--- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
+++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
@@ -1004,6 +1004,18 @@ Typ deklarující tyto metody by měl také respektovat následující pravidla:
Používání kontrolních výrazů v blocích catch je problematické, protože test projde, i když se nevyvolá žádná výjimka a blok catch se nikdy nespustí. K ověření, že je vyvolána výjimka, použijte metody Assert.Throws, Assert.ThrowsExactly, Assert.ThrowsAsync nebo Assert.ThrowsExactlyAsync a poté proveďte další kontrolní výrazy nad zachycenou výjimkou bez použití bloku try-catch.
+
+ Avoid using Thread.Sleep or Task.Delay in test methods
+ Avoid using Thread.Sleep or Task.Delay in test methods
+
+
+ Avoid using '{0}' in test methods as it can cause flakiness. Consider using asynchronous alternatives.
+ Avoid using '{0}' in test methods as it can cause flakiness. Consider using asynchronous alternatives.
+
+
+ Using 'Thread.Sleep' or 'Task.Delay' in test methods can lead to flaky tests when operations don't complete within the specified time frame. Consider using asynchronous alternatives like 'await Task.Delay' with a cancellation token or 'await task' instead.
+ Using 'Thread.Sleep' or 'Task.Delay' in test methods can lead to flaky tests when operations don't complete within the specified time frame. Consider using asynchronous alternatives like 'await Task.Delay' with a cancellation token or 'await task' instead.
+