new-runtimepoc/Configuration.cs

using Pester;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
 
// those types implement Pester configuration in a way that allows it to show information about each item
// in the powershell console without making it difficult to use. there are two tricks being used:
// - constructor taking IDictionary (most likely a hashtable) that will populate the object,
// this allows the object to be constructed from a hashtable simply by casting to the type
// both implicitly and explicitly, so the user does not have to care about what types are used
// but will still get the benefit of the data annotation in the object. Usage is like this:
// `$config.Debug = @{ WriteDebugMessages = $true; WriteDebugMessagesFrom = "Mock*" }`, which
// will populate the config with the given values while keeping all other values to the default.
// - to be able to assign values like this: `$config.Should.ErrorAction = 'Continue'` but still
// get the documentation when accessing the property, we use implicit casting to get an instance of
// StringOption, and then populate it from the option object that is already assigned to the property
//
// lastly most of the types go to Pester namespace to keep them from the global namespace because they are
// simple to use by implicit casting, with the only exception of PesterConfiguration because that is helpful
// to have in "type accelerator" form, but without the hassle of actually adding it as a type accelerator
// that way you can easily do `[PesterConfiguration]::Default` and then inspect it, or cast a hashtable to it
//
// ⚠ DO NOT use auto properties or any of the new fancy syntax in this file,
// PowerShell would not be able to compile it. DO USE nameof, it will be replaced on load.
 
namespace Pester
{
    internal static class Cloner
    {
        public static T ShallowClone<T>(T obj) where T : new()
        {
            var cfg = new T();
            var properties = typeof(T).GetProperties().ToList();
 
            foreach (var p in properties.Where(p => p.CanRead && p.CanWrite))
            {
                var value = p.GetValue(obj);
                p.SetValue(cfg, value);
            }
 
            return cfg;
        }
    }
 
    internal static class Merger
    {
        public static T Merge<T>(T configuration, T @override) where T : new()
        {
            var cfg = new T();
            var properties = typeof(T).GetProperties().ToList();
 
            foreach (var p in properties.Where(p => p.CanRead && p.CanWrite))
            {
                object value;
                var overrideValue = p.GetValue(@override);
                if (!((Option)overrideValue).IsOriginalValue())
                {
                    value = overrideValue;
                }
                else
                {
                    value = p.GetValue(configuration);
                }
 
                p.SetValue(cfg, value);
            }
 
            return cfg;
        }
    }
 
    public abstract class Option
    {
        protected bool _isOriginalValue;
 
        public bool IsOriginalValue()
        {
            return _isOriginalValue;
        }
    }
 
    public abstract class Option<T> : Option
    {
        public Option(Option<T> option, T value) : this(option.Description, option.Default, value)
        {
            _isOriginalValue = false;
        }
 
        public Option(string description, T defaultValue, T value)
        {
            _isOriginalValue = true;
            Default = defaultValue;
            Value = value;
            Description = description;
        }
 
        public T Default { get; private set; }
        public string Description { get; private set; }
        public T Value { get; set; }
 
        public override string ToString()
        {
            return string.Format("{0} ({1}, default: {2})", Description, Value, Default);
        }
    }
 
    public class StringOption : Option<string>
    {
        public StringOption(StringOption option, string value) : base(option, value)
        {
 
        }
 
        public StringOption(string description, string defaultValue) : base(description, defaultValue, defaultValue)
        {
 
        }
 
        public StringOption(string description, string defaultValue, string value) : base(description, defaultValue, value)
        {
 
        }
 
        public static implicit operator StringOption(string value)
        {
            return new StringOption(string.Empty, value, value);
        }
    }
 
    public class BoolOption : Option<bool>
    {
        public BoolOption(BoolOption option, bool value) : base(option, value)
        {
 
        }
 
        public BoolOption(string description, bool defaultValue) : base(description, defaultValue, defaultValue)
        {
 
        }
 
        public BoolOption(string description, bool defaultValue, bool value) : base(description, defaultValue, value)
        {
 
        }
 
        public static implicit operator BoolOption(bool value)
        {
            return new BoolOption(string.Empty, value, value);
        }
    }
 
    public class IntOption : Option<int>
    {
        public IntOption(IntOption option, int value) : base(option, value)
        {
 
        }
 
        public IntOption(string description, int defaultValue) : base(description, defaultValue, defaultValue)
        {
 
        }
 
        public IntOption(string description, int defaultValue, int value) : base(description, defaultValue, value)
        {
 
        }
 
        public static implicit operator IntOption(int value)
        {
            return new IntOption(string.Empty, value, value);
        }
    }
 
    public class DecimalOption : Option<decimal>
    {
        public DecimalOption(DecimalOption option, decimal value) : base(option, value)
        {
 
        }
 
        public DecimalOption(string description, decimal defaultValue) : base(description, defaultValue, defaultValue)
        {
 
        }
 
        public DecimalOption(string description, decimal defaultValue, decimal value) : base(description, defaultValue, value)
        {
 
        }
 
        public static implicit operator DecimalOption(decimal value)
        {
            return new DecimalOption(string.Empty, value, value);
        }
    }
 
    public class StringArrayOption : Option<string[]>
    {
        public StringArrayOption(StringArrayOption option, string[] value) : base(option, value)
        {
 
        }
 
        public StringArrayOption(string description, string[] defaultValue) : base(description, defaultValue, defaultValue)
        {
 
        }
 
        public StringArrayOption(string description, string[] defaultValue, string[] value) : base(description, defaultValue, value)
        {
 
        }
 
        public StringArrayOption(string[] value) : base("", new string[0], value)
        {
 
        }
 
        public StringArrayOption(string value) : base("", new string[0], new string[] { value })
        {
 
        }
 
        public StringArrayOption(object[] value) : base("", new string[0], value.Cast<string>().ToArray())
        {
 
        }
 
        public static implicit operator StringArrayOption(string[] value)
        {
            return new StringArrayOption(string.Empty, value, value);
        }
 
        public static implicit operator StringArrayOption(string value)
        {
            var array = new[] { value };
            return new StringArrayOption(string.Empty, array, array);
        }
    }
 
    public class ScriptBlockArrayOption : Option<ScriptBlock[]>
    {
        public ScriptBlockArrayOption(ScriptBlockArrayOption option, ScriptBlock[] value) : base(option, value)
        {
 
        }
 
        public ScriptBlockArrayOption(string description, ScriptBlock[] defaultValue) : base(description, defaultValue, defaultValue)
        {
 
        }
 
        public ScriptBlockArrayOption(string description, ScriptBlock[] defaultValue, ScriptBlock[] value) : base(description, defaultValue, value)
        {
 
        }
 
        public ScriptBlockArrayOption(object[] value) : base("", new ScriptBlock[0], value.Cast<ScriptBlock>().ToArray())
        {
 
        }
 
        public ScriptBlockArrayOption(ScriptBlock[] value) : base("", new ScriptBlock[0], value)
        {
 
        }
 
        public ScriptBlockArrayOption(ScriptBlock value) : this(new ScriptBlock[] { value })
        {
 
        }
 
        public static implicit operator ScriptBlockArrayOption(ScriptBlock[] value)
        {
            return new ScriptBlockArrayOption(string.Empty, value, value);
        }
 
        public static implicit operator ScriptBlockArrayOption(ScriptBlock value)
        {
            var array = new[] { value };
            return new ScriptBlockArrayOption(string.Empty, array, array);
        }
    }
 
    public abstract class ConfigurationSection
    {
        private string _description;
        public ConfigurationSection(string description)
        {
            _description = description;
        }
 
        public override string ToString()
        {
            return _description;
        }
    }
 
 
    internal static class DictionaryExtensions
    {
        public static T? GetValueOrNull<T>(this IDictionary dictionary, string key) where T : struct
        {
            return dictionary.Contains(key) ? dictionary[key] as T? : null;
        }
 
        public static T GetObjectOrNull<T>(this IDictionary dictionary, string key) where T : class
        {
            return dictionary.Contains(key) ? dictionary[key] as T : null;
        }
 
        public static IDictionary GetIDictionaryOrNull(this IDictionary dictionary, string key)
        {
            return dictionary.Contains(key) ? dictionary[key] as IDictionary : null;
        }
 
        public static T[] GetArrayOrNull<T>(this IDictionary dictionary, string key) where T : class
        {
            if (!dictionary.Contains(key))
                return null;
            var value = dictionary[key];
 
            if (value.GetType() == typeof(T[]))
            {
                return (T[])value;
            }
 
            if (value.GetType() == typeof(object[]))
            {
                try
                {
                    return ((object[])value).Cast<T>().ToArray();
                }
                catch { }
            }
 
            if (value.GetType() == typeof(T))
            {
                return new T[] { (T)value };
            }
 
            return null;
        }
    }
 
    public class ShouldConfiguration : ConfigurationSection
    {
 
        private StringOption _errorAction;
 
        public static ShouldConfiguration Default { get { return new ShouldConfiguration(); } }
 
        public static ShouldConfiguration ShallowClone(ShouldConfiguration configuration)
        {
            return Cloner.ShallowClone(configuration);
        }
 
        public ShouldConfiguration() : base("Should configuration.")
        {
            ErrorAction = new StringOption("Controls if Should throws on error. Use 'Stop' to throw on error, or 'Continue' to fail at the end of the test.", "Stop");
        }
 
        public ShouldConfiguration(IDictionary configuration) : this()
        {
            if (configuration != null)
            {
                ErrorAction = configuration.GetObjectOrNull<string>("ErrorAction") ?? ErrorAction;
            }
        }
 
        public StringOption ErrorAction
        {
            get { return _errorAction; }
            set
            {
                if (_errorAction == null)
                {
                    _errorAction = value;
                }
                else
                {
                    _errorAction = new StringOption(_errorAction, value.Value);
                }
            }
        }
    }
 
    public class DebugConfiguration : ConfigurationSection
    {
        public static DebugConfiguration Default { get { return new DebugConfiguration(); } }
 
        public static DebugConfiguration ShallowClone(DebugConfiguration configuration)
        {
            return Cloner.ShallowClone(configuration);
        }
 
        public DebugConfiguration() : base("Debug configuration for Pester. ⚠ Use at your own risk!")
        {
            ShowFullErrors = new BoolOption("Show full errors including Pester internal stack.", false);
            WriteDebugMessages = new BoolOption("Write Debug messages to screen.", false);
            WriteDebugMessagesFrom = new StringOption("Write Debug messages from a given source, WriteDebugMessages must be set to true for this to work. You can use like wildcards to get messages from multiple sources, as well as * to get everything.", "*");
            ShowNavigationMarkers = new BoolOption("Write paths after every block and test, for easy navigation in VSCode.", false);
            WriteVSCodeMarker = new BoolOption("Write VSCode marker for better integration with VSCode.", false);
            ReturnRawResultObject = new BoolOption("Returns unfiltered result object, this is for development only. Do not rely on this object for additional properties, non-public properties will be renamed without previous notice.", false);
        }
 
        public DebugConfiguration(IDictionary configuration) : this()
        {
            if (configuration != null)
            {
                ShowFullErrors = configuration.GetValueOrNull<bool>("ShowFullErrors") ?? ShowFullErrors;
                WriteDebugMessages = configuration.GetValueOrNull<bool>("WriteDebugMessages") ?? WriteDebugMessages;
                WriteDebugMessagesFrom = configuration.GetObjectOrNull<string>("WriteDebugMessagesFrom") ?? WriteDebugMessagesFrom;
                ShowNavigationMarkers = configuration.GetValueOrNull<bool>("ShowNavigationMarkers") ?? ShowNavigationMarkers;
                WriteVSCodeMarker = configuration.GetValueOrNull<bool>("WriteVSCodeMarker") ?? WriteVSCodeMarker;
                ReturnRawResultObject = configuration.GetValueOrNull<bool>("ReturnRawResultObject") ?? ReturnRawResultObject;
            }
        }
 
        private BoolOption _showFullErrors;
        private BoolOption _writeDebugMessages;
        private StringOption _writeDebugMessagesFrom;
        private BoolOption _showNavigationMarkers;
        private BoolOption _writeVsCodeMarker;
        private BoolOption _returnRawResultObject;
 
        public BoolOption ShowFullErrors
        {
            get { return _showFullErrors; }
            set
            {
                if (_showFullErrors == null)
                {
                    _showFullErrors = value;
                }
                else
                {
                    _showFullErrors = new BoolOption(_showFullErrors, value.Value);
                }
            }
        }
 
        public BoolOption WriteDebugMessages
        {
            get { return _writeDebugMessages; }
            set
            {
                if (_writeDebugMessages == null)
                {
                    _writeDebugMessages = value;
                }
                else
                {
                    _writeDebugMessages = new BoolOption(_writeDebugMessages, value.Value);
                }
            }
        }
 
        public StringOption WriteDebugMessagesFrom
        {
            get { return _writeDebugMessagesFrom; }
            set
            {
                if (_writeDebugMessagesFrom == null)
                {
                    _writeDebugMessagesFrom = value;
                }
                else
                {
                    _writeDebugMessagesFrom = new StringOption(_writeDebugMessagesFrom, value.Value);
                }
            }
        }
 
        public BoolOption ShowNavigationMarkers
        {
            get { return _showNavigationMarkers; }
            set
            {
                if (_showNavigationMarkers == null)
                {
                    _showNavigationMarkers = value;
                }
                else
                {
                    _showNavigationMarkers = new BoolOption(_showNavigationMarkers, value.Value);
                }
            }
        }
 
        public BoolOption WriteVSCodeMarker
        {
            get { return _writeVsCodeMarker; }
            set
            {
                if (_writeVsCodeMarker == null)
                {
                    _writeVsCodeMarker = value;
                }
                else
                {
                    _writeVsCodeMarker = new BoolOption(_writeVsCodeMarker, value.Value);
                }
            }
        }
 
        public BoolOption ReturnRawResultObject
        {
            get { return _returnRawResultObject; }
            set
            {
                if (_returnRawResultObject == null)
                {
                    _returnRawResultObject = value;
                }
                else
                {
                    _returnRawResultObject = new BoolOption(_returnRawResultObject, value.Value);
                }
            }
        }
    }
 
    public class CodeCoverageConfiguration : ConfigurationSection
    {
        private BoolOption _enabled;
        private StringOption _outputFormat;
        private StringOption _outputPath;
        private StringOption _outputEncoding;
        private StringArrayOption _path;
        private BoolOption _excludeTests;
 
        public static CodeCoverageConfiguration Default { get { return new CodeCoverageConfiguration(); } }
 
        public static CodeCoverageConfiguration ShallowClone(CodeCoverageConfiguration configuration)
        {
            return Cloner.ShallowClone(configuration);
        }
        public CodeCoverageConfiguration() : base("CodeCoverage configuration.")
        {
            Enabled = new BoolOption("Enable CodeCoverage.", false);
            OutputFormat = new StringOption("Format to use for code coverage report. Possible values: JaCoCo", "JaCoCo");
            OutputPath = new StringOption("Path relative to the current directory where code coverage report is saved.", "coverage.xml");
            OutputEncoding = new StringOption("Encoding of the output file.", "UTF8");
            Path = new StringArrayOption("Directories or files to be used for codecoverage, by default the Path(s) from general settings are used, unless overridden here.", new string[0]);
            ExcludeTests = new BoolOption("Exclude tests from code coverage. This uses the TestFilter from general configuration.", true);
 
        }
 
        public CodeCoverageConfiguration(IDictionary configuration) : this()
        {
            if (configuration != null)
            {
                Enabled = configuration.GetValueOrNull<bool>("Enabled") ?? Enabled;
                OutputFormat = configuration.GetObjectOrNull<string>("OutputFormat") ?? OutputFormat;
                OutputPath = configuration.GetObjectOrNull<string>("OutputPath") ?? OutputPath;
                OutputEncoding = configuration.GetObjectOrNull<string>("OutputEncoding") ?? OutputEncoding;
                Path = configuration.GetArrayOrNull<string>("Path") ?? Path;
                ExcludeTests = configuration.GetValueOrNull<bool>("ExcludeTests") ?? ExcludeTests;
            }
        }
 
        public BoolOption Enabled
        {
            get { return _enabled; }
            set
            {
                if (_enabled == null)
                {
                    _enabled = value;
                }
                else
                {
                    _enabled = new BoolOption(_enabled, value.Value);
                }
            }
        }
 
        public StringOption OutputFormat
        {
            get { return _outputFormat; }
            set
            {
                if (_outputFormat == null)
                {
                    _outputFormat = value;
                }
                else
                {
                    _outputFormat = new StringOption(_outputFormat, value.Value);
                }
            }
        }
 
        public StringOption OutputPath
        {
            get { return _outputPath; }
            set
            {
                if (_outputPath == null)
                {
                    _outputPath = value;
                }
                else
                {
                    _outputPath = new StringOption(_outputPath, value.Value);
                }
            }
        }
 
        public StringOption OutputEncoding
        {
            get { return _outputEncoding; }
            set
            {
                if (_outputEncoding == null)
                {
                    _outputEncoding = value;
                }
                else
                {
                    _outputEncoding = new StringOption(_outputEncoding, value.Value);
                }
            }
        }
 
        public StringArrayOption Path
        {
            get { return _path; }
            set
            {
                if (_path == null)
                {
                    _path = value;
                }
                else
                {
                    _path = new StringArrayOption(_path, value.Value);
                }
            }
        }
 
        public BoolOption ExcludeTests
        {
            get { return _excludeTests; }
            set
            {
                if (_excludeTests == null)
                {
                    _excludeTests = value;
                }
                else
                {
                    _excludeTests = new BoolOption(_excludeTests, value.Value);
                }
            }
        }
    }
 
    public class TestResultConfiguration : ConfigurationSection
    {
        private BoolOption _enabled;
        private StringOption _outputFormat;
        private StringOption _outputPath;
        private StringOption _testSuiteName;
        private StringOption _outputEncoding;
 
        public static TestResultConfiguration Default { get { return new TestResultConfiguration(); } }
 
        public static TestResultConfiguration ShallowClone(TestResultConfiguration configuration)
        {
            return Cloner.ShallowClone(configuration);
        }
 
        public TestResultConfiguration() : base("TestResult configuration.")
        {
            Enabled = new BoolOption("Enable TestResult.", false);
            OutputFormat = new StringOption("Format to use for test result report. Possible values: NUnit2.5", "NUnit2.5");
            OutputPath = new StringOption("Path relative to the current directory where test result report is saved.", "testResults.xml");
            OutputEncoding = new StringOption("Encoding of the output file.", "UTF8");
            TestSuiteName = new StringOption("Set the name assigned to the root 'test-suite' element.", "Pester");
        }
 
        public TestResultConfiguration(IDictionary configuration) : this()
        {
            if (configuration != null)
            {
                Enabled = configuration.GetValueOrNull<bool>("Enabled") ?? Enabled;
                OutputFormat = configuration.GetObjectOrNull<string>("OutputFormat") ?? OutputFormat;
                OutputPath = configuration.GetObjectOrNull<string>("OutputPath") ?? OutputPath;
                OutputEncoding = configuration.GetObjectOrNull<string>("OutputEncoding") ?? OutputPath;
                TestSuiteName = configuration.GetObjectOrNull<string>("TestSuiteName") ?? TestSuiteName;
            }
        }
 
        public BoolOption Enabled
        {
            get { return _enabled; }
            set
            {
                if (_enabled == null)
                {
                    _enabled = value;
                }
                else
                {
                    _enabled = new BoolOption(_enabled, value.Value);
                }
            }
        }
 
        public StringOption OutputFormat
        {
            get { return _outputFormat; }
            set
            {
                if (_outputFormat == null)
                {
                    _outputFormat = value;
                }
                else
                {
                    _outputFormat = new StringOption(_outputFormat, value.Value);
                }
            }
        }
 
        public StringOption OutputPath
        {
            get { return _outputPath; }
            set
            {
                if (_outputPath == null)
                {
                    _outputPath = value;
                }
                else
                {
                    _outputPath = new StringOption(_outputPath, value.Value);
                }
            }
        }
 
        public StringOption OutputEncoding
        {
            get { return _outputEncoding; }
            set
            {
                if (_outputEncoding == null)
                {
                    _outputEncoding = value;
                }
                else
                {
                    _outputEncoding = new StringOption(_outputEncoding, value.Value);
                }
            }
        }
 
        public StringOption TestSuiteName
        {
            get { return _testSuiteName; }
            set
            {
                if (_testSuiteName == null)
                {
                    _testSuiteName = value;
                }
                else
                {
                    _testSuiteName = new StringOption(_testSuiteName, value.Value);
                }
            }
        }
    }
 
    public class RunConfiguration : ConfigurationSection
    {
        private BoolOption _exit;
        private StringArrayOption _path;
        private StringArrayOption _excludePath;
        private ScriptBlockArrayOption _scriptBlock;
        private StringOption _testExtension;
 
        public static RunConfiguration Default { get { return new RunConfiguration(); } }
        public static RunConfiguration ShallowClone(RunConfiguration configuration)
        {
            return Cloner.ShallowClone(configuration);
        }
 
        public RunConfiguration(IDictionary configuration) : this()
        {
            if (configuration != null)
            {
                Exit = configuration.GetValueOrNull<bool>("Exit") ?? Exit;
                Path = configuration.GetArrayOrNull<string>("Path") ?? Path;
                ExcludePath = configuration.GetArrayOrNull<string>("ExcludePath") ?? ExcludePath;
                ScriptBlock = configuration.GetArrayOrNull<ScriptBlock>("ScriptBlock") ?? ScriptBlock;
                TestExtension = configuration.GetObjectOrNull<string>("TestExtension") ?? TestExtension;
            }
        }
 
        public RunConfiguration() : base("Run configuration.")
        {
            Exit = new BoolOption("Exit with non-zero exit code when the test run fails.", false);
            Path = new StringArrayOption("Directories to be searched for tests, paths directly to test files, or combination of both.", new string[] { "." });
            ExcludePath = new StringArrayOption("Directories or files to be excluded from the run.", new string[0]);
            ScriptBlock = new ScriptBlockArrayOption("ScriptBlocks containing tests to be executed.", new ScriptBlock[0]);
            TestExtension = new StringOption("Filter used to identify test files.", ".Tests.ps1");
        }
 
        public BoolOption Exit
        {
            get { return _exit; }
            set
            {
                if (_exit == null)
                {
                    _exit = value;
                }
                else
                {
                    _exit = new BoolOption(_exit, value.Value);
                }
            }
        }
 
        public StringArrayOption Path
        {
            get { return _path; }
            set
            {
                if (_path == null)
                {
                    _path = value;
                }
                else
                {
                    _path = new StringArrayOption(_path, value.Value);
                }
            }
        }
 
        public StringArrayOption ExcludePath
        {
            get { return _excludePath; }
            set
            {
                if (_excludePath == null)
                {
                    _excludePath = value;
                }
                else
                {
                    _excludePath = new StringArrayOption(_excludePath, value.Value);
                }
            }
        }
 
        public ScriptBlockArrayOption ScriptBlock
        {
            get { return _scriptBlock; }
            set
            {
                if (_scriptBlock == null)
                {
                    _scriptBlock = value;
                }
                else
                {
                    _scriptBlock = new ScriptBlockArrayOption(_scriptBlock, value.Value);
                }
            }
        }
 
        public StringOption TestExtension
        {
            get { return _testExtension; }
            set
            {
                if (_testExtension == null)
                {
                    _testExtension = value;
                }
                else
                {
                    _testExtension = new StringOption(_testExtension, value.Value);
                }
            }
        }
    }
 
    public class FilterConfiguration : ConfigurationSection
    {
        private StringArrayOption _tag;
        private StringArrayOption _excludeTag;
        private StringArrayOption _line;
        private StringArrayOption _name;
 
        public static FilterConfiguration Default { get { return new FilterConfiguration(); } }
        public static FilterConfiguration ShallowClone(FilterConfiguration configuration)
        {
            return Cloner.ShallowClone(configuration);
        }
 
        public FilterConfiguration(IDictionary configuration) : this()
        {
            if (configuration != null)
            {
                Tag = configuration.GetArrayOrNull<string>("Tag") ?? Tag;
                ExcludeTag = configuration.GetArrayOrNull<string>("ExcludeTag") ?? ExcludeTag;
                Line = configuration.GetArrayOrNull<string>("Line") ?? Line;
                Name = configuration.GetArrayOrNull<string>("Name") ?? Name;
            }
        }
        public FilterConfiguration() : base("Filter configuration")
        {
            Tag = new StringArrayOption("Tags of Describe, Context or It to be run.", new string[0]);
            ExcludeTag = new StringArrayOption("Tags of Describe, Context or It to be excluded from the run.", new string[0]);
            Line = new StringArrayOption(@"Filter by file and scriptblock start line, useful to run parsed tests programatically to avoid problems with expanded names. Example: 'C:\tests\file1.Tests.ps1:37'", new string[0]);
            Name = new StringArrayOption("Full name of test with -like wildcards, joined by dot. Example: '*.describe Get-Item.test1'", new string[0]);
        }
 
        public StringArrayOption Tag
        {
            get { return _tag; }
            set
            {
                if (_tag == null)
                {
                    _tag = value;
                }
                else
                {
                    _tag = new StringArrayOption(_tag, value.Value);
                }
            }
        }
 
        public StringArrayOption ExcludeTag
        {
            get { return _excludeTag; }
            set
            {
                if (_excludeTag == null)
                {
                    _excludeTag = value;
                }
                else
                {
                    _excludeTag = new StringArrayOption(_excludeTag, value.Value);
                }
            }
        }
        public StringArrayOption Line
        {
            get { return _line; }
            set
            {
                if (_line == null)
                {
                    _line = value;
                }
                else
                {
                    _line = new StringArrayOption(_line, value.Value);
                }
            }
        }
 
        public StringArrayOption Name
        {
            get { return _name; }
            set
            {
                if (_name == null)
                {
                    _name = value;
                }
                else
                {
                    _name = new StringArrayOption(_name, value.Value);
                }
            }
        }
    }
 
    public class OutputConfiguration : ConfigurationSection
    {
        private StringOption _verbosity;
 
        public static OutputConfiguration Default { get { return new OutputConfiguration(); } }
        public static OutputConfiguration ShallowClone(OutputConfiguration configuration)
        {
            return Cloner.ShallowClone(configuration);
        }
 
        public OutputConfiguration(IDictionary configuration) : this()
        {
            if (configuration != null)
            {
                Verbosity = configuration.GetObjectOrNull<string>("Verbosity") ?? Verbosity;
            }
        }
 
        public OutputConfiguration() : base("Output configuration")
        {
            Verbosity = new StringOption("The verbosity of output, options are Normal, None.", "Normal");
        }
 
        public StringOption Verbosity
        {
            get { return _verbosity; }
            set
            {
                if (_verbosity == null)
                {
                    _verbosity = value;
                }
                else
                {
                    _verbosity = new StringOption(_verbosity, value.Value);
                }
            }
        }
    }
}
 
public class PesterConfiguration
{
    public static PesterConfiguration Default { get { return new PesterConfiguration(); } }
 
    public static PesterConfiguration ShallowClone(PesterConfiguration configuration)
    {
        var cfg = Default;
        cfg.Run = RunConfiguration.ShallowClone(configuration.Run);
        cfg.Filter = FilterConfiguration.ShallowClone(configuration.Filter);
        cfg.CodeCoverage = CodeCoverageConfiguration.ShallowClone(configuration.CodeCoverage);
        cfg.TestResult = TestResultConfiguration.ShallowClone(configuration.TestResult);
        cfg.Should = ShouldConfiguration.ShallowClone(configuration.Should);
        cfg.Debug = DebugConfiguration.ShallowClone(configuration.Debug);
        cfg.Output = OutputConfiguration.ShallowClone(configuration.Output);
        return cfg;
    }
 
    public static PesterConfiguration Merge(PesterConfiguration configuration, PesterConfiguration @override)
    {
        var cfg = Default;
        cfg.Run = Merger.Merge(configuration.Run, @override.Run);
        cfg.Filter = Merger.Merge(configuration.Filter, @override.Filter);
        cfg.CodeCoverage = Merger.Merge(configuration.CodeCoverage, @override.CodeCoverage);
        cfg.TestResult = Merger.Merge(configuration.TestResult, @override.TestResult);
        cfg.Should = Merger.Merge(configuration.Should, @override.Should);
        cfg.Debug = Merger.Merge(configuration.Debug, @override.Debug);
        cfg.Output = Merger.Merge(configuration.Output, @override.Output);
        return cfg;
    }
 
    public PesterConfiguration(IDictionary configuration)
    {
        if (configuration != null)
        {
            Run = new RunConfiguration(configuration.GetIDictionaryOrNull("Run"));
            Filter = new FilterConfiguration(configuration.GetIDictionaryOrNull("Filter"));
            CodeCoverage = new CodeCoverageConfiguration(configuration.GetIDictionaryOrNull("CodeCoverage"));
            TestResult = new TestResultConfiguration(configuration.GetIDictionaryOrNull("TestResult"));
            Should = new ShouldConfiguration(configuration.GetIDictionaryOrNull("Should"));
            Debug = new DebugConfiguration(configuration.GetIDictionaryOrNull("Debug"));
            Output = new OutputConfiguration(configuration.GetIDictionaryOrNull("Output"));
        }
    }
 
    public PesterConfiguration()
    {
        Run = new RunConfiguration();
        Filter = new FilterConfiguration();
        CodeCoverage = new CodeCoverageConfiguration();
        TestResult = new TestResultConfiguration();
        Should = new ShouldConfiguration();
        Debug = new DebugConfiguration();
        Output = new OutputConfiguration();
    }
 
    public RunConfiguration Run { get; set; }
    public FilterConfiguration Filter { get; set; }
    public CodeCoverageConfiguration CodeCoverage { get; set; }
    public TestResultConfiguration TestResult { get; set; }
    public ShouldConfiguration Should { get; set; }
    public DebugConfiguration Debug { get; set; }
    public OutputConfiguration Output { get; set; }
}