Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The test runner is **Microsoft.Testing.Platform** with the **xUnit v3** runner (

### Test-suite behavior to know about

- Heavy Roslyn/integration tests share `[Collection(nameof(RoslynServices))]` and run **serially** on purpose: `MSBuildLocator.RegisterDefaults()` and the loader's `AssemblyLoadContext.Resolving` hooks (attached to the process-global Default ALC by `AssemblyLoadContextHook` and never detached) are process-global, not per-`RoslynServices`. The full suite is ~2 minutes.
- Heavy Roslyn/integration tests share `[Collection(nameof(RoslynServices))]` and run **serially** on purpose: the loader's `AssemblyLoadContext.Resolving` hooks (attached to the process-global Default ALC by `AssemblyLoadContextHook` and never detached) are process-global, not per-`RoslynServices`. The full suite is ~2 minutes. (`MSBuildLocator.RegisterDefaults()` is also process-global, but a `[ModuleInitializer]` in `TestAssemblyInitializer` runs it once at assembly load, so it's no longer the reason for the collection — and tests that only needed MSBuildLocator, like `NugetPackageInstallerTests`, can now run in isolation.)
- Some tests spawn `dotnet build` / MSBuild subprocesses (solution/project references) and a few touch the network (NuGet install). These are the slow ones and can occasionally be flaky.
- The inspect-feature integration tests (`InspectorRoundTripTests`, `InspectorCancellationTests`, `RemoteEditorServicesTests`, `InspectorServerProtocolTests`, `RemoteReadEvalPrintLoopTests`) **launch a real hooked child process** — the interactive PrettyPrompt loop itself cannot be driven without a TTY, so `RemoteReadEvalPrintLoopTests` stubs `IPrompt` (like `ReadEvalPrintLoopTests`) and everything below it is real. Two more inspect suites run in-process without a child: `InspectorEngineTests` hosts the real engine + Roslyn inside the test process, and `InspectorTransportTests` exercises the real OS pipe/socket transport (note: the Windows pipe uses zero-byte buffers, so a write rendezvouses with the peer's read — keep the read pending while writing).

Expand Down
20 changes: 20 additions & 0 deletions CSharpRepl.Services/IConsoleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

using System;
using System.Threading.Tasks;
using PrettyPrompt.Consoles;
using PrettyPrompt.Highlighting;
using Spectre.Console;
Expand All @@ -20,6 +22,13 @@ public interface IConsoleService
// The underlying Spectre console that provides e.g. color coded / wrapped output.
protected IAnsiConsole Ansi { get; }

/// <summary>
/// Whether the console is an interactive terminal. False when output is redirected (piped, <c>--eval</c>,
/// captured by a tool), where cursor movement and live displays (e.g. status spinners) can't render and
/// must degrade to plain text. Mirrors <c>!Console.IsOutputRedirected</c>; overridable so it can be faked in tests.
/// </summary>
bool IsInteractive => !Console.IsOutputRedirected;

/// <summary>Width, in characters, of the console buffer — for layout/wrapping math.</summary>
int BufferWidth => PrettyPromptConsole.BufferWidth;

Expand All @@ -36,6 +45,17 @@ public interface IConsoleService
void Write(string text) => Ansi.Write(text);
void Write(FormattedString text) => PrettyPromptConsole.Write(text);

/// <summary>Writes a line of Spectre.Console markup. During a live display (e.g. a status spinner) it renders above the live region.</summary>
void WriteMarkupLine(string markup) => Ansi.MarkupLine(markup);

/// <summary>
/// Runs <paramref name="action"/> while displaying an animated status spinner labelled <paramref name="status"/>
/// (Spectre markup), returning the action's result. Output written to this console during the action - e.g. via
/// <see cref="WriteMarkupLine"/> - is rendered above the live spinner and remains after it disappears.
/// </summary>
Task<T> RunWithStatusAsync<T>(string status, Spinner spinner, string color, Func<Task<T>> action)
=> Ansi.Status().Spinner(spinner).SpinnerStyle(Style.Parse(color)).StartAsync(status, _ => action());

void WriteLine(string text) => Ansi.WriteLine(text);
void WriteLine() => Ansi.WriteLine();
void WriteLine(FormattedString text) => PrettyPromptConsole.WriteLine(text);
Expand Down
Loading
Loading