Nucleic: Windows Port 1

This commit is contained in:
2026-07-27 22:02:34 -07:00
commit 049ab9bc78
155 changed files with 3568 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
<Project>
<!-- Thin shim: MSBuild only auto-imports Directory.Build.props from ancestor directories,
and the repo layout (docs/WINDOWS_PORT.md §10) keeps the real shared properties in
props/Directory.Build.props. -->
<Import Project="$(MSBuildThisFileDirectory)props\Directory.Build.props" />
</Project>
+48
View File
@@ -0,0 +1,48 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NucleicBroker", "NucleicBroker\NucleicBroker.csproj", "{9DFE1688-402D-4623-9F0F-B2F238F0AE78}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NucleicBroker.Tests", "NucleicBroker.Tests\NucleicBroker.Tests.csproj", "{513C933F-89FF-4918-87AB-E9B70625BE4B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Debug|x64.ActiveCfg = Debug|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Debug|x64.Build.0 = Debug|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Debug|x86.ActiveCfg = Debug|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Debug|x86.Build.0 = Debug|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Release|Any CPU.Build.0 = Release|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Release|x64.ActiveCfg = Release|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Release|x64.Build.0 = Release|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Release|x86.ActiveCfg = Release|Any CPU
{9DFE1688-402D-4623-9F0F-B2F238F0AE78}.Release|x86.Build.0 = Release|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Debug|x64.ActiveCfg = Debug|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Debug|x64.Build.0 = Debug|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Debug|x86.ActiveCfg = Debug|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Debug|x86.Build.0 = Debug|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Release|Any CPU.Build.0 = Release|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Release|x64.ActiveCfg = Release|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Release|x64.Build.0 = Release|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Release|x86.ActiveCfg = Release|Any CPU
{513C933F-89FF-4918-87AB-E9B70625BE4B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
+227
View File
@@ -0,0 +1,227 @@
using System.Text.Json;
using NucleicBroker;
namespace NucleicBroker.Tests;
/// <summary>
/// Contract tests for the §3.3 RPC surface: each test drives BrokerService with raw NDJSON
/// lines (exactly what hostd sends) and asserts on the emitted lines (exactly what hostd's
/// WslcBrokerClient parses). The Swift side's fake-broker tests mirror these — together they
/// pin the contract from both ends (docs/WINDOWS_PORT.md §14).
/// </summary>
public sealed class BrokerServiceTests
{
private readonly FakeWslc wslc = new();
private readonly StringWriter captured = new();
private readonly OutboundWriter outbound;
private readonly BrokerService service;
public BrokerServiceTests()
{
outbound = new OutboundWriter(captured);
service = new BrokerService(wslc, new UnavailableAiProvider(), outbound);
}
/// <summary>Send one request line and return every line emitted once drained.</summary>
private async Task<List<JsonElement>> RoundTrip(params string[] lines)
{
foreach (var line in lines)
await service.HandleLineAsync(line);
await outbound.CompleteAsync();
return captured.ToString()
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => JsonDocument.Parse(l).RootElement.Clone())
.ToList();
}
private static JsonElement Result(JsonElement response)
{
Assert.Equal("2.0", response.GetProperty("jsonrpc").GetString());
Assert.False(response.TryGetProperty("error", out var error), error.ToString());
return response.GetProperty("result");
}
[Fact]
public async Task Hello_ReportsProtocolAndCapabilities()
{
var lines = await RoundTrip("""{"jsonrpc":"2.0","id":1,"method":"hello"}""");
var result = Result(Assert.Single(lines));
Assert.Equal(BrokerService.ProtocolVersion, result.GetProperty("protocol").GetInt32());
Assert.Equal("9.9-test", result.GetProperty("wslcVersion").GetString());
var caps = result.GetProperty("capabilities").EnumerateArray().Select(c => c.GetString()).ToList();
Assert.Contains("proc", caps);
Assert.DoesNotContain("ai", caps); // UnavailableAiProvider
}
[Fact]
public async Task UnknownMethod_ReturnsMethodNotFound()
{
var lines = await RoundTrip("""{"jsonrpc":"2.0","id":7,"method":"nope.nothing"}""");
var error = Assert.Single(lines).GetProperty("error");
Assert.Equal(Rpc.MethodNotFound, error.GetProperty("code").GetInt32());
}
[Fact]
public async Task MalformedLine_ReturnsParseErrorWithNullId()
{
var lines = await RoundTrip("this is not json");
var response = Assert.Single(lines);
Assert.Equal(JsonValueKind.Null, response.GetProperty("id").ValueKind);
Assert.Equal(Rpc.ParseError, response.GetProperty("error").GetProperty("code").GetInt32());
}
[Fact]
public async Task MissingParams_ReturnsInvalidParams()
{
var lines = await RoundTrip("""{"jsonrpc":"2.0","id":8,"method":"session.ensure"}""");
var error = Assert.Single(lines).GetProperty("error");
Assert.Equal(Rpc.InvalidParams, error.GetProperty("code").GetInt32());
}
[Fact]
public async Task SessionEnsure_ForwardsSpecAndReturnsGateway()
{
var lines = await RoundTrip(
"""{"jsonrpc":"2.0","id":2,"method":"session.ensure","params":{"name":"nucleic-dev","dataDir":"C:\\data","cpu":4,"memoryMB":8192}}""");
Assert.Equal("172.28.0.1", Result(Assert.Single(lines)).GetProperty("gateway").GetString());
Assert.Equal(new SessionSpec("nucleic-dev", @"C:\data", 4, 8192), wslc.LastSession);
}
[Fact]
public async Task ImagePull_EmitsProgressNotificationsBeforeResult()
{
var lines = await RoundTrip(
"""{"jsonrpc":"2.0","id":3,"method":"image.pull","params":{"ref":"ghcr.io/abkslm/naros-agent:26.07"}}""");
Assert.Equal(3, lines.Count);
Assert.Equal("image.pullProgress", lines[0].GetProperty("method").GetString());
Assert.Equal("ghcr.io/abkslm/naros-agent:26.07",
lines[0].GetProperty("params").GetProperty("ref").GetString());
Assert.Equal("done", lines[1].GetProperty("params").GetProperty("status").GetString());
Result(lines[2]); // the pull's own success response arrives after its progress
}
[Fact]
public async Task ContainerLifecycle_CreateStartStateStopDelete()
{
var lines = await RoundTrip(
"""{"jsonrpc":"2.0","id":1,"method":"container.create","params":{"name":"nucleic-control-local","image":"img","volumes":[{"host":"C:\\repo","guest":"/work","ro":false}],"env":{"A":"1"},"initArgv":["/sbin/init"]}}""",
"""{"jsonrpc":"2.0","id":2,"method":"container.start","params":{"name":"nucleic-control-local"}}""",
"""{"jsonrpc":"2.0","id":3,"method":"container.state","params":{"name":"nucleic-control-local"}}""",
"""{"jsonrpc":"2.0","id":4,"method":"container.stop","params":{"name":"nucleic-control-local","signal":15,"graceMs":2000}}""",
"""{"jsonrpc":"2.0","id":5,"method":"container.delete","params":{"name":"nucleic-control-local"}}""",
"""{"jsonrpc":"2.0","id":6,"method":"container.state","params":{"name":"nucleic-control-local"}}""");
Assert.Equal(6, lines.Count);
Assert.Equal("running", Result(lines[2]).GetProperty("state").GetString());
Assert.Equal(("nucleic-control-local", 15, 2000), wslc.LastStop);
Assert.Equal("absent", Result(lines[5]).GetProperty("state").GetString());
var create = wslc.LastCreate!;
Assert.Equal("/work", Assert.Single(create.Volumes!).Guest);
Assert.False(Assert.Single(create.Volumes!).ReadOnly);
Assert.Equal("/sbin/init", Assert.Single(create.InitArgv!));
}
[Fact]
public async Task ProcExec_MintsIdAndRoutesStdioEventsInOrder()
{
wslc.ContainerStates["box"] = "running";
await service.HandleLineAsync(
"""{"jsonrpc":"2.0","id":1,"method":"proc.exec","params":{"container":"box","argv":["claude","-p"],"tty":false}}""");
// The facade emits output then exit, from its own (non-broker) thread.
wslc.Events!.ProcOutput(wslc.LastProcId, stderr: false, "hel"u8);
wslc.Events!.ProcOutput(wslc.LastProcId, stderr: false, "lo\n"u8);
wslc.Events!.ProcExited(wslc.LastProcId, 0);
await outbound.CompleteAsync();
var lines = captured.ToString().Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => JsonDocument.Parse(l).RootElement.Clone()).ToList();
var procId = Result(lines[0]).GetProperty("procId").GetInt64();
Assert.Equal(wslc.LastProcId, procId);
var stdout = lines.Where(l =>
l.TryGetProperty("method", out var m) && m.GetString() == "proc.stdout").ToList();
var decoded = string.Concat(stdout.Select(n =>
System.Text.Encoding.UTF8.GetString(
Convert.FromBase64String(n.GetProperty("params").GetProperty("b64").GetString()!))));
Assert.Equal("hello\n", decoded);
// exit is last, and never precedes the output that came before it
Assert.Equal("proc.exit", lines[^1].GetProperty("method").GetString());
Assert.Equal(0, lines[^1].GetProperty("params").GetProperty("code").GetInt32());
}
[Fact]
public async Task ProcStdin_DecodesBase64AndSupportsSignalCloseResize()
{
wslc.ContainerStates["box"] = "running";
await service.HandleLineAsync(
"""{"jsonrpc":"2.0","id":1,"method":"proc.exec","params":{"container":"box","argv":["sh"],"tty":true}}""");
var b64 = Convert.ToBase64String("{\"x\":1}\n"u8.ToArray());
// The trailing literal `} }` pairs are spaced apart: inside a `$$"""…"""` string two
// consecutive closing braces would read as an interpolation delimiter (CS9007), and
// whitespace between JSON braces is insignificant.
var lines = await RoundTrip(
$$"""{"jsonrpc":"2.0","id":2,"method":"proc.stdin","params":{"procId":{{wslc.LastProcId}},"b64":"{{b64}}"} }""",
$$"""{"jsonrpc":"2.0","id":3,"method":"proc.signal","params":{"procId":{{wslc.LastProcId}},"sig":2} }""",
$$"""{"jsonrpc":"2.0","id":4,"method":"proc.resize","params":{"procId":{{wslc.LastProcId}},"cols":120,"rows":40} }""",
$$"""{"jsonrpc":"2.0","id":5,"method":"proc.closeStdin","params":{"procId":{{wslc.LastProcId}} } }""");
Assert.Equal(5, lines.Count); // exec response + 4 above
var proc = wslc.LastProcess!;
Assert.Equal("{\"x\":1}\n", System.Text.Encoding.UTF8.GetString(proc.Stdin.ToArray()));
Assert.Equal([2], proc.Signals);
Assert.Equal((120, 40), proc.LastResize);
Assert.True(proc.StdinClosed);
}
[Fact]
public async Task UnknownProcId_ReturnsFacadeErrorWithKind()
{
var lines = await RoundTrip(
"""{"jsonrpc":"2.0","id":1,"method":"proc.signal","params":{"procId":999,"sig":9}}""");
var error = Assert.Single(lines).GetProperty("error");
Assert.Equal(Rpc.FacadeError, error.GetProperty("code").GetInt32());
Assert.Equal("not_found", error.GetProperty("data").GetProperty("kind").GetString());
}
[Fact]
public async Task FacadeError_SurfacesStructuredKind()
{
wslc.NextError = new WslcError(WslcError.PullFailed, "registry said no");
var lines = await RoundTrip(
"""{"jsonrpc":"2.0","id":1,"method":"image.pull","params":{"ref":"ghcr.io/x/y:1"}}""");
var error = Assert.Single(lines).GetProperty("error");
Assert.Equal("image_pull_failed", error.GetProperty("data").GetProperty("kind").GetString());
Assert.Equal("registry said no", error.GetProperty("message").GetString());
}
[Fact]
public async Task SessionDown_ForwardsAsNotification()
{
wslc.Events!.SessionDown("vm terminated");
await outbound.CompleteAsync();
var lines = await Task.FromResult(captured.ToString()
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => JsonDocument.Parse(l).RootElement.Clone()).ToList());
var note = Assert.Single(lines);
Assert.Equal("session.down", note.GetProperty("method").GetString());
Assert.Equal("vm terminated", note.GetProperty("params").GetProperty("reason").GetString());
}
[Fact]
public async Task AiGenerate_UnavailableProviderReturnsStructuredError()
{
var lines = await RoundTrip(
"""{"jsonrpc":"2.0","id":1,"method":"ai.generate","params":{"prompt":"hi"}}""");
var error = Assert.Single(lines).GetProperty("error");
Assert.Equal("ai_unavailable", error.GetProperty("data").GetProperty("kind").GetString());
}
[Fact]
public async Task ExecInStoppedContainer_ReturnsNotRunning()
{
wslc.ContainerStates["box"] = "stopped";
var lines = await RoundTrip(
"""{"jsonrpc":"2.0","id":1,"method":"proc.exec","params":{"container":"box","argv":["sh"]}}""");
var error = Assert.Single(lines).GetProperty("error");
Assert.Equal("not_running", error.GetProperty("data").GetProperty("kind").GetString());
}
}
+189
View File
@@ -0,0 +1,189 @@
using NucleicBroker;
namespace NucleicBroker.Tests;
/// <summary>
/// In-memory IWslc for the broker contract tests (docs/WINDOWS_PORT.md §14): records calls,
/// serves canned state, and exposes the event sink so tests can emit process output/exit and
/// session-death exactly as the wslc facade would — from arbitrary threads, asynchronously.
/// </summary>
public sealed class FakeWslc : IWslc
{
public IBrokerEvents? Events;
public List<string> Calls = [];
public Dictionary<string, string> ContainerStates = [];
public List<ImageInfo> Images = [];
public List<string> MissingComponents = [];
public string Gateway = "172.28.0.1";
public SessionSpec? LastSession;
public ContainerCreateSpec? LastCreate;
public (string Name, int Signal, int GraceMs)? LastStop;
public FakeProcess? LastProcess;
public long LastProcId;
public Exception? NextError;
public string? WslcVersion => "9.9-test";
public void SetEvents(IBrokerEvents events) => Events = events;
private void Record(string call)
{
Calls.Add(call);
if (NextError is { } error)
{
NextError = null;
throw error;
}
}
public Task<IReadOnlyList<string>> MissingComponentsAsync(CancellationToken ct)
{
Record("components.missing");
return Task.FromResult<IReadOnlyList<string>>(MissingComponents);
}
public Task InstallComponentsAsync(CancellationToken ct)
{
Record("components.install");
Events?.InstallProgress("installing", 50);
Events?.InstallProgress("done", 100);
return Task.CompletedTask;
}
public Task<string> EnsureSessionAsync(SessionSpec spec, CancellationToken ct)
{
Record("session.ensure");
LastSession = spec;
return Task.FromResult(Gateway);
}
public Task TerminateSessionAsync(CancellationToken ct)
{
Record("session.terminate");
return Task.CompletedTask;
}
public Task PullImageAsync(string reference, RegistryAuth? auth, CancellationToken ct)
{
Record($"image.pull:{reference}");
Events?.PullProgress(reference, "downloading", 10, 100);
Events?.PullProgress(reference, "done", 100, 100);
Images.Add(new ImageInfo(reference, "sha256:fake", 1234));
return Task.CompletedTask;
}
public Task<IReadOnlyList<ImageInfo>> ListImagesAsync(CancellationToken ct)
{
Record("image.list");
return Task.FromResult<IReadOnlyList<ImageInfo>>(Images);
}
public Task DeleteImageAsync(string reference, CancellationToken ct)
{
Record($"image.delete:{reference}");
Images.RemoveAll(i => i.Ref == reference);
return Task.CompletedTask;
}
public Task<ImageInfo?> InspectImageAsync(string reference, CancellationToken ct)
{
Record($"image.inspect:{reference}");
return Task.FromResult(Images.FirstOrDefault(i => i.Ref == reference));
}
public Task CreateContainerAsync(ContainerCreateSpec spec, CancellationToken ct)
{
Record($"container.create:{spec.Name}");
LastCreate = spec;
ContainerStates[spec.Name] = "stopped";
return Task.CompletedTask;
}
public Task StartContainerAsync(string name, CancellationToken ct)
{
Record($"container.start:{name}");
if (!ContainerStates.ContainsKey(name))
throw new WslcError(WslcError.NotFound, $"no container named {name}");
ContainerStates[name] = "running";
return Task.CompletedTask;
}
public Task StopContainerAsync(string name, int signal, int graceMs, CancellationToken ct)
{
Record($"container.stop:{name}");
LastStop = (name, signal, graceMs);
ContainerStates[name] = "stopped";
return Task.CompletedTask;
}
public Task DeleteContainerAsync(string name, bool force, CancellationToken ct)
{
Record($"container.delete:{name}");
ContainerStates.Remove(name);
return Task.CompletedTask;
}
public Task<IReadOnlyList<ContainerInfo>> ListContainersAsync(CancellationToken ct)
{
Record("container.list");
return Task.FromResult<IReadOnlyList<ContainerInfo>>(
ContainerStates.Select(kv => new ContainerInfo(kv.Key, "img", kv.Value)).ToList());
}
public Task<string> ContainerStateAsync(string name, CancellationToken ct)
{
Record($"container.state:{name}");
return Task.FromResult(ContainerStates.GetValueOrDefault(name, "absent"));
}
public Task<ContainerStatsInfo?> ContainerStatsAsync(string name, CancellationToken ct)
{
Record($"container.stats:{name}");
return Task.FromResult<ContainerStatsInfo?>(
ContainerStates.GetValueOrDefault(name) == "running"
? new ContainerStatsInfo(111, 222, 333, 0)
: null);
}
public Task<IWslcProcess> ExecAsync(long procId, ProcSpec spec, CancellationToken ct)
{
Record($"proc.exec:{spec.Container}:{string.Join(' ', spec.Argv)}");
if (ContainerStates.GetValueOrDefault(spec.Container) != "running")
throw new WslcError(WslcError.NotRunning, $"container {spec.Container} isn't running");
LastProcId = procId;
LastProcess = new FakeProcess();
return Task.FromResult<IWslcProcess>(LastProcess);
}
public sealed class FakeProcess : IWslcProcess
{
public MemoryStream Stdin = new();
public bool StdinClosed;
public List<int> Signals = [];
public (int Cols, int Rows)? LastResize;
public Task WriteStdinAsync(ReadOnlyMemory<byte> data, CancellationToken ct)
{
Stdin.Write(data.Span);
return Task.CompletedTask;
}
public Task CloseStdinAsync(CancellationToken ct)
{
StdinClosed = true;
return Task.CompletedTask;
}
public Task SignalAsync(int signal, CancellationToken ct)
{
Signals.Add(signal);
return Task.CompletedTask;
}
public Task ResizeAsync(int cols, int rows, CancellationToken ct)
{
LastResize = (cols, rows);
return Task.CompletedTask;
}
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Broker contract tests (docs/WINDOWS_PORT.md §14): drive BrokerService line-by-line
against FakeWslc — no wslc, no Windows, runnable on any CI leg. -->
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../NucleicBroker/NucleicBroker.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,50 @@
using System.Text.Json;
using NucleicBroker;
namespace NucleicBroker.Tests;
public sealed class OutboundWriterTests
{
[Fact]
public async Task PreservesEnqueueOrderAcrossKinds()
{
var captured = new StringWriter();
var writer = new OutboundWriter(captured);
writer.EnqueueJson("""{"a":1}""");
writer.EnqueueProcOutput(1, stderr: false, "one"u8);
writer.EnqueueProcOutput(1, stderr: false, "two"u8);
writer.EnqueueJson("""{"b":2}""");
await writer.CompleteAsync();
var lines = captured.ToString().Split('\n', StringSplitOptions.RemoveEmptyEntries);
Assert.Equal("""{"a":1}""", lines[0]);
Assert.Equal("""{"b":2}""", lines[^1]);
// The two same-stream chunks arrive between the json lines, concatenated in order —
// as ONE notification when the drain saw both queued, as two otherwise. Either way
// the decoded byte stream is identical.
var decoded = string.Concat(lines[1..^1].Select(l =>
{
var note = JsonDocument.Parse(l).RootElement;
Assert.Equal("proc.stdout", note.GetProperty("method").GetString());
return System.Text.Encoding.UTF8.GetString(
Convert.FromBase64String(note.GetProperty("params").GetProperty("b64").GetString()!));
}));
Assert.Equal("onetwo", decoded);
}
[Fact]
public async Task StderrAndStdoutChunksNeverMerge()
{
var captured = new StringWriter();
var writer = new OutboundWriter(captured);
writer.EnqueueProcOutput(1, stderr: false, "out"u8);
writer.EnqueueProcOutput(1, stderr: true, "err"u8);
await writer.CompleteAsync();
var methods = captured.ToString().Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => JsonDocument.Parse(l).RootElement.GetProperty("method").GetString())
.ToList();
Assert.Equal(["proc.stdout", "proc.stderr"], methods);
}
}
Binary file not shown.
@@ -0,0 +1,475 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v9.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v9.0": {
"NucleicBroker.Tests/1.0.0": {
"dependencies": {
"Microsoft.NET.Test.Sdk": "17.12.0",
"nucleic-brokerd": "1.0.0",
"xunit": "2.9.2",
"xunit.runner.visualstudio": "2.8.2",
"nucleic-brokerd.Reference": "1.0.0.0"
},
"runtime": {
"NucleicBroker.Tests.dll": {}
}
},
"Microsoft.CodeCoverage/17.12.0": {
"runtime": {
"lib/netcoreapp3.1/Microsoft.VisualStudio.CodeCoverage.Shim.dll": {
"assemblyVersion": "15.0.0.0",
"fileVersion": "17.1200.524.48002"
}
}
},
"Microsoft.NET.Test.Sdk/17.12.0": {
"dependencies": {
"Microsoft.CodeCoverage": "17.12.0",
"Microsoft.TestPlatform.TestHost": "17.12.0"
}
},
"Microsoft.TestPlatform.ObjectModel/17.12.0": {
"dependencies": {
"System.Reflection.Metadata": "1.6.0"
},
"runtime": {
"lib/netcoreapp3.1/Microsoft.TestPlatform.CoreUtilities.dll": {
"assemblyVersion": "15.0.0.0",
"fileVersion": "17.1200.24.56501"
},
"lib/netcoreapp3.1/Microsoft.TestPlatform.PlatformAbstractions.dll": {
"assemblyVersion": "15.0.0.0",
"fileVersion": "17.1200.24.56501"
},
"lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {
"assemblyVersion": "15.0.0.0",
"fileVersion": "17.1200.24.56501"
}
},
"resources": {
"lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "cs"
},
"lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "cs"
},
"lib/netcoreapp3.1/de/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "de"
},
"lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "de"
},
"lib/netcoreapp3.1/es/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "es"
},
"lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "es"
},
"lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "fr"
},
"lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "fr"
},
"lib/netcoreapp3.1/it/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "it"
},
"lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "it"
},
"lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "ja"
},
"lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "ja"
},
"lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "ko"
},
"lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "ko"
},
"lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "pl"
},
"lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "pl"
},
"lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "pt-BR"
},
"lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "pt-BR"
},
"lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "ru"
},
"lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "ru"
},
"lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "tr"
},
"lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "tr"
},
"lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "zh-Hans"
},
"lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "zh-Hans"
},
"lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
"locale": "zh-Hant"
},
"lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
"locale": "zh-Hant"
}
}
},
"Microsoft.TestPlatform.TestHost/17.12.0": {
"dependencies": {
"Microsoft.TestPlatform.ObjectModel": "17.12.0",
"Newtonsoft.Json": "13.0.1"
},
"runtime": {
"lib/netcoreapp3.1/Microsoft.TestPlatform.CommunicationUtilities.dll": {
"assemblyVersion": "15.0.0.0",
"fileVersion": "17.1200.24.56501"
},
"lib/netcoreapp3.1/Microsoft.TestPlatform.CrossPlatEngine.dll": {
"assemblyVersion": "15.0.0.0",
"fileVersion": "17.1200.24.56501"
},
"lib/netcoreapp3.1/Microsoft.TestPlatform.Utilities.dll": {
"assemblyVersion": "15.0.0.0",
"fileVersion": "17.1200.24.56501"
},
"lib/netcoreapp3.1/Microsoft.VisualStudio.TestPlatform.Common.dll": {
"assemblyVersion": "15.0.0.0",
"fileVersion": "17.1200.24.56501"
},
"lib/netcoreapp3.1/testhost.dll": {
"assemblyVersion": "15.0.0.0",
"fileVersion": "17.1200.24.56501"
}
},
"resources": {
"lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "cs"
},
"lib/netcoreapp3.1/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "cs"
},
"lib/netcoreapp3.1/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "cs"
},
"lib/netcoreapp3.1/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "de"
},
"lib/netcoreapp3.1/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "de"
},
"lib/netcoreapp3.1/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "de"
},
"lib/netcoreapp3.1/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "es"
},
"lib/netcoreapp3.1/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "es"
},
"lib/netcoreapp3.1/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "es"
},
"lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "fr"
},
"lib/netcoreapp3.1/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "fr"
},
"lib/netcoreapp3.1/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "fr"
},
"lib/netcoreapp3.1/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "it"
},
"lib/netcoreapp3.1/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "it"
},
"lib/netcoreapp3.1/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "it"
},
"lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "ja"
},
"lib/netcoreapp3.1/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "ja"
},
"lib/netcoreapp3.1/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "ja"
},
"lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "ko"
},
"lib/netcoreapp3.1/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "ko"
},
"lib/netcoreapp3.1/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "ko"
},
"lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "pl"
},
"lib/netcoreapp3.1/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "pl"
},
"lib/netcoreapp3.1/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "pl"
},
"lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "pt-BR"
},
"lib/netcoreapp3.1/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "pt-BR"
},
"lib/netcoreapp3.1/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "pt-BR"
},
"lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "ru"
},
"lib/netcoreapp3.1/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "ru"
},
"lib/netcoreapp3.1/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "ru"
},
"lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "tr"
},
"lib/netcoreapp3.1/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "tr"
},
"lib/netcoreapp3.1/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "tr"
},
"lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "zh-Hans"
},
"lib/netcoreapp3.1/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "zh-Hans"
},
"lib/netcoreapp3.1/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "zh-Hans"
},
"lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
"locale": "zh-Hant"
},
"lib/netcoreapp3.1/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
"locale": "zh-Hant"
},
"lib/netcoreapp3.1/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
"locale": "zh-Hant"
}
}
},
"Newtonsoft.Json/13.0.1": {
"runtime": {
"lib/netstandard2.0/Newtonsoft.Json.dll": {
"assemblyVersion": "13.0.0.0",
"fileVersion": "13.0.1.25517"
}
}
},
"System.Reflection.Metadata/1.6.0": {},
"xunit/2.9.2": {
"dependencies": {
"xunit.analyzers": "1.16.0",
"xunit.assert": "2.9.2",
"xunit.core": "2.9.2"
}
},
"xunit.abstractions/2.0.3": {
"runtime": {
"lib/netstandard2.0/xunit.abstractions.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "0.0.0.0"
}
}
},
"xunit.analyzers/1.16.0": {},
"xunit.assert/2.9.2": {
"runtime": {
"lib/net6.0/xunit.assert.dll": {
"assemblyVersion": "2.9.2.0",
"fileVersion": "2.9.2.0"
}
}
},
"xunit.core/2.9.2": {
"dependencies": {
"xunit.extensibility.core": "2.9.2",
"xunit.extensibility.execution": "2.9.2"
}
},
"xunit.extensibility.core/2.9.2": {
"dependencies": {
"xunit.abstractions": "2.0.3"
},
"runtime": {
"lib/netstandard1.1/xunit.core.dll": {
"assemblyVersion": "2.9.2.0",
"fileVersion": "2.9.2.0"
}
}
},
"xunit.extensibility.execution/2.9.2": {
"dependencies": {
"xunit.extensibility.core": "2.9.2"
},
"runtime": {
"lib/netstandard1.1/xunit.execution.dotnet.dll": {
"assemblyVersion": "2.9.2.0",
"fileVersion": "2.9.2.0"
}
}
},
"xunit.runner.visualstudio/2.8.2": {},
"nucleic-brokerd/1.0.0": {
"runtime": {
"nucleic-brokerd.dll": {
"fileVersion": "0.0.0.0"
}
}
},
"nucleic-brokerd.Reference/1.0.0.0": {
"runtime": {
"nucleic-brokerd.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
}
}
},
"libraries": {
"NucleicBroker.Tests/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.CodeCoverage/17.12.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==",
"path": "microsoft.codecoverage/17.12.0",
"hashPath": "microsoft.codecoverage.17.12.0.nupkg.sha512"
},
"Microsoft.NET.Test.Sdk/17.12.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==",
"path": "microsoft.net.test.sdk/17.12.0",
"hashPath": "microsoft.net.test.sdk.17.12.0.nupkg.sha512"
},
"Microsoft.TestPlatform.ObjectModel/17.12.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==",
"path": "microsoft.testplatform.objectmodel/17.12.0",
"hashPath": "microsoft.testplatform.objectmodel.17.12.0.nupkg.sha512"
},
"Microsoft.TestPlatform.TestHost/17.12.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==",
"path": "microsoft.testplatform.testhost/17.12.0",
"hashPath": "microsoft.testplatform.testhost.17.12.0.nupkg.sha512"
},
"Newtonsoft.Json/13.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
"path": "newtonsoft.json/13.0.1",
"hashPath": "newtonsoft.json.13.0.1.nupkg.sha512"
},
"System.Reflection.Metadata/1.6.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==",
"path": "system.reflection.metadata/1.6.0",
"hashPath": "system.reflection.metadata.1.6.0.nupkg.sha512"
},
"xunit/2.9.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-7LhFS2N9Z6Xgg8aE5lY95cneYivRMfRI8v+4PATa4S64D5Z/Plkg0qa8dTRHSiGRgVZ/CL2gEfJDE5AUhOX+2Q==",
"path": "xunit/2.9.2",
"hashPath": "xunit.2.9.2.nupkg.sha512"
},
"xunit.abstractions/2.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==",
"path": "xunit.abstractions/2.0.3",
"hashPath": "xunit.abstractions.2.0.3.nupkg.sha512"
},
"xunit.analyzers/1.16.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-hptYM7vGr46GUIgZt21YHO4rfuBAQS2eINbFo16CV/Dqq+24Tp+P5gDCACu1AbFfW4Sp/WRfDPSK8fmUUb8s0Q==",
"path": "xunit.analyzers/1.16.0",
"hashPath": "xunit.analyzers.1.16.0.nupkg.sha512"
},
"xunit.assert/2.9.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-QkNBAQG4pa66cholm28AxijBjrmki98/vsEh4Sx5iplzotvPgpiotcxqJQMRC8d7RV7nIT8ozh97957hDnZwsQ==",
"path": "xunit.assert/2.9.2",
"hashPath": "xunit.assert.2.9.2.nupkg.sha512"
},
"xunit.core/2.9.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-O6RrNSdmZ0xgEn5kT927PNwog5vxTtKrWMihhhrT0Sg9jQ7iBDciYOwzBgP2krBEk5/GBXI18R1lKvmnxGcb4w==",
"path": "xunit.core/2.9.2",
"hashPath": "xunit.core.2.9.2.nupkg.sha512"
},
"xunit.extensibility.core/2.9.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Ol+KlBJz1x8BrdnhN2DeOuLrr1I/cTwtHCggL9BvYqFuVd/TUSzxNT5O0NxCIXth30bsKxgMfdqLTcORtM52yQ==",
"path": "xunit.extensibility.core/2.9.2",
"hashPath": "xunit.extensibility.core.2.9.2.nupkg.sha512"
},
"xunit.extensibility.execution/2.9.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-rKMpq4GsIUIJibXuZoZ8lYp5EpROlnYaRpwu9Zr0sRZXE7JqJfEEbCsUriZqB+ByXCLFBJyjkTRULMdC+U566g==",
"path": "xunit.extensibility.execution/2.9.2",
"hashPath": "xunit.extensibility.execution.2.9.2.nupkg.sha512"
},
"xunit.runner.visualstudio/2.8.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-vm1tbfXhFmjFMUmS4M0J0ASXz3/U5XvXBa6DOQUL3fEz4Vt6YPhv+ESCarx6M6D+9kJkJYZKCNvJMas1+nVfmQ==",
"path": "xunit.runner.visualstudio/2.8.2",
"hashPath": "xunit.runner.visualstudio.2.8.2.nupkg.sha512"
},
"nucleic-brokerd/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"nucleic-brokerd.Reference/1.0.0.0": {
"type": "reference",
"serviceable": false,
"sha512": ""
}
}
}
@@ -0,0 +1,14 @@
{
"runtimeOptions": {
"tfm": "net9.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
},
"configProperties": {
"System.Globalization.Invariant": true,
"System.Globalization.PredefinedCulturesOnly": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}
Binary file not shown.
@@ -0,0 +1,23 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v9.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v9.0": {
"nucleic-brokerd/1.0.0": {
"runtime": {
"nucleic-brokerd.dll": {}
}
}
}
},
"libraries": {
"nucleic-brokerd/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}
@@ -0,0 +1,14 @@
{
"runtimeOptions": {
"tfm": "net9.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
},
"configProperties": {
"System.Globalization.Invariant": true,
"System.Globalization.PredefinedCulturesOnly": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")]

Some files were not shown because too many files have changed in this diff Show More