Browse Source
Introduce new base test case for decoder tests, and use it. Issue: SPR-17449pull/2018/head
20 changed files with 1048 additions and 568 deletions
@ -0,0 +1,450 @@
@@ -0,0 +1,450 @@
|
||||
/* |
||||
* Copyright 2002-2018 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 org.springframework.core.codec; |
||||
|
||||
import java.util.Map; |
||||
import java.util.function.Consumer; |
||||
|
||||
import org.junit.Test; |
||||
import org.reactivestreams.Publisher; |
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
import reactor.test.StepVerifier; |
||||
|
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.core.io.buffer.AbstractLeakCheckingTestCase; |
||||
import org.springframework.core.io.buffer.DataBuffer; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.MimeType; |
||||
|
||||
/** |
||||
* Abstract base class for {@link Decoder} unit tests. Subclasses need to implement |
||||
* {@link #canDecode()}, {@link #decode()} and {@link #decodeToMono()}, possibly using the wide |
||||
* variety of helper methods like {@link #testDecodeAll} or {@link #testDecodeToMonoAll}. |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @since 5.1.3 |
||||
*/ |
||||
@SuppressWarnings("ProtectedField") |
||||
public abstract class AbstractDecoderTestCase<D extends Decoder<?>> |
||||
extends AbstractLeakCheckingTestCase { |
||||
|
||||
/** |
||||
* The decoder to test. |
||||
*/ |
||||
protected D decoder; |
||||
|
||||
/** |
||||
* Construct a new {@code AbstractDecoderTestCase} for the given decoder. |
||||
* @param decoder the decoder |
||||
*/ |
||||
protected AbstractDecoderTestCase(D decoder) { |
||||
Assert.notNull(decoder, "Encoder must not be null"); |
||||
|
||||
this.decoder = decoder; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Subclasses should implement this method to test {@link Decoder#canDecode}. |
||||
*/ |
||||
@Test |
||||
public abstract void canDecode() throws Exception; |
||||
|
||||
/** |
||||
* Subclasses should implement this method to test {@link Decoder#decode}, possibly using |
||||
* {@link #testDecodeAll} or other helper methods. |
||||
*/ |
||||
@Test |
||||
public abstract void decode() throws Exception; |
||||
|
||||
/** |
||||
* Subclasses should implement this method to test {@link Decoder#decodeToMono}, possibly using |
||||
* {@link #testDecodeToMonoAll}. |
||||
*/ |
||||
@Test |
||||
public abstract void decodeToMono() throws Exception; |
||||
|
||||
// Flux
|
||||
|
||||
/** |
||||
* Helper methods that tests for a variety of {@link Flux} decoding scenarios. This methods |
||||
* invokes: |
||||
* <ul> |
||||
* <li>{@link #testDecode(Publisher, ResolvableType, Consumer, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeError(Publisher, ResolvableType, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeCancel(Publisher, ResolvableType, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeEmpty(ResolvableType, MimeType, Map)}</li> |
||||
* </ul> |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputClass the desired output class |
||||
* @param stepConsumer a consumer to {@linkplain StepVerifier verify} the output |
||||
* @param <T> the output type |
||||
*/ |
||||
protected <T> void testDecodeAll(Publisher<DataBuffer> input, Class<? extends T> outputClass, |
||||
Consumer<StepVerifier.FirstStep<T>> stepConsumer) { |
||||
testDecodeAll(input, ResolvableType.forClass(outputClass), stepConsumer, null, null); |
||||
} |
||||
|
||||
/** |
||||
* Helper methods that tests for a variety of {@link Flux} decoding scenarios. This methods |
||||
* invokes: |
||||
* <ul> |
||||
* <li>{@link #testDecode(Publisher, ResolvableType, Consumer, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeError(Publisher, ResolvableType, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeCancel(Publisher, ResolvableType, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeEmpty(ResolvableType, MimeType, Map)}</li> |
||||
* </ul> |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputType the desired output type |
||||
* @param stepConsumer a consumer to {@linkplain StepVerifier verify} the output |
||||
* @param mimeType the mime type to use for decoding. May be {@code null}. |
||||
* @param hints the hints used for decoding. May be {@code null}. |
||||
* @param <T> the output type |
||||
*/ |
||||
protected <T> void testDecodeAll(Publisher<DataBuffer> input, ResolvableType outputType, |
||||
Consumer<StepVerifier.FirstStep<T>> stepConsumer, |
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { |
||||
testDecode(input, outputType, stepConsumer, mimeType, hints); |
||||
testDecodeError(input, outputType, mimeType, hints); |
||||
testDecodeCancel(input, outputType, mimeType, hints); |
||||
testDecodeEmpty(outputType, mimeType, hints); |
||||
} |
||||
|
||||
/** |
||||
* Test a standard {@link Decoder#decode decode} scenario. For example: |
||||
* <pre class="code"> |
||||
* byte[] bytes1 = ... |
||||
* byte[] bytes2 = ... |
||||
* |
||||
* Flux<DataBuffer> input = Flux.concat( |
||||
* dataBuffer(bytes1), |
||||
* dataBuffer(bytes2)); |
||||
* |
||||
* testDecodeAll(input, byte[].class, step -> step |
||||
* .consumeNextWith(expectBytes(bytes1)) |
||||
* .consumeNextWith(expectBytes(bytes2)) |
||||
* .verifyComplete()); |
||||
* </pre> |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputClass the desired output class |
||||
* @param stepConsumer a consumer to {@linkplain StepVerifier verify} the output |
||||
* @param <T> the output type |
||||
*/ |
||||
protected <T> void testDecode(Publisher<DataBuffer> input, Class<? extends T> outputClass, |
||||
Consumer<StepVerifier.FirstStep<T>> stepConsumer) { |
||||
testDecode(input, ResolvableType.forClass(outputClass), stepConsumer, null, null); |
||||
} |
||||
|
||||
/** |
||||
* Test a standard {@link Decoder#decode decode} scenario. For example: |
||||
* <pre class="code"> |
||||
* byte[] bytes1 = ... |
||||
* byte[] bytes2 = ... |
||||
* |
||||
* Flux<DataBuffer> input = Flux.concat( |
||||
* dataBuffer(bytes1), |
||||
* dataBuffer(bytes2)); |
||||
* |
||||
* testDecodeAll(input, byte[].class, step -> step |
||||
* .consumeNextWith(expectBytes(bytes1)) |
||||
* .consumeNextWith(expectBytes(bytes2)) |
||||
* .verifyComplete()); |
||||
* </pre> |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputType the desired output type |
||||
* @param stepConsumer a consumer to {@linkplain StepVerifier verify} the output |
||||
* @param mimeType the mime type to use for decoding. May be {@code null}. |
||||
* @param hints the hints used for decoding. May be {@code null}. |
||||
* @param <T> the output type |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
protected <T> void testDecode(Publisher<DataBuffer> input, ResolvableType outputType, |
||||
Consumer<StepVerifier.FirstStep<T>> stepConsumer, |
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { |
||||
|
||||
Flux<T> result = (Flux<T>) this.decoder.decode(input, outputType, mimeType, hints); |
||||
StepVerifier.FirstStep<T> step = StepVerifier.create(result); |
||||
stepConsumer.accept(step); |
||||
} |
||||
|
||||
/** |
||||
* Test a {@link Decoder#decode decode} scenario where the input stream contains an error. |
||||
* This test method will feed the first element of the {@code input} stream to the decoder, |
||||
* followed by an {@link InputException}. |
||||
* The result is expected to contain one "normal" element, followed by the error. |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputType the desired output type |
||||
* @param mimeType the mime type to use for decoding. May be {@code null}. |
||||
* @param hints the hints used for decoding. May be {@code null}. |
||||
* @see InputException |
||||
*/ |
||||
protected void testDecodeError(Publisher<DataBuffer> input, ResolvableType outputType, |
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { |
||||
|
||||
input = Flux.concat( |
||||
Flux.from(input).take(1), |
||||
Flux.error(new InputException())); |
||||
|
||||
Flux<?> result = this.decoder.decode(input, outputType, mimeType, hints); |
||||
|
||||
StepVerifier.create(result) |
||||
.expectNextCount(1) |
||||
.expectError(InputException.class) |
||||
.verify(); |
||||
} |
||||
|
||||
/** |
||||
* Test a {@link Decoder#decode decode} scenario where the input stream is canceled. |
||||
* This test method will feed the first element of the {@code input} stream to the decoder, |
||||
* followed by a cancel signal. |
||||
* The result is expected to contain one "normal" element. |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputType the desired output type |
||||
* @param mimeType the mime type to use for decoding. May be {@code null}. |
||||
* @param hints the hints used for decoding. May be {@code null}. |
||||
*/ |
||||
protected void testDecodeCancel(Publisher<DataBuffer> input, ResolvableType outputType, |
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { |
||||
|
||||
Flux<?> result = this.decoder.decode(input, outputType, mimeType, hints); |
||||
|
||||
StepVerifier.create(result) |
||||
.expectNextCount(1) |
||||
.thenCancel() |
||||
.verify(); |
||||
} |
||||
|
||||
/** |
||||
* Test a {@link Decoder#decode decode} scenario where the input stream is empty. |
||||
* The output is expected to be empty as well. |
||||
* |
||||
* @param outputType the desired output type |
||||
* @param mimeType the mime type to use for decoding. May be {@code null}. |
||||
* @param hints the hints used for decoding. May be {@code null}. |
||||
*/ |
||||
protected void testDecodeEmpty(ResolvableType outputType, @Nullable MimeType mimeType, |
||||
@Nullable Map<String, Object> hints) { |
||||
|
||||
Flux<DataBuffer> input = Flux.empty(); |
||||
Flux<?> result = this.decoder.decode(input, outputType, mimeType, hints); |
||||
|
||||
StepVerifier.create(result) |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
// Mono
|
||||
|
||||
/** |
||||
* Helper methods that tests for a variety of {@link Mono} decoding scenarios. This methods |
||||
* invokes: |
||||
* <ul> |
||||
* <li>{@link #testDecodeToMono(Publisher, ResolvableType, Consumer, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeToMonoError(Publisher, ResolvableType, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeToMonoCancel(Publisher, ResolvableType, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeToMonoEmpty(ResolvableType, MimeType, Map)}</li> |
||||
* </ul> |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputClass the desired output class |
||||
* @param stepConsumer a consumer to {@linkplain StepVerifier verify} the output |
||||
* @param <T> the output type |
||||
*/ |
||||
protected <T> void testDecodeToMonoAll(Publisher<DataBuffer> input, |
||||
Class<? extends T> outputClass, Consumer<StepVerifier.FirstStep<T>> stepConsumer) { |
||||
|
||||
testDecodeToMonoAll(input, ResolvableType.forClass(outputClass), stepConsumer, null, null); |
||||
} |
||||
|
||||
/** |
||||
* Helper methods that tests for a variety of {@link Mono} decoding scenarios. This methods |
||||
* invokes: |
||||
* <ul> |
||||
* <li>{@link #testDecodeToMono(Publisher, ResolvableType, Consumer, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeToMonoError(Publisher, ResolvableType, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeToMonoCancel(Publisher, ResolvableType, MimeType, Map)}</li> |
||||
* <li>{@link #testDecodeToMonoEmpty(ResolvableType, MimeType, Map)}</li> |
||||
* </ul> |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputType the desired output type |
||||
* @param stepConsumer a consumer to {@linkplain StepVerifier verify} the output |
||||
* @param mimeType the mime type to use for decoding. May be {@code null}. |
||||
* @param hints the hints used for decoding. May be {@code null}. |
||||
* @param <T> the output type |
||||
*/ |
||||
protected <T> void testDecodeToMonoAll(Publisher<DataBuffer> input, ResolvableType outputType, |
||||
Consumer<StepVerifier.FirstStep<T>> stepConsumer, |
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { |
||||
testDecodeToMono(input, outputType, stepConsumer, mimeType, hints); |
||||
testDecodeToMonoError(input, outputType, mimeType, hints); |
||||
testDecodeToMonoCancel(input, outputType, mimeType, hints); |
||||
testDecodeToMonoEmpty(outputType, mimeType, hints); |
||||
} |
||||
|
||||
/** |
||||
* Test a standard {@link Decoder#decodeToMono) decode} scenario. For example: |
||||
* <pre class="code"> |
||||
* byte[] bytes1 = ... |
||||
* byte[] bytes2 = ... |
||||
* byte[] allBytes = ... // bytes1 + bytes2
|
||||
* |
||||
* Flux<DataBuffer> input = Flux.concat( |
||||
* dataBuffer(bytes1), |
||||
* dataBuffer(bytes2)); |
||||
* |
||||
* testDecodeAll(input, byte[].class, step -> step |
||||
* .consumeNextWith(expectBytes(allBytes)) |
||||
* .verifyComplete()); |
||||
* </pre> |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputClass the desired output class |
||||
* @param stepConsumer a consumer to {@linkplain StepVerifier verify} the output |
||||
* @param <T> the output type |
||||
*/ |
||||
protected <T> void testDecodeToMono(Publisher<DataBuffer> input, |
||||
Class<? extends T> outputClass, Consumer<StepVerifier.FirstStep<T>> stepConsumer) { |
||||
testDecodeToMono(input, ResolvableType.forClass(outputClass), stepConsumer, null, null); |
||||
} |
||||
|
||||
/** |
||||
* Test a standard {@link Decoder#decodeToMono) decode} scenario. For example: |
||||
* <pre class="code"> |
||||
* byte[] bytes1 = ... |
||||
* byte[] bytes2 = ... |
||||
* byte[] allBytes = ... // bytes1 + bytes2
|
||||
* |
||||
* Flux<DataBuffer> input = Flux.concat( |
||||
* dataBuffer(bytes1), |
||||
* dataBuffer(bytes2)); |
||||
* |
||||
* testDecodeAll(input, byte[].class, step -> step |
||||
* .consumeNextWith(expectBytes(allBytes)) |
||||
* .verifyComplete()); |
||||
* </pre> |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputType the desired output type |
||||
* @param stepConsumer a consumer to {@linkplain StepVerifier verify} the output |
||||
* @param mimeType the mime type to use for decoding. May be {@code null}. |
||||
* @param hints the hints used for decoding. May be {@code null}. |
||||
* @param <T> the output type |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
protected <T> void testDecodeToMono(Publisher<DataBuffer> input, ResolvableType outputType, |
||||
Consumer<StepVerifier.FirstStep<T>> stepConsumer, |
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { |
||||
|
||||
Mono<T> result = (Mono<T>) this.decoder.decodeToMono(input, outputType, mimeType, hints); |
||||
StepVerifier.FirstStep<T> step = StepVerifier.create(result); |
||||
stepConsumer.accept(step); |
||||
} |
||||
|
||||
/** |
||||
* Test a {@link Decoder#decodeToMono decode} scenario where the input stream contains an error. |
||||
* This test method will feed the first element of the {@code input} stream to the decoder, |
||||
* followed by an {@link InputException}. |
||||
* The result is expected to contain the error. |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputType the desired output type |
||||
* @param mimeType the mime type to use for decoding. May be {@code null}. |
||||
* @param hints the hints used for decoding. May be {@code null}. |
||||
* @see InputException |
||||
*/ |
||||
protected void testDecodeToMonoError(Publisher<DataBuffer> input, ResolvableType outputType, |
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { |
||||
|
||||
input = Flux.concat( |
||||
Flux.from(input).take(1), |
||||
Flux.error(new InputException())); |
||||
|
||||
Mono<?> result = this.decoder.decodeToMono(input, outputType, mimeType, hints); |
||||
|
||||
StepVerifier.create(result) |
||||
.expectError(InputException.class) |
||||
.verify(); |
||||
} |
||||
|
||||
/** |
||||
* Test a {@link Decoder#decodeToMono decode} scenario where the input stream is canceled. |
||||
* This test method will immediately cancel the output stream. |
||||
* |
||||
* @param input the input to be provided to the decoder |
||||
* @param outputType the desired output type |
||||
* @param mimeType the mime type to use for decoding. May be {@code null}. |
||||
* @param hints the hints used for decoding. May be {@code null}. |
||||
*/ |
||||
protected void testDecodeToMonoCancel(Publisher<DataBuffer> input, ResolvableType outputType, |
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { |
||||
|
||||
Mono<?> result = this.decoder.decodeToMono(input, outputType, mimeType, hints); |
||||
|
||||
StepVerifier.create(result) |
||||
.thenCancel() |
||||
.verify(); |
||||
} |
||||
|
||||
/** |
||||
* Test a {@link Decoder#decodeToMono decode} scenario where the input stream is empty. |
||||
* The output is expected to be empty as well. |
||||
* |
||||
* @param outputType the desired output type |
||||
* @param mimeType the mime type to use for decoding. May be {@code null}. |
||||
* @param hints the hints used for decoding. May be {@code null}. |
||||
*/ |
||||
protected void testDecodeToMonoEmpty(ResolvableType outputType, @Nullable MimeType mimeType, |
||||
@Nullable Map<String, Object> hints) { |
||||
|
||||
Flux<DataBuffer> input = Flux.empty(); |
||||
Mono<?> result = this.decoder.decodeToMono(input, outputType, mimeType, hints); |
||||
|
||||
StepVerifier.create(result) |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
/** |
||||
* Creates a deferred {@link DataBuffer} containing the given bytes. |
||||
* @param bytes the bytes that are to be stored in the buffer |
||||
* @return the deferred buffer |
||||
*/ |
||||
protected Mono<DataBuffer> dataBuffer(byte[] bytes) { |
||||
return Mono.defer(() -> { |
||||
DataBuffer dataBuffer = this.bufferFactory.allocateBuffer(bytes.length); |
||||
dataBuffer.write(bytes); |
||||
return Mono.just(dataBuffer); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Exception used in {@link #testDecodeError} and {@link #testDecodeToMonoError} |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
public static class InputException extends RuntimeException { |
||||
|
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
/* |
||||
* Copyright 2002-2018 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 org.springframework.core.io.buffer; |
||||
|
||||
import org.junit.After; |
||||
|
||||
/** |
||||
* Abstract base class for unit tests that allocate data buffers via a {@link DataBufferFactory}. |
||||
* After each unit test, this base class checks whether all created buffers have been released, |
||||
* throwing an {@link AssertionError} if not. |
||||
* |
||||
* @author Arjen Poutsma |
||||
* @since 5.1.3 |
||||
* @see LeakAwareDataBufferFactory |
||||
*/ |
||||
public abstract class AbstractLeakCheckingTestCase { |
||||
|
||||
/** |
||||
* The data buffer factory. |
||||
*/ |
||||
@SuppressWarnings("ProtectedField") |
||||
protected final LeakAwareDataBufferFactory bufferFactory = new LeakAwareDataBufferFactory(); |
||||
|
||||
/** |
||||
* Checks whether any of the data buffers created by {@link #bufferFactory} have not been |
||||
* released, throwing an assertion error if so. |
||||
*/ |
||||
@After |
||||
public final void checkForLeaks() { |
||||
this.bufferFactory.checkForLeaks(); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue