272 lines
7.1 KiB
C#
Raw Permalink Normal View History

// 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