From 004679f618878ea43e3fcedbf2ca28aae7b2025d Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 20 Jan 2026 15:43:55 +0100 Subject: [PATCH 01/10] Respect working directory of 'dotnet test' when determining results directory --- .../Builder/TestApplication.cs | 4 +++- .../Configurations/AggregatedConfiguration.cs | 4 +++- .../Configurations/ConfigurationManager.cs | 5 +++-- .../Helpers/EnvironmentVariableConstants.cs | 1 + .../Hosts/TestHostBuilder.cs | 2 +- .../Adapter_ExecuteRequestAsyncTests.cs | 2 +- .../AggregatedConfigurationTests.cs | 16 ++++++++-------- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs index c43941a379..1f00df5d19 100644 --- a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs +++ b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs @@ -310,7 +310,9 @@ private static ApplicationLoggingState CreateFileLoggerIfDiagnosticIsEnabled( } // Set the directory to the default test result directory - string directory = Path.Combine(testApplicationModuleInfo.GetCurrentTestApplicationDirectory(), AggregatedConfiguration.DefaultTestResultFolderName); + string? effectiveWorkingDirectory = environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY); + effectiveWorkingDirectory ??= testApplicationModuleInfo.GetCurrentTestApplicationDirectory(); + string directory = Path.Combine(effectiveWorkingDirectory, AggregatedConfiguration.DefaultTestResultFolderName); bool customDirectory = false; if (result.TryGetOptionArgumentList(PlatformCommandLineProvider.ResultDirectoryOptionKey, out string[]? resultDirectoryArg)) diff --git a/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs b/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs index ba1f697d5e..315af1d3e2 100644 --- a/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs +++ b/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs @@ -12,12 +12,14 @@ internal sealed class AggregatedConfiguration( IConfigurationProvider[] configurationProviders, ITestApplicationModuleInfo testApplicationModuleInfo, IFileSystem fileSystem, + IEnvironment environment, CommandLineParseResult commandLineParseResult) : IConfiguration { public const string DefaultTestResultFolderName = "TestResults"; private readonly IConfigurationProvider[] _configurationProviders = configurationProviders; private readonly ITestApplicationModuleInfo _testApplicationModuleInfo = testApplicationModuleInfo; private readonly IFileSystem _fileSystem = fileSystem; + private readonly IEnvironment _environment = environment; private readonly CommandLineParseResult _commandLineParseResult = commandLineParseResult; private string? _resultsDirectory; private string? _currentWorkingDirectory; @@ -94,7 +96,7 @@ private string GetResultsDirectoryCore(CommandLineParseResult commandLineParseRe // If not specified by command line, then use the configuration providers. // And finally fallback to DefaultTestResultFolderName relative to the current working directory. return CalculateFromConfigurationProviders(PlatformConfigurationConstants.PlatformResultDirectory) - ?? Path.Combine(this[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]!, DefaultTestResultFolderName); + ?? Path.Combine(_environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY) ?? this[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]!, DefaultTestResultFolderName); } private string GetCurrentWorkingDirectoryCore() diff --git a/src/Platform/Microsoft.Testing.Platform/Configurations/ConfigurationManager.cs b/src/Platform/Microsoft.Testing.Platform/Configurations/ConfigurationManager.cs index bf9b7a51e6..58bd22777f 100644 --- a/src/Platform/Microsoft.Testing.Platform/Configurations/ConfigurationManager.cs +++ b/src/Platform/Microsoft.Testing.Platform/Configurations/ConfigurationManager.cs @@ -12,11 +12,12 @@ namespace Microsoft.Testing.Platform.Configurations; -internal sealed class ConfigurationManager(IFileSystem fileSystem, ITestApplicationModuleInfo testApplicationModuleInfo) : IConfigurationManager +internal sealed class ConfigurationManager(IFileSystem fileSystem, ITestApplicationModuleInfo testApplicationModuleInfo, IEnvironment environment) : IConfigurationManager { private readonly List> _configurationSources = []; private readonly IFileSystem _fileSystem = fileSystem; private readonly ITestApplicationModuleInfo _testApplicationModuleInfo = testApplicationModuleInfo; + private readonly IEnvironment _environment = environment; public void AddConfigurationSource(Func source) => _configurationSources.Add(source); @@ -58,6 +59,6 @@ internal async Task BuildAsync(IFileLoggerProvider? syncFileLogg return defaultJsonConfiguration is null ? throw new InvalidOperationException(PlatformResources.ConfigurationManagerCannotFindDefaultJsonConfigurationErrorMessage) - : new AggregatedConfiguration([.. configurationProviders.OrderBy(x => x.Order).Select(x => x.ConfigurationProvider)], _testApplicationModuleInfo, _fileSystem, commandLineParseResult); + : new AggregatedConfiguration([.. configurationProviders.OrderBy(x => x.Order).Select(x => x.ConfigurationProvider)], _testApplicationModuleInfo, _fileSystem, _environment, commandLineParseResult); } } diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/EnvironmentVariableConstants.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/EnvironmentVariableConstants.cs index e496cbc3af..4cecf803a3 100644 --- a/src/Platform/Microsoft.Testing.Platform/Helpers/EnvironmentVariableConstants.cs +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/EnvironmentVariableConstants.cs @@ -36,6 +36,7 @@ internal static class EnvironmentVariableConstants // dotnet test public const string TESTINGPLATFORM_DOTNETTEST_EXECUTIONID = nameof(TESTINGPLATFORM_DOTNETTEST_EXECUTIONID); + public const string DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY = nameof(DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY); // Unhandled Exception public const string TESTINGPLATFORM_EXIT_PROCESS_ON_UNHANDLED_EXCEPTION = nameof(TESTINGPLATFORM_EXIT_PROCESS_ON_UNHANDLED_EXCEPTION); diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs index beb17eeeea..227f07a355 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs @@ -43,7 +43,7 @@ internal sealed class TestHostBuilder(IFileSystem fileSystem, IRuntimeFeature ru public ITestHostManager TestHost { get; } = new TestHostManager(); - public IConfigurationManager Configuration { get; } = new ConfigurationManager(fileSystem, testApplicationModuleInfo); + public IConfigurationManager Configuration { get; } = new ConfigurationManager(fileSystem, testApplicationModuleInfo, environment); public ILoggingManager Logging { get; } = new LoggingManager(); diff --git a/test/UnitTests/MSTest.Engine.UnitTests/Adapter_ExecuteRequestAsyncTests.cs b/test/UnitTests/MSTest.Engine.UnitTests/Adapter_ExecuteRequestAsyncTests.cs index efd47c4bf9..00f5d71825 100644 --- a/test/UnitTests/MSTest.Engine.UnitTests/Adapter_ExecuteRequestAsyncTests.cs +++ b/test/UnitTests/MSTest.Engine.UnitTests/Adapter_ExecuteRequestAsyncTests.cs @@ -109,7 +109,7 @@ public Services() ServiceProvider.AddService(new LoggerFactory()); ServiceProvider.AddService(new FakeClock()); ServiceProvider.AddService(new SystemTask()); - ServiceProvider.AddService(new AggregatedConfiguration([], new CurrentTestApplicationModuleInfo(new SystemEnvironment(), new SystemProcessHandler()), new SystemFileSystem(), new(null, [], []))); + ServiceProvider.AddService(new AggregatedConfiguration([], new CurrentTestApplicationModuleInfo(new SystemEnvironment(), new SystemProcessHandler()), new SystemFileSystem(), new SystemEnvironment(), new(null, [], []))); } public MessageBus MessageBus { get; } diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs index 284dd62341..a07b34082e 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs @@ -33,7 +33,7 @@ public void IndexerTest_DirectoryNotSetAndNoConfigurationProviders(string key) _ => throw ApplicationStateGuard.Unreachable(), }; - Assert.AreEqual(expected, new AggregatedConfiguration([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new(null, [], []))[key]); + Assert.AreEqual(expected, new AggregatedConfiguration([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new SystemEnvironment(), new(null, [], []))[key]); } [TestMethod] @@ -43,7 +43,7 @@ public void IndexerTest_DirectoryNotSetButConfigurationProvidersPresent_Director { Mock mockProvider = new(); - AggregatedConfiguration aggregatedConfiguration = new([mockProvider.Object], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new(null, [], [])); + AggregatedConfiguration aggregatedConfiguration = new([mockProvider.Object], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new SystemEnvironment(), new(null, [], [])); Assert.IsNull(aggregatedConfiguration[key]); } @@ -53,21 +53,21 @@ public void IndexerTest_DirectoryNotSetButConfigurationProvidersPresent_Director [DataRow(PlatformConfigurationConstants.PlatformTestHostWorkingDirectory)] public void IndexerTest_DirectoryNotSetButConfigurationProvidersPresent_DirectoryIsNotNull(string key) { - AggregatedConfiguration aggregatedConfiguration = new([new FakeConfigurationProvider(ExpectedPath)], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new(null, [], [])); + AggregatedConfiguration aggregatedConfiguration = new([new FakeConfigurationProvider(ExpectedPath)], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new SystemEnvironment(), new(null, [], [])); Assert.AreEqual(ExpectedPath, aggregatedConfiguration[key]); } [TestMethod] public void IndexerTest_ResultDirectorySet_DirectoryIsNotNull() { - AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); + AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new SystemEnvironment(), new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); Assert.AreEqual(ExpectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); } [TestMethod] public void IndexerTest_CurrentWorkingDirectorySet_DirectoryIsNotNull() { - AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new(null, [], [])); + AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new SystemEnvironment(), new(null, [], [])); aggregatedConfiguration.SetCurrentWorkingDirectory(ExpectedPath); Assert.AreEqual(ExpectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]); @@ -86,7 +86,7 @@ public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_Results Mock mockFileLogger = new(); mockFileLogger.Setup(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(It.IsAny())).Callback(() => { }); - AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); + AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, new SystemEnvironment(), new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); await aggregatedConfiguration.CheckTestResultsDirectoryOverrideAndCreateItAsync(mockFileLogger.Object); mockFileSystem.Verify(x => x.CreateDirectory(ExpectedPath), Times.Once); @@ -108,7 +108,7 @@ public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_Results Mock mockFileLogger = new(); mockFileLogger.Setup(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(It.IsAny())).Callback(() => { }); - AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); + AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, new SystemEnvironment(), new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); await aggregatedConfiguration.CheckTestResultsDirectoryOverrideAndCreateItAsync(mockFileLogger.Object); mockFileSystem.Verify(x => x.CreateDirectory(ExpectedPath), Times.Once); @@ -130,7 +130,7 @@ public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_Results Mock mockFileLogger = new(); mockFileLogger.Setup(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(It.IsAny())).Callback(() => { }); - AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, new(null, [], [])); + AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, new SystemEnvironment(), new(null, [], [])); await aggregatedConfiguration.CheckTestResultsDirectoryOverrideAndCreateItAsync(mockFileLogger.Object); string expectedPath = "a" + Path.DirectorySeparatorChar + "b" + Path.DirectorySeparatorChar + "TestResults"; From d76e16cd4bef38e049107e15e587639c4982869d Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 20 Jan 2026 15:53:13 +0100 Subject: [PATCH 02/10] Cleanup dead code --- .../Services/CurrentTestApplicationModuleInfo.cs | 2 +- .../Microsoft.Testing.Platform/Services/ExecutableInfo.cs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/Services/CurrentTestApplicationModuleInfo.cs b/src/Platform/Microsoft.Testing.Platform/Services/CurrentTestApplicationModuleInfo.cs index ebd46b602a..0da6703115 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/CurrentTestApplicationModuleInfo.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/CurrentTestApplicationModuleInfo.cs @@ -127,6 +127,6 @@ public ExecutableInfo GetCurrentExecutableInfo() _ => commandLineArguments, }; - return new(GetProcessPath(), arguments, GetCurrentTestApplicationDirectory()); + return new ExecutableInfo(GetProcessPath(), arguments); } } diff --git a/src/Platform/Microsoft.Testing.Platform/Services/ExecutableInfo.cs b/src/Platform/Microsoft.Testing.Platform/Services/ExecutableInfo.cs index 17b6023e2b..b638041eb1 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/ExecutableInfo.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/ExecutableInfo.cs @@ -3,14 +3,12 @@ namespace Microsoft.Testing.Platform.Services; -internal sealed class ExecutableInfo(string filePath, IEnumerable arguments, string workspace) +internal sealed class ExecutableInfo(string filePath, IEnumerable arguments) { public string FilePath { get; } = filePath; public IEnumerable Arguments { get; } = arguments; - public string Workspace { get; } = workspace; - public override string ToString() - => $"Process: {FilePath}, Arguments: {string.Join(' ', Arguments)}, Workspace: {Workspace}"; + => $"Process: {FilePath}, Arguments: {string.Join(' ', Arguments)}"; } From 5fd14ec41bdfd1ff34216a63914f0e504adc2157 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 20 Jan 2026 15:55:28 +0100 Subject: [PATCH 03/10] Delete dead code --- .../Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs | 5 ----- .../TestHostControllers/TestHostControllersManager.cs | 8 -------- 2 files changed, 13 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs index 227f07a355..a9787c3f6a 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs @@ -481,11 +481,6 @@ await LogTestHostCreatedAsync( policiesService.ProcessRole = TestProcessRole.TestHost; await proxyOutputDevice.HandleProcessRoleAsync(TestProcessRole.TestHost, testApplicationCancellationTokenSource.CancellationToken).ConfigureAwait(false); - // Setup the test host working folder. - // Out of the test host controller extension the current working directory is the test host working directory. - string? currentWorkingDirectory = configuration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]; - ApplicationStateGuard.Ensure(currentWorkingDirectory is not null); - testHostControllerInfo.IsCurrentProcessTestHostController = false; // If we're under test controllers and currently we're inside the started test host we connect to the out of process diff --git a/src/Platform/Microsoft.Testing.Platform/TestHostControllers/TestHostControllersManager.cs b/src/Platform/Microsoft.Testing.Platform/TestHostControllers/TestHostControllersManager.cs index a597b3aba9..77d9c3d213 100644 --- a/src/Platform/Microsoft.Testing.Platform/TestHostControllers/TestHostControllersManager.cs +++ b/src/Platform/Microsoft.Testing.Platform/TestHostControllers/TestHostControllersManager.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Microsoft.Testing.Platform.Configurations; using Microsoft.Testing.Platform.Extensions; using Microsoft.Testing.Platform.Extensions.TestHostControllers; using Microsoft.Testing.Platform.Helpers; @@ -108,13 +107,6 @@ public void AddDataConsumer(CompositeExtensionFactory compositeServiceFact internal async Task BuildAsync(ServiceProvider serviceProvider) { - // For now the test host working directory and the current working directory are the same. - // In future we could move the test host in a different directory for instance in case of - // the need to rewrite binary files. If we don't move files are locked by ourself. - var aggregatedConfiguration = (AggregatedConfiguration)serviceProvider.GetConfiguration(); - string? currentWorkingDirectory = aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]; - ApplicationStateGuard.Ensure(currentWorkingDirectory is not null); - List<(ITestHostEnvironmentVariableProvider TestHostEnvironmentVariableProvider, int RegistrationOrder)> environmentVariableProviders = []; foreach (Func environmentVariableProviderFactory in _environmentVariableProviderFactories) { From 26cf7e36c9c4cacbfbc886d83012b8719fd939a5 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 20 Jan 2026 16:36:41 +0100 Subject: [PATCH 04/10] Fix build --- .../Configuration/ConfigurationManagerTests.cs | 12 ++++++------ .../TestApplicationBuilderTests.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/ConfigurationManagerTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/ConfigurationManagerTests.cs index 063e9b5116..6304368516 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/ConfigurationManagerTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/ConfigurationManagerTests.cs @@ -27,7 +27,7 @@ public async ValueTask GetConfigurationValueFromJson(string jsonFileConfig, stri fileSystem.Setup(x => x.NewFileStream(It.IsAny(), FileMode.Open, FileAccess.Read)) .Returns(new MemoryFileStream(Encoding.UTF8.GetBytes(jsonFileConfig))); CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler()); - ConfigurationManager configurationManager = new(fileSystem.Object, testApplicationModuleInfo); + ConfigurationManager configurationManager = new(fileSystem.Object, testApplicationModuleInfo, new SystemEnvironment()); configurationManager.AddConfigurationSource(() => new JsonConfigurationSource(testApplicationModuleInfo, fileSystem.Object, null)); IConfiguration configuration = await configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List(), [])); Assert.AreEqual(result, configuration[key], $"Expected '{result}' found '{configuration[key]}'"); @@ -57,7 +57,7 @@ public async ValueTask InvalidJson_Fail() fileSystem.Setup(x => x.ExistFile(It.IsAny())).Returns(true); fileSystem.Setup(x => x.NewFileStream(It.IsAny(), FileMode.Open, FileAccess.Read)).Returns(() => new MemoryFileStream(Encoding.UTF8.GetBytes(string.Empty))); CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler()); - ConfigurationManager configurationManager = new(fileSystem.Object, testApplicationModuleInfo); + ConfigurationManager configurationManager = new(fileSystem.Object, testApplicationModuleInfo, new SystemEnvironment()); configurationManager.AddConfigurationSource(() => new JsonConfigurationSource(testApplicationModuleInfo, fileSystem.Object, null)); @@ -87,7 +87,7 @@ public async ValueTask GetConfigurationValueFromJsonWithFileLoggerProvider(strin loggerProviderMock.Setup(x => x.CreateLogger(It.IsAny())).Returns(loggerMock.Object); CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler()); - ConfigurationManager configurationManager = new(fileSystem.Object, testApplicationModuleInfo); + ConfigurationManager configurationManager = new(fileSystem.Object, testApplicationModuleInfo, new SystemEnvironment()); configurationManager.AddConfigurationSource(() => new JsonConfigurationSource(testApplicationModuleInfo, fileSystem.Object, null)); @@ -101,7 +101,7 @@ public async ValueTask GetConfigurationValueFromJsonWithFileLoggerProvider(strin public async ValueTask BuildAsync_EmptyConfigurationSources_ThrowsException() { CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler()); - ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo); + ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo, new SystemEnvironment()); await Assert.ThrowsAsync(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List(), []))); } @@ -112,7 +112,7 @@ public async ValueTask BuildAsync_ConfigurationSourcesNotEnabledAsync_ThrowsExce mockConfigurationSource.Setup(x => x.IsEnabledAsync()).ReturnsAsync(false); CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler()); - ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo); + ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo, new SystemEnvironment()); configurationManager.AddConfigurationSource(() => mockConfigurationSource.Object); await Assert.ThrowsAsync(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List(), []))); @@ -132,7 +132,7 @@ public async ValueTask BuildAsync_ConfigurationSourceIsAsyncInitializableExtensi }; CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler()); - ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo); + ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo, new SystemEnvironment()); configurationManager.AddConfigurationSource(() => fakeConfigurationSource); await Assert.ThrowsAsync(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List(), []))); diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestApplicationBuilderTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestApplicationBuilderTests.cs index c900654889..18ae50fd08 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestApplicationBuilderTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestApplicationBuilderTests.cs @@ -21,7 +21,7 @@ public sealed class TestApplicationBuilderTests public TestApplicationBuilderTests() { CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler()); - AggregatedConfiguration configuration = new([], testApplicationModuleInfo, new SystemFileSystem(), new(null, [], [])); + AggregatedConfiguration configuration = new([], testApplicationModuleInfo, new SystemFileSystem(), new SystemEnvironment(), new(null, [], [])); configuration.SetCurrentWorkingDirectory(string.Empty); configuration.SetCurrentWorkingDirectory(string.Empty); _serviceProvider.AddService(configuration); From 1fea1ee4fa968078406826959308abaac4e67c67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 12:52:25 +0000 Subject: [PATCH 05/10] Add tests for DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY and use mock environment in tests Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com> --- .../AggregatedConfigurationTests.cs | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs index a07b34082e..c698a4065d 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs @@ -17,6 +17,7 @@ public sealed class AggregatedConfigurationTests private const string ExpectedPath = "a/b/c"; private readonly Mock _testApplicationModuleInfoMock = new(); private readonly Mock _fileSystemMock = new(); + private readonly Mock _environmentMock = new(); [TestMethod] [DataRow(PlatformConfigurationConstants.PlatformResultDirectory)] @@ -33,7 +34,18 @@ public void IndexerTest_DirectoryNotSetAndNoConfigurationProviders(string key) _ => throw ApplicationStateGuard.Unreachable(), }; - Assert.AreEqual(expected, new AggregatedConfiguration([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new SystemEnvironment(), new(null, [], []))[key]); + Assert.AreEqual(expected, new AggregatedConfiguration([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, _environmentMock.Object, new(null, [], []))[key]); + } + + [TestMethod] + public void IndexerTest_DotnetCliTestCommandWorkingDirectorySet_UsedAsResultDirectoryBase() + { + const string dotnetTestWorkingDir = "DotnetTestWorkingDir"; + _environmentMock.Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY)) + .Returns(dotnetTestWorkingDir); + + AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, _environmentMock.Object, new(null, [], [])); + Assert.AreEqual(Path.Combine(dotnetTestWorkingDir, "TestResults"), aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); } [TestMethod] @@ -43,7 +55,7 @@ public void IndexerTest_DirectoryNotSetButConfigurationProvidersPresent_Director { Mock mockProvider = new(); - AggregatedConfiguration aggregatedConfiguration = new([mockProvider.Object], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new SystemEnvironment(), new(null, [], [])); + AggregatedConfiguration aggregatedConfiguration = new([mockProvider.Object], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, _environmentMock.Object, new(null, [], [])); Assert.IsNull(aggregatedConfiguration[key]); } @@ -53,21 +65,21 @@ public void IndexerTest_DirectoryNotSetButConfigurationProvidersPresent_Director [DataRow(PlatformConfigurationConstants.PlatformTestHostWorkingDirectory)] public void IndexerTest_DirectoryNotSetButConfigurationProvidersPresent_DirectoryIsNotNull(string key) { - AggregatedConfiguration aggregatedConfiguration = new([new FakeConfigurationProvider(ExpectedPath)], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new SystemEnvironment(), new(null, [], [])); + AggregatedConfiguration aggregatedConfiguration = new([new FakeConfigurationProvider(ExpectedPath)], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, _environmentMock.Object, new(null, [], [])); Assert.AreEqual(ExpectedPath, aggregatedConfiguration[key]); } [TestMethod] public void IndexerTest_ResultDirectorySet_DirectoryIsNotNull() { - AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new SystemEnvironment(), new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); + AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, _environmentMock.Object, new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); Assert.AreEqual(ExpectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); } [TestMethod] public void IndexerTest_CurrentWorkingDirectorySet_DirectoryIsNotNull() { - AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, new SystemEnvironment(), new(null, [], [])); + AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, _environmentMock.Object, new(null, [], [])); aggregatedConfiguration.SetCurrentWorkingDirectory(ExpectedPath); Assert.AreEqual(ExpectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]); @@ -86,7 +98,7 @@ public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_Results Mock mockFileLogger = new(); mockFileLogger.Setup(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(It.IsAny())).Callback(() => { }); - AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, new SystemEnvironment(), new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); + AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, _environmentMock.Object, new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); await aggregatedConfiguration.CheckTestResultsDirectoryOverrideAndCreateItAsync(mockFileLogger.Object); mockFileSystem.Verify(x => x.CreateDirectory(ExpectedPath), Times.Once); @@ -108,7 +120,7 @@ public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_Results Mock mockFileLogger = new(); mockFileLogger.Setup(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(It.IsAny())).Callback(() => { }); - AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, new SystemEnvironment(), new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); + AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, _environmentMock.Object, new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); await aggregatedConfiguration.CheckTestResultsDirectoryOverrideAndCreateItAsync(mockFileLogger.Object); mockFileSystem.Verify(x => x.CreateDirectory(ExpectedPath), Times.Once); @@ -130,7 +142,7 @@ public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_Results Mock mockFileLogger = new(); mockFileLogger.Setup(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(It.IsAny())).Callback(() => { }); - AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, new SystemEnvironment(), new(null, [], [])); + AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, _environmentMock.Object, new(null, [], [])); await aggregatedConfiguration.CheckTestResultsDirectoryOverrideAndCreateItAsync(mockFileLogger.Object); string expectedPath = "a" + Path.DirectorySeparatorChar + "b" + Path.DirectorySeparatorChar + "TestResults"; @@ -139,6 +151,32 @@ public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_Results Assert.AreEqual(expectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); Assert.AreEqual("a" + Path.DirectorySeparatorChar + "b", aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]); } + + [TestMethod] + public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_DotnetCliTestCommandWorkingDirectorySet_UsedAsResultDirectoryBase() + { + const string dotnetTestWorkingDir = "DotnetTestWorkingDir"; + _environmentMock.Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY)) + .Returns(dotnetTestWorkingDir); + + Mock mockTestApplicationModuleInfo = new(); + mockTestApplicationModuleInfo.Setup(x => x.GetCurrentTestApplicationFullPath()).Returns(ExpectedPath); + mockTestApplicationModuleInfo.Setup(x => x.GetCurrentTestApplicationDirectory()).Returns(Path.GetDirectoryName(ExpectedPath) ?? AppContext.BaseDirectory); + + Mock mockFileSystem = new(); + mockFileSystem.Setup(x => x.CreateDirectory(It.IsAny())).Returns((string path) => path); + + Mock mockFileLogger = new(); + mockFileLogger.Setup(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(It.IsAny())).Callback(() => { }); + + AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, _environmentMock.Object, new(null, [], [])); + await aggregatedConfiguration.CheckTestResultsDirectoryOverrideAndCreateItAsync(mockFileLogger.Object); + + string expectedPath = Path.Combine(dotnetTestWorkingDir, "TestResults"); + mockFileSystem.Verify(x => x.CreateDirectory(expectedPath), Times.Once); + mockFileLogger.Verify(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(expectedPath), Times.Once); + Assert.AreEqual(expectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); + } } internal sealed class FakeConfigurationProvider : IConfigurationProvider From a3229500e25e328f5504053ab6552c7154cc8e71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 16:51:29 +0000 Subject: [PATCH 06/10] Apply DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY to PlatformCurrentWorkingDirectory and improve tests Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com> --- .../Configurations/AggregatedConfiguration.cs | 3 + .../AggregatedConfigurationTests.cs | 60 ++++++++++++++++--- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs b/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs index 0e056d04ac..bd22f2089d 100644 --- a/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs +++ b/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs @@ -104,6 +104,9 @@ private string GetCurrentWorkingDirectoryCore() => _currentWorkingDirectory // If first time calculating it, prefer the value from configuration, ?? CalculateFromConfigurationProviders(PlatformConfigurationConstants.PlatformCurrentWorkingDirectory) + // then check if dotnet test working directory is set (to keep PlatformCurrentWorkingDirectory and + // PlatformResultDirectory consistent when running under 'dotnet test'), + ?? _environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY) // then fallback to the actual working directory. ?? _testApplicationModuleInfo.GetCurrentTestApplicationDirectory(); } diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs index c698a4065d..540bb5bd40 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs @@ -38,7 +38,7 @@ public void IndexerTest_DirectoryNotSetAndNoConfigurationProviders(string key) } [TestMethod] - public void IndexerTest_DotnetCliTestCommandWorkingDirectorySet_UsedAsResultDirectoryBase() + public void IndexerTest_DotnetCliTestCommandWorkingDirectorySet_UsedAsWorkingDirectoryBase() { const string dotnetTestWorkingDir = "DotnetTestWorkingDir"; _environmentMock.Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY)) @@ -46,6 +46,23 @@ public void IndexerTest_DotnetCliTestCommandWorkingDirectorySet_UsedAsResultDire AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, _environmentMock.Object, new(null, [], [])); Assert.AreEqual(Path.Combine(dotnetTestWorkingDir, "TestResults"), aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); + Assert.AreEqual(dotnetTestWorkingDir, aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]); + Assert.AreEqual(dotnetTestWorkingDir, aggregatedConfiguration[PlatformConfigurationConstants.PlatformTestHostWorkingDirectory]); + } + + [TestMethod] + public void IndexerTest_ResultsDirectoryCliArgTakesPrecedenceOverDotnetCliTestCommandWorkingDirectory() + { + const string dotnetTestWorkingDir = "DotnetTestWorkingDir"; + _environmentMock.Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY)) + .Returns(dotnetTestWorkingDir); + + AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, _environmentMock.Object, new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); + + // --results-directory CLI arg should win over DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY + Assert.AreEqual(ExpectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); + // Current working directory still comes from env var + Assert.AreEqual(dotnetTestWorkingDir, aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]); } [TestMethod] @@ -120,13 +137,15 @@ public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_Results Mock mockFileLogger = new(); mockFileLogger.Setup(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(It.IsAny())).Callback(() => { }); - AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, _environmentMock.Object, new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); + // Results directory comes from configuration provider (store), not CLI args + AggregatedConfiguration aggregatedConfiguration = new([new FakeConfigurationProvider(ExpectedPath)], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, _environmentMock.Object, new(null, [], [])); await aggregatedConfiguration.CheckTestResultsDirectoryOverrideAndCreateItAsync(mockFileLogger.Object); mockFileSystem.Verify(x => x.CreateDirectory(ExpectedPath), Times.Once); mockFileLogger.Verify(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(ExpectedPath), Times.Once); Assert.AreEqual(ExpectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); - Assert.AreEqual("a" + Path.DirectorySeparatorChar + "b", aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]); + // PlatformCurrentWorkingDirectory also comes from the configuration provider + Assert.AreEqual(ExpectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]); } [TestMethod] @@ -159,23 +178,48 @@ public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_DotnetC _environmentMock.Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY)) .Returns(dotnetTestWorkingDir); - Mock mockTestApplicationModuleInfo = new(); - mockTestApplicationModuleInfo.Setup(x => x.GetCurrentTestApplicationFullPath()).Returns(ExpectedPath); - mockTestApplicationModuleInfo.Setup(x => x.GetCurrentTestApplicationDirectory()).Returns(Path.GetDirectoryName(ExpectedPath) ?? AppContext.BaseDirectory); - Mock mockFileSystem = new(); mockFileSystem.Setup(x => x.CreateDirectory(It.IsAny())).Returns((string path) => path); Mock mockFileLogger = new(); mockFileLogger.Setup(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(It.IsAny())).Callback(() => { }); - AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, _environmentMock.Object, new(null, [], [])); + AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, mockFileSystem.Object, _environmentMock.Object, new(null, [], [])); await aggregatedConfiguration.CheckTestResultsDirectoryOverrideAndCreateItAsync(mockFileLogger.Object); string expectedPath = Path.Combine(dotnetTestWorkingDir, "TestResults"); mockFileSystem.Verify(x => x.CreateDirectory(expectedPath), Times.Once); mockFileLogger.Verify(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(expectedPath), Times.Once); Assert.AreEqual(expectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); + Assert.AreEqual(dotnetTestWorkingDir, aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]); + } + + [TestMethod] + public async ValueTask CheckTestResultsDirectoryOverrideAndCreateItAsync_ResultsDirectoryCliArgTakesPrecedenceOverDotnetCliTestCommandWorkingDirectory() + { + const string dotnetTestWorkingDir = "DotnetTestWorkingDir"; + _environmentMock.Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY)) + .Returns(dotnetTestWorkingDir); + + Mock mockTestApplicationModuleInfo = new(); + mockTestApplicationModuleInfo.Setup(x => x.GetCurrentTestApplicationFullPath()).Returns(ExpectedPath); + mockTestApplicationModuleInfo.Setup(x => x.GetCurrentTestApplicationDirectory()).Returns(Path.GetDirectoryName(ExpectedPath) ?? AppContext.BaseDirectory); + + Mock mockFileSystem = new(); + mockFileSystem.Setup(x => x.CreateDirectory(It.IsAny())).Returns((string path) => path); + + Mock mockFileLogger = new(); + mockFileLogger.Setup(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(It.IsAny())).Callback(() => { }); + + // --results-directory CLI arg takes precedence over DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY + AggregatedConfiguration aggregatedConfiguration = new([], mockTestApplicationModuleInfo.Object, mockFileSystem.Object, _environmentMock.Object, new(null, [new CommandLineParseOption("results-directory", [ExpectedPath])], [])); + await aggregatedConfiguration.CheckTestResultsDirectoryOverrideAndCreateItAsync(mockFileLogger.Object); + + mockFileSystem.Verify(x => x.CreateDirectory(ExpectedPath), Times.Once); + mockFileLogger.Verify(x => x.CheckLogFolderAndMoveToTheNewIfNeededAsync(ExpectedPath), Times.Once); + Assert.AreEqual(ExpectedPath, aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); + // Current working directory still comes from env var + Assert.AreEqual(dotnetTestWorkingDir, aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]); } } From 530e0efd4ee5c68f43a4768ded2efd9b72e1c562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 13 May 2026 12:11:42 +0200 Subject: [PATCH 07/10] Normalize DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY when empty/whitespace Treat empty/whitespace values of DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY as not set (normalize to null) so Path.Combine does not produce relative paths. Applied in GetResultsDirectoryCore, GetCurrentWorkingDirectoryCore, and CreateFileLoggerIfDiagnosticIsEnabled. --- .../Builder/TestApplication.cs | 6 ++- .../Configurations/AggregatedConfiguration.cs | 37 ++++++++++++++----- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs index 4d874d3f26..04fd745089 100644 --- a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs +++ b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs @@ -306,7 +306,11 @@ private static ApplicationLoggingState CreateFileLoggerIfDiagnosticIsEnabled( // Set the directory to the default test result directory string? effectiveWorkingDirectory = environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY); - effectiveWorkingDirectory ??= testApplicationModuleInfo.GetCurrentTestApplicationDirectory(); + if (RoslynString.IsNullOrWhiteSpace(effectiveWorkingDirectory)) + { + effectiveWorkingDirectory = testApplicationModuleInfo.GetCurrentTestApplicationDirectory(); + } + string directory = Path.Combine(effectiveWorkingDirectory, AggregatedConfiguration.DefaultTestResultFolderName); bool customDirectory = false; diff --git a/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs b/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs index bd22f2089d..e8ebd24081 100644 --- a/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs +++ b/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs @@ -95,18 +95,35 @@ private string GetResultsDirectoryCore(CommandLineParseResult commandLineParseRe // If not specified by command line, then use the configuration providers. // And finally fallback to DefaultTestResultFolderName relative to the current working directory. + string? dotnetTestCwd = _environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY); return CalculateFromConfigurationProviders(PlatformConfigurationConstants.PlatformResultDirectory) - ?? Path.Combine(_environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY) ?? this[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]!, DefaultTestResultFolderName); + ?? Path.Combine(!RoslynString.IsNullOrWhiteSpace(dotnetTestCwd) ? dotnetTestCwd : this[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]!, DefaultTestResultFolderName); } private string GetCurrentWorkingDirectoryCore() - // If the value is already set, use that. - => _currentWorkingDirectory - // If first time calculating it, prefer the value from configuration, - ?? CalculateFromConfigurationProviders(PlatformConfigurationConstants.PlatformCurrentWorkingDirectory) - // then check if dotnet test working directory is set (to keep PlatformCurrentWorkingDirectory and - // PlatformResultDirectory consistent when running under 'dotnet test'), - ?? _environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY) - // then fallback to the actual working directory. - ?? _testApplicationModuleInfo.GetCurrentTestApplicationDirectory(); + { + // If the value is already set, use that. + if (_currentWorkingDirectory is not null) + { + return _currentWorkingDirectory; + } + + // If first time calculating it, prefer the value from configuration, + string? fromConfig = CalculateFromConfigurationProviders(PlatformConfigurationConstants.PlatformCurrentWorkingDirectory); + if (fromConfig is not null) + { + return fromConfig; + } + + // then check if dotnet test working directory is set (to keep PlatformCurrentWorkingDirectory and + // PlatformResultDirectory consistent when running under 'dotnet test'), + string? dotnetTestCwd = _environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY); + if (!RoslynString.IsNullOrWhiteSpace(dotnetTestCwd)) + { + return dotnetTestCwd; + } + + // then fallback to the actual working directory. + return _testApplicationModuleInfo.GetCurrentTestApplicationDirectory(); + } } From 8a708480d497f8ea520d0510553fdb010575bf01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 13 May 2026 17:30:39 +0200 Subject: [PATCH 08/10] Remove redundant DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY check from GetResultsDirectoryCore PlatformCurrentWorkingDirectory (via GetCurrentWorkingDirectoryCore) already incorporates the env var, so the separate check in GetResultsDirectoryCore was redundant and could cause the default results directory to ignore an explicitly-set working directory or a provider-supplied one. --- .../Configurations/AggregatedConfiguration.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs b/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs index e8ebd24081..a8279e777d 100644 --- a/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs +++ b/src/Platform/Microsoft.Testing.Platform/Configurations/AggregatedConfiguration.cs @@ -95,9 +95,10 @@ private string GetResultsDirectoryCore(CommandLineParseResult commandLineParseRe // If not specified by command line, then use the configuration providers. // And finally fallback to DefaultTestResultFolderName relative to the current working directory. - string? dotnetTestCwd = _environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY); + // Note: PlatformCurrentWorkingDirectory already incorporates DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY + // (via GetCurrentWorkingDirectoryCore), so we don't need to check that env var separately here. return CalculateFromConfigurationProviders(PlatformConfigurationConstants.PlatformResultDirectory) - ?? Path.Combine(!RoslynString.IsNullOrWhiteSpace(dotnetTestCwd) ? dotnetTestCwd : this[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]!, DefaultTestResultFolderName); + ?? Path.Combine(this[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]!, DefaultTestResultFolderName); } private string GetCurrentWorkingDirectoryCore() From d8cbd114ca5cebafb234bf0db5d887bfead5a5c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Wed, 13 May 2026 18:24:59 +0200 Subject: [PATCH 09/10] Skip DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY in acceptance tests The env var leaks from the parent dotnet test process into child test host processes, causing DiagnosticTests to resolve the TestResults directory relative to the CI working directory instead of the test application directory. --- .../WellKnownEnvironmentVariables.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/WellKnownEnvironmentVariables.cs b/test/Utilities/Microsoft.Testing.TestInfrastructure/WellKnownEnvironmentVariables.cs index 4d636c0cec..4921721b60 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/WellKnownEnvironmentVariables.cs +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/WellKnownEnvironmentVariables.cs @@ -41,6 +41,7 @@ public static class WellKnownEnvironmentVariables // dotnet test "TESTINGPLATFORM_DOTNETTEST_EXECUTIONID", + "DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY", // Isolate from the skip banner in case of parent, children tests "TESTINGPLATFORM_CONSOLEOUTPUTDEVICE_SKIP_BANNER" From f23e6634b40c91253a3b4a7c90d2a522b962e7df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 09:23:19 +0000 Subject: [PATCH 10/10] Add tests for diagnostic directory and whitespace fallback in DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com> --- .../Builder/TestApplication.cs | 19 ++++++---- .../AggregatedConfigurationTests.cs | 16 ++++++++ ...TestApplicationDiagnosticVerbosityTests.cs | 38 +++++++++++++++++++ 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs index 04fd745089..c976bc61e5 100644 --- a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs +++ b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs @@ -305,13 +305,7 @@ private static ApplicationLoggingState CreateFileLoggerIfDiagnosticIsEnabled( } // Set the directory to the default test result directory - string? effectiveWorkingDirectory = environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY); - if (RoslynString.IsNullOrWhiteSpace(effectiveWorkingDirectory)) - { - effectiveWorkingDirectory = testApplicationModuleInfo.GetCurrentTestApplicationDirectory(); - } - - string directory = Path.Combine(effectiveWorkingDirectory, AggregatedConfiguration.DefaultTestResultFolderName); + string directory = GetDiagnosticDefaultDirectory(environment, testApplicationModuleInfo); bool customDirectory = false; if (result.TryGetOptionArgumentList(PlatformCommandLineProvider.ResultDirectoryOptionKey, out string[]? resultDirectoryArg)) @@ -387,6 +381,17 @@ private static ApplicationLoggingState CreateFileLoggerIfDiagnosticIsEnabled( || ThrowInvalidDiagnosticVerbosity(environmentLogLevel)); } + internal /* for testing purposes */ static string GetDiagnosticDefaultDirectory(IEnvironment environment, ITestApplicationModuleInfo testApplicationModuleInfo) + { + string? effectiveWorkingDirectory = environment.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY); + if (RoslynString.IsNullOrWhiteSpace(effectiveWorkingDirectory)) + { + effectiveWorkingDirectory = testApplicationModuleInfo.GetCurrentTestApplicationDirectory(); + } + + return Path.Combine(effectiveWorkingDirectory, AggregatedConfiguration.DefaultTestResultFolderName); + } + private static bool ThrowInvalidDiagnosticVerbosity(string environmentLogLevel) => throw new NotSupportedException($"Invalid environment value '{nameof(EnvironmentVariableConstants.TESTINGPLATFORM_DIAGNOSTIC_VERBOSITY)}', was expecting 'Trace', 'Debug', 'Information', 'Warning', 'Error', or 'Critical' but got '{environmentLogLevel}'."); } diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs index 540bb5bd40..1e84185a34 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/AggregatedConfigurationTests.cs @@ -50,6 +50,22 @@ public void IndexerTest_DotnetCliTestCommandWorkingDirectorySet_UsedAsWorkingDir Assert.AreEqual(dotnetTestWorkingDir, aggregatedConfiguration[PlatformConfigurationConstants.PlatformTestHostWorkingDirectory]); } + [TestMethod] + [DataRow("")] + [DataRow(" ")] + public void IndexerTest_DotnetCliTestCommandWorkingDirectoryIsWhitespace_FallsBackToTestApplicationDirectory(string envVarValue) + { + const string appDirectory = "AppDirectory"; + _environmentMock.Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY)) + .Returns(envVarValue); + _testApplicationModuleInfoMock.Setup(x => x.GetCurrentTestApplicationDirectory()).Returns(appDirectory); + + AggregatedConfiguration aggregatedConfiguration = new([], _testApplicationModuleInfoMock.Object, _fileSystemMock.Object, _environmentMock.Object, new(null, [], [])); + Assert.AreEqual(Path.Combine(appDirectory, "TestResults"), aggregatedConfiguration[PlatformConfigurationConstants.PlatformResultDirectory]); + Assert.AreEqual(appDirectory, aggregatedConfiguration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]); + Assert.AreEqual(appDirectory, aggregatedConfiguration[PlatformConfigurationConstants.PlatformTestHostWorkingDirectory]); + } + [TestMethod] public void IndexerTest_ResultsDirectoryCliArgTakesPrecedenceOverDotnetCliTestCommandWorkingDirectory() { diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestApplicationDiagnosticVerbosityTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestApplicationDiagnosticVerbosityTests.cs index eb7481ec46..192d219a69 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestApplicationDiagnosticVerbosityTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestApplicationDiagnosticVerbosityTests.cs @@ -1,7 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.Testing.Platform.Configurations; +using Microsoft.Testing.Platform.Helpers; using Microsoft.Testing.Platform.Logging; +using Microsoft.Testing.Platform.Services; + +using Moq; namespace Microsoft.Testing.Platform.UnitTests; @@ -29,4 +34,37 @@ public void TryParseDiagnosticVerbosity_WhenNullValue_ReturnsFalse() [TestMethod] public void TryParseDiagnosticVerbosity_WhenInvalidValue_ThrowsNotSupportedException() => Assert.ThrowsExactly(() => _ = TestApplication.TryParseDiagnosticVerbosity("invalid", out _)); + + [TestMethod] + public void GetDiagnosticDefaultDirectory_WhenDotnetCliTestCommandWorkingDirectorySet_UsesEnvVarAsBase() + { + const string dotnetTestWorkingDir = "DotnetTestWorkingDir"; + Mock environmentMock = new(); + environmentMock.Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY)) + .Returns(dotnetTestWorkingDir); + Mock moduleInfoMock = new(); + + string result = TestApplication.GetDiagnosticDefaultDirectory(environmentMock.Object, moduleInfoMock.Object); + + Assert.AreEqual(Path.Combine(dotnetTestWorkingDir, AggregatedConfiguration.DefaultTestResultFolderName), result); + moduleInfoMock.Verify(x => x.GetCurrentTestApplicationDirectory(), Times.Never); + } + + [TestMethod] + [DataRow(null)] + [DataRow("")] + [DataRow(" ")] + public void GetDiagnosticDefaultDirectory_WhenDotnetCliTestCommandWorkingDirectoryNullOrWhitespace_FallsBackToAppDirectory(string? envVarValue) + { + const string appDirectory = "AppDirectory"; + Mock environmentMock = new(); + environmentMock.Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.DOTNET_CLI_TEST_COMMAND_WORKING_DIRECTORY)) + .Returns(envVarValue); + Mock moduleInfoMock = new(); + moduleInfoMock.Setup(x => x.GetCurrentTestApplicationDirectory()).Returns(appDirectory); + + string result = TestApplication.GetDiagnosticDefaultDirectory(environmentMock.Object, moduleInfoMock.Object); + + Assert.AreEqual(Path.Combine(appDirectory, AggregatedConfiguration.DefaultTestResultFolderName), result); + } }