
ratpack.test.exec.ExecHarness Maven / Gradle / Ivy
Show all versions of ratpack-test Show documentation
/*
* Copyright 2014 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 ratpack.test.exec;
import ratpack.exec.*;
import ratpack.exec.internal.DefaultExecController;
import ratpack.func.Action;
import ratpack.func.Function;
import ratpack.registry.RegistrySpec;
import ratpack.test.exec.internal.DefaultExecHarness;
/**
* A utility for testing asynchronous support/service code.
*
* An execution harness is backed by a thread pool.
* It is important to call {@link #close()} when the object is no longer needed to shutdown this thread pool.
* Alternatively, if you are performing a single operation you can use one of the {@code *single} static methods.
*
* @see #yield(Function)
* @see #yieldSingle(Function)
* @see #run(Action)
* @see #runSingle(Action)
*/
public interface ExecHarness extends AutoCloseable {
/**
* Creates a new execution harness.
*
*
{@code
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
*
* // An async callback based API
* static class AsyncApi {
*
* static interface Callback {
* void receive(T value);
* }
*
* public void returnAsync(T value, Callback super T> callback) {
* new Thread(() -> callback.receive(value)).run();
* }
* }
*
* // Our service class that wraps the raw async API
* // In the real app this is created by the DI container (e.g. Guice)
* static class AsyncService {
* private final AsyncApi asyncApi = new AsyncApi();
*
* // Our method under test
* public Promise promise(final T value) {
* return Promise.async(downstream -> asyncApi.returnAsync(value, downstream::success));
* }
* }
*
* public static void main(String[] args) throws Throwable {
* // the harness must be close()'d when finished with to free resources
* try (ExecHarness harness = ExecHarness.harness()) {
*
* // set up the code under test
* final AsyncService service = new AsyncService();
*
* // exercise the async code using the harness, blocking until the promised value is available
* ExecResult result = harness.yield(execution -> service.promise("foo"));
*
* assertEquals("foo", result.getValue());
* }
* }
* }
* }
*
* When using Ratpack's RxJava integration, ExecHarness can be used to test {@code rx.Observable} instances by first converting them to a promise.
* See the {@code ratpack.rx.RxRatpack.asPromise(Observable)} documentation for an example of testing observables.
*
* @return a new execution harness
*/
static ExecHarness harness() {
return new DefaultExecHarness(new DefaultExecController());
}
static ExecHarness harness(int numThreads) {
return new DefaultExecHarness(new DefaultExecController(numThreads));
}
default ExecStarter fork() {
return getController().fork();
}
ExecController getController();
/**
* Synchronously returns a promised value.
*
* The given function will execute in a separate thread.
* The calling thread will block, waiting for the promised value to be provided.
*
* @param func a function that exercises some code that returns a promise
* @param the type of promised value
* @return the result of the execution
* @throws Exception any thrown by the function
*/
default ExecResult yield(Function super Execution, ? extends Promise> func) throws Exception {
return yield(Action.noop(), func);
}
/**
* Synchronously returns a promised value.
*
* The given function will execute in a separate thread.
* The calling thread will block, waiting for the promised value to be provided.
*
* @param registry the intial contents of the execution registry
* @param func a function that exercises some code that returns a promise
* @param the type of promised value
* @return the result of the execution
* @throws Exception any thrown by the function
*/
ExecResult yield(Action super RegistrySpec> registry, Function super Execution, ? extends Promise> func) throws Exception;
/**
* Creates an exec harness, {@link #yield(Function) executes} the given function with it before closing it, then returning execution result.
*
* @param func a function that exercises some code that returns a promise
* @param the type of promised value
* @return the result of the execution
* @throws Exception any thrown by the function, or the promise failure exception
*/
static ExecResult yieldSingle(Function super Execution, ? extends Promise> func) throws Exception {
try (ExecHarness harness = harness()) {
return harness.yield(func);
}
}
/**
* Creates an exec harness, {@link #yield(Action, Function) executes} the given function with it before closing it, then returning execution result.
*
* @param registry the intial contents of the execution registry
* @param func a function that exercises some code that returns a promise
* @param the type of promised value
* @return the result of the execution
* @throws Exception any thrown by the function, or the promise failure exception
*/
static ExecResult yieldSingle(Action super RegistrySpec> registry, Function super Execution, ? extends Promise> func) throws Exception {
try (ExecHarness harness = harness()) {
return harness.yield(registry, func);
}
}
/**
* Initiates an execution and blocks until it completes.
*
* If an uncaught exception is thrown during the execution, it will be thrown by this method.
*
* This method is useful for testing an execution that has some detectable side effect, as this method does not return the “result” of the execution.
*
* @param action the start of the execution
* @throws Exception any thrown during the execution that is not explicitly caught
* @see #runSingle(Action)
* @see #yield(Function)
*/
default void run(Action super Execution> action) throws Exception {
run(Action.noop(), action);
}
/**
* Initiates an execution and blocks until it completes.
*
* If an uncaught exception is thrown during the execution, it will be thrown by this method.
*
* This method is useful for testing an execution that has some detectable side effect, as this method does not return the “result” of the execution.
*
* @param registry the intial contents of the execution registry
* @param action the start of the execution
* @throws Exception any thrown during the execution that is not explicitly caught
* @see #runSingle(Action)
* @see #yield(Function)
*/
void run(Action super RegistrySpec> registry, Action super Execution> action) throws Exception;
/**
* Convenient form of {@link #run(Action)} that creates and closes a harness for the run.
*
* @param action the start of the execution
* @throws Exception any thrown during the execution that is not explicitly caught
* @see #run(Action)
* @see #yield(Function)
*/
static void runSingle(Action super Execution> action) throws Exception {
try (ExecHarness harness = harness()) {
harness.run(action);
}
}
/**
* Convenient form of {@link #run(Action, Action)} that creates and closes a harness for the run.
*
* @param registry the intial contents of the execution registry
* @param action the start of the execution
* @throws Exception any thrown during the execution that is not explicitly caught
* @see #run(Action)
* @see #yield(Function)
*/
static void runSingle(Action super RegistrySpec> registry, Action super Execution> action) throws Exception {
try (ExecHarness harness = harness()) {
harness.run(registry, action);
}
}
default void execute(Function super Execution, ? extends Operation> function) throws Exception {
yield(e -> function.apply(e).promise()).getValueOrThrow();
}
default void execute(Operation operation) throws Exception {
execute(e -> operation);
}
static void executeSingle(Function super Execution, ? extends Operation> function) throws Exception {
try (ExecHarness harness = harness()) {
harness.execute(function);
}
}
static void executeSingle(Operation operation) throws Exception {
try (ExecHarness harness = harness()) {
harness.execute(operation);
}
}
/**
* Shuts down the thread pool backing this harness.
*/
@Override
void close();
}