// 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.Resources;
namespace Microsoft.Testing.Platform.TestHostControllers;
///
/// Built-in that reads the
/// environmentVariables section from the loaded testconfig.json file and applies the
/// entries to the test host child process.
///
///
/// When the section is present and non-empty, this provider opts in to the test host controller
/// process model: the current process becomes the controller and spawns the actual test host as a
/// child process with the configured environment variables set on ProcessStartInfo.
///
[UnsupportedOSPlatform("browser")]
internal sealed class TestConfigurationEnvironmentVariableProvider : ITestHostEnvironmentVariableProvider
{
internal const string EnvironmentVariablesSectionName = "environmentVariables";
private readonly AggregatedConfiguration _configuration;
private IReadOnlyList>? _entries;
public TestConfigurationEnvironmentVariableProvider(IConfiguration configuration)
=> _configuration = (AggregatedConfiguration)configuration;
public string Uid => nameof(TestConfigurationEnvironmentVariableProvider);
public string Version => PlatformVersion.Version;
public string DisplayName => PlatformResources.TestConfigurationEnvironmentVariableProviderDisplayName;
public string Description => PlatformResources.TestConfigurationEnvironmentVariableProviderDescription;
public Task IsEnabledAsync()
{
IReadOnlyList> entries = _configuration.GetTestConfigJsonSection(EnvironmentVariablesSectionName);
if (entries.Count == 0)
{
// Clear any cached entries from a previous call so we never apply stale state if the
// method is invoked more than once on the same instance.
_entries = null;
return Task.FromResult(false);
}
// Validate names upfront to surface configuration errors before the test host process is
// launched. ProcessStartInfo.EnvironmentVariables would otherwise throw later with a
// less actionable error.
foreach (KeyValuePair entry in entries)
{
ValidateName(entry.Key);
}
_entries = entries;
return Task.FromResult(true);
}
public Task UpdateAsync(IEnvironmentVariables environmentVariables)
{
if (_entries is null)
{
return Task.CompletedTask;
}
foreach (KeyValuePair entry in _entries)
{
// We deliberately leave the variable unlocked so other providers (e.g. runsettings
// via the VSTest bridge, or user-registered providers) can still override the value
// by registering after us in the test application builder.
environmentVariables.SetVariable(new EnvironmentVariable(
entry.Key,
entry.Value ?? string.Empty,
isSecret: false,
isLocked: false));
}
return Task.CompletedTask;
}
public Task ValidateTestHostEnvironmentVariablesAsync(IReadOnlyEnvironmentVariables environmentVariables)
=> ValidationResult.ValidTask;
private static void ValidateName(string name)
{
if (RoslynString.IsNullOrEmpty(name))
{
throw new FormatException(PlatformResources.TestConfigurationEnvironmentVariableNameCannotBeEmptyErrorMessage);
}
for (int i = 0; i < name.Length; i++)
{
char c = name[i];
if (c is '=' or '\0')
{
throw new FormatException(string.Format(
CultureInfo.InvariantCulture,
PlatformResources.TestConfigurationEnvironmentVariableInvalidNameErrorMessage,
name));
}
}
}
}