diff --git a/src/xunit.v3.runner.common.tests/Logger/ConsoleRunnerLoggerTests.cs b/src/xunit.v3.runner.common.tests/Logger/ConsoleRunnerLoggerTests.cs
index 6477c8cf1..a9dcd410a 100644
--- a/src/xunit.v3.runner.common.tests/Logger/ConsoleRunnerLoggerTests.cs
+++ b/src/xunit.v3.runner.common.tests/Logger/ConsoleRunnerLoggerTests.cs
@@ -10,7 +10,7 @@ public void WriteLine_ColorsEnabled_PlainText()
{
var writer = new StringWriter();
var message = "foo bar";
- var sut = new ConsoleRunnerLogger(true);
+ var sut = new ConsoleRunnerLogger(true, false);
sut.WriteLine(writer, message);
@@ -22,7 +22,7 @@ public void WriteLine_ColorsEnabled_AnsiText()
{
var writer = new StringWriter();
var message = "\x1b[3m\x1b[36mhello world\u001b[0m || \x1b[94;103mbright blue on bright yellow\x1b[m";
- var sut = new ConsoleRunnerLogger(true);
+ var sut = new ConsoleRunnerLogger(true, false);
sut.WriteLine(writer, message);
@@ -34,7 +34,7 @@ public void WriteLine_ColorsDisabled_PlainText()
{
var writer = new StringWriter();
var message = "foo bar";
- var sut = new ConsoleRunnerLogger(false);
+ var sut = new ConsoleRunnerLogger(false, false);
sut.WriteLine(writer, message);
@@ -45,10 +45,31 @@ public void WriteLine_ColorsDisabled_PlainText()
public void WriteLine_ColorsDisabled_AnsiText()
{
var writer = new StringWriter();
- var sut = new ConsoleRunnerLogger(false);
+ var sut = new ConsoleRunnerLogger(false, false);
sut.WriteLine(writer, "\x1b[3m\x1b[36mhello world\u001b[0m || \x1b[94;103mbright blue on bright yellow\x1b[m");
Assert.Equal("hello world || bright blue on bright yellow" + Environment.NewLine, writer.ToString());
}
+
+ [Fact]
+ public void CanForceAnsiColors()
+ {
+ var oldConsoleOut = Console.Out;
+
+ try
+ {
+ var writer = new StringWriter();
+ var sut = new ConsoleRunnerLogger(true, true);
+ Console.SetOut(writer);
+
+ sut.LogError("This is an error message");
+
+ Assert.Equal("\u001b[91mThis is an error message\r\n\u001b[0m", writer.ToString());
+ }
+ finally
+ {
+ Console.SetOut(oldConsoleOut);
+ }
+ }
}
diff --git a/src/xunit.v3.runner.common/Frameworks/TestProjectConfiguration.cs b/src/xunit.v3.runner.common/Frameworks/TestProjectConfiguration.cs
index 2033814c2..86fda2497 100644
--- a/src/xunit.v3.runner.common/Frameworks/TestProjectConfiguration.cs
+++ b/src/xunit.v3.runner.common/Frameworks/TestProjectConfiguration.cs
@@ -103,6 +103,18 @@ public class TestProjectConfiguration
///
public bool PauseOrDefault => Pause ?? false;
+ ///
+ /// Gets or sets a flag indicating that ANSI color usage should be forced on Windows.
+ /// ANSI color is always used for non-Windows.
+ ///
+ public bool? UseAnsiColor { get; set; }
+
+ ///
+ /// Gets a flag indicating that ANSI color usage should be forced on Windows. ANSI color is
+ /// always used for non-Windows. If the flag is not set, returns the default value (false).
+ ///
+ public bool UseAnsiColorOrDefault => UseAnsiColor ?? false;
+
///
/// Gets or sets a flag indicating that the test runner should pause after all tests
/// have run.
diff --git a/src/xunit.v3.runner.common/Loggers/ConsoleRunnerLogger.cs b/src/xunit.v3.runner.common/Loggers/ConsoleRunnerLogger.cs
index 83afe01da..ccb9266f8 100644
--- a/src/xunit.v3.runner.common/Loggers/ConsoleRunnerLogger.cs
+++ b/src/xunit.v3.runner.common/Loggers/ConsoleRunnerLogger.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.IO;
using System.Text.RegularExpressions;
using Xunit.Internal;
@@ -14,13 +15,33 @@ public class ConsoleRunnerLogger : IRunnerLogger
readonly static Regex ansiSgrRegex = new Regex("\\e\\[\\d*(;\\d*)*m");
readonly bool useColors;
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Please use the new overload with the useAnsiColor flag")]
+ public ConsoleRunnerLogger(bool useColors) :
+ this(useColors, useAnsiColor: false, new object())
+ { }
+
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Please use the new overload with the useAnsiColor flag")]
+ public ConsoleRunnerLogger(
+ bool useColors,
+ object lockObject) :
+ this(useColors, useAnsiColor: false, lockObject)
+ { }
+
///
/// Initializes a new instance of the class.
///
/// A flag to indicate whether colors should be used when
/// logging messages.
- public ConsoleRunnerLogger(bool useColors)
- : this(useColors, new object())
+ /// A flag to indicate whether ANSI colors should be
+ /// forced on Windows.
+ public ConsoleRunnerLogger(
+ bool useColors,
+ bool useAnsiColor)
+ : this(useColors, useAnsiColor, new object())
{ }
///
@@ -28,15 +49,21 @@ public ConsoleRunnerLogger(bool useColors)
///
/// A flag to indicate whether colors should be used when
/// logging messages.
+ /// A flag to indicate whether ANSI colors should be
+ /// forced on Windows.
/// The lock object used to prevent console clashes.
public ConsoleRunnerLogger(
bool useColors,
+ bool useAnsiColor,
object lockObject)
{
Guard.ArgumentNotNull(lockObject);
this.useColors = useColors;
LockObject = lockObject;
+
+ if (useAnsiColor)
+ ConsoleHelper.UseAnsiColor();
}
///
@@ -51,7 +78,7 @@ public ConsoleRunnerLogger(bool useColors)
lock (LockObject)
using (SetColor(ConsoleColor.Red))
- WriteLine(Console.Error, message);
+ WriteLine(Console.Out, message);
}
///
diff --git a/src/xunit.v3.runner.common/Parsers/CommandLineParserBase.cs b/src/xunit.v3.runner.common/Parsers/CommandLineParserBase.cs
index 37eaa4ed2..bdab4b991 100644
--- a/src/xunit.v3.runner.common/Parsers/CommandLineParserBase.cs
+++ b/src/xunit.v3.runner.common/Parsers/CommandLineParserBase.cs
@@ -84,6 +84,7 @@ public abstract class CommandLineParserBase
AddParser("pause", OnPause, CommandLineGroup.General, null, "wait for input before running tests");
AddParser("preEnumerateTheories", OnPreEnumerateTheories, CommandLineGroup.General, null, "enable theory pre-enumeration (disabled by default)");
AddParser("stopOnFail", OnStopOnFail, CommandLineGroup.General, null, "stop on first test failure");
+ AddParser("useAnsiColor", OnUseAnsiColor, CommandLineGroup.General, null, "force using ANSI color output on Windows (non-Windows always uses ANSI colors)");
AddParser("wait", OnWait, CommandLineGroup.General, null, "wait for input after completion");
// Filter options
@@ -539,6 +540,12 @@ void OnTraitMinus(KeyValuePair option)
projectAssembly.Configuration.Filters.ExcludedTraits.Add(name, value);
}
+ void OnUseAnsiColor(KeyValuePair option)
+ {
+ GuardNoOptionValue(option);
+ Project.Configuration.UseAnsiColor = true;
+ }
+
void OnWait(KeyValuePair option)
{
GuardNoOptionValue(option);
diff --git a/src/xunit.v3.runner.common/Utility/ConsoleHelper.cs b/src/xunit.v3.runner.common/Utility/ConsoleHelper.cs
index 45ec5fb1e..b152d3a0f 100644
--- a/src/xunit.v3.runner.common/Utility/ConsoleHelper.cs
+++ b/src/xunit.v3.runner.common/Utility/ConsoleHelper.cs
@@ -15,17 +15,17 @@ public static class ConsoleHelper
///
/// Equivalent to ..
///
- public static Action ResetColor { get; }
+ public static Action ResetColor { get; private set; }
///
/// Equivalent to ..
///
- public static Action SetBackgroundColor { get; }
+ public static Action SetBackgroundColor { get; private set; }
///
/// Equivalent to ..
///
- public static Action SetForegroundColor { get; }
+ public static Action SetForegroundColor { get; private set; }
static ConsoleHelper()
{
@@ -106,4 +106,14 @@ static void SetForegroundColorANSI(ConsoleColor c)
static void ResetColorConsole() =>
Console.ResetColor();
+
+ ///
+ /// Force using ANSI color instead of deciding based on OS.
+ ///
+ public static void UseAnsiColor()
+ {
+ ResetColor = ResetColorANSI;
+ SetBackgroundColor = SetBackgroundColorANSI;
+ SetForegroundColor = SetForegroundColorANSI;
+ }
}
diff --git a/src/xunit.v3.runner.console/ConsoleRunner.cs b/src/xunit.v3.runner.console/ConsoleRunner.cs
index 769d20d34..1ef15a3f7 100644
--- a/src/xunit.v3.runner.console/ConsoleRunner.cs
+++ b/src/xunit.v3.runner.console/ConsoleRunner.cs
@@ -66,6 +66,9 @@ public async ValueTask EntryPoint()
}
var project = commandLine.Parse();
+ var useAnsiColor = project.Configuration.UseAnsiColorOrDefault;
+ if (useAnsiColor)
+ ConsoleHelper.UseAnsiColor();
if (project.Assemblies.Count == 0)
throw new ArgumentException("must specify at least one assembly");
@@ -94,7 +97,7 @@ public async ValueTask EntryPoint()
var globalDiagnosticMessages = project.Assemblies.Any(a => a.Configuration.DiagnosticMessagesOrDefault);
globalInternalDiagnosticMessages = project.Assemblies.Any(a => a.Configuration.InternalDiagnosticMessagesOrDefault);
noColor = project.Configuration.NoColorOrDefault;
- logger = new ConsoleRunnerLogger(!noColor, consoleLock);
+ logger = new ConsoleRunnerLogger(!noColor, useAnsiColor, consoleLock);
var globalDiagnosticMessageSink = ConsoleDiagnosticMessageSink.TryCreate(consoleLock, noColor, globalDiagnosticMessages, globalInternalDiagnosticMessages);
var reporter = project.RunnerReporter;
var reporterMessageHandler = await reporter.CreateMessageHandler(logger, globalDiagnosticMessageSink);
diff --git a/src/xunit.v3.runner.inproc.console/ConsoleRunner.cs b/src/xunit.v3.runner.inproc.console/ConsoleRunner.cs
index 2839cf6d9..9a85a3302 100644
--- a/src/xunit.v3.runner.inproc.console/ConsoleRunner.cs
+++ b/src/xunit.v3.runner.inproc.console/ConsoleRunner.cs
@@ -90,6 +90,9 @@ public async ValueTask EntryPoint()
noColor = true;
var project = commandLine.Parse();
+ var useAnsiColor = project.Configuration.UseAnsiColorOrDefault;
+ if (useAnsiColor)
+ ConsoleHelper.UseAnsiColor();
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
@@ -127,7 +130,7 @@ public async ValueTask EntryPoint()
if (!automated)
noColor = project.Configuration.NoColorOrDefault;
- logger = new ConsoleRunnerLogger(!noColor, consoleLock);
+ logger = new ConsoleRunnerLogger(!noColor, useAnsiColor, consoleLock);
_IMessageSink? globalDiagnosticMessageSink =
automated