File: RunSettings\RunConfiguration.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.ObjectModel\Microsoft.TestPlatform.ObjectModel.csproj (Microsoft.VisualStudio.TestPlatform.ObjectModel)
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Xml;

using Microsoft.VisualStudio.TestPlatform.CoreUtilities;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions;
using Microsoft.VisualStudio.TestPlatform.Utilities;

namespace Microsoft.VisualStudio.TestPlatform.ObjectModel;

/// <summary>
/// Stores information about a test settings.
/// </summary>
public class RunConfiguration : TestRunSettings
{
    /// <summary>
    /// Platform architecture which rocksteady should use for discovery/execution
    /// </summary>
    private Architecture _platform;

    private Architecture? _defaultPlatform;

    /// <summary>
    /// Maximum number of cores that the engine can use to run tests in parallel
    /// </summary>
    private int _maxCpuCount;

    /// <summary>
    /// .Net framework which rocksteady should use for discovery/execution
    /// </summary>
    private Framework? _framework;

    /// <summary>
    /// Specifies the frequency of the runStats/discoveredTests event
    /// </summary>
    private long _batchSize;

    /// <summary>
    /// Directory in which rocksteady/adapter should keep their run specific data.
    /// </summary>
    private string _resultsDirectory;

    /// <summary>
    /// Paths at which rocksteady should look for test adapters
    /// </summary>
    private string? _testAdaptersPaths;

    /// <summary>
    /// Indication to adapters to disable app domain.
    /// </summary>
    private bool _disableAppDomain;

    /// <summary>
    /// Indication to adapters to disable parallelization.
    /// </summary>
    private bool _disableParallelization;

    /// <summary>
    /// True if test run is triggered
    /// </summary>
    private bool _designMode;

    /// <summary>
    /// False indicates that the test adapter should not collect source information for discovered tests
    /// </summary>
    private bool _shouldCollectSourceInformation;

    /// <summary>
    /// Initializes a new instance of the <see cref="RunConfiguration"/> class.
    /// </summary>
    public RunConfiguration() : base(Constants.RunConfigurationSettingsName)
    {
        // Set defaults for target platform, framework version type and results directory.
        _platform = Constants.DefaultPlatform;
        _framework = Framework.DefaultFramework;
        _resultsDirectory = Constants.DefaultResultsDirectory;
        SolutionDirectory = null;
        TreatTestAdapterErrorsAsWarnings = Constants.DefaultTreatTestAdapterErrorsAsWarnings;
        BinariesRoot = null;
        _testAdaptersPaths = null;
        _maxCpuCount = Constants.DefaultCpuCount;
        _batchSize = Constants.DefaultBatchSize;
        TestSessionTimeout = 0;
        _disableAppDomain = false;
        _disableParallelization = false;
        _designMode = false;
        InIsolation = false;
        _shouldCollectSourceInformation = false;
        TargetDevice = null;
        ExecutionThreadApartmentState = Constants.DefaultExecutionThreadApartmentState;
        CaptureStandardOutput = !FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_STANDARD_OUTPUT_CAPTURING);
        ForwardStandardOutput = !FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING);
        DisableSharedTestHost = FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST);
        CreateNoNewWindow = true;
    }

    /// <summary>
    /// Gets or sets the solution directory.
    /// </summary>
    public string? SolutionDirectory
    {
        get;
        set;
    }

    /// <summary>
    /// Gets or sets the results directory.
    /// </summary>
    public string ResultsDirectory
    {
        get => _resultsDirectory;

        set
        {
            _resultsDirectory = value;
            ResultsDirectorySet = true;
        }
    }

    /// <summary>
    /// Gets or sets the Parallel execution option. Should be non-negative integer.
    /// </summary>
    public int MaxCpuCount
    {
        get => _maxCpuCount;
        set
        {
            _maxCpuCount = value;
            MaxCpuCountSet = true;
        }
    }

    /// <summary>
    /// Gets or sets the frequency of the runStats/discoveredTests event. Should be non-negative integer.
    /// </summary>
    public long BatchSize
    {
        get => _batchSize;
        set
        {
            _batchSize = value;
            BatchSizeSet = true;
        }
    }

    /// <summary>
    /// Gets or sets the testSessionTimeout. Should be non-negative integer.
    /// </summary>
    public long TestSessionTimeout { get; set; }

    /// <summary>
    /// Gets or sets the design mode value.
    /// </summary>
    public bool DesignMode
    {
        get => _designMode;

        set
        {
            _designMode = value;
            DesignModeSet = true;
        }
    }

    /// <summary>
    /// Gets or sets a value indicating whether to run tests in isolation or not.
    /// </summary>
    public bool InIsolation { get; set; }

    /// <summary>
    /// Gets a value indicating whether test adapter needs to collect source information for discovered tests
    /// </summary>
    public bool ShouldCollectSourceInformation
    {
        get => (CollectSourceInformationSet) ? _shouldCollectSourceInformation : _designMode;

        set
        {
            _shouldCollectSourceInformation = value;
            CollectSourceInformationSet = true;
        }
    }

    /// <summary>
    /// Gets or sets a value indicating whether app domain creation should be disabled.
    /// </summary>
    public bool DisableAppDomain
    {
        get => _disableAppDomain;

        set
        {
            _disableAppDomain = value;
            DisableAppDomainSet = true;
        }
    }

    /// <summary>
    /// Gets or sets the test adapter loading strategy.
    /// </summary>
    internal TestAdapterLoadingStrategy TestAdapterLoadingStrategy { get; set; }

    /// <summary>
    /// Gets a value indicating whether parallelism needs to be disabled by the adapters.
    /// </summary>
    public bool DisableParallelization
    {
        get => _disableParallelization;

        set
        {
            _disableParallelization = value;
            DisableParallelizationSet = true;
        }
    }

    /// <summary>
    /// Gets or sets the Target platform this run is targeting. Possible values are <see cref="Architecture"/> except for AnyCPU and Default.
    /// </summary>
    public Architecture TargetPlatform
    {
        get => _platform;

        set
        {
            _platform = value;
            TargetPlatformSet = true;
        }
    }

    /// <summary>
    /// Gets or sets the default platform that will be used for AnyCPU sources, or non-dll sources. Possible values are <see cref="Architecture"/> except for AnyCPU and Default.
    /// </summary>
    public Architecture? DefaultPlatform
    {
        get => _defaultPlatform;

        set
        {
            _defaultPlatform = value;
            DefaultPlatformSet = true;
        }
    }

    /// <summary>
    /// Gets or sets the target Framework this run is targeting.
    /// </summary>
    public Framework? TargetFramework
    {
        get => _framework;

        set
        {
            _framework = value;
            TargetFrameworkSet = true;
        }
    }
    /// <summary>
    /// Gets or sets value indicating exit code when no tests are discovered or executed
    /// </summary>
    public bool TreatNoTestsAsError
    {
        get;
        set;
    }

    /// <summary>
    /// Gets or sets the target Framework this run is targeting. Possible values are Framework3.5|Framework4.0|Framework4.5
    /// </summary>
    [Obsolete("Use TargetFramework instead")]
    public FrameworkVersion TargetFrameworkVersion
    {
        get => _framework?.Name switch
        {
            Constants.DotNetFramework35 => FrameworkVersion.Framework35,
            Constants.DotNetFramework40 => FrameworkVersion.Framework40,
            Constants.DotNetFramework45 => FrameworkVersion.Framework45,
            Constants.DotNetFrameworkCore10 => FrameworkVersion.FrameworkCore10,
            Constants.DotNetFrameworkUap10 => FrameworkVersion.FrameworkUap10,
            _ => Constants.DefaultFramework,
        };

        set
        {
            _framework = Framework.FromString(value.ToString());
            TargetFrameworkSet = true;
        }
    }

    /// <summary>
    /// Gets or sets the target device IP. For Phone this value is Device, for emulators "Mobile Emulator 10.0.15063.0 WVGA 4 inch 1GB"
    /// </summary>
    public string? TargetDevice { get; set; }

    /// <summary>
    /// Gets or sets the paths used for test adapters lookup in test platform.
    /// </summary>
    public string? TestAdaptersPaths
    {
        get => _testAdaptersPaths;

        set
        {
            _testAdaptersPaths = value;

            if (_testAdaptersPaths != null)
            {
                TestAdaptersPathsSet = true;
            }
        }
    }

    /// <summary>
    /// Gets or sets the execution thread apartment state.
    /// </summary>
    [CLSCompliant(false)]
    public PlatformApartmentState ExecutionThreadApartmentState { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether to treat the errors from test adapters as warnings.
    /// </summary>
    public bool TreatTestAdapterErrorsAsWarnings
    {
        get;
        set;
    }

    /// <summary>
    /// Gets a value indicating whether target platform set.
    /// </summary>
    public bool TargetPlatformSet
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets a value indicating whether default platform is set.
    /// </summary>
    public bool DefaultPlatformSet
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets a value indicating whether maximum parallelization count is set.
    /// </summary>
    public bool MaxCpuCountSet
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets a value indicating batch size is set
    /// </summary>
    public bool BatchSizeSet
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets a value indicating whether design mode is set.
    /// </summary>
    public bool DesignModeSet
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets a value indicating whether disable appdomain is set.
    /// </summary>
    public bool DisableAppDomainSet
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets a value indicating whether parallelism needs to be disabled by the adapters.
    /// </summary>
    public bool DisableParallelizationSet
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets a value indicating whether target framework set.
    /// </summary>
    public bool TargetFrameworkSet
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets a value indicating whether test adapters paths set.
    /// </summary>
    [MemberNotNullWhen(true, nameof(TestAdaptersPaths))]
    public bool TestAdaptersPathsSet
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets a value indicating whether results directory is set.
    /// </summary>
    public bool ResultsDirectorySet
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets the binaries root.
    /// </summary>
    public string? BinariesRoot { get; private set; }

    /// <summary>
    /// Collect source information
    /// </summary>
    public bool CollectSourceInformationSet { get; private set; }

    /// <summary>
    /// Default filter to use to filter tests
    /// </summary>
    public string? TestCaseFilter { get; private set; }

    /// <summary>
    /// Path to dotnet executable to be used to invoke testhost.dll. Specifying this will skip looking up testhost.exe and will force usage of the testhost.dll.
    /// </summary>
    public string? DotnetHostPath { get; private set; }

    /// <summary>
    /// When true, we capture standard output of child processes. When false the standard output is not captured and it will end up in command line.
    /// This makes the output visible to the user when running in vstest.console in-process. Such setup makes the behavior the same as in 17.6.3 and earlier.
    /// 
    /// The recommended way is to use this with ForwardStandardOutput=true to forward output as informational messages so the output is always visible in console and VS,
    /// unless the logging level is set to Warning or higher.
    ///
    /// Lastly this can be used with ForwardStandardOutput=false, to suppress the output in console, which is behavior of 17.7.0 till now.
    /// </summary>
    public bool CaptureStandardOutput { get; private set; }

    /// <summary>
    /// Forward captured standard output of testhost as Informational test messages. Default is true. Needs CaptureStandardOutput to be true.
    /// </summary>
    public bool ForwardStandardOutput { get; private set; }

    /// <summary>
    /// When true, the testhost process is started with CreateNoNewWindow, preventing it from opening a new console window.
    /// When false, the testhost process is allowed to create a new window, which enables child processes and native applications
    /// to propagate their console output. Default is true (preserving existing behavior).
    /// </summary>
    public bool CreateNoNewWindow { get; private set; }

    /// <summary>
    /// Disables sharing of .NET Framework testhosts.
    /// </summary>
    public bool DisableSharedTestHost { get; private set; }

    /// <summary>
    /// Skips passing VisualStudio built in adapters to the project.
    /// </summary>
    public bool SkipDefaultAdapters { get; private set; }

    /// <inheritdoc/>
    public override XmlElement ToXml()
    {
        XmlDocument doc = new();

        XmlElement root = doc.CreateElement(Constants.RunConfigurationSettingsName);

        XmlElement resultDirectory = doc.CreateElement("ResultsDirectory");
        resultDirectory.InnerXml = ResultsDirectory;
        root.AppendChild(resultDirectory);

        XmlElement targetPlatform = doc.CreateElement("TargetPlatform");
        targetPlatform.InnerXml = TargetPlatform.ToString();
        root.AppendChild(targetPlatform);

        if (DefaultPlatform != null)
        {
            XmlElement defaultPlatform = doc.CreateElement("DefaultPlatform");
            defaultPlatform.InnerXml = DefaultPlatform.ToString()!;
            root.AppendChild(defaultPlatform);
        }

        XmlElement maxCpuCount = doc.CreateElement("MaxCpuCount");
        maxCpuCount.InnerXml = MaxCpuCount.ToString(CultureInfo.CurrentCulture);
        root.AppendChild(maxCpuCount);

        XmlElement batchSize = doc.CreateElement("BatchSize");
        batchSize.InnerXml = BatchSize.ToString(CultureInfo.CurrentCulture);
        root.AppendChild(batchSize);

        XmlElement testSessionTimeout = doc.CreateElement("TestSessionTimeout");
        testSessionTimeout.InnerXml = TestSessionTimeout.ToString(CultureInfo.CurrentCulture);
        root.AppendChild(testSessionTimeout);

        XmlElement designMode = doc.CreateElement("DesignMode");
        designMode.InnerXml = DesignMode.ToString();
        root.AppendChild(designMode);

        XmlElement inIsolation = doc.CreateElement("InIsolation");
        inIsolation.InnerXml = InIsolation.ToString();
        root.AppendChild(inIsolation);

        XmlElement collectSourceInformation = doc.CreateElement("CollectSourceInformation");
        collectSourceInformation.InnerXml = ShouldCollectSourceInformation.ToString();
        root.AppendChild(collectSourceInformation);

        XmlElement disableAppDomain = doc.CreateElement("DisableAppDomain");
        disableAppDomain.InnerXml = DisableAppDomain.ToString();
        root.AppendChild(disableAppDomain);

        XmlElement disableParallelization = doc.CreateElement("DisableParallelization");
        disableParallelization.InnerXml = DisableParallelization.ToString();
        root.AppendChild(disableParallelization);

        XmlElement targetFrameworkVersion = doc.CreateElement("TargetFrameworkVersion");
        targetFrameworkVersion.InnerXml = TargetFramework?.ToString()!;
        root.AppendChild(targetFrameworkVersion);

        XmlElement executionThreadApartmentState = doc.CreateElement("ExecutionThreadApartmentState");
        executionThreadApartmentState.InnerXml = ExecutionThreadApartmentState.ToString();
        root.AppendChild(executionThreadApartmentState);

        if (TestAdaptersPaths != null)
        {
            XmlElement testAdaptersPaths = doc.CreateElement("TestAdaptersPaths");
            testAdaptersPaths.InnerXml = TestAdaptersPaths;
            root.AppendChild(testAdaptersPaths);
        }

        if (TestAdapterLoadingStrategy != TestAdapterLoadingStrategy.Default)
        {
            XmlElement adapterLoadingStrategy = doc.CreateElement("TestAdapterLoadingStrategy");
            adapterLoadingStrategy.InnerXml = TestAdapterLoadingStrategy.ToString();
            root.AppendChild(adapterLoadingStrategy);
        }

        XmlElement treatTestAdapterErrorsAsWarnings = doc.CreateElement("TreatTestAdapterErrorsAsWarnings");
        treatTestAdapterErrorsAsWarnings.InnerXml = TreatTestAdapterErrorsAsWarnings.ToString();
        root.AppendChild(treatTestAdapterErrorsAsWarnings);

        if (BinariesRoot != null)
        {
            XmlElement binariesRoot = doc.CreateElement("BinariesRoot");
            binariesRoot.InnerXml = BinariesRoot;
            root.AppendChild(binariesRoot);
        }

        if (!StringUtils.IsNullOrEmpty(TargetDevice))
        {
            XmlElement targetDevice = doc.CreateElement("TargetDevice");
            targetDevice.InnerXml = TargetDevice;
            root.AppendChild(targetDevice);
        }

        if (!StringUtils.IsNullOrEmpty(TestCaseFilter))
        {
            XmlElement testCaseFilter = doc.CreateElement(nameof(TestCaseFilter));
            testCaseFilter.InnerXml = TestCaseFilter;
            root.AppendChild(testCaseFilter);
        }

        if (!StringUtils.IsNullOrEmpty(DotnetHostPath))
        {
            XmlElement dotnetHostPath = doc.CreateElement(nameof(DotnetHostPath));
            dotnetHostPath.InnerXml = DotnetHostPath;
            root.AppendChild(dotnetHostPath);
        }

        if (TreatNoTestsAsError)
        {
            XmlElement treatAsError = doc.CreateElement(nameof(TreatNoTestsAsError));
            treatAsError.InnerText = TreatNoTestsAsError.ToString();
            root.AppendChild(treatAsError);
        }

        XmlElement captureStandardOutput = doc.CreateElement(nameof(CaptureStandardOutput));
        captureStandardOutput.InnerXml = CaptureStandardOutput.ToString();
        root.AppendChild(captureStandardOutput);

        XmlElement forwardStandardOutput = doc.CreateElement(nameof(ForwardStandardOutput));
        forwardStandardOutput.InnerXml = ForwardStandardOutput.ToString();
        root.AppendChild(forwardStandardOutput);

        XmlElement createNoNewWindow = doc.CreateElement(nameof(CreateNoNewWindow));
        createNoNewWindow.InnerXml = CreateNoNewWindow.ToString();
        root.AppendChild(createNoNewWindow);

        XmlElement disableSharedTesthost = doc.CreateElement(nameof(DisableSharedTestHost));
        disableSharedTesthost.InnerXml = DisableSharedTestHost.ToString();
        root.AppendChild(disableSharedTesthost);

        XmlElement skipDefaultAdapters = doc.CreateElement(nameof(SkipDefaultAdapters));
        skipDefaultAdapters.InnerXml = SkipDefaultAdapters.ToString();
        root.AppendChild(skipDefaultAdapters);

        return root;
    }

    /// <summary>
    /// Loads RunConfiguration from XmlReader.
    /// </summary>
    /// <param name="reader">XmlReader having run configuration node.</param>
    /// <returns></returns>
    public static RunConfiguration FromXml(XmlReader reader)
    {
        ValidateArg.NotNull(reader, nameof(reader));
        var runConfiguration = new RunConfiguration();
        var empty = reader.IsEmptyElement;

        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);

        // Process the fields in Xml elements
        reader.Read();
        if (!empty)
        {
            while (reader.NodeType == XmlNodeType.Element)
            {
                string elementName = reader.Name;
                // TODO: make run settings nodes case insensitive?
                switch (elementName)
                {
                    case "ResultsDirectory":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);

                        string resultsDir = reader.ReadElementContentAsString();
                        if (StringUtils.IsNullOrEmpty(resultsDir))
                        {
                            throw new SettingsException(
                                string.Format(
                                    CultureInfo.CurrentCulture,
                                    Resources.Resources.InvalidSettingsIncorrectValue,
                                    Constants.RunConfigurationSettingsName,
                                    resultsDir,
                                    elementName));
                        }

                        runConfiguration.ResultsDirectory = resultsDir;
                        break;

                    case "CollectSourceInformation":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        string collectSourceInformationStr = reader.ReadElementContentAsString();

                        bool bCollectSourceInformation;
                        if (!bool.TryParse(collectSourceInformationStr, out bCollectSourceInformation))
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, bCollectSourceInformation, elementName));
                        }

                        runConfiguration.ShouldCollectSourceInformation = bCollectSourceInformation;
                        break;

                    case "MaxCpuCount":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);

                        string cpuCount = reader.ReadElementContentAsString();
                        if (!int.TryParse(cpuCount, out int count) || count < 0)
                        {
                            throw new SettingsException(
                                string.Format(
                                    CultureInfo.CurrentCulture,
                                    Resources.Resources.InvalidSettingsIncorrectValue,
                                    Constants.RunConfigurationSettingsName,
                                    cpuCount,
                                    elementName));
                        }

                        runConfiguration.MaxCpuCount = count;
                        break;

                    case "BatchSize":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);

                        string batchSize = reader.ReadElementContentAsString();
                        if (!long.TryParse(batchSize, out long size) || size < 0)
                        {
                            throw new SettingsException(
                                string.Format(
                                    CultureInfo.CurrentCulture,
                                    Resources.Resources.InvalidSettingsIncorrectValue,
                                    Constants.RunConfigurationSettingsName,
                                    batchSize,
                                    elementName));
                        }

                        runConfiguration.BatchSize = size;
                        break;

                    case "TestSessionTimeout":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);

                        string testSessionTimeout = reader.ReadElementContentAsString();
                        if (!long.TryParse(testSessionTimeout, out long sessionTimeout) || sessionTimeout < 0)
                        {
                            throw new SettingsException(
                                string.Format(
                                    CultureInfo.CurrentCulture,
                                    Resources.Resources.InvalidSettingsIncorrectValue,
                                    Constants.RunConfigurationSettingsName,
                                    testSessionTimeout,
                                    elementName));
                        }

                        runConfiguration.TestSessionTimeout = sessionTimeout;
                        break;

                    case "DesignMode":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);

                        string designModeValueString = reader.ReadElementContentAsString();
                        if (!bool.TryParse(designModeValueString, out bool designMode))
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, designModeValueString, elementName));
                        }
                        runConfiguration.DesignMode = designMode;
                        break;

                    case "InIsolation":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);

                        string inIsolationValueString = reader.ReadElementContentAsString();
                        if (!bool.TryParse(inIsolationValueString, out bool inIsolation))
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, inIsolationValueString, elementName));
                        }
                        runConfiguration.InIsolation = inIsolation;
                        break;

                    case "DisableAppDomain":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);

                        string disableAppDomainValueString = reader.ReadElementContentAsString();
                        if (!bool.TryParse(disableAppDomainValueString, out bool disableAppDomainCheck))
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, disableAppDomainValueString, elementName));
                        }
                        runConfiguration.DisableAppDomain = disableAppDomainCheck;
                        break;

                    case "DisableParallelization":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);

                        string disableParallelizationValueString = reader.ReadElementContentAsString();
                        if (!bool.TryParse(disableParallelizationValueString, out bool disableParallelizationCheck))
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, disableParallelizationValueString, elementName));
                        }
                        runConfiguration.DisableParallelization = disableParallelizationCheck;
                        break;

                    case "TargetPlatform":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        Architecture archType;
                        string value = reader.ReadElementContentAsString();
                        try
                        {
                            archType = (Architecture)Enum.Parse(typeof(Architecture), value, true);
                            // Ensure that the parsed value is actually in the enum, and that Default or AnyCpu are not provided.
                            if (!Enum.IsDefined(typeof(Architecture), archType) || Architecture.Default == archType || Architecture.AnyCPU == archType)
                            {
                                throw new SettingsException(
                                    string.Format(
                                        CultureInfo.CurrentCulture,
                                        Resources.Resources.InvalidSettingsIncorrectValue,
                                        Constants.RunConfigurationSettingsName,
                                        value,
                                        elementName));
                            }
                        }
                        catch (ArgumentException)
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, value, elementName));
                        }

                        runConfiguration.TargetPlatform = archType;
                        break;

                    case nameof(DefaultPlatform):
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        Architecture defaultArchType;
                        string defaultPlatformValue = reader.ReadElementContentAsString();
                        try
                        {
                            defaultArchType = (Architecture)Enum.Parse(typeof(Architecture), defaultPlatformValue, true);
                            // Ensure that the parsed value is actually in the enum, and that Default or AnyCpu are not provided.
                            if (!Enum.IsDefined(typeof(Architecture), defaultArchType) || Architecture.Default == defaultArchType || Architecture.AnyCPU == defaultArchType)
                            {
                                throw new SettingsException(
                                    string.Format(
                                        CultureInfo.CurrentCulture,
                                        Resources.Resources.InvalidSettingsIncorrectValue,
                                        Constants.RunConfigurationSettingsName,
                                        defaultPlatformValue,
                                        elementName));
                            }
                        }
                        catch (ArgumentException)
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, defaultPlatformValue, elementName));
                        }

                        runConfiguration.DefaultPlatform = defaultArchType;
                        break;

                    case "TargetFrameworkVersion":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        Framework? frameworkType;
                        value = reader.ReadElementContentAsString();
                        try
                        {
                            frameworkType = Framework.FromString(value);

                            if (frameworkType == null)
                            {
                                throw new SettingsException(
                                    string.Format(
                                        CultureInfo.CurrentCulture,
                                        Resources.Resources.InvalidSettingsIncorrectValue,
                                        Constants.RunConfigurationSettingsName,
                                        value,
                                        elementName));
                            }
                        }
                        catch (ArgumentException)
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, value, elementName));
                        }

                        runConfiguration.TargetFramework = frameworkType;
                        break;

                    case "TestAdaptersPaths":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        runConfiguration.TestAdaptersPaths = reader.ReadElementContentAsString();
                        break;

                    case "TestAdapterLoadingStrategy":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        value = reader.ReadElementContentAsString();
                        runConfiguration.TestAdapterLoadingStrategy = Enum.TryParse<TestAdapterLoadingStrategy>(value, out var loadingStrategy)
                            ? loadingStrategy
                            : throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                    Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, value, elementName));

                        break;

                    case "TreatTestAdapterErrorsAsWarnings":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        bool treatTestAdapterErrorsAsWarnings;

                        value = reader.ReadElementContentAsString();

                        try
                        {
                            treatTestAdapterErrorsAsWarnings = bool.Parse(value);
                        }
                        catch (ArgumentException)
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, value, elementName));
                        }
                        catch (FormatException)
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, value, elementName));
                        }

                        runConfiguration.TreatTestAdapterErrorsAsWarnings = treatTestAdapterErrorsAsWarnings;
                        break;

                    case "SolutionDirectory":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        string? solutionDirectory = reader.ReadElementContentAsString();
                        solutionDirectory = Environment.ExpandEnvironmentVariables(solutionDirectory);

                        if (solutionDirectory.IsNullOrEmpty()
                            || !System.IO.Directory.Exists(solutionDirectory))
                        {
                            EqtTrace.Error(string.Format(CultureInfo.CurrentCulture, Resources.Resources.SolutionDirectoryNotExists, solutionDirectory));
                            solutionDirectory = null;
                        }

                        runConfiguration.SolutionDirectory = solutionDirectory;

                        break;

                    case "BinariesRoot":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        runConfiguration.BinariesRoot = reader.ReadElementContentAsString();
                        break;

                    case "ExecutionThreadApartmentState":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        string executionThreadApartmentState = reader.ReadElementContentAsString();
                        if (!Enum.TryParse(executionThreadApartmentState, out PlatformApartmentState apartmentState))
                        {
                            throw new SettingsException(
                                string.Format(
                                    CultureInfo.CurrentCulture,
                                    Resources.Resources.InvalidSettingsIncorrectValue,
                                    Constants.RunConfigurationSettingsName,
                                    executionThreadApartmentState,
                                    elementName));
                        }

                        runConfiguration.ExecutionThreadApartmentState = apartmentState;
                        break;

                    case "TargetDevice":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        runConfiguration.TargetDevice = reader.ReadElementContentAsString();
                        break;

                    case "TestCaseFilter":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        runConfiguration.TestCaseFilter = reader.ReadElementContentAsString();
                        break;

                    case "DotNetHostPath":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        string? dotnetHostPath = reader.ReadElementContentAsString();

#if !NETSTANDARD1_0
                        dotnetHostPath = Environment.ExpandEnvironmentVariables(dotnetHostPath);
#endif

                        runConfiguration.DotnetHostPath = dotnetHostPath;
                        break;
                    case "TreatNoTestsAsError":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        string treatNoTestsAsErrorValueString = reader.ReadElementContentAsString();
                        if (!bool.TryParse(treatNoTestsAsErrorValueString, out bool treatNoTestsAsError))
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, treatNoTestsAsErrorValueString, elementName));
                        }
                        runConfiguration.TreatNoTestsAsError = treatNoTestsAsError;
                        break;
                    // Configuration used but not exposed to the public to avoid the Warning inside the log
                    case "ForceOneTestAtTimePerTestHost":
                    case "EnvironmentVariables":
                    case "TargetFrameworkTestHostDemultiplexer":
                        reader.Skip();
                        break;

                    case "CaptureStandardOutput":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        string captureStandardOutputStr = reader.ReadElementContentAsString();

                        bool bCaptureStandardOutput;
                        if (!bool.TryParse(captureStandardOutputStr, out bCaptureStandardOutput))
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, bCaptureStandardOutput, elementName));
                        }

                        runConfiguration.CaptureStandardOutput = bCaptureStandardOutput;
                        break;

                    case "ForwardStandardOutput":
                        XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                        string forwardStandardOutputStr = reader.ReadElementContentAsString();

                        bool bForwardStandardOutput;
                        if (!bool.TryParse(forwardStandardOutputStr, out bForwardStandardOutput))
                        {
                            throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, bForwardStandardOutput, elementName));
                        }

                        runConfiguration.ForwardStandardOutput = bForwardStandardOutput;
                        break;

                    case nameof(CreateNoNewWindow):
                        {
                            XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                            string element = reader.ReadElementContentAsString();

                            bool boolValue;
                            if (!bool.TryParse(element, out boolValue))
                            {
                                throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                    Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, element, elementName));
                            }

                            runConfiguration.CreateNoNewWindow = boolValue;
                            break;
                        }

                    case nameof(DisableSharedTestHost):
                        {
                            XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                            string element = reader.ReadElementContentAsString();

                            bool boolValue;
                            if (!bool.TryParse(element, out boolValue))
                            {
                                throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                    Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, boolValue, elementName));
                            }

                            runConfiguration.DisableSharedTestHost = boolValue;
                            break;
                        }

                    case nameof(SkipDefaultAdapters):
                        {
                            XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
                            string element = reader.ReadElementContentAsString();

                            bool boolValue;
                            if (!bool.TryParse(element, out boolValue))
                            {
                                throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
                                    Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, boolValue, elementName));
                            }

                            runConfiguration.SkipDefaultAdapters = boolValue;
                            break;
                        }

                    default:
                        // Ignore a runsettings element that we don't understand. It could occur in the case
                        // the test runner is of a newer version, but the test host is of an earlier version.
                        EqtTrace.Warning(
                            string.Format(
                                CultureInfo.CurrentCulture,
                                Resources.Resources.InvalidSettingsXmlElement,
                                Constants.RunConfigurationSettingsName,
                                reader.Name));
                        reader.Skip();
                        break;
                }
            }

            reader.ReadEndElement();
        }

        return runConfiguration;
    }
}