reactor.test.publisher.DefaultTestPublisher Maven / Gradle / Ivy
/*
* Copyright (c) 2017-2022 VMware Inc. or its affiliates, 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.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.function.Consumer;
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;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;
/**
* 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;
volatile boolean wasRequested;
volatile long subscribeCount;
static final AtomicLongFieldUpdater SUBSCRIBED_COUNT =
AtomicLongFieldUpdater.newUpdater(DefaultTestPublisher.class, "subscribeCount");
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);
}
DefaultTestPublisher.SUBSCRIBED_COUNT.incrementAndGet(this);
if (replayOnSubscribe != null) {
replayOnSubscribe.accept(this);
}
} 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.addCap(REQUESTED, this, n);
parent.wasRequested = true;
}
}
@Override
public void cancel() {
if (!cancelled) {
DefaultTestPublisher.CANCEL_COUNT.incrementAndGet(parent);
if (parent.violations.contains(Violation.CLEANUP_ON_TERMINATE)||parent.violations.contains(Violation.DEFER_CANCELLATION)) {
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();
}
}
private Consumer> replayOnSubscribe = null;
public TestPublisher replayOnSubscribe(Consumer> replay) {
if (replayOnSubscribe == null) {
replayOnSubscribe = replay;
}
else {
replayOnSubscribe = replayOnSubscribe.andThen(replay);
}
return this;
}
@Override
public Flux flux() {
return Flux.from(this);
}
@Override
public boolean wasSubscribed() {
return subscribeCount > 0;
}
@Override
public long subscribeCount() {
return subscribeCount;
}
@Override
public boolean wasCancelled() {
return cancelCount > 0;
}
@Override
public boolean wasRequested() {
return wasRequested;
}
@Override
public Mono mono() {
if (violations.isEmpty()) {
return Mono.from(this);
}
else {
return Mono.fromDirect(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 smallest requested amount to be >= " + n + "; got " + minRequest);
}
return this;
}
@Override
public DefaultTestPublisher assertMaxRequested(long n) {
TestPublisherSubscription[] subs = subscribers;
long maxRequest = Stream.of(subs)
.mapToLong(s -> s.requested)
.max()
.orElse(0);
if (maxRequest > n) {
throw new AssertionError("Expected largest requested amount to be <= " + n + "; got " + maxRequest);
}
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(@Nullable T t) {
if (!violations.contains(Violation.ALLOW_NULL)) {
Objects.requireNonNull(t, "emitted values must be non-null");
}
TestPublisherSubscription[] subscribers = this.subscribers;
if (subscribers.length > 0) {
for (TestPublisherSubscription s : subscribers) {
s.onNext(t);
}
} else if (t != null) {
Operators.onNextDropped(t, Context.empty());
}
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;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy