/*
* Copyright (c) 2011-2016 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.core.publisher;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
import org.junit.Assert;
import org.junit.Test;
import org.reactivestreams.Subscription;
import reactor.core.scheduler.Schedulers;
import reactor.test.StepVerifier;
import reactor.util.concurrent.QueueSupplier;
import static org.assertj.core.api.Assertions.assertThat;
public class MonoSequenceEqualTest {
@Test
public void sequenceEquals() {
StepVerifier.create(Mono.sequenceEqual(
Flux.just("one", "two", "three"),
Flux.just("one", "two", "three")))
.expectNext(Boolean.TRUE)
.verifyComplete();
}
@Test
public void sequenceLongerLeft() {
StepVerifier.create(Mono.sequenceEqual(
Flux.just("one", "two", "three", "four"),
Flux.just("one", "two", "three")))
.expectNext(Boolean.FALSE)
.verifyComplete();
}
@Test
public void sequenceLongerRight() {
StepVerifier.create(Mono.sequenceEqual(
Flux.just("one", "two", "three"),
Flux.just("one", "two", "three", "four")))
.expectNext(Boolean.FALSE)
.verifyComplete();
}
@Test
public void sequenceErrorsLeft() {
StepVerifier.create(Mono.sequenceEqual(
Flux.just("one", "two").concatWith(Mono.error(new IllegalStateException())),
Flux.just("one", "two", "three")))
.verifyError(IllegalStateException.class);
}
@Test
public void sequenceErrorsRight() {
StepVerifier.create(Mono.sequenceEqual(
Flux.just("one", "two", "three"),
Flux.just("one", "two").concatWith(Mono.error(new IllegalStateException()))))
.verifyError(IllegalStateException.class);
}
@Test
public void sequenceErrorsBothPropagatesLeftError() {
StepVerifier.create(Mono.sequenceEqual(
Flux.just("one", "two", "three", "four").concatWith(Mono.error(new IllegalArgumentException("left"))).hide(),
Flux.just("one", "two").concatWith(Mono.error(new IllegalArgumentException("right"))).hide()))
.verifyErrorMessage("left");
}
@Test
public void sequenceErrorsBothPropagatesLeftErrorWithSmallRequest() {
StepVerifier.create(Mono.sequenceEqual(
Flux.just("one", "two", "three", "four")
.concatWith(Mono.error(new IllegalArgumentException("left")))
.hide(),
Flux.just("one", "two")
.concatWith(Mono.error(new IllegalArgumentException("right")))
.hide(),
Objects::equals, 1))
.verifyErrorMessage("right");
}
@Test
public void sequenceEmptyLeft() {
StepVerifier.create(Mono.sequenceEqual(
Flux.empty(),
Flux.just("one", "two", "three")))
.expectNext(Boolean.FALSE)
.verifyComplete();
}
@Test
public void sequenceEmptyRight() {
StepVerifier.create(Mono.sequenceEqual(
Flux.just("one", "two", "three"),
Flux.empty()))
.expectNext(Boolean.FALSE)
.verifyComplete();
}
@Test
public void sequenceEmptyBoth() {
StepVerifier.create(Mono.sequenceEqual(
Flux.empty(),
Flux.empty()))
.expectNext(Boolean.TRUE)
.verifyComplete();
}
@Test
public void equalPredicateFailure() {
StepVerifier.create(Mono.sequenceEqual(Mono.just("one"), Mono.just("one"),
(s1, s2) -> { throw new IllegalStateException("boom"); }))
.verifyErrorMessage("boom");
}
@Test
public void largeSequence() {
Flux<Integer> source = Flux.range(1, QueueSupplier.SMALL_BUFFER_SIZE * 4).subscribeOn(Schedulers.elastic());
StepVerifier.create(Mono.sequenceEqual(source, source))
.expectNext(Boolean.TRUE)
.expectComplete()
.verify(Duration.ofSeconds(5));
}
@Test
public void syncFusedCrash() {
Flux<Integer> source = Flux.range(1, 10).map(i -> { throw new IllegalArgumentException("boom"); });
StepVerifier.create(Mono.sequenceEqual(source, Flux.range(1, 10).hide()))
.verifyErrorMessage("boom");
StepVerifier.create(Mono.sequenceEqual(Flux.range(1, 10).hide(), source))
.verifyErrorMessage("boom");
}
@Test
public void differenceCancelsBothSources() {
AtomicBoolean sub1 = new AtomicBoolean();
AtomicBoolean sub2 = new AtomicBoolean();
Flux<Integer> source1 = Flux.range(1, 5).doOnCancel(() -> sub1.set(true));
Flux<Integer> source2 = Flux.just(1, 2, 3, 7, 8).doOnCancel(() -> sub2.set(true));
StepVerifier.create(Mono.sequenceEqual(source1, source2))
.expectNext(Boolean.FALSE)
.verifyComplete();
Assert.assertTrue("left not cancelled", sub1.get());
Assert.assertTrue("right not cancelled", sub2.get());
}
@Test
public void cancelCancelsBothSources() {
AtomicReference<Subscription> sub1 = new AtomicReference<>();
AtomicReference<Subscription> sub2 = new AtomicReference<>();
AtomicBoolean cancel1 = new AtomicBoolean();
AtomicBoolean cancel2 = new AtomicBoolean();
Flux<Integer> source1 = Flux.range(1, 5)
.doOnSubscribe(sub1::set)
.doOnCancel(() -> cancel1.set(true))
.hide();
Flux<Integer> source2 = Flux.just(1, 2, 3, 7, 8)
.doOnSubscribe(sub2::set)
.doOnCancel(() -> cancel2.set(true))
.hide();
Mono.sequenceEqual(source1, source2)
.subscribe(System.out::println, Throwable::printStackTrace, null,
Subscription::cancel);
Assert.assertNotNull("left not subscribed", sub1.get());
Assert.assertTrue("left not cancelled", cancel1.get());
Assert.assertNotNull("right not subscribed", sub2.get());
Assert.assertTrue("right not cancelled", cancel2.get());
}
@Test
public void doubleCancelCancelsOnce() {
AtomicReference<Subscription> sub1 = new AtomicReference<>();
AtomicReference<Subscription> sub2 = new AtomicReference<>();
AtomicLong cancel1 = new AtomicLong();
AtomicLong cancel2 = new AtomicLong();
Flux<Integer> source1 = Flux.range(1, 5)
.doOnSubscribe(sub1::set)
.doOnCancel(cancel1::incrementAndGet)
.hide();
Flux<Integer> source2 = Flux.just(1, 2, 3, 7, 8)
.doOnSubscribe(sub2::set)
.doOnCancel(cancel2::incrementAndGet)
.hide();
Mono.sequenceEqual(source1, source2)
.subscribe(System.out::println, Throwable::printStackTrace, null,
s -> { s.cancel(); s.cancel(); });
Assert.assertNotNull("left not subscribed", sub1.get());
assertThat(cancel1.get()).isEqualTo(1);
Assert.assertNotNull("right not subscribed", sub2.get());
assertThat(cancel2.get()).isEqualTo(1);
}
@Test
public void cancelCancelsBothSourcesIncludingNever() {
AtomicReference<Subscription> sub1 = new AtomicReference<>();
AtomicReference<Subscription> sub2 = new AtomicReference<>();
AtomicBoolean cancel1 = new AtomicBoolean();
AtomicBoolean cancel2 = new AtomicBoolean();
Flux<Integer> source1 = Flux.range(1, 5)
.doOnSubscribe(sub1::set)
.doOnCancel(() -> cancel1.set(true))
.hide();
Flux<Integer> source2 = Flux.<Integer>never()
.doOnSubscribe(sub2::set)
.doOnCancel(() -> cancel2.set(true));
Mono.sequenceEqual(source1, source2)
.subscribe(System.out::println, Throwable::printStackTrace, null,
Subscription::cancel);
Assert.assertNotNull("left not subscribed", sub1.get());
Assert.assertTrue("left not cancelled", cancel1.get());
Assert.assertNotNull("right not subscribed", sub2.get());
Assert.assertTrue("right not cancelled", cancel2.get());
}
@Test
public void subscribeInnerOnce() {
LongAdder innerSub1 = new LongAdder();
LongAdder innerSub2 = new LongAdder();
Flux<Integer> source1 = Flux.range(1, 5)
.doOnSubscribe((t) -> innerSub1.increment());
Flux<Integer> source2 = Flux.just(1, 2, 3, 7, 8)
.doOnSubscribe((t) -> innerSub2.increment());
Mono.sequenceEqual(source1, source2)
.subscribe();
Assert.assertEquals("left has been subscribed multiple times", 1, innerSub1.intValue());
Assert.assertEquals("right has been subscribed multiple times", 1, innerSub2.intValue());
}
//TODO multithreaded race between cancel and onNext, between cancel and drain, source overflow, error dropping to hook
}