using System; using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Internal; namespace MQTTnet.Tests { [TestClass] // Inspired from the vs-threading tests (https://github.com/Microsoft/vs-threading/blob/master/src/Microsoft.VisualStudio.Threading.Tests/AsyncAutoResetEventTests.cs) public class AsyncAutoResetEvent_Tests { private readonly AsyncAutoResetEvent _aare; public AsyncAutoResetEvent_Tests() { _aare = new AsyncAutoResetEvent(); } [TestMethod] public async Task Cleanup_Waiters() { var @lock = new AsyncAutoResetEvent(); var waitOnePassed = false; #pragma warning disable 4014 Task.Run(async () => #pragma warning restore 4014 { await @lock.WaitOneAsync(TimeSpan.FromSeconds(2)); waitOnePassed = true; }); await Task.Delay(500); Assert.AreEqual(1, @lock.WaitersCount); @lock.Set(); await Task.Delay(1000); Assert.IsTrue(waitOnePassed); Assert.AreEqual(0, @lock.WaitersCount); } [TestMethod] public async Task SingleThreadedPulse() { for (int i = 0; i < 5; i++) { var t = _aare.WaitOneAsync(); Assert.IsFalse(t.IsCompleted); _aare.Set(); await t; Assert.IsTrue(t.IsCompleted); } } [TestMethod] public async Task MultipleSetOnlySignalsOnce() { _aare.Set(); _aare.Set(); await _aare.WaitOneAsync(); var t = _aare.WaitOneAsync(); Assert.IsFalse(t.IsCompleted); await Task.Delay(500); Assert.IsFalse(t.IsCompleted); _aare.Set(); await t; Assert.IsTrue(t.IsCompleted); } [TestMethod] public async Task OrderPreservingQueue() { var waiters = new Task[5]; for (int i = 0; i < waiters.Length; i++) { waiters[i] = _aare.WaitOneAsync(); } for (int i = 0; i < waiters.Length; i++) { _aare.Set(); await waiters[i].ConfigureAwait(false); } } // This test does not work in appveyor but on local machine it does!? /////// /////// Verifies that inlining continuations do not have to complete execution before Set() returns. /////// ////[TestMethod] ////public async Task SetReturnsBeforeInlinedContinuations() ////{ //// var setReturned = new ManualResetEventSlim(); //// var inlinedContinuation = _aare.WaitOneAsync() //// .ContinueWith(delegate //// { //// // Arrange to synchronously block the continuation until Set() has returned, //// // which would deadlock if Set does not return until inlined continuations complete. //// Assert.IsTrue(setReturned.Wait(500)); //// }); //// await Task.Delay(100); //// _aare.Set(); //// setReturned.Set(); //// Assert.IsTrue(inlinedContinuation.Wait(500)); ////} [TestMethod] public void WaitAsync_WithCancellationToken() { var cts = new CancellationTokenSource(); Task waitTask = _aare.WaitOneAsync(cts.Token); Assert.IsFalse(waitTask.IsCompleted); // Cancel the request and ensure that it propagates to the task. cts.Cancel(); try { waitTask.GetAwaiter().GetResult(); Assert.IsTrue(false, "Task was expected to transition to a canceled state."); } catch (OperationCanceledException ex) { } // Now set the event and verify that a future waiter gets the signal immediately. _aare.Set(); waitTask = _aare.WaitOneAsync(); Assert.AreEqual(TaskStatus.WaitingForActivation, waitTask.Status); } [TestMethod] public void WaitAsync_WithCancellationToken_Precanceled() { // We construct our own pre-canceled token so that we can do // a meaningful identity check later. var tokenSource = new CancellationTokenSource(); tokenSource.Cancel(); var token = tokenSource.Token; // Verify that a pre-set signal is not reset by a canceled wait request. _aare.Set(); try { _aare.WaitOneAsync(token).GetAwaiter().GetResult(); Assert.IsTrue(false, "Task was expected to transition to a canceled state."); } catch (OperationCanceledException ex) { Assert.AreEqual(token, ex.CancellationToken); } // Verify that the signal was not acquired. Task waitTask = _aare.WaitOneAsync(); Assert.AreEqual(TaskStatus.RanToCompletion, waitTask.Status); } [TestMethod] public async Task WaitAsync_WithTimeout() { Task waitTask = _aare.WaitOneAsync(TimeSpan.FromMilliseconds(500)); Assert.IsFalse(waitTask.IsCompleted); // Cancel the request and ensure that it propagates to the task. await Task.Delay(1000).ConfigureAwait(false); try { waitTask.GetAwaiter().GetResult(); Assert.IsTrue(false, "Task was expected to transition to a timeout state."); } catch (TimeoutException) { Assert.IsTrue(true); } // Now set the event and verify that a future waiter gets the signal immediately. _aare.Set(); waitTask = _aare.WaitOneAsync(TimeSpan.FromMilliseconds(500)); Assert.AreEqual(TaskStatus.RanToCompletion, waitTask.Status); } [TestMethod] public void WaitAsync_Canceled_DoesNotInlineContinuations() { var cts = new CancellationTokenSource(); var task = _aare.WaitOneAsync(cts.Token); var completingActionFinished = new ManualResetEventSlim(); var continuation = task.ContinueWith( _ => Assert.IsTrue(completingActionFinished.Wait(500)), CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); cts.Cancel(); completingActionFinished.Set(); // Rethrow the exception if it turned out it deadlocked. continuation.GetAwaiter().GetResult(); } [TestMethod] public async Task AsyncAutoResetEvent() { var aare = new AsyncAutoResetEvent(); var globalI = 0; #pragma warning disable 4014 Task.Run(async () => #pragma warning restore 4014 { await aare.WaitOneAsync(CancellationToken.None); globalI += 1; }); #pragma warning disable 4014 Task.Run(async () => #pragma warning restore 4014 { await aare.WaitOneAsync(CancellationToken.None); globalI += 2; }); await Task.Delay(500); aare.Set(); await Task.Delay(500); aare.Set(); await Task.Delay(100); Assert.AreEqual(3, globalI); } } }