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