diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/ExtensionResources.resx b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/ExtensionResources.resx
index 089fc89eed..8179e3290f 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/ExtensionResources.resx
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/ExtensionResources.resx
@@ -149,9 +149,6 @@ Moving last attempt asset files to the default result directory
Retry failed tests
-
- The retry provider requires the default TestHostManager implementation.
-
The retry extension is not supported on browser platform. Browser-based tests cannot be retried due to platform limitations.
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.cs.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.cs.xlf
index 5bdadf2632..0ec43e125a 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.cs.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.cs.xlf
@@ -100,11 +100,6 @@ Přesouvání souborů prostředků posledního pokusu do výchozího adresáře
Možnost {0} nelze používat společně s možností {1}.
-
- The retry provider requires the default TestHostManager implementation.
- Zprostředkovatel opakování vyžaduje výchozí implementaci TestHostManager.
-
- Test host process exited before the retry service could connect to it. Exit code: {0}Hostitelský proces testu byl ukončen dříve, než se k němu mohla služba opakování připojit. Ukončovací kód: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.de.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.de.xlf
index e059b1b060..54f48e40b8 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.de.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.de.xlf
@@ -100,11 +100,6 @@ Medienobjektdateien des letzten Versuchs werden in das Standardergebnisverzeichn
Optionen "{0}" und "{1}" können nicht zusammen verwendet werden
-
- The retry provider requires the default TestHostManager implementation.
- Der Wiederholungsanbieter erfordert die standardmäßige TestHostManager-Implementierung.
-
- Test host process exited before the retry service could connect to it. Exit code: {0}Der Testhostprozess wurde beendet, bevor der Wiederholungsdienst eine Verbindung herstellen konnte. Exitcode: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.es.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.es.xlf
index 019d9943a9..55edc18d71 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.es.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.es.xlf
@@ -100,11 +100,6 @@ Moviendo los archivos de recursos del último intento al directorio de resultado
Las opciones '{0}' y '{1}' no se pueden usar juntas
-
- The retry provider requires the default TestHostManager implementation.
- El proveedor de reintentos requiere la implementación predeterminada de TestHostManager.
-
- Test host process exited before the retry service could connect to it. Exit code: {0}El proceso de host de prueba se cerró antes de que el servicio de reintento pudiera conectarse a él. Código de salida: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.fr.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.fr.xlf
index 27f4471ef2..2fc7921866 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.fr.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.fr.xlf
@@ -100,11 +100,6 @@ Déplacement des fichiers de ressources de la dernière tentative vers le réper
Les options «{0}» et «{1}» ne peuvent pas être utilisées ensemble
-
- The retry provider requires the default TestHostManager implementation.
- Le fournisseur de nouvelles tentatives requiert l'implémentation par défaut de TestHostManager.
-
- Test host process exited before the retry service could connect to it. Exit code: {0}Le processus hôte de test s’est arrêté avant que le service de nouvelle tentative puisse s’y connecter. Code de sortie : {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.it.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.it.xlf
index 0bdcf6f330..a55bf502da 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.it.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.it.xlf
@@ -100,11 +100,6 @@ Spostamento dei file di asset dell'ultimo tentativo nella directory dei risultat
Le opzioni '{0}' e '{1}' non possono essere usate insieme
-
- The retry provider requires the default TestHostManager implementation.
- Il provider di tentativi richiede l'implementazione predefinita di TestHostManager.
-
- Test host process exited before the retry service could connect to it. Exit code: {0}Il processo host di test è stato chiuso prima che il servizio di ripetizione potesse connettersi ad esso. Codice di uscita: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ja.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ja.xlf
index 644934883a..23d77b2232 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ja.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ja.xlf
@@ -100,11 +100,6 @@ Moving last attempt asset files to the default result directory
オプション '{0}' と '{1}' を一緒に使用することはできません
-
- The retry provider requires the default TestHostManager implementation.
- 再試行プロバイダーには、既定の TestHostManager 実装が必要です。
-
- Test host process exited before the retry service could connect to it. Exit code: {0}再試行サービスが接続する前に、テスト ホスト プロセスが終了しました。終了コード: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ko.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ko.xlf
index fd8219655f..df4c3290dd 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ko.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ko.xlf
@@ -100,11 +100,6 @@ Moving last attempt asset files to the default result directory
'{0}' 및 '{1}' 옵션은 함께 사용할 수 없습니다.
-
- The retry provider requires the default TestHostManager implementation.
- 다시 시도 공급자에는 기본 TestHostManager 구현이 필요합니다.
-
- Test host process exited before the retry service could connect to it. Exit code: {0}다시 시도 서비스가 연결되기 전에 테스트 호스트 프로세스가 종료되었습니다. 종료 코드: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.pl.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.pl.xlf
index 6ab00fd1b3..cde119184e 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.pl.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.pl.xlf
@@ -100,11 +100,6 @@ Przeniesienie plików zasobów ostatniej próby do domyślnego katalogu wyników
Opcji „{0}” i „{1}” nie można używać razem
-
- The retry provider requires the default TestHostManager implementation.
- Dostawca ponawiania wymaga domyślnej implementacji TestHostManager.
-
- Test host process exited before the retry service could connect to it. Exit code: {0}Proces hosta testowego zakończył się, zanim usługa ponawiania próby mogła nawiązać z nim połączenie. Kod zakończenia: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.pt-BR.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.pt-BR.xlf
index 3c806a6d8f..e7b749064a 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.pt-BR.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.pt-BR.xlf
@@ -100,11 +100,6 @@ Movendo arquivos de ativo da última tentativa para o diretório de resultados p
As opções ''{0}'' e ''{1}'' não podem ser usadas juntas
-
- The retry provider requires the default TestHostManager implementation.
- O provedor de repetição requer a implementação padrão de TestHostManager.
-
- Test host process exited before the retry service could connect to it. Exit code: {0}O processo de host de teste foi encerrado antes que o serviço de repetição pudesse se conectar a ele. Código de saída: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ru.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ru.xlf
index b709eba3b0..eecf000105 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ru.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.ru.xlf
@@ -100,11 +100,6 @@ Moving last attempt asset files to the default result directory
Параметры "{0}" и "{1}" не могут использоваться вместе
-
- The retry provider requires the default TestHostManager implementation.
- Для поставщика повторных попыток требуется реализация TestHostManager по умолчанию.
-
- Test host process exited before the retry service could connect to it. Exit code: {0}Тестовый хост-процесс завершился прежде, чем к нему смогла подключиться служба повторной попытки. Код выхода: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.tr.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.tr.xlf
index 92f4e47e5f..8879edbaee 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.tr.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.tr.xlf
@@ -100,11 +100,6 @@ Son deneme varlık dosyaları, varsayılan sonuç dizinine taşınıyor
'{0}' ve '{1}' seçenekleri birlikte kullanılamaz
-
- The retry provider requires the default TestHostManager implementation.
- Yeniden deneme sağlayıcısı varsayılan TestHostManager'ın uygulanmasını gerektiriyor.
-
- Test host process exited before the retry service could connect to it. Exit code: {0}Yeniden deneme hizmeti ona bağlanamadan test ana makinesi işleminden çıkıldı. Çıkış kodu: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.zh-Hans.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.zh-Hans.xlf
index db949f4223..643ff90857 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.zh-Hans.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.zh-Hans.xlf
@@ -100,11 +100,6 @@ Moving last attempt asset files to the default result directory
选项“{0}”和“{1}”不能一起使用
-
- The retry provider requires the default TestHostManager implementation.
- 重试提供程序需要默认的 TestHostManager 实现。
-
- Test host process exited before the retry service could connect to it. Exit code: {0}测试主机进程已在重试服务可以连接到它之前退出。退出代码: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.zh-Hant.xlf b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.zh-Hant.xlf
index 2a1b38e138..417569a8bc 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.zh-Hant.xlf
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Resources/xlf/ExtensionResources.zh-Hant.xlf
@@ -100,11 +100,6 @@ Moving last attempt asset files to the default result directory
選項 '{0}' 和 '{1}' 不能同時使用
-
- The retry provider requires the default TestHostManager implementation.
- 重試提供者需要預設的 TestHostManager 實作。
-
- Test host process exited before the retry service could connect to it. Exit code: {0}測試主機處理序在重試服務連線到它之前已結束。結束代碼: {0}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs
deleted file mode 100644
index 5e75951d3f..0000000000
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under dual-license. See LICENSE.PLATFORMTOOLS.txt file in the project root for full license information.
-
-using Microsoft.Testing.Extensions.Policy.Resources;
-using Microsoft.Testing.Platform.CommandLine;
-using Microsoft.Testing.Platform.Extensions.Messages;
-using Microsoft.Testing.Platform.Helpers;
-using Microsoft.Testing.Platform.Requests;
-using Microsoft.Testing.Platform.Services;
-
-namespace Microsoft.Testing.Extensions.Policy;
-
-[UnsupportedOSPlatform("browser")]
-internal sealed class RetryExecutionFilterFactory : ITestExecutionFilterFactory
-{
- private readonly IServiceProvider _serviceProvider;
- private readonly ICommandLineOptions _commandLineOptions;
- private RetryLifecycleCallbacks? _retryFailedTestsLifecycleCallbacks;
-
- public RetryExecutionFilterFactory(IServiceProvider serviceProvider)
- {
- _serviceProvider = serviceProvider;
- _commandLineOptions = serviceProvider.GetCommandLineOptions();
- }
-
- public string Uid => nameof(RetryExecutionFilterFactory);
-
- public string Version => AppVersion.DefaultSemVer;
-
- public string DisplayName => ExtensionResources.RetryFailedTestsExtensionDisplayName;
-
- public string Description => ExtensionResources.RetryFailedTestsExtensionDescription;
-
- public Task IsEnabledAsync()
- => Task.FromResult(_commandLineOptions.IsOptionSet(RetryCommandLineOptionsProvider.RetryFailedTestsPipeNameOptionName));
-
- public async Task<(bool, ITestExecutionFilter?)> TryCreateAsync()
- {
- _retryFailedTestsLifecycleCallbacks = _serviceProvider.GetRequiredService();
- if (_retryFailedTestsLifecycleCallbacks.FailedTestsIDToRetry?.Length > 0)
- {
- return (true, new TestNodeUidListFilter([.. _retryFailedTestsLifecycleCallbacks.FailedTestsIDToRetry.Select(x => new TestNodeUid(x))]));
- }
- else
- {
- ConsoleTestExecutionFilterFactory consoleTestExecutionFilterFactory = new(_commandLineOptions);
- return await consoleTestExecutionFilterFactory.TryCreateAsync().ConfigureAwait(false);
- }
- }
-}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs
index 404fce2aaf..7146c1cc7e 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs
@@ -5,7 +5,6 @@
using Microsoft.Testing.Extensions.Policy.Resources;
using Microsoft.Testing.Platform.Builder;
using Microsoft.Testing.Platform.Extensions;
-using Microsoft.Testing.Platform.TestHost;
namespace Microsoft.Testing.Extensions;
@@ -38,14 +37,5 @@ CompositeExtensionFactory compositeExtensionFactory
builder.TestHostOrchestrator
.AddTestHostOrchestrator(serviceProvider => new RetryOrchestrator(serviceProvider));
-
- if (builder.TestHost is not TestHostManager testHostManager)
- {
- throw new InvalidOperationException(
- ExtensionResources.RetryProviderRequiresDefaultTestHostManagerErrorMessage);
- }
-
- testHostManager
- .AddTestExecutionFilterFactory(serviceProvider => new RetryExecutionFilterFactory(serviceProvider));
}
}
diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs
index 5aeb86304a..0bc6041dd3 100644
--- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs
+++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs
@@ -170,6 +170,51 @@ public async Task OrchestrateTestHostExecutionAsync(CancellationToken cance
finalArguments.Add($"--{RetryCommandLineOptionsProvider.RetryFailedTestsPipeNameOptionName}");
finalArguments.Add(retryFailedTestsPipeServer.PipeName);
+ // When retrying, replace any existing test filter with --filter-uid for the failed tests
+ if (lastListOfFailedId is { Length: > 0 })
+ {
+ RemoveOption(finalArguments, TreeNodeFilterCommandLineOptionsProvider.TreenodeFilter);
+ RemoveOption(finalArguments, PlatformCommandLineProvider.FilterUidOptionKey);
+
+ // Estimate command line length to avoid hitting OS limits (notably ~32K on Windows).
+ const int CommandLineLengthLimit = 30_000;
+ int predictedLength = 0;
+ foreach (string arg in finalArguments)
+ {
+ predictedLength += arg.Length + 1;
+ }
+
+ predictedLength += 2 + PlatformCommandLineProvider.FilterUidOptionKey.Length + 1;
+ foreach (string uid in lastListOfFailedId)
+ {
+ predictedLength += uid.Length + 1;
+ }
+
+ if (predictedLength <= CommandLineLengthLimit)
+ {
+ finalArguments.Add($"--{PlatformCommandLineProvider.FilterUidOptionKey}");
+ finalArguments.AddRange(lastListOfFailedId);
+ }
+ else
+ {
+ // Use a response file to avoid exceeding command-line length limits.
+ // Write to retryRootFolder (not the per-attempt folder) so it won't be included
+ // in the final results move.
+ string responseFilePath = Path.Combine(retryRootFolder, $"retry-filter-uids-{attemptCount}.rsp");
+ using (IFileStream stream = _fileSystem.NewFileStream(responseFilePath, FileMode.Create, FileAccess.Write))
+ using (var writer = new StreamWriter(stream.Stream))
+ {
+ await writer.WriteAsync($"--{PlatformCommandLineProvider.FilterUidOptionKey}").ConfigureAwait(false);
+ foreach (string uid in lastListOfFailedId)
+ {
+ await writer.WriteAsync($" \"{uid}\"").ConfigureAwait(false);
+ }
+ }
+
+ finalArguments.Add($"@{responseFilePath}");
+ }
+ }
+
#if NET8_0_OR_GREATER
// On net8.0+, we can pass the arguments as a collection directly to ProcessStartInfo.
// When passing the collection, it's expected to be unescaped, so we pass what we have directly.
@@ -351,4 +396,43 @@ private static int GetOptionArgumentIndex(string optionName, string[] executable
index = Array.IndexOf(executableArgs, "--" + optionName);
return index >= 0 ? index : -1;
}
+
+ private static void RemoveOption(List arguments, string optionName)
+ {
+ string longForm = $"--{optionName}";
+ string shortForm = $"-{optionName}";
+
+ // Remove all occurrences since options like --filter-uid can appear multiple times.
+ // Also handle --option=value and --option:value forms produced by the command-line parser.
+ while (true)
+ {
+ int idx = -1;
+ for (int i = 0; i < arguments.Count; i++)
+ {
+ string arg = arguments[i];
+ if (arg == longForm || arg == shortForm
+ || arg.StartsWith(longForm + "=", StringComparison.Ordinal) || arg.StartsWith(longForm + ":", StringComparison.Ordinal)
+ || arg.StartsWith(shortForm + "=", StringComparison.Ordinal) || arg.StartsWith(shortForm + ":", StringComparison.Ordinal))
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx < 0)
+ {
+ break;
+ }
+
+ arguments.RemoveAt(idx);
+
+ // Always remove subsequent non-option arguments (the option's values),
+ // even when the first value was provided inline with = or :, because
+ // multi-arity options (e.g. --filter-uid=1 2) can have trailing values.
+ while (idx < arguments.Count && !arguments[idx].StartsWith('-'))
+ {
+ arguments.RemoveAt(idx);
+ }
+ }
+ }
}
diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/RetryFailedTestsTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/RetryFailedTestsTests.cs
index 229c18e078..1a6e1c36a0 100644
--- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/RetryFailedTestsTests.cs
+++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/RetryFailedTestsTests.cs
@@ -205,6 +205,39 @@ public async Task RetryFailedTests_PassingFromFirstTime_UsingTestTarget_MoveFile
}
}
+ [TestMethod]
+ [DynamicData(nameof(TargetFrameworks.NetForDynamicData), typeof(TargetFrameworks))]
+ public async Task RetryFailedTests_WithPreexistingFilterUid_ReplacesFilterOnRetry(string tfm)
+ {
+ var testHost = TestInfrastructure.TestHost.LocateFrom(AssetFixture.TargetAssetPath, AssetName, tfm);
+ string resultDirectory = Path.Combine(testHost.DirectoryName, Guid.NewGuid().ToString("N"));
+
+ // Use --filter-uid to select tests 1 and 2. Test 1 will fail on first attempt, pass on second.
+ // Test 3 is not in the filter, so it should never run.
+ TestHostResult testHostResult = await testHost.ExecuteAsync(
+ $"--retry-failed-tests 3 --filter-uid 1 --filter-uid 2 --report-trx --results-directory {resultDirectory}",
+ new()
+ {
+ { EnvironmentVariableConstants.TESTINGPLATFORM_TELEMETRY_OPTOUT, "1" },
+ { "METHOD1", "1" },
+ { "RESULTDIR", resultDirectory },
+ },
+ cancellationToken: TestContext.CancellationToken);
+
+ testHostResult.AssertExitCodeIs(ExitCodes.Success);
+ testHostResult.AssertOutputContains("Tests suite completed successfully in 2 attempts");
+ testHostResult.AssertOutputContains("Tests suite failed, total failed tests: 1, exit code: 2, attempt: 1/4");
+
+ // Verify that the retry attempt only ran the failed test (UID 1).
+ // The TRX in the top-level results directory (not under Retries/) is from the last attempt.
+ string[] topLevelTrxFiles = Directory.GetFiles(resultDirectory, "*.trx", SearchOption.TopDirectoryOnly);
+ Assert.HasCount(1, topLevelTrxFiles);
+
+ string trxContent = File.ReadAllText(topLevelTrxFiles[0]);
+ Assert.Contains("TestMethod1", trxContent);
+ Assert.DoesNotContain("TestMethod2", trxContent);
+ }
+
public sealed class TestAssetFixture() : TestAssetFixtureBase(AcceptanceFixture.NuGetGlobalPackagesFolder)
{
public string TargetAssetPath => GetAssetPath(AssetName);
@@ -252,6 +285,7 @@ public sealed class TestAssetFixture() : TestAssetFixtureBase(AcceptanceFixture.
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Extensions.TestFramework;
using Microsoft.Testing.Platform.MSBuild;
+using Microsoft.Testing.Platform.Requests;
using Microsoft.Testing.Platform.Services;
public class Program
@@ -306,46 +340,60 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context)
string resultDir = Environment.GetEnvironmentVariable("RESULTDIR")!;
bool crash = Environment.GetEnvironmentVariable("CRASH") == "1";
+ var uidFilter = (context.Request as TestExecutionRequest)?.Filter as TestNodeUidListFilter;
+
var testMethod1Identifier = new TestMethodIdentifierProperty(string.Empty, string.Empty, "DummyClassName", "TestMethod1", 0, Array.Empty(), string.Empty);
var testMethod2Identifier = new TestMethodIdentifierProperty(string.Empty, string.Empty, "DummyClassName", "TestMethod2", 0, Array.Empty(), string.Empty);
var testMethod3Identifier = new TestMethodIdentifierProperty(string.Empty, string.Empty, "DummyClassName", "TestMethod3", 0, Array.Empty(), string.Empty);
- if (TestMethod1(fail, resultDir, crash))
- {
- await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
- new TestNode() { Uid = "1", DisplayName = "TestMethod1", Properties = new(PassedTestNodeStateProperty.CachedInstance, testMethod1Identifier) }));
- }
- else
+ if (IsIncluded(uidFilter, "1"))
{
- await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
- new TestNode() { Uid = "1", DisplayName = "TestMethod1", Properties = new(new FailedTestNodeStateProperty(), testMethod1Identifier) }));
+ if (TestMethod1(fail, resultDir, crash))
+ {
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
+ new TestNode() { Uid = "1", DisplayName = "TestMethod1", Properties = new(PassedTestNodeStateProperty.CachedInstance, testMethod1Identifier) }));
+ }
+ else
+ {
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
+ new TestNode() { Uid = "1", DisplayName = "TestMethod1", Properties = new(new FailedTestNodeStateProperty(), testMethod1Identifier) }));
+ }
}
- if (TestMethod2(fail, resultDir))
+ if (IsIncluded(uidFilter, "2"))
{
- await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
- new TestNode() { Uid = "2", DisplayName = "TestMethod2", Properties = new(PassedTestNodeStateProperty.CachedInstance, testMethod2Identifier) }));
- }
- else
- {
- await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
- new TestNode() { Uid = "2", DisplayName = "TestMethod2", Properties = new(new FailedTestNodeStateProperty(), testMethod2Identifier) }));
+ if (TestMethod2(fail, resultDir))
+ {
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
+ new TestNode() { Uid = "2", DisplayName = "TestMethod2", Properties = new(PassedTestNodeStateProperty.CachedInstance, testMethod2Identifier) }));
+ }
+ else
+ {
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
+ new TestNode() { Uid = "2", DisplayName = "TestMethod2", Properties = new(new FailedTestNodeStateProperty(), testMethod2Identifier) }));
+ }
}
- if (TestMethod3(fail, resultDir))
- {
- await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
- new TestNode() { Uid = "3", DisplayName = "TestMethod3", Properties = new(PassedTestNodeStateProperty.CachedInstance, testMethod3Identifier) }));
- }
- else
+ if (IsIncluded(uidFilter, "3"))
{
- await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
- new TestNode() { Uid = "3", DisplayName = "TestMethod3", Properties = new(new FailedTestNodeStateProperty(), testMethod3Identifier) }));
+ if (TestMethod3(fail, resultDir))
+ {
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
+ new TestNode() { Uid = "3", DisplayName = "TestMethod3", Properties = new(PassedTestNodeStateProperty.CachedInstance, testMethod3Identifier) }));
+ }
+ else
+ {
+ await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid,
+ new TestNode() { Uid = "3", DisplayName = "TestMethod3", Properties = new(new FailedTestNodeStateProperty(), testMethod3Identifier) }));
+ }
}
context.Complete();
}
+ private static bool IsIncluded(TestNodeUidListFilter? filter, string uid)
+ => filter is null || filter.TestNodeUids.Any(n => n.Value == uid);
+
private bool TestMethod1(bool fail, string resultDir, bool crash)
{
if (crash)