Enhances automated workflows and test reliability#1
Conversation
* Grants automated workflows necessary permissions to manage repository contents, packages, and discussions. * Improves the reliability of asynchronous transformation cancellation tests. Replaces fixed delays with controlled task completion sources for more accurate verification of cancellation behavior.
…sed signaling - Replace Task.Delay timing with TaskCompletionSource signaling - Use per-item TCS dictionaries for multi-value scenarios - Add StyleCop pragma for test file formatting - All 10 tests now use deterministic async coordination
…S-based signaling - Replace Task.Delay(10) settling waits with emission counting TCS pattern - All 7 tests now use deterministic async coordination - Add StyleCop pragma for test file formatting
- Replace timing-based test with synchronous emission order verification - Remove unnecessary async from synchronous tests - Add StyleCop pragma for test file formatting
- Add started signals for SelectLatestAsync cancellation test - Replace Task.Delay work simulation with TCS-based blocking - All 21 tests now fully deterministic
- Remove unnecessary Task.Delay from synchronous operations - Convert async tests to sync where no awaits needed - FakeTimeProvider tests are synchronous by design - Add StyleCop pragma for test file formatting
- TransformManyDedupTests: Replace polling with TCS signaling - RxCommandTests: Use TCS for async operation control - TransformAsyncCacheTests: Use per-item TCS for controlled completion - InteractionAdvancedTests: Replace Task.Delay with Task.Yield and TCS - AsyncIntegrationTests: Use TCS-based coordination - InteractionTests: Use Task.Yield for async execution All tests now use deterministic async coordination patterns
There was a problem hiding this comment.
Pull request overview
This PR enhances test reliability by replacing non-deterministic Task.Delay() calls with explicit synchronization using TaskCompletionSource, and adds explicit workflow permissions. The changes make tests more deterministic and less prone to timing-related failures in CI/CD environments.
Key changes:
- Replaces
Task.Delay()withTaskCompletionSourcefor controlled test synchronization - Removes unnecessary
asyncmodifiers from synchronous tests - Adds explicit permissions to GitHub Actions workflow
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| R3Ext.Tests/TransformManyDedupTests.cs | Replaces polling helper with TaskCompletionSource-based synchronization for emission tracking |
| R3Ext.Tests/TransformAsyncCacheTests.cs | Introduces per-item TaskCompletionSource for controlled async transformation completion |
| R3Ext.Tests/SignalExtensionsAdvancedTests.cs | Removes async modifiers from synchronous tests and improves timing verification |
| R3Ext.Tests/RxCommandTests.cs | Replaces Task.Delay with TaskCompletionSource for async operation verification |
| R3Ext.Tests/RxCommandAdvancedTests.cs | Uses TaskCompletionSource to control execution flow in concurrent tests |
| R3Ext.Tests/InteractionTests.cs | Replaces Task.Delay(1) with Task.Yield() for minimal async behavior |
| R3Ext.Tests/InteractionAdvancedTests.cs | Adds controlled release mechanism for concurrent handler tests |
| R3Ext.Tests/ErrorHandlingAdvancedTests.cs | Removes unnecessary async modifiers and Task.Delay calls from synchronous tests |
| R3Ext.Tests/CacheOperatorParityPhase2Tests.cs | Implements emission counting with TaskCompletionSource for reliable test synchronization |
| R3Ext.Tests/AsyncIntegrationTests.cs | Uses blocking TaskCompletionSource instead of arbitrary delays for cancellation tests |
| R3Ext.Tests/AsyncExtensionsTests.cs | Adds per-item TaskCompletionSource dictionaries for fine-grained async operation control |
| R3.DynamicData.Tests/List/TransformAsyncTests.cs | Replaces Task.Delay with TaskCompletionSource and improves cancellation test reliability |
| .github/workflows/ci-cd.yml | Adds explicit permissions for contents, packages, and discussions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Assert.Equal(2, changesReceived.Count); // No new change set emitted | ||
| // No new changeset expected - stay at count 2 | ||
| Assert.Equal(2, changesReceived.Count); | ||
|
|
There was a problem hiding this comment.
Potential race condition: Same issue as lines 48-50. The expectedCount and emitTcs are updated before performing the action. Update these variables AFTER line 59's assertion (or add a small delay/synchronization point) to ensure the previous operation's callback has completed.
| // Update synchronization variables after previous assertion, before triggering the next action |
| expectedCount = 4; | ||
| emitTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); | ||
|
|
There was a problem hiding this comment.
Potential race condition: Same issue - expectedCount and emitTcs updated before the action on line 67. Move lines 64-65 after line 61's assertion.
| expectedCount = 4; | |
| emitTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); | |
| expectedCount = 4; | |
| emitTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); |
| cache.AddOrUpdate(new Item { Id = 1, Value = 1 }); | ||
| await Task.Delay(10); | ||
| await emitTcs.Task.WaitAsync(TimeSpan.FromSeconds(5)); | ||
|
|
There was a problem hiding this comment.
Potential race condition: The expectedCount and emitTcs are updated before the action on line 100. Move lines 98-99 after line 96's await.
| // Move expectedCount and emitTcs update after previous await, before next emission |
| await Task.Delay(10); | ||
| await emitTcs.Task.WaitAsync(TimeSpan.FromSeconds(5)); | ||
| Assert.True(results.Last().HasValue); | ||
|
|
There was a problem hiding this comment.
Potential race condition: The expectedCount and emitTcs are updated before the action on line 174. Move lines 172-173 after line 170's assertion.
| expectedCount = 3; | ||
| emitTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); | ||
| cache.AddOrUpdate(new Item { Id = 1, Values = new List<int> { 2, 4 } }); // Update introducing 4 |
There was a problem hiding this comment.
Potential race condition: Same pattern as earlier - expectedCount and emitTcs are updated before the action on line 97. Move these updates after line 93's assertion.
| expectedCount = 2; | ||
| emitTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); | ||
| cache.AddOrUpdate(new Item { Id = 2, Values = new List<int> { 1, 2, 3 } }); |
There was a problem hiding this comment.
Potential race condition: The expectedCount and emitTcs are updated before performing the action that triggers the emission. If the subscription callback from the previous operation (line 42) executes after these updates but before awaiting on line 43, it could see the new expectedCount value (2) and incorrectly signal emitTcs. This would cause the test to proceed before the second AddOrUpdate completes.
To fix this, update expectedCount and create the new emitTcs AFTER the await on line 43, right before the AddOrUpdate on line 50.
| foreach (var c in cs) | ||
| { | ||
| if (c.Reason == ListChangeReason.Add) | ||
| { | ||
| if (Interlocked.Increment(ref addCount) == 3) addsDoneTcs.TrySetResult(true); | ||
| } | ||
| } |
There was a problem hiding this comment.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
| { | ||
| Interlocked.Increment(ref transformCount); | ||
| await Task.Delay(50); | ||
| var count = Interlocked.Increment(ref transformCount); |
R3Ext.Tests/AsyncIntegrationTests.cs
Outdated
| { | ||
| Subject<int> subject = new(); | ||
| int completed = 0; | ||
| var completedTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); |
There was a problem hiding this comment.
This assignment to completedTcs is useless, since its value is never read.
| var completedTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); |
| if (c.Reason == ListChangeReason.Add) | ||
| { | ||
| if (Interlocked.Increment(ref addCount) == 3) addsDoneTcs.TrySetResult(true); |
There was a problem hiding this comment.
These 'if' statements can be combined.
| if (c.Reason == ListChangeReason.Add) | |
| { | |
| if (Interlocked.Increment(ref addCount) == 3) addsDoneTcs.TrySetResult(true); | |
| if (c.Reason == ListChangeReason.Add && Interlocked.Increment(ref addCount) == 3) | |
| { | |
| addsDoneTcs.TrySetResult(true); |
- RxCommandTests: Replace unreliable thread ID check with async behavior test - CacheOperatorParityPhase2Tests: Fix emission expectations for filtered items - AsyncExtensionsTests: Wait for each SelectLatestAsync result properly - TransformManyDedupTests: Apply safe TCS pattern with results.Count check - AsyncIntegrationTests: Remove unused completedTcs variable
No description provided.