diff --git a/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs b/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs
index 6717f757c9a..d93eb6448f6 100644
--- a/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs
+++ b/src/Aspire.Cli/Interaction/ConsoleInteractionService.cs
@@ -286,10 +286,10 @@ public void WriteConsoleLog(string message, int? lineNumber = null, string? type
var style = isErrorMessage ? s_errorMessageStyle
: type switch
{
- "waiting" => s_waitingMessageStyle,
- "running" => s_infoMessageStyle,
- "exitCode" => s_exitCodeMessageStyle,
- "failedToStart" => s_errorMessageStyle,
+ ConsoleLogTypes.Waiting => s_waitingMessageStyle,
+ ConsoleLogTypes.Running => s_infoMessageStyle,
+ ConsoleLogTypes.ExitCode => s_exitCodeMessageStyle,
+ ConsoleLogTypes.FailedToStart => s_errorMessageStyle,
_ => s_infoMessageStyle
};
@@ -302,11 +302,11 @@ public void DisplaySuccess(string message, bool allowMarkup = false)
DisplayMessage(KnownEmojis.CheckMark, message, allowMarkup);
}
- public void DisplayLines(IEnumerable<(string Stream, string Line)> lines)
+ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines)
{
foreach (var (stream, line) in lines)
{
- if (stream == "stdout")
+ if (stream == OutputLineStream.StdOut)
{
MessageConsole.MarkupLineInterpolated($"{line.EscapeMarkup()}");
}
diff --git a/src/Aspire.Cli/Interaction/ConsoleLogTypes.cs b/src/Aspire.Cli/Interaction/ConsoleLogTypes.cs
new file mode 100644
index 00000000000..d89093eedb6
--- /dev/null
+++ b/src/Aspire.Cli/Interaction/ConsoleLogTypes.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Aspire.Cli.Interaction;
+
+///
+/// Known semantic types for console log messages emitted directly by the CLI.
+///
+internal static class ConsoleLogTypes
+{
+ public const string Waiting = "waiting";
+ public const string Running = "running";
+ public const string ExitCode = "exitCode";
+ public const string FailedToStart = "failedToStart";
+}
diff --git a/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs b/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs
index e26db83e942..c6060ec1496 100644
--- a/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs
+++ b/src/Aspire.Cli/Interaction/ExtensionInteractionService.cs
@@ -342,9 +342,11 @@ public void DisplayDashboardUrls(DashboardUrlsState dashboardUrls)
Debug.Assert(result);
}
- public void DisplayLines(IEnumerable<(string Stream, string Line)> lines)
+ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines)
{
- var result = _extensionTaskChannel.Writer.TryWrite(() => Backchannel.DisplayLinesAsync(lines.Select(line => new DisplayLineState(line.Stream.RemoveSpectreFormatting(), line.Line.RemoveSpectreFormatting())), _cancellationToken));
+ var result = _extensionTaskChannel.Writer.TryWrite(() => Backchannel.DisplayLinesAsync(lines.Select(line => new DisplayLineState(
+ line.Stream == OutputLineStream.StdOut ? "stdout" : "stderr",
+ line.Line.RemoveSpectreFormatting())), _cancellationToken));
Debug.Assert(result);
_consoleInteractionService.DisplayLines(lines);
}
diff --git a/src/Aspire.Cli/Interaction/IInteractionService.cs b/src/Aspire.Cli/Interaction/IInteractionService.cs
index b3765c4bc59..0b2711e4adb 100644
--- a/src/Aspire.Cli/Interaction/IInteractionService.cs
+++ b/src/Aspire.Cli/Interaction/IInteractionService.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Cli.Backchannel;
+using Aspire.Cli.Utils;
using Spectre.Console;
using Spectre.Console.Rendering;
@@ -25,7 +26,7 @@ internal interface IInteractionService
void DisplayMarkupLine(string markup);
void DisplaySuccess(string message, bool allowMarkup = false);
void DisplaySubtleMessage(string message, bool allowMarkup = false);
- void DisplayLines(IEnumerable<(string Stream, string Line)> lines);
+ void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines);
void DisplayRenderable(IRenderable renderable);
Task DisplayLiveAsync(IRenderable initialRenderable, Func, Task> callback);
void DisplayCancellationMessage();
@@ -39,5 +40,7 @@ internal interface IInteractionService
ConsoleOutput Console { get; set; }
void DisplayVersionUpdateNotification(string newerVersion, string? updateCommand = null);
+ // The semantic type is stringly-typed because some values originate from backchannel payloads.
+ // Use ConsoleLogTypes for CLI-defined values.
void WriteConsoleLog(string message, int? lineNumber = null, string? type = null, bool isErrorMessage = false);
}
diff --git a/src/Aspire.Cli/Projects/DotNetAppHostProject.cs b/src/Aspire.Cli/Projects/DotNetAppHostProject.cs
index b500532975b..068be414192 100644
--- a/src/Aspire.Cli/Projects/DotNetAppHostProject.cs
+++ b/src/Aspire.Cli/Projects/DotNetAppHostProject.cs
@@ -308,8 +308,8 @@ public async Task RunAsync(AppHostProjectContext context, CancellationToken
"AppHost",
(stream, line) => _interactionService.WriteConsoleLog(
line,
- type: "running",
- isErrorMessage: stream == "stderr"));
+ type: ConsoleLogTypes.Running,
+ isErrorMessage: stream == OutputLineStream.StdErr));
context.OutputCollector = runOutputCollector;
// Signal that build/preparation is complete
diff --git a/src/Aspire.Cli/Projects/GuestAppHostProject.cs b/src/Aspire.Cli/Projects/GuestAppHostProject.cs
index 7597f93d2fa..b9c9e49bc69 100644
--- a/src/Aspire.Cli/Projects/GuestAppHostProject.cs
+++ b/src/Aspire.Cli/Projects/GuestAppHostProject.cs
@@ -506,8 +506,8 @@ await GenerateCodeViaRpcAsync(
{
launcher = _guestRuntime.CreateDefaultLauncher((stream, line) => _interactionService.WriteConsoleLog(
line,
- type: "running",
- isErrorMessage: stream == "stderr"));
+ type: ConsoleLogTypes.Running,
+ isErrorMessage: stream == OutputLineStream.StdErr));
}
// Start guest apphost - it will connect to AppHost server, define resources.
@@ -527,7 +527,7 @@ await GenerateCodeViaRpcAsync(
{
_logger.LogError("{Language} apphost exited with code {ExitCode}", DisplayName, guestExitCode);
- // Display the output (same pattern as DotNetCliRunner)
+ // Replay buffered output only when the process failed before live streaming began.
if (guestOutput is { HasLiveOutputCallback: false })
{
_interactionService.DisplayLines(guestOutput.GetLines());
diff --git a/src/Aspire.Cli/Projects/GuestRuntime.cs b/src/Aspire.Cli/Projects/GuestRuntime.cs
index 71c09f7886e..7337ee3b70e 100644
--- a/src/Aspire.Cli/Projects/GuestRuntime.cs
+++ b/src/Aspire.Cli/Projects/GuestRuntime.cs
@@ -150,8 +150,10 @@ public GuestRuntime(RuntimeSpec spec, ILogger logger, Func? com
///
/// Creates the default process-based launcher for this runtime.
+ /// Pass a callback only for interactive run flows that should stream output live.
+ /// Install and publish flows leave this and replay buffered output later if needed.
///
- public ProcessGuestLauncher CreateDefaultLauncher(Action? liveOutputCallback = null) => new(_spec.Language, _logger, _commandResolver, liveOutputCallback);
+ public ProcessGuestLauncher CreateDefaultLauncher(Action? liveOutputCallback = null) => new(_spec.Language, _logger, _commandResolver, liveOutputCallback);
///
/// Replaces placeholders in command arguments with actual values.
diff --git a/src/Aspire.Cli/Projects/ProcessGuestLauncher.cs b/src/Aspire.Cli/Projects/ProcessGuestLauncher.cs
index 9d6de7bc190..df3eae2bacd 100644
--- a/src/Aspire.Cli/Projects/ProcessGuestLauncher.cs
+++ b/src/Aspire.Cli/Projects/ProcessGuestLauncher.cs
@@ -15,9 +15,9 @@ internal sealed class ProcessGuestLauncher : IGuestProcessLauncher
private readonly string _language;
private readonly ILogger _logger;
private readonly Func _commandResolver;
- private readonly Action? _liveOutputCallback;
+ private readonly Action? _liveOutputCallback;
- public ProcessGuestLauncher(string language, ILogger logger, Func? commandResolver = null, Action? liveOutputCallback = null)
+ public ProcessGuestLauncher(string language, ILogger logger, Func? commandResolver = null, Action? liveOutputCallback = null)
{
_language = language;
_logger = logger;
@@ -72,6 +72,7 @@ public ProcessGuestLauncher(string language, ILogger logger, Func _lines = new(10000); // 10k lines.
+ private readonly CircularBuffer<(OutputLineStream Stream, string Line)> _lines = new(10000); // 10k lines.
private readonly object _lock = new object();
private readonly FileLoggerProvider? _fileLogger;
private readonly string _category;
- private readonly Action? _liveOutputCallback;
+ private readonly Action? _liveOutputCallback;
///
/// Creates an OutputCollector that only buffers output in memory.
@@ -26,8 +32,11 @@ public OutputCollector() : this(null, "AppHost")
///
/// Optional file logger for writing output to disk.
/// Category for log entries (e.g., "Build", "AppHost").
- /// Optional callback invoked immediately when a line is appended.
- public OutputCollector(FileLoggerProvider? fileLogger, string category = "AppHost", Action? liveOutputCallback = null)
+ ///
+ /// Optional callback used by interactive run flows that need lines as they arrive.
+ /// Leave this for buffer-only flows that replay output later on failure.
+ ///
+ public OutputCollector(FileLoggerProvider? fileLogger, string category = "AppHost", Action? liveOutputCallback = null)
{
_fileLogger = fileLogger;
_category = category;
@@ -38,15 +47,15 @@ public OutputCollector(FileLoggerProvider? fileLogger, string category = "AppHos
public void AppendOutput(string line)
{
- AppendLine("stdout", line);
+ AppendLine(OutputLineStream.StdOut, line);
}
public void AppendError(string line)
{
- AppendLine("stderr", line);
+ AppendLine(OutputLineStream.StdErr, line);
}
- public IEnumerable<(string Stream, string Line)> GetLines()
+ public IEnumerable<(OutputLineStream Stream, string Line)> GetLines()
{
lock (_lock)
{
@@ -54,12 +63,12 @@ public void AppendError(string line)
}
}
- private void AppendLine(string stream, string line)
+ private void AppendLine(OutputLineStream stream, string line)
{
lock (_lock)
{
_lines.Add((stream, line));
- _fileLogger?.WriteLog(DateTimeOffset.UtcNow, stream == "stderr" ? LogLevel.Error : LogLevel.Information, _category, line);
+ _fileLogger?.WriteLog(DateTimeOffset.UtcNow, stream == OutputLineStream.StdErr ? LogLevel.Error : LogLevel.Information, _category, line);
}
_liveOutputCallback?.Invoke(stream, line);
diff --git a/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs
index ca386eee71e..d671a48055d 100644
--- a/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs
+++ b/tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs
@@ -1574,7 +1574,7 @@ public Task> PromptForSelectionsAsync(string promptText, IEn
public void DisplayError(string errorMessage) { }
public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false) { }
public void DisplaySuccess(string message, bool allowMarkup = false) { }
- public void DisplayLines(IEnumerable<(string Stream, string Line)> lines) { }
+ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines) { }
public void DisplayCancellationMessage() { }
public Task ConfirmAsync(string promptText, bool defaultValue = true, CancellationToken cancellationToken = default) => Task.FromResult(true);
public Task PromptForFilePathAsync(string promptText, string? defaultValue = null, Func? validator = null, bool directory = false, bool required = false, CancellationToken cancellationToken = default)
diff --git a/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs b/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs
index 4e10044b760..a3e046d8e77 100644
--- a/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs
+++ b/tests/Aspire.Cli.Tests/Commands/PublishCommandPromptingIntegrationTests.cs
@@ -953,7 +953,7 @@ public Task ConfirmAsync(string promptText, bool defaultValue = true, Canc
public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false) { }
public void DisplaySuccess(string message, bool allowMarkup = false) { }
public void DisplaySubtleMessage(string message, bool allowMarkup = false) { }
- public void DisplayLines(IEnumerable<(string Stream, string Line)> lines) { }
+ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines) { }
public void DisplayCancellationMessage() { }
public void DisplayEmptyLine() { }
public void DisplayPlainText(string text) { }
diff --git a/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs b/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs
index 3b8e52d32f4..6c8d9d1acc5 100644
--- a/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs
+++ b/tests/Aspire.Cli.Tests/Commands/UpdateCommandTests.cs
@@ -11,6 +11,7 @@
using Aspire.Cli.Resources;
using Aspire.Cli.Tests.TestServices;
using Aspire.Cli.Tests.Utils;
+using Aspire.Cli.Utils;
using Microsoft.Extensions.DependencyInjection;
using Spectre.Console;
using Spectre.Console.Rendering;
@@ -1062,7 +1063,7 @@ public int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, stri
public void DisplayMarkupLine(string markup) => _innerService.DisplayMarkupLine(markup);
public void DisplaySuccess(string message, bool allowMarkup = false) => _innerService.DisplaySuccess(message, allowMarkup);
public void DisplaySubtleMessage(string message, bool allowMarkup = false) => _innerService.DisplaySubtleMessage(message, allowMarkup);
- public void DisplayLines(IEnumerable<(string Stream, string Line)> lines) => _innerService.DisplayLines(lines);
+ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines) => _innerService.DisplayLines(lines);
public void DisplayCancellationMessage()
{
OnCancellationMessageDisplayed?.Invoke();
diff --git a/tests/Aspire.Cli.Tests/Interaction/ConsoleInteractionServiceTests.cs b/tests/Aspire.Cli.Tests/Interaction/ConsoleInteractionServiceTests.cs
index 72055c4232f..50d85907190 100644
--- a/tests/Aspire.Cli.Tests/Interaction/ConsoleInteractionServiceTests.cs
+++ b/tests/Aspire.Cli.Tests/Interaction/ConsoleInteractionServiceTests.cs
@@ -112,8 +112,8 @@ public void DisplayLines_WithMarkupCharacters_DoesNotCauseMarkupParsingError()
var interactionService = CreateInteractionService(console, executionContext);
var lines = new[]
{
- ("stdout", "Command output with brackets"),
- ("stderr", "Error output with [square] brackets")
+ (OutputLineStream.StdOut, "Command output with brackets"),
+ (OutputLineStream.StdErr, "Error output with [square] brackets")
};
// Act - this should not throw an exception due to markup parsing
diff --git a/tests/Aspire.Cli.Tests/Projects/ExtensionGuestLauncherTests.cs b/tests/Aspire.Cli.Tests/Projects/ExtensionGuestLauncherTests.cs
index dace11a8043..52670fb8571 100644
--- a/tests/Aspire.Cli.Tests/Projects/ExtensionGuestLauncherTests.cs
+++ b/tests/Aspire.Cli.Tests/Projects/ExtensionGuestLauncherTests.cs
@@ -4,6 +4,7 @@
using Aspire.Cli.Backchannel;
using Aspire.Cli.Interaction;
using Aspire.Cli.Projects;
+using Aspire.Cli.Utils;
namespace Aspire.Cli.Tests.Projects;
@@ -165,7 +166,7 @@ public Task LaunchAppHostAsync(string projectFile, List arguments, List<
public void DisplayError(string errorMessage) => throw new NotImplementedException();
public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false) => throw new NotImplementedException();
public void DisplaySuccess(string message, bool allowMarkup = false) => throw new NotImplementedException();
- public void DisplayLines(IEnumerable<(string Stream, string Line)> lines) => throw new NotImplementedException();
+ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines) => throw new NotImplementedException();
public void DisplayCancellationMessage() => throw new NotImplementedException();
public Task ConfirmAsync(string promptText, bool defaultValue = true, CancellationToken cancellationToken = default) => throw new NotImplementedException();
public void DisplaySubtleMessage(string message, bool allowMarkup = false) => throw new NotImplementedException();
diff --git a/tests/Aspire.Cli.Tests/Projects/GuestRuntimeTests.cs b/tests/Aspire.Cli.Tests/Projects/GuestRuntimeTests.cs
index 973377b17d0..9a25ea74d78 100644
--- a/tests/Aspire.Cli.Tests/Projects/GuestRuntimeTests.cs
+++ b/tests/Aspire.Cli.Tests/Projects/GuestRuntimeTests.cs
@@ -319,7 +319,7 @@ public async Task InstallDependenciesAsync_WhenNpmIsMissing_ReturnsNodeInstallMe
output.GetLines(),
line =>
{
- Assert.Equal("stderr", line.Stream);
+ Assert.Equal(OutputLineStream.StdErr, line.Stream);
Assert.Equal("npm is not installed or not found in PATH. Please install Node.js and try again.", line.Line);
});
}
@@ -354,7 +354,7 @@ public async Task RunAsync_WhenNpxIsMissing_ReturnsNodeInstallMessage()
resolvedOutput.GetLines(),
line =>
{
- Assert.Equal("stderr", line.Stream);
+ Assert.Equal(OutputLineStream.StdErr, line.Stream);
Assert.Equal("npx is not installed or not found in PATH. Please install Node.js and try again.", line.Line);
});
}
diff --git a/tests/Aspire.Cli.Tests/Projects/ProcessGuestLauncherTests.cs b/tests/Aspire.Cli.Tests/Projects/ProcessGuestLauncherTests.cs
index 6a177d21e9e..a01bbc50c66 100644
--- a/tests/Aspire.Cli.Tests/Projects/ProcessGuestLauncherTests.cs
+++ b/tests/Aspire.Cli.Tests/Projects/ProcessGuestLauncherTests.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Cli.Projects;
+using Aspire.Cli.Utils;
using Microsoft.Extensions.Logging.Abstractions;
namespace Aspire.Cli.Tests.Projects;
@@ -12,7 +13,7 @@ public class ProcessGuestLauncherTests
public async Task LaunchAsync_ForwardsStdoutToLiveCallback()
{
// Arrange
- var forwardedLines = new List<(string Stream, string Line)>();
+ var forwardedLines = new List<(OutputLineStream Stream, string Line)>();
var launcher = new ProcessGuestLauncher(
"typescript",
NullLogger.Instance,
@@ -29,6 +30,6 @@ public async Task LaunchAsync_ForwardsStdoutToLiveCallback()
// Assert
Assert.Equal(0, exitCode);
Assert.NotNull(output);
- Assert.Contains(forwardedLines, line => line.Stream == "stdout" && !string.IsNullOrWhiteSpace(line.Line));
+ Assert.Contains(forwardedLines, line => line.Stream == OutputLineStream.StdOut && !string.IsNullOrWhiteSpace(line.Line));
}
}
diff --git a/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs b/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs
index 7433f9d9510..3bf653368cc 100644
--- a/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs
+++ b/tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs
@@ -481,7 +481,7 @@ public void ShowStatus(string message, Action work, KnownEmoji? emoji = null, bo
public void DisplaySuccess(string message, bool allowMarkup = false) { }
public void DisplayError(string message) { }
public void DisplayMessage(KnownEmoji emoji, string message, bool allowMarkup = false) { }
- public void DisplayLines(IEnumerable<(string Stream, string Line)> lines) { }
+ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines) { }
public void DisplayCancellationMessage() { }
public int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, string appHostHostingVersion) => 0;
public void DisplayPlainText(string text) { }
diff --git a/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs b/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs
index aa3bc81bc9b..a27e9ae7c6c 100644
--- a/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs
+++ b/tests/Aspire.Cli.Tests/TestServices/TestExtensionInteractionService.cs
@@ -3,6 +3,7 @@
using Aspire.Cli.Backchannel;
using Aspire.Cli.Interaction;
+using Aspire.Cli.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Spectre.Console;
@@ -112,7 +113,7 @@ public void WriteDebugSessionMessage(string message, bool stdout, string? textSt
{
}
- public void DisplayLines(IEnumerable<(string Stream, string Line)> lines)
+ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines)
{
}
diff --git a/tests/Aspire.Cli.Tests/TestServices/TestInteractionService.cs b/tests/Aspire.Cli.Tests/TestServices/TestInteractionService.cs
index 7bf0f3c190e..befce116ddf 100644
--- a/tests/Aspire.Cli.Tests/TestServices/TestInteractionService.cs
+++ b/tests/Aspire.Cli.Tests/TestServices/TestInteractionService.cs
@@ -4,6 +4,7 @@
using System.Collections;
using Aspire.Cli.Backchannel;
using Aspire.Cli.Interaction;
+using Aspire.Cli.Utils;
using Spectre.Console;
using Spectre.Console.Rendering;
@@ -153,7 +154,7 @@ public void DisplaySuccess(string message, bool allowMarkup = false)
{
}
- public void DisplayLines(IEnumerable<(string Stream, string Line)> lines)
+ public void DisplayLines(IEnumerable<(OutputLineStream Stream, string Line)> lines)
{
}
diff --git a/tests/Aspire.Cli.Tests/Utils/OutputCollectorTests.cs b/tests/Aspire.Cli.Tests/Utils/OutputCollectorTests.cs
index 7a1d23a49b8..d16efe2de02 100644
--- a/tests/Aspire.Cli.Tests/Utils/OutputCollectorTests.cs
+++ b/tests/Aspire.Cli.Tests/Utils/OutputCollectorTests.cs
@@ -44,8 +44,8 @@ public async Task OutputCollector_ThreadSafety_MultipleThreadsAddingLines()
Assert.Equal(threadCount * linesPerThread, lines.Count);
// Check that we have both stdout and stderr entries
- var stdoutLines = lines.Where(l => l.Stream == "stdout").ToList();
- var stderrLines = lines.Where(l => l.Stream == "stderr").ToList();
+ var stdoutLines = lines.Where(l => l.Stream == OutputLineStream.StdOut).ToList();
+ var stderrLines = lines.Where(l => l.Stream == OutputLineStream.StdErr).ToList();
Assert.Equal(threadCount * linesPerThread / 2, stdoutLines.Count);
Assert.Equal(threadCount * linesPerThread / 2, stderrLines.Count);
@@ -65,7 +65,7 @@ public void OutputCollector_GetLines_ReturnsSnapshotNotLiveReference()
// Assert - Snapshot should not be affected by subsequent additions
Assert.Single(snapshot);
Assert.Equal("initial line", snapshot[0].Line);
- Assert.Equal("stdout", snapshot[0].Stream);
+ Assert.Equal(OutputLineStream.StdOut, snapshot[0].Stream);
// New call should include the additional line
var newSnapshot = collector.GetLines().ToList();
@@ -107,7 +107,7 @@ public async Task OutputCollector_ConcurrentReadWrite_ShouldNotCrash()
public void OutputCollector_LiveCallback_ReceivesStdoutAndStderr()
{
// Arrange
- var forwardedLines = new List<(string Stream, string Line)>();
+ var forwardedLines = new List<(OutputLineStream Stream, string Line)>();
var collector = new OutputCollector(fileLogger: null, liveOutputCallback: (stream, line) => forwardedLines.Add((stream, line)));
// Act
@@ -117,7 +117,7 @@ public void OutputCollector_LiveCallback_ReceivesStdoutAndStderr()
// Assert
Assert.True(collector.HasLiveOutputCallback);
Assert.Equal(
- [("stdout", "hello"), ("stderr", "oops")],
+ [(OutputLineStream.StdOut, "hello"), (OutputLineStream.StdErr, "oops")],
forwardedLines);
}
}