Fix CameraStreamState.Failed compile error (19041 projection omits enum member names — compare by int); fix first-frame proof test (no dispatcher = no bitmap, assert LatestFrame instead)
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using ytLive.Services;
|
||||
|
||||
@@ -9,20 +12,28 @@ public class CameraManagerTests
|
||||
{
|
||||
private readonly List<string> _started;
|
||||
private readonly List<string> _stopped;
|
||||
private readonly VideoFrame? _pumpOnStart;
|
||||
private readonly string? _failOnStart;
|
||||
|
||||
public string DeviceId { get; }
|
||||
public event Action<VideoFrame>? FrameAvailable;
|
||||
public event Action<string>? SourceFailed;
|
||||
|
||||
public FakeFrameSource(string deviceId, List<string> started, List<string> stopped)
|
||||
public FakeFrameSource(string deviceId, List<string> started, List<string> stopped,
|
||||
VideoFrame? pumpOnStart = null, string? failOnStart = null)
|
||||
{
|
||||
DeviceId = deviceId;
|
||||
_started = started;
|
||||
_stopped = stopped;
|
||||
_pumpOnStart = pumpOnStart;
|
||||
_failOnStart = failOnStart;
|
||||
}
|
||||
|
||||
public Task StartAsync()
|
||||
{
|
||||
if (_failOnStart != null) throw new InvalidOperationException(_failOnStart);
|
||||
_started.Add(DeviceId);
|
||||
if (_pumpOnStart != null) FrameAvailable?.Invoke(_pumpOnStart);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -33,29 +44,15 @@ public class CameraManagerTests
|
||||
}
|
||||
|
||||
public void Pump(VideoFrame frame) => FrameAvailable?.Invoke(frame);
|
||||
public void Fail(string message) => SourceFailed?.Invoke(message);
|
||||
}
|
||||
|
||||
private sealed class FailingFrameSource : ICameraFrameSource
|
||||
{
|
||||
public string DeviceId { get; }
|
||||
public event Action<VideoFrame>? FrameAvailable;
|
||||
public bool Stopped;
|
||||
|
||||
public FailingFrameSource(string deviceId) => DeviceId = deviceId;
|
||||
|
||||
public Task StartAsync() => throw new InvalidOperationException("camera in use");
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
Stopped = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Pump(VideoFrame frame) => FrameAvailable?.Invoke(frame);
|
||||
}
|
||||
|
||||
// The refcount/coalescing tests model a camera whose frame pump is driven by
|
||||
// the test after acquire, so they skip the first-frame proof (TimeSpan.Zero);
|
||||
// the proof itself is exercised by the dedicated tests below.
|
||||
private static CameraManager CreateManager(List<string> started, List<string> stopped)
|
||||
=> new(new FakeEnumerator(), id => new FakeFrameSource(id, started, stopped));
|
||||
=> new(new FakeEnumerator(), id => new FakeFrameSource(id, started, stopped),
|
||||
null, TimeSpan.Zero);
|
||||
|
||||
private sealed class FakeEnumerator : ICameraEnumerator
|
||||
{
|
||||
@@ -93,7 +90,8 @@ public class CameraManagerTests
|
||||
FakeFrameSource? captured = null;
|
||||
var manager = new CameraManager(
|
||||
new FakeEnumerator(),
|
||||
id => captured = new FakeFrameSource(id, started, stopped));
|
||||
id => captured = new FakeFrameSource(id, started, stopped),
|
||||
null, TimeSpan.Zero);
|
||||
|
||||
await manager.AcquireAsync("dev1");
|
||||
var frame = new VideoFrame(2, 2, new byte[16]);
|
||||
@@ -105,7 +103,10 @@ public class CameraManagerTests
|
||||
[Fact]
|
||||
public async Task Acquire_FailingCamera_ReturnsFalseAndStops()
|
||||
{
|
||||
var manager = new CameraManager(new FakeEnumerator(), id => new FailingFrameSource(id));
|
||||
var manager = new CameraManager(
|
||||
new FakeEnumerator(),
|
||||
id => new FakeFrameSource(id, new List<string>(), new List<string>(), failOnStart: "camera in use"),
|
||||
null, TimeSpan.Zero);
|
||||
string? failedDevice = null;
|
||||
manager.CameraFailed += (device, _) => failedDevice = device;
|
||||
|
||||
@@ -119,4 +120,67 @@ public class CameraManagerTests
|
||||
var manager = CreateManager(new List<string>(), new List<string>());
|
||||
Assert.False(await manager.AcquireAsync(" "));
|
||||
}
|
||||
|
||||
// ─── First-frame proof (the integration test for this change) ───
|
||||
|
||||
[Fact]
|
||||
public async Task Acquire_SilentCamera_NoFirstFrame_FailsAndSurfacesCameraFailed()
|
||||
{
|
||||
var stopped = new List<string>();
|
||||
var manager = new CameraManager(
|
||||
new FakeEnumerator(),
|
||||
id => new FakeFrameSource(id, new List<string>(), stopped),
|
||||
null, TimeSpan.FromMilliseconds(150));
|
||||
string? failedDevice = null;
|
||||
string? failedMessage = null;
|
||||
manager.CameraFailed += (device, message) =>
|
||||
{
|
||||
failedDevice = device;
|
||||
failedMessage = message;
|
||||
};
|
||||
|
||||
// The reader "starts" fine but never delivers a frame — the exact
|
||||
// silent-empty-box scenario. Must fail, be reported, and be rolled back.
|
||||
Assert.False(await manager.AcquireAsync("dev1"));
|
||||
Assert.Equal("dev1", failedDevice);
|
||||
Assert.Contains("no frames", failedMessage);
|
||||
Assert.Null(manager.GetLatestFrame("dev1"));
|
||||
Assert.Contains("dev1", stopped);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Acquire_FirstFrameProvesAlive_ReturnsTrueWithoutWaitingTimeout()
|
||||
{
|
||||
var manager = new CameraManager(
|
||||
new FakeEnumerator(),
|
||||
id => new FakeFrameSource(id, new List<string>(), new List<string>(),
|
||||
pumpOnStart: new VideoFrame(2, 2, new byte[16])),
|
||||
null, TimeSpan.FromSeconds(10));
|
||||
|
||||
var started = await manager.AcquireAsync("dev1");
|
||||
|
||||
Assert.True(started);
|
||||
Assert.NotNull(manager.GetLatestFrame("dev1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Acquire_SourceFailureAfterStart_SurfacesCameraFailedAndRollsBack()
|
||||
{
|
||||
var stopped = new List<string>();
|
||||
FakeFrameSource? source = null;
|
||||
var manager = new CameraManager(
|
||||
new FakeEnumerator(),
|
||||
id => source = new FakeFrameSource(id, new List<string>(), stopped,
|
||||
pumpOnStart: new VideoFrame(2, 2, new byte[16])),
|
||||
null, TimeSpan.Zero);
|
||||
string? failedMessage = null;
|
||||
manager.CameraFailed += (_, message) => failedMessage = message;
|
||||
|
||||
Assert.True(await manager.AcquireAsync("dev1"));
|
||||
source!.Fail("capture failed (0x8007001F): device not available");
|
||||
|
||||
Assert.Contains("device not available", failedMessage);
|
||||
Assert.Null(manager.GetLatestFrame("dev1"));
|
||||
Assert.Contains("dev1", stopped);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user