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);
+ }
+}