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)
-
+
-
+
-
+