// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reactive; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reflection; using System.Threading; using Microsoft.Reactive.Testing; #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 ReactiveTests.Dummies; namespace ReactiveTests.Tests { [TestClass] public class ObservableConversionTests : ReactiveTest { #region + Subscribe + [TestMethod] public void SubscribeToEnumerable_ArgumentChecking() { ReactiveAssert.Throws(() => Observable.Subscribe((IEnumerable)null, DummyObserver.Instance)); ReactiveAssert.Throws(() => Observable.Subscribe(DummyEnumerable.Instance, (IObserver)null)); ReactiveAssert.Throws(() => Observable.Subscribe((IEnumerable)null, DummyObserver.Instance, DummyScheduler.Instance)); ReactiveAssert.Throws(() => Observable.Subscribe(DummyEnumerable.Instance, DummyObserver.Instance, null)); ReactiveAssert.Throws(() => Observable.Subscribe(DummyEnumerable.Instance, (IObserver)null, DummyScheduler.Instance)); ReactiveAssert.Throws(() => NullEnumeratorEnumerable.Instance.Subscribe(Observer.Create(x => { }), Scheduler.CurrentThread)); } [TestMethod] public void SubscribeToEnumerable_Finite() { var scheduler = new TestScheduler(); var results = scheduler.CreateObserver(); var d = default(IDisposable); var xs = default(MockEnumerable); scheduler.ScheduleAbsolute(Created, () => xs = new MockEnumerable(scheduler, Enumerable_Finite())); scheduler.ScheduleAbsolute(Subscribed, () => d = xs.Subscribe(results, scheduler)); scheduler.ScheduleAbsolute(Disposed, () => d.Dispose()); scheduler.Start(); results.Messages.AssertEqual( OnNext(201, 1), OnNext(202, 2), OnNext(203, 3), OnNext(204, 4), OnNext(205, 5), OnCompleted(206) ); xs.Subscriptions.AssertEqual( Subscribe(200, 206) ); } [TestMethod] public void SubscribeToEnumerable_Infinite() { var scheduler = new TestScheduler(); var results = scheduler.CreateObserver(); var d = default(IDisposable); var xs = default(MockEnumerable); scheduler.ScheduleAbsolute(Created, () => xs = new MockEnumerable(scheduler, Enumerable_Infinite())); scheduler.ScheduleAbsolute(Subscribed, () => d = xs.Subscribe(results, scheduler)); scheduler.ScheduleAbsolute(210, () => d.Dispose()); scheduler.Start(); results.Messages.AssertEqual( OnNext(201, 1), OnNext(202, 1), OnNext(203, 1), OnNext(204, 1), OnNext(205, 1), OnNext(206, 1), OnNext(207, 1), OnNext(208, 1), OnNext(209, 1) ); xs.Subscriptions.AssertEqual( Subscribe(200, 210) ); } [TestMethod] public void SubscribeToEnumerable_Error() { var scheduler = new TestScheduler(); var results = scheduler.CreateObserver(); var d = default(IDisposable); var xs = default(MockEnumerable); var ex = new Exception(); scheduler.ScheduleAbsolute(Created, () => xs = new MockEnumerable(scheduler, Enumerable_Error(ex))); scheduler.ScheduleAbsolute(Subscribed, () => d = xs.Subscribe(results, scheduler)); scheduler.ScheduleAbsolute(Disposed, () => d.Dispose()); scheduler.Start(); results.Messages.AssertEqual( OnNext(201, 1), OnNext(202, 2), OnNext(203, 3), OnError(204, ex) ); xs.Subscriptions.AssertEqual( Subscribe(200, 204) ); } #if !SILVERLIGHTM7 [TestMethod] public void SubscribeToEnumerable_DefaultScheduler() { for (int i = 0; i < 100; i++) { var scheduler = new TestScheduler(); var results1 = new List(); var results2 = new List(); var s1 = new Semaphore(0, 1); var s2 = new Semaphore(0, 1); Observable.Subscribe(Enumerable_Finite(), Observer.Create(x => results1.Add(x), ex => { throw ex; }, () => s1.Release())); Observable.Subscribe(Enumerable_Finite(), Observer.Create(x => results2.Add(x), ex => { throw ex; }, () => s2.Release()), DefaultScheduler.Instance); s1.WaitOne(); s2.WaitOne(); results1.AssertEqual(results2); } } #endif #endregion #region ToEnumerable [TestMethod] public void ToEnumerable_ArgumentChecking() { ReactiveAssert.Throws(() => Observable.ToEnumerable(default(IObservable))); } [TestMethod] public void ToEnumerable_Generic() { Assert.IsTrue(Observable.Range(0, 10).ToEnumerable().SequenceEqual(Enumerable.Range(0, 10))); } [TestMethod] public void ToEnumerable_NonGeneric() { Assert.IsTrue(((IEnumerable)Observable.Range(0, 10).ToEnumerable()).Cast().SequenceEqual(Enumerable.Range(0, 10))); } [TestMethod] public void ToEnumerable_ManualGeneric() { var res = Observable.Range(0, 10).ToEnumerable(); var ieg = res.GetEnumerator(); for (int i = 0; i < 10; i++) { Assert.IsTrue(ieg.MoveNext()); Assert.AreEqual(i, ieg.Current); } Assert.IsFalse(ieg.MoveNext()); } [TestMethod] public void ToEnumerable_ManualNonGeneric() { var res = (IEnumerable)Observable.Range(0, 10).ToEnumerable(); var ien = res.GetEnumerator(); for (int i = 0; i < 10; i++) { Assert.IsTrue(ien.MoveNext()); Assert.AreEqual(i, ien.Current); } Assert.IsFalse(ien.MoveNext()); } [TestMethod] public void ToEnumerable_ResetNotSupported() { ReactiveAssert.Throws(() => Observable.Range(0, 10).ToEnumerable().GetEnumerator().Reset()); } #endregion #region ToEvent [TestMethod] public void ToEvent_ArgumentChecks() { ReactiveAssert.Throws(() => Observable.ToEvent(default(IObservable))); ReactiveAssert.Throws(() => Observable.ToEvent(default(IObservable))); ReactiveAssert.Throws(() => Observable.ToEvent(default(IObservable>))); } [TestMethod] public void ToEvent_Unit() { var src = new Subject(); var evt = src.ToEvent(); var num = 0; var hnd = new Action(_ => { num++; }); evt.OnNext += hnd; Assert.AreEqual(0, num); src.OnNext(new Unit()); Assert.AreEqual(1, num); src.OnNext(new Unit()); Assert.AreEqual(2, num); evt.OnNext -= hnd; src.OnNext(new Unit()); Assert.AreEqual(2, num); } [TestMethod] public void ToEvent_NonUnit() { var src = new Subject(); var evt = src.ToEvent(); var lst = new List(); var hnd = new Action(e => { lst.Add(e); }); evt.OnNext += hnd; src.OnNext(1); src.OnNext(2); evt.OnNext -= hnd; src.OnNext(3); Assert.IsTrue(lst.SequenceEqual(new[] { 1, 2 })); } [TestMethod] public void ToEvent_FromEvent() { var src = new Subject(); var evt = src.ToEvent(); var res = Observable.FromEvent(h => evt.OnNext += h, h => evt.OnNext -= h); var lst = new List(); using (res.Subscribe(e => lst.Add(e), () => Assert.Fail())) { src.OnNext(1); src.OnNext(2); } src.OnNext(3); Assert.IsTrue(lst.SequenceEqual(new[] { 1, 2 })); } #endregion #region ToEventPattern [TestMethod] public void ToEventPattern_ArgumentChecking() { ReactiveAssert.Throws(() => Observable.ToEventPattern(null)); } [TestMethod] public void ToEventPattern_IEvent() { var src = new Subject>>(); var evt = src.ToEventPattern(); var snd = new object(); var lst = new List(); var hnd = new EventHandler>((s, e) => { Assert.AreSame(snd, s); lst.Add(e.Value); }); evt.OnNext += hnd; src.OnNext(new EventPattern>(snd, new EventArgs(42))); src.OnNext(new EventPattern>(snd, new EventArgs(43))); evt.OnNext -= hnd; src.OnNext(new EventPattern>(snd, new EventArgs(44))); Assert.IsTrue(lst.SequenceEqual(new[] { 42, 43 })); } [TestMethod] public void ToEventPattern_IEvent_Fails() { var src = new Subject>>(); var evt = src.ToEventPattern(); var snd = new object(); var lst = new List(); var hnd = new EventHandler>((s, e) => { Assert.AreSame(snd, s); lst.Add(e.Value); }); evt.OnNext += hnd; src.OnNext(new EventPattern>(snd, new EventArgs(42))); src.OnNext(new EventPattern>(snd, new EventArgs(43))); var ex = new Exception(); ReactiveAssert.Throws(ex, () => src.OnError(ex)); Assert.IsTrue(lst.SequenceEqual(new[] { 42, 43 })); } [TestMethod] public void ToEventPattern_IEvent_Completes() { var src = new Subject>>(); var evt = src.ToEventPattern(); var snd = new object(); var lst = new List(); var hnd = new EventHandler>((s, e) => { Assert.AreSame(snd, s); lst.Add(e.Value); }); evt.OnNext += hnd; src.OnNext(new EventPattern>(snd, new EventArgs(42))); src.OnNext(new EventPattern>(snd, new EventArgs(43))); src.OnCompleted(); Assert.IsTrue(lst.SequenceEqual(new[] { 42, 43 })); } class EventSrc { public event EventHandler> E; public void On(string s) { var e = E; if (e != null) e(this, new EventArgs(s)); } } class EventArgs : EventArgs { public T Value { get; private set; } public EventArgs(T value) { Value = value; } } [TestMethod] public void FromEventPattern_ToEventPattern() { var src = new EventSrc(); var evt = Observable.FromEventPattern>, EventArgs>(h => new EventHandler>(h), h => src.E += h, h => src.E -= h); var res = evt.ToEventPattern(); var lst = new List(); var hnd = new EventHandler>((s, e) => { Assert.AreSame(src, s); lst.Add(e.Value); }); src.On("bar"); res.OnNext += hnd; src.On("foo"); src.On("baz"); res.OnNext -= hnd; src.On("qux"); Assert.IsTrue(lst.SequenceEqual(new[] { "foo", "baz" })); } [TestMethod] public void ToEvent_DuplicateHandlers() { var src = new Subject(); var evt = src.ToEvent(); var num = 0; var hnd = new Action(e => num++); evt.OnNext += hnd; Assert.AreEqual(0, num); src.OnNext(new Unit()); Assert.AreEqual(1, num); evt.OnNext += hnd; src.OnNext(new Unit()); Assert.AreEqual(3, num); evt.OnNext -= hnd; src.OnNext(new Unit()); Assert.AreEqual(4, num); evt.OnNext -= hnd; src.OnNext(new Unit()); Assert.AreEqual(4, num); } [TestMethod] public void ToEvent_SourceCompletes() { var src = new Subject(); var evt = src.ToEvent(); var num = 0; var hnd = new Action(e => num++); evt.OnNext += hnd; Assert.AreEqual(0, num); src.OnNext(new Unit()); Assert.AreEqual(1, num); src.OnNext(new Unit()); Assert.AreEqual(2, num); src.OnCompleted(); Assert.AreEqual(2, num); #if !SILVERLIGHT // FieldAccessException var tbl = GetSubscriptionTable(evt); Assert.IsTrue(tbl.Count == 0); #endif } [TestMethod] public void ToEvent_SourceFails() { var src = new Subject(); var evt = src.ToEvent(); var num = 0; var hnd = new Action(e => num++); evt.OnNext += hnd; Assert.AreEqual(0, num); src.OnNext(new Unit()); Assert.AreEqual(1, num); src.OnNext(new Unit()); Assert.AreEqual(2, num); var ex = new Exception(); ReactiveAssert.Throws(ex, () => src.OnError(ex)); #if !SILVERLIGHT // FieldAccessException var tbl = GetSubscriptionTable(evt); Assert.IsTrue(tbl.Count == 0); #endif } [TestMethod] public void ToEvent_DoneImmediately() { var src = Observable.Empty(); var evt = src.ToEvent(); var num = 0; var hnd = new Action(e => num++); for (int i = 0; i < 2; i++) { evt.OnNext += hnd; Assert.AreEqual(0, num); #if !SILVERLIGHT // FieldAccessException var tbl = GetSubscriptionTable(evt); Assert.IsTrue(tbl.Count == 0); #endif } } [TestMethod] public void ToEvent_UnbalancedHandlers() { var src = new Subject(); var evt = src.ToEvent(); var num = 0; var hnd = new Action(e => num++); evt.OnNext += hnd; Assert.AreEqual(0, num); evt.OnNext -= hnd; Assert.AreEqual(0, num); evt.OnNext -= hnd; Assert.AreEqual(0, num); evt.OnNext += hnd; Assert.AreEqual(0, num); src.OnNext(new Unit()); Assert.AreEqual(1, num); src.OnNext(new Unit()); Assert.AreEqual(2, num); evt.OnNext -= hnd; Assert.AreEqual(2, num); src.OnNext(new Unit()); Assert.AreEqual(2, num); } private static Dictionary> GetSubscriptionTable(object evt) { return (Dictionary>)evt.GetType().GetField("_subscriptions", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(evt); } [TestMethod] public void EventPattern_Equality() { var e1 = new EventPattern("Bart", EventArgs.Empty); var e2 = new EventPattern("Bart", EventArgs.Empty); Assert.IsTrue(e1.Equals(e1)); Assert.IsTrue(e1.Equals(e2)); Assert.IsTrue(e2.Equals(e1)); Assert.IsTrue(e1 == e2); Assert.IsTrue(!(e1 != e2)); Assert.IsTrue(e1.GetHashCode() == e2.GetHashCode()); Assert.IsFalse(e1.Equals(null)); Assert.IsFalse(e1.Equals("xy")); Assert.IsFalse(e1 == null); } [TestMethod] public void EventPattern_Inequality() { var a1 = new MyEventArgs(); var a2 = new MyEventArgs(); var e1 = new EventPattern("Bart", a1); var e2 = new EventPattern("John", a1); var e3 = new EventPattern("Bart", a2); Assert.IsTrue(!e1.Equals(e2)); Assert.IsTrue(!e2.Equals(e1)); Assert.IsTrue(!(e1 == e2)); Assert.IsTrue(e1 != e2); Assert.IsTrue(e1.GetHashCode() != e2.GetHashCode()); Assert.IsTrue(!e1.Equals(e3)); Assert.IsTrue(!e3.Equals(e1)); Assert.IsTrue(!(e1 == e3)); Assert.IsTrue(e1 != e3); Assert.IsTrue(e1.GetHashCode() != e3.GetHashCode()); } class MyEventArgs : EventArgs { } #endregion #region + ToObservable + [TestMethod] public void EnumerableToObservable_ArgumentChecking() { ReactiveAssert.Throws(() => Observable.ToObservable((IEnumerable)null, DummyScheduler.Instance)); ReactiveAssert.Throws(() => Observable.ToObservable(DummyEnumerable.Instance, (IScheduler)null)); ReactiveAssert.Throws(() => Observable.ToObservable(DummyEnumerable.Instance, DummyScheduler.Instance).Subscribe(null)); ReactiveAssert.Throws(() => Observable.ToObservable(NullEnumeratorEnumerable.Instance, Scheduler.CurrentThread).Subscribe()); } [TestMethod] public void EnumerableToObservable_Complete() { var scheduler = new TestScheduler(); var e = new MockEnumerable(scheduler, new[] { 3, 1, 2, 4 } ); var results = scheduler.Start(() => e.ToObservable(scheduler) ); results.Messages.AssertEqual( OnNext(201, 3), OnNext(202, 1), OnNext(203, 2), OnNext(204, 4), OnCompleted(205) ); e.Subscriptions.AssertEqual( Subscribe(200, 205) ); } [TestMethod] public void EnumerableToObservable_Dispose() { var scheduler = new TestScheduler(); var e = new MockEnumerable(scheduler, new[] { 3, 1, 2, 4 } ); var results = scheduler.Start(() => e.ToObservable(scheduler), 203 ); results.Messages.AssertEqual( OnNext(201, 3), OnNext(202, 1) ); e.Subscriptions.AssertEqual( Subscribe(200, 203) ); } [TestMethod] public void EnumerableToObservable_Error() { var scheduler = new TestScheduler(); var ex = new Exception(); var e = new MockEnumerable(scheduler, EnumerableToObservable_Error_Core(ex) ); var results = scheduler.Start(() => e.ToObservable(scheduler) ); results.Messages.AssertEqual( OnNext(201, 1), OnNext(202, 2), OnError(203, ex) ); e.Subscriptions.AssertEqual( Subscribe(200, 203) ); } [TestMethod] public void EnumerableToObservable_Default_ArgumentChecking() { ReactiveAssert.Throws(() => Observable.ToObservable((IEnumerable)null)); ReactiveAssert.Throws(() => Observable.ToObservable(DummyEnumerable.Instance).Subscribe(null)); } [TestMethod] public void EnumerableToObservable_Default() { var xs = new[] { 4, 3, 1, 5, 9, 2 }; xs.ToObservable().AssertEqual(xs.ToObservable(DefaultScheduler.Instance)); } #if !NO_PERF [TestMethod] public void EnumerableToObservable_LongRunning_Complete() { var start = default(ManualResetEvent); var end = default(ManualResetEvent); var scheduler = new TestLongRunningScheduler(x => start = x, x => end = x); var e = new[] { 3, 1, 2, 4 }; var results = e.ToObservable(scheduler); var lst = new List(); results.Subscribe(lst.Add); start.WaitOne(); end.WaitOne(); Assert.IsTrue(e.SequenceEqual(lst)); } [TestMethod] public void EnumerableToObservable_LongRunning_Dispose() { var start = default(ManualResetEvent); var end = default(ManualResetEvent); var scheduler = new TestLongRunningScheduler(x => start = x, x => end = x); var e = Enumerable.Range(0, int.MaxValue); var results = e.ToObservable(scheduler); var lst = new List(); var d = results.Subscribe(lst.Add); start.WaitOne(); while (lst.Count < 100) ; d.Dispose(); end.WaitOne(); Assert.IsTrue(e.Take(100).SequenceEqual(lst.Take(100))); } [TestMethod] public void EnumerableToObservable_LongRunning_Error() { var start = default(ManualResetEvent); var end = default(ManualResetEvent); var scheduler = new TestLongRunningScheduler(x => start = x, x => end = x); var ex = new Exception(); var e = EnumerableToObservable_Error_Core(ex); var results = e.ToObservable(scheduler); var lst = new List(); var err = default(Exception); results.Subscribe(lst.Add, ex_ => err = ex_); start.WaitOne(); end.WaitOne(); Assert.IsTrue(new[] { 1, 2 }.SequenceEqual(lst)); Assert.AreSame(ex, err); } #endif static IEnumerable EnumerableToObservable_Error_Core(Exception ex) { yield return 1; yield return 2; throw ex; } [TestMethod] public void EnumerableToObservable_GetEnumeratorThrows() { var ex = new Exception(); var scheduler = new TestScheduler(); var xs = new RogueEnumerable(ex); var res = scheduler.Start(() => xs.ToObservable(scheduler) ); res.Messages.AssertEqual( OnError(200, ex) ); } #endregion #region |> Helpers <| IEnumerable Enumerable_Finite() { yield return 1; yield return 2; yield return 3; yield return 4; yield return 5; yield break; } IEnumerable Enumerable_Infinite() { while (true) yield return 1; } IEnumerable Enumerable_Error(Exception exception) { yield return 1; yield return 2; yield return 3; throw exception; } #endregion } }