From 86e3efa3f6cc5153f35f1996f4bec37c60c31e93 Mon Sep 17 00:00:00 2001 From: Codex Automation Date: Thu, 11 Jun 2026 15:40:40 +0200 Subject: [PATCH] Stabilize recording coordinator timing tests --- .../RecordingCoordinatorTests.cs | 71 +++++++++---------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/MeetingAssistant.Tests/RecordingCoordinatorTests.cs b/MeetingAssistant.Tests/RecordingCoordinatorTests.cs index 44f9753..c568ab2 100644 --- a/MeetingAssistant.Tests/RecordingCoordinatorTests.cs +++ b/MeetingAssistant.Tests/RecordingCoordinatorTests.cs @@ -15,7 +15,7 @@ namespace MeetingAssistant.Tests; public sealed class RecordingCoordinatorTests { - private static async Task WaitUntilAsync(Func condition) + private static async Task WaitUntilAsync(Func condition, string? timeoutMessage = null) { var deadline = DateTimeOffset.UtcNow.AddSeconds(15); while (DateTimeOffset.UtcNow < deadline) @@ -28,7 +28,7 @@ public sealed class RecordingCoordinatorTests 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) @@ -365,6 +365,7 @@ public sealed class RecordingCoordinatorTests await WaitUntilAsync(() => transcriptStore.Segments.Any(segment => segment.Text.Contains("latest transcript text", StringComparison.Ordinal))); + await WaitUntilAsync(() => clock.PendingDelayCount > 0); clock.Advance(TimeSpan.FromSeconds(2)); await promptService.WaitForPromptAsync(); await WaitUntilAsync(() => summaryPipeline.WasRun); @@ -414,8 +415,10 @@ public sealed class RecordingCoordinatorTests await coordinator.StartAsync(CancellationToken.None); + await WaitUntilAsync(() => clock.PendingDelayCount > 0); clock.Advance(TimeSpan.FromSeconds(5)); await WaitUntilAsync(() => !coordinator.CurrentStatus.IsRecording); + await WaitUntilAsync(() => summaryPipeline.WasRun); Assert.Empty(promptService.Requests); Assert.False(coordinator.CurrentStatus.IsRecording); @@ -459,6 +462,7 @@ public sealed class RecordingCoordinatorTests inactivityClock: clock); await coordinator.StartAsync(CancellationToken.None); + await WaitUntilAsync(() => clock.PendingDelayCount > 0); clock.Advance(TimeSpan.FromSeconds(2)); await promptService.WaitForPromptAsync(); 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 WaitUntilAsync(() => transcriptStore.Segments.Any(segment => segment.Text.Contains("chunk:2", StringComparison.Ordinal))); + await WaitUntilAsync(() => clock.PendingDelayCount > 0); clock.Advance(TimeSpan.FromSeconds(2)); await WaitUntilAsync(() => promptService.Requests.Count == 1); await audioSource.WriteAsync(new AudioChunk([1, 0, 2, 0], 16000, 1), CancellationToken.None); await WaitUntilAsync(() => transcriptStore.Segments.Any(segment => segment.Text.Contains("chunk:4", StringComparison.Ordinal))); + await WaitUntilAsync(() => clock.PendingDelayCount > 0); clock.Advance(TimeSpan.FromSeconds(2)); await WaitUntilAsync(() => promptService.Requests.Count == 2); @@ -2167,8 +2173,8 @@ public sealed class RecordingCoordinatorTests private sealed class InMemoryTranscriptStore : ITranscriptStore { + private readonly object gate = new(); private readonly List segments = []; - private readonly TaskCompletionSource segmentWritten = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly string transcriptPath; public InMemoryTranscriptStore(string transcriptPath = "memory-transcript.md") @@ -2191,30 +2197,33 @@ public sealed class RecordingCoordinatorTests public Task AppendLineAsync(TranscriptSession session, string line, CancellationToken cancellationToken) { - segments.Add(ParseTranscriptLine(line)); - segmentWritten.TrySetResult(); + lock (gate) + { + segments.Add(ParseTranscriptLine(line)); + } + return Task.CompletedTask; } - public async Task WaitForTextAsync(string text) + public Task WaitForTextAsync(string text) { - var deadline = DateTimeOffset.UtcNow.AddSeconds(5); - while (DateTimeOffset.UtcNow < deadline) - { - 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."); + return WaitUntilAsync( + () => Segments.Any(segment => segment?.Text?.Contains(text, StringComparison.Ordinal) == true), + $"Segment containing '{text}' was not written."); } public IReadOnlyList ReplacedSegments { get; private set; } = []; - public IReadOnlyList Segments => segments; + public IReadOnlyList Segments + { + get + { + lock (gate) + { + return segments.ToArray(); + } + } + } public MeetingNote? MetadataMeetingNote { get; private set; } @@ -2242,7 +2251,6 @@ public sealed class RecordingCoordinatorTests { private int created; private int appendCount; - private TaskCompletionSource appendObserved = new(TaskCreationOptions.RunContinuationsAsynchronously); public List AppendHistory { get; } = []; @@ -2268,29 +2276,14 @@ public sealed class RecordingCoordinatorTests { AppendHistory.Add(new TranscriptWrite(session, ParseTranscriptLine(line))); Interlocked.Increment(ref appendCount); - appendObserved.TrySetResult(); return Task.CompletedTask; } - public async Task WaitForAppendCountAsync(int expectedCount) + public Task WaitForAppendCountAsync(int expectedCount) { - var deadline = DateTimeOffset.UtcNow.AddSeconds(5); - while (DateTimeOffset.UtcNow < deadline) - { - 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)."); + return WaitUntilAsync( + () => Volatile.Read(ref appendCount) >= expectedCount, + $"Expected {expectedCount} transcript append(s)."); } public Task ReplaceLinesAsync(