/** * 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 static org.mockito.Mockito.*; import java.util.*; import java.util.concurrent.CountDownLatch; import org.junit.*; import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; import io.reactivex.internal.disposables.DisposableHelper; @RunWith(MockitoJUnitRunner.class) public class SerialDisposableTests { private SerialDisposable serialDisposable; @Before public void setUp() { serialDisposable = new SerialDisposable(); } @Test public void unsubscribingWithoutUnderlyingDoesNothing() { serialDisposable.dispose(); } @Test public void getDisposableShouldReturnset() { final Disposable underlying = mock(Disposable.class); serialDisposable.set(underlying); assertSame(underlying, serialDisposable.get()); final Disposable another = mock(Disposable.class); serialDisposable.set(another); assertSame(another, serialDisposable.get()); } @Test public void notDisposedWhenReplaced() { final Disposable underlying = mock(Disposable.class); serialDisposable.set(underlying); serialDisposable.replace(Disposables.empty()); serialDisposable.dispose(); verify(underlying, never()).dispose(); } @Test public void unsubscribingTwiceDoesUnsubscribeOnce() { Disposable underlying = mock(Disposable.class); serialDisposable.set(underlying); serialDisposable.dispose(); verify(underlying).dispose(); serialDisposable.dispose(); verifyNoMoreInteractions(underlying); } @Test public void settingSameDisposableTwiceDoesUnsubscribeIt() { Disposable underlying = mock(Disposable.class); serialDisposable.set(underlying); verifyZeroInteractions(underlying); serialDisposable.set(underlying); verify(underlying).dispose(); } @Test public void unsubscribingWithSingleUnderlyingUnsubscribes() { Disposable underlying = mock(Disposable.class); serialDisposable.set(underlying); underlying.dispose(); verify(underlying).dispose(); } @Test public void replacingFirstUnderlyingCausesUnsubscription() { Disposable first = mock(Disposable.class); serialDisposable.set(first); Disposable second = mock(Disposable.class); serialDisposable.set(second); verify(first).dispose(); } @Test public void whenUnsubscribingSecondUnderlyingUnsubscribed() { Disposable first = mock(Disposable.class); serialDisposable.set(first); Disposable second = mock(Disposable.class); serialDisposable.set(second); serialDisposable.dispose(); verify(second).dispose(); } @Test public void settingUnderlyingWhenUnsubscribedCausesImmediateUnsubscription() { serialDisposable.dispose(); Disposable underlying = mock(Disposable.class); serialDisposable.set(underlying); verify(underlying).dispose(); } @Test(timeout = 1000) public void settingUnderlyingWhenUnsubscribedCausesImmediateUnsubscriptionConcurrently() throws InterruptedException { final Disposable firstSet = mock(Disposable.class); serialDisposable.set(firstSet); final CountDownLatch start = new CountDownLatch(1); final int count = 10; final CountDownLatch end = new CountDownLatch(count); 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(); serialDisposable.dispose(); } catch (InterruptedException e) { fail(e.getMessage()); } finally { end.countDown(); } } }; t.start(); threads.add(t); } final Disposable underlying = mock(Disposable.class); start.countDown(); serialDisposable.set(underlying); end.await(); verify(firstSet).dispose(); verify(underlying).dispose(); for (final Thread t : threads) { t.join(); } } @Test public void concurrentSetDisposableShouldNotInterleave() throws InterruptedException { final int count = 10; final List<Disposable> subscriptions = new ArrayList<Disposable>(); final CountDownLatch start = new CountDownLatch(1); final CountDownLatch end = new CountDownLatch(count); final List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < count; i++) { final Disposable subscription = mock(Disposable.class); subscriptions.add(subscription); final Thread t = new Thread() { @Override public void run() { try { start.await(); serialDisposable.set(subscription); } catch (InterruptedException e) { fail(e.getMessage()); } finally { end.countDown(); } } }; t.start(); threads.add(t); } start.countDown(); end.await(); serialDisposable.dispose(); for (final Disposable subscription : subscriptions) { verify(subscription).dispose(); } for (final Thread t : threads) { t.join(); } } @Test public void disposeState() { Disposable empty = Disposables.empty(); SerialDisposable d = new SerialDisposable(empty); assertFalse(d.isDisposed()); assertSame(empty, d.get()); d.dispose(); assertTrue(d.isDisposed()); assertNotSame(empty, d.get()); assertNotSame(DisposableHelper.DISPOSED, d.get()); } }