Refactor service configuration

pull/618/head
NextTurn 2020-07-31 00:00:00 +08:00 committed by Next Turn
parent 94954e076f
commit efc3e34f6d
20 changed files with 415 additions and 530 deletions

View File

@ -1,90 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.ServiceProcess;
using System.Xml;
namespace WinSW.Configuration
{
/// <summary>
/// Default WinSW settings
/// </summary>
public sealed class DefaultWinSWSettings : IWinSWConfiguration
{
public string FullPath => throw new InvalidOperationException(nameof(this.FullPath) + " must be specified.");
public string Id => throw new InvalidOperationException(nameof(this.Id) + " must be specified.");
public string Caption => string.Empty;
public string Description => string.Empty;
public string Executable => throw new InvalidOperationException(nameof(this.Executable) + " must be specified.");
public bool HideWindow => false;
public string ExecutablePath => Process.GetCurrentProcess().MainModule.FileName;
// Installation
public bool AllowServiceAcountLogonRight => false;
public string? ServiceAccountPassword => null;
public string? ServiceAccountUserName => null;
public Native.SC_ACTION[] FailureActions => new Native.SC_ACTION[0];
public TimeSpan ResetFailureAfter => TimeSpan.FromDays(1);
// Executable management
public string Arguments => string.Empty;
public string? StartArguments => null;
public string? StopExecutable => null;
public string? StopArguments => null;
public string WorkingDirectory => Path.GetDirectoryName(this.FullPath)!;
public ProcessPriorityClass Priority => ProcessPriorityClass.Normal;
public TimeSpan StopTimeout => TimeSpan.FromSeconds(15);
// Service management
public ServiceStartMode StartMode => ServiceStartMode.Automatic;
public bool DelayedAutoStart => false;
public bool Preshutdown => false;
public string[] ServiceDependencies => new string[0];
public bool Interactive => false;
// Logging
public string LogDirectory => Path.GetDirectoryName(this.ExecutablePath)!;
public string LogMode => "append";
public bool OutFileDisabled => false;
public bool ErrFileDisabled => false;
public string OutFilePattern => ".out.log";
public string ErrFilePattern => ".err.log";
// Environment
public List<Download> Downloads => new List<Download>(0);
public Dictionary<string, string> EnvironmentVariables => new Dictionary<string, string>(0);
// Misc
public bool BeepOnShutdown => false;
// Extensions
public XmlNode? ExtensionsConfiguration => null;
}
}

View File

@ -1,76 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.ServiceProcess;
using System.Xml;
namespace WinSW.Configuration
{
// TODO: Document the parameters && refactor
public interface IWinSWConfiguration
{
string FullPath { get; }
string Id { get; }
string Caption { get; }
string Description { get; }
string Executable { get; }
string ExecutablePath { get; }
bool HideWindow { get; }
// Installation
bool AllowServiceAcountLogonRight { get; }
string? ServiceAccountPassword { get; }
string? ServiceAccountUserName { get; }
Native.SC_ACTION[] FailureActions { get; }
TimeSpan ResetFailureAfter { get; }
// Executable management
string Arguments { get; }
string? StartArguments { get; }
string? StopExecutable { get; }
string? StopArguments { get; }
string WorkingDirectory { get; }
ProcessPriorityClass Priority { get; }
TimeSpan StopTimeout { get; }
// Service management
ServiceStartMode StartMode { get; }
string[] ServiceDependencies { get; }
bool Interactive { get; }
// Logging
string LogDirectory { get; }
// TODO: replace by enum
string LogMode { get; }
// Environment
List<Download> Downloads { get; }
Dictionary<string, string> EnvironmentVariables { get; }
// Misc
bool BeepOnShutdown { get; }
// Extensions
XmlNode? ExtensionsConfiguration { get; }
}
}

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.ServiceProcess;
using System.Xml;
namespace WinSW.Configuration
{
/// <summary>
/// Default WinSW settings
/// </summary>
public abstract class ServiceConfig
{
public abstract string FullPath { get; }
public abstract string Id { get; }
public virtual string Caption => string.Empty;
public virtual string Description => string.Empty;
public abstract string Executable { get; }
public string ExecutablePath => Process.GetCurrentProcess().MainModule.FileName;
public virtual bool HideWindow => false;
// Installation
public virtual bool AllowServiceAcountLogonRight => false;
public virtual string? ServiceAccountPassword => null;
public virtual string? ServiceAccountUserName => null;
public virtual Native.SC_ACTION[] FailureActions => new Native.SC_ACTION[0];
public virtual TimeSpan ResetFailureAfter => TimeSpan.FromDays(1);
// Executable management
public virtual string Arguments => string.Empty;
public virtual string? StartArguments => null;
public virtual string? StopExecutable => null;
public virtual string? StopArguments => null;
public virtual string WorkingDirectory => Path.GetDirectoryName(this.FullPath)!;
public virtual ProcessPriorityClass Priority => ProcessPriorityClass.Normal;
public virtual TimeSpan StopTimeout => TimeSpan.FromSeconds(15);
// Service management
public virtual ServiceStartMode StartMode => ServiceStartMode.Automatic;
public virtual string[] ServiceDependencies => new string[0];
public virtual bool Interactive => false;
public virtual bool DelayedAutoStart => false;
public virtual bool Preshutdown => false;
// Logging
public virtual string LogDirectory => Path.GetDirectoryName(this.ExecutablePath)!;
public virtual string LogMode => "append";
public virtual bool OutFileDisabled => false;
public virtual bool ErrFileDisabled => false;
public virtual string OutFilePattern => ".out.log";
public virtual string ErrFilePattern => ".err.log";
// Environment
public virtual List<Download> Downloads => new List<Download>(0);
public virtual Dictionary<string, string> EnvironmentVariables => new Dictionary<string, string>(0);
// Misc
public virtual bool BeepOnShutdown => false;
// Extensions
public virtual XmlNode? ExtensionsConfiguration => null;
}
}

View File

@ -15,17 +15,15 @@ namespace WinSW
/// <summary>
/// In-memory representation of the configuration file.
/// </summary>
public class ServiceDescriptor : IWinSWConfiguration
public class XmlServiceConfig : ServiceConfig
{
protected readonly XmlDocument dom = new XmlDocument();
private readonly Dictionary<string, string> environmentVariables;
internal static ServiceDescriptor? TestDescriptor;
internal static XmlServiceConfig? TestConfig;
public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
public string FullPath { get; }
public override string FullPath { get; }
/// <summary>
/// Where did we find the configuration file?
@ -41,10 +39,7 @@ namespace WinSW
/// </summary>
public string BaseName { get; set; }
// Currently there is no opportunity to alter the executable path
public virtual string ExecutablePath => Defaults.ExecutablePath;
public ServiceDescriptor()
public XmlServiceConfig()
{
string path = this.ExecutablePath;
string baseName = Path.GetFileNameWithoutExtension(path);
@ -84,7 +79,7 @@ namespace WinSW
}
/// <exception cref="FileNotFoundException" />
public ServiceDescriptor(string path)
public XmlServiceConfig(string path)
{
if (!File.Exists(path))
{
@ -122,10 +117,10 @@ namespace WinSW
}
/// <summary>
/// Loads descriptor from existing DOM
/// Loads config from existing DOM
/// </summary>
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
public ServiceDescriptor(XmlDocument dom)
public XmlServiceConfig(XmlDocument dom)
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
{
this.dom = dom;
@ -133,16 +128,16 @@ namespace WinSW
this.environmentVariables = this.LoadEnvironmentVariables();
}
internal static ServiceDescriptor Create(string? path)
internal static XmlServiceConfig Create(string? path)
{
return path != null ? new ServiceDescriptor(path) : TestDescriptor ?? new ServiceDescriptor();
return path != null ? new XmlServiceConfig(path) : TestConfig ?? new XmlServiceConfig();
}
public static ServiceDescriptor FromXml(string xml)
public static XmlServiceConfig FromXml(string xml)
{
var dom = new XmlDocument();
dom.LoadXml(xml);
return new ServiceDescriptor(dom);
return new XmlServiceConfig(dom);
}
private string SingleElement(string tagName)
@ -213,31 +208,31 @@ namespace WinSW
/// <summary>
/// Path to the executable.
/// </summary>
public string Executable => this.SingleElement("executable");
public override string Executable => this.SingleElement("executable");
public bool HideWindow => this.SingleBoolElement("hidewindow", Defaults.HideWindow);
public override bool HideWindow => this.SingleBoolElement("hidewindow", base.HideWindow);
/// <summary>
/// Optionally specify a different Path to an executable to shutdown the service.
/// </summary>
public string? StopExecutable => this.SingleElement("stopexecutable", true);
public override string? StopExecutable => this.SingleElement("stopexecutable", true);
/// <summary>
/// The <c>arguments</c> element.
/// </summary>
public string Arguments
public override string Arguments
{
get
{
XmlNode? argumentsNode = this.dom.SelectSingleNode("//arguments");
return argumentsNode is null ? Defaults.Arguments : Environment.ExpandEnvironmentVariables(argumentsNode.InnerText);
return argumentsNode is null ? base.Arguments : Environment.ExpandEnvironmentVariables(argumentsNode.InnerText);
}
}
/// <summary>
/// The <c>startarguments</c> element.
/// </summary>
public string? StartArguments
public override string? StartArguments
{
get
{
@ -249,7 +244,7 @@ namespace WinSW
/// <summary>
/// The <c>stoparguments</c> element.
/// </summary>
public string? StopArguments
public override string? StopArguments
{
get
{
@ -274,12 +269,12 @@ namespace WinSW
public string? PoststopArguments => this.GetArguments(Names.Poststop);
public string WorkingDirectory
public override string WorkingDirectory
{
get
{
var wd = this.SingleElement("workingdirectory", true);
return string.IsNullOrEmpty(wd) ? Defaults.WorkingDirectory : wd!;
return string.IsNullOrEmpty(wd) ? base.WorkingDirectory : wd!;
}
}
@ -304,7 +299,7 @@ namespace WinSW
}
}
public XmlNode? ExtensionsConfiguration => this.dom.SelectSingleNode("//extensions");
public override XmlNode? ExtensionsConfiguration => this.dom.SelectSingleNode("//extensions");
/// <summary>
/// Combines the contents of all the elements of the given name,
@ -351,19 +346,19 @@ namespace WinSW
/// <summary>
/// LogDirectory is the service wrapper executable directory or the optionally specified logpath element.
/// </summary>
public string LogDirectory
public override string LogDirectory
{
get
{
XmlNode? loggingNode = this.dom.SelectSingleNode("//logpath");
return loggingNode is null
? Defaults.LogDirectory
? base.LogDirectory
: Environment.ExpandEnvironmentVariables(loggingNode.InnerText);
}
}
public string LogMode
public override string LogMode
{
get
{
@ -385,7 +380,7 @@ namespace WinSW
}
}
return mode ?? Defaults.LogMode;
return mode ?? base.LogMode;
}
}
@ -399,27 +394,27 @@ namespace WinSW
}
}
public bool OutFileDisabled => this.SingleBoolElement("outfiledisabled", Defaults.OutFileDisabled);
public override bool OutFileDisabled => this.SingleBoolElement("outfiledisabled", base.OutFileDisabled);
public bool ErrFileDisabled => this.SingleBoolElement("errfiledisabled", Defaults.ErrFileDisabled);
public override bool ErrFileDisabled => this.SingleBoolElement("errfiledisabled", base.ErrFileDisabled);
public string OutFilePattern
public override string OutFilePattern
{
get
{
XmlNode? loggingName = this.dom.SelectSingleNode("//outfilepattern");
return loggingName is null ? Defaults.OutFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
return loggingName is null ? base.OutFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
}
}
public string ErrFilePattern
public override string ErrFilePattern
{
get
{
XmlNode? loggingName = this.dom.SelectSingleNode("//errfilepattern");
return loggingName is null ? Defaults.ErrFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
return loggingName is null ? base.ErrFilePattern : Environment.ExpandEnvironmentVariables(loggingName.InnerText);
}
}
@ -514,14 +509,14 @@ namespace WinSW
/// <summary>
/// Optionally specified depend services that must start before this service starts.
/// </summary>
public string[] ServiceDependencies
public override string[] ServiceDependencies
{
get
{
XmlNodeList? nodeList = this.dom.SelectNodes("//depend");
if (nodeList is null)
{
return Defaults.ServiceDependencies;
return base.ServiceDependencies;
}
string[] serviceDependencies = new string[nodeList.Count];
@ -534,23 +529,23 @@ namespace WinSW
}
}
public string Id => this.SingleElement("id");
public override string Id => this.SingleElement("id");
public string Caption => this.SingleElement("name", true) ?? Defaults.Caption;
public override string Caption => this.SingleElement("name", true) ?? base.Caption;
public string Description => this.SingleElement("description", true) ?? Defaults.Description;
public override string Description => this.SingleElement("description", true) ?? base.Description;
/// <summary>
/// Start mode of the Service
/// </summary>
public ServiceStartMode StartMode
public override ServiceStartMode StartMode
{
get
{
string? p = this.SingleElement("startmode", true);
if (p is null)
{
return Defaults.StartMode;
return base.StartMode;
}
try
@ -575,9 +570,9 @@ namespace WinSW
/// True if the service should be installed with the DelayedAutoStart flag.
/// This setting will be applyed only during the install command and only when the Automatic start mode is configured.
/// </summary>
public bool DelayedAutoStart => this.SingleBoolElement("delayedAutoStart", Defaults.DelayedAutoStart);
public override bool DelayedAutoStart => this.SingleBoolElement("delayedAutoStart", base.DelayedAutoStart);
public bool Preshutdown => this.SingleBoolElement("preshutdown", Defaults.Preshutdown);
public override bool Preshutdown => this.SingleBoolElement("preshutdown", base.Preshutdown);
public TimeSpan? PreshutdownTimeout
{
@ -592,30 +587,30 @@ namespace WinSW
/// True if the service should beep when finished on shutdown.
/// This doesn't work on some OSes. See http://msdn.microsoft.com/en-us/library/ms679277%28VS.85%29.aspx
/// </summary>
public bool BeepOnShutdown => this.SingleBoolElement("beeponshutdown", Defaults.DelayedAutoStart);
public override bool BeepOnShutdown => this.SingleBoolElement("beeponshutdown", base.DelayedAutoStart);
/// <summary>
/// True if the service can interact with the desktop.
/// </summary>
public bool Interactive => this.SingleBoolElement("interactive", Defaults.DelayedAutoStart);
public override bool Interactive => this.SingleBoolElement("interactive", base.DelayedAutoStart);
/// <summary>
/// Environment variable overrides
/// </summary>
public Dictionary<string, string> EnvironmentVariables => new Dictionary<string, string>(this.environmentVariables);
public override Dictionary<string, string> EnvironmentVariables => new Dictionary<string, string>(this.environmentVariables);
/// <summary>
/// List of downloads to be performed by the wrapper before starting
/// a service.
/// </summary>
public List<Download> Downloads
public override List<Download> Downloads
{
get
{
XmlNodeList? nodeList = this.dom.SelectNodes("//download");
if (nodeList is null)
{
return Defaults.Downloads;
return base.Downloads;
}
List<Download> result = new List<Download>(nodeList.Count);
@ -631,7 +626,7 @@ namespace WinSW
}
}
public SC_ACTION[] FailureActions
public override SC_ACTION[] FailureActions
{
get
{
@ -661,7 +656,7 @@ namespace WinSW
}
}
public TimeSpan ResetFailureAfter => this.SingleTimeSpanElement(this.dom, "resetfailure", Defaults.ResetFailureAfter);
public override TimeSpan ResetFailureAfter => this.SingleTimeSpanElement(this.dom, "resetfailure", base.ResetFailureAfter);
protected string? GetServiceAccountPart(string subNodeName)
{
@ -683,16 +678,16 @@ namespace WinSW
protected string? AllowServiceLogon => this.GetServiceAccountPart("allowservicelogon");
public string? ServiceAccountPassword => this.GetServiceAccountPart("password");
public override string? ServiceAccountPassword => this.GetServiceAccountPart("password");
public string? ServiceAccountUserName => this.GetServiceAccountPart("username");
public override string? ServiceAccountUserName => this.GetServiceAccountPart("username");
public bool HasServiceAccount()
{
return this.dom.SelectSingleNode("//serviceaccount") != null;
}
public bool AllowServiceAcountLogonRight
public override bool AllowServiceAcountLogonRight
{
get
{
@ -711,19 +706,19 @@ namespace WinSW
/// <summary>
/// Time to wait for the service to gracefully shutdown the executable before we forcibly kill it
/// </summary>
public TimeSpan StopTimeout => this.SingleTimeSpanElement(this.dom, "stoptimeout", Defaults.StopTimeout);
public override TimeSpan StopTimeout => this.SingleTimeSpanElement(this.dom, "stoptimeout", base.StopTimeout);
/// <summary>
/// Desired process priority or null if not specified.
/// </summary>
public ProcessPriorityClass Priority
public override ProcessPriorityClass Priority
{
get
{
string? p = this.SingleElement("priority", true);
if (p is null)
{
return Defaults.Priority;
return base.Priority;
}
return (ProcessPriorityClass)Enum.Parse(typeof(ProcessPriorityClass), p, true);

View File

@ -10,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(ServiceDescriptor descriptor, XmlNode node)
public virtual void Configure(XmlServiceConfig config, XmlNode node)
{
// Do nothing
}

View File

@ -25,9 +25,9 @@ namespace WinSW.Extensions
/// <summary>
/// Init handler. Extension should load it's config during that step
/// </summary>
/// <param name="descriptor">Service descriptor</param>
/// <param name="config">Service config</param>
/// <param name="node">Configuration node</param>
void Configure(ServiceDescriptor descriptor, XmlNode node);
void Configure(XmlServiceConfig config, XmlNode node);
/// <summary>
/// Start handler. Called during startup of the service before the child process.

View File

@ -9,13 +9,13 @@ namespace WinSW.Extensions
{
public Dictionary<string, IWinSWExtension> Extensions { get; }
public ServiceDescriptor ServiceDescriptor { get; }
public XmlServiceConfig ServiceConfig { get; }
private static readonly ILog Log = LogManager.GetLogger(typeof(WinSWExtensionManager));
public WinSWExtensionManager(ServiceDescriptor serviceDescriptor)
public WinSWExtensionManager(XmlServiceConfig serviceConfig)
{
this.ServiceDescriptor = serviceDescriptor;
this.ServiceConfig = serviceConfig;
this.Extensions = new Dictionary<string, IWinSWExtension>();
}
@ -107,7 +107,7 @@ namespace WinSW.Extensions
/// <exception cref="Exception">Loading failure</exception>
public void LoadExtensions()
{
var extensionIds = this.ServiceDescriptor.ExtensionIds;
var extensionIds = this.ServiceConfig.ExtensionIds;
foreach (string extensionId in extensionIds)
{
this.LoadExtension(extensionId);
@ -127,7 +127,7 @@ namespace WinSW.Extensions
throw new ExtensionException(id, "Extension has been already loaded");
}
XmlNode? extensionsConfig = this.ServiceDescriptor.ExtensionsConfiguration;
XmlNode? extensionsConfig = this.ServiceConfig.ExtensionsConfiguration;
XmlElement? configNode = extensionsConfig is null ? null : extensionsConfig.SelectSingleNode("extension[@id='" + id + "'][1]") as XmlElement;
if (configNode is null)
{
@ -141,7 +141,7 @@ namespace WinSW.Extensions
extension.Descriptor = descriptor;
try
{
extension.Configure(this.ServiceDescriptor, configNode);
extension.Configure(this.ServiceConfig, configNode);
}
catch (Exception ex)
{ // Consider any unexpected exception as fatal

View File

@ -173,13 +173,13 @@ namespace WinSW.Plugins.RunawayProcessKiller
return parameters.Environment;
}
public override void Configure(ServiceDescriptor descriptor, XmlNode node)
public override void Configure(XmlServiceConfig config, XmlNode node)
{
// 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.ServiceId = descriptor.Id;
this.ServiceId = config.Id;
// TODO: Consider making it documented
var checkWinSWEnvironmentVariable = XmlHelper.SingleElement(node, "checkWinSWEnvironmentVariable", true);

View File

@ -27,7 +27,7 @@ namespace WinSW.Plugins.SharedDirectoryMapper
this.entries.Add(config);
}
public override void Configure(ServiceDescriptor descriptor, XmlNode node)
public override void Configure(XmlServiceConfig config, XmlNode node)
{
XmlNodeList? mapNodes = XmlHelper.SingleNode(node, "mapping", false)!.SelectNodes("map");
if (mapNodes != null)
@ -36,8 +36,7 @@ namespace WinSW.Plugins.SharedDirectoryMapper
{
if (mapNodes[i] is XmlElement mapElement)
{
var config = SharedDirectoryMapperConfig.FromXml(mapElement);
this.entries.Add(config);
this.entries.Add(SharedDirectoryMapperConfig.FromXml(mapElement));
}
}
}

View File

@ -15,26 +15,26 @@ namespace WinSW.Tests.Configuration
[Fact]
public void AllOptionsConfigShouldDeclareDefaults()
{
ServiceDescriptor desc = Load("complete");
XmlServiceConfig config = Load("complete");
Assert.Equal("myapp", desc.Id);
Assert.Equal("%BASE%\\myExecutable.exe", desc.Executable);
Assert.Equal("myapp", config.Id);
Assert.Equal("%BASE%\\myExecutable.exe", config.Executable);
ServiceDescriptorAssert.AssertAllOptionalPropertiesAreDefault(desc);
ServiceConfigAssert.AssertAllOptionalPropertiesAreDefault(config);
}
[Fact]
public void MinimalConfigShouldDeclareDefaults()
{
ServiceDescriptor desc = Load("minimal");
XmlServiceConfig config = Load("minimal");
Assert.Equal("myapp", desc.Id);
Assert.Equal("%BASE%\\myExecutable.exe", desc.Executable);
Assert.Equal("myapp", config.Id);
Assert.Equal("%BASE%\\myExecutable.exe", config.Executable);
ServiceDescriptorAssert.AssertAllOptionalPropertiesAreDefault(desc);
ServiceConfigAssert.AssertAllOptionalPropertiesAreDefault(config);
}
private static ServiceDescriptor Load(string exampleName)
private static XmlServiceConfig Load(string exampleName)
{
string directory = Environment.CurrentDirectory;
while (true)
@ -53,7 +53,7 @@ namespace WinSW.Tests.Configuration
XmlDocument dom = new XmlDocument();
dom.Load(path);
return new ServiceDescriptor(dom);
return new XmlServiceConfig(dom);
}
}
}

View File

@ -22,10 +22,10 @@ namespace WinSW.Tests
{
// Roundtrip data
Download d = new Download(From, To);
var sd = ConfigXmlBuilder.Create(this.output)
var config = ConfigXmlBuilder.Create(this.output)
.WithDownload(d)
.ToServiceDescriptor(true);
var loaded = this.GetSingleEntry(sd);
.ToServiceConfig(true);
var loaded = this.GetSingleEntry(config);
// Check default values
Assert.False(loaded.FailOnError);
@ -40,10 +40,10 @@ namespace WinSW.Tests
{
// Roundtrip data
Download d = new Download(From, To, true, Download.AuthType.Basic, "aUser", "aPassword", true);
var sd = ConfigXmlBuilder.Create(this.output)
var config = ConfigXmlBuilder.Create(this.output)
.WithDownload(d)
.ToServiceDescriptor(true);
var loaded = this.GetSingleEntry(sd);
.ToServiceConfig(true);
var loaded = this.GetSingleEntry(config);
// Check default values
Assert.True(loaded.FailOnError);
@ -58,10 +58,10 @@ namespace WinSW.Tests
{
// Roundtrip data
Download d = new Download(From, To, false, Download.AuthType.Sspi);
var sd = ConfigXmlBuilder.Create(this.output)
var config = ConfigXmlBuilder.Create(this.output)
.WithDownload(d)
.ToServiceDescriptor(true);
var loaded = this.GetSingleEntry(sd);
.ToServiceConfig(true);
var loaded = this.GetSingleEntry(config);
// Check default values
Assert.False(loaded.FailOnError);
@ -107,11 +107,11 @@ namespace WinSW.Tests
{
Download d = new Download(From, To, failOnError);
var sd = ConfigXmlBuilder.Create(this.output)
var config = ConfigXmlBuilder.Create(this.output)
.WithDownload(d)
.ToServiceDescriptor(true);
.ToServiceConfig(true);
var loaded = this.GetSingleEntry(sd);
var loaded = this.GetSingleEntry(config);
Assert.Equal(From, loaded.From);
Assert.Equal(To, loaded.To);
Assert.Equal(failOnError, loaded.FailOnError);
@ -123,11 +123,11 @@ namespace WinSW.Tests
[Fact]
public void Download_FailOnError_Undefined()
{
var sd = ConfigXmlBuilder.Create(this.output)
var config = ConfigXmlBuilder.Create(this.output)
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\"/>")
.ToServiceDescriptor(true);
.ToServiceConfig(true);
var loaded = this.GetSingleEntry(sd);
var loaded = this.GetSingleEntry(config);
Assert.False(loaded.FailOnError);
}
@ -138,10 +138,10 @@ namespace WinSW.Tests
[InlineData("Sspi")]
public void AuthType_Is_CaseInsensitive(string authType)
{
var sd = ConfigXmlBuilder.Create(this.output)
var config = ConfigXmlBuilder.Create(this.output)
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"" + authType + "\"/>")
.ToServiceDescriptor(true);
var loaded = this.GetSingleEntry(sd);
.ToServiceConfig(true);
var loaded = this.GetSingleEntry(config);
Assert.Equal(Download.AuthType.Sspi, loaded.Auth);
}
@ -149,11 +149,11 @@ namespace WinSW.Tests
public void Should_Fail_On_Unsupported_AuthType()
{
// TODO: will need refactoring once all fields are being parsed on startup
var sd = ConfigXmlBuilder.Create(this.output)
var config = ConfigXmlBuilder.Create(this.output)
.WithRawEntry("<download from=\"http://www.nosuchhostexists.foo.myorg/foo.xml\" to=\"%BASE%\\foo.xml\" auth=\"digest\"/>")
.ToServiceDescriptor(true);
.ToServiceConfig(true);
var e = Assert.Throws<InvalidDataException>(() => this.GetSingleEntry(sd));
var e = Assert.Throws<InvalidDataException>(() => this.GetSingleEntry(config));
Assert.StartsWith("Cannot parse <auth> Enum value from string 'digest'", e.Message);
}
@ -187,19 +187,19 @@ namespace WinSW.Tests
}
}
private Download GetSingleEntry(ServiceDescriptor sd)
private Download GetSingleEntry(XmlServiceConfig config)
{
var downloads = sd.Downloads.ToArray();
var downloads = config.Downloads.ToArray();
return Assert.Single(downloads);
}
private void AssertInitializationFails(Download download, string expectedMessagePart = null)
{
var sd = ConfigXmlBuilder.Create(this.output)
var config = ConfigXmlBuilder.Create(this.output)
.WithDownload(download)
.ToServiceDescriptor(true);
.ToServiceConfig(true);
var e = Assert.Throws<InvalidDataException>(() => this.GetSingleEntry(sd));
var e = Assert.Throws<InvalidDataException>(() => this.GetSingleEntry(config));
Assert.StartsWith(expectedMessagePart, e.Message);
}
}

View File

@ -12,7 +12,7 @@ namespace WinSW.Tests.Extensions
{
public class RunawayProcessKillerExtensionTest : ExtensionTestBase
{
private readonly ServiceDescriptor testServiceDescriptor;
private readonly XmlServiceConfig serviceConfig;
private readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(RunawayProcessKillerExtension));
@ -37,13 +37,13 @@ $@"<service>
</extension>
</extensions>
</service>";
this.testServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
this.serviceConfig = XmlServiceConfig.FromXml(seedXml);
}
[Fact]
public void LoadExtensions()
{
WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this.serviceConfig);
manager.LoadExtensions();
_ = Assert.Single(manager.Extensions);
@ -57,7 +57,7 @@ $@"<service>
[Fact]
public void StartStopExtension()
{
WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this.serviceConfig);
manager.LoadExtensions();
manager.FireOnWrapperStarted();
manager.FireBeforeWrapperStopped();
@ -84,10 +84,10 @@ $@"<service>
{
// Generate extension and ensure that the roundtrip is correct
var pidfile = Path.Combine(tmpDir, "process.pid");
var sd = ConfigXmlBuilder.Create(this.output, id: winswId)
var config = ConfigXmlBuilder.Create(this.output, id: winswId)
.WithRunawayProcessKiller(new RunawayProcessKillerExtension(pidfile), extensionId)
.ToServiceDescriptor();
WinSWExtensionManager manager = new WinSWExtensionManager(sd);
.ToServiceConfig();
WinSWExtensionManager manager = new WinSWExtensionManager(config);
manager.LoadExtensions();
var extension = manager.Extensions[extensionId] as RunawayProcessKillerExtension;
Assert.NotNull(extension);

View File

@ -6,7 +6,7 @@ namespace WinSW.Tests.Extensions
{
public class SharedDirectoryMapperConfigTest : ExtensionTestBase
{
private readonly ServiceDescriptor testServiceDescriptor;
private readonly XmlServiceConfig serviceConfig;
private readonly string testExtension = GetExtensionClassNameWithAssembly(typeof(SharedDirectoryMapper));
@ -35,13 +35,13 @@ $@"<service>
</extension>
</extensions>
</service>";
this.testServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
this.serviceConfig = XmlServiceConfig.FromXml(seedXml);
}
[Fact]
public void LoadExtensions()
{
WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this.serviceConfig);
manager.LoadExtensions();
Assert.Equal(2, manager.Extensions.Count);
}
@ -49,7 +49,7 @@ $@"<service>
[Fact]
public void StartStopExtension()
{
WinSWExtensionManager manager = new WinSWExtensionManager(this.testServiceDescriptor);
WinSWExtensionManager manager = new WinSWExtensionManager(this.serviceConfig);
manager.LoadExtensions();
manager.FireOnWrapperStarted();
manager.FireBeforeWrapperStopped();

View File

@ -8,7 +8,7 @@ using Xunit.Abstractions;
namespace WinSW.Tests
{
public class ServiceDescriptorTests
public class ServiceConfigTests
{
private const string ExpectedWorkingDirectory = @"Z:\Path\SubPath";
private const string Username = "User";
@ -18,9 +18,9 @@ namespace WinSW.Tests
private readonly ITestOutputHelper output;
private ServiceDescriptor extendedServiceDescriptor;
private XmlServiceConfig extendedServiceConfig;
public ServiceDescriptorTests(ITestOutputHelper output)
public ServiceConfigTests(ITestOutputHelper output)
{
this.output = output;
@ -40,13 +40,13 @@ $@"<service>
<workingdirectory>{ExpectedWorkingDirectory}</workingdirectory>
<logpath>C:\logs</logpath>
</service>";
this.extendedServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
this.extendedServiceConfig = XmlServiceConfig.FromXml(seedXml);
}
[Fact]
public void DefaultStartMode()
{
Assert.Equal(ServiceStartMode.Automatic, this.extendedServiceDescriptor.StartMode);
Assert.Equal(ServiceStartMode.Automatic, this.extendedServiceConfig.StartMode);
}
[Fact]
@ -70,8 +70,8 @@ $@"<service>
<logpath>C:\logs</logpath>
</service>";
this.extendedServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
_ = Assert.Throws<InvalidDataException>(() => this.extendedServiceDescriptor.StartMode);
this.extendedServiceConfig = XmlServiceConfig.FromXml(seedXml);
_ = Assert.Throws<InvalidDataException>(() => this.extendedServiceConfig.StartMode);
}
[Fact]
@ -95,48 +95,45 @@ $@"<service>
<logpath>C:\logs</logpath>
</service>";
this.extendedServiceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.Equal(ServiceStartMode.Manual, this.extendedServiceDescriptor.StartMode);
this.extendedServiceConfig = XmlServiceConfig.FromXml(seedXml);
Assert.Equal(ServiceStartMode.Manual, this.extendedServiceConfig.StartMode);
}
[Fact]
public void VerifyWorkingDirectory()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this.extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(ExpectedWorkingDirectory, this.extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(ExpectedWorkingDirectory, this.extendedServiceConfig.WorkingDirectory);
}
[Fact]
public void VerifyServiceLogonRight()
{
Assert.True(this.extendedServiceDescriptor.AllowServiceAcountLogonRight);
Assert.True(this.extendedServiceConfig.AllowServiceAcountLogonRight);
}
[Fact]
public void VerifyUsername()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this.extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(Domain + "\\" + Username, this.extendedServiceDescriptor.ServiceAccountUserName);
Assert.Equal(Domain + "\\" + Username, this.extendedServiceConfig.ServiceAccountUserName);
}
[Fact]
public void VerifyPassword()
{
Debug.WriteLine("_extendedServiceDescriptor.WorkingDirectory :: " + this.extendedServiceDescriptor.WorkingDirectory);
Assert.Equal(Password, this.extendedServiceDescriptor.ServiceAccountPassword);
Assert.Equal(Password, this.extendedServiceConfig.ServiceAccountPassword);
}
[Fact]
public void Priority()
{
var sd = ServiceDescriptor.FromXml("<service><id>test</id><priority>normal</priority></service>");
Assert.Equal(ProcessPriorityClass.Normal, sd.Priority);
var config = XmlServiceConfig.FromXml("<service><id>test</id><priority>normal</priority></service>");
Assert.Equal(ProcessPriorityClass.Normal, config.Priority);
sd = ServiceDescriptor.FromXml("<service><id>test</id><priority>idle</priority></service>");
Assert.Equal(ProcessPriorityClass.Idle, sd.Priority);
config = XmlServiceConfig.FromXml("<service><id>test</id><priority>idle</priority></service>");
Assert.Equal(ProcessPriorityClass.Idle, config.Priority);
sd = ServiceDescriptor.FromXml("<service><id>test</id></service>");
Assert.Equal(ProcessPriorityClass.Normal, sd.Priority);
config = XmlServiceConfig.FromXml("<service><id>test</id></service>");
Assert.Equal(ProcessPriorityClass.Normal, config.Priority);
}
[Fact]
@ -145,9 +142,9 @@ $@"<service>
const string seedXml = "<service>"
+ "<stoptimeout>60sec</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
var config = XmlServiceConfig.FromXml(seedXml);
Assert.Equal(TimeSpan.FromSeconds(60), serviceDescriptor.StopTimeout);
Assert.Equal(TimeSpan.FromSeconds(60), config.StopTimeout);
}
[Fact]
@ -156,9 +153,9 @@ $@"<service>
const string seedXml = "<service>"
+ "<stoptimeout>10min</stoptimeout>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
var config = XmlServiceConfig.FromXml(seedXml);
Assert.Equal(TimeSpan.FromMinutes(10), serviceDescriptor.StopTimeout);
Assert.Equal(TimeSpan.FromMinutes(10), config.StopTimeout);
}
[Fact]
@ -167,9 +164,9 @@ $@"<service>
const string seedXml = "<service>"
+ "<logname>MyTestApp</logname>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
var config = XmlServiceConfig.FromXml(seedXml);
Assert.Equal("MyTestApp", serviceDescriptor.LogName);
Assert.Equal("MyTestApp", config.LogName);
}
[Fact]
@ -178,9 +175,9 @@ $@"<service>
const string seedXml = "<service>"
+ "<outfiledisabled>true</outfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
var config = XmlServiceConfig.FromXml(seedXml);
Assert.True(serviceDescriptor.OutFileDisabled);
Assert.True(config.OutFileDisabled);
}
[Fact]
@ -189,9 +186,9 @@ $@"<service>
const string seedXml = "<service>"
+ "<errfiledisabled>true</errfiledisabled>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
var config = XmlServiceConfig.FromXml(seedXml);
Assert.True(serviceDescriptor.ErrFileDisabled);
Assert.True(config.ErrFileDisabled);
}
[Fact]
@ -200,9 +197,9 @@ $@"<service>
const string seedXml = "<service>"
+ "<outfilepattern>.out.test.log</outfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
var config = XmlServiceConfig.FromXml(seedXml);
Assert.Equal(".out.test.log", serviceDescriptor.OutFilePattern);
Assert.Equal(".out.test.log", config.OutFilePattern);
}
[Fact]
@ -211,9 +208,9 @@ $@"<service>
const string seedXml = "<service>"
+ "<errfilepattern>.err.test.log</errfilepattern>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
var config = XmlServiceConfig.FromXml(seedXml);
Assert.Equal(".err.test.log", serviceDescriptor.ErrFilePattern);
Assert.Equal(".err.test.log", config.ErrFilePattern);
}
[Fact]
@ -227,10 +224,10 @@ $@"<service>
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
serviceDescriptor.BaseName = "service";
var config = XmlServiceConfig.FromXml(seedXml);
config.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as SizeBasedRollingLogAppender;
var logHandler = config.LogHandler as SizeBasedRollingLogAppender;
Assert.NotNull(logHandler);
Assert.Equal(112 * 1024, logHandler.SizeThreshold);
Assert.Equal(113, logHandler.FilesToKeep);
@ -247,10 +244,10 @@ $@"<service>
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
serviceDescriptor.BaseName = "service";
var config = XmlServiceConfig.FromXml(seedXml);
config.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as TimeBasedRollingLogAppender;
var logHandler = config.LogHandler as TimeBasedRollingLogAppender;
Assert.NotNull(logHandler);
Assert.Equal(7, logHandler.Period);
Assert.Equal("log pattern", logHandler.Pattern);
@ -268,10 +265,10 @@ $@"<service>
+ "</log>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
serviceDescriptor.BaseName = "service";
var config = XmlServiceConfig.FromXml(seedXml);
config.BaseName = "service";
var logHandler = serviceDescriptor.LogHandler as RollingSizeTimeLogAppender;
var logHandler = config.LogHandler as RollingSizeTimeLogAppender;
Assert.NotNull(logHandler);
Assert.Equal(10240 * 1024, logHandler.SizeThreshold);
Assert.Equal("yyyy-MM-dd", logHandler.FilePattern);
@ -289,8 +286,8 @@ $@"<service>
+ "<allowservicelogon>true1</allowservicelogon>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.False(serviceDescriptor.AllowServiceAcountLogonRight);
var config = XmlServiceConfig.FromXml(seedXml);
Assert.False(config.AllowServiceAcountLogonRight);
}
[Fact]
@ -303,22 +300,22 @@ $@"<service>
+ "<password>" + Password + "</password>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXml(seedXml);
Assert.False(serviceDescriptor.AllowServiceAcountLogonRight);
var config = XmlServiceConfig.FromXml(seedXml);
Assert.False(config.AllowServiceAcountLogonRight);
}
[Fact]
public void VerifyResetFailureAfter()
{
var sd = ConfigXmlBuilder.Create(this.output).WithTag("resetfailure", "75 sec").ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromSeconds(75), sd.ResetFailureAfter);
var config = ConfigXmlBuilder.Create(this.output).WithTag("resetfailure", "75 sec").ToServiceConfig(true);
Assert.Equal(TimeSpan.FromSeconds(75), config.ResetFailureAfter);
}
[Fact]
public void VerifyStopTimeout()
{
var sd = ConfigXmlBuilder.Create(this.output).WithTag("stoptimeout", "35 secs").ToServiceDescriptor(true);
Assert.Equal(TimeSpan.FromSeconds(35), sd.StopTimeout);
var config = ConfigXmlBuilder.Create(this.output).WithTag("stoptimeout", "35 secs").ToServiceConfig(true);
Assert.Equal(TimeSpan.FromSeconds(35), config.StopTimeout);
}
/// <summary>
@ -327,8 +324,8 @@ $@"<service>
[Fact]
public void Arguments_LegacyParam()
{
var sd = ConfigXmlBuilder.Create(this.output).WithTag("arguments", "arg").ToServiceDescriptor(true);
Assert.Equal("arg", sd.Arguments);
var config = ConfigXmlBuilder.Create(this.output).WithTag("arguments", "arg").ToServiceConfig(true);
Assert.Equal("arg", config.Arguments);
}
[Theory]
@ -342,8 +339,8 @@ $@"<service>
bldr = bldr.WithDelayedAutoStart();
}
var sd = bldr.ToServiceDescriptor();
Assert.Equal(enabled, sd.DelayedAutoStart);
var config = bldr.ToServiceConfig();
Assert.Equal(enabled, config.DelayedAutoStart);
}
[Fact]
@ -378,16 +375,16 @@ $@"<service>
</poststop>
</service>";
ServiceDescriptor descriptor = ServiceDescriptor.FromXml(seedXml);
XmlServiceConfig config = XmlServiceConfig.FromXml(seedXml);
Assert.Equal(prestartExecutable, descriptor.PrestartExecutable);
Assert.Equal(prestartArguments, descriptor.PrestartArguments);
Assert.Equal(poststartExecutable, descriptor.PoststartExecutable);
Assert.Equal(poststartArguments, descriptor.PoststartArguments);
Assert.Equal(prestopExecutable, descriptor.PrestopExecutable);
Assert.Equal(prestopArguments, descriptor.PrestopArguments);
Assert.Equal(poststopExecutable, descriptor.PoststopExecutable);
Assert.Equal(poststopArguments, descriptor.PoststopArguments);
Assert.Equal(prestartExecutable, config.PrestartExecutable);
Assert.Equal(prestartArguments, config.PrestartArguments);
Assert.Equal(poststartExecutable, config.PoststartExecutable);
Assert.Equal(poststartArguments, config.PoststartArguments);
Assert.Equal(prestopExecutable, config.PrestopExecutable);
Assert.Equal(prestopArguments, config.PrestopArguments);
Assert.Equal(poststopExecutable, config.PoststopExecutable);
Assert.Equal(poststopArguments, config.PoststopArguments);
}
}
}

View File

@ -24,16 +24,16 @@ $@"<service>
<logpath>C:\winsw\logs</logpath>
</service>";
public static readonly ServiceDescriptor DefaultServiceDescriptor = ServiceDescriptor.FromXml(SeedXml);
public static readonly XmlServiceConfig DefaultServiceConfig = XmlServiceConfig.FromXml(SeedXml);
/// <summary>
/// Runs a simle test, which returns the output CLI
/// </summary>
/// <param name="arguments">CLI arguments to be passed</param>
/// <param name="descriptor">Optional Service descriptor (will be used for initializationpurposes)</param>
/// <param name="config">Optional Service config (will be used for initialization purposes)</param>
/// <returns>STDOUT if there's no exceptions</returns>
/// <exception cref="Exception">Command failure</exception>
public static string Test(string[] arguments, ServiceDescriptor descriptor = null)
public static string Test(string[] arguments, XmlServiceConfig config = null)
{
TextWriter tmpOut = Console.Out;
TextWriter tmpError = Console.Error;
@ -43,7 +43,7 @@ $@"<service>
Console.SetOut(swOut);
Console.SetError(swError);
ServiceDescriptor.TestDescriptor = descriptor ?? DefaultServiceDescriptor;
XmlServiceConfig.TestConfig = config ?? DefaultServiceConfig;
try
{
_ = Program.Run(arguments);
@ -52,7 +52,7 @@ $@"<service>
{
Console.SetOut(tmpOut);
Console.SetError(tmpError);
ServiceDescriptor.TestDescriptor = null;
XmlServiceConfig.TestConfig = null;
}
Assert.Equal(string.Empty, swError.ToString());
@ -63,9 +63,9 @@ $@"<service>
/// Runs a simle test, which returns the output CLI
/// </summary>
/// <param name="arguments">CLI arguments to be passed</param>
/// <param name="descriptor">Optional Service descriptor (will be used for initializationpurposes)</param>
/// <param name="config">Optional Service config (will be used for initialization purposes)</param>
/// <returns>Test results</returns>
public static CommandLineTestResult ErrorTest(string[] arguments, ServiceDescriptor descriptor = null)
public static CommandLineTestResult ErrorTest(string[] arguments, XmlServiceConfig config = null)
{
Exception exception = null;
@ -77,7 +77,7 @@ $@"<service>
Console.SetOut(swOut);
Console.SetError(swError);
ServiceDescriptor.TestDescriptor = descriptor ?? DefaultServiceDescriptor;
XmlServiceConfig.TestConfig = config ?? DefaultServiceConfig;
Program.TestExceptionHandler = (e, _) => exception = e;
try
{
@ -91,7 +91,7 @@ $@"<service>
{
Console.SetOut(tmpOut);
Console.SetError(tmpError);
ServiceDescriptor.TestDescriptor = null;
XmlServiceConfig.TestConfig = null;
Program.TestExceptionHandler = null;
}

View File

@ -104,9 +104,9 @@ namespace WinSW.Tests.Util
return res;
}
public ServiceDescriptor ToServiceDescriptor(bool dumpConfig = false)
public XmlServiceConfig ToServiceConfig(bool dumpConfig = false)
{
return ServiceDescriptor.FromXml(this.ToXmlString(dumpConfig));
return XmlServiceConfig.FromXml(this.ToXmlString(dumpConfig));
}
public ConfigXmlBuilder WithRawEntry(string entry)

View File

@ -0,0 +1,34 @@
using System.Reflection;
using WinSW.Configuration;
using Xunit;
namespace WinSW.Tests.Util
{
public static class ServiceConfigAssert
{
public static void AssertAllOptionalPropertiesAreDefault(XmlServiceConfig config)
{
var testConfig = new TestServiceConfig(config);
foreach (var property in typeof(ServiceConfig).GetProperties())
{
if (property.GetMethod!.IsVirtual)
{
Assert.Equal(property.GetValue(testConfig, null), property.GetValue(config, null));
}
}
}
private sealed class TestServiceConfig : ServiceConfig
{
private readonly XmlServiceConfig config;
internal TestServiceConfig(XmlServiceConfig config) => this.config = config;
public override string FullPath => this.config.FullPath;
public override string Id => this.config.Id;
public override string Executable => this.config.Executable;
}
}
}

View File

@ -1,64 +0,0 @@
using System.Collections.Generic;
using System.Reflection;
using WinSW.Configuration;
using Xunit;
namespace WinSW.Tests.Util
{
public static class ServiceDescriptorAssert
{
// TODO: convert to Extension attributes once the .NET dependency is upgraded
// BTW there is a way to get them working in .NET2, but KISS
public static void AssertPropertyIsDefault(ServiceDescriptor desc, string property)
{
PropertyInfo actualProperty = typeof(ServiceDescriptor).GetProperty(property);
Assert.NotNull(actualProperty);
PropertyInfo defaultProperty = typeof(DefaultWinSWSettings).GetProperty(property);
Assert.NotNull(defaultProperty);
Assert.Equal(defaultProperty.GetValue(ServiceDescriptor.Defaults, null), actualProperty.GetValue(desc, null));
}
public static void AssertPropertyIsDefault(ServiceDescriptor desc, List<string> properties)
{
foreach (var prop in properties)
{
AssertPropertyIsDefault(desc, prop);
}
}
public static void AssertAllOptionalPropertiesAreDefault(ServiceDescriptor desc)
{
AssertPropertyIsDefault(desc, AllOptionalProperties);
}
private static List<string> AllProperties
{
get
{
var res = new List<string>();
var properties = typeof(IWinSWConfiguration).GetProperties();
foreach (var prop in properties)
{
res.Add(prop.Name);
}
return res;
}
}
private static List<string> AllOptionalProperties
{
get
{
var properties = AllProperties;
properties.Remove("FullPath");
properties.Remove("Id");
properties.Remove("Executable");
properties.Remove("WorkingDirectory");
return properties;
}
}
}
}

View File

@ -71,20 +71,20 @@ namespace WinSW
{
Handler = CommandHandler.Create((string? pathToConfig) =>
{
ServiceDescriptor descriptor;
XmlServiceConfig config;
try
{
descriptor = ServiceDescriptor.Create(pathToConfig);
config = XmlServiceConfig.Create(pathToConfig);
}
catch (FileNotFoundException)
{
throw new CommandException("The specified command or file was not found.");
}
InitLoggers(descriptor, enableConsoleLogging: false);
InitLoggers(config, enableConsoleLogging: false);
Log.Debug("Starting WinSW in service mode");
ServiceBase.Run(new WrapperService(descriptor));
ServiceBase.Run(new WrapperService(config));
}),
};
@ -320,8 +320,8 @@ namespace WinSW
void Install(string? pathToConfig, bool noElevate, string? username, string? password)
{
ServiceDescriptor descriptor = ServiceDescriptor.Create(pathToConfig);
InitLoggers(descriptor, enableConsoleLogging: true);
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
InitLoggers(config, enableConsoleLogging: true);
if (!elevated)
{
@ -329,25 +329,25 @@ namespace WinSW
return;
}
Log.Info("Installing the service with id '" + descriptor.Id + "'");
Log.Info("Installing the service with id '" + config.Id + "'");
using ServiceManager scm = ServiceManager.Open();
if (scm.ServiceExists(descriptor.Id))
if (scm.ServiceExists(config.Id))
{
Console.WriteLine("Service with id '" + descriptor.Id + "' already exists");
Console.WriteLine("Service with id '" + config.Id + "' already exists");
Console.WriteLine("To install the service, delete the existing one or change service Id in the configuration file");
throw new CommandException("Installation failure: Service with id '" + descriptor.Id + "' already exists");
throw new CommandException("Installation failure: Service with id '" + config.Id + "' already exists");
}
if (descriptor.HasServiceAccount())
if (config.HasServiceAccount())
{
username = descriptor.ServiceAccountUserName ?? username;
password = descriptor.ServiceAccountPassword ?? password;
username = config.ServiceAccountUserName ?? username;
password = config.ServiceAccountPassword ?? password;
if (username is null || password is null)
{
switch (descriptor.ServiceAccountPrompt)
switch (config.ServiceAccountPrompt)
{
case "dialog":
Credentials.PropmtForCredentialsDialog(
@ -370,45 +370,45 @@ namespace WinSW
}
using Service sc = scm.CreateService(
descriptor.Id,
descriptor.Caption,
descriptor.StartMode,
"\"" + descriptor.ExecutablePath + "\"" + (pathToConfig != null ? " \"" + Path.GetFullPath(pathToConfig) + "\"" : null),
descriptor.ServiceDependencies,
config.Id,
config.Caption,
config.StartMode,
"\"" + config.ExecutablePath + "\"" + (pathToConfig != null ? " \"" + Path.GetFullPath(pathToConfig) + "\"" : null),
config.ServiceDependencies,
username,
password);
string description = descriptor.Description;
string description = config.Description;
if (description.Length != 0)
{
sc.SetDescription(description);
}
SC_ACTION[] actions = descriptor.FailureActions;
SC_ACTION[] actions = config.FailureActions;
if (actions.Length > 0)
{
sc.SetFailureActions(descriptor.ResetFailureAfter, actions);
sc.SetFailureActions(config.ResetFailureAfter, actions);
}
bool isDelayedAutoStart = descriptor.StartMode == ServiceStartMode.Automatic && descriptor.DelayedAutoStart;
bool isDelayedAutoStart = config.StartMode == ServiceStartMode.Automatic && config.DelayedAutoStart;
if (isDelayedAutoStart)
{
sc.SetDelayedAutoStart(true);
}
if (descriptor.PreshutdownTimeout is TimeSpan preshutdownTimeout)
if (config.PreshutdownTimeout is TimeSpan preshutdownTimeout)
{
sc.SetPreshutdownTimeout(preshutdownTimeout);
}
string? securityDescriptor = descriptor.SecurityDescriptor;
string? securityDescriptor = config.SecurityDescriptor;
if (securityDescriptor != null)
{
// throws ArgumentException
sc.SetSecurityDescriptor(new RawSecurityDescriptor(securityDescriptor));
}
string eventLogSource = descriptor.Id;
string eventLogSource = config.Id;
if (!EventLog.SourceExists(eventLogSource))
{
EventLog.CreateEventSource(eventLogSource, "Application");
@ -434,8 +434,8 @@ namespace WinSW
void Uninstall(string? pathToConfig, bool noElevate)
{
ServiceDescriptor descriptor = ServiceDescriptor.Create(pathToConfig);
InitLoggers(descriptor, enableConsoleLogging: true);
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
InitLoggers(config, enableConsoleLogging: true);
if (!elevated)
{
@ -443,18 +443,18 @@ namespace WinSW
return;
}
Log.Info("Uninstalling the service with id '" + descriptor.Id + "'");
Log.Info("Uninstalling the service with id '" + config.Id + "'");
using ServiceManager scm = ServiceManager.Open();
try
{
using Service sc = scm.OpenService(descriptor.Id);
using Service sc = scm.OpenService(config.Id);
if (sc.Status == ServiceControllerStatus.Running)
{
// We could fail the opeartion here, but it would be an incompatible change.
// So it is just a warning
Log.Warn("The service with id '" + descriptor.Id + "' is running. It may be impossible to uninstall it");
Log.Warn("The service with id '" + config.Id + "' is running. It may be impossible to uninstall it");
}
sc.Delete();
@ -464,18 +464,18 @@ namespace WinSW
switch (inner.NativeErrorCode)
{
case Errors.ERROR_SERVICE_DOES_NOT_EXIST:
Log.Warn("The service with id '" + descriptor.Id + "' does not exist. Nothing to uninstall");
Log.Warn("The service with id '" + config.Id + "' does not exist. Nothing to uninstall");
break; // there's no such service, so consider it already uninstalled
case Errors.ERROR_SERVICE_MARKED_FOR_DELETE:
Log.Error("Failed to uninstall the service with id '" + descriptor.Id + "'"
Log.Error("Failed to uninstall the service with id '" + config.Id + "'"
+ ". It has been marked for deletion.");
// TODO: change the default behavior to Error?
break; // it's already uninstalled, so consider it a success
default:
Log.Fatal("Failed to uninstall the service with id '" + descriptor.Id + "'. Error code is '" + inner.NativeErrorCode + "'");
Log.Fatal("Failed to uninstall the service with id '" + config.Id + "'. Error code is '" + inner.NativeErrorCode + "'");
throw;
}
}
@ -483,8 +483,8 @@ namespace WinSW
void Start(string? pathToConfig, bool noElevate)
{
ServiceDescriptor descriptor = ServiceDescriptor.Create(pathToConfig);
InitLoggers(descriptor, enableConsoleLogging: true);
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
InitLoggers(config, enableConsoleLogging: true);
if (!elevated)
{
@ -492,9 +492,9 @@ namespace WinSW
return;
}
Log.Info("Starting the service with id '" + descriptor.Id + "'");
Log.Info("Starting the service with id '" + config.Id + "'");
using var svc = new ServiceController(descriptor.Id);
using var svc = new ServiceController(config.Id);
try
{
@ -508,14 +508,14 @@ namespace WinSW
catch (InvalidOperationException e)
when (e.InnerException is Win32Exception inner && inner.NativeErrorCode == Errors.ERROR_SERVICE_ALREADY_RUNNING)
{
Log.Info($"The service with ID '{descriptor.Id}' has already been started");
Log.Info($"The service with ID '{config.Id}' has already been started");
}
}
void Stop(string? pathToConfig, bool noElevate, bool noWait, bool force)
{
ServiceDescriptor descriptor = ServiceDescriptor.Create(pathToConfig);
InitLoggers(descriptor, enableConsoleLogging: true);
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
InitLoggers(config, enableConsoleLogging: true);
if (!elevated)
{
@ -523,9 +523,9 @@ namespace WinSW
return;
}
Log.Info("Stopping the service with id '" + descriptor.Id + "'");
Log.Info("Stopping the service with id '" + config.Id + "'");
using var svc = new ServiceController(descriptor.Id);
using var svc = new ServiceController(config.Id);
try
{
@ -567,8 +567,8 @@ namespace WinSW
void Restart(string? pathToConfig, bool noElevate, bool force)
{
ServiceDescriptor descriptor = ServiceDescriptor.Create(pathToConfig);
InitLoggers(descriptor, enableConsoleLogging: true);
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
InitLoggers(config, enableConsoleLogging: true);
if (!elevated)
{
@ -576,9 +576,9 @@ namespace WinSW
return;
}
Log.Info("Restarting the service with id '" + descriptor.Id + "'");
Log.Info("Restarting the service with id '" + config.Id + "'");
using var svc = new ServiceController(descriptor.Id);
using var svc = new ServiceController(config.Id);
List<ServiceController>? startedDependentServices = null;
@ -632,19 +632,19 @@ namespace WinSW
void RestartSelf(string? pathToConfig)
{
ServiceDescriptor descriptor = ServiceDescriptor.Create(pathToConfig);
InitLoggers(descriptor, enableConsoleLogging: true);
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
InitLoggers(config, enableConsoleLogging: true);
if (!elevated)
{
throw new CommandException(new Win32Exception(Errors.ERROR_ACCESS_DENIED));
}
Log.Info("Restarting the service with id '" + descriptor.Id + "'");
Log.Info("Restarting the service with id '" + config.Id + "'");
// run restart from another process group. see README.md for why this is useful.
if (!ProcessApis.CreateProcess(null, descriptor.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, ProcessApis.CREATE_NEW_PROCESS_GROUP, IntPtr.Zero, null, default, out _))
if (!ProcessApis.CreateProcess(null, config.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, false, ProcessApis.CREATE_NEW_PROCESS_GROUP, IntPtr.Zero, null, default, out _))
{
throw new CommandException("Failed to invoke restart: " + Marshal.GetLastWin32Error());
}
@ -652,11 +652,11 @@ namespace WinSW
static void Status(string? pathToConfig)
{
ServiceDescriptor descriptor = ServiceDescriptor.Create(pathToConfig);
InitLoggers(descriptor, enableConsoleLogging: true);
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
InitLoggers(config, enableConsoleLogging: true);
Log.Debug("User requested the status of the process with id '" + descriptor.Id + "'");
using var svc = new ServiceController(descriptor.Id);
Log.Debug("User requested the status of the process with id '" + config.Id + "'");
using var svc = new ServiceController(config.Id);
try
{
Console.WriteLine(svc.Status == ServiceControllerStatus.Running ? "Started" : "Stopped");
@ -670,8 +670,8 @@ namespace WinSW
void Test(string? pathToConfig, bool noElevate, int? timeout, bool noBreak)
{
ServiceDescriptor descriptor = ServiceDescriptor.Create(pathToConfig);
InitLoggers(descriptor, enableConsoleLogging: true);
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
InitLoggers(config, enableConsoleLogging: true);
if (!elevated)
{
@ -679,7 +679,7 @@ namespace WinSW
return;
}
using WrapperService wsvc = new WrapperService(descriptor);
using WrapperService wsvc = new WrapperService(config);
wsvc.RaiseOnStart(args);
try
{
@ -715,8 +715,8 @@ namespace WinSW
void Refresh(string? pathToConfig, bool noElevate)
{
ServiceDescriptor descriptor = ServiceDescriptor.Create(pathToConfig);
InitLoggers(descriptor, enableConsoleLogging: true);
XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig);
InitLoggers(config, enableConsoleLogging: true);
if (!elevated)
{
@ -727,30 +727,30 @@ namespace WinSW
using ServiceManager scm = ServiceManager.Open();
try
{
using Service sc = scm.OpenService(descriptor.Id);
using Service sc = scm.OpenService(config.Id);
sc.ChangeConfig(descriptor.Caption, descriptor.StartMode, descriptor.ServiceDependencies);
sc.ChangeConfig(config.Caption, config.StartMode, config.ServiceDependencies);
sc.SetDescription(descriptor.Description);
sc.SetDescription(config.Description);
SC_ACTION[] actions = descriptor.FailureActions;
SC_ACTION[] actions = config.FailureActions;
if (actions.Length > 0)
{
sc.SetFailureActions(descriptor.ResetFailureAfter, actions);
sc.SetFailureActions(config.ResetFailureAfter, actions);
}
bool isDelayedAutoStart = descriptor.StartMode == ServiceStartMode.Automatic && descriptor.DelayedAutoStart;
bool isDelayedAutoStart = config.StartMode == ServiceStartMode.Automatic && config.DelayedAutoStart;
if (isDelayedAutoStart)
{
sc.SetDelayedAutoStart(true);
}
if (descriptor.PreshutdownTimeout is TimeSpan preshutdownTimeout)
if (config.PreshutdownTimeout is TimeSpan preshutdownTimeout)
{
sc.SetPreshutdownTimeout(preshutdownTimeout);
}
string? securityDescriptor = descriptor.SecurityDescriptor;
string? securityDescriptor = config.SecurityDescriptor;
if (securityDescriptor != null)
{
// throws ArgumentException
@ -807,9 +807,9 @@ namespace WinSW
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowNoSuchService(Win32Exception inner) => throw new CommandException(inner);
private static void InitLoggers(ServiceDescriptor descriptor, bool enableConsoleLogging)
private static void InitLoggers(XmlServiceConfig config, bool enableConsoleLogging)
{
if (ServiceDescriptor.TestDescriptor != null)
if (XmlServiceConfig.TestConfig != null)
{
return;
}
@ -828,7 +828,7 @@ namespace WinSW
List<IAppender> appenders = new List<IAppender>();
// .wrapper.log
string wrapperLogPath = Path.Combine(descriptor.LogDirectory, descriptor.BaseName + ".wrapper.log");
string wrapperLogPath = Path.Combine(config.LogDirectory, config.BaseName + ".wrapper.log");
var wrapperLog = new FileAppender
{
AppendToFile = true,

View File

@ -17,7 +17,7 @@ namespace WinSW
public sealed class WrapperService : ServiceBase, IEventLogger, IServiceEventLog
{
private readonly Process process = new Process();
private readonly ServiceDescriptor descriptor;
private readonly XmlServiceConfig config;
private Dictionary<string, string>? envs;
internal WinSWExtensionManager ExtensionManager { get; }
@ -45,11 +45,11 @@ namespace WinSW
/// </remarks>
public static Version Version => Assembly.GetExecutingAssembly().GetName().Version!;
public WrapperService(ServiceDescriptor descriptor)
public WrapperService(XmlServiceConfig config)
{
this.descriptor = descriptor;
this.ServiceName = this.descriptor.Id;
this.ExtensionManager = new WinSWExtensionManager(this.descriptor);
this.config = config;
this.ServiceName = config.Id;
this.ExtensionManager = new WinSWExtensionManager(config);
this.CanShutdown = true;
this.CanStop = true;
this.CanPauseAndContinue = false;
@ -59,12 +59,12 @@ namespace WinSW
// Register the event log provider
eventLogProvider.Service = this;
if (descriptor.Preshutdown)
if (config.Preshutdown)
{
this.AcceptPreshutdown();
}
Environment.CurrentDirectory = descriptor.WorkingDirectory;
Environment.CurrentDirectory = config.WorkingDirectory;
}
/// <summary>
@ -73,7 +73,7 @@ namespace WinSW
/// </summary>
private void HandleFileCopies()
{
var file = this.descriptor.BasePath + ".copies";
var file = this.config.BasePath + ".copies";
if (!File.Exists(file))
{
return; // nothing to handle
@ -123,14 +123,14 @@ namespace WinSW
/// <returns>Log Handler, which should be used for the spawned process</returns>
private LogHandler CreateExecutableLogHandler()
{
string logDirectory = this.descriptor.LogDirectory;
string logDirectory = this.config.LogDirectory;
if (!Directory.Exists(logDirectory))
{
Directory.CreateDirectory(logDirectory);
}
LogHandler logAppender = this.descriptor.LogHandler;
LogHandler logAppender = this.config.LogHandler;
logAppender.EventLogger = this;
return logAppender;
}
@ -243,12 +243,12 @@ namespace WinSW
private void DoStart()
{
this.envs = this.descriptor.EnvironmentVariables;
this.envs = this.config.EnvironmentVariables;
this.HandleFileCopies();
// handle downloads
List<Download> downloads = this.descriptor.Downloads;
List<Download> downloads = this.config.Downloads;
Task[] tasks = new Task[downloads.Count];
for (int i = 0; i < downloads.Count; i++)
{
@ -287,10 +287,10 @@ namespace WinSW
try
{
string? prestartExecutable = this.descriptor.PrestartExecutable;
string? prestartExecutable = this.config.PrestartExecutable;
if (prestartExecutable != null)
{
using Process process = this.StartProcess(prestartExecutable, this.descriptor.PrestartArguments);
using Process process = this.StartProcess(prestartExecutable, this.config.PrestartArguments);
this.WaitForProcessToExit(process);
this.LogInfo($"Pre-start process '{process.Format()}' exited with code {process.ExitCode}.");
}
@ -300,24 +300,24 @@ namespace WinSW
Log.Error(e);
}
string startArguments = this.descriptor.StartArguments ?? this.descriptor.Arguments;
string startArguments = this.config.StartArguments ?? this.config.Arguments;
this.LogInfo("Starting " + this.descriptor.Executable);
this.LogInfo("Starting " + this.config.Executable);
// Load and start extensions
this.ExtensionManager.LoadExtensions();
this.ExtensionManager.FireOnWrapperStarted();
LogHandler executableLogHandler = this.CreateExecutableLogHandler();
this.StartProcess(this.process, startArguments, this.descriptor.Executable, executableLogHandler);
this.StartProcess(this.process, startArguments, this.config.Executable, executableLogHandler);
this.ExtensionManager.FireOnProcessStarted(this.process);
try
{
string? poststartExecutable = this.descriptor.PoststartExecutable;
string? poststartExecutable = this.config.PoststartExecutable;
if (poststartExecutable != null)
{
using Process process = this.StartProcess(poststartExecutable, this.descriptor.PoststartArguments, process =>
using Process process = this.StartProcess(poststartExecutable, this.config.PoststartArguments, process =>
{
this.LogInfo($"Post-start process '{process.Format()}' exited with code {process.ExitCode}.");
});
@ -336,10 +336,10 @@ namespace WinSW
{
try
{
string? prestopExecutable = this.descriptor.PrestopExecutable;
string? prestopExecutable = this.config.PrestopExecutable;
if (prestopExecutable != null)
{
using Process process = this.StartProcess(prestopExecutable, this.descriptor.PrestopArguments);
using Process process = this.StartProcess(prestopExecutable, this.config.PrestopArguments);
this.WaitForProcessToExit(process);
this.LogInfo($"Pre-stop process '{process.Format()}' exited with code {process.ExitCode}.");
}
@ -349,15 +349,15 @@ namespace WinSW
Log.Error(e);
}
string? stopArguments = this.descriptor.StopArguments;
this.LogInfo("Stopping " + this.descriptor.Id);
string? stopArguments = this.config.StopArguments;
this.LogInfo("Stopping " + this.config.Id);
this.orderlyShutdown = true;
this.process.EnableRaisingEvents = false;
if (stopArguments is null)
{
Log.Debug("ProcessKill " + this.process.Id);
ProcessHelper.StopProcessTree(this.process, this.descriptor.StopTimeout);
ProcessHelper.StopProcessTree(this.process, this.config.StopTimeout);
this.ExtensionManager.FireOnProcessTerminated(this.process);
}
else
@ -366,7 +366,7 @@ namespace WinSW
Process stopProcess = new Process();
string stopExecutable = this.descriptor.StopExecutable ?? this.descriptor.Executable;
string stopExecutable = this.config.StopExecutable ?? this.config.Executable;
// TODO: Redirect logging to Log4Net once https://github.com/kohsuke/winsw/pull/213 is integrated
this.StartProcess(stopProcess, stopArguments, stopExecutable, null);
@ -378,10 +378,10 @@ namespace WinSW
try
{
string? poststopExecutable = this.descriptor.PoststopExecutable;
string? poststopExecutable = this.config.PoststopExecutable;
if (poststopExecutable != null)
{
using Process process = this.StartProcess(poststopExecutable, this.descriptor.PoststopArguments);
using Process process = this.StartProcess(poststopExecutable, this.config.PoststopArguments);
this.WaitForProcessToExit(process);
this.LogInfo($"Post-stop process '{process.Format()}' exited with code {process.ExitCode}.");
}
@ -394,12 +394,12 @@ namespace WinSW
// Stop extensions
this.ExtensionManager.FireBeforeWrapperStopped();
if (this.shuttingdown && this.descriptor.BeepOnShutdown)
if (this.shuttingdown && this.config.BeepOnShutdown)
{
Console.Beep();
}
Log.Info("Finished " + this.descriptor.Id);
Log.Info("Finished " + this.config.Id);
}
private void WaitForProcessToExit(Process process)
@ -485,11 +485,11 @@ namespace WinSW
executable: executable,
arguments: arguments,
envVars: this.envs,
workingDirectory: this.descriptor.WorkingDirectory,
priority: this.descriptor.Priority,
workingDirectory: this.config.WorkingDirectory,
priority: this.config.Priority,
onExited: OnProcessCompleted,
logHandler: logHandler,
hideWindow: this.descriptor.HideWindow);
hideWindow: this.config.HideWindow);
}
private Process StartProcess(string executable, string? arguments, Action<Process>? onExited = null)
@ -497,7 +497,7 @@ namespace WinSW
var info = new ProcessStartInfo(executable, arguments)
{
UseShellExecute = false,
WorkingDirectory = this.descriptor.WorkingDirectory,
WorkingDirectory = this.config.WorkingDirectory,
};
Process process = Process.Start(info);