reactor.test.publisher.DefaultTestPublisher Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2011-2017 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
*
* 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.publisher;
import java.util.EnumSet;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.stream.Stream;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.Fuseable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Operators;
/**
* A default implementation of a {@link TestPublisher}.
*
* @author Simon Basle
* @author Stephane Maldini
*/
class DefaultTestPublisher extends TestPublisher {
@SuppressWarnings("rawtypes")
private static final TestPublisherSubscription[] EMPTY = new TestPublisherSubscription[0];
@SuppressWarnings("rawtypes")
private static final TestPublisherSubscription[] TERMINATED = new TestPublisherSubscription[0];
volatile int cancelCount;
static final AtomicIntegerFieldUpdater CANCEL_COUNT =
AtomicIntegerFieldUpdater.newUpdater(DefaultTestPublisher.class, "cancelCount");
Throwable error;
volatile boolean hasOverflown;
final EnumSet violations;
@SuppressWarnings("unchecked")
volatile TestPublisherSubscription[] subscribers = EMPTY;
@SuppressWarnings("rawtypes")
static final AtomicReferenceFieldUpdater SUBSCRIBERS =
AtomicReferenceFieldUpdater.newUpdater(DefaultTestPublisher.class, TestPublisherSubscription[].class, "subscribers");
DefaultTestPublisher(Violation first, Violation... rest) {
this.violations = EnumSet.of(first, rest);
}
DefaultTestPublisher() {
this.violations = EnumSet.noneOf(Violation.class);
}
@Override
public void subscribe(Subscriber super T> s) {
Objects.requireNonNull(s, "s");
TestPublisherSubscription
p = new TestPublisherSubscription<>(s, this);
s.onSubscribe(p);
if (add(p)) {
if (p.cancelled) {
remove(p);
}
} else {
Throwable e = error;
if (e != null) {
s.onError(e);
} else {
s.onComplete();
}
}
}
boolean add(TestPublisherSubscription s) {
TestPublisherSubscription[] a = subscribers;
if (a == TERMINATED) {
return false;
}
synchronized (this) {
a = subscribers;
if (a == TERMINATED) {
return false;
}
int len = a.length;
@SuppressWarnings("unchecked") TestPublisherSubscription[] b = new TestPublisherSubscription[len + 1];
System.arraycopy(a, 0, b, 0, len);
b[len] = s;
subscribers = b;
return true;
}
}
@SuppressWarnings("unchecked")
void remove(TestPublisherSubscription s) {
TestPublisherSubscription[] a = subscribers;
if (violations.contains(Violation.CLEANUP_ON_TERMINATE)) {
return;
}
if (a == TERMINATED || a == EMPTY) {
return;
}
synchronized (this) {
a = subscribers;
if (a == TERMINATED || a == EMPTY) {
return;
}
int len = a.length;
int j = -1;
for (int i = 0; i < len; i++) {
if (a[i] == s) {
j = i;
break;
}
}
if (j < 0) {
return;
}
if (len == 1) {
subscribers = EMPTY;
return;
}
TestPublisherSubscription[] b = new TestPublisherSubscription[len - 1];
System.arraycopy(a, 0, b, 0, j);
System.arraycopy(a, j + 1, b, j, len - j - 1);
subscribers = b;
}
}
static final class TestPublisherSubscription implements Subscription {
final Subscriber super T> actual;
final Fuseable.ConditionalSubscriber super T> actualConditional;
final DefaultTestPublisher parent;
volatile boolean cancelled;
volatile long requested;
@SuppressWarnings("rawtypes")
static final AtomicLongFieldUpdater
REQUESTED =
AtomicLongFieldUpdater.newUpdater(TestPublisherSubscription.class, "requested");
@SuppressWarnings("unchecked")
TestPublisherSubscription(Subscriber super T> actual, DefaultTestPublisher parent) {
this.actual = actual;
if(actual instanceof Fuseable.ConditionalSubscriber){
this.actualConditional = (Fuseable.ConditionalSubscriber super T>) actual;
}
else {
this.actualConditional = null;
}
this.parent = parent;
}
@Override
public void request(long n) {
if (Operators.validate(n)) {
Operators.getAndAddCap(REQUESTED, this, n);
}
}
@Override
public void cancel() {
if (!cancelled) {
DefaultTestPublisher.CANCEL_COUNT.incrementAndGet(parent);
if (parent.violations.contains(Violation.CLEANUP_ON_TERMINATE)) {
return;
}
cancelled = true;
parent.remove(this);
}
}
void onNext(T value) {
long r = requested;
if (r != 0L || parent.violations.contains(Violation.REQUEST_OVERFLOW)) {
if (r == 0) {
parent.hasOverflown = true;
}
boolean sent;
if(actualConditional != null){
sent = actualConditional.tryOnNext(value);
}
else {
sent = true;
actual.onNext(value);
}
if (sent && r != Long.MAX_VALUE) {
REQUESTED.decrementAndGet(this);
}
return;
}
parent.remove(this);
actual.onError(new IllegalStateException("Can't deliver value due to lack of requests"));
}
void onError(Throwable e) {
actual.onError(e);
}
void onComplete() {
actual.onComplete();
}
}
@Override
public Flux flux() {
return Flux.from(this);
}
@Override
public Mono mono() {
return Mono.from(this);
}
@Override
public DefaultTestPublisher assertMinRequested(long n) {
TestPublisherSubscription[] subs = subscribers;
long minRequest = Stream.of(subs)
.mapToLong(s -> s.requested)
.min()
.orElse(0);
if (minRequest < n) {
throw new AssertionError("Expected minimum request of " + n + "; got " + minRequest);
}
return this;
}
@Override
public DefaultTestPublisher assertSubscribers() {
TestPublisherSubscription[] s = subscribers;
if (s == EMPTY || s == TERMINATED) {
throw new AssertionError("Expected subscribers");
}
return this;
}
@Override
public DefaultTestPublisher assertSubscribers(int n) {
int sl = subscribers.length;
if (sl != n) {
throw new AssertionError("Expected " + n + " subscribers, got " + sl);
}
return this;
}
@Override
public DefaultTestPublisher assertNoSubscribers() {
int sl = subscribers.length;
if (sl != 0) {
throw new AssertionError("Expected no subscribers, got " + sl);
}
return this;
}
@Override
public DefaultTestPublisher assertCancelled() {
if (cancelCount == 0) {
throw new AssertionError("Expected at least 1 cancellation");
}
return this;
}
@Override
public DefaultTestPublisher assertCancelled(int n) {
int cc = cancelCount;
if (cc != n) {
throw new AssertionError("Expected " + n + " cancellations, got " + cc);
}
return this;
}
@Override
public DefaultTestPublisher assertNotCancelled() {
if (cancelCount != 0) {
throw new AssertionError("Expected no cancellation");
}
return this;
}
@Override
public DefaultTestPublisher assertRequestOverflow() {
if (!hasOverflown) {
throw new AssertionError("Expected some request overflow");
}
return this;
}
@Override
public DefaultTestPublisher assertNoRequestOverflow() {
if (hasOverflown) {
throw new AssertionError("Unexpected request overflow");
}
return this;
}
@Override
public DefaultTestPublisher next(T t) {
if (!violations.contains(Violation.ALLOW_NULL)) {
Objects.requireNonNull(t, "emitted values must be non-null");
}
for (TestPublisherSubscription s : subscribers) {
s.onNext(t);
}
return this;
}
@Override
public DefaultTestPublisher error(Throwable t) {
Objects.requireNonNull(t, "t");
error = t;
TestPublisherSubscription>[] subs;
if (violations.contains(Violation.CLEANUP_ON_TERMINATE)) {
subs = subscribers;
} else {
subs = SUBSCRIBERS.getAndSet(this, TERMINATED);
}
for (TestPublisherSubscription> s : subs) {
s.onError(t);
}
return this;
}
@Override
public DefaultTestPublisher complete() {
TestPublisherSubscription>[] subs;
if (violations.contains(Violation.CLEANUP_ON_TERMINATE)) {
subs = subscribers;
} else {
subs = SUBSCRIBERS.getAndSet(this, TERMINATED);
}
for (TestPublisherSubscription> s : subs) {
s.onComplete();
}
return this;
}
}