From 8c4241369ce859a3dee22509a2b1e89d14a4b96b Mon Sep 17 00:00:00 2001 From: Buddhika Chathuranga Date: Fri, 7 Aug 2020 12:18:52 +0530 Subject: [PATCH] Yaml support for extensions --- src/Core/ServiceWrapper/WrapperService.cs | 1 + .../Configuration/DefaultSettings.cs | 3 +- .../Configuration/IWinSWConfiguration.cs | 2 +- .../Configuration/YamlConfiguration.cs | 25 ++- .../Extensions/AbstractWinSWExtension.cs | 5 +- .../ExtensionConfigurationProvider.cs | 55 ++++++ .../WinSWCore/Extensions/IWinSWExtension.cs | 6 +- .../Extensions/WinSWExtensionConfiguration.cs | 21 +++ .../Extensions/WinSWExtensionDescriptor.cs | 2 +- .../Extensions/WinSWExtensionManager.cs | 16 +- src/Core/WinSWCore/ServiceDescriptor.cs | 13 +- src/Core/WinSWCore/ServiceDescriptorYaml.cs | 4 +- src/Core/WinSWCore/Util/ObjectQuery.cs | 160 ++++++++++++++++++ .../RunawayProcessKillerExtension.cs | 17 +- .../SharedDirectoryMapper.cs | 25 +-- .../Extensions/RunawayProcessKillerTest.cs | 56 ------ src/Test/winswTests/YamlExtensionTest.cs | 54 ++++++ 17 files changed, 362 insertions(+), 103 deletions(-) create mode 100644 src/Core/WinSWCore/Extensions/ExtensionConfigurationProvider.cs create mode 100644 src/Core/WinSWCore/Extensions/WinSWExtensionConfiguration.cs create mode 100644 src/Core/WinSWCore/Util/ObjectQuery.cs create mode 100644 src/Test/winswTests/YamlExtensionTest.cs diff --git a/src/Core/ServiceWrapper/WrapperService.cs b/src/Core/ServiceWrapper/WrapperService.cs index 04ba60d..9ebda00 100644 --- a/src/Core/ServiceWrapper/WrapperService.cs +++ b/src/Core/ServiceWrapper/WrapperService.cs @@ -280,6 +280,7 @@ namespace WinSW Log.Info("Starting " + this.descriptor.Executable + ' ' + startArguments); // Load and start extensions + Console.WriteLine("Loading extensinos"); this.ExtensionManager.LoadExtensions(); this.ExtensionManager.FireOnWrapperStarted(); diff --git a/src/Core/WinSWCore/Configuration/DefaultSettings.cs b/src/Core/WinSWCore/Configuration/DefaultSettings.cs index aa45c01..ec35ba4 100644 --- a/src/Core/WinSWCore/Configuration/DefaultSettings.cs +++ b/src/Core/WinSWCore/Configuration/DefaultSettings.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Xml; using WMI; namespace WinSW.Configuration @@ -133,7 +132,7 @@ namespace WinSW.Configuration public bool BeepOnShutdown => false; // Extensions - public XmlNode? ExtensionsConfiguration => null; + public object? ExtensionsConfiguration => null; public string BaseName { diff --git a/src/Core/WinSWCore/Configuration/IWinSWConfiguration.cs b/src/Core/WinSWCore/Configuration/IWinSWConfiguration.cs index 1b21450..fd3786c 100644 --- a/src/Core/WinSWCore/Configuration/IWinSWConfiguration.cs +++ b/src/Core/WinSWCore/Configuration/IWinSWConfiguration.cs @@ -74,7 +74,7 @@ namespace WinSW.Configuration bool BeepOnShutdown { get; } // Extensions - XmlNode? ExtensionsConfiguration { get; } + object? ExtensionsConfiguration { get; } List ExtensionIds { get; } diff --git a/src/Core/WinSWCore/Configuration/YamlConfiguration.cs b/src/Core/WinSWCore/Configuration/YamlConfiguration.cs index 49e5610..8fcf06a 100644 --- a/src/Core/WinSWCore/Configuration/YamlConfiguration.cs +++ b/src/Core/WinSWCore/Configuration/YamlConfiguration.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Xml; using WinSW.Native; using WinSW.Util; using WMI; @@ -100,7 +99,7 @@ namespace WinSW.Configuration public string? SecurityDescriptorYaml { get; set; } [YamlMember(Alias = "extensions")] - public List? YamlExtensionIds { get; set; } + public object? YamlExtensions { get; set; } public class YamlEnv { @@ -641,10 +640,26 @@ namespace WinSW.Configuration public string LogMode => this.Log.Mode is null ? this.Defaults.LogMode : this.Log.Mode; - // TODO - Extensions - XmlNode? IWinSWConfiguration.ExtensionsConfiguration => throw new NotImplementedException(); + public object? ExtensionsConfiguration => this.YamlExtensions; - public List ExtensionIds => this.YamlExtensionIds ?? this.Defaults.ExtensionIds; + public List ExtensionIds + { + get + { + var result = new List(0); + var extensions = this.ExtensionsConfiguration; + var extensionConfigListObject = new ObjectQuery(extensions).ToList(); + + foreach (var item in extensionConfigListObject) + { + var configObject = new ObjectQuery(item); + var id = configObject.On("id").ToString(); + result.Add(id); + } + + return result; + } + } public string BaseName => this.Defaults.BaseName; diff --git a/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs b/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs index a3b4733..e5108a7 100644 --- a/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs +++ b/src/Core/WinSWCore/Extensions/AbstractWinSWExtension.cs @@ -1,5 +1,4 @@ -using System.Xml; -using WinSW.Configuration; +using WinSW.Configuration; namespace WinSW.Extensions { @@ -11,7 +10,7 @@ namespace WinSW.Extensions public WinSWExtensionDescriptor Descriptor { get; set; } #pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. - public virtual void Configure(IWinSWConfiguration descriptor, XmlNode node) + public virtual void Configure(IWinSWConfiguration descriptor, object settings) { // Do nothing } diff --git a/src/Core/WinSWCore/Extensions/ExtensionConfigurationProvider.cs b/src/Core/WinSWCore/Extensions/ExtensionConfigurationProvider.cs new file mode 100644 index 0000000..b90b813 --- /dev/null +++ b/src/Core/WinSWCore/Extensions/ExtensionConfigurationProvider.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using WinSW.Configuration; +using WinSW.Util; + +namespace WinSW.Extensions +{ + public class ExtensionConfigurationProvider + { + private readonly IWinSWConfiguration serviceDescriptor; + + private readonly List extensionConfigList; + + public ExtensionConfigurationProvider(IWinSWConfiguration serviceConfigs) + { + this.serviceDescriptor = serviceConfigs; + this.extensionConfigList = this.CreateExtensionConfigList(); + } + + public WinSWExtensionConfiguration? GetExtenstionConfiguration(string id) + { + foreach (var item in this.extensionConfigList) + { + if (item.Id.Equals(id)) + { + return item; + } + } + + return null; + } + + private List CreateExtensionConfigList() + { + var result = new List(0); + + var extensions = this.serviceDescriptor.ExtensionsConfiguration; + var extensionNodes = new ObjectQuery(extensions).ToList(); + + foreach (var extension in extensionNodes) + { + var query = new ObjectQuery(extension); + + var id = query.On("id").ToString(); + var enabled = query.On("enabled").ToBoolean(); + var className = query.On("classname").ToString(); + var settings = query.On("settings"); + + var extensionConfig = new WinSWExtensionConfiguration(id, enabled, className, settings); + result.Add(extensionConfig); + } + + return result; + } + } +} diff --git a/src/Core/WinSWCore/Extensions/IWinSWExtension.cs b/src/Core/WinSWCore/Extensions/IWinSWExtension.cs index d187be8..653bf43 100644 --- a/src/Core/WinSWCore/Extensions/IWinSWExtension.cs +++ b/src/Core/WinSWCore/Extensions/IWinSWExtension.cs @@ -1,5 +1,4 @@ -using System.Xml; -using WinSW.Configuration; +using WinSW.Configuration; namespace WinSW.Extensions { @@ -27,8 +26,7 @@ namespace WinSW.Extensions /// Init handler. Extension should load it's config during that step /// /// Service descriptor - /// Configuration node - void Configure(IWinSWConfiguration descriptor, XmlNode node); + void Configure(IWinSWConfiguration descriptor, object settings); /// /// Start handler. Called during startup of the service before the child process. diff --git a/src/Core/WinSWCore/Extensions/WinSWExtensionConfiguration.cs b/src/Core/WinSWCore/Extensions/WinSWExtensionConfiguration.cs new file mode 100644 index 0000000..62f02b0 --- /dev/null +++ b/src/Core/WinSWCore/Extensions/WinSWExtensionConfiguration.cs @@ -0,0 +1,21 @@ +namespace WinSW.Extensions +{ + public class WinSWExtensionConfiguration + { + public string Id { get; set; } + + public bool Enabled { get; set; } + + public string ClassName { get; set; } + + public object Settings { get; set; } + + public WinSWExtensionConfiguration(string id, bool enabled, string className, object settings) + { + this.Id = id; + this.Enabled = enabled; + this.ClassName = className; + this.Settings = settings; + } + } +} \ No newline at end of file diff --git a/src/Core/WinSWCore/Extensions/WinSWExtensionDescriptor.cs b/src/Core/WinSWCore/Extensions/WinSWExtensionDescriptor.cs index e8794f1..105a686 100644 --- a/src/Core/WinSWCore/Extensions/WinSWExtensionDescriptor.cs +++ b/src/Core/WinSWCore/Extensions/WinSWExtensionDescriptor.cs @@ -26,7 +26,7 @@ namespace WinSW.Extensions /// public string ClassName { get; private set; } - private WinSWExtensionDescriptor(string id, string className, bool enabled) + public WinSWExtensionDescriptor(string id, string className, bool enabled) { this.Id = id; this.Enabled = enabled; diff --git a/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs b/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs index 3b0bf6a..631bcbb 100644 --- a/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs +++ b/src/Core/WinSWCore/Extensions/WinSWExtensionManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Xml; using log4net; using WinSW.Configuration; @@ -12,12 +11,15 @@ namespace WinSW.Extensions public IWinSWConfiguration ServiceDescriptor { get; private set; } + public ExtensionConfigurationProvider ConfigProvider { get; private set; } + private static readonly ILog Log = LogManager.GetLogger(typeof(WinSWExtensionManager)); public WinSWExtensionManager(IWinSWConfiguration serviceDescriptor) { this.ServiceDescriptor = serviceDescriptor; this.Extensions = new Dictionary(); + this.ConfigProvider = new ExtensionConfigurationProvider(this.ServiceDescriptor); } /// @@ -127,21 +129,25 @@ namespace WinSW.Extensions throw new ExtensionException(id, "Extension has been already loaded"); } - XmlNode? extensionsConfig = this.ServiceDescriptor.ExtensionsConfiguration; - XmlElement? configNode = extensionsConfig is null ? null : extensionsConfig.SelectSingleNode("extension[@id='" + id + "'][1]") as XmlElement; + var configNode = this.ConfigProvider.GetExtenstionConfiguration(id); + if (configNode is null) { throw new ExtensionException(id, "Cannot get the configuration entry"); } - var descriptor = WinSWExtensionDescriptor.FromXml(configNode); + Console.WriteLine(configNode.Id); + Console.WriteLine(configNode.ClassName); + Console.WriteLine(configNode.Enabled); + + var descriptor = new WinSWExtensionDescriptor(configNode.Id, configNode.ClassName, configNode.Enabled); if (descriptor.Enabled) { IWinSWExtension extension = this.CreateExtensionInstance(descriptor.Id, descriptor.ClassName); extension.Descriptor = descriptor; try { - extension.Configure(this.ServiceDescriptor, configNode); + extension.Configure(this.ServiceDescriptor, configNode.Settings); } catch (Exception ex) { // Consider any unexpected exception as fatal diff --git a/src/Core/WinSWCore/ServiceDescriptor.cs b/src/Core/WinSWCore/ServiceDescriptor.cs index 78d8872..6f21c0c 100644 --- a/src/Core/WinSWCore/ServiceDescriptor.cs +++ b/src/Core/WinSWCore/ServiceDescriptor.cs @@ -208,7 +208,7 @@ namespace WinSW } } - public List ExtensionIds + /*public List ExtensionIds { get { @@ -227,9 +227,18 @@ namespace WinSW return result; } + }*/ + + public List ExtensionIds + { + get + { + return new List(0); + } } - public XmlNode? ExtensionsConfiguration => this.dom.SelectSingleNode("//extensions"); + // public XmlNode? ExtensionsConfiguration => this.dom.SelectSingleNode("//extensions"); + public object? ExtensionsConfiguration { get; set; } /// /// Combines the contents of all the elements of the given name, diff --git a/src/Core/WinSWCore/ServiceDescriptorYaml.cs b/src/Core/WinSWCore/ServiceDescriptorYaml.cs index 332b78b..d731879 100644 --- a/src/Core/WinSWCore/ServiceDescriptorYaml.cs +++ b/src/Core/WinSWCore/ServiceDescriptorYaml.cs @@ -18,7 +18,7 @@ namespace WinSW using (var reader = new StreamReader(basepath + ".yml")) { var file = reader.ReadToEnd(); - var deserializer = new DeserializerBuilder().Build(); + var deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().Build(); this.Configurations = deserializer.Deserialize(file); } @@ -47,7 +47,7 @@ namespace WinSW public static ServiceDescriptorYaml FromYaml(string yaml) { - var deserializer = new DeserializerBuilder().Build(); + var deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().Build(); var configs = deserializer.Deserialize(yaml); return new ServiceDescriptorYaml(configs); } diff --git a/src/Core/WinSWCore/Util/ObjectQuery.cs b/src/Core/WinSWCore/Util/ObjectQuery.cs new file mode 100644 index 0000000..9e082ff --- /dev/null +++ b/src/Core/WinSWCore/Util/ObjectQuery.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace WinSW.Util +{ + public class ObjectQuery + { + private readonly object configObject; + private object? current; + private string? key; + + public ObjectQuery(object? config) + { + if (config is null) + { + throw new InvalidDataException("Query object is null"); + } + + this.configObject = config; + this.current = this.configObject; + } + + public ObjectQuery On(string key) + { + this.key = key; + this.current = this.Query(this.configObject, key); + return this; + } + + public ObjectQuery Get(string key) + { + if (this.current == null) + { + throw new InvalidDataException("The key <" + key + "> is not exist"); + } + + this.key = key; + this.current = this.Query(this.current, key); + return this; + } + + public new string ToString() + { + if (this.current == null) + { + throw new InvalidDataException("The key <" + this.key + "> is not exist"); + } + + var result = this.current as string; + + if (result == null) + { + throw new InvalidDataException(this.key + " can't converto to a string"); + } + + return result; + } + + public List ToList() + { + if (this.current == null) + { + throw new InvalidDataException("The key <" + this.key + "> is not exist"); + } + + var list = this.current as List; + + if (list == null) + { + throw new InvalidDataException(this.key + " can't converto to List<" + typeof(T) + ">"); + } + + var result = new List(0); + foreach (var item in list) + { + result.Add((T)item); + } + + return result; + } + + public bool ToBoolean() + { + if (this.current == null) + { + throw new InvalidDataException("The key <" + this.key + "> is not exist"); + } + + var value = this.current as string; + + if (value == null) + { + throw new InvalidDataException(this.key + " can't convert into bool"); + } + + if (value == "true" || value == "yes" || value == "on") + { + return true; + } + else if (value == "false" || value == "no" || value == "off") + { + return false; + } + else + { + throw new InvalidDataException(value + " cannot convert into bool"); + } + } + + public ObjectQuery At(int index) + { + if (this.current == null) + { + throw new InvalidDataException("The key <" + this.key + "> is not exist"); + } + + var list = this.current as List; + + if (list == null) + { + throw new InvalidDataException("Can't execute At(index) on " + this.key); + } + + try + { + var result = list[index]; + this.current = result; + } + catch (IndexOutOfRangeException) + { + throw new InvalidDataException("Index " + index + " not in range"); + } + + return this; + } + + private object? Query(object dic, string key) + { + if (dic == null) + { + throw new InvalidDataException(key + " is not found"); + } + + var dict = dic as IDictionary; + if (dict != null) + { + foreach (KeyValuePair kvp in dict) + { + if (kvp.Key as string == key) + { + return kvp.Value; + } + } + } + + return null; + } + } +} diff --git a/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs b/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs index 2f495cf..0fbcaf2 100644 --- a/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs +++ b/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs @@ -3,7 +3,6 @@ using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Text; -using System.Xml; using log4net; using WinSW.Configuration; using WinSW.Extensions; @@ -180,17 +179,15 @@ namespace WinSW.Plugins.RunawayProcessKiller return parameters.Environment; } - public override void Configure(IWinSWConfiguration descriptor, XmlNode node) + public override void Configure(IWinSWConfiguration descriptor, object settings) { - // We expect the upper logic to process any errors - // TODO: a better parser API for types would be useful - this.Pidfile = XmlHelper.SingleElement(node, "pidfile", false)!; - this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse(XmlHelper.SingleElement(node, "stopTimeout", false)!)); - this.StopParentProcessFirst = bool.Parse(XmlHelper.SingleElement(node, "stopParentFirst", false)!); + var configQuery = new ObjectQuery(settings); + + this.Pidfile = configQuery.On("pidfile").ToString(); + this.StopTimeout = TimeSpan.FromMilliseconds(int.Parse(configQuery.On("stopTimeOut").ToString())); + this.StopParentProcessFirst = configQuery.On("StopParentFirst").ToBoolean(); + this.CheckWinSWEnvironmentVariable = configQuery.On("checkWinSWEnvironmentVariable").ToBoolean(); this.ServiceId = descriptor.Id; - // TODO: Consider making it documented - var checkWinSWEnvironmentVariable = XmlHelper.SingleElement(node, "checkWinSWEnvironmentVariable", true); - this.CheckWinSWEnvironmentVariable = checkWinSWEnvironmentVariable is null ? true : bool.Parse(checkWinSWEnvironmentVariable); } /// diff --git a/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs index 4813ebb..5514f04 100644 --- a/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs +++ b/src/Plugins/SharedDirectoryMapper/SharedDirectoryMapper.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Xml; using log4net; using WinSW.Configuration; using WinSW.Extensions; @@ -27,19 +26,21 @@ namespace WinSW.Plugins.SharedDirectoryMapper this._entries.Add(config); } - public override void Configure(IWinSWConfiguration descriptor, XmlNode node) + public override void Configure(IWinSWConfiguration descriptor, object settings) { - XmlNodeList? mapNodes = XmlHelper.SingleNode(node, "mapping", false)!.SelectNodes("map"); - if (mapNodes != null) + var configObject = new ObjectQuery(settings); + + var maps = configObject.On("mapping").ToList(); + + foreach (var map in maps) { - for (int i = 0; i < mapNodes.Count; i++) - { - if (mapNodes[i] is XmlElement mapElement) - { - var config = SharedDirectoryMapperConfig.FromXml(mapElement); - this._entries.Add(config); - } - } + var mapObject = new ObjectQuery(map); + var enable = mapObject.On("enabled").ToBoolean(); + var label = mapObject.On("label").ToString(); + var uncpath = mapObject.On("uncpath").ToString(); + + var config = new SharedDirectoryMapperConfig(enable, label, uncpath); + this._entries.Add(config); } } diff --git a/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs b/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs index b92bcfb..efa4628 100644 --- a/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs +++ b/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs @@ -63,61 +63,5 @@ $@" manager.FireOnWrapperStarted(); manager.FireBeforeWrapperStopped(); } - - [Test] - public void ShouldKillTheSpawnedProcess() - { - Assert.Ignore(); - - var winswId = "myAppWithRunaway"; - var extensionId = "runawayProcessKiller"; - var tmpDir = FilesystemTestHelper.CreateTmpDirectory(); - - // Spawn the test process - Process proc = new Process(); - var ps = proc.StartInfo; - ps.FileName = "cmd.exe"; - ps.Arguments = "/c pause"; - ps.UseShellExecute = false; - ps.RedirectStandardOutput = true; - ps.EnvironmentVariables[WinSWSystem.EnvVarNameServiceId] = winswId; - proc.Start(); - - try - { - // Generate extension and ensure that the roundtrip is correct - var pidfile = Path.Combine(tmpDir, "process.pid"); - var sd = ConfigXmlBuilder.create(id: winswId) - .WithRunawayProcessKiller(new RunawayProcessKillerExtension(pidfile), extensionId) - .ToServiceDescriptor(); - WinSWExtensionManager manager = new WinSWExtensionManager(sd); - manager.LoadExtensions(); - var extension = manager.Extensions[extensionId] as RunawayProcessKillerExtension; - Assert.IsNotNull(extension, "RunawayProcessKillerExtension should be loaded"); - Assert.AreEqual(pidfile, extension.Pidfile, "PidFile should have been retained during the config roundtrip"); - - // Inject PID - File.WriteAllText(pidfile, proc.Id.ToString()); - - // Try to terminate - Assert.That(!proc.HasExited, "Process " + proc + " has exited before the RunawayProcessKiller extension invocation"); - _ = proc.StandardOutput.Read(); - extension.OnWrapperStarted(); - Assert.That(proc.HasExited, "Process " + proc + " should have been terminated by RunawayProcessKiller"); - } - finally - { - if (!proc.HasExited) - { - Console.Error.WriteLine("Test: Killing runaway process with ID=" + proc.Id); - ProcessHelper.StopProcessAndChildren(proc.Id, TimeSpan.FromMilliseconds(100), false); - if (!proc.HasExited) - { - // The test is failed here anyway, but we add additional diagnostics info - Console.Error.WriteLine("Test: ProcessHelper failed to properly terminate process with ID=" + proc.Id); - } - } - } - } } } diff --git a/src/Test/winswTests/YamlExtensionTest.cs b/src/Test/winswTests/YamlExtensionTest.cs new file mode 100644 index 0000000..6a01e5a --- /dev/null +++ b/src/Test/winswTests/YamlExtensionTest.cs @@ -0,0 +1,54 @@ +using NUnit.Framework; +using WinSW; +using WinSW.Configuration; +using WinSW.Extensions; +using WinSW.Plugins.RunawayProcessKiller; +using winswTests.Extensions; + +namespace winswTests +{ + public class YamlExtensionTest : ExtensionTestBase + { + private IWinSWConfiguration _testServiceDescriptor { get; set; } + + readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(RunawayProcessKillerExtension)); + + [SetUp] + public void SetUp() + { + + string yamlDoc = $@"id: SERVICE_NAME +name: Jenkins Slave +description: This service runs Jenkins automation server. +executable: java +arguments: -Xrs -jar \""%BASE%\slave.jar\"" -jnlpUrl ... +extensions: + - id: killOnStartup + enabled: yes + classname: ""{this.testExtension}"" + settings: + pidfile: foo/bar/pid.txt + stopTimeOut: 5000 + StopParentFirst: true"; + + this._testServiceDescriptor = ServiceDescriptorYaml.FromYaml(yamlDoc).Configurations; + } + + + [Test] + public void LoadExtensions() + { + + WinSWExtensionManager manager = new WinSWExtensionManager(this._testServiceDescriptor); + manager.LoadExtensions(); + Assert.AreEqual(1, manager.Extensions.Count, "One extension should be loaded"); + + // Check the file is correct + var extension = manager.Extensions["killRunawayProcess"] as RunawayProcessKillerExtension; + Assert.IsNotNull(extension, "RunawayProcessKillerExtension should be loaded"); + Assert.AreEqual("foo/bar/pid.txt", extension.Pidfile, "Loaded PID file path is not equal to the expected one"); + Assert.AreEqual(5000, extension.StopTimeout.TotalMilliseconds, "Loaded Stop Timeout is not equal to the expected one"); + Assert.AreEqual(true, extension.StopParentProcessFirst, "Loaded StopParentFirst is not equal to the expected one"); + } + } +}