// 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.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; using DebuggerLaunchMode = Microsoft.VisualStudio.TestTools.UnitTesting.DebuggerLaunchMode; using MessageLevel = Microsoft.VisualStudio.TestTools.UnitTesting.MessageLevel; using StringEx = Microsoft.VisualStudio.TestTools.UnitTesting.StringEx; namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; internal sealed partial class MSTestSettings { public static void PopulateSettings(MSTestSettings settings) { CurrentSettings.AssemblyCleanupTimeout = settings.AssemblyCleanupTimeout; CurrentSettings.AssemblyInitializeTimeout = settings.AssemblyInitializeTimeout; CurrentSettings.ClassCleanupTimeout = settings.ClassCleanupTimeout; CurrentSettings.ClassInitializeTimeout = settings.ClassInitializeTimeout; CurrentSettings.ConsiderEmptyDataSourceAsInconclusive = settings.ConsiderEmptyDataSourceAsInconclusive; CurrentSettings.CooperativeCancellationTimeout = settings.CooperativeCancellationTimeout; CurrentSettings.DisableParallelization = settings.DisableParallelization; CurrentSettings.MapInconclusiveToFailed = settings.MapInconclusiveToFailed; CurrentSettings.MapNotRunnableToFailed = settings.MapNotRunnableToFailed; CurrentSettings.OrderTestsByNameInClass = settings.OrderTestsByNameInClass; CurrentSettings.OutputCaptureMode = settings.OutputCaptureMode; CurrentSettings.RandomizeTestOrder = settings.RandomizeTestOrder; CurrentSettings.RandomTestOrderSeed = settings.RandomTestOrderSeed; CurrentSettings.ParallelizationScope = settings.ParallelizationScope; CurrentSettings.ParallelizationWorkers = settings.ParallelizationWorkers; CurrentSettings.TestCleanupTimeout = settings.TestCleanupTimeout; CurrentSettings.TestInitializeTimeout = settings.TestInitializeTimeout; CurrentSettings.GlobalTestCleanupTimeout = settings.GlobalTestCleanupTimeout; CurrentSettings.GlobalTestInitializeTimeout = settings.GlobalTestInitializeTimeout; CurrentSettings.TestTimeout = settings.TestTimeout; CurrentSettings.TreatDiscoveryWarningsAsErrors = settings.TreatDiscoveryWarningsAsErrors; CurrentSettings.LaunchDebuggerOnAssertionFailure = settings.LaunchDebuggerOnAssertionFailure; CurrentSettings.CaptureAssertionFailureDiagnostics = settings.CaptureAssertionFailureDiagnostics; } #if !WINDOWS_UWP private static bool RunSettingsFileHasMSTestSettings(string? runSettingsXml) { if (StringEx.IsNullOrWhiteSpace(runSettingsXml)) { return false; } using var stringReader = new StringReader(runSettingsXml); var reader = XmlReader.Create(stringReader, RunSettingsUtilities.ReaderSettings); XmlReaderUtilities.ReadToRootNode(reader); reader.ReadToNextElement(); while (!string.Equals(reader.Name, SettingsName, StringComparison.OrdinalIgnoreCase) && !string.Equals(reader.Name, SettingsNameAlias, StringComparison.OrdinalIgnoreCase) && !reader.EOF) { reader.SkipToNextElement(); } return !reader.EOF; } #endif internal static MSTestSettings? GetSettings(string? runSettingsXml, string settingName, IAdapterMessageLogger? logger) { if (StringEx.IsNullOrWhiteSpace(runSettingsXml)) { return null; } using var stringReader = new StringReader(runSettingsXml); var reader = XmlReader.Create(stringReader, RunSettingsUtilities.ReaderSettings); XmlReaderUtilities.ReadToRootNode(reader); reader.ReadToNextElement(); while (!string.Equals(reader.Name, settingName, StringComparison.OrdinalIgnoreCase) && !reader.EOF) { reader.SkipToNextElement(); } return !reader.EOF ? ToSettings(reader.ReadSubtree(), logger) : null; } private static MSTestSettings ToSettings(XmlReader reader, IAdapterMessageLogger? logger) { if (reader is null) { throw new ArgumentNullException(nameof(reader)); } MSTestSettings settings = new(); reader.ReadToNextElement(); if (!reader.IsEmptyElement) { reader.Read(); while (reader.NodeType == XmlNodeType.Element) { string elementName = reader.Name.ToUpperInvariant(); switch (elementName) { case "CAPTURETRACEOUTPUT": ParseCaptureTraceOutputSetting(reader.ReadInnerXml(), "CaptureTraceOutput", logger, v => settings.OutputCaptureMode = v); break; case "MAPINCONCLUSIVETOFAILED": ParseBoolSetting(reader.ReadInnerXml(), "MapInconclusiveToFailed", logger, v => settings.MapInconclusiveToFailed = v); break; case "MAPNOTRUNNABLETOFAILED": ParseBoolSetting(reader.ReadInnerXml(), "MapNotRunnableToFailed", logger, v => settings.MapNotRunnableToFailed = v); break; case "TREATDISCOVERYWARNINGSASERRORS": ParseBoolSetting(reader.ReadInnerXml(), "TreatDiscoveryWarningsAsErrors", logger, v => settings.TreatDiscoveryWarningsAsErrors = v); break; case "PARALLELIZE": SetParallelSettings(reader.ReadSubtree(), settings); reader.SkipToNextElement(); break; case "TESTTIMEOUT": ParseTimeoutSetting(reader.ReadInnerXml(), "TestTimeout", logger, v => settings.TestTimeout = v); break; case "ASSEMBLYCLEANUPTIMEOUT": ParseTimeoutSetting(reader.ReadInnerXml(), "AssemblyCleanupTimeout", logger, v => settings.AssemblyCleanupTimeout = v); break; case "CONSIDEREMPTYDATASOURCEASINCONCLUSIVE": ParseBoolSetting(reader.ReadInnerXml(), "ConsiderEmptyDataSourceAsInconclusive", logger, v => settings.ConsiderEmptyDataSourceAsInconclusive = v); break; case "ASSEMBLYINITIALIZETIMEOUT": ParseTimeoutSetting(reader.ReadInnerXml(), "AssemblyInitializeTimeout", logger, v => settings.AssemblyInitializeTimeout = v); break; case "CLASSINITIALIZETIMEOUT": ParseTimeoutSetting(reader.ReadInnerXml(), "ClassInitializeTimeout", logger, v => settings.ClassInitializeTimeout = v); break; case "CLASSCLEANUPTIMEOUT": ParseTimeoutSetting(reader.ReadInnerXml(), "ClassCleanupTimeout", logger, v => settings.ClassCleanupTimeout = v); break; case "TESTINITIALIZETIMEOUT": ParseTimeoutSetting(reader.ReadInnerXml(), "TestInitializeTimeout", logger, v => settings.TestInitializeTimeout = v); break; case "TESTCLEANUPTIMEOUT": ParseTimeoutSetting(reader.ReadInnerXml(), "TestCleanupTimeout", logger, v => settings.TestCleanupTimeout = v); break; case "GLOBALTESTINITIALIZETIMEOUT": ParseTimeoutSetting(reader.ReadInnerXml(), "GlobalTestInitializeTimeout", logger, v => settings.GlobalTestInitializeTimeout = v); break; case "GLOBALTESTCLEANUPTIMEOUT": ParseTimeoutSetting(reader.ReadInnerXml(), "GlobalTestCleanupTimeout", logger, v => settings.GlobalTestCleanupTimeout = v); break; case "COOPERATIVECANCELLATIONTIMEOUT": ParseBoolSetting(reader.ReadInnerXml(), "CooperativeCancellationTimeout", logger, v => settings.CooperativeCancellationTimeout = v); break; case "ORDERTESTSBYNAMEINCLASS": ParseBoolSetting(reader.ReadInnerXml(), "OrderTestsByNameInClass", logger, v => settings.OrderTestsByNameInClass = v); break; case "RANDOMIZETESTORDER": ParseBoolSetting(reader.ReadInnerXml(), "RandomizeTestOrder", logger, v => settings.RandomizeTestOrder = v); break; case "RANDOMTESTORDERSEED": string randomTestOrderSeed = reader.ReadInnerXml(); if (int.TryParse(randomTestOrderSeed, NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsedRandomTestOrderSeed)) { settings.RandomTestOrderSeed = parsedRandomTestOrderSeed; } else { logger?.SendMessage(MessageLevel.Warning, string.Format(CultureInfo.CurrentCulture, Resource.InvalidValue, randomTestOrderSeed, "RandomTestOrderSeed")); } break; case "LAUNCHDEBUGGERONASSERTIONFAILURE": string launchDebuggerOnAssertionFailure = reader.ReadInnerXml(); if (TryParseEnum(launchDebuggerOnAssertionFailure, out DebuggerLaunchMode mode)) { settings.LaunchDebuggerOnAssertionFailure = mode; } else { logger?.SendMessage(MessageLevel.Warning, string.Format(CultureInfo.CurrentCulture, Resource.InvalidValue, launchDebuggerOnAssertionFailure, "LaunchDebuggerOnAssertionFailure")); } break; case "CAPTUREASSERTIONFAILUREDIAGNOSTICS": ParseBoolSetting(reader.ReadInnerXml(), "CaptureAssertionFailureDiagnostics", logger, v => settings.CaptureAssertionFailureDiagnostics = v); break; default: PlatformServiceProvider.Instance.SettingsProvider.Load(reader.ReadSubtree()); reader.SkipToNextElement(); break; } } } return settings; } private static void ParseBoolSetting(string rawValue, string settingName, IAdapterMessageLogger? logger, Action setSetting) { if (bool.TryParse(rawValue, out bool result)) { setSetting(result); } else { logger?.SendMessage(MessageLevel.Warning, string.Format(CultureInfo.CurrentCulture, Resource.InvalidValue, rawValue, settingName)); } } private static void ParseCaptureTraceOutputSetting(string rawValue, string settingName, IAdapterMessageLogger? logger, Action setSetting) { // Accept the legacy boolean spelling (true -> Result, false -> None) as well as the // TestOutputCaptureMode enum names (None/Result/Live) so existing runsettings keep working. if (bool.TryParse(rawValue, out bool boolResult)) { setSetting(boolResult ? TestOutputCaptureMode.Result : TestOutputCaptureMode.None); } else if (TryParseCaptureMode(rawValue, out TestOutputCaptureMode mode)) { setSetting(mode); } else { logger?.SendMessage(MessageLevel.Warning, string.Format(CultureInfo.CurrentCulture, Resource.InvalidValue, rawValue, settingName)); } } private static bool TryParseCaptureMode(string value, out TestOutputCaptureMode mode) { // Enum.TryParse (used by TryParseEnum) also accepts numeric strings such as "2", which would // silently enable an unexpected mode. Reject purely numeric input so only the documented values // (the legacy booleans and the TestOutputCaptureMode names) are honored. if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out _)) { mode = default; return false; } return TryParseEnum(value, out mode); } private static void ParseTimeoutSetting(string rawValue, string settingName, IAdapterMessageLogger? logger, Action setSetting) { if (int.TryParse(rawValue, out int result) && result > 0) { setSetting(result); } else { logger?.SendMessage(MessageLevel.Warning, string.Format(CultureInfo.CurrentCulture, Resource.InvalidTimeoutValue, rawValue, settingName)); } } private static bool TryParseEnum(string value, out T result) where T : struct, Enum => Enum.TryParse(value, true, out result) #if NETCOREAPP && Enum.IsDefined(result); #else && Enum.IsDefined(typeof(T), result); #endif }