/*
 * Copyright (c) 2011-2019 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.test;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.assertj.core.api.Assertions;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

import reactor.core.CoreSubscriber;
import reactor.core.Fuseable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxIdentityProcessor;
import reactor.core.publisher.FluxProcessor;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.Operators;
import reactor.core.publisher.Processors;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import reactor.test.publisher.TestPublisher;
import reactor.test.scheduler.VirtualTimeScheduler;
import reactor.util.context.Context;

import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.*;
import static reactor.test.publisher.TestPublisher.Violation.REQUEST_OVERFLOW;

/**
 * @author Arjen Poutsma
 * @author Sebastien Deleuze
 * @author Stephane Maldini
 * @author Simon Basle
 */
public class StepVerifierTests {

	@Test
	public void expectationErrorWithGenericValueFormatterBypassesExtractor() {
		Flux<String> flux = Flux.just("foobar");
		StepVerifierOptions options = StepVerifierOptions
				.create()
				.valueFormatter(ValueFormatters.forClass(Object.class, t -> t.getClass().getSimpleName() + "{'" + t + "'}"));

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(StepVerifier.create(flux, options)
				                        .expectError()
						::verify)
				.withMessage("expectation \"expectError()\" failed (expected: onError(); actual: ImmutableSignal{'onNext(foobar)'})");
	}

	@Test
	public void expectationErrorWithSpecificValueFormatterExtractsSignal() {
		Flux<String> flux = Flux.just("foobar");
		StepVerifierOptions options = StepVerifierOptions
				.create()
				.valueFormatter(ValueFormatters.forClass(String.class, s -> "" + s.length()));

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(StepVerifier.create(flux, options)
				                        .expectError()
						::verify)
				.withMessage("expectation \"expectError()\" failed (expected: onError(); actual: onNext(6))");
	}

	@Test
	public void expectationErrorWithoutValueFormatter() {
		Flux<String> flux = Flux.just("foobar");
		StepVerifierOptions options = StepVerifierOptions.create();

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(StepVerifier.create(flux, options)
				                        .expectError()
						::verify)
				.withMessage("expectation \"expectError()\" failed (expected: onError(); actual: onNext(foobar))");
	}

	@Test
	public void expectInvalidNextsWithCustomConverter() {
		Flux<String> flux = Flux.just("foo", "bar");

		StepVerifierOptions options = StepVerifierOptions.create()
		                                                 .valueFormatter(ValueFormatters.forClass(String.class, s -> "String=>" + s));

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux, options)
				                              .expectNext("foo", "baz")
				                              .expectComplete()
				                              .verify())
				.withMessage("expectation \"expectNext(String=>baz)\" failed (expected value: String=>baz; actual value: String=>bar)");
	}

	@Test
	public void expectNext() {
		Flux<String> flux = Flux.just("foo", "bar");

		StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectNext("bar")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void expectInvalidNext() {
		Flux<String> flux = Flux.just("foo", "bar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
				                              .expectNext("foo")
				                              .expectNext("baz")
				                              .expectComplete()
				                              .verify())
				.withMessageEndingWith("(expected value: baz; actual value: bar)");
	}

	@Test
	public void expectNextAsync() {
		Flux<String> flux = Flux.just("foo", "bar")
		                        .publishOn(Schedulers.parallel());

		StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectNext("bar")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void expectNexts() {
		Flux<String> flux = Flux.just("foo", "bar");

		StepVerifier.create(flux)
		            .expectNext("foo", "bar")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void expectNextsMoreThan6() {
		Flux<Integer> flux = Flux.range(1, 7);

		StepVerifier.create(flux)
		            .expectNext(1, 2, 3, 4, 5, 6, 7)
		            .expectComplete()
		            .verify();
	}

	@Test
	public void expectInvalidNexts() {
		Flux<String> flux = Flux.just("foo", "bar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
				                              .expectNext("foo", "baz")
				                              .expectComplete()
				                              .verify())
	            .withMessage("expectation \"expectNext(baz)\" failed (expected value: baz; actual value: bar)");
	}

	@Test
	public void expectNextMatches() {
		Flux<String> flux = Flux.just("foo", "bar");

		StepVerifier.create(flux)
		            .expectNextMatches("foo"::equals)
		            .expectNextMatches("bar"::equals)
		            .expectComplete()
		            .verify();
	}

	@Test
	public void expectInvalidNextMatches() {
		Flux<String> flux = Flux.just("foo", "bar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNextMatches("foo"::equals)
		            .expectNextMatches("baz"::equals)
		            .expectComplete()
		            .verify())
	            .withMessage("expectation \"expectNextMatches\" failed (predicate failed on value: bar)");
	}

	@Test
	public void consumeNextWith() throws Exception {
		Flux<String> flux = Flux.just("bar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
				                              .consumeNextWith(s -> {
					                              if (!"foo".equals(s)) {
						                              throw new AssertionError("e:" + s);
					                              }
				                              })
				                              .expectComplete()
				                              .verify())
				.withMessage("e:bar");
	}

	@Test
	public void consumeNextWith2() throws Exception {
		Flux<String> flux = Flux.just("bar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
				                              .consumeNextWith(s -> {
					                              if (!"foo".equals(s)) {
						                              throw new AssertionError(s);
					                              }
				                              })
				                              .expectComplete()
				                              .verify())
				.withMessage("bar");
	}

	@Test
	public void assertNext() throws Exception {
		Flux<String> flux = Flux.just("foo");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
				                              .assertNext(s -> assertThat(s).endsWith("ooz"))
				                              .expectComplete()
				                              .verify())
				.withMessage("\nExpecting:\n <\"foo\">\nto end with:\n <\"ooz\">\n");
	}

	@Test
	public void missingNext() {
		Flux<String> flux = Flux.just("foo", "bar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectComplete()
		            .verify())
	            .withMessage("expectation \"expectComplete\" failed (expected: onComplete(); actual: onNext(bar))");
	}

	@Test
	public void missingNextAsync() {
		Flux<String> flux = Flux.just("foo", "bar")
		                        .publishOn(Schedulers.parallel());

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectComplete()
		            .verify())
	            .withMessage("expectation \"expectComplete\" failed (expected: onComplete(); actual: onNext(bar))");
	}

	@Test
	public void expectNextCount() {
		Flux<String> flux = Flux.just("foo", "bar");

		StepVerifier.create(flux, 0)
		            .thenRequest(1)
		            .expectNextCount(1)
		            .thenRequest(1)
		            .expectNextCount(1)
		            .expectComplete()
		            .verify();
	}

	@Test
	public void expectNextCountLots() {
		Flux<Integer> flux = Flux.range(0, 1_000_000);

		StepVerifier.create(flux, 0)
		            .thenRequest(100_000)
		            .expectNextCount(100_000)
		            .thenRequest(500_000)
		            .expectNextCount(500_000)
		            .thenRequest(500_000)
		            .expectNextCount(400_000)
		            .expectComplete()
		            .verify();
	}

	@Test
	public void expectNextCountZeroBeforeExpectNext() {
		StepVerifier.create(Flux.just("foo", "bar"))
				.expectNextCount(0)
				.expectNext("foo", "bar")
				.expectComplete()
				.verify();
	}

	@Test
	public void expectNextCountLotsError() {
		Flux<Integer> flux = Flux.range(0, 1_000_000);

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux, 0)
		            .thenRequest(100_000)
		            .expectNextCount(100_000)
		            .thenRequest(Integer.MAX_VALUE)
		            .expectNextCount(900_001)
		            .expectComplete()
		            .verify())
	            .withMessageStartingWith("expectation \"expectNextCount(900001)\" failed")
				.withMessageContaining("expected: count = 900001; actual: counted = 900000; signal: onComplete()");
	}

	@Test
	public void expectNextCountLotsUnderRequestErrorReportedAtEnd() {
		Flux<Integer> flux = Flux.range(0, 1_000_000);

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux, 0)
		            .thenRequest(100_000)
		            .expectNextCount(100_000)
		            .thenRequest(500_000)
		            .expectNextCount(499_999)
		            .thenRequest(500_000)
		            .expectNextCount(400_000)
		            .expectComplete()
		            .verify())
	            .withMessage("expectation \"expectComplete\" failed (expected: onComplete(); actual: onNext(999999))");
	}

	@Test
	public void expectNextCount2() {
		Flux<String> flux = Flux.just("foo", "bar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNext("foo", "bar")
		            .expectNextCount(2)
		            .expectComplete()
		            .verify())
	            .withMessage("expectation \"expectNextCount(2)\" failed (expected: count = 2; actual: counted = 0; signal: onComplete())");
	}

	@Test
	public void expectNextCount3() {
		Flux<String> flux = Flux.just("foo", "bar");

		StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectNextCount(1)
		            .expectComplete()
		            .verify();
	}

	@Test
	public void expectNextCountZero() {
		Flux<String> flux = Flux.empty();

		StepVerifier.create(flux)
		            .expectNextCount(0)
		            .expectComplete()
		            .verify();
	}

	@Test
	public void expectNextCountError() {
		Flux<String> flux = Flux.just("foo", "bar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNextCount(4)
		            .thenCancel()
		            .verify())
				.withMessage("expectation \"expectNextCount(4)\" failed (expected: count = 4; actual: counted = 2; signal: onComplete())");
	}

	@Test
	public void error() {
		Flux<String> flux = Flux.just("foo")
		                        .concatWith(Mono.error(new IllegalArgumentException()));

		StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectError()
		            .verify();
	}

	@Test
	public void errorClass() {
		Flux<String> flux = Flux.just("foo")
		                        .concatWith(Mono.error(new IllegalArgumentException()));

		StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectError(IllegalArgumentException.class)
		            .verify();
	}

	@Test
	public void errorMessage() {
		Flux<String> flux = Flux.just("foo")
		                        .concatWith(Mono.error(new IllegalArgumentException(
				                        "Error message")));

		StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectErrorMessage("Error message")
		            .verify();
	}

	@Test
	public void errorMatches() {
		Flux<String> flux = Flux.just("foo")
		                        .concatWith(Mono.error(new IllegalArgumentException()));

		StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectErrorMatches(t -> t instanceof IllegalArgumentException)
		            .verify();
	}

	@Test
	public void errorMatchesInvalid() {
		Flux<String> flux = Flux.just("foo")
		                        .concatWith(Mono.error(new IllegalArgumentException()));

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectErrorMatches(t -> t instanceof IllegalStateException)
		            .verify())
	            .withMessage("expectation \"expectErrorMatches\" failed (predicate failed on exception: java.lang.IllegalArgumentException)");
	}

	@Test
	public void errorSatisfies() {
		Flux<String> flux = Flux.just("foo")
		                        .concatWith(Mono.error(new IllegalArgumentException()));

		StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectErrorSatisfies(t -> assertThat(t).isInstanceOf(IllegalArgumentException.class))
		            .verify();
	}

	@Test
	public void errorSatisfiesInvalid() {
		Flux<String> flux = Flux.just("foo")
		                        .concatWith(Mono.error(new IllegalArgumentException()));

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectErrorSatisfies(t -> assertThat(t).hasMessage("foo"))
		            .verify())
	            .withMessage("expectation \"expectErrorSatisfies\" failed (assertion failed on exception <java.lang.IllegalArgumentException>: " +
			            "\nExpecting message:\n <\"foo\">\nbut was:\n <null>)");
	}

	@Test
	public void consumeErrorWith() {
		Flux<String> flux = Flux.just("foo")
		                        .concatWith(Mono.error(new IllegalArgumentException()));

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
			            .expectNext("foo")
			            .consumeErrorWith(throwable -> {
				            if (!(throwable instanceof IllegalStateException)) {
					            throw new AssertionError(throwable.getClass()
					                                              .getSimpleName());
				            }
			            })
			            .verify())
	            .withMessage("IllegalArgumentException");
	}

	@Test
	public void request() {
		Flux<String> flux = Flux.just("foo", "bar");

		StepVerifier.create(flux, 1)
		            .thenRequest(1)
		            .expectNext("foo")
		            .thenRequest(1)
		            .expectNext("bar")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void cancel() {
		Flux<String> flux = Flux.just("foo", "bar", "baz");

		StepVerifier.create(flux)
		            .expectNext("foo")
		            .thenCancel()
		            .verify();
	}

	@Test
	public void cancelInvalid() {
		Flux<String> flux = Flux.just("bar", "baz");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNext("foo")
		            .thenCancel()
		            .verify())
	            .withMessage("expectation \"expectNext(foo)\" failed (expected value: foo; actual value: bar)");
	}

	@Test
	public void thenCancel_cancelsAfterFirst() {
		TestPublisher<Long> publisher = TestPublisher.create();
		AtomicBoolean downStreamCancelled = new AtomicBoolean();
		AtomicBoolean asserted = new AtomicBoolean();
		Flux<Long> source = publisher
				.flux()
				.onBackpressureBuffer()
				.doOnCancel(() -> downStreamCancelled.set(true))
				.log();

		Duration took = StepVerifier.create(source,  1)
		                            .then(() -> Schedulers.boundedElastic().schedule(() -> publisher.next(0L)))
		                            .assertNext(next -> {
			                            LockSupport.parkNanos(Duration.ofMillis(500)
			                                                          .toNanos());
			                            asserted.set(true);
			                            assertThat(next).isEqualTo(0L);
		                            })
		                            .then(() -> Schedulers.boundedElastic().schedule(() ->
				                            publisher.next(1L)))
		                            .then(() -> Schedulers.boundedElastic().schedule(() ->
				                            publisher.next(2L), 50, TimeUnit.MILLISECONDS))
		                            .expectNoEvent(Duration.ofMillis(100))
		                            .thenRequest(1)
		                            .thenRequest(1)
		                            .assertNext(next -> {
			                            LockSupport.parkNanos(Duration.ofMillis(500)
			                                                          .toNanos());
			                            assertThat(next).isEqualTo(1L);
		                            })
		                            .thenAwait(Duration.ofSeconds(2))
		                            .thenCancel()
		                            .verify(Duration.ofSeconds(5));

		publisher.assertCancelled();

		assertThat(asserted.get())
				.as("expectation processed")
				.isTrue();
		assertThat(downStreamCancelled.get())
				.as("is cancelled by awaitThenCancel")
				.isTrue();
		assertThat(took.toMillis())
				.as("blocked on first assertNext")
				.isGreaterThanOrEqualTo(1000L);
	}

	@Test
	public void thenCancel_cancelsAfterFirst2() {
		TestPublisher<Long> publisher = TestPublisher.create();
		AtomicBoolean downStreamCancelled = new AtomicBoolean();
		AtomicBoolean asserted = new AtomicBoolean();
		Flux<Long> source = publisher
				.flux()
				.doOnCancel(() -> downStreamCancelled.set(true));

		Duration took = StepVerifier.create(source)
		                            .then(() -> Schedulers.boundedElastic().schedule(() -> publisher.next(0L)))
		                            .assertNext(next -> {
			                            asserted.set(true);
			                            assertThat(next).isEqualTo(0L);
		                            })
		                            .then(() -> Schedulers.boundedElastic().schedule(() ->
				                            publisher.next(1L)))
		                            .thenCancel()
		                            .verify(Duration.ofSeconds(5));

		publisher.assertCancelled();

		assertThat(asserted.get())
				.as("expectation processed")
				.isTrue();
		assertThat(downStreamCancelled.get())
				.as("is cancelled by awaitThenCancel")
				.isTrue();
	}

	@Test
	public void subscribedTwice() {
		Flux<String> flux = Flux.just("foo", "bar");

		StepVerifier s = StepVerifier.create(flux)
		                             .expectNext("foo")
		                             .expectNext("bar")
		                             .expectComplete();

		s.verify();
		s.verify();
	}

	@Test
	public void verifyThenOnCompleteRange() {
		FluxIdentityProcessor<Void> p = Processors.more().multicastNoBackpressure();

		Flux<String> flux = Flux.range(0, 3)
		                        .map(d -> "t" + d)
		                        .takeUntilOther(p);

		StepVerifier.create(flux, 2)
		            .expectNext("t0", "t1")
		            .then(p::onComplete)
		            .expectComplete()
		            .verify();

	}

	@Test
	public void verifyDuration() {
		long interval = 200;
		Flux<String> flux = Flux.interval(Duration.ofMillis(interval))
		                        .map(l -> "foo")
		                        .take(2);

		Duration duration = StepVerifier.create(flux)
		                                .thenAwait(Duration.ofSeconds(100))
		                                .expectNext("foo")
		                                .expectNext("foo")
		                                .expectComplete()
		                                .verify(Duration.ofMillis(500));

		assertThat(duration.toMillis()).isGreaterThan(2 * interval);
	}

	@Test
	public void verifyDurationTimeout() {
		Flux<String> flux = Flux.interval(Duration.ofMillis(200))
		                        .map(l -> "foo")
		                        .take(2);

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNext("foo")
		            .expectNext("foo")
		            .expectComplete()
		            .verify(Duration.ofMillis(300)))
	            .withMessageStartingWith("VerifySubscriber timed out on");
	}

	@Test
	public void verifyNeverWithExpectTimeout() {
		Flux<String> flux = Flux.never();

		StepVerifier.create(flux)
		            .expectTimeout(Duration.ofMillis(500))
		            .verify();
	}

	@Test
	public void verifySubscription() {
		Mono<String> flux = Mono.just("foo");

		StepVerifier.create(flux)
		            .expectSubscriptionMatches(s -> s instanceof Fuseable.QueueSubscription)
		            .expectNext("foo")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyNextAs() {
		Flux<String> flux = Flux.just("foo", "bar", "foobar");

		StepVerifier.create(flux)
		            .expectNextSequence(Arrays.asList("foo", "bar", "foobar"))
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyNextAsErrorTooFewInIterable() {
		Flux<String> flux = Flux.just("foo", "bar", "foobar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNextSequence(Arrays.asList("foo", "bar"))
		            .expectComplete()
		            .verify())
	            .withMessage("expectation \"expectComplete\" failed (expected: onComplete(); actual: onNext(foobar))");
	}

	@Test
	public void verifyNextAsErrorTooManyInIterable() {
		Flux<String> flux = Flux.just("foo", "bar", "foobar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNextSequence(Arrays.asList("foo", "bar", "foobar", "bar"))
		            .expectComplete()
		            .verify())
				.withMessageStartingWith("expectation \"expectNextSequence\" failed (")
				.withMessageEndingWith("expected next value: bar; actual signal: onComplete(); iterable: [foo, bar, foobar, bar])");
	}

	@Test
	public void verifyNextAs2() {
		final List<Integer> source = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

		Flux<Integer> flux = Flux.fromStream(source.stream());

		StepVerifier.create(flux)
		            .expectNextSequence(source)
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyNextAsWithEmptyFlux() {
	    final List<Integer> source = Arrays.asList(1,2,3);
		Flux<Integer> flux = Flux.empty();

        assertThatExceptionOfType(AssertionError.class)
                .isThrownBy(() -> StepVerifier.create(flux)
				.expectNextSequence(source)
				.expectComplete()
				.verify())
                .withMessageStartingWith("expectation \"expectNextSequence\" failed (")
                .withMessageEndingWith("expected next value: 1; actual signal: onComplete(); iterable: [1, 2, 3])");
	}

	@Test
	public void verifyRecordMatches() {
		Flux<String> flux = Flux.just("foo", "bar", "foobar");

		StepVerifier.create(flux)
		            .recordWith(ArrayList::new)
		            .expectNextCount(3)
		            .expectRecordedMatches(c -> c.contains("foobar"))
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyRecordMatchesError() {
		Flux<String> flux = Flux.just("foo", "bar", "foobar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .recordWith(ArrayList::new)
		            .expectNextCount(3)
		            .expectRecordedMatches(c -> c.contains("foofoo"))
		            .expectComplete()
		            .verify())
				.withMessage("expectation \"expectRecordedMatches\" failed (expected collection predicate match; actual: [foo, bar, foobar])");
	}

	@Test
	public void verifyRecordNullError() {
		Flux<String> flux = Flux.just("foo", "bar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .recordWith(() -> null)
		            .expectComplete()
		            .verify())
				.withMessage("expectation \"recordWith\" failed (expected collection; actual supplied is [null])");
	}

	@Test
	public void verifyRecordMatchesError2() {
		Flux<String> flux = Flux.just("foo", "bar", "foobar");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectNext("foo", "bar", "foobar")
		            .expectRecordedMatches(c -> c.size() == 3)
		            .expectComplete()
		            .verify())
				.withMessage("expectation \"expectRecordedMatches\" failed (expected record collector; actual record is [null])");
	}

	@Test
	public void verifyRecordWith2() {
		final List<Integer> source = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

		Flux<Integer> flux = Flux.fromStream(source.stream());

		StepVerifier.create(flux)
		            .recordWith(ArrayList::new)
		            .expectNextCount(10)
		            .consumeRecordedWith(c -> assertThat(c).containsExactlyElementsOf(source))
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifySubscriptionError() {
		Mono<String> flux = Mono.just("foo");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectSubscriptionMatches(s -> false)
		            .expectNext("foo")
		            .expectComplete()
		            .verify())
				.withMessageStartingWith("expectation \"expectSubscriptionMatches\" failed (predicate failed on subscription: ");
	}

	@Test
	public void verifyConsumeSubscription() {
		Mono<String> flux = Mono.just("foo");

		StepVerifier.create(flux)
		            .consumeSubscriptionWith(s -> assertThat(s).isInstanceOf(Fuseable.QueueSubscription.class))
		            .expectNext("foo")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyConsumeSubscriptionAfterFirst() {
		Mono<String> flux = Mono.just("foo");

		StepVerifier.create(flux)
		            .expectNext("foo")
		            .consumeSubscriptionWith(s -> assertThat(s).isInstanceOf(Fuseable.QueueSubscription.class))
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyConsumeSubscriptionError() {
		Mono<String> flux = Mono.just("foo");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .consumeSubscriptionWith(s -> Assertions.fail("boom"))
		            .expectNext("foo")
		            .expectComplete()
		            .verify())
				.withMessage("boom");
	}

	@Test
	public void verifyFusion() {
		Mono<String> flux = Mono.just("foo");

		StepVerifier.create(flux)
		            .expectFusion()
		            .expectNext("foo")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyFusionError() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> Mono.just("foo")
				                      .hide()
				                      .as(StepVerifier::create)
				                      .expectFusion()
				                      .expectNext("foo")
				                      .expectComplete()
				                      .verify())
				.withMessage("expectation failed (expected fuseable source but actual " +
						"Subscription is not: 3)");
	}

	@Test
	public void verifyNoFusion() {
		Mono<String> flux = Mono.just("foo")
		                        .hide();

		StepVerifier.create(flux)
		            .expectNoFusionSupport()
		            .expectNext("foo")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyNoFusionError() {
		Mono<String> flux = Mono.just("foo");

		StepVerifier.create(flux.hide())
		            .expectNoFusionSupport()
		            .expectNext("foo")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyFusionModeRequest() {
		Mono<String> flux = Mono.just("foo");

		StepVerifier.create(flux)
		            .expectFusion(Fuseable.SYNC)
		            .expectNext("foo")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyFusionModeExpected() {
		Mono<String> flux = Mono.just("foo");

		StepVerifier.create(flux)
		            .expectFusion(Fuseable.SYNC, Fuseable.SYNC)
		            .expectNext("foo")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyFusionModeExpectedError() {
		Mono<String> flux = Mono.just("foo");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectFusion(Fuseable.SYNC, Fuseable.ASYNC)
		            .expectNext("foo")
		            .expectComplete()
		            .verify())
				.withMessage("expectation failed (expected fusion mode: (async); actual: (sync))");
	}

	@Test
	public void verifyFusionModeExpected2() {
		Flux<String> flux = Flux.just("foo", "bar")
		                        .publishOn(Schedulers.immediate());

		StepVerifier.create(flux)
		            .expectFusion(Fuseable.SYNC | Fuseable.ASYNC, Fuseable.ASYNC)
		            .expectNext("foo", "bar")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyFusionModeExpectedCancel() {
		Flux<String> flux = Flux.just("foo", "bar");

		StepVerifier.create(flux)
		            .expectFusion(Fuseable.SYNC, Fuseable.SYNC)
		            .expectNext("foo")
		            .thenCancel()
		            .verify();
	}

	@Test
	public void verifyFusionModeExpected2Error() {
		Flux<String> flux = Flux.just("foo", "bar")
		                        .publishOn(Schedulers.immediate());

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(flux)
		            .expectFusion(Fuseable.ASYNC, Fuseable.SYNC)
		            .expectNext("foo", "bar")
		            .expectComplete()
		            .verify())
				.withMessage("expectation failed (expected fusion mode: (sync); actual: (async))");
	}

	@Test
	public void verifyVirtualTimeOnSubscribe() {
		StepVerifier.withVirtualTime(() -> Mono.delay(Duration.ofDays(2))
		                                       .map(l -> "foo"))
		            .thenAwait(Duration.ofDays(3))
		            .expectNext("foo")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyVirtualTimeOnError() {
		StepVerifier.withVirtualTime(() -> Mono.never()
		                                       .timeout(Duration.ofDays(2))
		                                       .map(l -> "foo"))
		            .thenAwait(Duration.ofDays(2))
		            .expectError(TimeoutException.class)
		            .verify();
	}

	@Test
	public void verifyVirtualTimeNoEvent() {
		StepVerifier.withVirtualTime(() -> Mono.just("foo")
		                                       .delaySubscription(Duration.ofDays(2)))
		            .expectSubscription()
		            .expectNoEvent(Duration.ofDays(2))
		            .expectNext("foo")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyVirtualTimeNoEventError() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.withVirtualTime(() -> Mono.just("foo")
		                                       .delaySubscription(Duration.ofDays(2)))
		            .expectSubscription()
		            .expectNoEvent(Duration.ofDays(2))
		            .expectNext("foo")
		            .expectNoEvent(Duration.ofDays(2))
		            .expectComplete()
		            .verify())
				.withMessage("Unexpected completion during a no-event expectation");
	}

	@Test
	public void verifyVirtualTimeNoEventInterval() {
		StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofSeconds(3))
		                                       .take(2))
		            .expectSubscription()
		            .expectNoEvent(Duration.ofSeconds(3))
		            .expectNext(0L)
		            .expectNoEvent(Duration.ofSeconds(3))
		            .expectNext(1L)
		            .expectComplete()
		            .verify();
	}

	@Test
	//TODO shouldn't there be only one error rather than Multiple exceptions?
	public void verifyVirtualTimeNoEventIntervalError() {
		Throwable thrown = catchThrowable(() ->
				StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofSeconds(3))
				                                       .take(2))
				            .expectSubscription()
				            .expectNoEvent(Duration.ofSeconds(3))
				            .expectNext(0L)
				            .expectNoEvent(Duration.ofSeconds(4))
				            .expectNext(1L)
				            .thenAwait()
				            .expectComplete()
				            .verify());

		assertThat(thrown).isInstanceOf(AssertionError.class)
		                  .hasMessageContaining("Multiple exceptions")
		                  .hasMessageContaining("expectation failed (expected no event: onNext(1)")
		                  .hasMessageContaining("expectation failed (expected no event: onComplete()");
	}

	//see https://github.com/reactor/reactor-core/issues/1913
	@Test
	public void verifyExpectTimeoutFailsWhenSomeEvent() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Mono.just("foo"))
				                              .expectTimeout(Duration.ofMillis(1300))
				                              .verify())
				.withMessage("expectation \"expectTimeout\" failed (expected: timeout(1.3s); actual: onNext(foo))");
	}

	//see https://github.com/reactor/reactor-core/issues/1913
	@Test
	public void verifyVirtualTimeExpectTimeoutFailsWhenSomeEvent() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.withVirtualTime(() -> Mono.just("foo"))
				                              .expectTimeout(Duration.ofDays(3))
				                              .verify())
				.withMessage("expectation \"expectTimeout\" failed (expected: timeout(72h); actual: onNext(foo))");
	}

	@Test
	public void verifyExpectTimeoutNever() {
		StepVerifier.create(Mono.never())
		            .expectSubscription()
		            .expectTimeout(Duration.ofSeconds(1))
		            .verify();
	}

	@Test
	public void verifyVirtualTimeExpectTimeoutNever() {
		StepVerifier.withVirtualTime(Mono::never)
		            .expectSubscription()
		            .expectTimeout(Duration.ofDays(10000))
		            .verify();
	}

	@Test
	public void verifyExpectTimeoutDoesntCareAboutSubscription() {
		StepVerifier.withVirtualTime(Mono::never)
		            .expectTimeout(Duration.ofDays(10000))
		            .verify();

		StepVerifier.create(Mono.never())
		            .expectTimeout(Duration.ofSeconds(1))
		            .verify();
	}

	@Test
	public void verifyVirtualTimeOnNext() {
		StepVerifier.withVirtualTime(() -> Flux.just("foo", "bar", "foobar")
		                                       .delayElements(Duration.ofHours(1))
		                                       .log())
		            .thenAwait(Duration.ofHours(1))
		            .expectNext("foo")
		            .thenAwait(Duration.ofHours(1))
		            .expectNext("bar")
		            .thenAwait(Duration.ofHours(1))
		            .expectNext("foobar")
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyVirtualTimeOnComplete() {
		StepVerifier.withVirtualTime(() -> Flux.empty()
		                                       .delaySubscription(Duration.ofHours(1))
		                                       .log())
		            .thenAwait(Duration.ofHours(1))
		            .expectComplete()
		            .verify();
	}

	@Test
	public void verifyVirtualTimeOnNextInterval() {
		Duration r;

		r = StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofSeconds(3))
		                                           .map(d -> "t" + d))
		                .thenAwait(Duration.ofSeconds(3))
		                .expectNext("t0")
		                .thenAwait(Duration.ofSeconds(3))
		                .expectNext("t1")
		                .thenAwait(Duration.ofSeconds(3))
		                .expectNext("t2")
		                .thenCancel()
		                .verify();

		assertThat(r.minus(Duration.ofSeconds(9)).toMillis()).isNegative();
	}

	@Test
	public void verifyVirtualTimeNoLookupFails() {
		assertThatExceptionOfType(NullPointerException.class)
				.isThrownBy(() -> StepVerifier.withVirtualTime(Flux::empty, null, 1))
	            .withMessage("vtsLookup");
	}

	@Test
	public void verifyVirtualTimeNoScenarioFails() {
		assertThatExceptionOfType(NullPointerException.class)
				.isThrownBy(() -> StepVerifier.withVirtualTime(null, 1))
	            .withMessage("scenarioSupplier");
	}

	@Test(timeout = 3000)
	public void verifyVirtualTimeOnNextIntervalManual() {
		VirtualTimeScheduler vts = VirtualTimeScheduler.create();

		StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofMillis(1000), vts)
		                                       .map(d -> "t" + d))
		            .then(() -> vts.advanceTimeBy(Duration.ofHours(1)))
		            .expectNextCount(3600)
		            .thenCancel()
		            .verify();
	}

	@Test
	public void verifyVirtualTimeOnErrorInterval() {
		StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofSeconds(3))
		                                       .map(d -> "t" + d),
				0)
		            .thenRequest(1)
		            .thenAwait(Duration.ofSeconds(3))
		            .expectNext("t0")
		            .thenRequest(1)
		            .thenAwait(Duration.ofSeconds(3))
		            .expectNext("t1")
		            .thenAwait(Duration.ofSeconds(3))
		            .expectError(IllegalStateException.class)
		            .verify();

	}

	@Test
	public void verifyVirtualTimeOnErrorAsync() {
		VirtualTimeScheduler vts = VirtualTimeScheduler.create();
		StepVerifier.withVirtualTime(() -> Flux.just(123)
		                                       .subscribeOn(vts),
				() -> vts, 0)
		            .thenRequest(1)
		            .thenAwait()
		            .expectNext(123)
		            .expectComplete()
		            .verify();
	}

	@Test(timeout = 1000)
	public void verifyCreatedForAllSchedulerUsesVirtualTime() {
		//a timeout will occur if virtual time isn't used
		StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofSeconds(3))
		                                       .map(d -> "t" + d),
				VirtualTimeScheduler::create,
				0)
		            .thenRequest(1)
		            .thenAwait(Duration.ofSeconds(1))
		            .thenAwait(Duration.ofSeconds(2))
		            .expectNext("t0")
		            .thenCancel()
		            .verify();
	}

	@Test
	public void noSignalRealTime() {
		Duration verifyDuration = StepVerifier.create(Mono.never())
		                                      .expectSubscription()
		                                      .expectTimeout(Duration.ofSeconds(1))
		                                      .verify(Duration.ofMillis(1100));

		assertThat(verifyDuration.toMillis()).isGreaterThanOrEqualTo(1000L);
	}

	@Test(timeout = 500)
	public void noSignalVirtualTime() {
		StepVerifier.withVirtualTime(Mono::never, 1)
		            .expectSubscription()
		            .expectTimeout(Duration.ofSeconds(100))
		            .verify();
	}

	@Test
	public void longDelayAndNoTermination() {
		StepVerifier.withVirtualTime(() -> Flux.just("foo", "bar")
		                                       .delayElements(Duration.ofSeconds(5))
		                                       .concatWith(Mono.never()),
				Long.MAX_VALUE)
		            .expectSubscription()
		            .expectNoEvent(Duration.ofSeconds(5))
		            .expectNext("foo")
		            .expectNoEvent(Duration.ofSeconds(5))
		            .expectNextCount(1)
		            .expectTimeout(Duration.ofHours(10))
		            .verify();
	}

	//source: https://stackoverflow.com/questions/58486417/how-to-verify-with-stepverifier-that-provided-mono-did-not-completed
	@Test
	public void expectTimeoutSmokeTest() {
		Mono<String> neverMono = Mono.never();
		Mono<String> completingMono = Mono.empty();

		StepVerifier.create(neverMono, StepVerifierOptions.create().scenarioName("neverMono should pass"))
				.expectTimeout(Duration.ofSeconds(1))
				.verify();

		StepVerifier shouldFail = StepVerifier.create(completingMono).expectTimeout(Duration.ofSeconds(1));

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(shouldFail::verify)
				.withMessage("expectation \"expectTimeout\" failed (expected: timeout(1s); actual: onComplete())");
	}

	@Test
	public void verifyTimeoutSmokeTest() {
		Mono<String> neverMono = Mono.never();
		Mono<String> completingMono = Mono.empty();

		StepVerifier.create(neverMono, StepVerifierOptions.create().scenarioName("neverMono should pass"))
				.verifyTimeout(Duration.ofSeconds(1));

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(completingMono).verifyTimeout(Duration.ofSeconds(1)))
				.withMessage("expectation \"expectTimeout\" failed (expected: timeout(1s); actual: onComplete())");
	}

	@Test
	public void thenAwaitThenCancelWaitsForDuration() {
		Duration verifyDuration = StepVerifier.create(Flux.just("foo", "bar")
		                                                  .delayElements(Duration.ofMillis(500)))
		                                      .expectSubscription()
		                                      .thenAwait(Duration.ofMillis(500))
		                                      .expectNext("foo")
		                                      .thenAwait(Duration.ofMillis(200))
		                                      .thenCancel()
		                                      .verify(Duration.ofMillis(1000));

		assertThat(verifyDuration.toMillis()).isGreaterThanOrEqualTo(700L);
	}

	@Test
	public void testThenConsumeWhile() {
		StepVerifier.create(Flux.range(3, 8))
		            .expectNextMatches(first -> first == 3)
		            .thenConsumeWhile(v -> v < 9)
		            .expectNext(9)
		            .expectNext(10)
		            .expectComplete()
		            .log()
		            .verify();
	}

	@Test
	public void testThenConsumeWhileWithConsumer() {
		LongAdder count = new LongAdder();

		StepVerifier.create(Flux.range(3, 8))
		            .expectNextMatches(first -> first == 3)
		            .thenConsumeWhile(v -> v < 9, v -> count.increment())
		            .expectNext(9)
		            .expectNext(10)
		            .expectComplete()
		            .log()
		            .verify();

		assertThat(count.intValue()).isEqualTo(5);
	}

	@Test
	public void testThenConsumeWhileFails() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.range(3, 8))
			            .expectNextMatches(first -> first == 3)
			            .thenConsumeWhile(v -> v <= 9)
			            .expectNext(9)
			            .expectNext(10)
			            .expectComplete()
			            .log()
			            .verify())
		        .withMessageContaining("expectNext(9)");
	}

	@Test
	public void testExpectRecordedMatches() {
		List<Integer> expected = Arrays.asList(1,2);

		StepVerifier.create(Flux.just(1,2))
		            .recordWith(ArrayList::new)
		            .thenConsumeWhile(i -> i < 2)
		            .expectRecordedMatches(expected::equals)
		            .thenCancel()
		            .verify();
	}

	@Test
	public void testExpectRecordedMatchesTwice() {
		List<Integer> expected1 = Arrays.asList(1,2);
		List<Integer> expected2 = Arrays.asList(3,4);

		StepVerifier.create(Flux.just(1,2,3,4))
		            .recordWith(ArrayList::new)
		            .thenConsumeWhile(i -> i < 2)
		            .expectRecordedMatches(expected1::equals)
		            .recordWith(ArrayList::new)
		            .thenConsumeWhile(i -> i < 4)
		            .expectRecordedMatches(expected2::equals)
		            .thenCancel()
		            .verify();
	}

	@Test
	public void testExpectRecordedMatchesWithoutComplete() {
		List<Integer> expected = Arrays.asList(1,2);

		TestPublisher<Integer> publisher = TestPublisher.createCold();
		publisher.next(1);
		publisher.next(2);

		StepVerifier.create(publisher)
		            .recordWith(ArrayList::new)
		            .thenConsumeWhile(i -> i < 2)
		            .expectRecordedMatches(expected::equals)
		            .thenCancel()
		            .verify();
	}

	@Test
	public void testWithDescription() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo", "bar", "baz"), 3)
			            .expectNext("foo")
			            .as("first")
			            .expectNext("bar")
			            .as("second")
			            .expectNext("bar")
			            .as("third")
			            .as("this is ignored")
			            .expectComplete()
			            .log()
			            .verify())
				.withMessageStartingWith("expectation \"third\" failed");
	}

	@Test
	public void testWithDescriptionAndScenarioName() {
		StepVerifierOptions options = StepVerifierOptions.create()
		                                                 .initialRequest(3)
		                                                 .scenarioName("some scenario name");
		StepVerifier stepVerifier = StepVerifier
				.create(Flux.just("foo", "bar", "baz"), options)
				.expectNext("foo")
				.as("first")
				.expectNext("bar")
				.as("second")
				.expectNext("bar")
				.as("third")
				.as("this is ignored")
				.expectComplete()
				.log();

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(stepVerifier::verify)
				.withMessage("[some scenario name] expectation \"third\" failed (expected value: bar; actual value: baz)");
	}

	@Test
	public void testDurationFailureWithScenarioName() {
		StepVerifierOptions options = StepVerifierOptions.create()
		                                                 .scenarioName("some scenario name");
		StepVerifier stepVerifier = StepVerifier
				.create(Mono.delay(Duration.ofMillis(100)), options)
				.expectNextCount(1)
				.expectComplete();

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> stepVerifier.verify(Duration.ofMillis(10)))
				.withMessageStartingWith("[some scenario name] VerifySubscriber timed out on [email protected]");
	}

	@Test
	public void noCancelOnUnexpectedErrorSignal() {
		LongAdder cancelled = new LongAdder();
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.error(new IllegalArgumentException())
			                        .doOnCancel(cancelled::increment))
			            .expectComplete()
			            .verify())
				.withMessageContaining("expected: onComplete(); actual: onError");
		assertThat(cancelled.intValue())
				.overridingErrorMessage("the expectComplete assertion caused a cancellation")
				.isZero();
	}

	@Test
	public void noCancelOnUnexpectedCompleteSignal() {
		LongAdder cancelled = new LongAdder();
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.empty()
			                        .doOnCancel(cancelled::increment))
			            .expectError()
			            .verify())
				.withMessageContaining("expected: onError(); actual: onComplete()");
		assertThat(cancelled.intValue())
				.overridingErrorMessage("the expectError assertion caused a cancellation")
				.isZero();
	}

	@Test
	public void noCancelOnUnexpectedCompleteSignal2() {
		LongAdder cancelled = new LongAdder();
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo")
			                        .doOnCancel(cancelled::increment))
			            .expectNext("foo", "bar")
			            .expectComplete()
			            .verify())
		        .withMessageContaining("expected: onNext(bar); actual: onComplete()");

		assertThat(cancelled.intValue())
				.overridingErrorMessage("the expectNext assertion caused a cancellation")
	            .isZero();
	}

	@Test
	public void noCancelOnCompleteWhenSequenceUnexpected() {
		LongAdder cancelled = new LongAdder();
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo")
			                        .doOnCancel(cancelled::increment))
			            .expectNextSequence(Arrays.asList("foo", "bar"))
			            .expectComplete()
			            .verify())
		        .withMessageContaining("expectNextSequence");
		assertThat(cancelled.intValue())
				.overridingErrorMessage("the expectNextSequence assertion caused a cancellation")
		        .isZero();
	}

	@Test
	public void noCancelOnCompleteWhenCountUnexpected() {
		LongAdder cancelled = new LongAdder();
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo")
			                        .doOnCancel(cancelled::increment))
			            .expectNextCount(2)
			            .expectComplete()
			            .verify())
				.withMessageContaining("expectNextCount");

		assertThat(cancelled.intValue())
				.overridingErrorMessage("the expectNextCount assertion caused a cancellation")
				.isZero();
	}

	@Test
	public void noCancelOnErrorWhenCollectUnexpected() {
		LongAdder cancelled = new LongAdder();
		LongAdder records = new LongAdder();
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.error(new IllegalArgumentException())
			                        .doOnCancel(cancelled::increment))
			            .recordWith(() -> {
								records.increment();
								return new ArrayList<>();
			            })
			            .expectRecordedMatches(l -> l.size() == 2)
			            .expectComplete()
			            .verify())
		        .withMessageContaining("expected collection predicate match");

		assertThat(cancelled.intValue())
				.overridingErrorMessage("the expectRecordedMatches assertion caused a cancellation")
				.isZero();
		assertThat(records.intValue())
				.as("unexpected number of records")
				.isEqualTo(1);
	}

	//TODO records: find a way to test the case where supplied collection is null, and signal is complete/error
	//TODO records: find a way to test the case where there hasn't been a recorder set, and signal is complete/error

	@Test
	public void cancelOnUnexpectedNextWithMoreData() {
		LongAdder cancelled = new LongAdder();
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo", "bar")
			                        .doOnCancel(cancelled::increment))
			            .expectNext("baz")
			            .expectComplete()
			            .verify())
		        .withMessageContaining("expected value: baz;");

		assertThat(cancelled.intValue())
				.overridingErrorMessage("the expectNext assertion didn't cause a cancellation")
		        .isEqualTo(1);
	}

	@Test
	public void boundedInitialOverflowIsDetected() {
		TestPublisher<String> publisher = TestPublisher.createNoncompliant(
				REQUEST_OVERFLOW);

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(publisher, 1)
				                              .then(() -> publisher.emit("foo", "bar"))
				                              .expectNext("foo")
				                              .expectComplete()
				                              .verify())
				.withMessageStartingWith("request overflow (")
				.withMessageEndingWith("expected production of at most 1;" +
						" produced: 2; request overflown by signal: onNext(bar))");
	}

	@Test
	public void boundedRequestOverflowIsDetected() {
		TestPublisher<String> publisher = TestPublisher.createNoncompliant(
				REQUEST_OVERFLOW);

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(publisher, 0)
			            .thenRequest(2)
			            .then(() -> publisher.emit("foo", "bar", "baz"))
			            .expectNext("foo", "bar")
			            .expectComplete()
			            .verify())
				.withMessageStartingWith("request overflow (")
		        .withMessageEndingWith("expected production of at most 2;"
					+ " produced: 3; request overflown by signal: onNext(baz))");
	}

	@Test
	public void initialBoundedThenUnboundedRequestDoesntOverflow() {
		TestPublisher<String> publisher = TestPublisher.createNoncompliant(
				REQUEST_OVERFLOW);

		StepVerifier.create(publisher, 2)
		            .thenRequest(Long.MAX_VALUE - 2)
		            .then(() -> publisher.emit("foo", "bar", "baz"))
	                .expectNext("foo", "bar", "baz")
	                .expectComplete()
	                .verify();
	}

	@Test
	public void verifyErrorTriggersVerificationFail() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.empty())
				                              .verifyError())
		        .withMessage("expectation \"expectError()\" failed (expected: onError(); actual: onComplete())");
	}

	@Test
	public void verifyErrorTriggersVerificationSuccess() {
		StepVerifier.create(Flux.error(new IllegalArgumentException()))
		            .verifyError();
	}

	@Test
	public void verifyErrorClassTriggersVerificationFail() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.empty())
				                              .verifyError(IllegalArgumentException.class))
		        .withMessage("expectation \"expectError(Class)\" failed (expected: onError(IllegalArgumentException); actual: onComplete())");
	}

	@Test
	public void verifyErrorClassTriggersVerificationSuccess() {
		StepVerifier.create(Flux.error(new IllegalArgumentException()))
		            .verifyError(IllegalArgumentException.class);
	}

	@Test
	public void verifyErrorMessageTriggersVerificationFail() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.empty())
				                              .verifyErrorMessage("boom"))
		        .withMessage("expectation \"expectErrorMessage\" failed (expected: onError(\"boom\"); actual: onComplete())");
	}

	@Test
	public void verifyErrorMessageTriggersVerificationSuccess() {
		StepVerifier.create(Flux.error(new IllegalArgumentException("boom")))
		            .verifyErrorMessage("boom");
	}

	@Test
	public void verifyErrorPredicateTriggersVerificationFailBadSignal() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.empty())
				                              .verifyErrorMatches(e -> e instanceof IllegalArgumentException))
		        .withMessage("expectation \"expectErrorMatches\" failed (expected: onError(); actual: onComplete())");
	}

	@Test
	public void verifyErrorPredicateTriggersVerificationFailNoMatch() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.error(new IllegalArgumentException("boom")))
				                              .verifyErrorMatches(e -> e.getMessage() == null))
		        .withMessage("expectation \"expectErrorMatches\" failed (predicate failed on exception: java.lang.IllegalArgumentException: boom)");
	}

	@Test
	public void verifyErrorPredicateTriggersVerificationSuccess() {
		StepVerifier.create(Flux.error(new IllegalArgumentException("boom")))
		            .verifyErrorMatches(e -> e instanceof IllegalArgumentException);
	}

	@Test
	public void verifyErrorAssertionTriggersVerificationFailBadSignal() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.empty())
				                              .verifyErrorSatisfies(e -> assertThat(e).isNotNull()))
		        .withMessage("expectation \"verifyErrorSatisfies\" failed (expected: onError(); actual: onComplete())");
	}

	@Test
	public void verifyErrorAssertionTriggersVerificationFailNoMatch() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.error(new IllegalArgumentException("boom")))
				                              .verifyErrorSatisfies(e -> assertThat(e).hasMessage("foo")))
		        .withMessage("expectation \"verifyErrorSatisfies\" failed (assertion failed on exception <java.lang.IllegalArgumentException: boom>: "
				        + "\nExpecting message:\n <\"foo\">\nbut was:\n <\"boom\">)");
	}

	@Test
	public void verifyErrorAssertionTriggersVerificationSuccess() {
		StepVerifier.create(Flux.error(new IllegalArgumentException("boom")))
		            .verifyErrorSatisfies(e -> assertThat(e).hasMessage("boom"));
	}

	@Test
	public void verifyCompleteTriggersVerificationFail() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.error(new IllegalArgumentException()))
				                              .verifyComplete())
		        .withMessage("expectation \"expectComplete\" failed (expected: onComplete(); actual: onError(java.lang.IllegalArgumentException))");
	}

	@Test
	public void verifyCompleteTriggersVerificationSuccess() {
			StepVerifier.create(Flux.just(1, 2))
			            .expectNext(1, 2)
		                .verifyComplete();
	}

	@Test
	public void expectNextCountAfterExpectNext() {
		StepVerifier.create(Flux.range(1, 5))
	                .expectNext(1, 2)
	                .expectNextCount(3)
	                .verifyComplete();
	}

	@Test
	public void expectNextCountAfterThenConsumeWhile() {
		StepVerifier.create(Flux.range(1, 5).log())
	                .thenConsumeWhile(i -> i <= 2)
	                .expectNextCount(3)
	                .verifyComplete();
	}

	@Test
	public void expectNextCountAfterExpectNextCount() {
		StepVerifier.create(Flux.range(1, 5))
	                .expectNextCount(2)
	                .expectNextCount(3)
	                .verifyComplete();
	}

	@Test
	public void expectNextCountAfterExpectNextMatches() {
		StepVerifier.create(Flux.range(1, 5))
	                .expectNextMatches(i -> true)
	                .expectNextMatches(i -> true)
	                .expectNextCount(3)
	                .verifyComplete();
	}

	@Test
	public void expectNextCountAfterExpectNextSequence() {
		StepVerifier.create(Flux.range(1, 5))
	                .expectNextSequence(Arrays.asList(1, 2))
	                .expectNextCount(3)
	                .verifyComplete();
	}

	@Test
	public void expectNextCountAfterConsumeNextWith() {
		StepVerifier.create(Flux.range(1, 5))
	                .consumeNextWith(i -> {})
	                .consumeNextWith(i -> {})
	                .expectNextCount(3)
	                .verifyComplete();
	}

	@Test
	public void expectNextSequenceWithPartialMatchingSequence() {
		StepVerifier.create(Flux.range(1, 5))
	                .expectNextSequence(Arrays.asList(1, 2, 3))
	                .expectNext(4, 5)
	                .verifyComplete();
	}

	@Test
	public void expectNextSequenceWithPartialMatchingSequenceNoMoreExpectation() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.range(1, 5))
	                .expectNextSequence(Arrays.asList(1, 2, 3))
	                .verifyComplete())
	            .withMessage("expectation \"expectComplete\" failed (expected: onComplete(); actual: onNext(4))");
	}

	@Test
	public void expectNextSequenceEmptyListBeforeExpectNext() {
		StepVerifier.create(Flux.just("foo", "bar"))
				.expectNextSequence(emptyList())
				.expectNext("foo", "bar")
				.expectComplete()
				.verify();
	}

	@Test
	public void expectNextErrorIsSuppressed() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo")
				                                          .flatMap(r -> { throw new ArrayIndexOutOfBoundsException();}))
	                .expectNext("foo")
	                .verifyError())
				.satisfies(error -> {
					assertThat(error)
							.hasMessageStartingWith("expectation \"expectNext(foo)\" failed")
							.hasMessageContaining("actual: onError(java.lang.ArrayIndexOutOfBoundsException)");
					assertThat(error.getSuppressed())
							.hasSize(1)
							.allMatch(spr -> spr instanceof ArrayIndexOutOfBoundsException);
				});
	}

	@Test
	public void consumeNextErrorIsSuppressed() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo")
				.flatMap(r -> { throw new ArrayIndexOutOfBoundsException();}))
	                .consumeNextWith(v -> assertThat(v).isNotNull())
	                .verifyError())
				.satisfies(error -> {
					assertThat(error)
							.hasMessageStartingWith("expectation \"consumeNextWith\" failed")
							.hasMessageContaining("actual: onError(java.lang.ArrayIndexOutOfBoundsException)");
					assertThat(error.getSuppressed())
							.hasSize(1)
							.allMatch(spr -> spr instanceof ArrayIndexOutOfBoundsException);
				});
	}

	@Test
	public void expectNextCountErrorIsSuppressed() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo")
				.flatMap(r -> { throw new ArrayIndexOutOfBoundsException();}))
	                .expectNextCount(1)
	                .verifyError())
				.satisfies(error -> {
					assertThat(error)
							.hasMessageStartingWith("expectation \"expectNextCount(1)\" failed")
							.hasMessageContaining("signal: onError(java.lang.ArrayIndexOutOfBoundsException)");
					assertThat(error.getSuppressed())
							.hasSize(1)
							.allMatch(spr -> spr instanceof ArrayIndexOutOfBoundsException);
				});
	}

	@Test
	public void expectNextSequenceErrorIsSuppressed() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo")
				.flatMap(r -> { throw new ArrayIndexOutOfBoundsException();}))
	                .expectNextSequence(Arrays.asList("foo"))
	                .verifyError())
				.satisfies(error -> {
					assertThat(error)
							.hasMessageStartingWith("expectation \"expectNextSequence\" failed")
							.hasMessageContaining("actual signal: onError(java.lang.ArrayIndexOutOfBoundsException)");
					assertThat(error.getSuppressed())
							.hasSize(1)
							.allMatch(spr -> spr instanceof ArrayIndexOutOfBoundsException);
				});
	}

	@Test
	public void consumeWhileErrorIsSuppressed() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo", "bar", "foobar")
		                        .map(r -> { if (r.length() > 3) throw new ArrayIndexOutOfBoundsException(); return r;}))
	                .thenConsumeWhile(s -> s.length() <= 3) //doesn't fail by itself...
	                .verifyComplete()) //...so this will fail
				.satisfies(error -> {
					assertThat(error)
							.hasMessageStartingWith("expectation \"expectComplete\" failed")
							.hasMessageContaining("actual: onError(java.lang.ArrayIndexOutOfBoundsException)");
					assertThat(error.getSuppressed())
							.hasSize(1)
							.allMatch(spr -> spr instanceof ArrayIndexOutOfBoundsException);
				});
	}

	@Test
	public void requestBufferDoesntOverflow() {
		LongAdder requestCallCount = new LongAdder();
		LongAdder totalRequest = new LongAdder();
		Flux<Integer> source = Flux.range(1, 10).hide()
		                           .doOnRequest(r -> requestCallCount.increment())
		                           .doOnRequest(totalRequest::add);

		StepVerifier.withVirtualTime(//start with a request for 1 buffer
				() -> source.bufferUntil(i -> i % 3 == 0), 1)
		            .expectSubscription()
		            .expectNext(Arrays.asList(1, 2, 3))
		            .expectNoEvent(Duration.ofSeconds(1))
		            .thenRequest(2)
		            .expectNext(Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9))
		            .expectNoEvent(Duration.ofSeconds(1))
		            .thenRequest(3)
		            .expectNext(Collections.singletonList(10))
		            .expectComplete()
		            .verify();

		//see same pattern in reactor.core.publisher.FluxBufferPredicateTest.requestBounded
		assertThat(requestCallCount.intValue()).isEqualTo(9L);
		assertThat(totalRequest.longValue()).isEqualTo(12L);
	}

	@Test(timeout = 1000L)
	public void expectCancelDoNotHang() {
		StepVerifier.create(Flux.just("foo", "bar"), 1)
		            .expectNext("foo")
		            .thenCancel()
		            .verify();
	}

	@Test(timeout = 1000L)
	public void consumeNextWithLowRequestShortcircuits() {
		StepVerifier.Step<String> validSoFar = StepVerifier.create(Flux.just("foo", "bar"), 1)
				                         .expectNext("foo");

		assertThatExceptionOfType(IllegalArgumentException.class)
				.isThrownBy(() -> validSoFar.consumeNextWith(s -> {}))
	            .withMessageStartingWith("The scenario will hang at consumeNextWith due to too little request being performed for the expectations to finish")
	            .withMessageEndingWith("request remaining since last step: 0, expected: 1");
	}

	@Test(timeout = 1000L)
	public void assertNextLowRequestShortcircuits() {
		StepVerifier.Step<String> validSoFar = StepVerifier.create(Flux.just("foo", "bar"), 1)
		                                                   .expectNext("foo");

		assertThatExceptionOfType(IllegalArgumentException.class)
				.isThrownBy(() -> validSoFar.assertNext(s -> {}))
				.withMessageStartingWith("The scenario will hang at assertNext due to too little request being performed for the expectations to finish")
				.withMessageEndingWith("request remaining since last step: 0, expected: 1");
	}

	@Test(timeout = 1000L)
	public void expectNextLowRequestShortcircuits() {
		StepVerifier.Step<String> validSoFar = StepVerifier.create(Flux.just("foo", "bar"), 1)
		                                                   .expectNext("foo");

		assertThatExceptionOfType(IllegalArgumentException.class)
				.isThrownBy(() -> validSoFar.expectNext("bar"))
				.withMessageStartingWith("The scenario will hang at expectNext(bar) due to too little request being performed for the expectations to finish")
				.withMessageEndingWith("request remaining since last step: 0, expected: 1");
	}

	@Test(timeout = 1000L)
	public void expectNextCountLowRequestShortcircuits() {
		assertThatExceptionOfType(IllegalArgumentException.class)
				.isThrownBy(() -> StepVerifier.create(Flux.just("foo", "bar"), 1)
				                              .expectNextCount(2)
				)
	            .withMessageStartingWith("The scenario will hang at expectNextCount(2) due to too little request being performed for the expectations to finish; ")
				.withMessageEndingWith("request remaining since last step: 1, expected: 2");
	}

	@Test(timeout = 1000L)
	public void expectNextMatchesLowRequestShortcircuits() {
		StepVerifier.Step<String> validSoFar = StepVerifier.create(Flux.just("foo", "bar"), 1)
		                                                   .expectNext("foo");

		assertThatExceptionOfType(IllegalArgumentException.class)
				.isThrownBy(() -> validSoFar.expectNextMatches("bar"::equals))
				.withMessageStartingWith("The scenario will hang at expectNextMatches due to too little request being performed for the expectations to finish")
				.withMessageEndingWith("request remaining since last step: 0, expected: 1");
	}

	@Test(timeout = 1000L)
	public void expectNextSequenceLowRequestShortcircuits() {
		StepVerifier.Step<String> validSoFar = StepVerifier.create(Flux.just("foo", "bar"), 1);
		List<String> expected = Arrays.asList("foo", "bar");

		assertThatExceptionOfType(IllegalArgumentException.class)
				.isThrownBy(() -> validSoFar.expectNextSequence(expected))
				.withMessageStartingWith("The scenario will hang at expectNextSequence due to too little request being performed for the expectations to finish")
				.withMessageEndingWith("request remaining since last step: 1, expected: 2");
	}

	@Test(timeout = 1000L)
	public void thenConsumeWhileLowRequestShortcircuits() {
		StepVerifier.Step<Integer> validSoFar = StepVerifier.create(Flux.just(1, 2), 1)
		                                                    .expectNext(1);

		assertThatExceptionOfType(IllegalArgumentException.class)
				.isThrownBy(() -> validSoFar.thenConsumeWhile(s -> s == 1))
				.withMessageStartingWith("The scenario will hang at thenConsumeWhile due to too little request being performed for the expectations to finish; ")
	            .withMessageEndingWith("request remaining since last step: 0, expected: at least 1 (best effort estimation)");
	}

	@Test(timeout = 1000L)
	public void lowRequestCheckCanBeDisabled() {
		StepVerifier.create(Flux.just(1, 2),
				StepVerifierOptions.create().initialRequest(1).checkUnderRequesting(false))
		            .expectNext(1)
		            .thenConsumeWhile(s -> s == 1); //don't verify, this alone would throw an exception if check activated
	}

	@Test
	public void takeAsyncFusedBackpressured() {
		FluxIdentityProcessor<String> up = Processors.unicast();
		StepVerifier.create(up.take(3), 0)
		            .expectFusion()
		            .then(() -> up.onNext("test"))
		            .then(() -> up.onNext("test"))
		            .then(() -> up.onNext("test"))
		            .thenRequest(2)
		            .expectNext("test", "test")
		            .thenRequest(1)
		            .expectNext("test")
		            .verifyComplete();
	}

	@Test
	public void cancelAsyncFusion() {
		FluxIdentityProcessor<String> up = Processors.unicast();
		StepVerifier.create(up.take(3), 0)
		            .expectFusion()
		            .then(() -> up.onNext("test"))
		            .then(() -> up.onNext("test"))
		            .then(() -> up.onNext("test"))
		            .thenRequest(2)
		            .expectNext("test", "test")
		            .thenCancel()
					.verify();
	}

	@Test
	public void virtualTimeSchedulerUseExactlySupplied() {
		VirtualTimeScheduler vts1 = VirtualTimeScheduler.create();
		VirtualTimeScheduler vts2 = VirtualTimeScheduler.create();
		VirtualTimeScheduler.getOrSet(vts1);

		StepVerifier.withVirtualTime(Mono::empty, () -> vts2, Long.MAX_VALUE)
		            .then(() -> assertThat(VirtualTimeScheduler.get()).isSameAs(vts2))
		            .verifyComplete();

		assertThat(vts1.isDisposed()).isFalse();
		assertThat(vts2.isDisposed()).isTrue();
		assertThat(VirtualTimeScheduler.isFactoryEnabled()).isFalse();
	}

	@Test
	public void virtualTimeSchedulerVeryLong() {
		StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofMillis(1))
		                                       .map(tick -> new Date())
		                                       .take(100000)
		                                       .collectList())
		            .thenAwait(Duration.ofHours(1000))
		            .consumeNextWith(list -> Assert.assertTrue(list.size() == 100000))
		            .verifyComplete();
	}

	@Test
	public void withInitialContext() {
		StepVerifier.create(Mono.subscriberContext(),
				StepVerifierOptions.create().withInitialContext(Context.of("foo", "bar")))
		            .assertNext(c -> Assertions.assertThat(c.getOrDefault("foo", "baz"))
		                                       .isEqualTo("bar"))
		            .verifyComplete();
	}

	@Test
	public void withInitialContextButNoPropagation() {
		StepVerifier.create(Mono.just(1), //just(1) uses a ScalarSubscription which can't be resolved to a chain of parents
				StepVerifierOptions.create().withInitialContext(Context.of("foo", "bar")))
		            .expectNoAccessibleContext()
		            .expectNext(1)
		            .verifyComplete();
	}

	@Test
	public void withInitialContextAndContextAssertionsParents() {
		StepVerifier.create(Mono.just(1).map(i -> i + 10), //this causes the subscription to be resolvable to a chain of parents
				StepVerifierOptions.create().withInitialContext(Context.of("foo", "bar")))
		            .expectAccessibleContext()
		            .contains("foo", "bar")
		            .then()
		            .expectNext(11)
		            .verifyComplete();
	}

	//see https://github.com/reactor/reactor-core/issues/959
	@Test
	public void assertNextWithSubscribeOnDirectProcessor() {
		Scheduler scheduler = Schedulers.newBoundedElastic(1, 100, "test");
		FluxIdentityProcessor<Integer> processor = Processors.more().multicastNoBackpressure();
		Mono<Integer> doAction = Mono.fromSupplier(() -> 22)
		                             .doOnNext(processor::onNext)
		                             .subscribeOn(scheduler);

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(
						StepVerifier.create(processor)
						            .then(doAction::subscribe)
						            .assertNext(v -> assertThat(v).isEqualTo(23))
						            .thenCancel()
								::verify);
	}

	//see https://github.com/reactor/reactor-core/issues/959
	@Test
	public void assertNextWithSubscribeOnJust() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(
						StepVerifier.create(Flux.just(1)
						                        .subscribeOn(Schedulers.newSingle("test")))
						            .then(() -> System.out.println("foo"))
						            .assertNext(v -> assertThat(v).isNull())
						            .thenCancel()
								::verify);
	}

	@Test
	public void parallelVerifyWithVtsMutuallyExclusive() {
		ExecutorService executorService = Executors.newFixedThreadPool(2);
		for (int i = 0; i < 10; i++) {
			Future<Duration> ex1 = executorService.submit(() -> StepVerifier
					.withVirtualTime(() -> Flux.just("A", "B", "C")
					                           .delaySequence(Duration.ofMillis(100))
					)
					.then(() -> {
						try {
							Thread.sleep(100);
						}
						catch (InterruptedException e) {
							e.printStackTrace();
						}
					})
					.thenAwait(Duration.ofMillis(100))
					.expectNextCount(3)
					.verifyComplete());

			Future<Duration> ex2 = executorService.submit(() -> StepVerifier
					.withVirtualTime(() -> Flux.just(1, 2, 3)
					                           .delaySequence(Duration.ofMillis(100))
					)
					.thenAwait(Duration.ofMillis(100))
					.expectNextCount(3)
					.expectComplete()
					.verify());

			assertThatCode(ex1::get).as("execution 1 in iteration #" + i).doesNotThrowAnyException();
			assertThatCode(ex2::get).as("execution 2 in iteration #" + i).doesNotThrowAnyException();
		}
	}

	@Test(timeout = 5000)
	public void gh783() {
		int size = 1;
		Scheduler parallel = Schedulers.newParallel("gh-783");
		StepVerifier.withVirtualTime(() -> Flux.just("Oops")
		                                       .take(size)
		                                       .subscribeOn(parallel)
		                                       .flatMap(message -> {
			                                       Flux<Long> interval = Flux.interval(Duration.ofSeconds(1));
			                                       return interval.map( tick -> message);
		                                       })
		                                       .take(size)
		                                       .collectList()
		)
		            .thenAwait(Duration.ofHours(1))
		            .consumeNextWith(list -> assertThat(list).hasSize(size))
		            .verifyComplete();
	}

	@Test(timeout = 5000)
	public void gh783_deferredAdvanceTime() {
		int size = 61;
		Scheduler parallel = Schedulers.newParallel("gh-783");
		StepVerifier.withVirtualTime(() -> Flux.range(1, 10)
		                                       .take(size)
		                                       .subscribeOn(parallel)
		                                       .flatMap(message -> {
			                                       Flux<Long> interval = Flux.interval(Duration.ofSeconds(1));
			                                       return interval.map( tick -> message);
		                                       }, 30,1)
		                                       .take(size)
		                                       .collectList()
		)
		            .thenAwait(Duration.ofHours(2))
		            .consumeNextWith(list -> assertThat(list).hasSize(size))
		            .expectComplete()
		            .verify();
	}

	@Test
	@Ignore
	//FIXME this case of doubly-nested schedules is still not fully fixed
	public void gh783_withInnerFlatmap() {
		int size = 61;
		Scheduler parallel = Schedulers.newParallel("gh-783");
		StepVerifier.withVirtualTime(() -> Flux.range(1, 10)
		                                       .take(size)
		                                       .subscribeOn(parallel)
		                                       .flatMap(message -> {
			                                       Flux<Long> interval = Flux.interval(Duration.ofSeconds(1));
			                                       return interval.flatMap( tick -> Mono.delay(Duration.ofMillis(500))
			                                                                            .thenReturn(message)
			                                                                            .subscribeOn(parallel))
			                                                      .subscribeOn(parallel);
		                                       }, 1,30)
		                                       .take(size)
		                                       .collectList()
		)
		            .thenAwait(Duration.ofMillis(1500 * (size + 10)))
		            .consumeNextWith(list -> assertThat(list).hasSize(size))
		            .expectComplete()
		            .verify(Duration.ofSeconds(5));
	}

	@Test
	public void gh783_intervalFullyEmitted() {
		StepVerifier.withVirtualTime(() -> Flux.just("foo").flatMap(message -> Flux.interval(Duration.ofMinutes(5)).take(12)))
		            .expectSubscription()
		            .expectNoEvent(Duration.ofMinutes(5))
		            .expectNext(0L)
		            .thenAwait(Duration.ofMinutes(25))
		            .expectNext(1L, 2L, 3L, 4L, 5L)
		            .thenAwait(Duration.ofMinutes(30))
		            .expectNext(6L, 7L, 8L, 9L, 10L, 11L)
		            .expectComplete()
		            .verify(Duration.ofMillis(500));
	}

	@Test
	public void gh783_firstSmallAdvance() {
		StepVerifier.withVirtualTime(() -> Flux.just("foo").flatMap(message -> Flux.interval(Duration.ofMinutes(5)).take(12)))
		            .expectSubscription()
		            .expectNoEvent(Duration.ofMinutes(3))
		            .thenAwait(Duration.ofHours(1))
		            .expectNextCount(12)
		            .expectComplete()
		            .verify(Duration.ofMillis(500));
	}

	@Test
	public void noEventExpectationButComplete() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(StepVerifier.create(Flux.empty().hide())
				                        .expectSubscription()
				                        .expectNoEvent(Duration.ofMillis(50))
				                        .expectComplete()
						::verify)
				.withMessage("Unexpected completion during a no-event expectation");
	}

	@Test
	public void noEventExpectationButError() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(StepVerifier.create(Flux.error(new IllegalStateException("boom")).hide())
				                        .expectSubscription()
				                        .expectNoEvent(Duration.ofMillis(50))
				                        .expectComplete()
						::verify)
				.withMessage("Unexpected error during a no-event expectation: java.lang.IllegalStateException: boom")
				.withCause(new IllegalStateException("boom"));
	}

	@Test
	public void virtualTimeNoEventExpectationButComplete() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(StepVerifier.withVirtualTime(() -> Flux.empty().hide())
				                        .expectSubscription()
				                        .expectNoEvent(Duration.ofMillis(50))
				                        .expectComplete()
						::verify)
				.withMessage("Unexpected completion during a no-event expectation");
	}

	@Test
	public void virtualTimeNoEventExpectationButError() {
		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(StepVerifier.withVirtualTime(() -> Flux.error(new IllegalStateException("boom")).hide())
				                        .expectSubscription()
				                        .expectNoEvent(Duration.ofMillis(50))
				                        .expectComplete()
						::verify)
				.withMessage("Unexpected error during a no-event expectation: java.lang.IllegalStateException: boom")
				.withCause(new IllegalStateException("boom"));
	}

	@Test
	public void verifyLaterCanVerifyConnectableFlux() {
		Flux<Integer> autoconnectableFlux = Flux.just(1, 2, 3).publish().autoConnect(2);

		StepVerifier deferred1 = StepVerifier.create(autoconnectableFlux)
		                                     .expectNext(1, 2, 3)
		                                     .expectComplete()
		                                     .verifyLater();

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> deferred1.verify(Duration.ofSeconds(1)))
				.withMessageContaining("timed out");

		StepVerifier deferred2 = StepVerifier.create(autoconnectableFlux)
		                                     .expectNext(1, 2, 3)
		                                     .expectComplete()
		                                     .verifyLater()
		                                     .verifyLater()
		                                     .verifyLater()
		                                     .verifyLater();

		deferred1.verify(Duration.ofSeconds(1));
		deferred2.verify(Duration.ofSeconds(1));
	}

	@Test
	public void verifyLaterCanVerifyConnectableFlux_withAssertionErrors() {
		Flux<Integer> autoconnectableFlux = Flux.just(1, 2, 3).publish().autoConnect(2);

		StepVerifier deferred1 = StepVerifier.create(autoconnectableFlux)
		                                     .expectNext(1, 2, 4)
		                                     .expectComplete()
		                                     .verifyLater();

		StepVerifier deferred2 = StepVerifier.create(autoconnectableFlux)
		                                     .expectNext(1, 2, 5)
		                                     .expectComplete()
		                                     .verifyLater();

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> deferred1.verify(Duration.ofSeconds(10)))
				.withMessage("expectation \"expectNext(4)\" failed (expected value: 4; actual value: 3)");

		assertThatExceptionOfType(AssertionError.class)
				.isThrownBy(() -> deferred2.verify(Duration.ofSeconds(10)))
				.withMessage("expectation \"expectNext(5)\" failed (expected value: 5; actual value: 3)");
	}

	@Test
	public void verifyDrainOnRequestInCaseOfFusion() {
		MonoProcessor<Integer> processor = MonoProcessor.create();
		StepVerifier.create(processor, 0)
				.expectFusion(Fuseable.ANY)
				.then(() -> processor.onNext(1))
				.thenRequest(1)
				.expectNext(1)
				.verifyComplete();
	}

	@Test
	public void verifyDrainOnRequestInCaseOfFusion2() {
		ArrayList<Long> requests = new ArrayList<>();
		FluxIdentityProcessor<Integer> processor = Processors.unicast();
		StepVerifier.create(processor.doOnRequest(requests::add), 0)
				.expectFusion(Fuseable.ANY)
				.then(() -> {
					processor.onNext(1);
					processor.onComplete();
				})
				.thenRequest(1)
				.thenRequest(1)
				.thenRequest(1)
				.expectNext(1)
				.verifyComplete();

		assertThat(requests).containsExactly(1L, 1L, 1L);
	}

	//see https://github.com/reactor/reactor-core/issues/2060
	@Test
	public void externalGetOrSetTakenIntoAccount() {
		Scheduler.Worker subscriptionWorker = VirtualTimeScheduler.getOrSet().createWorker();
		List<String> source = Stream.of("first", "second", "third").collect(Collectors.toList());


		StepVerifier.withVirtualTime(() -> {
			FluxIdentityProcessor<String> fluxEmitter = Processors.multicast();

			subscriptionWorker.schedulePeriodically(() -> {
				if (source.size() > 0) {
					fluxEmitter.onNext(source.remove(0));
				}
				else {
					fluxEmitter.onComplete();
				}
			}, 0, 10, TimeUnit.MILLISECONDS);
			return fluxEmitter;
		})
		            .expectNext("first")
		            .expectNoEvent(Duration.ofMillis(10))
		            .expectNext("second")
		            .expectNoEvent(Duration.ofMillis(10))
		            .expectNext("third")
		            .expectNoEvent(Duration.ofMillis(10))
		            .expectComplete()
		            .verify(Duration.ofSeconds(2));
	}

	// See https://github.com/reactor/reactor-core/issues/2107
	@Test
	public void mutualizedSubscribeErrorHandlingPostOnSubscribe() {
		Flux<Object> errorInSubscribeFlux = new Flux<Object>() {
			@Override
			public void subscribe(CoreSubscriber<? super Object> actual) {
				actual.onSubscribe(Operators.emptySubscription());
				throw new IllegalStateException("ErrorInSubscribeFlux");
			}
		};
		StepVerifier.create(errorInSubscribeFlux).verifyErrorSatisfies(e -> {
            assertThat(e)
		            .isInstanceOf(IllegalStateException.class)
                    .hasMessage("ErrorInSubscribeFlux");
        });
	}
}