// 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; using Microsoft.Testing.Platform.Resources; using Microsoft.Testing.Platform.Services; using static Microsoft.Testing.Platform.Configurations.JsonConfigurationSource; namespace Microsoft.Testing.Platform.Configurations; 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); internal async Task BuildAsync(IFileLoggerProvider? syncFileLoggerProvider, CommandLineParseResult commandLineParseResult) { List<(IConfigurationProvider ConfigurationProvider, int Order)> configurationProviders = []; JsonConfigurationProvider? defaultJsonConfiguration = null; foreach (Func configurationSource in _configurationSources) { IConfigurationSource serviceInstance = configurationSource(); if (!await serviceInstance.IsEnabledAsync().ConfigureAwait(false)) { continue; } await serviceInstance.TryInitializeAsync().ConfigureAwait(false); IConfigurationProvider configurationProvider = await serviceInstance.BuildAsync(commandLineParseResult).ConfigureAwait(false); await configurationProvider.LoadAsync().ConfigureAwait(false); if (configurationProvider is JsonConfigurationProvider configuration) { defaultJsonConfiguration = configuration; } configurationProviders.Add((configurationProvider, serviceInstance.Order)); } if (defaultJsonConfiguration is null) { throw new InvalidOperationException(PlatformResources.ConfigurationManagerCannotFindDefaultJsonConfigurationErrorMessage); } configurationProviders.Sort(static (a, b) => a.Order.CompareTo(b.Order)); var configurationProvidersArray = new IConfigurationProvider[configurationProviders.Count]; for (int i = 0; i < configurationProvidersArray.Length; i++) { configurationProvidersArray[i] = configurationProviders[i].ConfigurationProvider; } if (syncFileLoggerProvider is not null && defaultJsonConfiguration.ConfigurationFile != null) { ILogger logger = syncFileLoggerProvider.CreateLogger(nameof(ConfigurationManager)); if (logger.IsEnabled(LogLevel.Trace)) { try { using IFileStream configFileStream = _fileSystem.NewFileStream(defaultJsonConfiguration.ConfigurationFile, FileMode.Open, FileAccess.Read); StreamReader streamReader = new(configFileStream.Stream); await logger.LogTraceAsync($"Configuration file ('{defaultJsonConfiguration.ConfigurationFile}') content:\n{await streamReader.ReadToEndAsync().ConfigureAwait(false)}").ConfigureAwait(false); } catch (Exception ex) { // The trace-level dump is a diagnostic aid only; never let an I/O failure here // break the configuration pipeline. await logger.LogTraceAsync($"Failed to dump configuration file ('{defaultJsonConfiguration.ConfigurationFile}') content: {ex.GetType().FullName}: {ex.Message}").ConfigureAwait(false); } } } return new AggregatedConfiguration(configurationProvidersArray, _testApplicationModuleInfo, _fileSystem, _environment, commandLineParseResult); } }