/** * Copyright (c) 2016-present, RxJava Contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See * the License for the specific language governing permissions and limitations under the License. */ package io.reactivex.disposables; import static org.junit.Assert.*; import java.io.IOException; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import io.reactivex.TestHelper; import io.reactivex.exceptions.CompositeException; import io.reactivex.functions.Action; import io.reactivex.schedulers.Schedulers; public class CompositeDisposableTest { @Test public void testSuccess() { final AtomicInteger counter = new AtomicInteger(); CompositeDisposable s = new CompositeDisposable(); s.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { counter.incrementAndGet(); } })); s.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { counter.incrementAndGet(); } })); s.dispose(); assertEquals(2, counter.get()); } @Test(timeout = 1000) public void shouldUnsubscribeAll() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); final CompositeDisposable s = new CompositeDisposable(); final int count = 10; final CountDownLatch start = new CountDownLatch(1); for (int i = 0; i < count; i++) { s.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { counter.incrementAndGet(); } })); } final List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < count; i++) { final Thread t = new Thread() { @Override public void run() { try { start.await(); s.dispose(); } catch (final InterruptedException e) { fail(e.getMessage()); } } }; t.start(); threads.add(t); } start.countDown(); for (final Thread t : threads) { t.join(); } assertEquals(count, counter.get()); } @Test public void testException() { final AtomicInteger counter = new AtomicInteger(); CompositeDisposable s = new CompositeDisposable(); s.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { throw new RuntimeException("failed on first one"); } })); s.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { counter.incrementAndGet(); } })); try { s.dispose(); fail("Expecting an exception"); } catch (RuntimeException e) { // we expect this assertEquals(e.getMessage(), "failed on first one"); } // we should still have disposed to the second one assertEquals(1, counter.get()); } @Test public void testCompositeException() { final AtomicInteger counter = new AtomicInteger(); CompositeDisposable s = new CompositeDisposable(); s.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { throw new RuntimeException("failed on first one"); } })); s.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { throw new RuntimeException("failed on second one too"); } })); s.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { counter.incrementAndGet(); } })); try { s.dispose(); fail("Expecting an exception"); } catch (CompositeException e) { // we expect this assertEquals(e.getExceptions().size(), 2); } // we should still have disposed to the second one assertEquals(1, counter.get()); } @Test public void testRemoveUnsubscribes() { Disposable s1 = Disposables.empty(); Disposable s2 = Disposables.empty(); CompositeDisposable s = new CompositeDisposable(); s.add(s1); s.add(s2); s.remove(s1); assertTrue(s1.isDisposed()); assertFalse(s2.isDisposed()); } @Test public void testClear() { Disposable s1 = Disposables.empty(); Disposable s2 = Disposables.empty(); CompositeDisposable s = new CompositeDisposable(); s.add(s1); s.add(s2); assertFalse(s1.isDisposed()); assertFalse(s2.isDisposed()); s.clear(); assertTrue(s1.isDisposed()); assertTrue(s2.isDisposed()); assertFalse(s.isDisposed()); Disposable s3 = Disposables.empty(); s.add(s3); s.dispose(); assertTrue(s3.isDisposed()); assertTrue(s.isDisposed()); } @Test public void testUnsubscribeIdempotence() { final AtomicInteger counter = new AtomicInteger(); CompositeDisposable s = new CompositeDisposable(); s.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { counter.incrementAndGet(); } })); s.dispose(); s.dispose(); s.dispose(); // we should have only disposed once assertEquals(1, counter.get()); } @Test(timeout = 1000) public void testUnsubscribeIdempotenceConcurrently() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); final CompositeDisposable s = new CompositeDisposable(); final int count = 10; final CountDownLatch start = new CountDownLatch(1); s.add(Disposables.fromRunnable(new Runnable() { @Override public void run() { counter.incrementAndGet(); } })); final List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < count; i++) { final Thread t = new Thread() { @Override public void run() { try { start.await(); s.dispose(); } catch (final InterruptedException e) { fail(e.getMessage()); } } }; t.start(); threads.add(t); } start.countDown(); for (final Thread t : threads) { t.join(); } // we should have only disposed once assertEquals(1, counter.get()); } @Test public void testTryRemoveIfNotIn() { CompositeDisposable csub = new CompositeDisposable(); CompositeDisposable csub1 = new CompositeDisposable(); CompositeDisposable csub2 = new CompositeDisposable(); csub.add(csub1); csub.remove(csub1); csub.add(csub2); csub.remove(csub1); // try removing agian } @Test(expected = NullPointerException.class) public void testAddingNullDisposableIllegal() { CompositeDisposable csub = new CompositeDisposable(); csub.add(null); } @Test public void initializeVarargs() { Disposable d1 = Disposables.empty(); Disposable d2 = Disposables.empty(); CompositeDisposable cd = new CompositeDisposable(d1, d2); assertEquals(2, cd.size()); cd.clear(); assertEquals(0, cd.size()); assertTrue(d1.isDisposed()); assertTrue(d2.isDisposed()); Disposable d3 = Disposables.empty(); Disposable d4 = Disposables.empty(); cd = new CompositeDisposable(d3, d4); cd.dispose(); assertTrue(d3.isDisposed()); assertTrue(d4.isDisposed()); assertEquals(0, cd.size()); } @Test public void initializeIterable() { Disposable d1 = Disposables.empty(); Disposable d2 = Disposables.empty(); CompositeDisposable cd = new CompositeDisposable(Arrays.asList(d1, d2)); assertEquals(2, cd.size()); cd.clear(); assertEquals(0, cd.size()); assertTrue(d1.isDisposed()); assertTrue(d2.isDisposed()); Disposable d3 = Disposables.empty(); Disposable d4 = Disposables.empty(); cd = new CompositeDisposable(Arrays.asList(d3, d4)); assertEquals(2, cd.size()); cd.dispose(); assertTrue(d3.isDisposed()); assertTrue(d4.isDisposed()); assertEquals(0, cd.size()); } @Test public void addAll() { CompositeDisposable cd = new CompositeDisposable(); Disposable d1 = Disposables.empty(); Disposable d2 = Disposables.empty(); Disposable d3 = Disposables.empty(); cd.addAll(d1, d2); cd.addAll(d3); assertFalse(d1.isDisposed()); assertFalse(d2.isDisposed()); assertFalse(d3.isDisposed()); cd.clear(); assertTrue(d1.isDisposed()); assertTrue(d2.isDisposed()); d1 = Disposables.empty(); d2 = Disposables.empty(); cd = new CompositeDisposable(); cd.addAll(d1, d2); assertFalse(d1.isDisposed()); assertFalse(d2.isDisposed()); cd.dispose(); assertTrue(d1.isDisposed()); assertTrue(d2.isDisposed()); assertEquals(0, cd.size()); cd.clear(); assertEquals(0, cd.size()); } @Test public void addAfterDisposed() { CompositeDisposable cd = new CompositeDisposable(); cd.dispose(); Disposable d1 = Disposables.empty(); assertFalse(cd.add(d1)); assertTrue(d1.isDisposed()); d1 = Disposables.empty(); Disposable d2 = Disposables.empty(); assertFalse(cd.addAll(d1, d2)); assertTrue(d1.isDisposed()); assertTrue(d2.isDisposed()); } @Test public void delete() { CompositeDisposable cd = new CompositeDisposable(); Disposable d1 = Disposables.empty(); assertFalse(cd.delete(d1)); Disposable d2 = Disposables.empty(); cd.add(d2); assertFalse(cd.delete(d1)); cd.dispose(); assertFalse(cd.delete(d1)); } @Test public void disposeRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); Runnable run = new Runnable() { @Override public void run() { cd.dispose(); } }; TestHelper.race(run, run, Schedulers.io()); } } @Test public void addRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); Runnable run = new Runnable() { @Override public void run() { cd.add(Disposables.empty()); } }; TestHelper.race(run, run, Schedulers.io()); } } @Test public void addAllRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); Runnable run = new Runnable() { @Override public void run() { cd.addAll(Disposables.empty()); } }; TestHelper.race(run, run, Schedulers.io()); } } @Test public void removeRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); cd.add(d1); Runnable run = new Runnable() { @Override public void run() { cd.remove(d1); } }; TestHelper.race(run, run, Schedulers.io()); } } @Test public void deleteRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); cd.add(d1); Runnable run = new Runnable() { @Override public void run() { cd.delete(d1); } }; TestHelper.race(run, run, Schedulers.io()); } } @Test public void clearRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); cd.add(d1); Runnable run = new Runnable() { @Override public void run() { cd.clear(); } }; TestHelper.race(run, run, Schedulers.io()); } } @Test public void addDisposeRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); Runnable run = new Runnable() { @Override public void run() { cd.dispose(); } }; Runnable run2 = new Runnable() { @Override public void run() { cd.add(Disposables.empty()); } }; TestHelper.race(run, run2, Schedulers.io()); } } @Test public void addAllDisposeRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); Runnable run = new Runnable() { @Override public void run() { cd.dispose(); } }; Runnable run2 = new Runnable() { @Override public void run() { cd.addAll(Disposables.empty()); } }; TestHelper.race(run, run2, Schedulers.io()); } } @Test public void removeDisposeRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); cd.add(d1); Runnable run = new Runnable() { @Override public void run() { cd.dispose(); } }; Runnable run2 = new Runnable() { @Override public void run() { cd.remove(d1); } }; TestHelper.race(run, run2, Schedulers.io()); } } @Test public void deleteDisposeRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); cd.add(d1); Runnable run = new Runnable() { @Override public void run() { cd.dispose(); } }; Runnable run2 = new Runnable() { @Override public void run() { cd.delete(d1); } }; TestHelper.race(run, run2, Schedulers.io()); } } @Test public void clearDisposeRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); cd.add(d1); Runnable run = new Runnable() { @Override public void run() { cd.dispose(); } }; Runnable run2 = new Runnable() { @Override public void run() { cd.clear(); } }; TestHelper.race(run, run2, Schedulers.io()); } } @Test public void sizeDisposeRace() { for (int i = 0; i < 500; i++) { final CompositeDisposable cd = new CompositeDisposable(); final Disposable d1 = Disposables.empty(); cd.add(d1); Runnable run = new Runnable() { @Override public void run() { cd.dispose(); } }; Runnable run2 = new Runnable() { @Override public void run() { cd.size(); } }; TestHelper.race(run, run2, Schedulers.io()); } } @Test public void disposeThrowsIAE() { CompositeDisposable cd = new CompositeDisposable(); cd.add(Disposables.fromAction(new Action() { @Override public void run() throws Exception { throw new IllegalArgumentException(); } })); Disposable d1 = Disposables.empty(); cd.add(d1); try { cd.dispose(); fail("Failed to throw"); } catch (IllegalArgumentException ex) { // expected } assertTrue(d1.isDisposed()); } @Test public void disposeThrowsError() { CompositeDisposable cd = new CompositeDisposable(); cd.add(Disposables.fromAction(new Action() { @Override public void run() throws Exception { throw new AssertionError(); } })); Disposable d1 = Disposables.empty(); cd.add(d1); try { cd.dispose(); fail("Failed to throw"); } catch (AssertionError ex) { // expected } assertTrue(d1.isDisposed()); } @Test public void disposeThrowsCheckedException() { CompositeDisposable cd = new CompositeDisposable(); cd.add(Disposables.fromAction(new Action() { @Override public void run() throws Exception { throw new IOException(); } })); Disposable d1 = Disposables.empty(); cd.add(d1); try { cd.dispose(); fail("Failed to throw"); } catch (RuntimeException ex) { // expected if (!(ex.getCause() instanceof IOException)) { fail(ex.toString() + " should have thrown RuntimeException(IOException)"); } } assertTrue(d1.isDisposed()); } @SuppressWarnings("unchecked") static <E extends Throwable> void throwSneaky() throws E { throw (E)new IOException(); } @Test public void disposeThrowsCheckedExceptionSneaky() { CompositeDisposable cd = new CompositeDisposable(); cd.add(new Disposable() { @Override public void dispose() { CompositeDisposableTest.<RuntimeException>throwSneaky(); } @Override public boolean isDisposed() { // TODO Auto-generated method stub return false; } }); Disposable d1 = Disposables.empty(); cd.add(d1); try { cd.dispose(); fail("Failed to throw"); } catch (RuntimeException ex) { // expected if (!(ex.getCause() instanceof IOException)) { fail(ex.toString() + " should have thrown RuntimeException(IOException)"); } } assertTrue(d1.isDisposed()); } }