Skip to content

Commit

Permalink
Enable multiplier style max threads support
Browse files Browse the repository at this point in the history
  • Loading branch information
bradwilson committed Apr 19, 2024
1 parent 9677d41 commit 3722e54
Show file tree
Hide file tree
Showing 17 changed files with 186 additions and 191 deletions.
14 changes: 10 additions & 4 deletions src/xunit.console/CommandLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,11 +261,17 @@ protected XunitProject Parse(Predicate<string> fileExists)
break;

default:
int threadValue;
if (!int.TryParse(option.Value, out threadValue) || threadValue < 1)
throw new ArgumentException("incorrect argument value for -maxthreads (must be 'default', 'unlimited', or a positive number)");
var match = ConfigUtility.MultiplierStyleMaxParallelThreadsRegex.Match(option.Value);
// Use invariant format and convert ',' to '.' so we can always support both formats, regardless of locale
// If we stick to locale-only parsing, we could break people when moving from one locale to another (for example,
// from people running tests on their desktop in a comma locale vs. running them in CI with a decimal locale).
if (match.Success && decimal.TryParse(match.Groups[1].Value.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var maxThreadMultiplier))
MaxParallelThreads = (int)(maxThreadMultiplier * Environment.ProcessorCount);
else if (int.TryParse(option.Value, out var threadValue) && threadValue > 0)
MaxParallelThreads = threadValue;
else
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "incorrect argument value for -maxthreads (must be 'default', 'unlimited', a positive number, or a multiplier in the form of '{0}x')", 0.0m));

MaxParallelThreads = threadValue;
break;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/xunit.console/ConsoleRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ void PrintUsage(IReadOnlyList<IRunnerReporter> reporters)
Console.WriteLine(" -maxthreads count : maximum thread count for collection parallelization");
Console.WriteLine(" : default - run with default (1 thread per CPU thread)");
Console.WriteLine(" : unlimited - run with unbounded thread count");
Console.WriteLine(" : (number) - limit task thread pool size to 'count'");
Console.WriteLine(" : (integer) - use exactly this many threads (e.g., '2' = 2 threads)");
Console.WriteLine(" : (float)x - use a multiple of CPU threads (e.g., '2.0x' = 2.0 * the number of CPU threads)");
#if NETFRAMEWORK
Console.WriteLine(" -appdomains mode : choose an app domain mode");
Console.WriteLine(" : ifavailable - choose based on library type");
Expand Down
19 changes: 17 additions & 2 deletions src/xunit.runner.utility/Configuration/ConfigReader_Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,28 @@ static TestAssemblyConfiguration LoadConfiguration(Stream configStream, string c
}
else if (string.Equals(propertyName, Configuration.MaxParallelThreads, StringComparison.OrdinalIgnoreCase))
{
var numberValue = propertyValue as JsonNumber;
if (numberValue != null)
if (propertyValue is JsonNumber numberValue)
{
int maxParallelThreads;
if (int.TryParse(numberValue.Raw, out maxParallelThreads) && maxParallelThreads >= -1)
result.MaxParallelThreads = maxParallelThreads;
}
else if (propertyValue is JsonString stringValue)
{
if (string.Equals("default", stringValue, StringComparison.OrdinalIgnoreCase))
result.MaxParallelThreads = null;
else if (string.Equals("unlimited", stringValue, StringComparison.OrdinalIgnoreCase))
result.MaxParallelThreads = -1;
else
{
var match = ConfigUtility.MultiplierStyleMaxParallelThreadsRegex.Match(stringValue);
// Use invariant format and convert ',' to '.' so we can always support both formats, regardless of locale
// If we stick to locale-only parsing, we could break people when moving from one locale to another (for example,
// from people running tests on their desktop in a comma locale vs. running them in CI with a decimal locale).
if (match.Success && decimal.TryParse(match.Groups[1].Value.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var maxThreadMultiplier))
result.MaxParallelThreads = (int)(maxThreadMultiplier * Environment.ProcessorCount);
}
}
}
else if (string.Equals(propertyName, Configuration.LongRunningTestSeconds, StringComparison.OrdinalIgnoreCase))
{
Expand Down
16 changes: 16 additions & 0 deletions src/xunit.runner.utility/Utility/ConfigUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Text.RegularExpressions;

namespace Xunit
{
/// <summary>
/// Utilities for reading configuration values.
/// </summary>
public static class ConfigUtility
{
/// <summary>
/// Gets the regular expression that matches the multiplier-style value for maximum
/// parallel threads (that is, '0.5x', '2x', etc.).
/// </summary>
public static readonly Regex MultiplierStyleMaxParallelThreadsRegex = new(@"^(\d+((\.|,)\d+)?)(x|X)$");
}
}
10 changes: 9 additions & 1 deletion test/test.xunit.console/CommandLineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public static void InvalidValues(string value)
{
var ex = Assert.Throws<ArgumentException>(() => TestableCommandLine.Parse("assemblyName.dll", "-maxthreads", value));

Assert.Equal("incorrect argument value for -maxthreads (must be 'default', 'unlimited', or a positive number)", ex.Message);
Assert.Equal("incorrect argument value for -maxthreads (must be 'default', 'unlimited', a positive number, or a multiplier in the form of '0.0x')", ex.Message);
}

[Theory]
Expand All @@ -226,6 +226,14 @@ public static void ValidValues(string value, int expected)

Assert.Equal(expected, commandLine.MaxParallelThreads);
}

[Theory]
public static void MultiplierValue()
{
var commandLine = TestableCommandLine.Parse("assemblyName.dll", "-maxthreads", "2.0x");

Assert.Equal(Environment.ProcessorCount * 2, commandLine.MaxParallelThreads);
}
}

public class AppDomainsOption
Expand Down

0 comments on commit 3722e54

Please sign in to comment.