Skip to content

Commit 94da55e

Browse files
EvangelinkCopilotCopilot
authored
Add acceptance test combining --retry-failed-tests with --treenode-filter (#8405)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 066dc85 commit 94da55e

1 file changed

Lines changed: 81 additions & 6 deletions

File tree

test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/RetryFailedTestsTests.cs

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,54 @@ public async Task RetryFailedTests_WithPreexistingFilterUid_ReplacesFilterOnRetr
262262
Assert.DoesNotContain("TestMethod2", trxContent);
263263
}
264264

265+
[TestMethod]
266+
[DynamicData(nameof(TargetFrameworks.NetForDynamicData), typeof(TargetFrameworks))]
267+
public async Task RetryFailedTests_WithPreexistingTreeNodeFilter_ReplacesFilterOnRetry(string tfm)
268+
{
269+
var testHost = TestInfrastructure.TestHost.LocateFrom(AssetFixture.TargetAssetPath, AssetName, tfm);
270+
string resultDirectory = Path.Combine(testHost.DirectoryName, Guid.NewGuid().ToString("N"));
271+
272+
// Use --treenode-filter to select tests TestMethod1 and TestMethod2. The filter intentionally starts
273+
// with '/' because TreeNodeFilter expressions are matched against slash-prefixed node paths such as
274+
// '/TestMethod1'. Test 1 will fail on first attempt, pass on second. TestMethod3 is not matched by
275+
// the filter, so it should never run.
276+
// On retry, the orchestrator strips --treenode-filter and replaces it with --filter-uid for the failed
277+
// tests only; this test guards against issue #5673 (Retry + tree node filter must not stack filters).
278+
TestHostResult testHostResult = await testHost.ExecuteAsync(
279+
$"--retry-failed-tests 3 --treenode-filter \"/(TestMethod1|TestMethod2)\" --report-trx --results-directory {resultDirectory}",
280+
new()
281+
{
282+
{ EnvironmentVariableConstants.TESTINGPLATFORM_TELEMETRY_OPTOUT, "1" },
283+
{ "METHOD1", "1" },
284+
{ "RESULTDIR", resultDirectory },
285+
},
286+
cancellationToken: TestContext.CancellationToken);
287+
288+
testHostResult.AssertExitCodeIs(ExitCode.Success);
289+
testHostResult.AssertOutputContains("Tests suite completed successfully in 2 attempts");
290+
testHostResult.AssertOutputContains("Tests suite failed, total failed tests: 1, exit code: 2, attempt: 1/4");
291+
292+
// Verify that the first attempt honored the treenode-filter.
293+
string[] retryTrxFiles = Directory.GetFiles(Path.Combine(resultDirectory, "Retries"), "*.trx", SearchOption.AllDirectories);
294+
Assert.HasCount(1, retryTrxFiles);
295+
296+
string retryTrxContent = File.ReadAllText(retryTrxFiles[0]);
297+
Assert.Contains("TestMethod1", retryTrxContent);
298+
Assert.Contains("TestMethod2", retryTrxContent);
299+
Assert.DoesNotContain("TestMethod3", retryTrxContent);
300+
301+
// Verify that the retry attempt only ran the failed test (TestMethod1) - i.e. the treenode-filter was
302+
// dropped and replaced by --filter-uid 1.
303+
// The TRX in the top-level results directory (not under Retries/) is from the last attempt.
304+
string[] topLevelTrxFiles = Directory.GetFiles(resultDirectory, "*.trx", SearchOption.TopDirectoryOnly);
305+
Assert.HasCount(1, topLevelTrxFiles);
306+
307+
string trxContent = File.ReadAllText(topLevelTrxFiles[0]);
308+
Assert.Contains("TestMethod1", trxContent);
309+
Assert.DoesNotContain("TestMethod2", trxContent);
310+
Assert.DoesNotContain("TestMethod3", trxContent);
311+
}
312+
265313
public sealed class TestAssetFixture() : TestAssetFixtureBase()
266314
{
267315
public string TargetAssetPath => GetAssetPath(AssetName);
@@ -303,8 +351,10 @@ public override (string ID, string Name, string Code) GetAssetsToGenerate() => (
303351
using Microsoft.Testing.Extensions.TrxReport.Abstractions;
304352
using Microsoft.Testing.Platform.Builder;
305353
using Microsoft.Testing.Platform.Capabilities.TestFramework;
354+
using Microsoft.Testing.Platform.Extensions;
306355
using Microsoft.Testing.Platform.Extensions.Messages;
307356
using Microsoft.Testing.Platform.Extensions.TestFramework;
357+
using Microsoft.Testing.Platform.Helpers;
308358
using Microsoft.Testing.Platform.MSBuild;
309359
using Microsoft.Testing.Platform.Requests;
310360
using Microsoft.Testing.Platform.Services;
@@ -314,18 +364,29 @@ public class Program
314364
public static async Task<int> Main(string[] args)
315365
{
316366
ITestApplicationBuilder builder = await TestApplication.CreateBuilderAsync(args);
367+
TreeNodeFilterExtension treeNodeFilterExtension = new();
317368
builder.RegisterTestFramework(
318369
sp => new TestFrameworkCapabilities(new TrxReportCapability()),
319370
(_,__) => new DummyTestFramework());
320371
builder.AddCrashDumpProvider();
321372
builder.AddTrxReportProvider();
322373
builder.AddRetryProvider();
323374
builder.AddMSBuild();
375+
builder.AddTreeNodeFilterService(treeNodeFilterExtension);
324376
using ITestApplication app = await builder.BuildAsync();
325377
return await app.RunAsync();
326378
}
327379
}
328380
381+
public class TreeNodeFilterExtension : IExtension
382+
{
383+
public string Uid => nameof(TreeNodeFilterExtension);
384+
public string Version => "1.0.0";
385+
public string DisplayName => nameof(TreeNodeFilterExtension);
386+
public string Description => nameof(TreeNodeFilterExtension);
387+
public Task<bool> IsEnabledAsync() => Task.FromResult(true);
388+
}
389+
329390
public class TrxReportCapability : ITrxReportCapability
330391
{
331392
bool ITrxReportCapability.IsSupported { get; } = true;
@@ -361,13 +422,15 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context)
361422
string resultDir = Environment.GetEnvironmentVariable("RESULTDIR")!;
362423
bool crash = Environment.GetEnvironmentVariable("CRASH") == "1";
363424
364-
var uidFilter = (context.Request as TestExecutionRequest)?.Filter as TestNodeUidListFilter;
425+
var filter = (context.Request as TestExecutionRequest)?.Filter;
426+
var uidFilter = filter as TestNodeUidListFilter;
427+
var treeNodeFilter = filter as TreeNodeFilter;
365428
366429
var testMethod1Identifier = new TestMethodIdentifierProperty(string.Empty, string.Empty, "DummyClassName", "TestMethod1", 0, Array.Empty<string>(), string.Empty);
367430
var testMethod2Identifier = new TestMethodIdentifierProperty(string.Empty, string.Empty, "DummyClassName", "TestMethod2", 0, Array.Empty<string>(), string.Empty);
368431
var testMethod3Identifier = new TestMethodIdentifierProperty(string.Empty, string.Empty, "DummyClassName", "TestMethod3", 0, Array.Empty<string>(), string.Empty);
369432
370-
if (IsIncluded(uidFilter, "1"))
433+
if (IsIncluded(uidFilter, treeNodeFilter, "1", "TestMethod1"))
371434
{
372435
if (TestMethod1(fail, resultDir, crash))
373436
{
@@ -381,7 +444,7 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context)
381444
}
382445
}
383446
384-
if (IsIncluded(uidFilter, "2"))
447+
if (IsIncluded(uidFilter, treeNodeFilter, "2", "TestMethod2"))
385448
{
386449
if (TestMethod2(fail, resultDir))
387450
{
@@ -395,7 +458,7 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context)
395458
}
396459
}
397460
398-
if (IsIncluded(uidFilter, "3"))
461+
if (IsIncluded(uidFilter, treeNodeFilter, "3", "TestMethod3"))
399462
{
400463
if (TestMethod3(fail, resultDir))
401464
{
@@ -412,8 +475,20 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context)
412475
context.Complete();
413476
}
414477
415-
private static bool IsIncluded(TestNodeUidListFilter? filter, string uid)
416-
=> filter is null || filter.TestNodeUids.Any(n => n.Value == uid);
478+
private static bool IsIncluded(TestNodeUidListFilter? uidFilter, TreeNodeFilter? treeNodeFilter, string uid, string displayName)
479+
{
480+
if (uidFilter is not null && !uidFilter.TestNodeUids.Any(n => n.Value == uid))
481+
{
482+
return false;
483+
}
484+
485+
if (treeNodeFilter is not null && !treeNodeFilter.MatchesFilter("/" + displayName, new PropertyBag()))
486+
{
487+
return false;
488+
}
489+
490+
return true;
491+
}
417492
418493
private bool TestMethod1(bool fail, string resultDir, bool crash)
419494
{

0 commit comments

Comments
 (0)