Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TestConsole currently only captures stdOut, it should capture stdErr as well #1732

Open
sudhz opened this issue Jan 13, 2025 · 5 comments
Open
Labels
area-Testing-Framework Spectre.Console testing framework for developers. feature needs triage

Comments

@sudhz
Copy link

sudhz commented Jan 13, 2025

Problem

The TestConsole class captures only the standard output (stdout) by default. It uses a StringWriter to capture the console output.

How to fix

To capture both stdout and stderr, you would need to modify the TestConsole class to handle both streams.

Proposed solution

namespace Spectre.Console.Testing;

/// <summary>
/// A testable console.
/// </summary>
public sealed class TestConsole : IAnsiConsole, IDisposable
{
    private readonly IAnsiConsole _console;
    private readonly StringWriter _stdoutWriter;
    private readonly StringWriter _stderrWriter;
    private IAnsiConsoleCursor? _cursor;

    /// <inheritdoc/>
    public Profile Profile => _console.Profile;

    /// <inheritdoc/>
    public IExclusivityMode ExclusivityMode => _console.ExclusivityMode;

    /// <summary>
    /// Gets the console input.
    /// </summary>
    public TestConsoleInput Input { get; }

    /// <inheritdoc/>
    public RenderPipeline Pipeline => _console.Pipeline;

    /// <inheritdoc/>
    public IAnsiConsoleCursor Cursor => _cursor ?? _console.Cursor;

    /// <inheritdoc/>
    IAnsiConsoleInput IAnsiConsole.Input => Input;

    /// <summary>
    /// Gets the console output.
    /// </summary>
    public string Output => _stdoutWriter.ToString();

    /// <summary>
    /// Gets the console error output.
    /// </summary>
    public string ErrorOutput => _stderrWriter.ToString();

    /// <summary>
    /// Gets the console output lines.
    /// </summary>
    public IReadOnlyList<string> Lines => Output.NormalizeLineEndings().TrimEnd('\n').Split(new char[] { '\n' });

    /// <summary>
    /// Gets or sets a value indicating whether or not VT/ANSI sequences
    /// should be emitted to the console.
    /// </summary>
    public bool EmitAnsiSequences { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="TestConsole"/> class.
    /// </summary>
    public TestConsole()
    {
        _stdoutWriter = new StringWriter();
        _stderrWriter = new StringWriter();
        _cursor = new NoopCursor();

        Input = new TestConsoleInput();
        EmitAnsiSequences = false;

        _console = AnsiConsole.Create(new AnsiConsoleSettings
        {
            Ansi = AnsiSupport.Yes,
            ColorSystem = (ColorSystemSupport)ColorSystem.TrueColor,
            Out = new AnsiConsoleOutput(_stdoutWriter),
            Error = new AnsiConsoleOutput(_stderrWriter),
            Interactive = InteractionSupport.No,
            ExclusivityMode = new NoopExclusivityMode(),
            Enrichment = new ProfileEnrichment
            {
                UseDefaultEnrichers = false,
            },
        });

        _console.Profile.Width = 80;
        _console.Profile.Height = 24;
        _console.Profile.Capabilities.Ansi = true;
        _console.Profile.Capabilities.Unicode = true;
    }

    /// <inheritdoc/>
    public void Dispose()
    {
        _stdoutWriter.Dispose();
        _stderrWriter.Dispose();
    }

    /// <inheritdoc/>
    public void Clear(bool home)
    {
        _console.Clear(home);
    }

    /// <inheritdoc/>
    public void Write(IRenderable renderable)
    {
        if (EmitAnsiSequences)
        {
            _console.Write(renderable);
        }
        else
        {
            foreach (var segment in renderable.GetSegments(this))
            {
                if (segment.IsControlCode)
                {
                    continue;
                }

                Profile.Out.Writer.Write(segment.Text);
            }
        }
    }

    internal void SetCursor(IAnsiConsoleCursor? cursor)
    {
        _cursor = cursor;
    }
}

Please upvote 👍 this issue if you are interested in it.

@github-project-automation github-project-automation bot moved this to Todo 🕑 in Spectre Console Jan 13, 2025
@sudhz sudhz closed this as not planned Won't fix, can't repro, duplicate, stale Jan 13, 2025
@github-project-automation github-project-automation bot moved this from Todo 🕑 to Done 🚀 in Spectre Console Jan 13, 2025
@FrankRay78
Copy link
Contributor

FrankRay78 commented Jan 29, 2025

Why did you close this @sudhz ?

I'm reading Command Line Interface Guidelines and it says the following:

Image

I came across this issue wondering if/how the TestConsole handles stderr.

It seems that it doesn't, but probably should.

Tell me if I'm mistaken.

@FrankRay78 FrankRay78 reopened this Jan 29, 2025
@github-project-automation github-project-automation bot moved this from Done 🚀 to In Progress 👨‍💻 in Spectre Console Jan 29, 2025
@patriksvensson
Copy link
Contributor

@FrankRay78 They closed it because TestConsole supports stderr (see ErrorOutput property).

@FrankRay78
Copy link
Contributor

@patriksvensson I don't see ErrorOutput (or anything similar) on either TestConsole or CommandAppTester - have I missed something? My repo is sync'd with upstream.

@FrankRay78 FrankRay78 added the area-Testing-Framework Spectre.Console testing framework for developers. label Jan 30, 2025
@patriksvensson
Copy link
Contributor

I've might be mistaken

@FrankRay78
Copy link
Contributor

FrankRay78 commented Jan 30, 2025

@patriksvensson I don't know this as well as you, but where my interested started was trying to unit test the provided exception handler outputs the message to stderr, not stdout. I've searched and I don't find anywhere where stderr is explicitly used for non-expected outputs. eg. Option missing required value etc. There also seems a GNU consensus that --help goes to stdout when explicitly requested, but stderr if say the app is run without a default command and without a command being set see here. Is it fair to say that stdout is used for basically all of spectre.console output? That's what I'm wondering.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-Testing-Framework Spectre.Console testing framework for developers. feature needs triage
Projects
Status: In Progress 👨‍💻
Development

No branches or pull requests

3 participants