Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/NUnitConsoleAndEngine.CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
- name: 🔨 Build, Test, Package and Publish (Cake script decides)
run: dotnet cake --target=ContinuousIntegration --configuration=Release #--verbosity=diagnostic

# Upload packages before publishing in case of later failures
# Upload packages
- name: 💾 Upload build artifacts
if: always()
uses: actions/upload-artifact@v7
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<DebugSymbols>true</DebugSymbols>
<!-- Commonly Used Package Versions -->
<AnnotatedReferenceAssemblyVersion>8.0.0</AnnotatedReferenceAssemblyVersion>
<EngineApiVersion>4.0.0-beta.3</EngineApiVersion>
<EngineApiVersion>4.0.0-beta.4</EngineApiVersion>
<!-- Informational Settings -->
<Company>NUnit Software</Company>
<Product>NUnit 4 Runner and Engine</Product>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,22 @@ public void Run_Throws_NUnitEngineException()
[Test]
public void StopRun_Passes_Along_NUnitEngineException()
{
_driver.When(x => x.StopRun(Arg.Any<bool>()))
_driver.When(x => x.ForcedStop())
.Do(x => { throw new NUnitEngineException("Message"); });

var ex = Assert.Throws<NUnitEngineException>(() => _runner.ForcedStop());
Assert.That(ex.Message, Is.EqualTo("Message"));
}

[Test]
public void StopRun_Throws_NUnitEngineException()
public void StopRun_Throws_ArgumentException()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is looks identical to StopRun_Passes_Along_NUnitEngineException.
Proofing that _runner.ForcedStop() calls _driver.ForcedStop()

{
_driver.When(x => x.StopRun(Arg.Any<bool>()))
_driver.When(x => x.ForcedStop())
.Do(x => { throw new ArgumentException("Message"); });

var ex = Assert.Throws<NUnitEngineException>(() => _runner.ForcedStop());
Assert.That(ex.InnerException, Is.Not.Null);
Assert.That(ex.InnerException, Is.InstanceOf<ArgumentException>());
Assert.That(ex.InnerException.Message, Is.EqualTo("Message"));
var ex = Assert.Throws<ArgumentException>(() => _runner.ForcedStop());
Assert.That(ex.
Message, Is.EqualTo("Message"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ public string Explore(string filter)
return GetLoadResult();
}

public void StopRun(bool force)
public void RequestStop()
{
}

public void ForcedStop()
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class NUnit3DriverFactory : IDriverFactory
/// <param name="reference">An AssemblyName referring to the possible test framework.</param>
public bool IsSupportedTestFramework(AssemblyName reference)
{
return NUNIT_FRAMEWORK.Equals(reference.Name, StringComparison.OrdinalIgnoreCase) && reference.Version?.Major >= 3;
return NUNIT_FRAMEWORK.Equals(reference.Name, StringComparison.OrdinalIgnoreCase) && reference.Version?.Major is 3 or 4;
}

#if NETFRAMEWORK
Expand Down
18 changes: 16 additions & 2 deletions src/NUnitCommon/nunit.agent.core/Drivers/NUnitFrameworkApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

namespace NUnit.Engine.Drivers
{
/// <summary>
/// Driver API for the NUnit Framework. Provides a common interface to al
/// versions of the framework, in spite of differences in their own API.
/// </summary>
public interface NUnitFrameworkApi
{
/// <summary>
Expand Down Expand Up @@ -45,7 +49,17 @@ public interface NUnitFrameworkApi
/// <summary>
/// Cancel the ongoing test run. If no test is running, the call is ignored.
/// </summary>
/// <param name="force">If true, cancel any ongoing test threads, otherwise wait for them to complete.</param>
void StopRun(bool force);
void RequestStop();

/// <summary>
/// Force the current test run to stop, killing threads or processes if necessary.
/// </summary>
/// <exception cref="NotImplementedException" />
void ForcedStop();

/// <summary>
/// Gets a flag indicating whether ForcedStop is supported for the framework version in use.
/// </summary>
bool ForcedStopSupported { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ public string Run(ITestEventListener? listener, string filter)

public void RunAsync(Action<string>? callback, string filter) => throw new NotImplementedException();

public void StopRun(bool force) => ExecuteAction(STOP_RUN_ACTION, force);
public void RequestStop() => ExecuteAction(STOP_RUN_ACTION, false);

public void ForcedStop() => ExecuteAction(STOP_RUN_ACTION, true);

public bool ForcedStopSupported => true;

public string Explore(string filter)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,18 @@ public void RunAsync(Action<string>? callback, string filter)
ExecuteMethod(RUN_ASYNC_METHOD, [typeof(Action<string>), typeof(string)], callback, filter);
}

public void StopRun(bool force)
public void RequestStop()
{
ExecuteMethod(STOP_RUN_METHOD, force);
ExecuteMethod(STOP_RUN_METHOD, false);
}

public void ForcedStop()
{
ExecuteMethod(STOP_RUN_METHOD, true);
}

public bool ForcedStopSupported => _nunitRef.Version.ShouldNotBeNull().Major is 3 or 4;

public string Explore(string filter)
{
CheckLoadWasCalled();
Expand Down
22 changes: 20 additions & 2 deletions src/NUnitCommon/nunit.agent.core/Drivers/NUnitFrameworkDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using NUnit.Engine.Extensibility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;

#if NETCOREAPP3_1_OR_GREATER
Expand All @@ -20,6 +21,8 @@ public class NUnitFrameworkDriver : IFrameworkDriver
private static readonly Version MINIMUM_NUNIT_VERSION = new(3, 2, 0);
private static readonly Logger log = InternalTrace.GetLogger(nameof(NUnitFrameworkDriver));

private readonly Version _nunitVersion;

#if NETFRAMEWORK
private readonly NUnitFrameworkApi _api;

Expand All @@ -35,6 +38,7 @@ public NUnitFrameworkDriver(AppDomain testDomain, string id, AssemblyName nunitR
Guard.ArgumentNotNull(nunitRef);

ID = id;
_nunitVersion = nunitRef.Version.ShouldNotBeNull();

if (nunitRef.Version >= MINIMUM_NUNIT_VERSION)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use the new field.

Suggested change
if (nunitRef.Version >= MINIMUM_NUNIT_VERSION)
if (_nunitVersion >= MINIMUM_NUNIT_VERSION)

{
Expand Down Expand Up @@ -69,6 +73,8 @@ internal NUnitFrameworkDriver(AppDomain testDomain, string api, string id, Assem
Guard.ArgumentNotNullOrEmpty(id);
Guard.ArgumentNotNull(nunitRef);

_nunitVersion = nunitRef.Version.ShouldNotBeNull();

ID = id;
API = api;

Expand Down Expand Up @@ -99,6 +105,7 @@ public NUnitFrameworkDriver(string id, AssemblyName nunitRef)
ID = id;
API = "2018";

_nunitVersion = nunitRef.Version.ShouldNotBeNull();
_api = new NUnitFrameworkApi2018(ID, nunitRef);
}

Expand Down Expand Up @@ -152,8 +159,19 @@ public void RunAsync(ITestEventListener? listener, string filter) =>
/// <summary>
/// Cancel the ongoing test run. If no test is running, the call is ignored.
/// </summary>
/// <param name="force">If true, cancel any ongoing test threads, otherwise wait for them to complete.</param>
public void StopRun(bool force) => _api.StopRun(force);
public void RequestStop() => _api.RequestStop();

/// <summary>
/// Force the current test run to stop, killing threads or processes if necessary.
/// If no tests are running, the call is ignored.
/// </summary>
public void ForcedStop()
{
if (_api.ForcedStopSupported)
_api.ForcedStop();
else
Process.GetCurrentProcess().Kill();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that not the same as calling Exit?

Suggested change
Process.GetCurrentProcess().Kill();
Environment.Exit(1);

}

/// <summary>
/// Returns information about the tests in an assembly.
Expand Down
16 changes: 2 additions & 14 deletions src/NUnitCommon/nunit.agent.core/Runners/TestAgentRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,25 +244,13 @@ public AsyncTestEngineResult RunAsync(ITestEventListener? listener, TestFilter f
/// Request the current test run to stop. If no tests are running,
/// the call is ignored.
/// </summary>
public void RequestStop() => StopRun(false);
public void RequestStop() => GetLoadedDriver().RequestStop();

/// <summary>
/// Force the current test run to stop, killing threads or processes if necessary.
/// If no tests are running, the call is ignored.
/// </summary>
public void ForcedStop() => StopRun(true);

private void StopRun(bool force)
{
try
{
GetLoadedDriver().StopRun(force);
}
catch (Exception ex) when (!(ex is NUnitEngineException))
{
throw new NUnitEngineException("An exception occurred in the driver while stopping the run.", ex);
}
}
public void ForcedStop() => GetLoadedDriver().ForcedStop();

private IFrameworkDriver GetLoadedDriver()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,13 @@ public void RunAsync()
_engineRunner.Received().Run(Arg.Any<TestEventDispatcher>(), filter);
}

[TestCase(false)]
[TestCase(true)]
public void StopRun(bool force)
[Test]
public void RequestStop()
{
_masterTestRunner.GetEngineRunner();
_masterTestRunner.StopRun(force);
if (force)
_engineRunner.Received().ForcedStop();
else
_engineRunner.Received().RequestStop();
_masterTestRunner.RequestStop();

_engineRunner.Received().RequestStop();
}
#endif
}
Expand Down
35 changes: 21 additions & 14 deletions src/NUnitEngine/nunit.engine/Runners/MasterTestRunner.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt

#define USE_WORK_ITEM_TRACKER

using System;
using System.ComponentModel;
using System.Diagnostics;
Expand Down Expand Up @@ -51,7 +53,9 @@ public class MasterTestRunner : ITestRunner
private bool _disposed;

private TestEventDispatcher _eventDispatcher = new TestEventDispatcher();
#if USE_WORK_ITEM_TRACKER
private WorkItemTracker _workItemTracker = new WorkItemTracker();
#endif

private int _testRunTimeout;
private Timer? _testRunTimer;
Expand Down Expand Up @@ -186,19 +190,16 @@ public ITestRun RunAsync(ITestEventListener? listener, TestFilter filter)
/// <summary>
/// Cancel the ongoing test run. If no test is running, the call is ignored.
/// </summary>
/// <param name="force">If true, cancel any ongoing test threads, otherwise wait for them to complete.</param>
public void StopRun(bool force)
{
if (_engineRunner is null)
return; // No test is was even started.
public void RequestStop() => _engineRunner?.RequestStop();

if (!force)
_engineRunner.RequestStop();
else
public void ForcedStop()
{
if (_engineRunner is not null)
{
_engineRunner.ForcedStop();

// Frameworks should handle StopRun(true) by cancelling all tests and notifying
#if USE_WORK_ITEM_TRACKER
// Framework drivers should handle ForcedStop() by cancelling all tests and notifying
// us of the completion of any tests that were running. However, this feature
// may be absent in some frameworks or may be broken and we may not pass on the
// notifications needed by some runners. In fact, such a bug is present in the
Expand All @@ -225,6 +226,7 @@ public void StopRun(bool force)

_engineRunner.Unload();
}
#endif
}
}

Expand Down Expand Up @@ -454,9 +456,11 @@ private int CountTests(TestFilter filter)
/// <returns>A TestEngineResult giving the result of the test execution</returns>
private TestEngineResult RunTests(ITestEventListener? listener, TestFilter filter)
{
_workItemTracker.Clear();
_eventDispatcher.Listeners.Clear();
#if USE_WORK_ITEM_TRACKER
_workItemTracker.Clear();
_eventDispatcher.Listeners.Add(_workItemTracker);
#endif

if (listener is not null)
_eventDispatcher.Listeners.Add(listener);
Expand Down Expand Up @@ -530,15 +534,18 @@ private TestEngineResult RunTests(ITestEventListener? listener, TestFilter filte
private void OnTestRunTimeout(object? sender, ElapsedEventArgs e)
{
// Unlikely as it is, let's see if we can stop this cooperatively
StopRun(false);
RequestStop();
// TODO: Should we wait for run complete if we are not using WorkItemTracker?

// We wait for the cooperative stop and do a forced stop if it fails.
// WaitForCompletion will actually be called twice, once here and
// again in StopRun(true), so the wait is doubled but no harm done.
// StopRun(True) also calls _workItemTracker to try to fix up any
// again in ForcedStop(), so the wait is doubled but no harm done.
// ForcedStop() also calls _workItemTracker to try to fix up any
// in-flight items and produce as informative a result as possible.
#if USE_WORK_ITEM_TRACKER
if (!_workItemTracker.WaitForCompletion(WAIT_FOR_CANCEL_TO_COMPLETE))
StopRun(true);
ForcedStop();
#endif
}

private AsyncTestEngineResult RunTestsAsync(ITestEventListener? listener, TestFilter filter)
Expand Down