a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
272 lines
7.1 KiB
C#
272 lines
7.1 KiB
C#
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
|
#if !NO_TPL
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
#if NUNIT
|
|
using NUnit.Framework;
|
|
using TestClassAttribute = NUnit.Framework.TestFixtureAttribute;
|
|
using TestMethodAttribute = NUnit.Framework.TestAttribute;
|
|
using TestInitializeAttribute = NUnit.Framework.SetUpAttribute;
|
|
#else
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
#endif
|
|
using System.Collections;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Diagnostics;
|
|
//using System.Reactive.Linq;
|
|
//using System.Reactive.Concurrency;
|
|
|
|
namespace Tests
|
|
{
|
|
public partial class AsyncTests
|
|
{
|
|
[TestInitialize]
|
|
public void InitTests()
|
|
{
|
|
TaskScheduler.UnobservedTaskException += (o, e) =>
|
|
{
|
|
};
|
|
}
|
|
|
|
/*
|
|
[TestMethod]
|
|
public void TestPushPopAsync()
|
|
{
|
|
var stack = new Stack<int>();
|
|
var count = 10;
|
|
|
|
var observable = Observable.Generate(
|
|
0,
|
|
i => i < count,
|
|
i => i + 1,
|
|
i => i,
|
|
i => TimeSpan.FromMilliseconds(1), // change this to 0 to avoid the problem [1]
|
|
Scheduler.ThreadPool);
|
|
|
|
var task = DoSomethingAsync(observable, stack);
|
|
|
|
// we give it a timeout so the test can fail instead of hang
|
|
task.Wait(TimeSpan.FromSeconds(2));
|
|
|
|
Assert.AreEqual(10, stack.Count);
|
|
}
|
|
|
|
private Task DoSomethingAsync(IObservable<int> observable, Stack<int> stack)
|
|
{
|
|
var ae = observable
|
|
.ToAsyncEnumerable()
|
|
//.Do(i => Debug.WriteLine("Bug-fixing side effect: " + i)) // [2]
|
|
.GetEnumerator();
|
|
|
|
var tcs = new TaskCompletionSource<object>();
|
|
|
|
var a = default(Action);
|
|
a = new Action(() =>
|
|
{
|
|
ae.MoveNext().ContinueWith(t =>
|
|
{
|
|
if (t.Result)
|
|
{
|
|
var i = ae.Current;
|
|
Debug.WriteLine("Doing something with " + i);
|
|
Thread.Sleep(50);
|
|
stack.Push(i);
|
|
a();
|
|
}
|
|
else
|
|
tcs.TrySetResult(null);
|
|
});
|
|
});
|
|
|
|
a();
|
|
|
|
return tcs.Task;
|
|
}
|
|
*/
|
|
|
|
static IEnumerable<int> Xs(Action a)
|
|
{
|
|
try
|
|
{
|
|
var rnd = new Random();
|
|
|
|
while (true)
|
|
{
|
|
yield return rnd.Next(0, 43);
|
|
Thread.Sleep(rnd.Next(0, 500));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
a();
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CorrectDispose()
|
|
{
|
|
var disposed = false;
|
|
|
|
var xs = new[] { 1, 2, 3 }.WithDispose(() =>
|
|
{
|
|
disposed = true;
|
|
}).ToAsyncEnumerable();
|
|
|
|
var ys = xs.Select(x => x + 1);
|
|
|
|
var e = ys.GetEnumerator();
|
|
e.Dispose();
|
|
|
|
Assert.IsTrue(disposed);
|
|
|
|
Assert.IsFalse(e.MoveNext().Result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void DisposesUponError()
|
|
{
|
|
var disposed = false;
|
|
|
|
var xs = new[] { 1, 2, 3 }.WithDispose(() =>
|
|
{
|
|
disposed = true;
|
|
}).ToAsyncEnumerable();
|
|
|
|
var ex = new Exception("Bang!");
|
|
var ys = xs.Select(x => { if (x == 1) throw ex; return x; });
|
|
|
|
var e = ys.GetEnumerator();
|
|
AssertThrows<Exception>(() => e.MoveNext());
|
|
|
|
Assert.IsTrue(disposed);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CorrectCancel()
|
|
{
|
|
var disposed = false;
|
|
|
|
var xs = new[] { 1, 2, 3 }.WithDispose(() =>
|
|
{
|
|
disposed = true;
|
|
}).ToAsyncEnumerable();
|
|
|
|
var ys = xs.Select(x => x + 1).Where(x => true);
|
|
|
|
var e = ys.GetEnumerator();
|
|
var cts = new CancellationTokenSource();
|
|
var t = e.MoveNext(cts.Token);
|
|
|
|
cts.Cancel();
|
|
|
|
try
|
|
{
|
|
t.Wait();
|
|
}
|
|
catch
|
|
{
|
|
// Don't care about the outcome; we could have made it to element 1
|
|
// but we could also have cancelled the MoveNext-calling task. Either
|
|
// way, we want to wait for the task to be completed and check that
|
|
}
|
|
finally
|
|
{
|
|
// the cancellation bubbled all the way up to the source to dispose
|
|
// it. This design is chosen because cancelling a MoveNext call leaves
|
|
// the enumerator in an indeterminate state. Further interactions with
|
|
// it should be forbidden.
|
|
Assert.IsTrue(disposed);
|
|
}
|
|
|
|
Assert.IsFalse(e.MoveNext().Result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CanCancelMoveNext()
|
|
{
|
|
var evt = new ManualResetEvent(false);
|
|
var xs = Blocking(evt).ToAsyncEnumerable().Select(x => x).Where(x => true);
|
|
|
|
var e = xs.GetEnumerator();
|
|
var cts = new CancellationTokenSource();
|
|
var t = e.MoveNext(cts.Token);
|
|
|
|
cts.Cancel();
|
|
|
|
try
|
|
{
|
|
t.Wait();
|
|
Assert.Fail();
|
|
}
|
|
catch
|
|
{
|
|
Assert.IsTrue(t.IsCanceled);
|
|
}
|
|
|
|
evt.Set();
|
|
}
|
|
|
|
static IEnumerable<int> Blocking(ManualResetEvent evt)
|
|
{
|
|
evt.WaitOne();
|
|
yield return 42;
|
|
}
|
|
}
|
|
|
|
static class MyExt
|
|
{
|
|
public static IEnumerable<T> WithDispose<T>(this IEnumerable<T> source, Action a)
|
|
{
|
|
return EnumerableEx.Create(() =>
|
|
{
|
|
var e = source.GetEnumerator();
|
|
return new Enumerator<T>(e.MoveNext, () => e.Current, () => { e.Dispose(); a(); });
|
|
});
|
|
}
|
|
|
|
class Enumerator<T> : IEnumerator<T>
|
|
{
|
|
private readonly Func<bool> _moveNext;
|
|
private readonly Func<T> _current;
|
|
private readonly Action _dispose;
|
|
|
|
public Enumerator(Func<bool> moveNext, Func<T> current, Action dispose)
|
|
{
|
|
_moveNext = moveNext;
|
|
_current = current;
|
|
_dispose = dispose;
|
|
}
|
|
|
|
public T Current
|
|
{
|
|
get { return _current(); }
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_dispose();
|
|
}
|
|
|
|
object IEnumerator.Current
|
|
{
|
|
get { return Current; }
|
|
}
|
|
|
|
public bool MoveNext()
|
|
{
|
|
return _moveNext();
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#endif |