From 94668c9f779f1ffd5ce05d74e2ae8e4f35d65d44 Mon Sep 17 00:00:00 2001 From: NextTurn <45985406+NextTurn@users.noreply.github.com> Date: Mon, 9 Dec 2019 00:00:00 +0800 Subject: [PATCH] Increase code coverage --- src/Core/ServiceWrapper/Main.cs | 2 +- .../ServiceWrapper/Properties/AssemblyInfo.cs | 3 + .../winswTests/Configuration/ExamplesTest.cs | 19 +- ...DownloadTest.cs => DownloadConfigTests.cs} | 56 +---- src/Test/winswTests/DownloadTests.cs | 231 ++++++++++++++++++ src/Test/winswTests/MainTest.cs | 26 +- src/Test/winswTests/Util/AsyncAssert.cs | 29 +++ .../winswTests/Util/DateTimeExtensions.cs | 12 + src/Test/winswTests/Util/TestHelper.cs | 16 ++ 9 files changed, 332 insertions(+), 62 deletions(-) create mode 100644 src/Core/ServiceWrapper/Properties/AssemblyInfo.cs rename src/Test/winswTests/{DownloadTest.cs => DownloadConfigTests.cs} (84%) create mode 100644 src/Test/winswTests/DownloadTests.cs create mode 100644 src/Test/winswTests/Util/AsyncAssert.cs create mode 100644 src/Test/winswTests/Util/DateTimeExtensions.cs create mode 100644 src/Test/winswTests/Util/TestHelper.cs diff --git a/src/Core/ServiceWrapper/Main.cs b/src/Core/ServiceWrapper/Main.cs index e58fa5b..0336a4b 100644 --- a/src/Core/ServiceWrapper/Main.cs +++ b/src/Core/ServiceWrapper/Main.cs @@ -1044,7 +1044,7 @@ namespace winsw appenders.ToArray()); } - private static unsafe bool IsProcessElevated() + internal static unsafe bool IsProcessElevated() { IntPtr process = Kernel32.GetCurrentProcess(); if (!Advapi32.OpenProcessToken(process, TokenAccessLevels.Read, out IntPtr token)) diff --git a/src/Core/ServiceWrapper/Properties/AssemblyInfo.cs b/src/Core/ServiceWrapper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f05656e --- /dev/null +++ b/src/Core/ServiceWrapper/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("winswTests")] diff --git a/src/Test/winswTests/Configuration/ExamplesTest.cs b/src/Test/winswTests/Configuration/ExamplesTest.cs index e21ac63..8aa6dbc 100644 --- a/src/Test/winswTests/Configuration/ExamplesTest.cs +++ b/src/Test/winswTests/Configuration/ExamplesTest.cs @@ -12,7 +12,7 @@ namespace winswTests.Configuration /// The test uses a relative path to example files, which is based on the current project structure. /// [TestFixture] - class ExamplesTest + public class ExamplesTest { [Test] public void AllOptionsConfigShouldDeclareDefaults() @@ -40,10 +40,21 @@ namespace winswTests.Configuration ServiceDescriptorAssert.AssertAllOptionalPropertiesAreDefault(desc); } - private ServiceDescriptor Load(string exampleName) + private static ServiceDescriptor Load(string exampleName) { - var directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - string path = Path.GetFullPath($@"{directory}\..\..\..\..\..\..\examples\sample-{exampleName}.xml"); + string directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + while (true) + { + if (File.Exists(Path.Combine(directory, ".gitignore"))) + { + break; + } + + directory = Path.GetDirectoryName(directory); + Assert.That(directory, Is.Not.Null); + } + + string path = Path.Combine(directory, $@"examples\sample-{exampleName}.xml"); Assert.That(path, Does.Exist); XmlDocument dom = new XmlDocument(); diff --git a/src/Test/winswTests/DownloadTest.cs b/src/Test/winswTests/DownloadConfigTests.cs similarity index 84% rename from src/Test/winswTests/DownloadTest.cs rename to src/Test/winswTests/DownloadConfigTests.cs index 3dceba0..54637bd 100644 --- a/src/Test/winswTests/DownloadTest.cs +++ b/src/Test/winswTests/DownloadConfigTests.cs @@ -1,8 +1,4 @@ using System.IO; -using System.Net; -#if VNEXT -using System.Threading.Tasks; -#endif using NUnit.Framework; using winsw; using winswTests.Util; @@ -10,61 +6,11 @@ using winswTests.Util; namespace winswTests { [TestFixture] - class DownloadTest + public class DownloadConfigTests { private const string From = "https://www.nosuchhostexists.foo.myorg/foo.xml"; private const string To = "%BASE%\\foo.xml"; - [Test] -#if VNEXT - public async Task DownloadFileAsync() -#else - public void DownloadFile() -#endif - { - string from = Path.GetTempFileName(); - string to = Path.GetTempFileName(); - - try - { - const string contents = "WinSW"; - File.WriteAllText(from, contents); -#if VNEXT - await new Download(from, to).PerformAsync(); -#else - new Download(from, to).Perform(); -#endif - Assert.That(File.ReadAllText(to), Is.EqualTo(contents)); - } - finally - { - File.Delete(from); - File.Delete(to); - } - } - - [Test] - public void DownloadFile_NonExistent() - { - string from = Path.GetTempPath() + Path.GetRandomFileName(); - string to = Path.GetTempFileName(); - - try - { - Assert.That( -#if VNEXT - async () => await new Download(from, to).PerformAsync(), -#else - () => new Download(from, to).Perform(), -#endif - Throws.TypeOf()); - } - finally - { - File.Delete(to); - } - } - [Test] public void Roundtrip_Defaults() { diff --git a/src/Test/winswTests/DownloadTests.cs b/src/Test/winswTests/DownloadTests.cs new file mode 100644 index 0000000..d5bf118 --- /dev/null +++ b/src/Test/winswTests/DownloadTests.cs @@ -0,0 +1,231 @@ +#if VNEXT +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using NUnit.Framework; +using winsw; +using winswTests.Util; + +namespace winswTests +{ + [TestFixture] + public class DownloadTests + { + private readonly HttpListener globalListener = new HttpListener(); + + private readonly byte[] contents = { 0x57, 0x69, 0x6e, 0x53, 0x57 }; + + private string globalPrefix; + + [OneTimeSetUp] + public void SetUp() + { + TcpListener tcpListener = new TcpListener(IPAddress.Loopback, 0); + tcpListener.Start(); + int port = ((IPEndPoint)tcpListener.LocalEndpoint).Port; + string prefix = $"http://localhost:{port}/"; + this.globalListener.Prefixes.Add(prefix); + this.globalPrefix = prefix; + { + tcpListener.Stop(); + this.globalListener.Start(); + } + } + + [OneTimeTearDown] + public void TearDown() + { + this.globalListener.Stop(); + this.globalListener.Close(); + } + + private async Task TestClientServerAsync(Func client, Action server, AuthenticationSchemes authenticationSchemes = AuthenticationSchemes.Anonymous, [CallerMemberName] string path = null) + { + HttpListener listener = new HttpListener(); + string prefix = $"{this.globalPrefix}{path}/"; + listener.Prefixes.Add(prefix); + listener.AuthenticationSchemes = authenticationSchemes; + listener.Start(); + + Task serverTask = null; + try + { + serverTask = ListenAsync(); + + string dest = Path.GetTempFileName(); + try + { + await client(prefix, dest); + } + finally + { + File.Delete(dest); + } + } + finally + { + listener.Stop(); + listener.Close(); + if (serverTask != null) + { + await serverTask; + } + } + + async Task ListenAsync() + { + HttpListenerContext context = await listener.GetContextAsync(); + try + { + server(context); + } + catch + { + context.Response.Abort(); + } + } + } + + [Test] + public async Task TestHttpAsync() + { + await this.TestClientServerAsync( + async (source, dest) => + { + await new Download(source, dest).PerformAsync(); + Assert.That(File.ReadAllBytes(dest), Is.EqualTo(this.contents)); + }, + context => + { + context.Response.OutputStream.Write(this.contents, 0, this.contents.Length); + context.Response.Close(); + }); + } + + [Test] + public async Task TestHttp_NoAuthAsync() + { + await this.TestClientServerAsync( + async (source, dest) => + { + await new Download(source, dest, false, Download.AuthType.none).PerformAsync(); + Assert.That(File.ReadAllBytes(dest), Is.EqualTo(this.contents)); + }, + context => + { + if (((WebHeaderCollection)context.Request.Headers)[HttpRequestHeader.Authorization] != null) + { + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + context.Response.Close(); + } + + context.Response.OutputStream.Write(this.contents, 0, this.contents.Length); + context.Response.Close(); + }); + } + + [Test] + public async Task TestHttp_BasicAuthAsync() + { + const string username = nameof(username); + const string password = nameof(password); + + await this.TestClientServerAsync( + async (source, dest) => + { + await new Download(source, dest, false, Download.AuthType.basic, username, password, true).PerformAsync(); + Assert.That(File.ReadAllBytes(dest), Is.EqualTo(this.contents)); + }, + context => + { + HttpListenerBasicIdentity identity = (HttpListenerBasicIdentity)context.User.Identity; + if (identity.Name != username || identity.Password != password) + { + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + context.Response.Close(); + } + + context.Response.OutputStream.Write(this.contents, 0, this.contents.Length); + context.Response.Close(); + }, + AuthenticationSchemes.Basic); + } + + [Test] + public async Task TestHttp_IfModifiedSince_ModifiedAsync() + { + DateTime lastModified = DateTime.Now.TrimToSeconds(); + DateTime prevModified = lastModified.AddDays(-1); + + await this.TestClientServerAsync( + async (source, dest) => + { + File.WriteAllBytes(dest, this.contents); + File.SetLastWriteTime(dest, prevModified); + await new Download(source, dest).PerformAsync(); + Assert.That(File.GetLastWriteTime(dest), Is.EqualTo(lastModified)); + Assert.That(File.ReadAllBytes(dest), Is.Not.EqualTo(this.contents)); + }, + context => + { + string ifModifiedSince = ((WebHeaderCollection)context.Request.Headers)[HttpRequestHeader.IfModifiedSince]; + if (ifModifiedSince != null && DateTime.Parse(ifModifiedSince) >= lastModified) + { + context.Response.StatusCode = (int)HttpStatusCode.NotModified; + } + + context.Response.Headers[HttpResponseHeader.LastModified] = lastModified.ToUniversalTime().ToString("r"); + context.Response.Close(); + }); + } + + [Test] + public async Task TestHttp_IfModifiedSince_NotModifiedAsync() + { + DateTime lastModified = DateTime.Now.TrimToSeconds(); + + await this.TestClientServerAsync( + async (source, dest) => + { + File.WriteAllBytes(dest, this.contents); + File.SetLastWriteTime(dest, lastModified); + await new Download(source, dest).PerformAsync(); + Assert.That(File.GetLastWriteTime(dest), Is.EqualTo(lastModified)); + Assert.That(File.ReadAllBytes(dest), Is.EqualTo(this.contents)); + }, + context => + { + string ifModifiedSince = ((WebHeaderCollection)context.Request.Headers)[HttpRequestHeader.IfModifiedSince]; + if (ifModifiedSince != null && DateTime.Parse(ifModifiedSince) >= lastModified) + { + context.Response.StatusCode = (int)HttpStatusCode.NotModified; + } + + context.Response.Headers[HttpResponseHeader.LastModified] = lastModified.ToUniversalTime().ToString("r"); + context.Response.Close(); + }); + } + + [Test] + public async Task TestHttp_NotFound_ThrowsAsync() + { + await this.TestClientServerAsync( + async (source, dest) => + { + WebException exception = await AsyncAssert.ThrowsAsync( + async () => await new Download(source, dest).PerformAsync()); + + Assert.That(exception.Status, Is.EqualTo(WebExceptionStatus.ProtocolError)); + }, + context => + { + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + context.Response.Close(); + }); + } + } +} +#endif diff --git a/src/Test/winswTests/MainTest.cs b/src/Test/winswTests/MainTest.cs index 057bb58..e434e3b 100644 --- a/src/Test/winswTests/MainTest.cs +++ b/src/Test/winswTests/MainTest.cs @@ -1,5 +1,4 @@ using System; -using System.Security.Principal; using System.ServiceProcess; using NUnit.Framework; using winsw; @@ -8,8 +7,31 @@ using winswTests.Util; namespace winswTests { [TestFixture] - class MainTest + public class MainTest { + [Test] + public void TestInstall() + { + TestHelper.RequireProcessElevated(); + + try + { + _ = CLITestHelper.CLITest(new[] { "install" }); + + using ServiceController controller = new ServiceController(CLITestHelper.Id); + Assert.That(controller.DisplayName, Is.EqualTo(CLITestHelper.Name)); + Assert.That(controller.CanStop, Is.False); + Assert.That(controller.CanShutdown, Is.False); + Assert.That(controller.CanPauseAndContinue, Is.False); + Assert.That(controller.Status, Is.EqualTo(ServiceControllerStatus.Stopped)); + Assert.That(controller.ServiceType, Is.EqualTo(ServiceType.Win32OwnProcess)); + } + finally + { + _ = CLITestHelper.CLITest(new[] { "uninstall" }); + } + } + [Test] public void PrintVersion() { diff --git a/src/Test/winswTests/Util/AsyncAssert.cs b/src/Test/winswTests/Util/AsyncAssert.cs new file mode 100644 index 0000000..0c2ffa9 --- /dev/null +++ b/src/Test/winswTests/Util/AsyncAssert.cs @@ -0,0 +1,29 @@ +#if VNEXT +using System; +using System.Threading.Tasks; +using NUnit.Framework; +using NUnit.Framework.Constraints; + +namespace winswTests.Util +{ + internal static class AsyncAssert + { + internal static async Task ThrowsAsync(AsyncTestDelegate code) + where TActual : Exception + { + Exception caught = null; + try + { + await code(); + } + catch (Exception e) + { + caught = e; + } + + Assert.That(caught, new ExceptionTypeConstraint(typeof(TActual))); + return (TActual)caught; + } + } +} +#endif diff --git a/src/Test/winswTests/Util/DateTimeExtensions.cs b/src/Test/winswTests/Util/DateTimeExtensions.cs new file mode 100644 index 0000000..07c18ed --- /dev/null +++ b/src/Test/winswTests/Util/DateTimeExtensions.cs @@ -0,0 +1,12 @@ +#if VNEXT +using System; + +namespace winswTests.Util +{ + internal static class DateTimeExtensions + { + internal static DateTime TrimToSeconds(this DateTime dateTime) => + dateTime.AddTicks(-(dateTime.Ticks % TimeSpan.TicksPerSecond)); + } +} +#endif diff --git a/src/Test/winswTests/Util/TestHelper.cs b/src/Test/winswTests/Util/TestHelper.cs new file mode 100644 index 0000000..0d1f7e4 --- /dev/null +++ b/src/Test/winswTests/Util/TestHelper.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using winsw; + +namespace winswTests.Util +{ + internal static class TestHelper + { + internal static void RequireProcessElevated() + { + if (!WrapperService.IsProcessElevated()) + { + Assert.Ignore(); + } + } + } +}