diff --git a/CliFx.Tests/CliApplicationBuilderTests.cs b/CliFx.Tests/CliApplicationBuilderTests.cs index f27577f..7f02908 100644 --- a/CliFx.Tests/CliApplicationBuilderTests.cs +++ b/CliFx.Tests/CliApplicationBuilderTests.cs @@ -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(); } diff --git a/CliFx.Tests/CliApplicationTests.cs b/CliFx.Tests/CliApplicationTests.cs index 860ad4e..d2cc44a 100644 --- a/CliFx.Tests/CliApplicationTests.cs +++ b/CliFx.Tests/CliApplicationTests.cs @@ -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? 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(); // 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); diff --git a/CliFx.Tests/SystemConsoleTests.cs b/CliFx.Tests/SystemConsoleTests.cs deleted file mode 100644 index 80c1e6b..0000000 --- a/CliFx.Tests/SystemConsoleTests.cs +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/CliFx.Tests/Utilities/ProgressTickerTests.cs b/CliFx.Tests/Utilities/ProgressTickerTests.cs index 1343e80..f831d39 100644 --- a/CliFx.Tests/Utilities/ProgressTickerTests.cs +++ b/CliFx.Tests/Utilities/ProgressTickerTests.cs @@ -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(); } } } \ No newline at end of file diff --git a/CliFx.Tests/VirtualConsoleTests.cs b/CliFx.Tests/VirtualConsoleTests.cs index 5c0b414..09f42eb 100644 --- a/CliFx.Tests/VirtualConsoleTests.cs +++ b/CliFx.Tests/VirtualConsoleTests.cs @@ -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); diff --git a/CliFx/IConsole.cs b/CliFx/IConsole.cs index 8c1087e..2b513cd 100644 --- a/CliFx/IConsole.cs +++ b/CliFx/IConsole.cs @@ -12,7 +12,7 @@ namespace CliFx /// /// Input stream (stdin). /// - TextReader Input { get; } + StreamReader Input { get; } /// /// Whether the input stream is redirected. @@ -22,7 +22,7 @@ namespace CliFx /// /// Output stream (stdout). /// - TextWriter Output { get; } + StreamWriter Output { get; } /// /// Whether the output stream is redirected. @@ -32,7 +32,7 @@ namespace CliFx /// /// Error stream (stderr). /// - TextWriter Error { get; } + StreamWriter Error { get; } /// /// Whether the error stream is redirected. diff --git a/CliFx/SystemConsole.cs b/CliFx/SystemConsole.cs index 6addf77..566e843 100644 --- a/CliFx/SystemConsole.cs +++ b/CliFx/SystemConsole.cs @@ -12,19 +12,19 @@ namespace CliFx private CancellationTokenSource? _cancellationTokenSource; /// - public TextReader Input => Console.In; + public StreamReader Input { get; } /// public bool IsInputRedirected => Console.IsInputRedirected; /// - public TextWriter Output => Console.Out; + public StreamWriter Output { get; } /// public bool IsOutputRedirected => Console.IsOutputRedirected; /// - public TextWriter Error => Console.Error; + public StreamWriter Error { get; } /// public bool IsErrorRedirected => Console.IsErrorRedirected; @@ -43,6 +43,16 @@ namespace CliFx set => Console.BackgroundColor = value; } + /// + /// Initializes an instance of . + /// + 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}; + } + /// public void ResetColor() => Console.ResetColor(); diff --git a/CliFx/VirtualConsole.cs b/CliFx/VirtualConsole.cs index ba82919..bab5e7b 100644 --- a/CliFx/VirtualConsole.cs +++ b/CliFx/VirtualConsole.cs @@ -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. /// - 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(); + /// + /// Initializes an instance of . + /// + 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; + } + + /// + /// Initializes an instance of . + /// + public VirtualConsole() + : this(true) + { + } + + /// + /// Writes raw data to input stream. + /// + public void WriteInputData(byte[] data) => _inputStream.Write(data, 0, data.Length); + + /// + /// Writes text to input stream. + /// + public void WriteInputString(string str) => WriteInputData(Input.CurrentEncoding.GetBytes(str)); + + /// + /// Reads all data written to output stream thus far. + /// + public byte[] ReadOutputData() => _outputStream.ToArray(); + + /// + /// Reads all text written to output stream thus far. + /// + public string ReadOutputString() => Output.Encoding.GetString(ReadOutputData()); + + /// + /// Reads all data written to error stream thus far. + /// + public byte[] ReadErrorData() => _errorStream.ToArray(); + + /// + /// Reads all text written to error stream thus far. + /// + public string ReadErrorString() => Error.Encoding.GetString(ReadErrorData()); + + /// + /// Sends an interrupt signal. + /// + public void Cancel() => _cts.Cancel(); + + /// + /// Sends an interrupt signal after a delay. + /// + public void CancelAfter(TimeSpan delay) => _cts.CancelAfter(delay); + } + + public partial class VirtualConsole : IConsole + { /// - public TextReader Input { get; } + public StreamReader Input { get; } /// public bool IsInputRedirected { get; } /// - public TextWriter Output { get; } + public StreamWriter Output { get; } /// public bool IsOutputRedirected { get; } /// - public TextWriter Error { get; } + public StreamWriter Error { get; } /// public bool IsErrorRedirected { get; } @@ -37,50 +105,6 @@ namespace CliFx /// public ConsoleColor BackgroundColor { get; set; } = ConsoleColor.Black; - /// - /// Initializes an instance of . - /// - 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; - } - - /// - /// Initializes an instance of . - /// - public VirtualConsole(TextReader input, TextWriter output, TextWriter error, - CancellationToken cancellationToken = default) - : this(input, true, output, true, error, true, cancellationToken) - { - } - - /// - /// Initializes an instance of using output stream (stdout) and error stream (stderr). - /// Input stream (stdin) is replaced with a no-op stub. - /// - public VirtualConsole(TextWriter output, TextWriter error, CancellationToken cancellationToken = default) - : this(TextReader.Null, output, error, cancellationToken) - { - } - - /// - /// Initializes an instance of using output stream (stdout). - /// Input stream (stdin) and error stream (stderr) are replaced with no-op stubs. - /// - public VirtualConsole(TextWriter output, CancellationToken cancellationToken = default) - : this(output, TextWriter.Null, cancellationToken) - { - } - /// public void ResetColor() { @@ -89,6 +113,21 @@ namespace CliFx } /// - public CancellationToken GetCancellationToken() => _cancellationToken; + public CancellationToken GetCancellationToken() => _cts.Token; + } + + public partial class VirtualConsole : IDisposable + { + /// + public void Dispose() + { + _inputStream.Dispose(); + _outputStream.Dispose(); + _errorStream.Dispose(); + _cts.Dispose(); + Input.Dispose(); + Output.Dispose(); + Error.Dispose(); + } } } \ No newline at end of file