test(fakes): add FakeNdiInterop and FakeFrameClock; feat(discovery): add DiscoveryEvent
Some checks failed
CI / build-and-test (push) Failing after 24s
Some checks failed
CI / build-and-test (push) Failing after 24s
This commit is contained in:
parent
f562303b47
commit
c07a668672
3 changed files with 119 additions and 0 deletions
9
src/TeamsISO.Engine/Discovery/DiscoveryEvent.cs
Normal file
9
src/TeamsISO.Engine/Discovery/DiscoveryEvent.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using TeamsISO.Engine.Domain;
|
||||
|
||||
namespace TeamsISO.Engine.Discovery;
|
||||
|
||||
public abstract record DiscoveryEvent
|
||||
{
|
||||
public sealed record Added(NdiSource Source) : DiscoveryEvent;
|
||||
public sealed record Removed(NdiSource Source) : DiscoveryEvent;
|
||||
}
|
||||
39
src/tests/TeamsISO.Engine.Tests/Fakes/FakeFrameClock.cs
Normal file
39
src/tests/TeamsISO.Engine.Tests/Fakes/FakeFrameClock.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using TeamsISO.Engine.Pipeline;
|
||||
|
||||
namespace TeamsISO.Engine.Tests.Fakes;
|
||||
|
||||
/// <summary>
|
||||
/// Manual-tick clock. Tests advance the clock and trigger the awaiter explicitly.
|
||||
/// </summary>
|
||||
public sealed class FakeFrameClock : IFrameClock
|
||||
{
|
||||
private long _nowTicks;
|
||||
private TaskCompletionSource<bool>? _pendingTick;
|
||||
private readonly object _gate = new();
|
||||
|
||||
public long NowTicks => Interlocked.Read(ref _nowTicks);
|
||||
|
||||
public void Advance(TimeSpan by)
|
||||
{
|
||||
Interlocked.Add(ref _nowTicks, by.Ticks);
|
||||
TaskCompletionSource<bool>? toSignal;
|
||||
lock (_gate)
|
||||
{
|
||||
toSignal = _pendingTick;
|
||||
_pendingTick = null;
|
||||
}
|
||||
toSignal?.TrySetResult(true);
|
||||
}
|
||||
|
||||
public ValueTask<bool> WaitForNextTickAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
TaskCompletionSource<bool> tcs;
|
||||
lock (_gate)
|
||||
{
|
||||
_pendingTick ??= new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
tcs = _pendingTick;
|
||||
}
|
||||
cancellationToken.Register(() => tcs.TrySetResult(false));
|
||||
return new ValueTask<bool>(tcs.Task);
|
||||
}
|
||||
}
|
||||
71
src/tests/TeamsISO.Engine.Tests/Fakes/FakeNdiInterop.cs
Normal file
71
src/tests/TeamsISO.Engine.Tests/Fakes/FakeNdiInterop.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
using System.Collections.Concurrent;
|
||||
using TeamsISO.Engine.Interop;
|
||||
using TeamsISO.Engine.Pipeline;
|
||||
|
||||
namespace TeamsISO.Engine.Tests.Fakes;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory test double for <see cref="INdiInterop"/>. Tests configure source lists and frame
|
||||
/// queues; the fake feeds those into engine code as if a real NDI runtime were present.
|
||||
/// </summary>
|
||||
public sealed class FakeNdiInterop : INdiInterop
|
||||
{
|
||||
public List<string> Sources { get; } = new();
|
||||
public ConcurrentDictionary<string, ConcurrentQueue<RawFrame>> ReceiverFrames { get; } = new();
|
||||
public ConcurrentDictionary<string, List<ProcessedFrame>> SentFrames { get; } = new();
|
||||
public string RuntimeVersion { get; set; } = "6.0.0";
|
||||
public Dictionary<string, int> ReceiverCreatedCount { get; } = new();
|
||||
public Dictionary<string, int> SenderCreatedCount { get; } = new();
|
||||
|
||||
public NdiFindHandle CreateFinder() => new FakeFindHandle();
|
||||
public IReadOnlyList<string> GetCurrentSources(NdiFindHandle finder) => Sources.ToArray();
|
||||
|
||||
public NdiReceiverHandle CreateReceiver(string sourceFullName)
|
||||
{
|
||||
ReceiverCreatedCount[sourceFullName] = ReceiverCreatedCount.GetValueOrDefault(sourceFullName) + 1;
|
||||
ReceiverFrames.GetOrAdd(sourceFullName, _ => new ConcurrentQueue<RawFrame>());
|
||||
return new FakeReceiverHandle(sourceFullName);
|
||||
}
|
||||
|
||||
public RawFrame? CaptureFrame(NdiReceiverHandle receiver, int timeoutMs)
|
||||
{
|
||||
var key = ((FakeReceiverHandle)receiver).Source;
|
||||
if (ReceiverFrames.TryGetValue(key, out var q) && q.TryDequeue(out var frame))
|
||||
return frame;
|
||||
return null; // simulate timeout
|
||||
}
|
||||
|
||||
public NdiSenderHandle CreateSender(string outputName)
|
||||
{
|
||||
SenderCreatedCount[outputName] = SenderCreatedCount.GetValueOrDefault(outputName) + 1;
|
||||
SentFrames.GetOrAdd(outputName, _ => new List<ProcessedFrame>());
|
||||
return new FakeSenderHandle(outputName);
|
||||
}
|
||||
|
||||
public void SendFrame(NdiSenderHandle sender, ProcessedFrame frame)
|
||||
{
|
||||
var key = ((FakeSenderHandle)sender).Output;
|
||||
SentFrames[key].Add(frame);
|
||||
}
|
||||
|
||||
public string GetRuntimeVersion() => RuntimeVersion;
|
||||
|
||||
private sealed class FakeFindHandle : NdiFindHandle
|
||||
{
|
||||
public override void Dispose() { }
|
||||
}
|
||||
|
||||
private sealed class FakeReceiverHandle : NdiReceiverHandle
|
||||
{
|
||||
public string Source { get; }
|
||||
public FakeReceiverHandle(string source) => Source = source;
|
||||
public override void Dispose() { }
|
||||
}
|
||||
|
||||
private sealed class FakeSenderHandle : NdiSenderHandle
|
||||
{
|
||||
public string Output { get; }
|
||||
public FakeSenderHandle(string output) => Output = output;
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue