diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index a7c2739..7b72446 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -5,7 +5,7 @@ on: branches: [ main ] pull_request: branches: [ main ] - + env: packageVersionPrefix: ${{ '0.0.' }} packageVersionSuffixForFeatureBranch: ${{ '-alpha' }} @@ -13,12 +13,15 @@ env: jobs: build-and-test: - runs-on: ubuntu-latest + strategy: + matrix: + # os: [ macos-latest, ubuntu-latest, windows-latest ] + os: [ windows-latest ] + runs-on: ${{ matrix.os }} name: Build and test - steps: - uses: actions/checkout@v2 - + - name: Setup environment variables (main branch) if: github.ref == 'refs/heads/main' run: echo "packageVersion=${{ env.packageVersionPrefix }}${{ github.run_number }}${{ env.packageVersionSuffixForMainBranch }}" >> $GITHUB_ENV @@ -38,13 +41,17 @@ jobs: dotnet tool install --global dotnet-reportgenerator-globaltool - name: Install dependencies - run: dotnet restore + run: dotnet restore RemoteControlledProcess.sln - name: Build - run: dotnet build --configuration Debug --no-restore + run: dotnet build --configuration Debug --no-restore RemoteControlledProcess.sln - - name: Test with coverage - run: dotnet test --no-restore --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput='./TestResults/coverage.cobertura.xml' + - name: Run tests sequentially with coverage + # By default tests run in parallel. + # Coverlet instrumentation requires running tests sequentially. + # Thus, using a separate project and the /p:BuildInParallel switch to serialize the test runs + # See also: https://github.com/nunit/nunit3-vs-adapter/issues/657 + run: dotnet test --no-restore --verbosity normal /p:BuildInParallel=false /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput='./TestResults/coverage.cobertura.xml' RemoteControlledProcess.Tests.proj - name: Build NuGet package run: | @@ -54,7 +61,7 @@ jobs: - name: Test NuGet package run: ./smoketest.sh working-directory: RemoteControlledProcess.Nupkg.Tests - + - name: Generate coverage reports run: reportgenerator "-reports:RemoteControlledProcess.Acceptance.Tests/TestResults/*.xml;RemoteControlledProcess.Unit.Tests/TestResults/*.xml" \ "-targetdir:report" \ @@ -78,9 +85,9 @@ jobs: publish-reports: runs-on: ubuntu-latest name: Publish coverage reports - + needs: build-and-test - + steps: # the repository is required by codeclimate-action - uses: actions/checkout@v2 @@ -90,7 +97,7 @@ jobs: with: name: coverage-reports path: coverage-reports - + - name: Publish coverage report to coveralls.io uses: coverallsapp/github-action@master with: @@ -107,7 +114,7 @@ jobs: publish-nuget: runs-on: ubuntu-latest name: Publish NuGet package - + needs: publish-reports steps: @@ -116,7 +123,7 @@ jobs: with: name: nuget-package path: nuget-package - + - name: Identify package version run: cat nuget-package/VERSION.txt >> $GITHUB_ENV diff --git a/RemoteControlledProcess.Acceptance.Tests/Features/SmokeTests.cs b/RemoteControlledProcess.Acceptance.Tests/Features/SmokeTests.cs index 28ed0dc..9d08668 100644 --- a/RemoteControlledProcess.Acceptance.Tests/Features/SmokeTests.cs +++ b/RemoteControlledProcess.Acceptance.Tests/Features/SmokeTests.cs @@ -1,9 +1,14 @@ using Xunit; +using Xunit.Abstractions; namespace RemoteControlledProcess.Acceptance.Tests.Features { public class SmokeTests { + private readonly ITestOutputHelper _testOutputHelper; + + public SmokeTests(ITestOutputHelper testOutputHelper) => _testOutputHelper = testOutputHelper; + /// /// SmokeTest used to verify that the NuGet package has been created correctly. /// @@ -18,8 +23,10 @@ public void SmokeTest() processWrapper.Start(); processWrapper.ShutdownGracefully(); processWrapper.ForceTermination(); + var output = processWrapper.ReadOutput(); - Assert.Contains("STOP", output); + _testOutputHelper.WriteLine($"Process produced the following output: \"{output}\""); + Assert.Contains("Process ID", output); } } } \ No newline at end of file diff --git a/RemoteControlledProcess.ConsumerDriven.Tests/GlobalSuppressions.cs b/RemoteControlledProcess.ConsumerDriven.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000..5883ae2 --- /dev/null +++ b/RemoteControlledProcess.ConsumerDriven.Tests/GlobalSuppressions.cs @@ -0,0 +1,6 @@ +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage( + "Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", + Justification = "Unit test naming follows https://osherove.com/blog/2005/4/3/naming-standards-for-unit-tests.html", + Scope = "namespaceanddescendants", Target = "RemoteControlledProcess.ConsumerDriven.Tests")] \ No newline at end of file diff --git a/RemoteControlledProcess.ConsumerDriven.Tests/KillTests.cs b/RemoteControlledProcess.ConsumerDriven.Tests/KillTests.cs new file mode 100644 index 0000000..fba8a7e --- /dev/null +++ b/RemoteControlledProcess.ConsumerDriven.Tests/KillTests.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using Xunit; +using Xunit.Abstractions; + +namespace RemoteControlledProcess.ConsumerDriven.Tests +{ + public class KillTests + { + private readonly ITestOutputHelper _testOutputHelper; + + public KillTests(ITestOutputHelper testOutputHelper) => _testOutputHelper = testOutputHelper; + + [Fact] + public void RunKillProcess_NotOnWindows_ShowsProcessOutput() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _testOutputHelper.WriteLine($"Windows OS detected. Skipping test RunKillProcess_NotOnWindows_ShowsProcessOutput."); + return; + } + + var output = ProcessRunner.RunProcess("kill", "-l", _testOutputHelper); + + Assert.Contains("term", output); + } + } +} diff --git a/RemoteControlledProcess.ConsumerDriven.Tests/ProcessRunner.cs b/RemoteControlledProcess.ConsumerDriven.Tests/ProcessRunner.cs new file mode 100644 index 0000000..513aba3 --- /dev/null +++ b/RemoteControlledProcess.ConsumerDriven.Tests/ProcessRunner.cs @@ -0,0 +1,29 @@ +using System.Diagnostics; +using Xunit.Abstractions; + +namespace RemoteControlledProcess.ConsumerDriven.Tests +{ + public static class ProcessRunner + { + public static string RunProcess(string processName, string arguments, ITestOutputHelper testOutputHelper) + { + var processStartInfo = new ProcessStartInfo(processName) + { + UseShellExecute = false, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + Arguments = arguments + }; + + var process = new Process { StartInfo = processStartInfo }; + process.Start(); + process.WaitForExit(30000); + + var output = process.StandardOutput.ReadToEnd(); + testOutputHelper.WriteLine($"Process produced the following output: \"{output}\""); + + return output; + } + } +} \ No newline at end of file diff --git a/RemoteControlledProcess.ConsumerDriven.Tests/RemoteControlledProcess.ConsumerDriven.Tests.csproj b/RemoteControlledProcess.ConsumerDriven.Tests/RemoteControlledProcess.ConsumerDriven.Tests.csproj new file mode 100644 index 0000000..9631cee --- /dev/null +++ b/RemoteControlledProcess.ConsumerDriven.Tests/RemoteControlledProcess.ConsumerDriven.Tests.csproj @@ -0,0 +1,26 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/RemoteControlledProcess.ConsumerDriven.Tests/TaskkillTests.cs b/RemoteControlledProcess.ConsumerDriven.Tests/TaskkillTests.cs new file mode 100644 index 0000000..06cdf83 --- /dev/null +++ b/RemoteControlledProcess.ConsumerDriven.Tests/TaskkillTests.cs @@ -0,0 +1,29 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using Xunit; +using Xunit.Abstractions; + +namespace RemoteControlledProcess.ConsumerDriven.Tests +{ + public class TaskkillTests + { + private ITestOutputHelper _testOutputHelper; + + public TaskkillTests(ITestOutputHelper testOutputHelper) => _testOutputHelper = testOutputHelper; + + [Fact] + public void RunTaskkillProcess_OnWindows_ShowsProcessOutput() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _testOutputHelper.WriteLine($"Non-Windows OS detected. Skipping test RunTaskkillProcess_OnWindows_ShowsProcessOutput."); + return; + } + + var output = ProcessRunner.RunProcess("taskkill", "/?", _testOutputHelper); + + Assert.Contains("TASKKILL", output); + Assert.Contains("/PID", output); + } + } +} diff --git a/RemoteControlledProcess.Tests.proj b/RemoteControlledProcess.Tests.proj new file mode 100644 index 0000000..66e162d --- /dev/null +++ b/RemoteControlledProcess.Tests.proj @@ -0,0 +1,5 @@ + + + + + diff --git a/RemoteControlledProcess.Unit.Tests/ExceptionReporterExtensionTests.cs b/RemoteControlledProcess.Unit.Tests/ExceptionReporterExtensionTests.cs index 290ea6d..6391db6 100644 --- a/RemoteControlledProcess.Unit.Tests/ExceptionReporterExtensionTests.cs +++ b/RemoteControlledProcess.Unit.Tests/ExceptionReporterExtensionTests.cs @@ -11,7 +11,7 @@ public class ExceptionReporterExtensionTests [Theory] [InlineData(0, - "Unhandled exception in .*RemoteControlledProcess.Unit.Tests/ExceptionReporterExtensionTests.cs:[0-9]+")] + @"Unhandled exception in .*RemoteControlledProcess\.Unit\.Tests[\/\\]ExceptionReporterExtensionTests\.cs:[0-9]+")] [InlineData(1, ExceptionMessage)] public void Write_CalledInCatchBlock_WrittenMessagesMatch(int invocationIndex, string expectedMessageRegex) { @@ -51,7 +51,7 @@ public void Log_CalledInCatchBlock_LogsCriticalMessageWithExpectedRegex() // Assert var expectedMessageRegex = - "Unhandled exception in .*RemoteControlledProcess.Unit.Tests/ExceptionReporterExtensionTests.cs:[0-9]+"; + @"Unhandled exception in .*RemoteControlledProcess\.Unit\.Tests[\/\\]ExceptionReporterExtensionTests\.cs:[0-9]+"; var actualLogLevel = (LogLevel)loggerMock.Invocations[0].Arguments[0]; var actualMessage = loggerMock.Invocations[0].Arguments[2].ToString(); Assert.Equal(LogLevel.Critical, actualLogLevel); diff --git a/RemoteControlledProcess.sln b/RemoteControlledProcess.sln index 6fe1f85..f32a93c 100644 --- a/RemoteControlledProcess.sln +++ b/RemoteControlledProcess.sln @@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{86E95D10-0 run-application.bat = run-application.bat run-application.sh = run-application.sh Nuget-OfficialOnly.config = Nuget-OfficialOnly.config + RemoteControlledProcess.Tests.proj = RemoteControlledProcess.Tests.proj EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteControlledProcess.Application", "RemoteControlledProcess.Application\RemoteControlledProcess.Application.csproj", "{5122B0AC-FAD4-44F4-B372-C5A6AED29E22}" @@ -32,6 +33,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RemoteControlledProcess.Nup EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteControlledProcess.Unit.Tests", "RemoteControlledProcess.Unit.Tests\RemoteControlledProcess.Unit.Tests.csproj", "{7BC3300D-4599-409C-9839-A69E6E66CD7F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteControlledProcess.ConsumerDriven.Tests", "RemoteControlledProcess.ConsumerDriven.Tests\RemoteControlledProcess.ConsumerDriven.Tests.csproj", "{288D6EDE-DC2C-4EE7-A3BA-7FE605865413}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -90,6 +93,18 @@ Global {7BC3300D-4599-409C-9839-A69E6E66CD7F}.Release|x64.Build.0 = Release|Any CPU {7BC3300D-4599-409C-9839-A69E6E66CD7F}.Release|x86.ActiveCfg = Release|Any CPU {7BC3300D-4599-409C-9839-A69E6E66CD7F}.Release|x86.Build.0 = Release|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Debug|Any CPU.Build.0 = Debug|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Debug|x64.ActiveCfg = Debug|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Debug|x64.Build.0 = Debug|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Debug|x86.ActiveCfg = Debug|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Debug|x86.Build.0 = Debug|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Release|Any CPU.ActiveCfg = Release|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Release|Any CPU.Build.0 = Release|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Release|x64.ActiveCfg = Release|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Release|x64.Build.0 = Release|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Release|x86.ActiveCfg = Release|Any CPU + {288D6EDE-DC2C-4EE7-A3BA-7FE605865413}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RemoteControlledProcess/ProcessWrapper.cs b/RemoteControlledProcess/ProcessWrapper.cs index e328e8f..662197e 100644 --- a/RemoteControlledProcess/ProcessWrapper.cs +++ b/RemoteControlledProcess/ProcessWrapper.cs @@ -8,6 +8,8 @@ namespace RemoteControlledProcess { + // TODO: Double check whether serializing tests is really required - see dotnet.yml + public sealed class ProcessWrapper : IDisposable { private readonly string _appDir; @@ -233,8 +235,10 @@ private void WaitForProcessExit() { TestOutputHelper?.WriteLine("Waiting for process to shutdown ..."); _process.WaitForExit(2000); - TestOutputHelper?.WriteLine($"Process {_appProjectName} has " + (_process.HasExited ? "" : "NOT ") + - "completed."); + + var processDescription = IsCoverletEnabled ? $"coverlet({_appProjectName})" : _appProjectName; + var conditionalNot = _process.HasExited ? "" : "NOT "; + TestOutputHelper?.WriteLine($"Process {processDescription} has {conditionalNot}completed."); } public void ForceTermination()