mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Expose raw streams in IConsole to allow writing/reading binary data
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.IO;
|
||||
using CliFx.Tests.TestCommands;
|
||||
|
||||
namespace CliFx.Tests
|
||||
@@ -27,7 +26,7 @@ namespace CliFx.Tests
|
||||
.UseExecutableName("test")
|
||||
.UseVersionText("test")
|
||||
.UseDescription("test")
|
||||
.UseConsole(new VirtualConsole(TextWriter.Null))
|
||||
.UseConsole(new VirtualConsole())
|
||||
.UseTypeActivator(Activator.CreateInstance)
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -320,8 +320,7 @@ namespace CliFx.Tests
|
||||
string? expectedStdOut = null)
|
||||
{
|
||||
// Arrange
|
||||
await using var stdOutStream = new StringWriter();
|
||||
var console = new VirtualConsole(stdOutStream);
|
||||
using var console = new VirtualConsole();
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommands(commandTypes)
|
||||
@@ -333,7 +332,7 @@ namespace CliFx.Tests
|
||||
|
||||
// Act
|
||||
var exitCode = await application.RunAsync(commandLineArguments, environmentVariables);
|
||||
var stdOut = stdOutStream.ToString().Trim();
|
||||
var stdOut = console.ReadOutputString().Trim();
|
||||
|
||||
// Assert
|
||||
exitCode.Should().Be(0);
|
||||
@@ -354,8 +353,7 @@ namespace CliFx.Tests
|
||||
int? expectedExitCode = null)
|
||||
{
|
||||
// Arrange
|
||||
await using var stdErrStream = new StringWriter();
|
||||
var console = new VirtualConsole(TextWriter.Null, stdErrStream);
|
||||
using var console = new VirtualConsole();
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommands(commandTypes)
|
||||
@@ -367,19 +365,19 @@ namespace CliFx.Tests
|
||||
|
||||
// Act
|
||||
var exitCode = await application.RunAsync(commandLineArguments, environmentVariables);
|
||||
var stderr = stdErrStream.ToString().Trim();
|
||||
var stdErr = console.ReadErrorString().Trim();
|
||||
|
||||
// Assert
|
||||
exitCode.Should().NotBe(0);
|
||||
stderr.Should().NotBeNullOrWhiteSpace();
|
||||
stdErr.Should().NotBeNullOrWhiteSpace();
|
||||
|
||||
if (expectedExitCode != null)
|
||||
exitCode.Should().Be(expectedExitCode);
|
||||
|
||||
if (expectedStdErr != null)
|
||||
stderr.Should().Be(expectedStdErr);
|
||||
stdErr.Should().Be(expectedStdErr);
|
||||
|
||||
Console.WriteLine(stderr);
|
||||
Console.WriteLine(stdErr);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GetTestCases_RunAsync_Help))]
|
||||
@@ -389,8 +387,7 @@ namespace CliFx.Tests
|
||||
IReadOnlyList<string>? expectedSubstrings = null)
|
||||
{
|
||||
// Arrange
|
||||
await using var stdOutStream = new StringWriter();
|
||||
var console = new VirtualConsole(stdOutStream);
|
||||
using var console = new VirtualConsole();
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommands(commandTypes)
|
||||
@@ -404,7 +401,7 @@ namespace CliFx.Tests
|
||||
|
||||
// Act
|
||||
var exitCode = await application.RunAsync(commandLineArguments, environmentVariables);
|
||||
var stdOut = stdOutStream.ToString().Trim();
|
||||
var stdOut = console.ReadOutputString().Trim();
|
||||
|
||||
// Assert
|
||||
exitCode.Should().Be(0);
|
||||
@@ -420,11 +417,7 @@ namespace CliFx.Tests
|
||||
public async Task RunAsync_Cancellation_Test()
|
||||
{
|
||||
// Arrange
|
||||
using var cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
await using var stdOutStream = new StringWriter();
|
||||
await using var stdErrStream = new StringWriter();
|
||||
var console = new VirtualConsole(stdOutStream, stdErrStream, cancellationTokenSource.Token);
|
||||
using var console = new VirtualConsole();
|
||||
|
||||
var application = new CliApplicationBuilder()
|
||||
.AddCommand(typeof(CancellableCommand))
|
||||
@@ -435,10 +428,11 @@ namespace CliFx.Tests
|
||||
var environmentVariables = new Dictionary<string, string>();
|
||||
|
||||
// Act
|
||||
cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(0.2));
|
||||
console.CancelAfter(TimeSpan.FromSeconds(0.2));
|
||||
|
||||
var exitCode = await application.RunAsync(commandLineArguments, environmentVariables);
|
||||
var stdOut = stdOutStream.ToString().Trim();
|
||||
var stdErr = stdErrStream.ToString().Trim();
|
||||
var stdOut = console.ReadOutputString().Trim();
|
||||
var stdErr = console.ReadErrorString().Trim();
|
||||
|
||||
// Assert
|
||||
exitCode.Should().NotBe(0);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace CliFx.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class SystemConsoleTests
|
||||
{
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// Reset console color so it doesn't carry on into the next tests
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
[Test(Description = "Must be in sync with system console")]
|
||||
public void Smoke_Test()
|
||||
{
|
||||
// Arrange
|
||||
var console = new SystemConsole();
|
||||
|
||||
// Act
|
||||
console.ResetColor();
|
||||
console.ForegroundColor = ConsoleColor.DarkMagenta;
|
||||
console.BackgroundColor = ConsoleColor.DarkMagenta;
|
||||
|
||||
// Assert
|
||||
console.Input.Should().BeSameAs(Console.In);
|
||||
console.IsInputRedirected.Should().Be(Console.IsInputRedirected);
|
||||
console.Output.Should().BeSameAs(Console.Out);
|
||||
console.IsOutputRedirected.Should().Be(Console.IsOutputRedirected);
|
||||
console.Error.Should().BeSameAs(Console.Error);
|
||||
console.IsErrorRedirected.Should().Be(Console.IsErrorRedirected);
|
||||
console.ForegroundColor.Should().Be(Console.ForegroundColor);
|
||||
console.BackgroundColor.Should().Be(Console.BackgroundColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using CliFx.Utilities;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
@@ -14,31 +12,25 @@ namespace CliFx.Tests.Utilities
|
||||
public void Report_Test()
|
||||
{
|
||||
// Arrange
|
||||
var formatProvider = CultureInfo.InvariantCulture;
|
||||
|
||||
using var stdout = new StringWriter(formatProvider);
|
||||
|
||||
var console = new VirtualConsole(TextReader.Null, false, stdout, false, TextWriter.Null, false);
|
||||
using var console = new VirtualConsole(false);
|
||||
var ticker = console.CreateProgressTicker();
|
||||
|
||||
var progressValues = Enumerable.Range(0, 100).Select(p => p / 100.0).ToArray();
|
||||
var progressStringValues = progressValues.Select(p => p.ToString("P2", formatProvider)).ToArray();
|
||||
var progressStringValues = progressValues.Select(p => p.ToString("P2")).ToArray();
|
||||
|
||||
// Act
|
||||
foreach (var progress in progressValues)
|
||||
ticker.Report(progress);
|
||||
|
||||
// Assert
|
||||
stdout.ToString().Should().ContainAll(progressStringValues);
|
||||
console.ReadOutputString().Should().ContainAll(progressStringValues);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Report_Redirected_Test()
|
||||
{
|
||||
// Arrange
|
||||
using var stdout = new StringWriter();
|
||||
|
||||
var console = new VirtualConsole(stdout);
|
||||
using var console = new VirtualConsole();
|
||||
var ticker = console.CreateProgressTicker();
|
||||
|
||||
var progressValues = Enumerable.Range(0, 100).Select(p => p / 100.0).ToArray();
|
||||
@@ -48,7 +40,7 @@ namespace CliFx.Tests.Utilities
|
||||
ticker.Report(progress);
|
||||
|
||||
// Assert
|
||||
stdout.ToString().Should().BeEmpty();
|
||||
console.ReadOutputString().Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
@@ -12,11 +11,8 @@ namespace CliFx.Tests
|
||||
public void Smoke_Test()
|
||||
{
|
||||
// Arrange
|
||||
using var stdin = new StringReader("hello world");
|
||||
using var stdout = new StringWriter();
|
||||
using var stderr = new StringWriter();
|
||||
|
||||
var console = new VirtualConsole(stdin, stdout, stderr);
|
||||
using var console = new VirtualConsole();
|
||||
console.WriteInputString("hello world");
|
||||
|
||||
// Act
|
||||
console.ResetColor();
|
||||
@@ -24,13 +20,10 @@ namespace CliFx.Tests
|
||||
console.BackgroundColor = ConsoleColor.DarkMagenta;
|
||||
|
||||
// Assert
|
||||
console.Input.Should().BeSameAs(stdin);
|
||||
console.Input.Should().NotBeSameAs(Console.In);
|
||||
console.IsInputRedirected.Should().BeTrue();
|
||||
console.Output.Should().BeSameAs(stdout);
|
||||
console.Output.Should().NotBeSameAs(Console.Out);
|
||||
console.IsOutputRedirected.Should().BeTrue();
|
||||
console.Error.Should().BeSameAs(stderr);
|
||||
console.Error.Should().NotBeSameAs(Console.Error);
|
||||
console.IsErrorRedirected.Should().BeTrue();
|
||||
console.ForegroundColor.Should().NotBe(Console.ForegroundColor);
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace CliFx
|
||||
/// <summary>
|
||||
/// Input stream (stdin).
|
||||
/// </summary>
|
||||
TextReader Input { get; }
|
||||
StreamReader Input { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the input stream is redirected.
|
||||
@@ -22,7 +22,7 @@ namespace CliFx
|
||||
/// <summary>
|
||||
/// Output stream (stdout).
|
||||
/// </summary>
|
||||
TextWriter Output { get; }
|
||||
StreamWriter Output { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the output stream is redirected.
|
||||
@@ -32,7 +32,7 @@ namespace CliFx
|
||||
/// <summary>
|
||||
/// Error stream (stderr).
|
||||
/// </summary>
|
||||
TextWriter Error { get; }
|
||||
StreamWriter Error { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the error stream is redirected.
|
||||
|
||||
@@ -12,19 +12,19 @@ namespace CliFx
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
/// <inheritdoc />
|
||||
public TextReader Input => Console.In;
|
||||
public StreamReader Input { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsInputRedirected => Console.IsInputRedirected;
|
||||
|
||||
/// <inheritdoc />
|
||||
public TextWriter Output => Console.Out;
|
||||
public StreamWriter Output { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsOutputRedirected => Console.IsOutputRedirected;
|
||||
|
||||
/// <inheritdoc />
|
||||
public TextWriter Error => Console.Error;
|
||||
public StreamWriter Error { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsErrorRedirected => Console.IsErrorRedirected;
|
||||
@@ -43,6 +43,16 @@ namespace CliFx
|
||||
set => Console.BackgroundColor = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="SystemConsole"/>.
|
||||
/// </summary>
|
||||
public SystemConsole()
|
||||
{
|
||||
Input = new StreamReader(Console.OpenStandardInput(), Console.InputEncoding, false);
|
||||
Output = new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding) {AutoFlush = true};
|
||||
Error = new StreamWriter(Console.OpenStandardError(), Console.OutputEncoding) {AutoFlush = true};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetColor() => Console.ResetColor();
|
||||
|
||||
|
||||
@@ -9,24 +9,92 @@ namespace CliFx
|
||||
/// Does not leak to system console in any way.
|
||||
/// Use this class as a substitute for system console when running tests.
|
||||
/// </summary>
|
||||
public class VirtualConsole : IConsole
|
||||
public partial class VirtualConsole
|
||||
{
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
private readonly MemoryStream _inputStream = new MemoryStream();
|
||||
private readonly MemoryStream _outputStream = new MemoryStream();
|
||||
private readonly MemoryStream _errorStream = new MemoryStream();
|
||||
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="VirtualConsole"/>.
|
||||
/// </summary>
|
||||
public VirtualConsole(bool isRedirected)
|
||||
{
|
||||
Input = new StreamReader(_inputStream, Console.InputEncoding, false);
|
||||
Output = new StreamWriter(_outputStream, Console.OutputEncoding) {AutoFlush = true};
|
||||
Error = new StreamWriter(_errorStream, Console.OutputEncoding) {AutoFlush = true};
|
||||
|
||||
IsInputRedirected = isRedirected;
|
||||
IsOutputRedirected = isRedirected;
|
||||
IsErrorRedirected = isRedirected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="VirtualConsole"/>.
|
||||
/// </summary>
|
||||
public VirtualConsole()
|
||||
: this(true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes raw data to input stream.
|
||||
/// </summary>
|
||||
public void WriteInputData(byte[] data) => _inputStream.Write(data, 0, data.Length);
|
||||
|
||||
/// <summary>
|
||||
/// Writes text to input stream.
|
||||
/// </summary>
|
||||
public void WriteInputString(string str) => WriteInputData(Input.CurrentEncoding.GetBytes(str));
|
||||
|
||||
/// <summary>
|
||||
/// Reads all data written to output stream thus far.
|
||||
/// </summary>
|
||||
public byte[] ReadOutputData() => _outputStream.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Reads all text written to output stream thus far.
|
||||
/// </summary>
|
||||
public string ReadOutputString() => Output.Encoding.GetString(ReadOutputData());
|
||||
|
||||
/// <summary>
|
||||
/// Reads all data written to error stream thus far.
|
||||
/// </summary>
|
||||
public byte[] ReadErrorData() => _errorStream.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Reads all text written to error stream thus far.
|
||||
/// </summary>
|
||||
public string ReadErrorString() => Error.Encoding.GetString(ReadErrorData());
|
||||
|
||||
/// <summary>
|
||||
/// Sends an interrupt signal.
|
||||
/// </summary>
|
||||
public void Cancel() => _cts.Cancel();
|
||||
|
||||
/// <summary>
|
||||
/// Sends an interrupt signal after a delay.
|
||||
/// </summary>
|
||||
public void CancelAfter(TimeSpan delay) => _cts.CancelAfter(delay);
|
||||
}
|
||||
|
||||
public partial class VirtualConsole : IConsole
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public TextReader Input { get; }
|
||||
public StreamReader Input { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsInputRedirected { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TextWriter Output { get; }
|
||||
public StreamWriter Output { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsOutputRedirected { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TextWriter Error { get; }
|
||||
public StreamWriter Error { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsErrorRedirected { get; }
|
||||
@@ -37,50 +105,6 @@ namespace CliFx
|
||||
/// <inheritdoc />
|
||||
public ConsoleColor BackgroundColor { get; set; } = ConsoleColor.Black;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="VirtualConsole"/>.
|
||||
/// </summary>
|
||||
public VirtualConsole(TextReader input, bool isInputRedirected,
|
||||
TextWriter output, bool isOutputRedirected,
|
||||
TextWriter error, bool isErrorRedirected,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Input = input;
|
||||
IsInputRedirected = isInputRedirected;
|
||||
Output = output;
|
||||
IsOutputRedirected = isOutputRedirected;
|
||||
Error = error;
|
||||
IsErrorRedirected = isErrorRedirected;
|
||||
_cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="VirtualConsole"/>.
|
||||
/// </summary>
|
||||
public VirtualConsole(TextReader input, TextWriter output, TextWriter error,
|
||||
CancellationToken cancellationToken = default)
|
||||
: this(input, true, output, true, error, true, cancellationToken)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="VirtualConsole"/> using output stream (stdout) and error stream (stderr).
|
||||
/// Input stream (stdin) is replaced with a no-op stub.
|
||||
/// </summary>
|
||||
public VirtualConsole(TextWriter output, TextWriter error, CancellationToken cancellationToken = default)
|
||||
: this(TextReader.Null, output, error, cancellationToken)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of <see cref="VirtualConsole"/> using output stream (stdout).
|
||||
/// Input stream (stdin) and error stream (stderr) are replaced with no-op stubs.
|
||||
/// </summary>
|
||||
public VirtualConsole(TextWriter output, CancellationToken cancellationToken = default)
|
||||
: this(output, TextWriter.Null, cancellationToken)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetColor()
|
||||
{
|
||||
@@ -89,6 +113,21 @@ namespace CliFx
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CancellationToken GetCancellationToken() => _cancellationToken;
|
||||
public CancellationToken GetCancellationToken() => _cts.Token;
|
||||
}
|
||||
|
||||
public partial class VirtualConsole : IDisposable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_inputStream.Dispose();
|
||||
_outputStream.Dispose();
|
||||
_errorStream.Dispose();
|
||||
_cts.Dispose();
|
||||
Input.Dispose();
|
||||
Output.Dispose();
|
||||
Error.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user