diff --git a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks.UnitTests/Microsoft.NET.Build.Extensions.Tasks.UnitTests.csproj b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks.UnitTests/Microsoft.NET.Build.Extensions.Tasks.UnitTests.csproj index 109d01102759..3e0102c21879 100644 --- a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks.UnitTests/Microsoft.NET.Build.Extensions.Tasks.UnitTests.csproj +++ b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks.UnitTests/Microsoft.NET.Build.Extensions.Tasks.UnitTests.csproj @@ -24,11 +24,12 @@ <ItemGroup> <ProjectReference Include="..\Microsoft.NET.Build.Extensions.Tasks\Microsoft.NET.Build.Extensions.Tasks.csproj" /> + <ProjectReference Include="..\..\..\test\Microsoft.NET.TestFramework\Microsoft.NET.TestFramework.csproj" /> </ItemGroup> <ItemGroup> <Compile Include="**\*.cs" /> - <Compile Include="..\..\..\test\Common\Program.UnitTests.cs" /> + <Compile Include="..\..\..\test\Common\Program.cs" /> <Compile Include="..\Microsoft.NET.Build.Tasks.UnitTests\Mocks\MockBuildEngine.cs" /> <Compile Include="..\Microsoft.NET.Build.Tasks.UnitTests\Mocks\MockTaskItem.cs" /> </ItemGroup> diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Microsoft.NET.Build.Tasks.UnitTests.csproj b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Microsoft.NET.Build.Tasks.UnitTests.csproj index 263fb5a44b49..7652a0865022 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Microsoft.NET.Build.Tasks.UnitTests.csproj +++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/Microsoft.NET.Build.Tasks.UnitTests.csproj @@ -35,7 +35,7 @@ <ItemGroup> <Compile Include="**\*.cs" /> - <Compile Include="..\..\..\test\Common\Program.UnitTests.cs" /> + <Compile Include="..\..\..\test\Common\Program.cs" /> </ItemGroup> <ItemGroup> diff --git a/test/Common/Program.UnitTests.cs b/test/Common/Program.UnitTests.cs deleted file mode 100644 index 31f308383400..000000000000 --- a/test/Common/Program.UnitTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -partial class Program -{ - public static int Main(string[] args) - { - var newArgs = args.ToList(); - - // Help argument needs to be the first one to xunit, so don't insert assembly location in that case - if (args.Any(arg => arg.Equals("-help", StringComparison.CurrentCultureIgnoreCase) || arg.Equals("/?"))) - { - newArgs.Insert(0, "/?"); - } - else - { - newArgs.Insert(0, typeof(Program).Assembly.Location); - } - - int returnCode = Xunit.ConsoleClient.Program.Main(newArgs.ToArray()); - - return returnCode; - } -} diff --git a/test/Common/Program.cs b/test/Common/Program.cs index 8b54b8bd40a0..d395586c7655 100644 --- a/test/Common/Program.cs +++ b/test/Common/Program.cs @@ -1,5 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.NET.TestFramework; +using Microsoft.NET.TestFramework.Commands; #pragma warning disable SA1205 // Partial elements should declare access partial class Program diff --git a/test/HelixTasks/SDKCustomCreateXUnitWorkItemsWithTestExclusion.cs b/test/HelixTasks/SDKCustomCreateXUnitWorkItemsWithTestExclusion.cs index 87f95c449cb7..65f2b992a07d 100644 --- a/test/HelixTasks/SDKCustomCreateXUnitWorkItemsWithTestExclusion.cs +++ b/test/HelixTasks/SDKCustomCreateXUnitWorkItemsWithTestExclusion.cs @@ -104,6 +104,14 @@ private async Task<List<ITaskItem>> PrepareWorkItem(ITaskItem xunitProject) xunitProject.TryGetMetadata("ExcludeAdditionalParameters", out string ExcludeAdditionalParameters); xunitProject.TryGetMetadata("Arguments", out string arguments); + TimeSpan timeout = TimeSpan.FromMinutes(5); + if (!string.IsNullOrEmpty(XUnitWorkItemTimeout)) + { + if (!TimeSpan.TryParse(XUnitWorkItemTimeout, out timeout)) + { + Log.LogWarning($"Invalid value \"{XUnitWorkItemTimeout}\" provided for XUnitWorkItemTimeout; falling back to default value of \"00:05:00\" (5 minutes)"); + } + } string assemblyName = Path.GetFileName(targetPath); @@ -142,24 +150,21 @@ private async Task<List<ITaskItem>> PrepareWorkItem(ITaskItem xunitProject) var partitionedWorkItem = new List<ITaskItem>(); foreach (var assemblyPartitionInfo in assemblyPartitionInfos) { - string command = $"{driver} exec {assemblyName} {testExecutionDirectory} {msbuildAdditionalSdkResolverFolder} {(XUnitArguments != null ? " " + XUnitArguments : "")} -xml testResults.xml {assemblyPartitionInfo.ClassListArgumentString} {arguments}"; + string command; if (netFramework) { var testFilter = string.IsNullOrEmpty(assemblyPartitionInfo.ClassListArgumentString) ? "" : $"--filter \"{assemblyPartitionInfo.ClassListArgumentString}\""; - command = $"{driver} test {assemblyName} {testExecutionDirectory} {msbuildAdditionalSdkResolverFolder} {(XUnitArguments != null ? " " + XUnitArguments : "")} --results-directory .\\ --logger trx {testFilter}"; + command = $"{driver} test {assemblyName} -e HELIX_WORK_ITEM_TIMEOUT={timeout} {testExecutionDirectory} {msbuildAdditionalSdkResolverFolder} " + + $"{(XUnitArguments != null ? " " + XUnitArguments : "")} --results-directory .\\ --logger trx {testFilter}"; } - - Log.LogMessage($"Creating work item with properties Identity: {assemblyName}, PayloadDirectory: {publishDirectory}, Command: {command}"); - - TimeSpan timeout = TimeSpan.FromMinutes(5); - if (!string.IsNullOrEmpty(XUnitWorkItemTimeout)) + else { - if (!TimeSpan.TryParse(XUnitWorkItemTimeout, out timeout)) - { - Log.LogWarning($"Invalid value \"{XUnitWorkItemTimeout}\" provided for XUnitWorkItemTimeout; falling back to default value of \"00:05:00\" (5 minutes)"); - } + command = $"{driver} exec {assemblyName} -e HELIX_WORK_ITEM_TIMEOUT={timeout} {testExecutionDirectory} {msbuildAdditionalSdkResolverFolder} " + + $"{(XUnitArguments != null ? " " + XUnitArguments : "")} -xml testResults.xml {assemblyPartitionInfo.ClassListArgumentString} {arguments}"; } + Log.LogMessage($"Creating work item with properties Identity: {assemblyName}, PayloadDirectory: {publishDirectory}, Command: {command}"); + partitionedWorkItem.Add(new Microsoft.Build.Utilities.TaskItem(assemblyPartitionInfo.DisplayName + testIdentityDifferentiator, new Dictionary<string, string>() { { "Identity", assemblyPartitionInfo.DisplayName + testIdentityDifferentiator}, diff --git a/test/Microsoft.NET.TestFramework/TestCommandLine.cs b/test/Microsoft.NET.TestFramework/TestCommandLine.cs index f873ca8fab23..2519a063e819 100644 --- a/test/Microsoft.NET.TestFramework/TestCommandLine.cs +++ b/test/Microsoft.NET.TestFramework/TestCommandLine.cs @@ -29,7 +29,9 @@ public class TestCommandLine public string MsbuildAdditionalSdkResolverFolder { get; set; } - public List<string> TestConfigFiles { get; private set; } = new List<string>(); + public List<(string name, string value)> EnvironmentVariables { get; set; } = []; + + public List<string> TestConfigFiles { get; private set; } = []; public HashSet<string> TestListsToRun { get; private set; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase); @@ -91,6 +93,10 @@ public static TestCommandLine Parse(string[] args) { ret.TestListsToRun.Add(argStack.Pop()); } + else if (arg.Equals("-e", StringComparison.CurrentCultureIgnoreCase)) + { + ret.EnvironmentVariables.Add(ParseEnvironmentVariableArg(argStack.Pop())); + } else if (arg.Equals("-showSdkInfo", StringComparison.CurrentCultureIgnoreCase)) { ret.ShowSdkInfo = true; @@ -125,6 +131,17 @@ public static TestCommandLine Parse(string[] args) return ret; } + private static (string name, string value) ParseEnvironmentVariableArg(string arg) + { + var i = arg.IndexOf('='); + if (i <= 0) + { + throw new ArgumentException($"Invalid environment variable specification (expected 'name=value'): '{arg}'"); + } + + return (arg.Substring(0, i), arg.Substring(i + 1)); + } + public List<string> GetXunitArgsFromTestConfig() { List<TestSpecifier> testsToSkip = new(); diff --git a/test/Microsoft.NET.TestFramework/TestContext.cs b/test/Microsoft.NET.TestFramework/TestContext.cs index f7aae8e04cd4..93a5a47e1038 100644 --- a/test/Microsoft.NET.TestFramework/TestContext.cs +++ b/test/Microsoft.NET.TestFramework/TestContext.cs @@ -74,6 +74,11 @@ public static void Initialize(TestCommandLine commandLine) CommandLoggingContext.SetVerbose(true); Reporter.Reset(); + foreach (var (name, value) in commandLine.EnvironmentVariables) + { + Environment.SetEnvironmentVariable(name, value); + } + Environment.SetEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0"); // Reset this environment variable so that if the dotnet under test is different than the diff --git a/test/dotnet-watch.Tests/Utilities/AwaitableProcess.cs b/test/dotnet-watch.Tests/Utilities/AwaitableProcess.cs index 200c21a0e5fa..77da26b85b4c 100644 --- a/test/dotnet-watch.Tests/Utilities/AwaitableProcess.cs +++ b/test/dotnet-watch.Tests/Utilities/AwaitableProcess.cs @@ -8,6 +8,10 @@ namespace Microsoft.DotNet.Watcher.Tools { internal class AwaitableProcess : IDisposable { + // cancel just before we hit timeout used on CI (XUnitWorkItemTimeout value in sdk\test\UnitTests.proj) + private static readonly TimeSpan s_timeout = Environment.GetEnvironmentVariable("HELIX_WORK_ITEM_TIMEOUT") is { } value + ? TimeSpan.Parse(value).Subtract(TimeSpan.FromSeconds(10)) : TimeSpan.FromMinutes(1); + private readonly object _testOutputLock = new(); private Process _process; @@ -70,8 +74,7 @@ public async Task<string> GetOutputLineAsync(Predicate<string> success, Predicat { using var cancellationOnFailure = new CancellationTokenSource(); - // cancel just before we hit 2 minute time out used on CI (sdk\test\UnitTests.proj) - cancellationOnFailure.CancelAfter(TimeSpan.FromSeconds(110)); + cancellationOnFailure.CancelAfter(s_timeout); var failedLineCount = 0; while (!_source.Completion.IsCompleted && failedLineCount == 0)