Nucleic: Windows Port 1
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user