Skip to content

Commit

Permalink
Fix RunSettings/RunConfiguration/ResultsDirectory (#3902)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoRossignoli authored Oct 2, 2024
1 parent d0f47ec commit 496d67d
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

using System.Xml.Linq;

using Microsoft.Testing.Extensions.VSTestBridge.CommandLine;
using Microsoft.Testing.Platform.CommandLine;
using Microsoft.Testing.Platform.Configurations;
using Microsoft.Testing.Platform.Helpers;

namespace Microsoft.Testing.Extensions.VSTestBridge.Configurations;

internal sealed class RunSettingsConfigurationProvider : IConfigurationSource, IConfigurationProvider
internal sealed class RunSettingsConfigurationProvider(IFileSystem fileSystem) : IConfigurationSource, IConfigurationProvider
{
private readonly string _runsettings;
private readonly IFileSystem _fileSystem = fileSystem;

public RunSettingsConfigurationProvider(string runSettings) => _runsettings = runSettings;
private string? _runSettingsFileContent;

/// <inheritdoc />
public string Uid { get; } = nameof(RunSettingsConfigurationProvider);
Expand All @@ -26,6 +28,8 @@ internal sealed class RunSettingsConfigurationProvider : IConfigurationSource, I
/// <inheritdoc />
public string Description { get; } = "Configuration source to bridge VSTest xml runsettings configuration into Microsoft Testing Platform configuration model.";

public int Order => 2;

/// <inheritdoc />
public Task<bool> IsEnabledAsync() => Task.FromResult(true);

Expand All @@ -35,9 +39,15 @@ internal sealed class RunSettingsConfigurationProvider : IConfigurationSource, I
/// <inheritdoc />
public bool TryGet(string key, out string? value)
{
if (_runSettingsFileContent is null)
{
value = null;
return false;
}

if (key == PlatformConfigurationConstants.PlatformResultDirectory)
{
var document = XDocument.Parse(_runsettings);
var document = XDocument.Parse(_runSettingsFileContent);
value = document.Element("RunSettings")?.Element("RunConfiguration")?.Element("ResultsDirectory")?.Value;
if (value is not null)
{
Expand All @@ -50,6 +60,16 @@ public bool TryGet(string key, out string? value)
}

/// <inheritdoc />
public IConfigurationProvider Build()
=> new RunSettingsConfigurationProvider(_runsettings);
public async Task<IConfigurationProvider> BuildAsync(CommandLineParseResult commandLineParseResult)
{
if (commandLineParseResult.TryGetOptionArgumentList(RunSettingsCommandLineOptionsProvider.RunSettingsOptionName, out string[]? runSettingsFilePath))
{
if (_fileSystem.Exists(runSettingsFilePath[0]))
{
_runSettingsFileContent = await _fileSystem.ReadAllTextAsync(runSettingsFilePath[0]);
}
}

return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// 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.Extensions.VSTestBridge.Configurations;
using Microsoft.Testing.Platform.Builder;
using Microsoft.Testing.Platform.Extensions;
using Microsoft.Testing.Platform.Helpers;

namespace Microsoft.Testing.Extensions.VSTestBridge.Helpers;

Expand All @@ -26,7 +28,14 @@ public static void AddTestCaseFilterService(this ITestApplicationBuilder builder
/// <param name="builder">The test application builder.</param>
/// <param name="extension">The extension that will be used as the source of registration for this helper service.</param>
public static void AddRunSettingsService(this ITestApplicationBuilder builder, IExtension extension)
=> builder.CommandLine.AddProvider(() => new RunSettingsCommandLineOptionsProvider(extension));
{
if (builder is TestApplicationBuilder testApplicationBuilder)
{
testApplicationBuilder.Configuration.AddConfigurationSource(() => new RunSettingsConfigurationProvider(new SystemFileSystem()));
}

builder.CommandLine.AddProvider(() => new RunSettingsCommandLineOptionsProvider(extension));
}

/// <summary>
/// Allows to register the VSTest TestRunParameters service.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.CommandLine;
using Microsoft.Testing.Platform.Extensions;
using Microsoft.Testing.Platform.Helpers;
using Microsoft.Testing.Platform.Logging;
Expand All @@ -19,9 +20,9 @@ internal sealed class ConfigurationManager(IFileSystem fileSystem, ITestApplicat

public void AddConfigurationSource(Func<IConfigurationSource> source) => _configurationSources.Add(source);

internal async Task<IConfiguration> BuildAsync(IFileLoggerProvider? syncFileLoggerProvider)
internal async Task<IConfiguration> BuildAsync(IFileLoggerProvider? syncFileLoggerProvider, CommandLineParseResult commandLineParseResult)
{
List<IConfigurationProvider> configurationProviders = [];
List<(IConfigurationProvider ConfigurationProvider, int Order)> configurationProviders = [];
JsonConfigurationProvider? defaultJsonConfiguration = null;
foreach (Func<IConfigurationSource> configurationSource in _configurationSources)
{
Expand All @@ -33,14 +34,14 @@ internal async Task<IConfiguration> BuildAsync(IFileLoggerProvider? syncFileLogg

await serviceInstance.TryInitializeAsync();

IConfigurationProvider configurationProvider = serviceInstance.Build();
IConfigurationProvider configurationProvider = await serviceInstance.BuildAsync(commandLineParseResult);
await configurationProvider.LoadAsync();
if (configurationProvider is JsonConfigurationProvider configuration)
{
defaultJsonConfiguration = configuration;
}

configurationProviders.Add(configurationProvider);
configurationProviders.Add((configurationProvider, serviceInstance.Order));
}

if (syncFileLoggerProvider is not null)
Expand All @@ -57,6 +58,6 @@ internal async Task<IConfiguration> BuildAsync(IFileLoggerProvider? syncFileLogg

return defaultJsonConfiguration is null
? throw new InvalidOperationException(PlatformResources.ConfigurationManagerCannotFindDefaultJsonConfigurationErrorMessage)
: new AggregatedConfiguration(configurationProviders.ToArray(), _testApplicationModuleInfo, _fileSystem);
: new AggregatedConfiguration(configurationProviders.OrderBy(x => x.Order).Select(x => x.ConfigurationProvider).ToArray(), _testApplicationModuleInfo, _fileSystem);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.CommandLine;
using Microsoft.Testing.Platform.Helpers;

namespace Microsoft.Testing.Platform.Configurations;
Expand All @@ -19,8 +20,10 @@ internal class EnvironmentVariablesConfigurationSource(IEnvironment environmentV
// Can be empty string because it's not used in the UI
public string Description => string.Empty;

public int Order => 1;

public Task<bool> IsEnabledAsync() => Task.FromResult(true);

public IConfigurationProvider Build()
=> new EnvironmentVariablesConfigurationProvider(_environmentVariables);
public Task<IConfigurationProvider> BuildAsync(CommandLineParseResult commandLineParseResult)
=> Task.FromResult<IConfigurationProvider>(new EnvironmentVariablesConfigurationProvider(_environmentVariables));
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// 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.CommandLine;
using Microsoft.Testing.Platform.Extensions;

namespace Microsoft.Testing.Platform.Configurations;

internal interface IConfigurationSource : IExtension
{
IConfigurationProvider Build();
int Order { get; }

Task<IConfigurationProvider> BuildAsync(CommandLineParseResult commandLineParseResult);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.CommandLine;
using Microsoft.Testing.Platform.Helpers;
using Microsoft.Testing.Platform.Logging;
using Microsoft.Testing.Platform.Services;
Expand All @@ -27,9 +28,11 @@ internal sealed partial class JsonConfigurationSource(ITestApplicationModuleInfo
// Can be empty string because it's not used in the UI
public string Description { get; } = string.Empty;

public int Order => 3;

/// <inheritdoc />
public Task<bool> IsEnabledAsync() => Task.FromResult(true);

public IConfigurationProvider Build()
=> new JsonConfigurationProvider(_testApplicationModuleInfo, _fileSystem, _fileLoggerProvider?.CreateLogger(typeof(JsonConfigurationProvider).ToString()));
public Task<IConfigurationProvider> BuildAsync(CommandLineParseResult commandLineParseResult)
=> Task.FromResult((IConfigurationProvider)new JsonConfigurationProvider(_testApplicationModuleInfo, _fileSystem, _fileLoggerProvider?.CreateLogger(typeof(JsonConfigurationProvider).ToString())));
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ internal interface IFileSystem
IFileStream NewFileStream(string path, FileMode mode, FileAccess access);

string ReadAllText(string path);

Task<string> ReadAllTextAsync(string path);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,15 @@ internal sealed class SystemFileSystem : IFileSystem
public IFileStream NewFileStream(string path, FileMode mode, FileAccess access) => new SystemFileStream(path, mode, access);

public string ReadAllText(string path) => File.ReadAllText(path);

#if NETCOREAPP
public Task<string> ReadAllTextAsync(string path) => File.ReadAllTextAsync(path);
#else
public async Task<string> ReadAllTextAsync(string path)
{
using FileStream stream = new(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan);
using StreamReader reader = new(stream);
return await reader.ReadToEndAsync();
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public async Task<ITestHost> BuildAsync(
Configuration.AddConfigurationSource(() => new JsonConfigurationSource(_testApplicationModuleInfo, _fileSystem, loggingState.FileLoggerProvider));

// Build the IConfiguration - we need special treatment because the configuration is needed by extensions.
var configuration = (AggregatedConfiguration)await ((ConfigurationManager)Configuration).BuildAsync(loggingState.FileLoggerProvider);
var configuration = (AggregatedConfiguration)await ((ConfigurationManager)Configuration).BuildAsync(loggingState.FileLoggerProvider, loggingState.CommandLineParseResult);
serviceProvider.TryAddService(configuration);

// Current test platform is picky on unhandled exception, we will tear down the process in that case.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Text;

using Microsoft.Testing.Platform.CommandLine;
using Microsoft.Testing.Platform.Configurations;
using Microsoft.Testing.Platform.Helpers;
using Microsoft.Testing.Platform.Logging;
Expand Down Expand Up @@ -34,7 +35,7 @@ public async ValueTask GetConfigurationValueFromJson(string jsonFileConfig, stri
CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler());
ConfigurationManager configurationManager = new(fileSystem.Object, testApplicationModuleInfo);
configurationManager.AddConfigurationSource(() => new JsonConfigurationSource(testApplicationModuleInfo, fileSystem.Object, null));
IConfiguration configuration = await configurationManager.BuildAsync(null);
IConfiguration configuration = await configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>()));
Assert.AreEqual(result, configuration[key], $"Expected '{result}' found '{configuration[key]}'");
}

Expand Down Expand Up @@ -64,7 +65,7 @@ public async ValueTask InvalidJson_Fail()
ConfigurationManager configurationManager = new(fileSystem.Object, testApplicationModuleInfo);
configurationManager.AddConfigurationSource(() =>
new JsonConfigurationSource(testApplicationModuleInfo, fileSystem.Object, null));
await Assert.ThrowsAsync<Exception>(() => configurationManager.BuildAsync(null));
await Assert.ThrowsAsync<Exception>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>())));
}

[ArgumentsProvider(nameof(GetConfigurationValueFromJsonData))]
Expand All @@ -90,7 +91,7 @@ public async ValueTask GetConfigurationValueFromJsonWithFileLoggerProvider(strin
configurationManager.AddConfigurationSource(() =>
new JsonConfigurationSource(testApplicationModuleInfo, fileSystem.Object, null));

IConfiguration configuration = await configurationManager.BuildAsync(loggerProviderMock.Object);
IConfiguration configuration = await configurationManager.BuildAsync(loggerProviderMock.Object, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>()));
Assert.AreEqual(result, configuration[key], $"Expected '{result}' found '{configuration[key]}'");

loggerMock.Verify(x => x.LogAsync(LogLevel.Trace, It.IsAny<string>(), null, LoggingExtensions.Formatter), Times.Once);
Expand All @@ -100,7 +101,7 @@ public async ValueTask BuildAsync_EmptyConfigurationSources_ThrowsException()
{
CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler());
ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo);
await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null));
await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>())));
}

public async ValueTask BuildAsync_ConfigurationSourcesNotEnabledAsync_ThrowsException()
Expand All @@ -112,7 +113,7 @@ public async ValueTask BuildAsync_ConfigurationSourcesNotEnabledAsync_ThrowsExce
ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo);
configurationManager.AddConfigurationSource(() => mockConfigurationSource.Object);

await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null));
await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>())));

mockConfigurationSource.Verify(x => x.IsEnabledAsync(), Times.Once);
}
Expand All @@ -131,7 +132,7 @@ public async ValueTask BuildAsync_ConfigurationSourceIsAsyncInitializableExtensi
ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo);
configurationManager.AddConfigurationSource(() => fakeConfigurationSource);

await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null));
await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>())));
}

private class FakeConfigurationSource : IConfigurationSource, IAsyncInitializableExtension
Expand All @@ -146,7 +147,9 @@ private class FakeConfigurationSource : IConfigurationSource, IAsyncInitializabl

public required IConfigurationProvider ConfigurationProvider { get; set; }

public IConfigurationProvider Build() => ConfigurationProvider;
public int Order => 100;

public Task<IConfigurationProvider> BuildAsync(CommandLineParseResult commandLineParseResult) => Task.FromResult(ConfigurationProvider);

public Task InitializeAsync() => Task.CompletedTask;

Expand Down

0 comments on commit 496d67d

Please sign in to comment.