/*
 * Copyright (c) 2011-2018 Pivotal Software Inc, All Rights Reserved.
 *
 * 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
 *
 *       https://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 reactor.core.publisher;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.assertj.core.api.Assertions;
import org.assertj.core.api.Assumptions;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.reactivestreams.Subscription;

import reactor.core.CoreSubscriber;
import reactor.core.Disposable;
import reactor.core.Disposables;
import reactor.core.Fuseable;
import reactor.core.Scannable;
import reactor.core.Scannable.Attr;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import reactor.test.StepVerifier;
import reactor.test.publisher.TestPublisher;
import reactor.test.scheduler.VirtualTimeScheduler;
import reactor.test.subscriber.AssertSubscriber;
import reactor.test.util.RaceTestUtils;
import reactor.util.context.Context;

public class FluxSwitchOnFirstTest {

    @Test
    public void shouldNotSubscribeTwice() {
        Throwable[] throwables = new Throwable[1];
        CountDownLatch latch = new CountDownLatch(1);
        StepVerifier.create(Flux.just(1L)
                    .switchOnFirst((s, f) -> {
                        RaceTestUtils.race(
                            () -> f.subscribe(__ -> {}, t -> {
                                throwables[0] = t;
                                latch.countDown();
                            },  latch::countDown),
                            () -> f.subscribe(__ -> {}, t -> {
                                throwables[0] = t;
                                latch.countDown();
                            },  latch::countDown)
                        );

                        return Flux.empty();
                    }))
                    .expectSubscription()
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));


        Assertions.assertThat(throwables[0])
                  .isInstanceOf(IllegalStateException.class)
                  .hasMessage("FluxSwitchOnFirst allows only one Subscriber");
    }

    @Test
    public void shouldNotSubscribeTwiceConditional() {
        Throwable[] throwables = new Throwable[1];
        CountDownLatch latch = new CountDownLatch(1);
        StepVerifier.create(Flux.just(1L)
                                .switchOnFirst((s, f) -> {
                                    RaceTestUtils.race(
                                            () -> f.subscribe(__ -> {}, t -> {
                                                throwables[0] = t;
                                                latch.countDown();
                                            },  latch::countDown),
                                            () -> f.subscribe(__ -> {}, t -> {
                                                throwables[0] = t;
                                                latch.countDown();
                                            },  latch::countDown)
                                    );

                                    return Flux.empty();
                                })
                                .filter(e -> true)
                    )
                    .expectSubscription()
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));


        Assertions.assertThat(throwables[0])
                  .isInstanceOf(IllegalStateException.class)
                  .hasMessage("FluxSwitchOnFirst allows only one Subscriber");
    }

    @Test
    public void shouldNotSubscribeTwiceWhenCanceled() {
        CountDownLatch latch = new CountDownLatch(1);
        CountDownLatch nextLatch = new CountDownLatch(1);
        StepVerifier.create(Flux.just(1L)
                                .doOnComplete(() -> {
                                    try {
                                        if (!latch.await(5, TimeUnit.SECONDS)) throw new IllegalStateException("latch didn't complete in 5s");
                                    }
                                    catch (InterruptedException e) {
                                        throw new RuntimeException(e);
                                    }
                                })
                                .hide()
                                .publishOn(Schedulers.parallel())
                                .cancelOn(NoOpsScheduler.INSTANCE)
                                .doOnCancel(latch::countDown)
                                .switchOnFirst((s, f) -> f)
                                .doOnSubscribe(s ->
                                    Schedulers.boundedElastic()
                                            .schedule(() -> {
                                                try {
                                                    nextLatch.await();
                                                } catch (InterruptedException e) {
                                                    e.printStackTrace();
                                                }
                                                s.cancel();
                                            })
                                )
                                .doOnNext(t -> nextLatch.countDown())
        )
                    .expectSubscription()
                    .expectNext(1L)
                    .expectNoEvent(Duration.ofMillis(200))
                    .thenCancel()
                    .verifyThenAssertThat(Duration.ofSeconds(20))
                    .hasNotDroppedErrors();
    }

    @Test
    public void shouldNotSubscribeTwiceConditionalWhenCanceled() {
        CountDownLatch latch = new CountDownLatch(1);
        CountDownLatch nextLatch = new CountDownLatch(1);
        StepVerifier.create(Flux.just(1L)
                                .doOnComplete(() -> {
                                    try {
                                        if (!latch.await(5, TimeUnit.SECONDS)) throw new IllegalStateException("latch didn't complete in 5s");
                                    }
                                    catch (InterruptedException e) {
                                        throw new RuntimeException(e);
                                    }
                                })
                                .hide()
                                .publishOn(Schedulers.parallel())
                                .cancelOn(NoOpsScheduler.INSTANCE)
                                .doOnCancel(latch::countDown)
                                .switchOnFirst((s, f) -> f)
                                .filter(e -> true)
                                .doOnSubscribe(s ->
                                    Schedulers.boundedElastic()
                                        .schedule(() -> {
                                            try {
                                                nextLatch.await();
                                            } catch (InterruptedException e) {
                                                e.printStackTrace();
                                            }
                                            s.cancel();
                                        })
                                )
                                .doOnNext(t -> nextLatch.countDown())
        )
                    .expectSubscription()
                    .expectNext(1L)
                    .expectNoEvent(Duration.ofMillis(200))
                    .thenCancel()
                    .verifyThenAssertThat(Duration.ofSeconds(5))
                    .hasNotDroppedErrors();
    }

    @Test
    public void shouldSendOnErrorSignalConditional() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        RuntimeException error = new RuntimeException();
        StepVerifier.create(Flux.<Long>error(error)
                .switchOnFirst((s, f) -> {
                    first[0] = s;

                    return f;
                })
                .filter(e -> true)
        )
                    .expectSubscription()
                    .expectError(RuntimeException.class)
                    .verify(Duration.ofSeconds(5));


        Assertions.assertThat(first).containsExactly(Signal.error(error));
    }

    @Test
    public void shouldSendOnNextSignalConditional() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        StepVerifier.create(Flux.just(1L)
                                .switchOnFirst((s, f) -> {
                                    first[0] = s;

                                    return f;
                                })
                                .filter(e -> true)
                    )
                    .expectSubscription()
                    .expectNext(1L)
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));


        Assertions.assertThat((long) first[0].get()).isEqualTo(1L);
    }

    @Test
    public void shouldSendOnErrorSignalWithDelaySubscription() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        RuntimeException error = new RuntimeException();
        StepVerifier.create(Flux.<Long>error(error)
                .switchOnFirst((s, f) -> {
                    first[0] = s;

                    return f.delaySubscription(Duration.ofMillis(100));
                }))
                    .expectSubscription()
                    .expectError(RuntimeException.class)
                    .verify(Duration.ofSeconds(5));


        Assertions.assertThat(first).containsExactly(Signal.error(error));
    }

    @Test
    public void shouldSendOnCompleteSignalWithDelaySubscription() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        StepVerifier.create(Flux.<Long>empty()
                .switchOnFirst((s, f) -> {
                    first[0] = s;

                    return f.delaySubscription(Duration.ofMillis(100));
                }))
                    .expectSubscription()
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));


        Assertions.assertThat(first).containsExactly(Signal.complete());
    }

    @Test
    public void shouldSendOnErrorSignal() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        RuntimeException error = new RuntimeException();
        StepVerifier.create(Flux.<Long>error(error)
                                .switchOnFirst((s, f) -> {
                                    first[0] = s;

                                    return f;
                                }))
                    .expectSubscription()
                    .expectError(RuntimeException.class)
                    .verify(Duration.ofSeconds(5));


        Assertions.assertThat(first).containsExactly(Signal.error(error));
    }

    @Test
    public void shouldSendOnNextSignal() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        StepVerifier.create(Flux.just(1L)
                                .switchOnFirst((s, f) -> {
                                    first[0] = s;

                                    return f;
                                }))
                    .expectSubscription()
                    .expectNext(1L)
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));


        Assertions.assertThat((long) first[0].get()).isEqualTo(1L);
    }


    @Test
    public void shouldSendOnNextAsyncSignal() {
        for (int i = 0; i < 10000; i++) {
            @SuppressWarnings("unchecked") Signal<? extends Long>[] first = new Signal[1];

            StepVerifier.create(Flux.just(1L)
                                    .switchOnFirst((s, f) -> {
                                        first[0] = s;

                                        return f.subscribeOn(Schedulers.boundedElastic());
                                    }))
                        .expectSubscription()
                        .expectNext(1L)
                        .expectComplete()
                        .verify(Duration.ofSeconds(5));

            Assertions.assertThat((long) first[0].get())
                      .isEqualTo(1L);
        }
    }

    @Test
    public void shouldSendOnNextAsyncSignalConditional() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        StepVerifier.create(Flux.just(1L)
                                .switchOnFirst((s, f) -> {
                                    first[0] = s;

                                    return f.subscribeOn(Schedulers.boundedElastic());
                                })
                                .filter(p -> true)
                    )
                    .expectSubscription()
                    .expectNext(1L)
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));


        Assertions.assertThat((long) first[0].get()).isEqualTo(1L);
    }

    @Test
    public void shouldNeverSendIncorrectRequestSizeToUpstream() throws InterruptedException {
        TestPublisher<Long> publisher = TestPublisher.createCold();
        AtomicLong capture = new AtomicLong();
        ArrayList<Long> requested = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(1);
        Flux<Long> switchTransformed = publisher.flux()
                                                .doOnRequest(requested::add)
                                                .switchOnFirst((first, innerFlux) -> innerFlux);

        publisher.next(1L);
        publisher.complete();

        switchTransformed.subscribeWith(new LambdaSubscriber<>(capture::set, __ -> {}, latch::countDown, s -> s.request(1)));

        latch.await(5, TimeUnit.SECONDS);

        Assertions.assertThat(capture.get()).isEqualTo(1L);
        Assertions.assertThat(requested).containsExactly(1L);
    }

    @Test
    public void shouldNeverSendIncorrectRequestSizeToUpstreamConditional() throws InterruptedException {
        TestPublisher<Long> publisher = TestPublisher.createCold();
        AtomicLong capture = new AtomicLong();
        ArrayList<Long> requested = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(1);
        Flux<Long> switchTransformed = publisher.flux()
                                                .doOnRequest(requested::add)
                                                .switchOnFirst((first, innerFlux) -> innerFlux)
                                                .filter(e -> true);

        publisher.next(1L);
        publisher.complete();

        switchTransformed.subscribeWith(new LambdaSubscriber<>(capture::set, __ -> {}, latch::countDown, s -> s.request(1)));

        latch.await(5, TimeUnit.SECONDS);

        Assertions.assertThat(capture.get()).isEqualTo(1L);
        Assertions.assertThat(requested).containsExactly(1L);
    }

    @Test
    public void shouldBeRequestedOneFromUpstreamTwiceInCaseOfConditional() throws InterruptedException {
        TestPublisher<Long> publisher = TestPublisher.createCold();
        ArrayList<Long> capture = new ArrayList<>();
        ArrayList<Long> requested = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(1);
        Flux<Long> switchTransformed = publisher.flux()
                                                .doOnRequest(requested::add)
                                                .switchOnFirst((first, innerFlux) -> innerFlux)
                                                .filter(e -> false);

        publisher.next(1L);
        publisher.complete();

        switchTransformed.subscribeWith(new LambdaSubscriber<>(capture::add, __ -> {}, latch::countDown, s -> s.request(1)));

        latch.await(5, TimeUnit.SECONDS);

        Assertions.assertThat(capture).isEmpty();
        Assertions.assertThat(requested).containsExactly(1L, 1L);
    }

    @Test
    public void shouldBeRequestedExactlyOneAndThenLongMaxValue() throws InterruptedException {
        TestPublisher<Long> publisher = TestPublisher.createCold();
        ArrayList<Long> capture = new ArrayList<>();
        ArrayList<Long> requested = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(1);
        Flux<Long> switchTransformed = publisher.flux()
                                                .doOnRequest(requested::add)
                                                .switchOnFirst((first, innerFlux) -> innerFlux);

        publisher.next(1L);
        publisher.complete();

        switchTransformed.subscribe(capture::add, __ -> {}, latch::countDown);

        latch.await(5, TimeUnit.SECONDS);

        Assertions.assertThat(capture).containsExactly(1L);
        Assertions.assertThat(requested).containsExactly(1L, Long.MAX_VALUE);
    }

    @Test
    public void shouldBeRequestedExactlyOneAndThenLongMaxValueConditional() throws InterruptedException {
        TestPublisher<Long> publisher = TestPublisher.createCold();
        ArrayList<Long> capture = new ArrayList<>();
        ArrayList<Long> requested = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(1);
        Flux<Long> switchTransformed = publisher.flux()
                                                .doOnRequest(requested::add)
                                                .switchOnFirst((first, innerFlux) -> innerFlux);

        publisher.next(1L);
        publisher.complete();

        switchTransformed.subscribe(capture::add, __ -> {}, latch::countDown);

        latch.await(5, TimeUnit.SECONDS);

        Assertions.assertThat(capture).containsExactly(1L);
        Assertions.assertThat(requested).containsExactly(1L, Long.MAX_VALUE);
    }

    @Test
    public void shouldReturnCorrectContextOnEmptySource() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        Flux<Long> switchTransformed = Flux.<Long>empty()
                .switchOnFirst((f, innerFlux) -> {
                    first[0] = f;
                    innerFlux.subscribe();

                    return Flux.<Long>empty();
                })
                .subscriberContext(Context.of("a", "c"))
                .subscriberContext(Context.of("c", "d"));

        StepVerifier.create(switchTransformed, 0)
                    .expectSubscription()
                    .thenRequest(1)
                    .expectAccessibleContext()
                    .contains("a", "c")
                    .contains("c", "d")
                    .then()
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));

        Assertions.assertThat(first).containsExactly(Signal.complete(Context.of("a", "c").put("c", "d")));
    }

    @Test
    public void shouldReturnCorrectContextIfLoosingChain() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        Flux<Long> switchTransformed = Flux.<Long>empty()
                .switchOnFirst((f, innerFlux) -> {
                    first[0] = f;
                    return innerFlux;
                })
                .subscriberContext(Context.of("a", "c"))
                .subscriberContext(Context.of("c", "d"));

        StepVerifier.create(switchTransformed, 0)
                .expectSubscription()
                .thenRequest(1)
                .expectAccessibleContext()
                .contains("a", "c")
                .contains("c", "d")
                .then()
                .expectComplete()
                .verify(Duration.ofSeconds(5));

        Assertions.assertThat(first).containsExactly(Signal.complete(Context.of("a", "c").put("c", "d")));
    }

    @Test
    public void shouldNotFailOnIncorrectPublisherBehavior() {
        TestPublisher<Long> publisher =
                TestPublisher.createNoncompliant(TestPublisher.Violation.CLEANUP_ON_TERMINATE);
        Flux<Long> switchTransformed = publisher.flux()
                                                .switchOnFirst((first, innerFlux) -> innerFlux.subscriberContext(Context.of("a", "b")));

        StepVerifier.create(new Flux<Long>() {
            @Override
            public void subscribe(CoreSubscriber<? super Long> actual) {
                switchTransformed.subscribe(actual);
                publisher.next(1L);
            }
        }, 0)
                    .thenRequest(1)
                    .expectNext(1L)
                    .thenRequest(1)
                    .then(() -> publisher.next(2L))
                    .expectNext(2L)
                    .then(() -> publisher.error(new RuntimeException()))
                    .then(() -> publisher.error(new RuntimeException()))
                    .then(() -> publisher.error(new RuntimeException()))
                    .then(() -> publisher.error(new RuntimeException()))
                    .expectError()
                    .verifyThenAssertThat(Duration.ofSeconds(5))
                    .hasDroppedErrors(3)
                    .tookLessThan(Duration.ofSeconds(10));

        publisher.assertWasRequested();
        publisher.assertNoRequestOverflow();
    }

    @Test
    // Since context is immutable, with switchOnFirst it should not be mutable as well. Upstream should observe downstream
    // Inner should be able to access downstreamContext but should not modify upstream context after the first element
    public void shouldNotBeAbleToAccessUpstreamContext() {
        TestPublisher<Long> publisher = TestPublisher.createCold();

        Flux<String> switchTransformed = publisher.flux()
                                                  .switchOnFirst(
                                                      (first, innerFlux) -> innerFlux.map(String::valueOf)
                                                                                     .subscriberContext(Context.of("a", "b"))
                                                  )
                                                  .subscriberContext(Context.of("a", "c"))
                                                  .subscriberContext(Context.of("c", "d"));

        publisher.next(1L);

        StepVerifier.create(switchTransformed, 0)
                    .thenRequest(1)
                    .expectNext("1")
                    .thenRequest(1)
                    .then(() -> publisher.next(2L))
                    .expectNext("2")
                    .expectAccessibleContext()
                    .contains("a", "c")
                    .contains("c", "d")
                    .then()
                    .then(publisher::complete)
                    .expectComplete()
                    .verify(Duration.ofSeconds(10));

        publisher.assertWasRequested();
        publisher.assertNoRequestOverflow();
    }

    @Test
    public void shouldNotHangWhenOneElementUpstream() {
        TestPublisher<Long> publisher = TestPublisher.createCold();

        Flux<String> switchTransformed = publisher.flux()
                                                  .switchOnFirst((first, innerFlux) ->
                                                      innerFlux.map(String::valueOf)
                                                               .subscriberContext(Context.of("a", "b"))
                                                  )
                                                  .subscriberContext(Context.of("a", "c"))
                                                  .subscriberContext(Context.of("c", "d"));

        publisher.next(1L);
        publisher.complete();

        StepVerifier.create(switchTransformed, 0)
                    .thenRequest(1)
                    .expectNext("1")
                    .expectComplete()
                    .verify(Duration.ofSeconds(10));

        publisher.assertWasRequested();
        publisher.assertNoRequestOverflow();
    }

    @Test
    public void backpressureTest() {
        TestPublisher<Long> publisher = TestPublisher.createCold();
        AtomicLong requested = new AtomicLong();

        Flux<String> switchTransformed = publisher.flux()
                                                  .doOnRequest(requested::addAndGet)
                                                  .switchOnFirst((first, innerFlux) -> innerFlux.map(String::valueOf));

        publisher.next(1L);

        StepVerifier.create(switchTransformed, 0)
                    .thenRequest(1)
                    .expectNext("1")
                    .thenRequest(1)
                    .then(() -> publisher.next(2L))
                    .expectNext("2")
                    .then(publisher::complete)
                    .expectComplete()
                    .verify(Duration.ofSeconds(10));

        publisher.assertWasRequested();
        publisher.assertNoRequestOverflow();

        Assertions.assertThat(requested.get()).isEqualTo(2L);
    }

    @Test
    public void backpressureConditionalTest() {
        Flux<Integer> publisher = Flux.range(0, 10000);
        AtomicLong requested = new AtomicLong();

        Flux<String> switchTransformed = publisher.doOnRequest(requested::addAndGet)
                                                  .switchOnFirst((first, innerFlux) -> innerFlux.map(String::valueOf))
                                                  .filter(e -> false);

        StepVerifier.create(switchTransformed, 0)
                    .thenRequest(1)
                    .expectComplete()
                    .verify(Duration.ofSeconds(10));

        Assertions.assertThat(requested.get()).isEqualTo(2L);

    }

    @Test
    public void backpressureHiddenConditionalTest() {
        Flux<Integer> publisher = Flux.range(0, 10000);
        AtomicLong requested = new AtomicLong();

        Flux<String> switchTransformed = publisher.doOnRequest(requested::addAndGet)
                                                  .switchOnFirst((first, innerFlux) -> innerFlux.map(String::valueOf)
                                                                                                .hide())
                                                  .filter(e -> false);

        StepVerifier.create(switchTransformed, 0)
                    .thenRequest(1)
                    .expectComplete()
                    .verify(Duration.ofSeconds(10));

        Assertions.assertThat(requested.get()).isEqualTo(10001L);
    }

    @Test
    public void backpressureDrawbackOnConditionalInTransformTest() {
        Flux<Integer> publisher = Flux.range(0, 10000);
        AtomicLong requested = new AtomicLong();

        Flux<String> switchTransformed = publisher.doOnRequest(requested::addAndGet)
                                                  .switchOnFirst((first, innerFlux) -> innerFlux
                                                          .map(String::valueOf)
                                                          .filter(e -> false));

        StepVerifier.create(switchTransformed, 0)
                    .thenRequest(1)
                    .expectComplete()
                    .verify(Duration.ofSeconds(10));

        Assertions.assertThat(requested.get()).isEqualTo(10001L);
    }

    @Test
    public void shouldErrorOnOverflowTest() {
        TestPublisher<Long> publisher = TestPublisher.createCold();

        Flux<String> switchTransformed = publisher.flux()
                                                  .switchOnFirst((first, innerFlux) -> innerFlux.map(String::valueOf));

        publisher.next(1L);

        StepVerifier.create(switchTransformed, 0)
                    .thenRequest(1)
                    .expectNext("1")
                    .then(() -> publisher.next(2L))
                    .expectErrorSatisfies(t -> Assertions
                        .assertThat(t)
                        .isInstanceOf(IllegalStateException.class)
                        .hasMessage("Can't deliver value due to lack of requests")
                    )
                    .verify(Duration.ofSeconds(10));

        publisher.assertWasRequested();
        publisher.assertNoRequestOverflow();
    }

    @Test
    public void shouldPropagateonCompleteCorrectly() {
        Flux<String> switchTransformed = Flux.empty()
                                             .switchOnFirst((first, innerFlux) -> innerFlux.map(String::valueOf));

        StepVerifier.create(switchTransformed)
                    .expectComplete()
                    .verify(Duration.ofSeconds(10));
    }

    @Test
    public void shouldPropagateOnCompleteWithMergedElementsCorrectly() {
        Flux<String> switchTransformed = Flux.empty()
                                             .switchOnFirst((first, innerFlux) -> innerFlux.map(String::valueOf)
                                                                                           .mergeWith(Flux.just("1", "2", "3")));

        StepVerifier.create(switchTransformed)
                    .expectNext("1", "2", "3")
                    .expectComplete()
                    .verify(Duration.ofSeconds(10));
    }

    @Test
    public void shouldPropagateErrorCorrectly() {
        Flux<String> switchTransformed = Flux.error(new RuntimeException("hello"))
                                             .transform(flux -> new FluxSwitchOnFirst<>(
                                                     flux,
                                                     (first, innerFlux) -> innerFlux.map(
                                                             String::valueOf), true));

        StepVerifier.create(switchTransformed)
                    .expectErrorMessage("hello")
                    .verify(Duration.ofSeconds(10));
    }

    @Test
    public void shouldBeAbleToBeCancelledProperly() throws InterruptedException {
        CountDownLatch latch1 = new CountDownLatch(1);
        CountDownLatch latch2 = new CountDownLatch(1);
        TestPublisher<Integer> publisher = TestPublisher.createCold();
        Flux<String> switchTransformed = publisher.flux()
                .doOnCancel(latch2::countDown)
                .switchOnFirst((first, innerFlux) -> innerFlux.map(String::valueOf))
                .doOnCancel(() -> {
                    try {
                        if (!latch1.await(5, TimeUnit.SECONDS)) throw new IllegalStateException("latch didn't complete in 5s");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                })
                .cancelOn(Schedulers.boundedElastic());

        publisher.next(1);

        StepVerifier stepVerifier = StepVerifier.create(switchTransformed, 0)
                .thenCancel()
                .verifyLater();

        latch1.countDown();
        stepVerifier.verify(Duration.ofSeconds(10));

        Assertions.assertThat(latch2.await(1, TimeUnit.SECONDS)).isTrue();

        Instant endTime = Instant.now().plusSeconds(5);
        while (!publisher.wasCancelled()) {
            if (endTime.isBefore(Instant.now())) {
                break;
            }
        }
        publisher.assertCancelled();
        publisher.assertWasRequested();
    }

    @Test
    public void shouldBeAbleToBeCancelledProperly2() {
        TestPublisher<Integer> publisher = TestPublisher.createCold();
        Flux<String> switchTransformed = publisher.flux()
                                                  .switchOnFirst((first, innerFlux) ->
                                                      innerFlux
                                                          .map(String::valueOf)
                                                          .take(1)
                                                  );

        publisher.next(1);
        publisher.next(2);
        publisher.next(3);
        publisher.next(4);

        StepVerifier.create(switchTransformed, 1)
                    .expectNext("1")
                    .expectComplete()
                    .verify(Duration.ofSeconds(10));

        publisher.assertCancelled();
        publisher.assertWasRequested();
    }

    @Test
    public void shouldBeAbleToBeCancelledProperly3() {
        TestPublisher<Integer> publisher = TestPublisher.createCold();
        Flux<String> switchTransformed = publisher.flux()
                                                  .switchOnFirst((first, innerFlux) ->
                                                          innerFlux
                                                                  .map(String::valueOf)
                                                  )
                                                  .take(1);

        publisher.next(1);
        publisher.next(2);
        publisher.next(3);
        publisher.next(4);

        StepVerifier.create(switchTransformed, 1)
                    .expectNext("1")
                    .expectComplete()
                    .verify(Duration.ofSeconds(10));

        publisher.assertCancelled();
        publisher.assertWasRequested();
    }

    @Test
    public void shouldBeAbleToCatchDiscardedElement() {
        TestPublisher<Integer> publisher = TestPublisher.create();
        Integer[] discarded = new Integer[1];
        Flux<String> switchTransformed = publisher.flux()
                                                  .switchOnFirst((first, innerFlux) -> innerFlux.map(String::valueOf))
                                                  .doOnDiscard(Integer.class, e -> discarded[0] = e);

        StepVerifier.create(switchTransformed, 0)
                    .expectSubscription()
                    .then(() -> publisher.next(1))
                    .thenCancel()
                    .verify(Duration.ofSeconds(10));

        publisher.assertCancelled();
        publisher.assertWasRequested();

        Assertions.assertThat(discarded).containsExactly(1);
    }

    @Test
    public void shouldBeAbleToCatchDiscardedElementInCaseOfConditional() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        CountDownLatch latch2 = new CountDownLatch(1);
        TestPublisher<Integer> publisher = TestPublisher.create();
        int[] discarded = new int[1];
        Flux<String> switchTransformed = publisher.flux()
                .doOnCancel(latch2::countDown)
                .switchOnFirst((first, innerFlux) -> innerFlux.map(String::valueOf))
                .filter(t -> true)
                .doOnDiscard(Integer.class, e -> discarded[0] = e)
                .doOnCancel(() -> {
                    try {
                        if (!latch.await(5, TimeUnit.SECONDS)) throw new IllegalStateException("latch didn't complete in 5s");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                })
                .cancelOn(Schedulers.boundedElastic());

        StepVerifier stepVerifier = StepVerifier.create(switchTransformed, 0)
                .expectSubscription()
                .then(() -> publisher.next(1))
                .thenCancel()
                .verifyLater();

        latch.countDown();
        stepVerifier.verify(Duration.ofSeconds(1));
        Assertions.assertThat(latch2.await(1, TimeUnit.SECONDS)).isTrue();

        Instant endTime = Instant.now().plusSeconds(5);
        while (discarded[0] == 0) {
            if (endTime.isBefore(Instant.now())) {
                break;
            }
        }

        publisher.assertCancelled();
        publisher.assertWasRequested();

        Assertions.assertThat(discarded).containsExactly(1);
    }

    @Test
    public void shouldBeAbleToCancelSubscription() throws InterruptedException {
        Flux<Long> publisher = Flux.just(1L);
        ArrayList<Integer> capturedElementsNumber = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            final ArrayList<Throwable> dropped = new ArrayList<>();
            final AtomicLong requested = new AtomicLong();
            final CountDownLatch latch = new CountDownLatch(1);
            final AssertSubscriber<Long> assertSubscriber = new AssertSubscriber<>(Context.of(Hooks.KEY_ON_ERROR_DROPPED, (Consumer<Throwable>) dropped::add), 0);
            final Flux<Long> switchTransformed = publisher
                                                    .doOnRequest(requested::addAndGet)
                                                    .doOnCancel(latch::countDown)
                                                    .switchOnFirst((first, innerFlux) -> innerFlux.doOnComplete(latch::countDown));

            switchTransformed.subscribe(assertSubscriber);

            RaceTestUtils.race(assertSubscriber::cancel, () -> assertSubscriber.request(1));

            Assertions.assertThat(latch.await(500, TimeUnit.SECONDS)).isTrue();

            capturedElementsNumber.add(assertSubscriber.values().size());
        }

        Assumptions.assumeThat(capturedElementsNumber).contains(0);
        Assumptions.assumeThat(capturedElementsNumber).contains(1);
    }

    @Test
    public void shouldReturnNormallyIfExceptionIsThrownOnNextDuringSwitching() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        Optional<?> expectedCause = Optional.of(1L);

        StepVerifier.create(Flux.just(1L)
                                .switchOnFirst((s, f) -> {
                                    first[0] = s;
                                    throw new NullPointerException();
                                }))
                    .expectSubscription()
                    .expectError(NullPointerException.class)
                    .verifyThenAssertThat(Duration.ofSeconds(5))
                    .hasOperatorErrorsSatisfying(c ->
                        Assertions.assertThat(c)
                                  .hasOnlyOneElementSatisfying(t -> {
                                      Assertions.assertThat(t.getT1()).containsInstanceOf(NullPointerException.class);
                                      Assertions.assertThat(t.getT2()).isEqualTo(expectedCause);
                                  })
                    );


        Assertions.assertThat((long) first[0].get()).isEqualTo(1L);
    }

    @Test
    public void shouldReturnNormallyIfExceptionIsThrownOnErrorDuringSwitching() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        NullPointerException npe = new NullPointerException();
        RuntimeException error = new RuntimeException();
        StepVerifier.create(Flux.<Long>error(error)
                                .switchOnFirst((s, f) -> {
                                    first[0] = s;
                                    throw npe;
                                }))
                    .expectSubscription()
                    .verifyError(NullPointerException.class);


        Assertions.assertThat(first).containsExactly(Signal.error(error));
    }

    @Test
    public void shouldReturnNormallyIfExceptionIsThrownOnCompleteDuringSwitching() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        StepVerifier.create(Flux.<Long>empty()
                            .switchOnFirst((s, f) -> {
                                first[0] = s;
                                throw new NullPointerException();
                            })
                    )
                    .expectSubscription()
                    .expectError(NullPointerException.class)
                    .verifyThenAssertThat()
                    .hasOperatorErrorMatching(t -> {
                        Assertions.assertThat(t)
                                  .isInstanceOf(NullPointerException.class);
                        return true;
                    });


        Assertions.assertThat(first).containsExactly(Signal.complete());
    }

    @Test
    public void shouldReturnNormallyIfExceptionIsThrownOnNextDuringSwitchingConditional() {
        @SuppressWarnings("unchecked")
        Signal<? extends Integer>[] first = new Signal[1];
        Optional<?> expectedCause = Optional.of(1);

        StepVerifier
            .create(
                Flux.range(1, 100)
                    .switchOnFirst((s, f) -> {
                        first[0] = s;
                        throw new NullPointerException();
                    })
                    .filter(__ -> true)
            )
            .expectSubscription()
            .expectError(NullPointerException.class)
            .verifyThenAssertThat()
            .hasOperatorErrorsSatisfying(c ->
                Assertions.assertThat(c)
                        .hasOnlyOneElementSatisfying(t -> {
                            Assertions.assertThat(t.getT1()).containsInstanceOf(NullPointerException.class);
                            Assertions.assertThat(t.getT2()).isEqualTo(expectedCause);
                        })
            );


        Assertions.assertThat((long) first[0].get()).isEqualTo(1L);
    }

    @Test
    public void shouldReturnNormallyIfExceptionIsThrownOnErrorDuringSwitchingConditional() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        NullPointerException npe = new NullPointerException();
        RuntimeException error = new RuntimeException();
        StepVerifier.create(Flux.<Long>error(error)
                .switchOnFirst((s, f) -> {
                    first[0] = s;
                    throw npe;
                }).filter(__ -> true))
                .expectSubscription()
                .verifyError(NullPointerException.class);


        Assertions.assertThat(first).containsExactly(Signal.error(error));
    }

    @Test
    public void shouldReturnNormallyIfExceptionIsThrownOnCompleteDuringSwitchingConditional() {
        @SuppressWarnings("unchecked")
        Signal<? extends Long>[] first = new Signal[1];

        StepVerifier.create(Flux.<Long>empty()
                .switchOnFirst((s, f) -> {
                    first[0] = s;
                    throw new NullPointerException();
                }).filter(__ -> true)
        )
                .expectSubscription()
                .expectError(NullPointerException.class)
                .verifyThenAssertThat()
                .hasOperatorErrorMatching(t -> {
                    Assertions.assertThat(t)
                            .isInstanceOf(NullPointerException.class);
                    return true;
                });


        Assertions.assertThat(first).containsExactly(Signal.complete());
    }

    @Test
    public void sourceSubscribedOnce() {
        AtomicInteger subCount = new AtomicInteger();
        Flux<Integer> source = Flux.range(1, 10)
                                   .hide()
                                   .doOnSubscribe(subscription -> subCount.incrementAndGet());

        StepVerifier.create(source.switchOnFirst((s, f) -> f.filter(v -> v % 2 == s.get())))
                    .expectNext(1, 3, 5, 7, 9)
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));

        Assertions.assertThat(subCount).hasValue(1);
    }

    @Test
    public void checkHotSource() {
        FluxIdentityProcessor<Long> processor = Processors.replay(1);

        processor.onNext(1L);
        processor.onNext(2L);
        processor.onNext(3L);


        StepVerifier.create(processor.switchOnFirst((s, f) -> f.filter(v -> v % s.get() == 0)))
                    .expectNext(3L)
                    .then(() -> {
                        processor.onNext(4L);
                        processor.onNext(5L);
                        processor.onNext(6L);
                        processor.onNext(7L);
                        processor.onNext(8L);
                        processor.onNext(9L);
                        processor.onComplete();
                    })
                    .expectNext(6L, 9L)
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));
    }

    @Test
    public void shouldCancelSourceOnUnrelatedPublisherComplete() {
        FluxIdentityProcessor<Long> testPublisher = Processors.multicast();

        testPublisher.onNext(1L);

        StepVerifier.create(testPublisher.switchOnFirst((s, f) -> Flux.empty()))
                    .expectSubscription()
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));

        Assertions.assertThat(testPublisher.scan(Attr.CANCELLED)).isTrue();
    }

    @Test
    public void shouldNotCancelSourceOnUnrelatedPublisherComplete() {
        FluxIdentityProcessor<Long> testPublisher = Processors.multicast();

        testPublisher.onNext(1L);

        StepVerifier.create(testPublisher.switchOnFirst((s, f) -> Flux.empty(), false))
                .expectSubscription()
                .expectComplete()
                .verify(Duration.ofSeconds(5));

        Assertions.assertThat(testPublisher.scan(Attr.CANCELLED)).isFalse();
    }

    @Test
    public void shouldCancelSourceOnUnrelatedPublisherError() {
        FluxIdentityProcessor<Long> testPublisher = Processors.multicast();

        testPublisher.onNext(1L);

        StepVerifier.create(testPublisher.switchOnFirst((s, f) -> Flux.error(new RuntimeException("test"))))
                    .expectSubscription()
                    .expectErrorSatisfies(t ->
                        Assertions.assertThat(t)
                                  .hasMessage("test")
                                  .isExactlyInstanceOf(RuntimeException.class)
                    )
                    .verify(Duration.ofSeconds(5));

        Assertions.assertThat(testPublisher.scan(Attr.CANCELLED)).isTrue();
    }

    @Test
    public void shouldCancelSourceOnUnrelatedPublisherCancel() {
        TestPublisher<Long> testPublisher = TestPublisher.create();

        StepVerifier.create(testPublisher.flux().switchOnFirst((s, f) -> Flux.error(new RuntimeException("test"))))
                    .expectSubscription()
                    .thenCancel()
                    .verify(Duration.ofSeconds(5));

        Assertions.assertThat(testPublisher.wasCancelled()).isTrue();
    }

    @Test
    public void shouldCancelSourceOnUnrelatedPublisherCompleteConditional() {
        FluxIdentityProcessor<Long> testPublisher = Processors.multicast();

        testPublisher.onNext(1L);

        StepVerifier.create(testPublisher.switchOnFirst((s, f) -> Flux.empty().delaySubscription(Duration.ofMillis(10))).filter(__ -> true))
                    .then(() -> {
                        List<? extends Scannable> subs = testPublisher.inners().collect(Collectors.toList());
                        Assertions.assertThat(subs)
                                  .hasSize(1)
                                  .first()
                                  .extracting(psi -> psi.scan(Attr.ACTUAL))
                                  .isInstanceOf(Fuseable.ConditionalSubscriber.class);
                    })
                    .expectComplete()
                    .verify(Duration.ofSeconds(5));

        Assertions.assertThat(testPublisher.scan(Attr.CANCELLED)).isTrue();
    }

    @Test
    public void shouldNotCancelSourceOnUnrelatedPublisherCompleteConditional() {
        FluxIdentityProcessor<Long> testPublisher = Processors.multicast();

        testPublisher.onNext(1L);

        StepVerifier.create(testPublisher.switchOnFirst((s, f) -> Flux.empty().delaySubscription(Duration.ofMillis(10)), false).filter(__ -> true))
                .then(() -> {
                    List<? extends Scannable> subs = testPublisher.inners().collect(Collectors.toList());
                    Assertions.assertThat(subs)
                              .hasSize(1)
                              .first()
                              .extracting(psi -> psi.scan(Attr.ACTUAL))
                              .isInstanceOf(Fuseable.ConditionalSubscriber.class);
                })
                .expectComplete()
                .verify(Duration.ofSeconds(5));

        Assertions.assertThat(testPublisher.scan(Attr.CANCELLED)).isFalse();
    }

    @Test
    public void shouldCancelInnerSubscriptionImmediatelyUpOnReceivingIfDownstreamIsAlreadyCancelledConditional() {
        VirtualTimeScheduler virtualTimeScheduler = VirtualTimeScheduler.getOrSet();
        TestPublisher<Long> testPublisher = TestPublisher.create();
        TestPublisher<Long> testPublisherInner = TestPublisher.create();

        try {
            StepVerifier
                .create(
                    testPublisher
                        .flux()
                        .switchOnFirst((s, f) ->
                            testPublisherInner
                                .flux()
                                .transform(Operators.lift((__, cs) -> new BaseSubscriber<Long>() {
                                    @Override
                                    protected void hookOnSubscribe(Subscription subscription) {
                                        Schedulers.parallel().schedule(() -> cs.onSubscribe(this), 1, TimeUnit.SECONDS);
                                    }
                                })),
                            false
                        )
                        .filter(__ -> true)
                )
                .expectSubscription()
                .then(() -> testPublisher.next(1L))
                .thenCancel()
                .verify(Duration.ofSeconds(5));

            Assertions.assertThat(testPublisher.wasCancelled()).isTrue();
            Assertions.assertThat(testPublisherInner.wasCancelled()).isFalse();
            virtualTimeScheduler.advanceTimeBy(Duration.ofMillis(1000));
            Assertions.assertThat(testPublisherInner.wasCancelled()).isTrue();
        } finally {
            VirtualTimeScheduler.reset();
        }
    }

    @Test
    public void shouldCancelInnerSubscriptionImmediatelyUpOnReceivingIfDownstreamIsAlreadyCancelled() {
        VirtualTimeScheduler virtualTimeScheduler = VirtualTimeScheduler.getOrSet();
        TestPublisher<Long> testPublisher = TestPublisher.create();
        TestPublisher<Long> testPublisherInner = TestPublisher.create();

        try {
            StepVerifier
                    .create(
                            testPublisher
                                    .flux()
                                    .switchOnFirst((s, f) ->
                                                    testPublisherInner
                                                            .flux()
                                                            .transform(Operators.lift((__, cs) -> new BaseSubscriber<Long>() {
                                                                @Override
                                                                protected void hookOnSubscribe(Subscription subscription) {
                                                                    Schedulers.parallel().schedule(() -> cs.onSubscribe(this), 1, TimeUnit.SECONDS);
                                                                }
                                                            })),
                                            false
                                    )
                    )
                    .expectSubscription()
                    .then(() -> testPublisher.next(1L))
                    .thenCancel()
                    .verify(Duration.ofSeconds(5));

            Assertions.assertThat(testPublisher.wasCancelled()).isTrue();
            Assertions.assertThat(testPublisherInner.wasCancelled()).isFalse();
            virtualTimeScheduler.advanceTimeBy(Duration.ofMillis(1000));
            Assertions.assertThat(testPublisherInner.wasCancelled()).isTrue();
        } finally {
            VirtualTimeScheduler.reset();
        }
    }

    @Test
    public void shouldCancelSourceOnUnrelatedPublisherErrorConditional() {
        FluxIdentityProcessor<Long> testPublisher = Processors.multicast();

        testPublisher.onNext(1L);

        StepVerifier.create(testPublisher.switchOnFirst((s, f) -> Flux.error(new RuntimeException("test")).delaySubscription(Duration.ofMillis(10))).filter(__ -> true))
                    .then(() -> {
                        List<? extends Scannable> subs = testPublisher.inners().collect(Collectors.toList());
                        Assertions.assertThat(subs)
                                  .hasSize(1)
                                  .first()
                                  .extracting(psi -> psi.scan(Attr.ACTUAL))
                                  .isInstanceOf(Fuseable.ConditionalSubscriber.class);
                    })
                    .expectErrorSatisfies(t ->
                            Assertions.assertThat(t)
                                      .hasMessage("test")
                                      .isExactlyInstanceOf(RuntimeException.class)
                    )
                    .verify(Duration.ofSeconds(5));

        Assertions.assertThat(testPublisher.scan(Attr.CANCELLED)).isTrue();
    }

    @Test
    public void shouldCancelSourceOnUnrelatedPublisherCancelConditional() {
        FluxIdentityProcessor<Long> testPublisher = Processors.multicast();

        testPublisher.onNext(1L);

        StepVerifier.create(testPublisher.switchOnFirst((s, f) -> Flux.error(new RuntimeException("test")).delaySubscription(Duration.ofMillis(10))).filter(__ -> true))
                    .then(() -> {
                        List<? extends Scannable> subs = testPublisher.inners().collect(Collectors.toList());
                        Assertions.assertThat(subs)
                                  .hasSize(1)
                                  .first()
                                  .extracting(psi -> psi.scan(Attr.ACTUAL))
                                  .isInstanceOf(Fuseable.ConditionalSubscriber.class);
                    })
                    .thenAwait(Duration.ofMillis(50))
                    .thenCancel()
                    .verify();

        Assertions.assertThat(testPublisher.scan(Attr.CANCELLED)).isTrue();
    }

    @Test
    public void shouldCancelUpstreamBeforeFirst() {
        FluxIdentityProcessor<Long> testPublisher = Processors.multicast();

        StepVerifier.create(testPublisher.switchOnFirst((s, f) -> Flux.error(new RuntimeException("test"))))
                .thenAwait(Duration.ofMillis(50))
                .thenCancel()
                .verify(Duration.ofSeconds(2));

        Assertions.assertThat(testPublisher.scan(Attr.CANCELLED)).isTrue();
    }

    @Test
    public void shouldContinueWorkingRegardlessTerminalOnDownstream() {
        TestPublisher<Long> testPublisher = TestPublisher.create();

        @SuppressWarnings("unchecked")
        Flux<Long>[] intercepted = new Flux[1];

        StepVerifier.create(testPublisher.flux().switchOnFirst((s, f) -> {
            intercepted[0] = f;
            return Flux.just(2L);
        }, false))
                .expectSubscription()
                .then(() -> testPublisher.next(1L))
                .expectNext(2L)
                .expectComplete()
                .verify(Duration.ofSeconds(2));

        Assertions.assertThat(testPublisher.wasCancelled()).isFalse();

        StepVerifier.create(intercepted[0])
                .expectSubscription()
                .expectNext(1L)
                .then(testPublisher::complete)
                .expectComplete()
                .verify(Duration.ofSeconds(1));
    }

    @Test
     public void shouldCancelSourceOnOnDownstreamTerminal() {
        TestPublisher<Long> testPublisher = TestPublisher.create();

        StepVerifier.create(testPublisher.flux().switchOnFirst((s, f) -> Flux.just(1L), true))
                .expectSubscription()
                .then(() -> testPublisher.next(1L))
                .expectNext(1L)
                .expectComplete()
                .verify(Duration.ofSeconds(2));

        Assertions.assertThat(testPublisher.wasCancelled()).isTrue();
    }

    @Test
    public void racingTest() throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            @SuppressWarnings("unchecked")
            CoreSubscriber<? super Integer>[] subscribers = new CoreSubscriber[1];
            Subscription[] downstreamSubscriptions = new Subscription[1];
            Subscription[] innerSubscriptions = new Subscription[1];


            AtomicLong requested = new AtomicLong();

            Flux.range(0, 3)
                    .doOnRequest(requested::addAndGet)
                    .switchOnFirst((s, f) -> new Flux<Integer>() {

                        @Override
                        public void subscribe(CoreSubscriber<? super Integer> actual) {
                            subscribers[0] = actual;
                            f.subscribe(actual::onNext, actual::onError, actual::onComplete, (s) -> innerSubscriptions[0] = s);
                        }
                    })
                    .subscribe(__ -> {
                    }, __ -> {
                    }, () -> {
                    }, s -> downstreamSubscriptions[0] = s);

            CoreSubscriber<? super Integer> subscriber = subscribers[0];
            Subscription downstreamSubscription = downstreamSubscriptions[0];
            Subscription innerSubscription = innerSubscriptions[0];
            downstreamSubscription.request(1);

            RaceTestUtils.race(() -> subscriber.onSubscribe(innerSubscription), () -> downstreamSubscription.request(1));

            Assertions.assertThat(requested.get()).isEqualTo(2);
        }
    }

    @Test
    public void racingConditionalTest() {
        for (int i = 0; i < 1000; i++) {
            @SuppressWarnings("unchecked")
            CoreSubscriber<? super Integer>[] subscribers = new CoreSubscriber[1];
            Subscription[] downstreamSubscriptions = new Subscription[1];
            Subscription[] innerSubscriptions = new Subscription[1];


            AtomicLong requested = new AtomicLong();

            Flux.range(0, 3)
                .doOnRequest(requested::addAndGet)
                .switchOnFirst((s, f) -> new Flux<Integer>() {

                    @Override
                    public void subscribe(CoreSubscriber<? super Integer> actual) {
                        subscribers[0] = actual;
                        f.subscribe(new Fuseable.ConditionalSubscriber<Integer>() {
                            @SuppressWarnings("unchecked")
                            @Override
                            public boolean tryOnNext(Integer integer) {
                                return ((Fuseable.ConditionalSubscriber<? super Integer>)actual).tryOnNext(integer);
                            }

                            @Override
                            public void onSubscribe(Subscription s) {
                                innerSubscriptions[0] = s;
                            }

                            @Override
                            public void onNext(Integer integer) {
                                actual.onNext(integer);
                            }

                            @Override
                            public void onError(Throwable throwable) {
                                actual.onError(throwable);
                            }

                            @Override
                            public void onComplete() {
                                actual.onComplete();
                            }
                        });
                    }
                })
                .filter(__ -> true)
                .subscribe(__ -> { }, __ -> { }, () -> { }, s -> downstreamSubscriptions[0] = s);

            CoreSubscriber subscriber = subscribers[0];
            Subscription downstreamSubscription = downstreamSubscriptions[0];
            Subscription innerSubscription = innerSubscriptions[0];
            downstreamSubscription.request(1);

            RaceTestUtils.race(() -> subscriber.onSubscribe(innerSubscription), () -> downstreamSubscription.request(1));

            Assertions.assertThat(requested.get()).isEqualTo(2);
        }
    }

    @Test
    public void racingInnerSubscribeAndOuterCancelTest() throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            @SuppressWarnings("unchecked")
            CoreSubscriber<? super Integer>[] subscribers = new CoreSubscriber[1];
            @SuppressWarnings("unchecked")
            FluxSwitchOnFirst.SwitchOnFirstMain<Integer,Integer>[] sofSubscriber = new FluxSwitchOnFirst.SwitchOnFirstMain[1];
            @SuppressWarnings("unchecked")
            Flux<Integer>[] innerFlux = new Flux[1];


            AtomicLong requested = new AtomicLong();
            ArrayList<Throwable> dropped = new ArrayList<>();
            AssertSubscriber<Integer> assertSubscriber = new AssertSubscriber<>(Context.of(Hooks.KEY_ON_ERROR_DROPPED, (Consumer<Throwable>) dropped::add), 0);

            Flux.range(0, 3)
                    .doOnRequest(requested::addAndGet)
                    .transform(Operators.<Integer, Integer>lift((__, cs) -> {
                        @SuppressWarnings("unchecked")
                        FluxSwitchOnFirst.SwitchOnFirstMain<Integer, Integer> sofCs = (FluxSwitchOnFirst.SwitchOnFirstMain<Integer,Integer>) cs;
                        sofSubscriber[0] = sofCs;
                        return cs;
                    }))
                    .switchOnFirst((s, f) -> new Flux<Integer>() {
                        @Override
                        public void subscribe(CoreSubscriber<? super Integer> actual) {
                            subscribers[0] = actual;
                            innerFlux[0] = f;
                        }
                    })
                    .subscribe(assertSubscriber);

            Flux<Integer> f = innerFlux[0];
            CoreSubscriber<? super Integer> subscriber = subscribers[0];
            assertSubscriber.request(1);

            RaceTestUtils.race(() -> f.subscribe(subscriber), () -> assertSubscriber.cancel());

            Assertions.assertThat(sofSubscriber[0].inner).isEqualTo(Operators.EMPTY_SUBSCRIBER);

            // if cancel first then the upstream observes request(1) and request(1) if cancel later then only a single request
            Assertions.assertThat(requested.get()).isBetween(1L, 2L);

            assertSubscriber.assertNoError();

            if (dropped.size() > 0) {
                Assertions.assertThat(dropped)
                        .hasSize(1)
                        .first()
                        .isInstanceOf(CancellationException.class);
            }
            dropped.clear();
        }
    }

    @Test
    public void racingInnerSubscribeAndOuterCancelConditionalTest() throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            @SuppressWarnings("unchecked")
            CoreSubscriber<? super Integer>[] subscribers = new CoreSubscriber[1];
            @SuppressWarnings("unchecked")
            FluxSwitchOnFirst.SwitchOnFirstConditionalMain<Integer, Integer>[] sofSubscriber = new FluxSwitchOnFirst.SwitchOnFirstConditionalMain[1];
            @SuppressWarnings("unchecked")
            Flux<Integer>[] innerFlux = new Flux[1];

            ArrayList<Throwable> dropped = new ArrayList<>();
            AssertSubscriber<Integer> assertSubscriber = new AssertSubscriber<>(Context.of(Hooks.KEY_ON_ERROR_DROPPED, (Consumer<Throwable>) dropped::add), 0);

            AtomicLong requested = new AtomicLong();

            Flux.range(0, 3)
                    .doOnRequest(requested::addAndGet)
                    .transform(Operators.<Integer, Integer>lift((__, cs) -> {
                        @SuppressWarnings("unchecked")
                        FluxSwitchOnFirst.SwitchOnFirstConditionalMain<Integer, Integer> sofCs = (FluxSwitchOnFirst.SwitchOnFirstConditionalMain<Integer,Integer>) cs;
                        sofSubscriber[0] = sofCs;
                        return cs;
                    }))
                    .switchOnFirst((s, f) -> new Flux<Integer>() {
                        @Override
                        public void subscribe(CoreSubscriber<? super Integer> actual) {
                            subscribers[0] = actual;
                            innerFlux[0] = f;
                        }
                    })
                    .filter(__ -> true)
                    .subscribe(assertSubscriber);

            Flux<Integer> f = innerFlux[0];
            CoreSubscriber<? super Integer> subscriber = subscribers[0];
            assertSubscriber.request(1);

            RaceTestUtils.race(() -> f.subscribe(subscriber), () -> assertSubscriber.cancel());

            Assertions.assertThat(sofSubscriber[0].inner).isEqualTo(Operators.EMPTY_SUBSCRIBER);

            // if cancel first then the upstream observes request(1) and request(1) if cancel later then only a single request
            Assertions.assertThat(requested.get()).isBetween(1L, 2L);

            assertSubscriber.assertNoError();

            if (dropped.size() > 0) {
                Assertions.assertThat(dropped)
                        .hasSize(1)
                        .first()
                        .isInstanceOf(CancellationException.class);
            }
            dropped.clear();
        }
    }

    @SuppressWarnings("rawtypes")
    @Test
    public void unitRequestRacingTest() {
        @SuppressWarnings("unchecked")
        BiFunction<FluxSwitchOnFirst.AbstractSwitchOnFirstMain, CoreSubscriber, InnerOperator>[] factories = new BiFunction[] {
                (parent, assertSubscriber) -> new FluxSwitchOnFirst.SwitchOnFirstControlSubscriber((FluxSwitchOnFirst.AbstractSwitchOnFirstMain) parent, (CoreSubscriber) assertSubscriber, true),
                (parent, assertSubscriber) -> new FluxSwitchOnFirst.SwitchOnFirstConditionalControlSubscriber((FluxSwitchOnFirst.AbstractSwitchOnFirstMain) parent, (Fuseable.ConditionalSubscriber) assertSubscriber, true)
        };
        for (BiFunction<FluxSwitchOnFirst.AbstractSwitchOnFirstMain, CoreSubscriber, InnerOperator> factory : factories) {
            for (int i = 0; i < 10000; i++) {
                FluxSwitchOnFirst.AbstractSwitchOnFirstMain mockParent = Mockito.mock(FluxSwitchOnFirst.AbstractSwitchOnFirstMain.class);
                Mockito.doNothing().when(mockParent).request(Mockito.anyLong());
                Mockito.doNothing().when(mockParent).cancel();
                Subscription mockSubscription = Mockito.mock(Subscription.class);
                ArgumentCaptor<Long> longArgumentCaptor = ArgumentCaptor.forClass(Long.class);
                Mockito.doNothing().when(mockSubscription).request(longArgumentCaptor.capture());
                Mockito.doNothing().when(mockSubscription).cancel();
                AssertSubscriber<Object> subscriber = AssertSubscriber.create(0);
                InnerOperator switchOnFirstControlSubscriber = factory.apply(mockParent, Operators.toConditionalSubscriber(subscriber));

                switchOnFirstControlSubscriber.request(10);
                RaceTestUtils.race(() -> switchOnFirstControlSubscriber.request(10), () -> switchOnFirstControlSubscriber.onSubscribe(mockSubscription), Schedulers.parallel());

                Assertions.assertThat(longArgumentCaptor.getAllValues().size()).isBetween(1, 2);
                if (longArgumentCaptor.getAllValues().size() == 1) {
                    Assertions.assertThat(longArgumentCaptor.getValue()).isEqualTo(20L);
                }
                else if (longArgumentCaptor.getAllValues().size() == 2) {
                    Assertions.assertThat(longArgumentCaptor.getAllValues()).containsExactly(10L, 10L);
                }
                else {
                    Assertions.fail("Unexpected number of calls");
                }
            }
        }
    }

    @SuppressWarnings("rawtypes")
    @Test
    public void unitRequestsAreSerialTest() {
        @SuppressWarnings("unchecked")
        BiFunction<FluxSwitchOnFirst.AbstractSwitchOnFirstMain, CoreSubscriber, InnerOperator>[] factories = new BiFunction[] {
                (parent, assertSubscriber) -> new FluxSwitchOnFirst.SwitchOnFirstControlSubscriber((FluxSwitchOnFirst.AbstractSwitchOnFirstMain) parent, (CoreSubscriber) assertSubscriber, true),
                (parent, assertSubscriber) -> new FluxSwitchOnFirst.SwitchOnFirstConditionalControlSubscriber((FluxSwitchOnFirst.AbstractSwitchOnFirstMain) parent, (Fuseable.ConditionalSubscriber) assertSubscriber, true)
        };
        for (BiFunction<FluxSwitchOnFirst.AbstractSwitchOnFirstMain, CoreSubscriber, InnerOperator> factory : factories) {
            for (int i = 0; i < 100000; i++) {
                long[] valueHolder = new long[] { 0 };
                FluxSwitchOnFirst.AbstractSwitchOnFirstMain mockParent = Mockito.mock(FluxSwitchOnFirst.AbstractSwitchOnFirstMain.class);
                Mockito.doNothing().when(mockParent).request(Mockito.anyLong());
                Mockito.doNothing().when(mockParent).cancel();
                Subscription mockSubscription = Mockito.mock(Subscription.class);
                Mockito.doAnswer((a) -> valueHolder[0] += (long) a.getArgument(0)).when(mockSubscription).request(Mockito.anyLong());
                Mockito.doNothing().when(mockSubscription).cancel();
                AssertSubscriber<Object> subscriber = AssertSubscriber.create(0);
                InnerOperator switchOnFirstControlSubscriber = factory.apply(mockParent, Operators.toConditionalSubscriber(subscriber));

                switchOnFirstControlSubscriber.request(10);
                RaceTestUtils.race(() -> {
                            switchOnFirstControlSubscriber.request(10);
                            switchOnFirstControlSubscriber.request(10);
                            switchOnFirstControlSubscriber.request(10);
                            switchOnFirstControlSubscriber.request(10);
                        },
                        () -> switchOnFirstControlSubscriber.onSubscribe(mockSubscription),
                        Schedulers.parallel());

                switchOnFirstControlSubscriber.request(10);
                Assertions.assertThat(valueHolder[0])
                          .isEqualTo(60L);
                mockSubscription.toString();
            }
        }
    }

    @SuppressWarnings("rawtypes")
    @Test
    public void unitCancelRacingTest() {
        @SuppressWarnings("unchecked")
        BiFunction<FluxSwitchOnFirst.AbstractSwitchOnFirstMain, CoreSubscriber, InnerOperator>[] factories = new BiFunction[] {
                (parent, assertSubscriber) -> new FluxSwitchOnFirst.SwitchOnFirstControlSubscriber((FluxSwitchOnFirst.AbstractSwitchOnFirstMain) parent, (CoreSubscriber) assertSubscriber, true),
                (parent, assertSubscriber) -> new FluxSwitchOnFirst.SwitchOnFirstConditionalControlSubscriber((FluxSwitchOnFirst.AbstractSwitchOnFirstMain) parent, (Fuseable.ConditionalSubscriber) assertSubscriber, true)
        };
        for (BiFunction<FluxSwitchOnFirst.AbstractSwitchOnFirstMain, CoreSubscriber, InnerOperator> factory : factories) {
            for (int i = 0; i < 10000; i++) {
                FluxSwitchOnFirst.AbstractSwitchOnFirstMain mockParent = Mockito.mock(FluxSwitchOnFirst.AbstractSwitchOnFirstMain.class);
                Mockito.doNothing().when(mockParent).request(Mockito.anyLong());
                Mockito.doNothing().when(mockParent).cancel();
                Subscription mockSubscription = Mockito.mock(Subscription.class);
                ArgumentCaptor<Long> longArgumentCaptor = ArgumentCaptor.forClass(Long.class);
                Mockito.doNothing().when(mockSubscription).request(longArgumentCaptor.capture());
                Mockito.doNothing().when(mockSubscription).cancel();
                AssertSubscriber<Object> subscriber = AssertSubscriber.create(0);
                InnerOperator switchOnFirstControlSubscriber = factory.apply(mockParent, Operators.toConditionalSubscriber(subscriber));

                switchOnFirstControlSubscriber.request(10);
                RaceTestUtils.race(() -> switchOnFirstControlSubscriber.cancel(), () -> switchOnFirstControlSubscriber.onSubscribe(mockSubscription), Schedulers.parallel());

                Assertions.assertThat(longArgumentCaptor.getAllValues().size()).isBetween(0, 1);
                Mockito.verify(mockParent).cancel();
                if (longArgumentCaptor.getAllValues().size() == 1) {
                    Assertions.assertThat(longArgumentCaptor.getValue()).isEqualTo(10L);
                }
                else if (longArgumentCaptor.getAllValues().size() > 1) {
                    Assertions.fail("Unexpected number of calls");
                }
            }
        }
    }

    @Test
    public void onCompleteAndRequestRacingTest() {
        Long signal = 1L;
        @SuppressWarnings("unchecked")
        Function<CoreSubscriber<Object>, FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object>>[] factories = new Function[2];
        factories[0] = assertSubscriber -> new FluxSwitchOnFirst.SwitchOnFirstMain<>(assertSubscriber, (s, f) -> f, true);
        factories[1] = assertSubscriber -> new FluxSwitchOnFirst.SwitchOnFirstConditionalMain<>((Fuseable.ConditionalSubscriber<Object>) assertSubscriber, (s, f) -> f, true);

        for (Function<CoreSubscriber<Object>, FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object>> factory : factories) {
            for (int i = 0; i < 1000; i++) {
                Subscription mockSubscription = Mockito.mock(Subscription.class);
                ArgumentCaptor<Long> requestCaptor = ArgumentCaptor.forClass(Long.class);
                Mockito.doNothing().when(mockSubscription).request(requestCaptor.capture());
                Mockito.doNothing().when(mockSubscription).cancel();
                AssertSubscriber<Object> assertSubscriber = AssertSubscriber.create(0);
                CoreSubscriber<? super Object> conditionalAssert = Operators.toConditionalSubscriber(assertSubscriber);
                FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object> switchOnFirstMain = factory.apply(conditionalAssert);

                switchOnFirstMain.onSubscribe(mockSubscription);
                Mockito.verify(mockSubscription).request(Mockito.longThat(argument -> argument.equals(1L)));
                Mockito.clearInvocations(mockSubscription);
                switchOnFirstMain.onNext(signal);
                RaceTestUtils.race(() -> switchOnFirstMain.onComplete(), () -> switchOnFirstMain.request(55));
                Mockito.verify(mockSubscription).request(Mockito.longThat(argument -> argument.equals(54L) || argument.equals(55L)));
                assertSubscriber.assertSubscribed()
                        .awaitAndAssertNextValues(signal)
                        .await(Duration.ofSeconds(5))
                        .assertComplete();
            }
        }
    }

    @Test
    public void onErrorAndRequestRacingTest() {
        Long signal = 1L;
        RuntimeException ex = new RuntimeException();
        @SuppressWarnings("unchecked")
        Function<CoreSubscriber<Object>, FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object>>[] factories = new Function[2];
        factories[0] = assertSubscriber -> new FluxSwitchOnFirst.SwitchOnFirstMain<>(assertSubscriber, (s, f) -> f, true);
        factories[1] = assertSubscriber -> new FluxSwitchOnFirst.SwitchOnFirstConditionalMain<>((Fuseable.ConditionalSubscriber<Object>) assertSubscriber, (s, f) -> f, true);

        for (Function<CoreSubscriber<Object>, FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object,Object>> factory : factories) {
            for (int i = 0; i < 1000; i++) {
                Subscription mockSubscription = Mockito.mock(Subscription.class);
                ArgumentCaptor<Long> requestCaptor = ArgumentCaptor.forClass(Long.class);
                Mockito.doNothing().when(mockSubscription).request(requestCaptor.capture());
                Mockito.doNothing().when(mockSubscription).cancel();
                AssertSubscriber<Object> assertSubscriber = AssertSubscriber.create(0);
                CoreSubscriber<? super Object> conditionalAssert = Operators.toConditionalSubscriber(assertSubscriber);
                FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object> switchOnFirstMain = factory.apply(conditionalAssert);

                switchOnFirstMain.onSubscribe(mockSubscription);
                Mockito.verify(mockSubscription).request(Mockito.longThat(argument -> argument.equals(1L)));
                Mockito.clearInvocations(mockSubscription);
                switchOnFirstMain.onNext(signal);
                RaceTestUtils.race(() -> switchOnFirstMain.onError(ex), () -> switchOnFirstMain.request(55));
                Mockito.verify(mockSubscription).request(Mockito.longThat(argument -> argument.equals(54L) || argument.equals(55L)));
                assertSubscriber.assertSubscribed()
                                .awaitAndAssertNextValues(signal)
                                .await(Duration.ofSeconds(5))
                                .assertErrorWith(t -> Assertions.assertThat(t).isEqualTo(ex));
            }
        }
    }

    @Test
    public void cancelAndRequestRacingWithOnCompleteAfterTest() {
        Long signal = 1L;
        @SuppressWarnings("unchecked")
        Function<CoreSubscriber<Object>, FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object>>[] factories = new Function[2];
        factories[0] = assertSubscriber -> new FluxSwitchOnFirst.SwitchOnFirstMain<>(assertSubscriber, (s, f) -> f, true);
        factories[1] = assertSubscriber -> new FluxSwitchOnFirst.SwitchOnFirstConditionalMain<>((Fuseable.ConditionalSubscriber<Object>) assertSubscriber, (s, f) -> f, true);

        for (Function<CoreSubscriber<Object>, FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object>> factory : factories){
            for (int i = 0; i < 1000; i++) {
                Subscription mockSubscription = Mockito.mock(Subscription.class);
                ArgumentCaptor<Long> requestCaptor = ArgumentCaptor.forClass(Long.class);
                AtomicReference<Object> discarded = new AtomicReference<>();
                Mockito.doNothing().when(mockSubscription).request(requestCaptor.capture());
                Mockito.doNothing().when(mockSubscription).cancel();
                AssertSubscriber<Object> assertSubscriber = new AssertSubscriber<>(Context.of(Hooks.KEY_ON_DISCARD, (Consumer<Object>) o -> Assertions.assertThat(discarded.getAndSet(o)).isNull()), 0L);

                Fuseable.ConditionalSubscriber<? super Object> assertConditional = Operators.toConditionalSubscriber(assertSubscriber);
                FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object> switchOnFirstMain = factory.apply(assertConditional);

                switchOnFirstMain.onSubscribe(mockSubscription);
                Mockito.verify(mockSubscription).request(Mockito.longThat(argument -> argument.equals(1L)));
                Mockito.clearInvocations(mockSubscription);
                switchOnFirstMain.onNext(signal);
                RaceTestUtils.race(() -> switchOnFirstMain.cancel(), () -> switchOnFirstMain.request(55));
                switchOnFirstMain.onComplete();
                assertSubscriber.assertNotTerminated();
                Object discardedValue = discarded.get();
                if (discardedValue == null) {
                    assertSubscriber.awaitAndAssertNextValues(signal);
                } else {
                    Assertions.assertThat(discardedValue).isEqualTo(signal);
                }

                Mockito.verify(mockSubscription).request(Mockito.longThat(argument -> argument.equals(54L) || argument.equals(55L)));
            }
        }
    }

    @Test
    public void cancelAndRequestRacingOnErrorAfterTest() {
        Long signal = 1L;
        @SuppressWarnings("unchecked")
        Function<CoreSubscriber<Object>, FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object>>[] factories = new Function[2];
        factories[0] = assertSubscriber -> new FluxSwitchOnFirst.SwitchOnFirstMain<>(assertSubscriber, (s, f) -> f, true);
        factories[1] = assertSubscriber -> new FluxSwitchOnFirst.SwitchOnFirstConditionalMain<>((Fuseable.ConditionalSubscriber<Object>) assertSubscriber, (s, f) -> f, true);

        for (Function<CoreSubscriber<Object>, FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object>> factory : factories) {
            for (int i = 0; i < 1000; i++) {
                Subscription mockSubscription = Mockito.mock(Subscription.class);
                ArgumentCaptor<Long> requestCaptor = ArgumentCaptor.forClass(Long.class);
                AtomicReference<Object> discarded = new AtomicReference<>();
                AtomicReference<Object> discardedError = new AtomicReference<>();
                Mockito.doNothing().when(mockSubscription).request(requestCaptor.capture());
                Mockito.doNothing().when(mockSubscription).cancel();
                AssertSubscriber<Object> assertSubscriber = new AssertSubscriber<>(Context.of(
                        Hooks.KEY_ON_DISCARD, (Consumer<Object>) o -> Assertions.assertThat(discarded.getAndSet(o)).isNull(),
                        Hooks.KEY_ON_ERROR_DROPPED, (Consumer<Object>) o -> Assertions.assertThat(discardedError.getAndSet(o)).isNull()
                ), 0L);
                CoreSubscriber<? super Object> conditionalAssert = Operators.toConditionalSubscriber(assertSubscriber);
                FluxSwitchOnFirst.AbstractSwitchOnFirstMain<Object, Object> switchOnFirstMain = factory.apply(conditionalAssert);


                switchOnFirstMain.onSubscribe(mockSubscription);
                Mockito.verify(mockSubscription).request(Mockito.longThat(argument -> argument.equals(1L)));
                Mockito.clearInvocations(mockSubscription);
                switchOnFirstMain.onNext(signal);
                RaceTestUtils.race(() -> switchOnFirstMain.cancel(), () -> switchOnFirstMain.request(55));
                switchOnFirstMain.onError(new NullPointerException());
                assertSubscriber.assertNotTerminated();
                Assertions.assertThat(discardedError.get()).isInstanceOf(NullPointerException.class);
                Object discardedValue = discarded.get();
                if (discardedValue == null) {
                    assertSubscriber.awaitAndAssertNextValues(signal);
                } else {
                    Assertions.assertThat(discardedValue).isEqualTo(signal);
                }

                Mockito.verify(mockSubscription).request(Mockito.longThat(argument -> argument.equals(54L) || argument.equals(55L)));
            }
        }
    }


    private static final class NoOpsScheduler implements Scheduler {

        static final NoOpsScheduler INSTANCE = new NoOpsScheduler();

        private NoOpsScheduler() {}

        @Override
        public Disposable schedule(Runnable task) {
            return Disposables.composite();
        }

        @Override
        public Worker createWorker() {
            return NoOpsWorker.INSTANCE;
        }

        static final class NoOpsWorker implements Worker {

            static final NoOpsWorker INSTANCE = new NoOpsWorker();

            @Override
            public Disposable schedule(Runnable task) {
                return Disposables.never();
            }

            @Override
            public void dispose() {

            }
        };
    }
}