// 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.Concurrency; using System.Reactive.Disposables; 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 namespace ReactiveTests.Tests { [TestClass] public class DisposableTests { [TestMethod] public void AnonymousDisposable_Create() { var d = Disposable.Create(() => { }); Assert.IsNotNull(d); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void AnonymousDisposable_CreateNull() { Disposable.Create(null); } [TestMethod] public void AnonymousDisposable_Dispose() { var disposed = false; var d = Disposable.Create(() => { disposed = true; }); Assert.IsFalse(disposed); d.Dispose(); Assert.IsTrue(disposed); var c = d as ICancelable; Assert.IsNotNull(c); Assert.IsTrue(c.IsDisposed); } [TestMethod] public void EmptyDisposable() { var d = Disposable.Empty; Assert.IsNotNull(d); d.Dispose(); } [TestMethod] public void BooleanDisposable() { var d = new BooleanDisposable(); Assert.IsFalse(d.IsDisposed); d.Dispose(); Assert.IsTrue(d.IsDisposed); d.Dispose(); Assert.IsTrue(d.IsDisposed); } [TestMethod] public void SingleAssignmentDisposable_SetNull() { var d = new SingleAssignmentDisposable(); d.Disposable = null; } [TestMethod] public void SingleAssignmentDisposable_DisposeAfterSet() { var disposed = false; var d = new SingleAssignmentDisposable(); var dd = Disposable.Create(() => { disposed = true; }); d.Disposable = dd; Assert.AreSame(dd, d.Disposable); Assert.IsFalse(disposed); d.Dispose(); Assert.IsTrue(disposed); d.Dispose(); Assert.IsTrue(disposed); Assert.IsTrue(d.IsDisposed); } [TestMethod] public void SingleAssignmentDisposable_DisposeBeforeSet() { var disposed = false; var d = new SingleAssignmentDisposable(); var dd = Disposable.Create(() => { disposed = true; }); Assert.IsFalse(disposed); d.Dispose(); Assert.IsFalse(disposed); Assert.IsTrue(d.IsDisposed); d.Disposable = dd; Assert.IsTrue(disposed); //Assert.IsNull(d.Disposable); // BREAKING CHANGE v2 > v1.x - Undefined behavior after disposal. d.Disposable.Dispose(); // This should be a nop. d.Dispose(); Assert.IsTrue(disposed); } [TestMethod] public void SingleAssignmentDisposable_SetMultipleTimes() { var d = new SingleAssignmentDisposable(); d.Disposable = Disposable.Empty; ReactiveAssert.Throws(() => { d.Disposable = Disposable.Empty; }); } [TestMethod] public void CompositeDisposable_ArgumentChecking() { ReactiveAssert.Throws(() => new CompositeDisposable(default(IDisposable[]))); ReactiveAssert.Throws(() => new CompositeDisposable(default(IEnumerable))); ReactiveAssert.Throws(() => new CompositeDisposable(-1)); } [TestMethod] public void CompositeDisposable_Contains() { var d1 = Disposable.Create(() => {} ); var d2 = Disposable.Create(() => { }); var g = new CompositeDisposable(d1, d2); Assert.AreEqual(2, g.Count); Assert.IsTrue(g.Contains(d1)); Assert.IsTrue(g.Contains(d2)); ReactiveAssert.Throws(() => g.Contains(null)); } [TestMethod] public void CompositeDisposable_IsReadOnly() { Assert.IsFalse(new CompositeDisposable().IsReadOnly); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void CompositeDisposable_CopyTo_Null() { new CompositeDisposable().CopyTo(null, 0); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void CompositeDisposable_CopyTo_Negative() { new CompositeDisposable().CopyTo(new IDisposable[2], -1); } [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))] public void CompositeDisposable_CopyTo_BeyondEnd() { new CompositeDisposable().CopyTo(new IDisposable[2], 2); } [TestMethod] public void CompositeDisposable_CopyTo() { var d1 = Disposable.Create(() => { }); var d2 = Disposable.Create(() => { }); var g = new CompositeDisposable(new List { d1, d2 }); var d = new IDisposable[3]; g.CopyTo(d, 1); Assert.AreSame(d1, d[1]); Assert.AreSame(d2, d[2]); } [TestMethod] public void CompositeDisposable_ToArray() { var d1 = Disposable.Create(() => { }); var d2 = Disposable.Create(() => { }); var g = new CompositeDisposable(d1, d2); Assert.AreEqual(2, g.Count); var x = Enumerable.ToArray(g); Assert.IsTrue(g.ToArray().SequenceEqual(new[] { d1, d2 })); } [TestMethod] public void CompositeDisposable_GetEnumerator() { var d1 = Disposable.Create(() => { }); var d2 = Disposable.Create(() => { }); var g = new CompositeDisposable(d1, d2); var lst = new List(); foreach (var x in g) lst.Add(x); Assert.IsTrue(lst.SequenceEqual(new[] { d1, d2 })); } [TestMethod] public void CompositeDisposable_GetEnumeratorNonGeneric() { var d1 = Disposable.Create(() => { }); var d2 = Disposable.Create(() => { }); var g = new CompositeDisposable(d1, d2); var lst = new List(); foreach (IDisposable x in (IEnumerable)g) lst.Add(x); Assert.IsTrue(lst.SequenceEqual(new[] { d1, d2 })); } [TestMethod] public void CompositeDisposable_CollectionInitializer() { var d1 = Disposable.Create(() => { }); var d2 = Disposable.Create(() => { }); var g = new CompositeDisposable { d1, d2 }; Assert.AreEqual(2, g.Count); Assert.IsTrue(g.Contains(d1)); Assert.IsTrue(g.Contains(d2)); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void CompositeDisposable_AddNull() { new CompositeDisposable().Add(null); } [TestMethod] public void CompositeDisposable_Add() { var d1 = Disposable.Create(() => { }); var d2 = Disposable.Create(() => { }); var g = new CompositeDisposable(d1); Assert.AreEqual(1, g.Count); Assert.IsTrue(g.Contains(d1)); g.Add(d2); Assert.AreEqual(2, g.Count); Assert.IsTrue(g.Contains(d2)); } [TestMethod] public void CompositeDisposable_AddAfterDispose() { var disp1 = false; var disp2 = false; var d1 = Disposable.Create(() => { disp1 = true; }); var d2 = Disposable.Create(() => { disp2 = true; }); var g = new CompositeDisposable(d1); Assert.AreEqual(1, g.Count); g.Dispose(); Assert.IsTrue(disp1); Assert.AreEqual(0, g.Count); // CHECK g.Add(d2); Assert.IsTrue(disp2); Assert.AreEqual(0, g.Count); // CHECK Assert.IsTrue(g.IsDisposed); } [TestMethod] public void CompositeDisposable_Remove() { var disp1 = false; var disp2 = false; var d1 = Disposable.Create(() => { disp1 = true; }); var d2 = Disposable.Create(() => { disp2 = true; }); var g = new CompositeDisposable(d1, d2); Assert.AreEqual(2, g.Count); Assert.IsTrue(g.Contains(d1)); Assert.IsTrue(g.Contains(d2)); Assert.IsTrue(g.Remove(d1)); Assert.AreEqual(1, g.Count); Assert.IsFalse(g.Contains(d1)); Assert.IsTrue(g.Contains(d2)); Assert.IsTrue(disp1); Assert.IsTrue(g.Remove(d2)); Assert.IsFalse(g.Contains(d1)); Assert.IsFalse(g.Contains(d2)); Assert.IsTrue(disp2); var disp3 = false; var d3 = Disposable.Create(() => { disp3 = true; }); Assert.IsFalse(g.Remove(d3)); Assert.IsFalse(disp3); } [TestMethod] public void CompositeDisposable_Clear() { var disp1 = false; var disp2 = false; var d1 = Disposable.Create(() => { disp1 = true; }); var d2 = Disposable.Create(() => { disp2 = true; }); var g = new CompositeDisposable(d1, d2); Assert.AreEqual(2, g.Count); g.Clear(); Assert.IsTrue(disp1); Assert.IsTrue(disp2); Assert.AreEqual(0, g.Count); var disp3 = false; var d3 = Disposable.Create(() => { disp3 = true; }); g.Add(d3); Assert.IsFalse(disp3); Assert.AreEqual(1, g.Count); } [TestMethod] public void CompositeDisposable_RemoveOptimizationBehavior() { var g = new CompositeDisposable(); var m = new Dictionary(); var r = new List(); var N = 100; for (int i = 0; i < N; i++) { var j = i; var d = Disposable.Create(() => r.Add(j)); m[j] = d; g.Add(d); } var d1 = Enumerable.Range(0, N).Where(i => i % 2 == 0).ToArray(); foreach (var i in d1) g.Remove(m[i]); Assert.IsTrue(r.SequenceEqual(d1)); var d2 = Enumerable.Range(0, N).Where(i => i % 3 == 0).ToArray(); foreach (var i in d2) g.Remove(m[i]); Assert.IsTrue(r.SequenceEqual(d1.Concat(d2.Where(x => !d1.Any(y => x == y))))); var d3 = Enumerable.Range(0, N).Where(i => i % 5 == 0).ToArray(); foreach (var i in d3) g.Remove(m[i]); Assert.IsTrue(r.SequenceEqual(d1.Concat(d2.Where(x => !d1.Any(y => x == y))).Concat(d3.Where(x => !d1.Any(y => x == y) && !d2.Any(y => x == y))))); g.Dispose(); var z = r.Except(d1.Union(d2).Union(d3)).ToArray(); Assert.IsTrue(z.SequenceEqual(Enumerable.Range(0, N).Where(i => !(i % 2 == 0 || i % 3 == 0 || i % 5 == 0)))); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void CompositeDisposable_RemoveNull() { new CompositeDisposable().Remove(null); } #if DESKTOPCLR40 || DESKTOPCLR45 [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void CancellationDisposable_Ctor_Null() { new CancellationDisposable(null); } [TestMethod] public void CancellationDisposable_DefaultCtor() { var c = new CancellationDisposable(); Assert.IsNotNull(c.Token); Assert.IsFalse(c.Token.IsCancellationRequested); Assert.IsTrue(c.Token.CanBeCanceled); c.Dispose(); Assert.IsTrue(c.IsDisposed); Assert.IsTrue(c.Token.IsCancellationRequested); } [TestMethod] public void CancellationDisposable_TokenCtor() { var t = new CancellationTokenSource(); var c = new CancellationDisposable(t); Assert.IsTrue(t.Token == c.Token); Assert.IsFalse(c.Token.IsCancellationRequested); Assert.IsTrue(c.Token.CanBeCanceled); c.Dispose(); Assert.IsTrue(c.IsDisposed); Assert.IsTrue(c.Token.IsCancellationRequested); } #endif [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void ContextDisposable_CreateNullContext() { new ContextDisposable(null, Disposable.Empty); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void ContextDisposable_CreateNullDisposable() { new ContextDisposable(new SynchronizationContext(), null); } [TestMethod] public void ContextDisposable() { var disp = false; var m = new MySync(); var c = new ContextDisposable(m, Disposable.Create(() => { disp = true; })); Assert.AreSame(m, c.Context); Assert.IsFalse(m._disposed); Assert.IsFalse(disp); c.Dispose(); Assert.IsTrue(c.IsDisposed); Assert.IsTrue(m._disposed); Assert.IsTrue(disp); } class MySync : SynchronizationContext { internal bool _disposed = false; public override void Post(SendOrPostCallback d, object state) { d(state); _disposed = true; } } [TestMethod] public void SerialDisposable_Ctor_Prop() { var m = new SerialDisposable(); Assert.IsNull(m.Disposable); } [TestMethod] public void SerialDisposable_ReplaceBeforeDispose() { var disp1 = false; var disp2 = false; var m = new SerialDisposable(); var d1 = Disposable.Create(() => { disp1 = true; }); m.Disposable = d1; Assert.AreSame(d1, m.Disposable); Assert.IsFalse(disp1); var d2 = Disposable.Create(() => { disp2 = true; }); m.Disposable = d2; Assert.AreSame(d2, m.Disposable); Assert.IsTrue(disp1); Assert.IsFalse(disp2); } [TestMethod] public void SerialDisposable_ReplaceAfterDispose() { var disp1 = false; var disp2 = false; var m = new SerialDisposable(); m.Dispose(); Assert.IsTrue(m.IsDisposed); var d1 = Disposable.Create(() => { disp1 = true; }); m.Disposable = d1; Assert.IsNull(m.Disposable); // CHECK Assert.IsTrue(disp1); var d2 = Disposable.Create(() => { disp2 = true; }); m.Disposable = d2; Assert.IsNull(m.Disposable); // CHECK Assert.IsTrue(disp2); } [TestMethod] public void SerialDisposable_Dispose() { var disp = false; var m = new SerialDisposable(); var d = Disposable.Create(() => { disp = true; }); m.Disposable = d; Assert.AreSame(d, m.Disposable); Assert.IsFalse(disp); m.Dispose(); Assert.IsTrue(m.IsDisposed); Assert.IsTrue(disp); //Assert.IsNull(m.Disposable); // BREAKING CHANGE v2 > v1.x - Undefined behavior after disposal. } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void RefCountDisposable_Ctor_Null() { new RefCountDisposable(null); } [TestMethod] public void RefCountDisposable_SingleReference() { var d = new BooleanDisposable(); var r = new RefCountDisposable(d); Assert.IsFalse(d.IsDisposed); r.Dispose(); Assert.IsTrue(d.IsDisposed); r.Dispose(); Assert.IsTrue(d.IsDisposed); } [TestMethod] public void RefCountDisposable_RefCounting() { var d = new BooleanDisposable(); var r = new RefCountDisposable(d); Assert.IsFalse(d.IsDisposed); var d1 = r.GetDisposable(); var d2 = r.GetDisposable(); Assert.IsFalse(d.IsDisposed); d1.Dispose(); Assert.IsFalse(d.IsDisposed); d2.Dispose(); Assert.IsFalse(d.IsDisposed); // CHECK r.Dispose(); Assert.IsTrue(d.IsDisposed); Assert.IsTrue(r.IsDisposed); var d3 = r.GetDisposable(); // CHECK d3.Dispose(); } [TestMethod] public void RefCountDisposable_PrimaryDisposesFirst() { var d = new BooleanDisposable(); var r = new RefCountDisposable(d); Assert.IsFalse(d.IsDisposed); var d1 = r.GetDisposable(); var d2 = r.GetDisposable(); Assert.IsFalse(d.IsDisposed); d1.Dispose(); Assert.IsFalse(d.IsDisposed); r.Dispose(); Assert.IsFalse(d.IsDisposed); d2.Dispose(); Assert.IsTrue(d.IsDisposed); } [TestMethod] public void ScheduledDisposable_Null() { ReactiveAssert.Throws(() => new ScheduledDisposable(null, Disposable.Empty)); ReactiveAssert.Throws(() => new ScheduledDisposable(Scheduler.Immediate, null)); } [TestMethod] public void ScheduledDisposable() { var d = new BooleanDisposable(); var s = new ScheduledDisposable(Scheduler.Immediate, d); Assert.IsFalse(d.IsDisposed); Assert.AreSame(Scheduler.Immediate, s.Scheduler); Assert.AreSame(d, s.Disposable); s.Dispose(); Assert.IsTrue(d.IsDisposed); Assert.IsTrue(s.IsDisposed); Assert.AreSame(Scheduler.Immediate, s.Scheduler); //Assert.AreSame(d, s.Disposable); // BREAKING CHANGE v2 > v1.x - Undefined behavior after disposal. s.Disposable.Dispose(); // This should be a nop. } [TestMethod] public void MultipleAssignmentDisposable() { var m = new MultipleAssignmentDisposable(); var disp1 = false; var d1 = Disposable.Create(() => { disp1 = true; }); m.Disposable = d1; Assert.AreSame(d1, m.Disposable); Assert.IsFalse(m.IsDisposed); var disp2 = false; var d2 = Disposable.Create(() => { disp2 = true; }); m.Disposable = d2; Assert.AreSame(d2, m.Disposable); Assert.IsFalse(m.IsDisposed); Assert.IsFalse(disp1); m.Dispose(); Assert.IsTrue(disp2); Assert.IsTrue(m.IsDisposed); //Assert.IsNull(m.Disposable); // BREAKING CHANGE v2 > v1.x - Undefined behavior after disposal. m.Disposable.Dispose(); // This should be a nop. var disp3 = false; var d3 = Disposable.Create(() => { disp3 = true; }); m.Disposable = d3; Assert.IsTrue(disp3); //Assert.IsNull(m.Disposable); // BREAKING CHANGE v2 > v1.x - Undefined behavior after disposal. m.Disposable.Dispose(); // This should be a nop. Assert.IsTrue(m.IsDisposed); } } }