/* * 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 }