/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package reactor.test;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.Fuseable;
import reactor.core.publisher.Operators;
/**
* <pre>
* ###############################################################
* ###############################################################
* ###############################################################
*
* THIS CODE IS IMPORTED FROM REACTOR-CORE BECAUSE OF
* https://github.com/reactor/reactor-core/issues/135
*
* ###############################################################
* ###############################################################
* ###############################################################
* </pre>
*
* A Subscriber implementation that hosts assertion tests for its state and allows asynchronous cancellation and
* requesting.
* <p>
* To create a new instance of {@link TestSubscriber}, you have the choice between these static methods:
* <ul>
* <li>{@link TestSubscriber#subscribe(Publisher)}: create a new {@link TestSubscriber}, subscribe to it with the
* specified {@link Publisher} and requests an unbounded number of elements.</li>
* <li>{@link TestSubscriber#subscribe(Publisher, long)}: create a new {@link TestSubscriber}, subscribe to it with the
* specified {@link Publisher} and requests {@code n} elements (can be 0 if you want no initial demand).
* <li>{@link TestSubscriber#create()}: create a new {@link TestSubscriber} and requests an unbounded number of
* elements.</li>
* <li>{@link TestSubscriber#create(long)}: create a new {@link TestSubscriber} and requests {@code n} elements (can be
* 0 if you want no initial demand).
* </ul>
* <p>
* If you are testing asynchronous publishers, don't forget to use one of the {@code await*()} methods to wait for the
* data to assert.
* <p>
* You can extend this class but only the onNext, onError and onComplete can be overridden. You can call
* {@link #request(long)} and {@link #cancel()} from any thread or from within the overridable methods but you should
* avoid calling the assertXXX methods asynchronously.
* <p>
* Usage:
*
* <pre>
* {@code
* TestSubscriber
* .subscribe(publisher)
* .await()
* .assertValues("ABC", "DEF");
* }
* </pre>
*
* @param <T> the value type.
* @author Sebastien Deleuze
* @author David Karnok
* @author Anatoly Kadyshev
* @author Stephane Maldini
* @author Brian Clozel
*/
public class TestSubscriber<T> implements Subscriber<T>, Subscription {
/**
* Default timeout for waiting next values to be received
*/
public static final Duration DEFAULT_VALUES_TIMEOUT = Duration.ofSeconds(3);
@SuppressWarnings("rawtypes") private static final AtomicLongFieldUpdater<TestSubscriber> REQUESTED = AtomicLongFieldUpdater
.newUpdater(TestSubscriber.class, "requested");
@SuppressWarnings("rawtypes") private static final AtomicReferenceFieldUpdater<TestSubscriber, List> NEXT_VALUES = AtomicReferenceFieldUpdater
.newUpdater(TestSubscriber.class, List.class, "values");
@SuppressWarnings("rawtypes") private static final AtomicReferenceFieldUpdater<TestSubscriber, Subscription> S = AtomicReferenceFieldUpdater
.newUpdater(TestSubscriber.class, Subscription.class, "s");
private final List<Throwable> errors = new LinkedList<>();
private final CountDownLatch cdl = new CountDownLatch(1);
volatile Subscription s;
volatile long requested;
volatile List<T> values = new LinkedList<>();
/**
* The fusion mode to request.
*/
private int requestedFusionMode = -1;
/**
* The established fusion mode.
*/
private volatile int establishedFusionMode = -1;
/**
* The fuseable QueueSubscription in case a fusion mode was specified.
*/
private Fuseable.QueueSubscription<T> qs;
private int subscriptionCount = 0;
private int completionCount = 0;
private volatile long valueCount = 0L;
private volatile long nextValueAssertedCount = 0L;
private Duration valuesTimeout = DEFAULT_VALUES_TIMEOUT;
private boolean valuesStorage = true;
// ==============================================================================================================
// Static methods
// ==============================================================================================================
/**
* Blocking method that waits until {@code conditionSupplier} returns true, or if it does not before the specified
* timeout, throws an {@link AssertionError} with the specified error message supplier.
*
* @param timeout the timeout duration
* @param errorMessageSupplier the error message supplier
* @param conditionSupplier condition to break out of the wait loop
* @throws AssertionError
*/
public static void await(Duration timeout, Supplier<String> errorMessageSupplier, BooleanSupplier conditionSupplier) {
Objects.requireNonNull(errorMessageSupplier);
Objects.requireNonNull(conditionSupplier);
Objects.requireNonNull(timeout);
long timeoutNs = timeout.toNanos();
long startTime = System.nanoTime();
do {
if (conditionSupplier.getAsBoolean()) {
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
} while (System.nanoTime() - startTime < timeoutNs);
throw new AssertionError(errorMessageSupplier.get());
}
/**
* Blocking method that waits until {@code conditionSupplier} returns true, or if it does not before the specified
* timeout, throw an {@link AssertionError} with the specified error message.
*
* @param timeout the timeout duration
* @param errorMessage the error message
* @param conditionSupplier condition to break out of the wait loop
* @throws AssertionError
*/
public static void await(Duration timeout, final String errorMessage, BooleanSupplier conditionSupplier) {
await(timeout, new Supplier<String>() {
@Override
public String get() {
return errorMessage;
}
}, conditionSupplier);
}
/**
* Create a new {@link TestSubscriber} that requests an unbounded number of elements.
* <p>
* Be sure at least a publisher has subscribed to it via {@link Publisher#subscribe(Subscriber)} before use assert
* methods.
*
* @see #subscribe(Publisher)
* @param <T> the observed value type
* @return a fresh TestSubscriber instance
*/
public static <T> TestSubscriber<T> create() {
return new TestSubscriber<>();
}
/**
* Create a new {@link TestSubscriber} that requests initially {@code n} elements. You can then manage the demand with
* {@link Subscription#request(long)}.
* <p>
* Be sure at least a publisher has subscribed to it via {@link Publisher#subscribe(Subscriber)} before use assert
* methods.
*
* @param n Number of elements to request (can be 0 if you want no initial demand).
* @see #subscribe(Publisher, long)
* @param <T> the observed value type
* @return a fresh TestSubscriber instance
*/
public static <T> TestSubscriber<T> create(long n) {
return new TestSubscriber<>(n);
}
/**
* Create a new {@link TestSubscriber} that requests an unbounded number of elements, and make the specified
* {@code publisher} subscribe to it.
*
* @param publisher The publisher to subscribe with
* @param <T> the observed value type
* @return a fresh TestSubscriber instance
*/
public static <T> TestSubscriber<T> subscribe(Publisher<T> publisher) {
TestSubscriber<T> subscriber = new TestSubscriber<>();
publisher.subscribe(subscriber);
return subscriber;
}
/**
* Create a new {@link TestSubscriber} that requests initially {@code n} elements, and make the specified
* {@code publisher} subscribe to it. You can then manage the demand with {@link Subscription#request(long)}.
*
* @param publisher The publisher to subscribe with
* @param n Number of elements to request (can be 0 if you want no initial demand).
* @param <T> the observed value type
* @return a fresh TestSubscriber instance
*/
public static <T> TestSubscriber<T> subscribe(Publisher<T> publisher, long n) {
TestSubscriber<T> subscriber = new TestSubscriber<>(n);
publisher.subscribe(subscriber);
return subscriber;
}
// ==============================================================================================================
// Private constructors
// ==============================================================================================================
private TestSubscriber() {
this(Long.MAX_VALUE);
}
private TestSubscriber(long n) {
if (n < 0) {
throw new IllegalArgumentException("initialRequest >= required but it was " + n);
}
REQUESTED.lazySet(this, n);
}
// ==============================================================================================================
// Configuration
// ==============================================================================================================
/**
* Enable or disabled the values storage. It is enabled by default, and can be disable in order to be able to perform
* performance benchmarks or tests with a huge amount values.
*
* @param enabled enable value storage?
* @return this
*/
public final TestSubscriber<T> configureValuesStorage(boolean enabled) {
this.valuesStorage = enabled;
return this;
}
/**
* Configure the timeout in seconds for waiting next values to be received (3 seconds by default).
*
* @param timeout the new default value timeout duration
* @return this
*/
public final TestSubscriber<T> configureValuesTimeout(Duration timeout) {
this.valuesTimeout = timeout;
return this;
}
/**
* Returns the established fusion mode or -1 if it was not enabled
*
* @return the fusion mode, see Fuseable constants
*/
public final int establishedFusionMode() {
return establishedFusionMode;
}
// ==============================================================================================================
// Assertions
// ==============================================================================================================
/**
* Assert a complete successfully signal has been received.
*
* @return this
*/
public final TestSubscriber<T> assertComplete() {
assertNoError();
int c = completionCount;
if (c == 0) {
throw new AssertionError("Not completed", null);
}
if (c > 1) {
throw new AssertionError("Multiple completions: " + c, null);
}
return this;
}
/**
* Assert the specified values have been received. Values storage should be enabled to use this method.
*
* @param expectedValues the values to assert
* @see #configureValuesStorage(boolean)
* @return this
*/
public final TestSubscriber<T> assertContainValues(Set<? extends T> expectedValues) {
if (!valuesStorage) {
throw new IllegalStateException("Using assertNoValues() requires enabling values storage");
}
if (expectedValues.size() > values.size()) {
throw new AssertionError("Actual contains fewer elements" + values, null);
}
Iterator<? extends T> expected = expectedValues.iterator();
for (;;) {
boolean n2 = expected.hasNext();
if (n2) {
T t2 = expected.next();
if (!values.contains(t2)) {
throw new AssertionError(
"The element is not contained in the " + "received resuls" + " = " + valueAndClass(t2), null);
}
} else {
break;
}
}
return this;
}
/**
* Assert an error signal has been received.
*
* @return this
*/
public final TestSubscriber<T> assertError() {
assertNotComplete();
int s = errors.size();
if (s == 0) {
throw new AssertionError("No error", null);
}
if (s > 1) {
throw new AssertionError("Multiple errors: " + s, null);
}
return this;
}
/**
* Assert an error signal has been received.
*
* @param clazz The class of the exception contained in the error signal
* @return this
*/
public final TestSubscriber<T> assertError(Class<? extends Throwable> clazz) {
assertNotComplete();
int s = errors.size();
if (s == 0) {
throw new AssertionError("No error", null);
}
if (s == 1) {
Throwable e = errors.get(0);
if (!clazz.isInstance(e)) {
throw new AssertionError("Error class incompatible: expected = " + clazz + ", actual = " + e, null);
}
}
if (s > 1) {
throw new AssertionError("Multiple errors: " + s, null);
}
return this;
}
public final TestSubscriber<T> assertErrorMessage(String message) {
assertNotComplete();
int s = errors.size();
if (s == 0) {
assertionError("No error", null);
}
if (s == 1) {
if (!Objects.equals(message, errors.get(0).getMessage())) {
assertionError(
"Error class incompatible: expected = \"" + message + "\", actual = \"" + errors.get(0).getMessage() + "\"",
null);
}
}
if (s > 1) {
assertionError("Multiple errors: " + s, null);
}
return this;
}
/**
* Assert an error signal has been received.
*
* @param expectation A method that can verify the exception contained in the error signal and throw an exception
* (like an {@link AssertionError}) if the exception is not valid.
* @return this
*/
public final TestSubscriber<T> assertErrorWith(Consumer<? super Throwable> expectation) {
assertNotComplete();
int s = errors.size();
if (s == 0) {
throw new AssertionError("No error", null);
}
if (s == 1) {
expectation.accept(errors.get(0));
}
if (s > 1) {
throw new AssertionError("Multiple errors: " + s, null);
}
return this;
}
/**
* Assert that the upstream was a Fuseable source.
*
* @return this
*/
public final TestSubscriber<T> assertFuseableSource() {
if (qs == null) {
throw new AssertionError("Upstream was not Fuseable");
}
return this;
}
/**
* Assert that the fusion mode was granted.
*
* @return this
*/
public final TestSubscriber<T> assertFusionEnabled() {
if (establishedFusionMode != Fuseable.SYNC && establishedFusionMode != Fuseable.ASYNC) {
throw new AssertionError("Fusion was not enabled");
}
return this;
}
public final TestSubscriber<T> assertFusionMode(int expectedMode) {
if (establishedFusionMode != expectedMode) {
throw new AssertionError("Wrong fusion mode: expected: " + fusionModeName(expectedMode) + ", actual: "
+ fusionModeName(establishedFusionMode));
}
return this;
}
/**
* Assert that the fusion mode was granted.
*
* @return this
*/
public final TestSubscriber<T> assertFusionRejected() {
if (establishedFusionMode != Fuseable.NONE) {
throw new AssertionError("Fusion was granted");
}
return this;
}
/**
* Assert no error signal has been received.
*
* @return this
*/
public final TestSubscriber<T> assertNoError() {
int s = errors.size();
if (s == 1) {
Throwable e = errors.get(0);
String valueAndClass = e == null ? null : e + " (" + e.getClass().getSimpleName() + ")";
throw new AssertionError("Error present: " + valueAndClass, null);
}
if (s > 1) {
throw new AssertionError("Multiple errors: " + s, null);
}
return this;
}
/**
* Assert no values have been received.
*
* @return this
*/
public final TestSubscriber<T> assertNoValues() {
if (valueCount != 0) {
throw new AssertionError("No values expected but received: [length = " + values.size() + "] " + values, null);
}
return this;
}
/**
* Assert that the upstream was not a Fuseable source.
*
* @return this
*/
public final TestSubscriber<T> assertNonFuseableSource() {
if (qs != null) {
throw new AssertionError("Upstream was Fuseable");
}
return this;
}
/**
* Assert no complete successfully signal has been received.
*
* @return this
*/
public final TestSubscriber<T> assertNotComplete() {
int c = completionCount;
if (c == 1) {
throw new AssertionError("Completed", null);
}
if (c > 1) {
throw new AssertionError("Multiple completions: " + c, null);
}
return this;
}
/**
* Assert no subscription occurred.
*
* @return this
*/
public final TestSubscriber<T> assertNotSubscribed() {
int s = subscriptionCount;
if (s == 1) {
throw new AssertionError("OnSubscribe called once", null);
}
if (s > 1) {
throw new AssertionError("OnSubscribe called multiple times: " + s, null);
}
return this;
}
/**
* Assert no complete successfully or error signal has been received.
*
* @return this
*/
public final TestSubscriber<T> assertNotTerminated() {
if (cdl.getCount() == 0) {
throw new AssertionError("Terminated", null);
}
return this;
}
/**
* Assert subscription occurred (once).
*
* @return this
*/
public final TestSubscriber<T> assertSubscribed() {
int s = subscriptionCount;
if (s == 0) {
throw new AssertionError("OnSubscribe not called", null);
}
if (s > 1) {
throw new AssertionError("OnSubscribe called multiple times: " + s, null);
}
return this;
}
/**
* Assert either complete successfully or error signal has been received.
*
* @return this
*/
public final TestSubscriber<T> assertTerminated() {
if (cdl.getCount() != 0) {
throw new AssertionError("Not terminated", null);
}
return this;
}
/**
* Assert {@code n} values has been received.
*
* @param n the expected value count
* @return this
*/
public final TestSubscriber<T> assertValueCount(long n) {
if (valueCount != n) {
throw new AssertionError("Different value count: expected = " + n + ", actual = " + valueCount, null);
}
return this;
}
/**
* Assert the specified values have been received in the same order read by the passed {@link Iterable}. Values
* storage should be enabled to use this method.
*
* @param expectedSequence the values to assert
* @see #configureValuesStorage(boolean)
* @return this
*/
public final TestSubscriber<T> assertValueSequence(Iterable<? extends T> expectedSequence) {
if (!valuesStorage) {
throw new IllegalStateException("Using assertNoValues() requires enabling values storage");
}
Iterator<T> actual = values.iterator();
Iterator<? extends T> expected = expectedSequence.iterator();
int i = 0;
for (;;) {
boolean n1 = actual.hasNext();
boolean n2 = expected.hasNext();
if (n1 && n2) {
T t1 = actual.next();
T t2 = expected.next();
if (!Objects.equals(t1, t2)) {
throw new AssertionError("The element with index " + i + " does not match: expected = " + valueAndClass(t2)
+ ", actual = " + valueAndClass(t1), null);
}
i++;
} else if (n1 && !n2) {
throw new AssertionError("Actual contains more elements" + values, null);
} else if (!n1 && n2) {
throw new AssertionError("Actual contains fewer elements: " + values, null);
} else {
break;
}
}
return this;
}
/**
* Assert the specified values have been received in the declared order. Values storage should be enabled to use this
* method.
*
* @param expectedValues the values to assert
* @return this
* @see #configureValuesStorage(boolean)
*/
@SafeVarargs
public final TestSubscriber<T> assertValues(T... expectedValues) {
return assertValueSequence(Arrays.asList(expectedValues));
}
/**
* Assert the specified values have been received in the declared order. Values storage should be enabled to use this
* method.
*
* @param expectations One or more methods that can verify the values and throw a exception (like an
* {@link AssertionError}) if the value is not valid.
* @return this
* @see #configureValuesStorage(boolean)
*/
@SafeVarargs
public final TestSubscriber<T> assertValuesWith(Consumer<T>... expectations) {
if (!valuesStorage) {
throw new IllegalStateException("Using assertNoValues() requires enabling values storage");
}
final int expectedValueCount = expectations.length;
if (expectedValueCount != values.size()) {
throw new AssertionError("Different value count: expected = " + expectedValueCount + ", actual = " + valueCount,
null);
}
for (int i = 0; i < expectedValueCount; i++) {
Consumer<T> consumer = expectations[i];
T actualValue = values.get(i);
consumer.accept(actualValue);
}
return this;
}
// ==============================================================================================================
// Await methods
// ==============================================================================================================
/**
* Blocking method that waits until a complete successfully or error signal is received.
*
* @return this
*/
public final TestSubscriber<T> await() {
if (cdl.getCount() == 0) {
return this;
}
try {
cdl.await();
} catch (InterruptedException ex) {
throw new AssertionError("Wait interrupted", ex);
}
return this;
}
/**
* Blocking method that waits until a complete successfully or error signal is received or until a timeout occurs.
*
* @param timeout The timeout value
* @return this
*/
public final TestSubscriber<T> await(Duration timeout) {
if (cdl.getCount() == 0) {
return this;
}
try {
if (!cdl.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
throw new AssertionError("No complete or error signal before timeout");
}
return this;
} catch (InterruptedException ex) {
throw new AssertionError("Wait interrupted", ex);
}
}
/**
* Blocking method that waits until {@code n} next values have been received.
*
* @param n the value count to assert
* @return this
*/
public final TestSubscriber<T> awaitAndAssertNextValueCount(final long n) {
await(valuesTimeout, () -> {
if (valuesStorage) {
return String.format("%d out of %d next values received within %d, " + "values : %s",
valueCount - nextValueAssertedCount, n, valuesTimeout.toMillis(), values.toString());
}
return String.format("%d out of %d next values received within %d", valueCount - nextValueAssertedCount, n,
valuesTimeout.toMillis());
}, () -> valueCount >= (nextValueAssertedCount + n));
nextValueAssertedCount += n;
return this;
}
/**
* Blocking method that waits until {@code n} next values have been received (n is the number of values provided) to
* assert them.
*
* @param values the values to assert
* @return this
*/
@SafeVarargs
@SuppressWarnings("unchecked")
public final TestSubscriber<T> awaitAndAssertNextValues(T... values) {
final int expectedNum = values.length;
final List<Consumer<T>> expectations = new ArrayList<>();
for (int i = 0; i < expectedNum; i++) {
final T expectedValue = values[i];
expectations.add(actualValue -> {
if (!actualValue.equals(expectedValue)) {
throw new AssertionError(String.format("Expected Next signal: %s, but got: %s", expectedValue, actualValue));
}
});
}
awaitAndAssertNextValuesWith(expectations.toArray((Consumer<T>[]) new Consumer[0]));
return this;
}
/**
* Blocking method that waits until {@code n} next values have been received (n is the number of expectations
* provided) to assert them.
*
* @param expectations One or more methods that can verify the values and throw a exception (like an
* {@link AssertionError}) if the value is not valid.
* @return this
*/
@SafeVarargs
public final TestSubscriber<T> awaitAndAssertNextValuesWith(Consumer<T>... expectations) {
valuesStorage = true;
final int expectedValueCount = expectations.length;
await(valuesTimeout, () -> {
if (valuesStorage) {
return String.format("%d out of %d next values received within %d, " + "values : %s",
valueCount - nextValueAssertedCount, expectedValueCount, valuesTimeout.toMillis(), values.toString());
}
return String.format("%d out of %d next values received within %d ms", valueCount - nextValueAssertedCount,
expectedValueCount, valuesTimeout.toMillis());
}, () -> valueCount >= (nextValueAssertedCount + expectedValueCount));
List<T> nextValuesSnapshot;
List<T> empty = new ArrayList<>();
for (;;) {
nextValuesSnapshot = values;
if (NEXT_VALUES.compareAndSet(this, values, empty)) {
break;
}
}
if (nextValuesSnapshot.size() < expectedValueCount) {
throw new AssertionError(String.format("Expected %d number of signals but received %d", expectedValueCount,
nextValuesSnapshot.size()));
}
for (int i = 0; i < expectedValueCount; i++) {
Consumer<T> consumer = expectations[i];
T actualValue = nextValuesSnapshot.get(i);
consumer.accept(actualValue);
}
nextValueAssertedCount += expectedValueCount;
return this;
}
// ==============================================================================================================
// Overrides
// ==============================================================================================================
@Override
public void cancel() {
Subscription a = s;
if (a != Operators.cancelledSubscription()) {
a = S.getAndSet(this, Operators.cancelledSubscription());
if (a != null && a != Operators.cancelledSubscription()) {
a.cancel();
}
}
}
public final boolean isCancelled() {
return s == Operators.cancelledSubscription();
}
public final boolean isStarted() {
return s != null;
}
public final boolean isTerminated() {
return isCancelled();
}
@Override
public void onComplete() {
completionCount++;
cdl.countDown();
}
@Override
public void onError(Throwable t) {
errors.add(t);
cdl.countDown();
}
@Override
public void onNext(T t) {
if (establishedFusionMode == Fuseable.ASYNC) {
for (;;) {
t = qs.poll();
if (t == null) {
break;
}
valueCount++;
if (valuesStorage) {
List<T> nextValuesSnapshot;
for (;;) {
nextValuesSnapshot = values;
nextValuesSnapshot.add(t);
if (NEXT_VALUES.compareAndSet(this, nextValuesSnapshot, nextValuesSnapshot)) {
break;
}
}
}
}
} else {
valueCount++;
if (valuesStorage) {
List<T> nextValuesSnapshot;
for (;;) {
nextValuesSnapshot = values;
nextValuesSnapshot.add(t);
if (NEXT_VALUES.compareAndSet(this, nextValuesSnapshot, nextValuesSnapshot)) {
break;
}
}
}
}
}
@Override
@SuppressWarnings("unchecked")
public void onSubscribe(Subscription s) {
subscriptionCount++;
int requestMode = requestedFusionMode;
if (requestMode >= 0) {
if (!setWithoutRequesting(s)) {
if (!isCancelled()) {
errors.add(new IllegalStateException("Subscription already set: " + subscriptionCount));
}
} else {
if (s instanceof Fuseable.QueueSubscription) {
this.qs = (Fuseable.QueueSubscription<T>) s;
int m = qs.requestFusion(requestMode);
establishedFusionMode = m;
if (m == Fuseable.SYNC) {
for (;;) {
T v = qs.poll();
if (v == null) {
onComplete();
break;
}
onNext(v);
}
} else {
requestDeferred();
}
} else {
requestDeferred();
}
}
} else {
if (!set(s)) {
if (!isCancelled()) {
errors.add(new IllegalStateException("Subscription already set: " + subscriptionCount));
}
}
}
}
@Override
public void request(long n) {
if (Operators.validate(n)) {
if (establishedFusionMode != Fuseable.SYNC) {
normalRequest(n);
}
}
}
public final long requestedFromDownstream() {
return requested;
}
/**
* Setup what fusion mode should be requested from the incomining Subscription if it happens to be QueueSubscription
*
* @param requestMode the mode to request, see Fuseable constants
* @return this
*/
public final TestSubscriber<T> requestedFusionMode(int requestMode) {
this.requestedFusionMode = requestMode;
return this;
}
public Subscription upstream() {
return s;
}
// ==============================================================================================================
// Non public methods
// ==============================================================================================================
protected final void normalRequest(long n) {
Subscription a = s;
if (a != null) {
a.request(n);
} else {
Operators.addAndGet(REQUESTED, this, n);
a = s;
if (a != null) {
long r = REQUESTED.getAndSet(this, 0L);
if (r != 0L) {
a.request(r);
}
}
}
}
/**
* Requests the deferred amount if not zero.
*/
protected final void requestDeferred() {
long r = REQUESTED.getAndSet(this, 0L);
if (r != 0L) {
s.request(r);
}
}
/**
* Atomically sets the single subscription and requests the missed amount from it.
*
* @param s
* @return false if this arbiter is cancelled or there was a subscription already set
*/
protected final boolean set(Subscription s) {
Objects.requireNonNull(s, "s");
Subscription a = this.s;
if (a == Operators.cancelledSubscription()) {
s.cancel();
return false;
}
if (a != null) {
s.cancel();
Operators.reportSubscriptionSet();
return false;
}
if (S.compareAndSet(this, null, s)) {
long r = REQUESTED.getAndSet(this, 0L);
if (r != 0L) {
s.request(r);
}
return true;
}
a = this.s;
if (a != Operators.cancelledSubscription()) {
s.cancel();
return false;
}
Operators.reportSubscriptionSet();
return false;
}
/**
* Sets the Subscription once but does not request anything.
*
* @param s the Subscription to set
* @return true if successful, false if the current subscription is not null
*/
protected final boolean setWithoutRequesting(Subscription s) {
Objects.requireNonNull(s, "s");
for (;;) {
Subscription a = this.s;
if (a == Operators.cancelledSubscription()) {
s.cancel();
return false;
}
if (a != null) {
s.cancel();
Operators.reportSubscriptionSet();
return false;
}
if (S.compareAndSet(this, null, s)) {
return true;
}
}
}
/**
* Prepares and throws an AssertionError exception based on the message, cause, the active state and the potential
* errors so far.
*
* @param message the message
* @param cause the optional Throwable cause
* @throws AssertionError as expected
*/
protected final void assertionError(String message, Throwable cause) {
StringBuilder b = new StringBuilder();
if (cdl.getCount() != 0) {
b.append("(active) ");
}
b.append(message);
List<Throwable> err = errors;
if (!err.isEmpty()) {
b.append(" (+ ").append(err.size()).append(" errors)");
}
AssertionError e = new AssertionError(b.toString(), cause);
for (Throwable t : err) {
e.addSuppressed(t);
}
throw e;
}
protected final String fusionModeName(int mode) {
switch (mode) {
case -1:
return "Disabled";
case Fuseable.NONE:
return "None";
case Fuseable.SYNC:
return "Sync";
case Fuseable.ASYNC:
return "Async";
default:
return "Unknown(" + mode + ")";
}
}
protected final String valueAndClass(Object o) {
if (o == null) {
return null;
}
return o + " (" + o.getClass().getSimpleName() + ")";
}
}