Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: .NET

on: [push]
on: [push, pull_request]

jobs:
build:
Expand Down Expand Up @@ -39,6 +39,12 @@ jobs:
7.0.x
8.0.x
9.0.x
10.0.x

- name: Setup native build dependencies
run: |
sudo apt-get update
sudo apt-get install --yes clang mono-runtime zlib1g-dev

- name: Setup NuGet package reference
run: |
Expand All @@ -48,11 +54,9 @@ jobs:
#-----------------------------------------------------------------------
# Build

- name: Build
run: dotnet build -p:Configuration=Release -p:Platform="Any CPU" -p:RestoreNoCache=True -p:BuildIdentifier=${GITHUB_RUN_NUMBER} FlashCap.sln

- name: Build NuGet packages
run: dotnet pack -p:Configuration=Release -p:Platform="Any CPU" -p:BuildIdentifier=${GITHUB_RUN_NUMBER} -o artifacts FlashCap.sln
- name: Build and verify
run: node scripts/test-all.mjs
timeout-minutes: 30

#-----------------------------------------------------------------------
# Test
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="RelaxVersioner" Version="3.13.0" PrivateAssets="All" />
<PackageReference Include="RelaxVersioner" Version="3.24.0" PrivateAssets="All" />
</ItemGroup>

<ItemGroup Condition="'$(Configuration)' == 'Release'">
Expand Down
190 changes: 190 additions & 0 deletions FlashCap.Core.Tests/AsyncSourceReaderStateTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
////////////////////////////////////////////////////////////////////////////
//
// FlashCap - Independent camera capture library.
// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
//
// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
//
////////////////////////////////////////////////////////////////////////////

using FlashCap.Internal.MediaFoundation;
using NUnit.Framework;
using System;
using System.Threading.Tasks;

namespace FlashCap.Core.Tests;

[TestFixture]
public sealed class AsyncSourceReaderStateTests
{
[Test]
public void StartRequestsFirstSampleOnlyOnce()
{
var requests = 0;
var state = new AsyncSourceReaderState(() => requests++, () => { });

Assert.That(state.Start(), Is.True);
Assert.That(state.Start(), Is.True);

Assert.That(requests, Is.EqualTo(1));
Assert.That(state.Completion.IsCompleted, Is.False);
Assert.That(state.StopRequested.IsCompleted, Is.False);
}

[Test]
public async Task StopBeforeStartCompletesWithoutFlushing()
{
var flushes = 0;
var state = new AsyncSourceReaderState(() => { }, () => flushes++);

state.Stop();
state.Stop();

Assert.That(state.Completion.IsCompletedSuccessfully, Is.True);
await state.Completion;
Assert.That(flushes, Is.EqualTo(0));
Assert.That(state.Start(), Is.False);
Assert.That(state.EnterCallback(out var processSample), Is.False);
Assert.That(processSample, Is.False);
}

[Test]
public async Task RunningStateRequestsSamplesAndCompletesAfterFlush()
{
var requests = 0;
var flushes = 0;
var state = new AsyncSourceReaderState(() => requests++, () => flushes++);

Assert.That(state.Start(), Is.True);
Assert.That(state.EnterCallback(out var processSample), Is.True);
Assert.That(processSample, Is.True);
state.ExitCallback(null, true);
state.Stop();

Assert.That(requests, Is.EqualTo(2));
Assert.That(flushes, Is.EqualTo(1));
Assert.That(state.Completion.IsCompleted, Is.False);

state.OnFlushed();
Assert.That(state.Completion.IsCompletedSuccessfully, Is.True);
await state.Completion;

state.Stop();
Assert.That(flushes, Is.EqualTo(1));
}

[Test]
public async Task CompletionWaitsForActiveCallbackAfterFlush()
{
var state = new AsyncSourceReaderState(() => { }, () => { });
Assert.That(state.Start(), Is.True);
Assert.That(state.EnterCallback(out var processSample), Is.True);
Assert.That(processSample, Is.True);

state.Stop();
state.OnFlushed();

Assert.That(state.Completion.IsCompleted, Is.False);

state.ExitCallback(null, false);
Assert.That(state.Completion.IsCompletedSuccessfully, Is.True);
await state.Completion;
}

[Test]
public async Task CallbackFailureRequestsStopAndFaultsAfterFlush()
{
var failure = new InvalidOperationException("Callback failed.");
var state = new AsyncSourceReaderState(() => { }, () => { });
Assert.That(state.Start(), Is.True);
Assert.That(state.EnterCallback(out _), Is.True);

state.ExitCallback(failure, false);

Assert.That(state.StopRequested.IsCompletedSuccessfully, Is.True);
await state.StopRequested;
Assert.That(state.Completion.IsCompleted, Is.False);

state.Stop();
state.OnFlushed();

Assert.That(state.Completion.IsFaulted, Is.True);
var thrown = Assert.ThrowsAsync<InvalidOperationException>(
() => state.Completion);
Assert.That(thrown, Is.SameAs(failure));
}

[Test]
public async Task RequestingNextSampleFailureRequestsStop()
{
var requests = 0;
var failure = new InvalidOperationException("Request failed.");
var state = new AsyncSourceReaderState(
() =>
{
if (++requests == 2)
{
throw failure;
}
},
() => { });
Assert.That(state.Start(), Is.True);
Assert.That(state.EnterCallback(out _), Is.True);

state.ExitCallback(null, true);

Assert.That(state.StopRequested.IsCompletedSuccessfully, Is.True);
await state.StopRequested;
Assert.That(requests, Is.EqualTo(2));

state.Stop();
state.OnFlushed();
Assert.That(state.Completion.IsFaulted, Is.True);
var thrown = Assert.ThrowsAsync<InvalidOperationException>(
() => state.Completion);
Assert.That(thrown, Is.SameAs(failure));
}

[Test]
public void StartPropagatesRequestFailureAndFaultsCompletion()
{
var failure = new InvalidOperationException("Startup request failed.");
var state = new AsyncSourceReaderState(() => throw failure, () => { });

var thrown = Assert.Throws<InvalidOperationException>(() => state.Start());
Assert.That(thrown, Is.SameAs(failure));

Assert.That(state.Completion.IsFaulted, Is.True);
thrown = Assert.ThrowsAsync<InvalidOperationException>(
() => state.Completion);
Assert.That(thrown, Is.SameAs(failure));
Assert.That(state.StopRequested.IsCompleted, Is.False);
}

[Test]
public void FlushFailureIsReportedAndFaultsCompletion()
{
var failure = new InvalidOperationException("Flush failed.");
var state = new AsyncSourceReaderState(() => { }, () => throw failure);
Assert.That(state.Start(), Is.True);

state.Stop();

Assert.That(state.FlushFailure, Is.SameAs(failure));
Assert.That(state.Completion.IsFaulted, Is.True);
var thrown = Assert.ThrowsAsync<InvalidOperationException>(
() => state.Completion);
Assert.That(thrown, Is.SameAs(failure));
}

[Test]
public void ExitCallbackRequiresActiveCallback()
{
var state = new AsyncSourceReaderState(() => { }, () => { });

var exception = Assert.Throws<InvalidOperationException>(() =>
state.ExitCallback(null, false));

Assert.That(exception!.Message, Is.EqualTo("No asynchronous Source Reader callback is active."));
}
}
21 changes: 21 additions & 0 deletions FlashCap.Core.Tests/FlashCap.Core.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<AssemblyName>FlashCap.Core.Tests</AssemblyName>
<IsTestProject>true</IsTestProject>
<IsPackable>false</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableNUnitRunner>true</EnableNUnitRunner>
<OutputType>Exe</OutputType>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageReference Include="NUnit" Version="4.6.1" />
<PackageReference Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.14.0" PrivateAssets="All" />
<ProjectReference Include="../FlashCap.Core/FlashCap.Core.csproj" />
</ItemGroup>

</Project>
Loading
Loading