From b85593eb11422e7836605fd3943b1958ccb848df Mon Sep 17 00:00:00 2001 From: NextTurn <45985406+NextTurn@users.noreply.github.com> Date: Sat, 8 Aug 2020 00:00:00 +0800 Subject: [PATCH] Increase code coverage --- Directory.Build.props | 9 + WinSW.nuspec | 2 +- eng/build.yml | 12 +- src/.runsettings | 6 + src/WinSW.Core/Configuration/ServiceConfig.cs | 2 +- .../Configuration/XmlServiceConfig.cs | 4 +- src/WinSW.Tests/CommandLineTests.cs | 113 +++++++++++ src/WinSW.Tests/MainTest.cs | 83 -------- src/WinSW.Tests/MetadataTests.cs | 5 +- src/WinSW.Tests/Util/AssertEx.cs | 9 + src/WinSW.Tests/Util/CommandLineTestHelper.cs | 85 ++++++-- .../Util/InterProcessCodeCoverageSession.cs | 191 ++++++++++++++++++ src/WinSW.Tests/Util/Layout.cs | 9 + src/WinSW.Tests/WinSW.Tests.csproj | 24 ++- src/WinSW.Tests/xunit.runner.json | 4 + src/WinSW.sln | 1 + src/WinSW/Program.cs | 10 +- src/WinSW/WinSW.csproj | 20 +- 18 files changed, 466 insertions(+), 123 deletions(-) create mode 100644 src/.runsettings create mode 100644 src/WinSW.Tests/CommandLineTests.cs delete mode 100644 src/WinSW.Tests/MainTest.cs create mode 100644 src/WinSW.Tests/Util/AssertEx.cs create mode 100644 src/WinSW.Tests/Util/InterProcessCodeCoverageSession.cs create mode 100644 src/WinSW.Tests/xunit.runner.json diff --git a/Directory.Build.props b/Directory.Build.props index 402beb4..fe5b418 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,6 +4,15 @@ true full $(MSBuildThisFileDirectory)artifacts\ + $(ArtifactsDir)bin\ + $(ArtifactsDir)obj\ + $(ArtifactsDir)publish\ + $(MSBuildThisFileDirectory)src\.runsettings + + + + $(ArtifactsBinDir)$(MSBuildProjectName)\ + $(ArtifactsObjDir)$(MSBuildProjectName)\ diff --git a/WinSW.nuspec b/WinSW.nuspec index b781840..fad1fb1 100644 --- a/WinSW.nuspec +++ b/WinSW.nuspec @@ -25,7 +25,7 @@ More info about the wrapper is available in the projects GitHub repository. - + diff --git a/eng/build.yml b/eng/build.yml index 55a8e59..c2f181a 100644 --- a/eng/build.yml +++ b/eng/build.yml @@ -50,7 +50,7 @@ jobs: inputs: command: test projects: src\WinSW.Tests\WinSW.Tests.csproj - arguments: -c $(BuildConfiguration) --no-build + arguments: -c $(BuildConfiguration) --collect "XPlat Code Coverage" --no-build - task: NuGetToolInstaller@1 displayName: Install Nuget inputs: @@ -62,19 +62,19 @@ jobs: packagesToPack: WinSW.nuspec versioningScheme: byEnvVar versionEnvVar: BuildVersion - - publish: artifacts\WinSW.NET461.exe + - publish: artifacts\publish\WinSW.NET461.exe artifact: WinSW.NET461.exe_$(BuildConfiguration) displayName: Publish .NET 4.6.1 - - publish: artifacts\WinSW.NETCore.x64.zip + - publish: artifacts\publish\WinSW.NETCore.x64.zip artifact: WinSW.NETCore.x64.zip_$(BuildConfiguration) displayName: Publish .NET Core x64 .zip - - publish: artifacts\WinSW.NETCore.x86.zip + - publish: artifacts\publish\WinSW.NETCore.x86.zip artifact: WinSW.NETCore.x86.zip_$(BuildConfiguration) displayName: Publish .NET Core x86 .zip - - publish: artifacts\WinSW.NETCore.x64.exe + - publish: artifacts\publish\WinSW.NETCore.x64.exe artifact: WinSW.NETCore.x64.exe_$(BuildConfiguration) displayName: Publish .NET Core x64 .exe - - publish: artifacts\WinSW.NETCore.x86.exe + - publish: artifacts\publish\WinSW.NETCore.x86.exe artifact: WinSW.NETCore.x86.exe_$(BuildConfiguration) displayName: Publish .NET Core x86 .exe - publish: $(Build.ArtifactStagingDirectory)\WinSW.$(BuildVersion).nupkg diff --git a/src/.runsettings b/src/.runsettings new file mode 100644 index 0000000..cf4838b --- /dev/null +++ b/src/.runsettings @@ -0,0 +1,6 @@ + + + + x64 + + diff --git a/src/WinSW.Core/Configuration/ServiceConfig.cs b/src/WinSW.Core/Configuration/ServiceConfig.cs index f81c69a..1f2fffe 100644 --- a/src/WinSW.Core/Configuration/ServiceConfig.cs +++ b/src/WinSW.Core/Configuration/ServiceConfig.cs @@ -22,7 +22,7 @@ namespace WinSW.Configuration public abstract string Executable { get; } - public string ExecutablePath => Process.GetCurrentProcess().MainModule.FileName; + public virtual string ExecutablePath => Process.GetCurrentProcess().MainModule.FileName; public virtual bool HideWindow => false; diff --git a/src/WinSW.Core/Configuration/XmlServiceConfig.cs b/src/WinSW.Core/Configuration/XmlServiceConfig.cs index 87793df..0f45a58 100644 --- a/src/WinSW.Core/Configuration/XmlServiceConfig.cs +++ b/src/WinSW.Core/Configuration/XmlServiceConfig.cs @@ -30,14 +30,14 @@ namespace WinSW /// /// This string is "c:\abc\def\ghi" when the configuration XML is "c:\abc\def\ghi.xml" /// - public string BasePath { get; set; } + public virtual string BasePath { get; } /// /// The file name portion of the configuration file. /// /// In the above example, this would be "ghi". /// - public string BaseName { get; set; } + public virtual string BaseName { get; set; } /// public XmlServiceConfig(string path) diff --git a/src/WinSW.Tests/CommandLineTests.cs b/src/WinSW.Tests/CommandLineTests.cs new file mode 100644 index 0000000..befffd7 --- /dev/null +++ b/src/WinSW.Tests/CommandLineTests.cs @@ -0,0 +1,113 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.ServiceProcess; +using WinSW.Tests.Util; +using Xunit; +using Helper = WinSW.Tests.Util.CommandLineTestHelper; + +namespace WinSW.Tests +{ + public class CommandLineTests + { + [ElevatedFact] + public void Install_Start_Stop_Uninstall_Console_App() + { + using var config = Helper.TestXmlServiceConfig.FromXml(Helper.SeedXml); + + try + { + _ = Helper.Test(new[] { "install", config.FullPath }, config); + + using var controller = new ServiceController(Helper.Name); + Assert.Equal(Helper.DisplayName, controller.DisplayName); + Assert.False(controller.CanStop); + Assert.False(controller.CanShutdown); + Assert.False(controller.CanPauseAndContinue); + Assert.Equal(ServiceControllerStatus.Stopped, controller.Status); + Assert.Equal(ServiceType.Win32OwnProcess, controller.ServiceType); + +#if NETFRAMEWORK + InterProcessCodeCoverageSession session = null; + try + { + try + { + _ = Helper.Test(new[] { "start", config.FullPath }, config); + controller.Refresh(); + Assert.Equal(ServiceControllerStatus.Running, controller.Status); + Assert.True(controller.CanStop); + + if (Environment.GetEnvironmentVariable("System.DefinitionId") != null) + { + session = new InterProcessCodeCoverageSession(Helper.Name); + } + } + finally + { + _ = Helper.Test(new[] { "stop", config.FullPath }, config); + controller.Refresh(); + Assert.Equal(ServiceControllerStatus.Stopped, controller.Status); + } + } + finally + { + session?.Wait(); + } +#endif + } + finally + { + _ = Helper.Test(new[] { "uninstall", config.FullPath }, config); + } + } + + [Fact] + public void FailOnUnknownCommand() + { + const string commandName = "unknown"; + + CommandLineTestResult result = Helper.ErrorTest(new[] { commandName }); + + Assert.Equal($"Unrecognized command or argument '{commandName}'\r\n\r\n", result.Error); + } + + /// + /// https://github.com/kohsuke/winsw/issues/206 + /// + [Fact(Skip = "unknown")] + public void ShouldNotPrintLogsForStatusCommand() + { + string cliOut = Helper.Test(new[] { "status" }); + Assert.Equal("NonExistent" + Environment.NewLine, cliOut); + } + + [Fact] + public void Customize() + { + const string OldCompanyName = "CloudBees, Inc."; + const string NewCompanyName = "CLOUDBEES, INC."; + + string inputPath = Layout.WinSWExe; + + Assert.Equal(OldCompanyName, FileVersionInfo.GetVersionInfo(inputPath).CompanyName); + + // deny write access + using FileStream file = File.OpenRead(inputPath); + + string outputPath = Path.GetTempFileName(); + Program.TestExecutablePath = inputPath; + try + { + _ = Helper.Test(new[] { "customize", "-o", outputPath, "--manufacturer", NewCompanyName }); + + Assert.Equal(NewCompanyName, FileVersionInfo.GetVersionInfo(outputPath).CompanyName); + } + finally + { + Program.TestExecutablePath = null; + File.Delete(outputPath); + } + } + } +} diff --git a/src/WinSW.Tests/MainTest.cs b/src/WinSW.Tests/MainTest.cs deleted file mode 100644 index 8ea7e27..0000000 --- a/src/WinSW.Tests/MainTest.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.ServiceProcess; -using WinSW.Tests.Util; -using Xunit; - -namespace WinSW.Tests -{ - public class MainTest - { - [ElevatedFact] - public void TestInstall() - { - try - { - _ = CommandLineTestHelper.Test(new[] { "install" }); - - using ServiceController controller = new ServiceController(CommandLineTestHelper.Id); - Assert.Equal(CommandLineTestHelper.Name, controller.DisplayName); - Assert.False(controller.CanStop); - Assert.False(controller.CanShutdown); - Assert.False(controller.CanPauseAndContinue); - Assert.Equal(ServiceControllerStatus.Stopped, controller.Status); - Assert.Equal(ServiceType.Win32OwnProcess, controller.ServiceType); - } - finally - { - _ = CommandLineTestHelper.Test(new[] { "uninstall" }); - } - } - - [Fact] - public void FailOnUnknownCommand() - { - const string commandName = "unknown"; - - CommandLineTestResult result = CommandLineTestHelper.ErrorTest(new[] { commandName }); - - Assert.Equal($"Unrecognized command or argument '{commandName}'\r\n\r\n", result.Error); - } - - /// - /// https://github.com/kohsuke/winsw/issues/206 - /// - [Fact(Skip = "unknown")] - public void ShouldNotPrintLogsForStatusCommand() - { - string cliOut = CommandLineTestHelper.Test(new[] { "status" }); - Assert.Equal("NonExistent" + Environment.NewLine, cliOut); - } - -#if NET461 - [Fact] - public void Customize() - { - const string OldCompanyName = "CloudBees, Inc."; - const string NewCompanyName = "CLOUDBEES, INC."; - - string inputPath = Path.Combine(Layout.ArtifactsDirectory, "WinSW.NET461.exe"); - - Assert.Equal(OldCompanyName, FileVersionInfo.GetVersionInfo(inputPath).CompanyName); - - // deny write access - using FileStream file = File.OpenRead(inputPath); - - string outputPath = Path.GetTempFileName(); - Program.TestExecutablePath = inputPath; - try - { - _ = CommandLineTestHelper.Test(new[] { "customize", "-o", outputPath, "--manufacturer", NewCompanyName }); - - Assert.Equal(NewCompanyName, FileVersionInfo.GetVersionInfo(outputPath).CompanyName); - } - finally - { - Program.TestExecutablePath = null; - File.Delete(outputPath); - } - } -#endif - } -} diff --git a/src/WinSW.Tests/MetadataTests.cs b/src/WinSW.Tests/MetadataTests.cs index da65691..049bb0f 100644 --- a/src/WinSW.Tests/MetadataTests.cs +++ b/src/WinSW.Tests/MetadataTests.cs @@ -1,4 +1,4 @@ -#if NET461 +#if NETFRAMEWORK using System; using System.IO; using System.Reflection.Metadata; @@ -15,8 +15,7 @@ namespace WinSW.Tests { var version = new Version(4, 0, 0, 0); - using var file = File.OpenRead(Path.Combine(Layout.ArtifactsDirectory, "WinSW.NET461.exe")); - using var peReader = new PEReader(file); + using var peReader = new PEReader(File.OpenRead(Layout.NET461Exe)); var metadataReader = peReader.GetMetadataReader(); foreach (var handle in metadataReader.AssemblyReferences) { diff --git a/src/WinSW.Tests/Util/AssertEx.cs b/src/WinSW.Tests/Util/AssertEx.cs new file mode 100644 index 0000000..a23aea5 --- /dev/null +++ b/src/WinSW.Tests/Util/AssertEx.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace WinSW.Tests.Util +{ + internal static class AssertEx + { + internal static void Succeeded(int hr) => Assert.InRange(hr, 0, int.MaxValue); + } +} diff --git a/src/WinSW.Tests/Util/CommandLineTestHelper.cs b/src/WinSW.Tests/Util/CommandLineTestHelper.cs index f87b552..5354cf4 100644 --- a/src/WinSW.Tests/Util/CommandLineTestHelper.cs +++ b/src/WinSW.Tests/Util/CommandLineTestHelper.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Runtime.CompilerServices; +using System.Xml; using Xunit; namespace WinSW.Tests.Util @@ -9,22 +11,18 @@ namespace WinSW.Tests.Util /// public static class CommandLineTestHelper { - public const string Id = "WinSW.Tests"; - public const string Name = "WinSW Test Service"; + public const string Name = "WinSW.Tests"; + public const string DisplayName = "WinSW Test Service"; - private static readonly string SeedXml = + internal static readonly string SeedXml = $@" - {Id} - {Name} - The service. - node.exe - My Arguments - - C:\winsw\workdir - C:\winsw\logs + {Name} + {DisplayName} + cmd.exe + /c timeout /t -1 /nobreak "; - public static readonly XmlServiceConfig DefaultServiceConfig = XmlServiceConfig.FromXml(SeedXml); + private static readonly XmlServiceConfig DefaultServiceConfig = XmlServiceConfig.FromXml(SeedXml); /// /// Runs a simle test, which returns the output CLI @@ -97,6 +95,69 @@ $@" return new CommandLineTestResult(swOut.ToString(), swError.ToString(), exception); } + + internal sealed class TestXmlServiceConfig : XmlServiceConfig, IDisposable + { + private readonly string directory; + + private bool disposed; + + internal TestXmlServiceConfig(XmlDocument document, string name) + : base(document) + { + string directory = this.directory = Path.Combine(Path.GetTempPath(), name); + _ = Directory.CreateDirectory(directory); + + try + { + string path = this.FullPath = Path.Combine(directory, "config.xml"); + using (var file = File.CreateText(path)) + { + file.Write(SeedXml); + } + + this.BaseName = name; + this.BasePath = Path.Combine(directory, name); + } + catch + { + Directory.Delete(directory, true); + throw; + } + } + + ~TestXmlServiceConfig() => this.Dispose(false); + + public override string FullPath { get; } + + public override string BasePath { get; } + + public override string BaseName { get; set; } + + public override string ExecutablePath => Layout.WinSWExe; + + internal static TestXmlServiceConfig FromXml(string xml, [CallerMemberName] string name = null) + { + var document = new XmlDocument(); + document.LoadXml(xml); + return new TestXmlServiceConfig(document, name); + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool _) + { + if (!disposed) + { + Directory.Delete(this.directory, true); + disposed = true; + } + } + } } /// diff --git a/src/WinSW.Tests/Util/InterProcessCodeCoverageSession.cs b/src/WinSW.Tests/Util/InterProcessCodeCoverageSession.cs new file mode 100644 index 0000000..5177194 --- /dev/null +++ b/src/WinSW.Tests/Util/InterProcessCodeCoverageSession.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.Diagnostics.Runtime; +using Microsoft.Diagnostics.Runtime.Interop; +using WinSW.Native; +using Xunit; + +namespace WinSW.Tests.Util +{ + internal sealed class InterProcessCodeCoverageSession : IDebugEventCallbacks + { + private readonly Type trackerType; + private readonly FieldInfo hitsField; + + private readonly IDebugControl control; + private readonly DataTarget target; + private readonly Thread thread; + + private List exceptions; + private bool exited; + + internal InterProcessCodeCoverageSession(string serviceName) + { + var trackerType = this.trackerType = typeof(Program).Assembly.GetTypes().Single(type => type.Namespace == "Coverlet.Core.Instrumentation.Tracker"); + var hitsField = this.hitsField = trackerType.GetField("HitsArray", BindingFlags.Public | BindingFlags.Static); + Assert.NotNull(hitsField); + + using var scm = ServiceManager.Open(); + using var sc = scm.OpenService(serviceName, ServiceApis.ServiceAccess.QUERY_STATUS); + + int processId = sc.ProcessId; + Assert.True(processId >= 0); + + var guid = new Guid("27fe5639-8407-4f47-8364-ee118fb08ac8"); + int hr = Native.DebugCreate(guid, out object unknown); + AssertEx.Succeeded(hr); + + var client = (IDebugClient)unknown; + this.control = (IDebugControl)unknown; + + hr = client.AttachProcess(0, (uint)processId, DEBUG_ATTACH.DEFAULT); + AssertEx.Succeeded(hr); + + hr = client.SetEventCallbacks(this); + AssertEx.Succeeded(hr); + + IntPtr pointer = Marshal.GetIUnknownForObject(client); + Assert.Equal(1, Marshal.Release(pointer)); + + target = DataTarget.CreateFromDbgEng(pointer); + + var thread = this.thread = new Thread(() => + { + try + { + using (this.target) + { + do + { + int hr = this.control.WaitForEvent(DEBUG_WAIT.DEFAULT, 0xffffffff); + AssertEx.Succeeded(hr); + } + while (!this.exited); + } + } + catch (Exception e) + { + (this.exceptions ??= new List()).Add(e); + } + }); + thread.Start(); + } + + /// + internal void Wait() + { + this.thread.Join(); + + if (this.exceptions != null) + { + throw new AggregateException(this.exceptions); + } + } + + int IDebugEventCallbacks.GetInterestMask(out DEBUG_EVENT Mask) + { + Mask = DEBUG_EVENT.EXIT_PROCESS; + return 0; + } + + int IDebugEventCallbacks.Breakpoint(IDebugBreakpoint Bp) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.Exception(in EXCEPTION_RECORD64 Exception, uint FirstChance) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.CreateThread(ulong Handle, ulong DataOffset, ulong StartOffset) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.ExitThread(uint ExitCode) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.CreateProcess(ulong ImageFileHandle, ulong Handle, ulong BaseOffset, uint ModuleSize, string ModuleName, string ImageName, uint CheckSum, uint TimeDateStamp, ulong InitialThreadHandle, ulong ThreadDataOffset, ulong StartOffset) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.ExitProcess(uint ExitCode) + { + this.exited = true; + + try + { + using var runtime = this.target.ClrVersions.Single().CreateRuntime(); + + var module = runtime.EnumerateModules().First(module => module.Name == typeof(Program).Assembly.Location); + + var type = module.GetTypeByName(this.trackerType.FullName); + var field = type.GetStaticFieldByName(this.hitsField.Name); + var array = field.ReadObject(runtime.AppDomains.Single()).AsArray(); + + int[] hits = (int[])this.hitsField.GetValue(null); + + int[] values = array.ReadValues(0, hits.Length); + for (int i = 0; i < hits.Length; i++) + { + hits[i] += values[i]; + } + } + catch (Exception e) + { + (this.exceptions ??= new List()).Add(e); + } + + return (int)DEBUG_STATUS.BREAK; + } + + int IDebugEventCallbacks.LoadModule(ulong ImageFileHandle, ulong BaseOffset, uint ModuleSize, string ModuleName, string ImageName, uint CheckSum, uint TimeDateStamp) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.UnloadModule(string ImageBaseName, ulong BaseOffset) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.SystemError(uint Error, uint Level) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.SessionStatus(DEBUG_SESSION Status) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.ChangeDebuggeeState(DEBUG_CDS Flags, ulong Argument) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.ChangeEngineState(DEBUG_CES Flags, ulong Argument) + { + throw new NotImplementedException(); + } + + int IDebugEventCallbacks.ChangeSymbolState(DEBUG_CSS Flags, ulong Argument) + { + throw new NotImplementedException(); + } + + private static class Native + { + [DllImport("dbgeng.dll")] + internal static extern int DebugCreate(in Guid InterfaceId, [MarshalAs(UnmanagedType.IUnknown)] out object Interface); + } + } +} diff --git a/src/WinSW.Tests/Util/Layout.cs b/src/WinSW.Tests/Util/Layout.cs index 733f28a..8390ad6 100644 --- a/src/WinSW.Tests/Util/Layout.cs +++ b/src/WinSW.Tests/Util/Layout.cs @@ -35,5 +35,14 @@ namespace WinSW.Tests.Util } internal static string ArtifactsDirectory => artifactsDirectory ??= Path.Combine(RepositoryRoot, "artifacts"); + + internal static string NET461Exe => Path.Combine(ArtifactsDirectory, "publish", "WinSW.NET461.exe"); + + internal static string WinSWExe => +#if NETCOREAPP + Path.ChangeExtension(typeof(Program).Assembly.Location, ".exe"); +#else + typeof(Program).Assembly.Location; +#endif } } diff --git a/src/WinSW.Tests/WinSW.Tests.csproj b/src/WinSW.Tests/WinSW.Tests.csproj index 1abc76a..bc0969c 100644 --- a/src/WinSW.Tests/WinSW.Tests.csproj +++ b/src/WinSW.Tests/WinSW.Tests.csproj @@ -1,7 +1,7 @@  - net461;netcoreapp5.0 + net471;netcoreapp5.0 latest @@ -10,6 +10,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + @@ -29,4 +31,24 @@ + + + PreserveNewest + + + + + + + <_FilesToCopy Include="$(ArtifactsBinDir)WinSW\$(Configuration)\netcoreapp3.1\WinSW.runtimeconfig*.json" /> + + + + <_FilesToCopy Include="$(ArtifactsBinDir)WinSW\$(Configuration)\net461\System.ValueTuple.dll" /> + + + + + + diff --git a/src/WinSW.Tests/xunit.runner.json b/src/WinSW.Tests/xunit.runner.json new file mode 100644 index 0000000..8465a45 --- /dev/null +++ b/src/WinSW.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "shadowCopy": false +} diff --git a/src/WinSW.sln b/src/WinSW.sln index c22c576..a69a0a8 100644 --- a/src/WinSW.sln +++ b/src/WinSW.sln @@ -16,6 +16,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4975DCF4-C32C-43ED-A731-8FCC1F7E6746}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .runsettings = .runsettings EndProjectSection EndProject Global diff --git a/src/WinSW/Program.cs b/src/WinSW/Program.cs index f043174..b394fe1 100644 --- a/src/WinSW/Program.cs +++ b/src/WinSW/Program.cs @@ -991,16 +991,16 @@ namespace WinSW /// private static XmlServiceConfig CreateConfig(string? path) { - if (path != null) - { - return new XmlServiceConfig(path); - } - if (TestConfig != null) { return TestConfig; } + if (path != null) + { + return new XmlServiceConfig(path); + } + path = Path.ChangeExtension(ExecutablePath, ".xml"); if (!File.Exists(path)) { diff --git a/src/WinSW/WinSW.csproj b/src/WinSW/WinSW.csproj index f3e8189..28e16a6 100644 --- a/src/WinSW/WinSW.csproj +++ b/src/WinSW/WinSW.csproj @@ -38,20 +38,22 @@ - + + false + - - + + - - + + @@ -73,7 +75,7 @@ $(InputAssemblies) "$(OutDir)System.Numerics.Vectors.dll" $(InputAssemblies) "$(OutDir)System.Runtime.CompilerServices.Unsafe.dll" $(InputAssemblies) "$(OutDir)System.ValueTuple.dll" - "$(ArtifactsDir)WinSW.$(TargetFrameworkSuffix).exe" + "$(ArtifactsPublishDir)WinSW.$(TargetFrameworkSuffix).exe" @@ -82,14 +84,14 @@ "$(ILMerge)" $(ILMergeArgs) - + - + - +