diff --git a/MedallionShell.Tests/GeneralTest.cs b/MedallionShell.Tests/GeneralTest.cs index 1ec8ae9..125d7fa 100644 --- a/MedallionShell.Tests/GeneralTest.cs +++ b/MedallionShell.Tests/GeneralTest.cs @@ -13,6 +13,7 @@ namespace Medallion.Shell.Tests { using System.Collections; + using System.Text.RegularExpressions; using static UnitTestHelpers; public class GeneralTest @@ -145,45 +146,39 @@ public void TestZeroTimeout() [Test] public void TestCancellationAlreadyCanceled() { - using (var alreadyCanceled = new CancellationTokenSource(millisecondsDelay: 0)) - { - var command = TestShell.Run(SampleCommand, new object[] { "sleep", 1000000 }, o => o.CancellationToken(alreadyCanceled.Token)); - Assert.Throws(() => command.Wait()); - Assert.Throws(() => command.Result.ToString()); - command.Task.Status.ShouldEqual(TaskStatus.Canceled); - Assert.DoesNotThrow(() => command.ProcessId.ToString(), "still executes a command and gets a process ID"); - } + using var alreadyCanceled = new CancellationTokenSource(millisecondsDelay: 0); + var command = TestShell.Run(SampleCommand, new object[] { "sleep", 1000000 }, o => o.CancellationToken(alreadyCanceled.Token)); + Assert.Throws(() => command.Wait()); + Assert.Throws(() => command.Result.ToString()); + command.Task.Status.ShouldEqual(TaskStatus.Canceled); + Assert.DoesNotThrow(() => command.ProcessId.ToString(), "still executes a command and gets a process ID"); } [Test] public void TestCancellationNotCanceled() { - using (var notCanceled = new CancellationTokenSource()) - { - var command = TestShell.Run(SampleCommand, new object[] { "sleep", 1000000 }, o => o.CancellationToken(notCanceled.Token)); - command.Task.Wait(50).ShouldEqual(false); - command.Kill(); - command.Task.Wait(1000).ShouldEqual(true); - command.Result.Success.ShouldEqual(false); - } + using var notCanceled = new CancellationTokenSource(); + var command = TestShell.Run(SampleCommand, new object[] { "sleep", 1000000 }, o => o.CancellationToken(notCanceled.Token)); + command.Task.Wait(50).ShouldEqual(false); + command.Kill(); + command.Task.Wait(1000).ShouldEqual(true); + command.Result.Success.ShouldEqual(false); } [Test] public void TestCancellationCanceledPartway() { - using (var cancellationTokenSource = new CancellationTokenSource()) - { - var results = new SyncCollection(); - var command = TestShell.Run(SampleCommand, new object[] { "echo", "--per-char" }, o => o.CancellationToken(cancellationTokenSource.Token)) > results; - command.StandardInput.WriteLine("hello"); - var timeout = Task.Delay(TimeSpan.FromSeconds(10)); - while (results.Count == 0 && !timeout.IsCompleted) { } - results.Count.ShouldEqual(1); - cancellationTokenSource.Cancel(); - var aggregateException = Assert.Throws(() => command.Task.Wait(1000)); - Assert.IsInstanceOf(aggregateException.GetBaseException()); - CollectionAssert.AreEqual(results, new[] { "hello" }); - } + using var cancellationTokenSource = new CancellationTokenSource(); + var results = new SyncCollection(); + var command = TestShell.Run(SampleCommand, new object[] { "echo", "--per-char" }, o => o.CancellationToken(cancellationTokenSource.Token)) > results; + command.StandardInput.WriteLine("hello"); + var timeout = Task.Delay(TimeSpan.FromSeconds(10)); + while (results.Count == 0 && !timeout.IsCompleted) { } + results.Count.ShouldEqual(1); + cancellationTokenSource.Cancel(); + var aggregateException = Assert.Throws(() => command.Task.Wait(1000)); + Assert.IsInstanceOf(aggregateException.GetBaseException()); + CollectionAssert.AreEqual(results, new[] { "hello" }); } private class SyncCollection : ICollection @@ -207,16 +202,14 @@ private class SyncCollection : ICollection [Test] public void TestCancellationCanceledAfterCompletion() { - using (var cancellationTokenSource = new CancellationTokenSource()) - { - var results = new List(); - var command = TestShell.Run(SampleCommand, new object[] { "echo" }, o => o.CancellationToken(cancellationTokenSource.Token)) > results; - command.StandardInput.WriteLine("hello"); - command.StandardInput.Close(); - command.Task.Wait(1000).ShouldEqual(true); - cancellationTokenSource.Cancel(); - command.Result.Success.ShouldEqual(true); - } + using var cancellationTokenSource = new CancellationTokenSource(); + var results = new List(); + var command = TestShell.Run(SampleCommand, new object[] { "echo" }, o => o.CancellationToken(cancellationTokenSource.Token)) > results; + command.StandardInput.WriteLine("hello"); + command.StandardInput.Close(); + command.Task.Wait(1000).ShouldEqual(true); + cancellationTokenSource.Cancel(); + command.Result.Success.ShouldEqual(true); } [Test] @@ -357,7 +350,7 @@ public void TestVersioning() var version = typeof(Command).GetTypeInfo().Assembly.GetName().Version.ToString(); var informationalVersion = (AssemblyInformationalVersionAttribute)typeof(Command).GetTypeInfo().Assembly.GetCustomAttribute(typeof(AssemblyInformationalVersionAttribute)); Assert.IsNotNull(informationalVersion); - version.ShouldEqual(informationalVersion.InformationalVersion + ".0"); + version.ShouldEqual(Regex.Replace(informationalVersion.InformationalVersion, "-.*$", string.Empty) + ".0"); } [Test] @@ -495,12 +488,10 @@ void TestHelper(bool disposeOnExit) #if !NETCOREAPP2_2 // https://stackoverflow.com/questions/2633628/can-i-get-command-line-arguments-of-other-processes-from-net-c - string GetCommandLine(int processId) + static string GetCommandLine(int processId) { - using (var searcher = new System.Management.ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + processId)) - { - return searcher.Get().Cast().Single()["CommandLine"].ToString(); - } + using var searcher = new System.Management.ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + processId); + return searcher.Get().Cast().Single()["CommandLine"].ToString(); } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { diff --git a/MedallionShell.Tests/Streams/PipeTest.cs b/MedallionShell.Tests/Streams/PipeTest.cs index 9a6f512..8bcb67e 100644 --- a/MedallionShell.Tests/Streams/PipeTest.cs +++ b/MedallionShell.Tests/Streams/PipeTest.cs @@ -364,7 +364,7 @@ public static Task WriteTextAsync(this Pipe @this, string text) return new StreamWriter(@this.InputStream) { AutoFlush = true }.WriteAsync(text); } - public static async Task ReadTextAsync(this Pipe @this, int count, CancellationToken token = default(CancellationToken)) + public static async Task ReadTextAsync(this Pipe @this, int count, CancellationToken token = default) { var bytes = new byte[count]; var bytesRead = 0; diff --git a/MedallionShell/MedallionShell.csproj b/MedallionShell/MedallionShell.csproj index da01294..e16fbcf 100644 --- a/MedallionShell/MedallionShell.csproj +++ b/MedallionShell/MedallionShell.csproj @@ -1,14 +1,14 @@  - netstandard1.3;netstandard2.0;net45;net46 + netstandard1.3;netstandard2.0;net45;net46;net471 Medallion.Shell - 1.6.1 - 1.6.1.0 - 1.6.1.0 + 1.6.2 + 1.6.2.0 + 1.6.2.0 Michael Adelson A lightweight, cross-platform library that simplifies working with processes in .NET Copyright © 2017 Michael Adelson @@ -42,7 +42,7 @@ - + diff --git a/MedallionShell/PlatformCompatibilityHelper.cs b/MedallionShell/PlatformCompatibilityHelper.cs index 7d95e7c..2263614 100644 --- a/MedallionShell/PlatformCompatibilityHelper.cs +++ b/MedallionShell/PlatformCompatibilityHelper.cs @@ -13,30 +13,7 @@ internal static class PlatformCompatibilityHelper // see http://www.mono-project.com/docs/faq/technical/ public static readonly bool IsMono = Type.GetType("Mono.Runtime") != null; - public static bool IsWindows - { - get - { -#if !NET45 - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); -#else - if (!IsMono) { return true; } -#pragma warning disable DE0007, DE0009 - switch (Environment.OSVersion.Platform) - { - case PlatformID.Win32S: - case PlatformID.Win32Windows: - case PlatformID.Win32NT: - case PlatformID.WinCE: - case PlatformID.Xbox: - return true; - default: - return false; - } -#pragma warning restore DE0007, DE0009 -#endif - } - } + public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); public static CommandLineSyntax GetDefaultCommandLineSyntax() { diff --git a/MedallionShell/Signals/WindowsProcessSignaler.cs b/MedallionShell/Signals/WindowsProcessSignaler.cs index 7507d08..8463856 100644 --- a/MedallionShell/Signals/WindowsProcessSignaler.cs +++ b/MedallionShell/Signals/WindowsProcessSignaler.cs @@ -69,43 +69,41 @@ private static async Task SendSignalFromCurrentProcess(int processId, Nati await SignalFromCurrentProcessLock.WaitAsync().ConfigureAwait(false); try { - using (var waitForSignalSemaphore = new SemaphoreSlim(initialCount: 0, maxCount: 1)) + using var waitForSignalSemaphore = new SemaphoreSlim(initialCount: 0, maxCount: 1); + NativeMethods.ConsoleCtrlDelegate handler = receivedSignal => { - NativeMethods.ConsoleCtrlDelegate handler = receivedSignal => + if (receivedSignal == signal) { - if (receivedSignal == signal) - { - waitForSignalSemaphore.Release(); - // if we're signaling another process on the same console, we return true - // to prevent the signal from bubbling. If we're signaling ourselves, we - // allow it to bubble since presumably that's what the caller wanted - return processId != ProcessHelper.CurrentProcessId; - } - return false; - }; - if (!NativeMethods.SetConsoleCtrlHandler(handler, add: true)) - { - Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + waitForSignalSemaphore.Release(); + // if we're signaling another process on the same console, we return true + // to prevent the signal from bubbling. If we're signaling ourselves, we + // allow it to bubble since presumably that's what the caller wanted + return processId != ProcessHelper.CurrentProcessId; } - try + return false; + }; + if (!NativeMethods.SetConsoleCtrlHandler(handler, add: true)) + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + try + { + if (!NativeMethods.GenerateConsoleCtrlEvent(signal, NativeMethods.AllProcessesWithCurrentConsoleGroup)) { - if (!NativeMethods.GenerateConsoleCtrlEvent(signal, NativeMethods.AllProcessesWithCurrentConsoleGroup)) - { - return false; - } - - // Wait until the signal has reached our handler and been handled to know that it is safe to - // remove the handler. - // Timeout here just to ensure we don't hang forever if something weird happens (e. g. someone - // else registers a handler concurrently with us). - return await waitForSignalSemaphore.WaitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false); + return false; } - finally + + // Wait until the signal has reached our handler and been handled to know that it is safe to + // remove the handler. + // Timeout here just to ensure we don't hang forever if something weird happens (e. g. someone + // else registers a handler concurrently with us). + return await waitForSignalSemaphore.WaitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false); + } + finally + { + if (!NativeMethods.SetConsoleCtrlHandler(handler, add: false)) { - if (!NativeMethods.SetConsoleCtrlHandler(handler, add: false)) - { - Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); - } + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } } } diff --git a/MedallionShell/Streams/ProcessStreamWriter.cs b/MedallionShell/Streams/ProcessStreamWriter.cs index 1908d77..803bc54 100644 --- a/MedallionShell/Streams/ProcessStreamWriter.cs +++ b/MedallionShell/Streams/ProcessStreamWriter.cs @@ -124,23 +124,21 @@ public Task PipeFromAsync(IEnumerable chars, bool leaveWriterOpen = false) : () => Task.Run(async () => { var buffer = new char[Constants.CharBufferSize]; - using (var enumerator = chars.GetEnumerator()) + using var enumerator = chars.GetEnumerator(); + while (true) { - while (true) + var i = 0; + while (i < buffer.Length && enumerator.MoveNext()) { - var i = 0; - while (i < buffer.Length && enumerator.MoveNext()) - { - buffer[i++] = enumerator.Current; - } - if (i > 0) - { - await this.WriteAsync(buffer, 0, count: i).ConfigureAwait(false); - } - else - { - break; - } + buffer[i++] = enumerator.Current; + } + if (i > 0) + { + await this.WriteAsync(buffer, 0, count: i).ConfigureAwait(false); + } + else + { + break; } } }),