Skip to content

Commit

Permalink
Add a start options data class for AssemblyRunner.Start (#2922)
Browse files Browse the repository at this point in the history
  • Loading branch information
bradwilson committed May 3, 2024
1 parent ac36daf commit ed625e5
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 16 deletions.
81 changes: 65 additions & 16 deletions src/xunit.runner.utility/Runners/AssemblyRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class AssemblyRunner : LongLivedMarshalByRefObject, IDisposable, IMessage
readonly TestAssemblyConfiguration configuration;
readonly IFrontController controller;
readonly ManualResetEvent discoveryCompleteEvent = new ManualResetEvent(true);
readonly ManualResetEvent discoveryCompleteIntermediateEvent = new ManualResetEvent(true);
readonly ManualResetEvent executionCompleteEvent = new ManualResetEvent(true);
readonly object statusLock = new object();
int testCasesDiscovered;
Expand Down Expand Up @@ -160,6 +161,7 @@ public void Dispose()

controller.SafeDispose();
discoveryCompleteEvent.SafeDispose();
discoveryCompleteIntermediateEvent.SafeDispose();
executionCompleteEvent.SafeDispose();
}

Expand Down Expand Up @@ -205,18 +207,26 @@ ITestFrameworkExecutionOptions GetExecutionOptions(bool? diagnosticMessages, boo
/// Obsolete method. Call the overload with parallelAlgorithm.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use the overload with parallelAlgorithm")]
[Obsolete("Please use the overload with startOptions")]
public void Start(string typeName = null,
bool? diagnosticMessages = null,
TestMethodDisplay? methodDisplay = null,
TestMethodDisplayOptions? methodDisplayOptions = null,
bool? preEnumerateTheories = null,
bool? parallel = null,
int? maxParallelThreads = null,
bool? internalDiagnosticMessages = null)
{
Start(typeName, diagnosticMessages, methodDisplay, methodDisplayOptions, preEnumerateTheories, parallel, maxParallelThreads, internalDiagnosticMessages, null);
}
bool? internalDiagnosticMessages = null) =>
Start(new AssemblyRunnerStartOptions()
{
DiagnosticMessages = diagnosticMessages,
InternalDiagnosticMessages = internalDiagnosticMessages,
MaxParallelThreads = maxParallelThreads,
MethodDisplay = methodDisplay,
MethodDisplayOptions = methodDisplayOptions,
Parallel = parallel,
PreEnumerateTheories = preEnumerateTheories,
TypesToRun = typeName == null ? new string[0] : new string[1] { typeName },
});

/// <summary>
/// Starts running tests from a single type (if provided) or the whole assembly (if not). This call returns
Expand All @@ -240,6 +250,8 @@ ITestFrameworkExecutionOptions GetExecutionOptions(bool? diagnosticMessages, boo
/// <param name="internalDiagnosticMessages">Set to <c>true</c> to enable internal diagnostic messages; set to <c>false</c> to disable them.
/// By default, uses the value from the assembly configuration file.</param>
/// <param name="parallelAlgorithm">The parallel algorithm to be used; defaults to <see cref="ParallelAlgorithm.Conservative"/>.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Please use the overload with startOptions")]
public void Start(string typeName = null,
bool? diagnosticMessages = null,
TestMethodDisplay? methodDisplay = null,
Expand All @@ -248,7 +260,27 @@ ITestFrameworkExecutionOptions GetExecutionOptions(bool? diagnosticMessages, boo
bool? parallel = null,
int? maxParallelThreads = null,
bool? internalDiagnosticMessages = null,
ParallelAlgorithm? parallelAlgorithm = null)
ParallelAlgorithm? parallelAlgorithm = null) =>
Start(new AssemblyRunnerStartOptions()
{
DiagnosticMessages = diagnosticMessages,
InternalDiagnosticMessages = internalDiagnosticMessages,
MaxParallelThreads = maxParallelThreads,
MethodDisplay = methodDisplay,
MethodDisplayOptions = methodDisplayOptions,
Parallel = parallel,
ParallelAlgorithm = parallelAlgorithm,
PreEnumerateTheories = preEnumerateTheories,
TypesToRun = typeName == null ? new string[0] : new string[1] { typeName },
});

/// <summary>
/// Starts running tests. This call returns immediately, and status results are dispatched to the
/// events on this class. Callers can check <see cref="Status"/> to find out the current status.
/// </summary>
/// <param name="startOptions">The start options. For default values, you may pass
/// <see cref="AssemblyRunnerStartOptions.Empty"/>.</param>
public void Start(AssemblyRunnerStartOptions startOptions)
{
lock (statusLock)
{
Expand All @@ -264,22 +296,40 @@ ITestFrameworkExecutionOptions GetExecutionOptions(bool? diagnosticMessages, boo

XunitWorkerThread.QueueUserWorkItem(() =>
{
var discoveryOptions = GetDiscoveryOptions(diagnosticMessages, methodDisplay, methodDisplayOptions, preEnumerateTheories, internalDiagnosticMessages);
if (typeName != null)
controller.Find(typeName, false, this, discoveryOptions);
else
var discoveryOptions = GetDiscoveryOptions(startOptions.DiagnosticMessages,
startOptions.MethodDisplay,
startOptions.MethodDisplayOptions,
startOptions.PreEnumerateTheories,
startOptions.InternalDiagnosticMessages);
if (startOptions.TypesToRun.Length == 0)
{
discoveryCompleteIntermediateEvent.Reset();
controller.Find(false, this, discoveryOptions);
discoveryCompleteIntermediateEvent.WaitOne();
}
else
foreach (var typeName in startOptions.TypesToRun.Where(t => !string.IsNullOrEmpty(t)))
{
discoveryCompleteIntermediateEvent.Reset();
controller.Find(typeName, false, this, discoveryOptions);
discoveryCompleteIntermediateEvent.WaitOne();
}
OnDiscoveryComplete?.Invoke(new DiscoveryCompleteInfo(testCasesDiscovered, testCasesToRun.Count));
discoveryCompleteEvent.Set();
discoveryCompleteEvent.WaitOne();
if (cancelled)
{
// Synthesize the execution complete message, since we're not going to run at all
if (OnExecutionComplete != null)
OnExecutionComplete(ExecutionCompleteInfo.Empty);
OnExecutionComplete?.Invoke(ExecutionCompleteInfo.Empty);
return;
}
var executionOptions = GetExecutionOptions(diagnosticMessages, parallel, parallelAlgorithm, maxParallelThreads, internalDiagnosticMessages);
var executionOptions = GetExecutionOptions(startOptions.DiagnosticMessages,
startOptions.Parallel,
startOptions.ParallelAlgorithm,
startOptions.MaxParallelThreads,
startOptions.InternalDiagnosticMessages);
controller.RunTests(testCasesToRun, this, executionOptions);
executionCompleteEvent.WaitOne();
});
Expand Down Expand Up @@ -337,8 +387,7 @@ bool IMessageSinkWithTypes.OnMessageWithTypes(IMessageSinkMessage message, HashS

if (DispatchMessage<IDiscoveryCompleteMessage>(message, messageTypes, discoveryComplete =>
{
OnDiscoveryComplete?.Invoke(new DiscoveryCompleteInfo(testCasesDiscovered, testCasesToRun.Count));
discoveryCompleteEvent.Set();
discoveryCompleteIntermediateEvent.Set();
}))
return !cancelled;

Expand Down
96 changes: 96 additions & 0 deletions src/xunit.runner.utility/Runners/AssemblyRunnerStartOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;

namespace Xunit.Runners
{
/// <summary>
/// Represents options to be used when calling
/// <see cref="AssemblyRunner.Start(AssemblyRunnerStartOptions)"/>.
/// </summary>
public class AssemblyRunnerStartOptions
{
string[] typesToRun;

/// <summary>
/// Indicates whether diagnostic messages should be generated. If unset (or set
/// to <c>null</c>), will use the value from the configuration file (and if that
/// isn't set, will use the default value of <c>false</c>).
/// </summary>
public bool? DiagnosticMessages { get; set; }

/// <summary>
/// Gets an empty set of options (representing all default behavior).
/// </summary>
public static AssemblyRunnerStartOptions Empty => new();

/// <summary>
/// Indicates whether internal diagnostic messages should be generated (these are
/// typically low level diagnostic messages from the test engine itself that may
/// be requested by xUnit.net developers when debugging issues inside xUnit.net
/// itself). If unset (or set to <c>null</c>), will use the value from the
/// configuraiton file (and if that isn't set, will use the default value
/// of <c>false</c>).
/// </summary>
public bool? InternalDiagnosticMessages { get; set; }

/// <summary>
/// Indicates how many threads to use to run parallel tests (will have no affect
/// if parallelism is turned off). A value of <c>-1</c> indicates a desire for
/// no thread limit; a value of <c>0</c> indicates a desire for the default
/// limit (which is <see cref="Environment.ProcessorCount"/>); a value greater
/// than 0 indicates an exact thread count is desired. If unset (or set to
/// <c>null</c>), will use the value from the configuration file (and if that
/// isn't set, will use the default value of <see cref="Environment.ProcessorCount"/>).
/// </summary>
public int? MaxParallelThreads { get; set; }

/// <summary>
/// Indicates how to display test methods. If unset (or set to <c>null</c>),
/// will use the value from the configuration file (and if that isn't set,
/// will use the default value of <see cref="TestMethodDisplay.ClassAndMethod"/>).
/// </summary>
public TestMethodDisplay? MethodDisplay { get; set; }

/// <summary>
/// Indicates how to interpret test method names for display. If unset (or set
/// to <c>null</c>), will use the value from the configuration file (and if that
/// isn't set, will use the default value of <see cref="TestMethodDisplayOptions.None"/>).
/// </summary>
public TestMethodDisplayOptions? MethodDisplayOptions { get; set; }

/// <summary>
/// Indicates whether to run test collections in parallel. If unset (or set to
/// <c>null</c>), will use the value from the configuration file (and if that
/// isn't set, will use the default value of <c>true</c>). Note that test
/// collection parallelization is only available in v2 test projects.
/// </summary>
public bool? Parallel { get; set; }

/// <summary>
/// Indicates which algorithm to use when parallelizing tests (will have no effect
/// if parallelism is turned off or if the max parallel threads is set to <c>-1</c>).
/// If unset (or set to <c>null</c>), will use the value from the configuration
/// file (and if that isn't set, will use the default value of
/// <see cref="ParallelAlgorithm.Conservative"/>. For more information on the
/// parallelism algorithms, see
/// <see href="https://xunit.net/docs/running-tests-in-parallel#algorithms"/>.
/// </summary>
public ParallelAlgorithm? ParallelAlgorithm { get; set; }

/// <summary>
/// Indicates whether theories should be pre-enumerated (that is, enumerated during
/// discovery rather than during execution). If unset (or set to <c>null</c>),
/// will use the value from the configuration file (and if that isn't set,
/// will use the default value of <c>false</c>).
/// </summary>
public bool? PreEnumerateTheories { get; set; }

/// <summary>
/// Indicates the types to be run. If empty, will run all types in the assembly.
/// </summary>
public string[] TypesToRun
{
get => typesToRun;
set => typesToRun = value ?? [];
}
}
}

0 comments on commit ed625e5

Please sign in to comment.