From e7be9cafb13012159819e066097d29402c788156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Tue, 21 Jul 2026 16:29:14 +0200 Subject: [PATCH 1/4] Parallelize test target frameworks Dispatch each target framework through a parallel inner MSBuild invocation and add a barrier-based regression test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed12a139-3ab6-45cc-8f6f-e01edba82614 --- .../Microsoft.DotNet.Arcade.Sdk.Tests.csproj | 3 ++ .../ParallelTestTfmsTests.cs | 47 +++++++++++++++++ .../ParallelTestTfms/ParallelTestTfms.proj | 17 +++++++ .../eng/Parallel/Parallel.targets | 51 +++++++++++++++++++ .../tools/Tests.targets | 26 ++++++---- 5 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 src/Microsoft.DotNet.Arcade.Sdk.Tests/ParallelTestTfmsTests.cs create mode 100644 src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/ParallelTestTfms.proj create mode 100644 src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/Microsoft.DotNet.Arcade.Sdk.Tests.csproj b/src/Microsoft.DotNet.Arcade.Sdk.Tests/Microsoft.DotNet.Arcade.Sdk.Tests.csproj index d8dd48a2ef3..e972ffee777 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk.Tests/Microsoft.DotNet.Arcade.Sdk.Tests.csproj +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/Microsoft.DotNet.Arcade.Sdk.Tests.csproj @@ -20,6 +20,9 @@ + diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/ParallelTestTfmsTests.cs b/src/Microsoft.DotNet.Arcade.Sdk.Tests/ParallelTestTfmsTests.cs new file mode 100644 index 00000000000..391b9da4ad2 --- /dev/null +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/ParallelTestTfmsTests.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.IO; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.DotNet.Arcade.Sdk.Tests +{ + [Collection(TestProjectCollection.Name)] + public class ParallelTestTfmsTests + { + private readonly ITestOutputHelper _output; + private readonly TestProjectFixture _fixture; + + public ParallelTestTfmsTests(ITestOutputHelper output, TestProjectFixture fixture) + { + _output = output; + _fixture = fixture; + } + + [Fact] + public void TargetFrameworksRunInParallel() + { + var app = _fixture.CreateTestApp("ParallelTestTfms"); + Directory.CreateDirectory(Path.Combine(app.WorkingDirectory, "artifacts", "log")); + var startInfo = new ProcessStartInfo + { + FileName = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? "dotnet", + WorkingDirectory = app.WorkingDirectory, + }; + startInfo.ArgumentList.Add("msbuild"); + startInfo.ArgumentList.Add("ParallelTestTfms.proj"); + startInfo.ArgumentList.Add("/t:Test"); + startInfo.ArgumentList.Add("/m:4"); + startInfo.ArgumentList.Add("/nr:false"); + + var exitCode = app.Run(_output, startInfo); + + Assert.Equal(0, exitCode); + Assert.True(File.Exists(Path.Combine(app.WorkingDirectory, "artifacts", "tmp", "test-tfm-a.started"))); + Assert.True(File.Exists(Path.Combine(app.WorkingDirectory, "artifacts", "tmp", "test-tfm-b.started"))); + } + } +} diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/ParallelTestTfms.proj b/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/ParallelTestTfms.proj new file mode 100644 index 00000000000..1c6b70cd6e5 --- /dev/null +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/ParallelTestTfms.proj @@ -0,0 +1,17 @@ + + + test-tfm-a;test-tfm-b + true + true + Parallel + $(MSBuildThisFileDirectory)eng\ + Core + x64 + $(MSBuildThisFileDirectory)$(TargetFramework)\ParallelTestTfms.dll + $(MSBuildThisFileDirectory)artifacts\tmp\ + $(MSBuildThisFileDirectory)artifacts\log\ + $(MSBuildThisFileDirectory)artifacts\TestResults\ + + + + diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets b/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets new file mode 100644 index 00000000000..a96ccac21f8 --- /dev/null +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets @@ -0,0 +1,51 @@ + + + + + + + + + + + + + !File.Exists(file.ItemSpec))) + { + if (DateTime.UtcNow >= deadline) + { + Log.LogError("Timed out waiting for all target frameworks to start."); + break; + } + + Thread.Sleep(100); + } + ]]> + + + + + + + + + <_StartedFile>$(ArtifactsTmpDir)$(TargetFramework).started + + + <_ExpectedStartedFile Include="$(ArtifactsTmpDir)test-tfm-a.started" /> + <_ExpectedStartedFile Include="$(ArtifactsTmpDir)test-tfm-b.started" /> + + + + + + + + + diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets index 1bd2527c060..243bb22eb5d 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets @@ -4,8 +4,8 @@ true - <_GetTestsToRunTarget Condition="'$(TargetFrameworks)' == ''">_InnerGetTestsToRun - <_GetTestsToRunTarget Condition="'$(TargetFrameworks)' != ''">_OuterGetTestsToRun + <_RunTestsTarget Condition="'$(TargetFrameworks)' == ''">_InnerRunTests + <_RunTestsTarget Condition="'$(TargetFrameworks)' != ''">_OuterRunTests Core @@ -32,8 +32,8 @@ x64 - - + + <_TestArchitectureItems Include="$(TestArchitectures)" /> @@ -78,15 +78,21 @@ - + + + <_TargetFramework Include="$(TargetFrameworks)" /> + <_TargetFramework Project="$(MSBuildProjectFile)" /> + <_InnerTestBuild Include="%(_TargetFramework.Project)" + AdditionalProperties="TargetFramework=%(_TargetFramework.Identity)" /> - - + + From 61209ed927dd42dca80cb1f61509ccdcb8844a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Tue, 21 Jul 2026 16:51:58 +0200 Subject: [PATCH 2/4] Remove duplicate test targets import Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed12a139-3ab6-45cc-8f6f-e01edba82614 --- .../testassets/ParallelTestTfms/ParallelTestTfms.proj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/ParallelTestTfms.proj b/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/ParallelTestTfms.proj index 1c6b70cd6e5..13c6554a522 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/ParallelTestTfms.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/ParallelTestTfms.proj @@ -13,5 +13,4 @@ $(MSBuildThisFileDirectory)artifacts\TestResults\ - From ecd92ef0ea87ed0832b3da6ea891aa7c9972feee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 22 Jul 2026 10:19:43 +0200 Subject: [PATCH 3/4] Fix parallel TFM tests on Helix Use writable Helix paths for functional test fixtures, simplify inner-build item construction, and normalize barrier paths across platforms. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed12a139-3ab6-45cc-8f6f-e01edba82614 --- .../Utilities/TestProjectFixture.cs | 9 +++++++-- .../ParallelTestTfms/eng/Parallel/Parallel.targets | 2 +- src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets | 3 +-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/Utilities/TestProjectFixture.cs b/src/Microsoft.DotNet.Arcade.Sdk.Tests/Utilities/TestProjectFixture.cs index ab50f335a26..61e3bd3d490 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk.Tests/Utilities/TestProjectFixture.cs +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/Utilities/TestProjectFixture.cs @@ -20,6 +20,7 @@ public class TestProjectFixture : IDisposable { private readonly ConcurrentQueue _disposables = new ConcurrentQueue(); private readonly string _logOutputDir; + private readonly string _tempRoot; private readonly string _testAssets; private readonly string _boilerPlateDir; @@ -31,7 +32,11 @@ public class TestProjectFixture : IDisposable public TestProjectFixture() { ClearPackages(); - _logOutputDir = GetType().Assembly.GetCustomAttributes().Single(m => m.Key == "LogOutputDir").Value; + var helixWorkItemRoot = Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT"); + _logOutputDir = helixWorkItemRoot == null + ? GetType().Assembly.GetCustomAttributes().Single(m => m.Key == "LogOutputDir").Value + : Path.Combine(helixWorkItemRoot, "test-logs"); + _tempRoot = helixWorkItemRoot ?? Path.GetTempPath(); _testAssets = Path.Combine(AppContext.BaseDirectory, "testassets"); _boilerPlateDir = Path.Combine(_testAssets, "boilerplate"); } @@ -40,7 +45,7 @@ public TestApp CreateTestApp(string name) { var testAppFiles = Path.Combine(_testAssets, name); var instanceName = Path.GetRandomFileName(); - var tempDir = Path.Combine(Path.GetTempPath(), "arcade", instanceName); + var tempDir = Path.Combine(_tempRoot, "arcade", instanceName); var app = new TestApp(tempDir, _logOutputDir, new[] { testAppFiles, _boilerPlateDir }); _disposables.Enqueue(app); return app; diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets b/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets index a96ccac21f8..cbf1ae9dc6e 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets @@ -14,7 +14,7 @@ !File.Exists(file.ItemSpec))) + while (Files.Any(file => !File.Exists(file.ItemSpec.Replace('\\', Path.DirectorySeparatorChar)))) { if (DateTime.UtcNow >= deadline) { diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets b/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets index 243bb22eb5d..56384e3d89e 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/Tests.targets @@ -85,8 +85,7 @@ <_TargetFramework Include="$(TargetFrameworks)" /> - <_TargetFramework Project="$(MSBuildProjectFile)" /> - <_InnerTestBuild Include="%(_TargetFramework.Project)" + <_InnerTestBuild Include="$(MSBuildProjectFile)" AdditionalProperties="TargetFramework=%(_TargetFramework.Identity)" /> Date: Wed, 22 Jul 2026 12:27:34 +0200 Subject: [PATCH 4/4] Harden parallel TFM test fixtures Treat empty Helix roots as unset and make the fake test runner produce and track its declared stdout artifact. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed12a139-3ab6-45cc-8f6f-e01edba82614 --- .../ParallelTestTfmsTests.cs | 1 - .../Utilities/TestProjectFixture.cs | 1 + .../testassets/ParallelTestTfms/eng/Parallel/Parallel.targets | 4 +++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/ParallelTestTfmsTests.cs b/src/Microsoft.DotNet.Arcade.Sdk.Tests/ParallelTestTfmsTests.cs index 391b9da4ad2..2f4c45d07a3 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk.Tests/ParallelTestTfmsTests.cs +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/ParallelTestTfmsTests.cs @@ -25,7 +25,6 @@ public ParallelTestTfmsTests(ITestOutputHelper output, TestProjectFixture fixtur public void TargetFrameworksRunInParallel() { var app = _fixture.CreateTestApp("ParallelTestTfms"); - Directory.CreateDirectory(Path.Combine(app.WorkingDirectory, "artifacts", "log")); var startInfo = new ProcessStartInfo { FileName = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? "dotnet", diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/Utilities/TestProjectFixture.cs b/src/Microsoft.DotNet.Arcade.Sdk.Tests/Utilities/TestProjectFixture.cs index 61e3bd3d490..d8d2096ae89 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk.Tests/Utilities/TestProjectFixture.cs +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/Utilities/TestProjectFixture.cs @@ -33,6 +33,7 @@ public TestProjectFixture() { ClearPackages(); var helixWorkItemRoot = Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT"); + helixWorkItemRoot = string.IsNullOrWhiteSpace(helixWorkItemRoot) ? null : helixWorkItemRoot; _logOutputDir = helixWorkItemRoot == null ? GetType().Assembly.GetCustomAttributes().Single(m => m.Key == "LogOutputDir").Value : Path.Combine(helixWorkItemRoot, "test-logs"); diff --git a/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets b/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets index cbf1ae9dc6e..1ff259eb1f6 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets +++ b/src/Microsoft.DotNet.Arcade.Sdk.Tests/testassets/ParallelTestTfms/eng/Parallel/Parallel.targets @@ -41,11 +41,13 @@ <_ExpectedStartedFile Include="$(ArtifactsTmpDir)test-tfm-a.started" /> <_ExpectedStartedFile Include="$(ArtifactsTmpDir)test-tfm-b.started" /> - + + +