diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Helpers/TestApplicationBuilderExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Helpers/TestApplicationBuilderExtensions.cs index 9ec3739da9..85e32217b9 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Helpers/TestApplicationBuilderExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Helpers/TestApplicationBuilderExtensions.cs @@ -54,5 +54,5 @@ public static void AddTestRunParametersService(this ITestApplicationBuilder buil /// The extension that will be used as the source of registration for this helper service. public static void AddRunSettingsEnvironmentVariableProvider(this ITestApplicationBuilder builder, IExtension extension) => builder.TestHostControllers.AddEnvironmentVariableProvider(serviceProvider - => new RunSettingsEnvironmentVariableProvider(extension, serviceProvider.GetCommandLineOptions(), serviceProvider.GetFileSystem())); + => new RunSettingsEnvironmentVariableProvider(extension, serviceProvider.GetCommandLineOptions(), serviceProvider.GetFileSystem(), serviceProvider.GetEnvironment())); } diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/TestHostControllers/RunSettingsEnvironmentVariableProvider.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/TestHostControllers/RunSettingsEnvironmentVariableProvider.cs index 72a5fd8152..31cacda715 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/TestHostControllers/RunSettingsEnvironmentVariableProvider.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/TestHostControllers/RunSettingsEnvironmentVariableProvider.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Testing.Extensions.VSTestBridge.CommandLine; +using Microsoft.Testing.Platform; using Microsoft.Testing.Platform.CommandLine; using Microsoft.Testing.Platform.Extensions; using Microsoft.Testing.Platform.Extensions.TestHostControllers; @@ -14,13 +15,15 @@ internal sealed class RunSettingsEnvironmentVariableProvider : ITestHostEnvironm private readonly IExtension _extension; private readonly ICommandLineOptions _commandLineOptions; private readonly IFileSystem _fileSystem; + private readonly IEnvironment _environment; private XDocument? _runSettings; - public RunSettingsEnvironmentVariableProvider(IExtension extension, ICommandLineOptions commandLineOptions, IFileSystem fileSystem) + public RunSettingsEnvironmentVariableProvider(IExtension extension, ICommandLineOptions commandLineOptions, IFileSystem fileSystem, IEnvironment environment) { _extension = extension; _commandLineOptions = commandLineOptions; _fileSystem = fileSystem; + _environment = environment; } public string Uid => _extension.Uid; @@ -33,23 +36,56 @@ public RunSettingsEnvironmentVariableProvider(IExtension extension, ICommandLine public async Task IsEnabledAsync() { - if (!_commandLineOptions.TryGetOptionArgumentList(RunSettingsCommandLineOptionsProvider.RunSettingsOptionName, out string[]? runsettings)) + string? runSettingsFilePath = null; + string? runSettingsContent = null; + + // Try to get runsettings from command line + if (_commandLineOptions.TryGetOptionArgumentList(RunSettingsCommandLineOptionsProvider.RunSettingsOptionName, out string[]? runsettings) + && runsettings.Length > 0) { - return false; + if (_fileSystem.ExistFile(runsettings[0])) + { + runSettingsFilePath = runsettings[0]; + } } - if (!_fileSystem.ExistFile(runsettings[0])) + // If not from command line, try environment variable with content + if (runSettingsFilePath is null) { - return false; + runSettingsContent = _environment.GetEnvironmentVariable("TESTINGPLATFORM_EXPERIMENTAL_VSTEST_RUNSETTINGS"); } - using IFileStream fileStream = _fileSystem.NewFileStream(runsettings[0], FileMode.Open, FileAccess.Read); + // If not from content env var, try environment variable with file path + if (runSettingsFilePath is null && RoslynString.IsNullOrEmpty(runSettingsContent)) + { + string? envVarFilePath = _environment.GetEnvironmentVariable("TESTINGPLATFORM_VSTESTBRIDGE_RUNSETTINGS_FILE"); + if (!RoslynString.IsNullOrEmpty(envVarFilePath) && _fileSystem.ExistFile(envVarFilePath)) + { + runSettingsFilePath = envVarFilePath; + } + } + + // If we have a file path, read from file + if (runSettingsFilePath is not null) + { + using IFileStream fileStream = _fileSystem.NewFileStream(runSettingsFilePath, FileMode.Open, FileAccess.Read); #if NETCOREAPP - _runSettings = await XDocument.LoadAsync(fileStream.Stream, LoadOptions.None, CancellationToken.None).ConfigureAwait(false); + _runSettings = await XDocument.LoadAsync(fileStream.Stream, LoadOptions.None, CancellationToken.None).ConfigureAwait(false); #else - using StreamReader streamReader = new(fileStream.Stream); - _runSettings = XDocument.Parse(await streamReader.ReadToEndAsync().ConfigureAwait(false)); + using StreamReader streamReader = new(fileStream.Stream); + _runSettings = XDocument.Parse(await streamReader.ReadToEndAsync().ConfigureAwait(false)); #endif + } + else if (!RoslynString.IsNullOrEmpty(runSettingsContent)) + { + // If we have content, parse it directly + _runSettings = XDocument.Parse(runSettingsContent); + } + else + { + return false; + } + return _runSettings.Element("RunSettings")?.Element("RunConfiguration")?.Element("EnvironmentVariables") is not null; } diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/TestHostControllers/RunSettingsEnvironmentVariableProviderTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/TestHostControllers/RunSettingsEnvironmentVariableProviderTests.cs new file mode 100644 index 0000000000..766fbca07b --- /dev/null +++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/TestHostControllers/RunSettingsEnvironmentVariableProviderTests.cs @@ -0,0 +1,245 @@ +// 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.Extensions.VSTestBridge.TestHostControllers; +using Microsoft.Testing.Extensions.VSTestBridge.UnitTests.Helpers; +using Microsoft.Testing.Platform.CommandLine; +using Microsoft.Testing.Platform.Extensions.TestHostControllers; +using Microsoft.Testing.Platform.Helpers; + +using Moq; + +namespace Microsoft.Testing.Extensions.VSTestBridge.UnitTests.TestHostControllers; + +[TestClass] +public sealed class RunSettingsEnvironmentVariableProviderTests +{ + private const string RunSettingsWithEnvironmentVariables = """ + + + + + TestValue + AnotherValue + + + + """; + + private const string RunSettingsWithoutEnvironmentVariables = """ + + + + + + """; + + [TestMethod] + public async Task IsEnabledAsync_WhenCommandLineOptionProvided_ReturnsTrue() + { + // Arrange + const string filePath = "test.runsettings"; + var commandLineOptions = new Mock(); + commandLineOptions.Setup(x => x.TryGetOptionArgumentList("settings", out It.Ref.IsAny)) + .Returns((string optionName, out string[]? value) => + { + value = [filePath]; + return true; + }); + + var fileSystem = new Mock(); + fileSystem.Setup(x => x.ExistFile(filePath)).Returns(true); + var fileStream = new Mock(); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(RunSettingsWithEnvironmentVariables)); + fileStream.Setup(x => x.Stream).Returns(stream); + fileSystem.Setup(x => x.NewFileStream(filePath, FileMode.Open, FileAccess.Read)).Returns(fileStream.Object); + + var environment = new Mock(); + + var provider = new RunSettingsEnvironmentVariableProvider(new TestExtension(), commandLineOptions.Object, fileSystem.Object, environment.Object); + + // Act + bool result = await provider.IsEnabledAsync(); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public async Task IsEnabledAsync_WhenCommandLineOptionProvidedButNoEnvironmentVariables_ReturnsFalse() + { + // Arrange + const string filePath = "test.runsettings"; + var commandLineOptions = new Mock(); + commandLineOptions.Setup(x => x.TryGetOptionArgumentList("settings", out It.Ref.IsAny)) + .Returns((string optionName, out string[]? value) => + { + value = [filePath]; + return true; + }); + + var fileSystem = new Mock(); + fileSystem.Setup(x => x.ExistFile(filePath)).Returns(true); + var fileStream = new Mock(); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(RunSettingsWithoutEnvironmentVariables)); + fileStream.Setup(x => x.Stream).Returns(stream); + fileSystem.Setup(x => x.NewFileStream(filePath, FileMode.Open, FileAccess.Read)).Returns(fileStream.Object); + + var environment = new Mock(); + + var provider = new RunSettingsEnvironmentVariableProvider(new TestExtension(), commandLineOptions.Object, fileSystem.Object, environment.Object); + + // Act + bool result = await provider.IsEnabledAsync(); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public async Task IsEnabledAsync_WhenEnvironmentVariableWithContentProvided_ReturnsTrue() + { + // Arrange + var commandLineOptions = new Mock(); + commandLineOptions.Setup(x => x.TryGetOptionArgumentList("settings", out It.Ref.IsAny)) + .Returns((string optionName, out string[]? value) => + { + value = null; + return false; + }); + + var fileSystem = new Mock(); + + var environment = new Mock(); + environment.Setup(x => x.GetEnvironmentVariable("TESTINGPLATFORM_EXPERIMENTAL_VSTEST_RUNSETTINGS")) + .Returns(RunSettingsWithEnvironmentVariables); + + var provider = new RunSettingsEnvironmentVariableProvider(new TestExtension(), commandLineOptions.Object, fileSystem.Object, environment.Object); + + // Act + bool result = await provider.IsEnabledAsync(); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public async Task IsEnabledAsync_WhenEnvironmentVariableWithContentProvidedButNoEnvironmentVariables_ReturnsFalse() + { + // Arrange + var commandLineOptions = new Mock(); + commandLineOptions.Setup(x => x.TryGetOptionArgumentList("settings", out It.Ref.IsAny)) + .Returns((string optionName, out string[]? value) => + { + value = null; + return false; + }); + + var fileSystem = new Mock(); + + var environment = new Mock(); + environment.Setup(x => x.GetEnvironmentVariable("TESTINGPLATFORM_EXPERIMENTAL_VSTEST_RUNSETTINGS")) + .Returns(RunSettingsWithoutEnvironmentVariables); + + var provider = new RunSettingsEnvironmentVariableProvider(new TestExtension(), commandLineOptions.Object, fileSystem.Object, environment.Object); + + // Act + bool result = await provider.IsEnabledAsync(); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public async Task IsEnabledAsync_WhenEnvironmentVariableWithFilePathProvided_ReturnsTrue() + { + // Arrange + const string filePath = "test.runsettings"; + var commandLineOptions = new Mock(); + commandLineOptions.Setup(x => x.TryGetOptionArgumentList("settings", out It.Ref.IsAny)) + .Returns((string optionName, out string[]? value) => + { + value = null; + return false; + }); + + var fileSystem = new Mock(); + fileSystem.Setup(x => x.ExistFile(filePath)).Returns(true); + var fileStream = new Mock(); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(RunSettingsWithEnvironmentVariables)); + fileStream.Setup(x => x.Stream).Returns(stream); + fileSystem.Setup(x => x.NewFileStream(filePath, FileMode.Open, FileAccess.Read)).Returns(fileStream.Object); + + var environment = new Mock(); + environment.Setup(x => x.GetEnvironmentVariable("TESTINGPLATFORM_VSTESTBRIDGE_RUNSETTINGS_FILE")) + .Returns(filePath); + + var provider = new RunSettingsEnvironmentVariableProvider(new TestExtension(), commandLineOptions.Object, fileSystem.Object, environment.Object); + + // Act + bool result = await provider.IsEnabledAsync(); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public async Task IsEnabledAsync_WhenNoRunsettingsProvided_ReturnsFalse() + { + // Arrange + var commandLineOptions = new Mock(); + commandLineOptions.Setup(x => x.TryGetOptionArgumentList("settings", out It.Ref.IsAny)) + .Returns((string optionName, out string[]? value) => + { + value = null; + return false; + }); + + var fileSystem = new Mock(); + + var environment = new Mock(); + + var provider = new RunSettingsEnvironmentVariableProvider(new TestExtension(), commandLineOptions.Object, fileSystem.Object, environment.Object); + + // Act + bool result = await provider.IsEnabledAsync(); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public async Task UpdateAsync_SetsEnvironmentVariablesFromRunsettings() + { + // Arrange + var commandLineOptions = new Mock(); + commandLineOptions.Setup(x => x.TryGetOptionArgumentList("settings", out It.Ref.IsAny)) + .Returns((string optionName, out string[]? value) => + { + value = null; + return false; + }); + + var fileSystem = new Mock(); + + var environment = new Mock(); + environment.Setup(x => x.GetEnvironmentVariable("TESTINGPLATFORM_EXPERIMENTAL_VSTEST_RUNSETTINGS")) + .Returns(RunSettingsWithEnvironmentVariables); + + var provider = new RunSettingsEnvironmentVariableProvider(new TestExtension(), commandLineOptions.Object, fileSystem.Object, environment.Object); + + var environmentVariables = new Mock(); + var capturedVariables = new List(); + environmentVariables.Setup(x => x.SetVariable(It.IsAny())) + .Callback(capturedVariables.Add); + + // Act + await provider.IsEnabledAsync(); + await provider.UpdateAsync(environmentVariables.Object); + + // Assert + Assert.HasCount(2, capturedVariables); + Assert.Contains(v => v.Variable == "TEST_ENV" && v.Value == "TestValue", capturedVariables); + Assert.Contains(v => v.Variable == "ANOTHER_VAR" && v.Value == "AnotherValue", capturedVariables); + } +}