Stabilize recording coordinator timing tests
PR and Push Build/Test / build-and-test (push) Failing after 9m40s

This commit is contained in:
2026-06-11 15:42:05 +02:00
parent 1f57cb98b7
commit 86e3efa3f6
@@ -15,7 +15,7 @@ namespace MeetingAssistant.Tests;
public sealed class RecordingCoordinatorTests public sealed class RecordingCoordinatorTests
{ {
private static async Task WaitUntilAsync(Func<bool> condition) private static async Task WaitUntilAsync(Func<bool> condition, string? timeoutMessage = null)
{ {
var deadline = DateTimeOffset.UtcNow.AddSeconds(15); var deadline = DateTimeOffset.UtcNow.AddSeconds(15);
while (DateTimeOffset.UtcNow < deadline) while (DateTimeOffset.UtcNow < deadline)
@@ -28,7 +28,7 @@ public sealed class RecordingCoordinatorTests
await Task.Delay(25); await Task.Delay(25);
} }
throw new TimeoutException("Condition was not met."); throw new TimeoutException(timeoutMessage ?? "Condition was not met.");
} }
private static async Task AppendTextWithRetryAsync(string path, string text) private static async Task AppendTextWithRetryAsync(string path, string text)
@@ -365,6 +365,7 @@ public sealed class RecordingCoordinatorTests
await WaitUntilAsync(() => transcriptStore.Segments.Any(segment => await WaitUntilAsync(() => transcriptStore.Segments.Any(segment =>
segment.Text.Contains("latest transcript text", StringComparison.Ordinal))); segment.Text.Contains("latest transcript text", StringComparison.Ordinal)));
await WaitUntilAsync(() => clock.PendingDelayCount > 0);
clock.Advance(TimeSpan.FromSeconds(2)); clock.Advance(TimeSpan.FromSeconds(2));
await promptService.WaitForPromptAsync(); await promptService.WaitForPromptAsync();
await WaitUntilAsync(() => summaryPipeline.WasRun); await WaitUntilAsync(() => summaryPipeline.WasRun);
@@ -414,8 +415,10 @@ public sealed class RecordingCoordinatorTests
await coordinator.StartAsync(CancellationToken.None); await coordinator.StartAsync(CancellationToken.None);
await WaitUntilAsync(() => clock.PendingDelayCount > 0);
clock.Advance(TimeSpan.FromSeconds(5)); clock.Advance(TimeSpan.FromSeconds(5));
await WaitUntilAsync(() => !coordinator.CurrentStatus.IsRecording); await WaitUntilAsync(() => !coordinator.CurrentStatus.IsRecording);
await WaitUntilAsync(() => summaryPipeline.WasRun);
Assert.Empty(promptService.Requests); Assert.Empty(promptService.Requests);
Assert.False(coordinator.CurrentStatus.IsRecording); Assert.False(coordinator.CurrentStatus.IsRecording);
@@ -459,6 +462,7 @@ public sealed class RecordingCoordinatorTests
inactivityClock: clock); inactivityClock: clock);
await coordinator.StartAsync(CancellationToken.None); await coordinator.StartAsync(CancellationToken.None);
await WaitUntilAsync(() => clock.PendingDelayCount > 0);
clock.Advance(TimeSpan.FromSeconds(2)); clock.Advance(TimeSpan.FromSeconds(2));
await promptService.WaitForPromptAsync(); await promptService.WaitForPromptAsync();
await WaitUntilAsync(() => clock.PendingDelayCount > 0); await WaitUntilAsync(() => clock.PendingDelayCount > 0);
@@ -509,12 +513,14 @@ public sealed class RecordingCoordinatorTests
await audioSource.WriteAsync(new AudioChunk([1, 0], 16000, 1), CancellationToken.None); await audioSource.WriteAsync(new AudioChunk([1, 0], 16000, 1), CancellationToken.None);
await WaitUntilAsync(() => transcriptStore.Segments.Any(segment => await WaitUntilAsync(() => transcriptStore.Segments.Any(segment =>
segment.Text.Contains("chunk:2", StringComparison.Ordinal))); segment.Text.Contains("chunk:2", StringComparison.Ordinal)));
await WaitUntilAsync(() => clock.PendingDelayCount > 0);
clock.Advance(TimeSpan.FromSeconds(2)); clock.Advance(TimeSpan.FromSeconds(2));
await WaitUntilAsync(() => promptService.Requests.Count == 1); await WaitUntilAsync(() => promptService.Requests.Count == 1);
await audioSource.WriteAsync(new AudioChunk([1, 0, 2, 0], 16000, 1), CancellationToken.None); await audioSource.WriteAsync(new AudioChunk([1, 0, 2, 0], 16000, 1), CancellationToken.None);
await WaitUntilAsync(() => transcriptStore.Segments.Any(segment => await WaitUntilAsync(() => transcriptStore.Segments.Any(segment =>
segment.Text.Contains("chunk:4", StringComparison.Ordinal))); segment.Text.Contains("chunk:4", StringComparison.Ordinal)));
await WaitUntilAsync(() => clock.PendingDelayCount > 0);
clock.Advance(TimeSpan.FromSeconds(2)); clock.Advance(TimeSpan.FromSeconds(2));
await WaitUntilAsync(() => promptService.Requests.Count == 2); await WaitUntilAsync(() => promptService.Requests.Count == 2);
@@ -2167,8 +2173,8 @@ public sealed class RecordingCoordinatorTests
private sealed class InMemoryTranscriptStore : ITranscriptStore private sealed class InMemoryTranscriptStore : ITranscriptStore
{ {
private readonly object gate = new();
private readonly List<TranscriptionSegment> segments = []; private readonly List<TranscriptionSegment> segments = [];
private readonly TaskCompletionSource segmentWritten = new(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly string transcriptPath; private readonly string transcriptPath;
public InMemoryTranscriptStore(string transcriptPath = "memory-transcript.md") public InMemoryTranscriptStore(string transcriptPath = "memory-transcript.md")
@@ -2190,31 +2196,34 @@ public sealed class RecordingCoordinatorTests
} }
public Task AppendLineAsync(TranscriptSession session, string line, CancellationToken cancellationToken) public Task AppendLineAsync(TranscriptSession session, string line, CancellationToken cancellationToken)
{
lock (gate)
{ {
segments.Add(ParseTranscriptLine(line)); segments.Add(ParseTranscriptLine(line));
segmentWritten.TrySetResult(); }
return Task.CompletedTask; return Task.CompletedTask;
} }
public async Task WaitForTextAsync(string text) public Task WaitForTextAsync(string text)
{ {
var deadline = DateTimeOffset.UtcNow.AddSeconds(5); return WaitUntilAsync(
while (DateTimeOffset.UtcNow < deadline) () => Segments.Any(segment => segment?.Text?.Contains(text, StringComparison.Ordinal) == true),
{ $"Segment containing '{text}' was not written.");
if (segments.Any(segment => segment?.Text?.Contains(text, StringComparison.Ordinal) == true))
{
return;
}
await segmentWritten.Task.WaitAsync(TimeSpan.FromMilliseconds(100));
}
throw new TimeoutException($"Segment containing '{text}' was not written.");
} }
public IReadOnlyList<TranscriptionSegment> ReplacedSegments { get; private set; } = []; public IReadOnlyList<TranscriptionSegment> ReplacedSegments { get; private set; } = [];
public IReadOnlyList<TranscriptionSegment> Segments => segments; public IReadOnlyList<TranscriptionSegment> Segments
{
get
{
lock (gate)
{
return segments.ToArray();
}
}
}
public MeetingNote? MetadataMeetingNote { get; private set; } public MeetingNote? MetadataMeetingNote { get; private set; }
@@ -2242,7 +2251,6 @@ public sealed class RecordingCoordinatorTests
{ {
private int created; private int created;
private int appendCount; private int appendCount;
private TaskCompletionSource appendObserved = new(TaskCreationOptions.RunContinuationsAsynchronously);
public List<TranscriptWrite> AppendHistory { get; } = []; public List<TranscriptWrite> AppendHistory { get; } = [];
@@ -2268,29 +2276,14 @@ public sealed class RecordingCoordinatorTests
{ {
AppendHistory.Add(new TranscriptWrite(session, ParseTranscriptLine(line))); AppendHistory.Add(new TranscriptWrite(session, ParseTranscriptLine(line)));
Interlocked.Increment(ref appendCount); Interlocked.Increment(ref appendCount);
appendObserved.TrySetResult();
return Task.CompletedTask; return Task.CompletedTask;
} }
public async Task WaitForAppendCountAsync(int expectedCount) public Task WaitForAppendCountAsync(int expectedCount)
{ {
var deadline = DateTimeOffset.UtcNow.AddSeconds(5); return WaitUntilAsync(
while (DateTimeOffset.UtcNow < deadline) () => Volatile.Read(ref appendCount) >= expectedCount,
{ $"Expected {expectedCount} transcript append(s).");
if (Volatile.Read(ref appendCount) >= expectedCount)
{
return;
}
var observed = appendObserved;
await observed.Task.WaitAsync(TimeSpan.FromMilliseconds(100));
if (ReferenceEquals(observed, appendObserved))
{
appendObserved = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
}
}
throw new TimeoutException($"Expected {expectedCount} transcript append(s).");
} }
public Task ReplaceLinesAsync( public Task ReplaceLinesAsync(