513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
88 lines
3.5 KiB
C#
88 lines
3.5 KiB
C#
using ZB.MOM.WW.ScadaBridge.CentralUI.ScriptAnalysis;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.ScriptAnalysis;
|
|
|
|
/// <summary>
|
|
/// Regression tests for the <c>SandboxConsoleCapture</c> writer that the Test Run
|
|
/// sandbox installs on <c>Console.Out</c>/<c>Console.Error</c>. A past defect
|
|
/// surfaced an intra-script concurrency hazard: a sandboxed script can fan out
|
|
/// work with <c>Task.WhenAll</c> / <c>Task.Run</c> and every child task inherits
|
|
/// the capture <c>StringWriter</c> via <c>AsyncLocal</c>; <c>StringWriter</c> is
|
|
/// not thread-safe, so concurrent writes could corrupt the buffer. These tests
|
|
/// drive the writer the same way Roslyn-hosted user code does.
|
|
/// </summary>
|
|
public class SandboxConsoleCaptureTests
|
|
{
|
|
/// <summary>
|
|
/// A capture scope shared across <c>Task.WhenAll</c> child
|
|
/// tasks must serialise writes so the resulting transcript contains exactly
|
|
/// the expected number of lines without character-level interleaving.
|
|
/// </summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task BeginCapture_ConcurrentWritesFromTasks_DoNotCorruptBuffer()
|
|
{
|
|
// The static install routes Console.Out through the singleton sandbox
|
|
// capture writer for the test process — this is idempotent and matches
|
|
// the way ScriptAnalysisService bootstraps the sandbox in production.
|
|
var (capture, _) = SandboxConsoleCapture.Install();
|
|
|
|
var buffer = new StringWriter();
|
|
const int taskCount = 32;
|
|
const int linesPerTask = 50;
|
|
const int expectedLines = taskCount * linesPerTask;
|
|
|
|
using (capture.BeginCapture(buffer))
|
|
{
|
|
// AsyncLocal flows the capture scope into each Task.Run, mirroring
|
|
// a sandboxed script doing `await Task.WhenAll(...)` over Tasks
|
|
// that each `Console.WriteLine`.
|
|
var tasks = Enumerable.Range(0, taskCount).Select(i => Task.Run(() =>
|
|
{
|
|
for (var j = 0; j < linesPerTask; j++)
|
|
{
|
|
Console.WriteLine($"task-{i}-line-{j}");
|
|
}
|
|
}));
|
|
await Task.WhenAll(tasks);
|
|
}
|
|
|
|
var captured = buffer.ToString();
|
|
// Without the lock, concurrent StringWriter.WriteLine can drop or
|
|
// interleave characters and produce malformed lines / a wrong count.
|
|
// We assert the exact line count and that every emitted token is
|
|
// present on a line of its own — both fail under the unprotected
|
|
// implementation.
|
|
var lines = captured.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
|
|
Assert.Equal(expectedLines, lines.Length);
|
|
|
|
for (var i = 0; i < taskCount; i++)
|
|
{
|
|
for (var j = 0; j < linesPerTask; j++)
|
|
{
|
|
Assert.Contains($"task-{i}-line-{j}", lines);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sanity check: the most basic capture happy-path still works after the
|
|
/// concurrency lock was introduced.
|
|
/// </summary>
|
|
[Fact]
|
|
public void BeginCapture_SingleThreadedWrites_AreCaptured()
|
|
{
|
|
var (capture, _) = SandboxConsoleCapture.Install();
|
|
var buffer = new StringWriter();
|
|
|
|
using (capture.BeginCapture(buffer))
|
|
{
|
|
Console.WriteLine("hello");
|
|
Console.Write("world");
|
|
}
|
|
|
|
Assert.Contains("hello", buffer.ToString());
|
|
Assert.Contains("world", buffer.ToString());
|
|
}
|
|
}
|