├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── io │ └── github │ └── avivcarmis │ └── javared │ ├── executor │ ├── BaseRedSynchronizer.java │ ├── PreconditionFailedException.java │ ├── RedSynchronizer.java │ └── RedVoidSynchronizer.java │ ├── future │ ├── BaseOpenRedFuture.java │ ├── OpenRedFuture.java │ ├── OpenRedFutureOf.java │ ├── RedFuture.java │ ├── RedFutureHub.java │ ├── RedFutureOf.java │ └── callbacks │ │ ├── Callback.java │ │ └── EmptyCallback.java │ └── test │ ├── RedTestContext.java │ └── RedTestRunner.java └── test └── java └── io └── github └── avivcarmis └── javared ├── TestRedFuture.java ├── TestRedSynchronizer.java └── TestRedTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | target/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | addons: 7 | apt: 8 | packages: 9 | - oracle-java8-installer -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Aviv C 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaRed : Effective Concurrency Modules for Java 2 | [![The Java Red Library](https://avivcarmis.github.io/java-red/images/logo.png "The Java Red Library")](https://github.com/avivcarmis/java-red "The Java Red Library") 3 | 4 | ------------ 5 | 6 | [![Java Red Build Status at Travis CI](https://travis-ci.org/avivcarmis/java-red.svg?branch=master "Java Red Build Status at Travis CI")](https://travis-ci.org/avivcarmis/java-red "Java Red Build Status at Travis CI") 7 | 8 | The **JavaRed** library essentially introduces an effective, intuitive interface to define and manage asynchronous, concurrent [graph execution flows](https://github.com/avivcarmis/java-red/wiki/About-Graph-Execution "graph execution flows"). 9 | JavaRed requires JDK 1.8 or higher. 10 | 11 | ### Latest Release 12 | ------------ 13 | The most recent release is JavaRed 1.0.1, released April 6, 2017. 14 | 15 | To add a dependency on JavaRed Library using Maven, use the following: 16 | ``` 17 | 18 | io.github.avivcarmis 19 | java-red 20 | 1.0.1 21 | 22 | ``` 23 | 24 | To add a dependency on JavaRed Library using Gradle, use the following: 25 | ``` 26 | compile 'io.github.avivcarmis:java-red:1.0.1' 27 | ``` 28 | 29 | API Docs: 30 | - Latest: https://avivcarmis.github.io/java-red/apidocs/latest/ 31 | - 1.0.1: https://avivcarmis.github.io/java-red/apidocs/1.0.1/ 32 | 33 | ### Getting Started 34 | ------------ 35 | The JavaRed Library getting start guide at Github Wiki page: https://github.com/avivcarmis/java-red/wiki 36 | 37 | ### Useful Links 38 | ------------ 39 | - [The project GitHub page](https://github.com/avivcarmis/java-red "The project GitHub page") 40 | - [The project Issue Tracker on GitHub](https://github.com/avivcarmis/java-red/issues "The project Issue Tracker on GitHub") 41 | - [The project build Status at Travis CI](https://travis-ci.org/avivcarmis/java-red "The project build Status at Travis CI") -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | com.google.guava 11 | guava 12 | 21.0 13 | 14 | 15 | 16 | 17 | junit 18 | junit 19 | 4.12 20 | 21 | 22 | 23 | 24 | 4.0.0 25 | io.github.avivcarmis 26 | java-red 27 | 1.0.1-SNAPSHOT 28 | jar 29 | ${project.groupId}:${project.artifactId} 30 | Effective Concurrency Modules for Java. 31 | https://github.com/avivcarmis/java-red 32 | 33 | GitHub Issues 34 | https://github.com/avivcarmis/java-red/issues 35 | 36 | 2017 37 | 38 | 39 | MIT License 40 | http://www.opensource.org/licenses/mit-license.php 41 | 42 | 43 | 44 | scm:git:https://github.com/avivcarmis/java-red.git 45 | scm:git:git@github.com:avivcarmis/java-red.git 46 | https://github.com/avivcarmis/java-red 47 | 48 | 49 | Travis CI 50 | https://travis-ci.org/avivcarmis/java-red 51 | 52 | 53 | 54 | 55 | Aviv Carmi 56 | avivcarmis@gmail.com 57 | 58 | owner 59 | developer 60 | 61 | +2 62 | 63 | 64 | 65 | 66 | 67 | ossrh 68 | https://oss.sonatype.org/content/repositories/snapshots 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-compiler-plugin 77 | 3.5.1 78 | 79 | 1.8 80 | 1.8 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | release 89 | 90 | 91 | 92 | 93 | org.sonatype.plugins 94 | nexus-staging-maven-plugin 95 | 1.6.8 96 | true 97 | 98 | ossrh 99 | https://oss.sonatype.org/ 100 | true 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-javadoc-plugin 107 | 2.10.4 108 | 109 | 110 | attach-javadocs 111 | 112 | jar 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-source-plugin 121 | 2.2.1 122 | 123 | 124 | attach-sources 125 | 126 | jar-no-fork 127 | 128 | 129 | 130 | 131 | 132 | 133 | org.apache.maven.plugins 134 | maven-gpg-plugin 135 | 1.5 136 | 137 | 138 | sign-artifacts 139 | verify 140 | 141 | sign 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/executor/PreconditionFailedException.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.executor; 2 | 3 | /** 4 | * Represents the indication error that will be thrown in case an 5 | * execution precondition failed. 6 | */ 7 | abstract public class PreconditionFailedException extends Exception { 8 | 9 | // Constructors 10 | 11 | private PreconditionFailedException(String message) { 12 | super(message); 13 | } 14 | 15 | private PreconditionFailedException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | // Static 20 | 21 | /** 22 | * Will be thrown in case an execution was expecting a precondition to 23 | * succeed, but failed. 24 | * 25 | * Original failure may be retrieved through {@link #getCause()} 26 | */ 27 | public static class Success extends PreconditionFailedException { 28 | 29 | Success(Throwable cause) { 30 | super("expected success but failed", cause); 31 | } 32 | 33 | } 34 | 35 | /** 36 | * Will be thrown in case an execution was expecting a precondition to 37 | * fail, but succeeded. 38 | */ 39 | public static class Failure extends PreconditionFailedException { 40 | 41 | private Failure() { 42 | super("expected failure but succeeded"); 43 | } 44 | 45 | static final Failure INSTANCE = new Failure(); 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/executor/RedSynchronizer.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.executor; 2 | 3 | import io.github.avivcarmis.javared.future.RedFuture; 4 | import io.github.avivcarmis.javared.future.RedFutureOf; 5 | 6 | /** 7 | * A class to implement execution of a Red Synchronizer which receive INPUT typed 8 | * inputs and returns OUTPUT typed outputs 9 | * 10 | * @param type of the input of the execution 11 | * @param type of the output of the execution 12 | */ 13 | abstract public class RedSynchronizer extends BaseRedSynchronizer { 14 | 15 | // Public 16 | 17 | /** 18 | * Receive an input and executes it, returns a {@link RedFutureOf} 19 | * of the execution output. 20 | * 21 | * @param input input to execute 22 | * @return {@link RedFutureOf} of the execution output 23 | */ 24 | public RedFutureOf execute(INPUT input) { 25 | try { 26 | Result result = handle(input); 27 | return result == null ? null : result._future; 28 | } catch (Throwable t) { 29 | return RedFuture.failedOf(t); 30 | } 31 | } 32 | 33 | // Private 34 | 35 | /** 36 | * Implements the execution flow of the Synchronizer 37 | * @param input input to handle 38 | * @return the result producing the output of the execution 39 | * @throws Throwable to enable throwable catching 40 | */ 41 | abstract protected Result handle(INPUT input) throws Throwable; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/executor/RedVoidSynchronizer.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.executor; 2 | 3 | import io.github.avivcarmis.javared.future.RedFuture; 4 | 5 | /** 6 | * A class to implement execution of a Red Synchronizer which receive INPUT typed 7 | * inputs and returns a {@link RedFuture} to indicate the completion of an execution. 8 | * 9 | * @param type of the input of the execution 10 | */ 11 | abstract public class RedVoidSynchronizer extends BaseRedSynchronizer { 12 | 13 | // Public 14 | 15 | /** 16 | * Receive an input and executes it, returns a {@link RedFuture} 17 | * of the execution completion. 18 | * 19 | * @param input input to execute 20 | * @return {@link RedFuture} of the execution output 21 | */ 22 | public RedFuture execute(INPUT input) { 23 | try { 24 | Marker result = handle(input); 25 | return result == null ? null : result._future; 26 | } catch (Throwable t) { 27 | return RedFuture.failedOf(t); 28 | } 29 | } 30 | 31 | // Private 32 | 33 | /** 34 | * Implements the execution flow of the Synchronizer 35 | * @param input input to handle 36 | * @return the marker indicating the execution completion 37 | * @throws Throwable to enable throwable catching 38 | */ 39 | abstract protected Marker handle(INPUT input) throws Throwable; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/future/BaseOpenRedFuture.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.future; 2 | 3 | import com.google.common.util.concurrent.FutureCallback; 4 | import com.google.common.util.concurrent.Futures; 5 | import com.google.common.util.concurrent.ListenableFuture; 6 | import com.google.common.util.concurrent.SettableFuture; 7 | import io.github.avivcarmis.javared.future.callbacks.Callback; 8 | import io.github.avivcarmis.javared.future.callbacks.EmptyCallback; 9 | 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.Executor; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.TimeoutException; 14 | import java.util.logging.Level; 15 | import java.util.logging.Logger; 16 | 17 | /** 18 | * An abstract class to implement common functionality of both {@link RedFuture} and {@link RedFutureOf} 19 | */ 20 | abstract public class BaseOpenRedFuture implements RedFuture { 21 | 22 | // Constants 23 | 24 | private static final Logger LOGGER = Logger.getLogger(RedFuture.class.getName()); 25 | 26 | // Fields 27 | 28 | /** 29 | * The underlying Guava future. 30 | */ 31 | private final SettableFuture _settableFuture; 32 | 33 | // Constructors 34 | 35 | @SuppressWarnings("WeakerAccess") 36 | protected BaseOpenRedFuture() { 37 | _settableFuture = SettableFuture.create(); 38 | } 39 | 40 | // Public 41 | 42 | /** 43 | * Fails the future with given throwable. 44 | * As result of this method invocation, all registered failure and finally callbacks 45 | * will be invoked. 46 | * If the future is already completed, a warning will be logged. @see {@link #fail(Throwable, boolean)} 47 | * 48 | * @param throwable cause of the failure 49 | */ 50 | public void fail(Throwable throwable) { 51 | fail(throwable, true); 52 | } 53 | 54 | /** 55 | * Fails the future with given throwable. 56 | * As result of this method invocation, all registered failure and finally callbacks 57 | * will be invoked. 58 | * If the future is already completed, this call will be ignored. 59 | * 60 | * @param throwable cause of the failure 61 | */ 62 | public void tryFail(Throwable throwable) { 63 | fail(throwable, false); 64 | } 65 | 66 | @Override 67 | public RedFuture addSuccessCallback(EmptyCallback callback) { 68 | Futures.addCallback(_settableFuture, safeCallback(o -> callback.call(), null)); 69 | return this; 70 | } 71 | 72 | @Override 73 | public RedFuture addSuccessCallback(Executor executor, EmptyCallback callback) { 74 | Futures.addCallback(_settableFuture, safeCallback(t -> callback.call(), null), executor); 75 | return this; 76 | } 77 | 78 | @Override 79 | public RedFuture addFailureCallback(Callback callback) { 80 | Futures.addCallback(_settableFuture, safeCallback(null, callback)); 81 | return this; 82 | } 83 | 84 | @Override 85 | public RedFuture addFailureCallback(Executor executor, Callback callback) { 86 | Futures.addCallback(_settableFuture, safeCallback(null, callback), executor); 87 | return this; 88 | } 89 | 90 | @Override 91 | public RedFuture addFinallyCallback(EmptyCallback callback) { 92 | Futures.addCallback(_settableFuture, safeCallback(t -> callback.call(), throwable -> callback.call())); 93 | return this; 94 | } 95 | 96 | @Override 97 | public RedFuture addFinallyCallback(Executor executor, EmptyCallback callback) { 98 | Futures.addCallback(_settableFuture, safeCallback(t -> callback.call(), throwable -> callback.call()), executor); 99 | return this; 100 | } 101 | 102 | @Override 103 | public ListenableFuture getListenableFuture() { 104 | return _settableFuture; 105 | } 106 | 107 | @Override 108 | public void waitForCompletion() throws ExecutionException, InterruptedException { 109 | _settableFuture.get(); 110 | } 111 | 112 | @Override 113 | public void waitForCompletion(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException { 114 | _settableFuture.get(timeout, unit); 115 | } 116 | 117 | @Override 118 | public boolean isDone() { 119 | return _settableFuture.isDone(); 120 | } 121 | 122 | // Private 123 | 124 | /** 125 | * Resolving the underlying {@link SettableFuture} with given value, either logging or 126 | * not logging warning in case the future is already complete, according to require parameter. 127 | * 128 | * @param value value to resolve the future with 129 | * @param require whether or not to log warning in case the future is already complete 130 | * @return true if successfully resolved, false if future is already complete 131 | */ 132 | @SuppressWarnings("WeakerAccess") 133 | protected boolean resolve(T value, boolean require) { 134 | if (!_settableFuture.set(value)) { 135 | if (!_settableFuture.isCancelled() && require) { 136 | LOGGER.log(Level.WARNING, "red future resolved more than once"); 137 | } 138 | return false; 139 | } 140 | return true; 141 | } 142 | 143 | /** 144 | * Failing the underlying {@link SettableFuture} with given cause, either logging or 145 | * not logging warning in case the future is already complete, according to require parameter. 146 | * 147 | * @param throwable cause to fail the future with 148 | * @param require whether or not to log warning in case the future is already complete 149 | * @return true if successfully failed, false if future is already complete 150 | */ 151 | @SuppressWarnings("WeakerAccess") 152 | protected boolean fail(Throwable throwable, boolean require) { 153 | if (!_settableFuture.setException(throwable)) { 154 | if (!_settableFuture.isCancelled() && require) { 155 | LOGGER.log(Level.WARNING, "red future failed more than once"); 156 | } 157 | return false; 158 | } 159 | return true; 160 | } 161 | 162 | /** 163 | * Generates a guava {@link FutureCallback} to attach to a {@link ListenableFuture} with given 164 | * success and failure callbacks. Wraps the invocations such that if an uncaught runtime exception 165 | * is thrown from a callback invocation, a warning is logged. Such a case must be seriously considered 166 | * in an asynchronous system, as it can cause the system to freeze if some pending propagation has not 167 | * reached before the exception was thrown. 168 | * @param onSuccess callback on be invoked on success, or null to skip success event. 169 | * @param onFailure callback on be invoked on failure, or null to skip failure event. 170 | * @param type of the returned {@link FutureCallback} 171 | * @return a guava {@link FutureCallback} to attach to a {@link ListenableFuture} 172 | */ 173 | @SuppressWarnings("WeakerAccess") 174 | protected FutureCallback safeCallback(Callback onSuccess, Callback onFailure) { 175 | return new FutureCallback() { 176 | 177 | @Override 178 | public void onSuccess(K t) { 179 | call(onSuccess, t); 180 | } 181 | 182 | @Override 183 | public void onFailure(Throwable throwable) { 184 | call(onFailure, throwable); 185 | } 186 | 187 | }; 188 | } 189 | 190 | private void call(Callback callback, K value) { 191 | if (callback != null) { 192 | try { 193 | callback.call(value); 194 | } catch (Throwable caught) { 195 | LOGGER.log(Level.WARNING, "exception thrown during red future callback execution, " + 196 | "this may cause system freeze due to callback propagation stop", caught); 197 | } 198 | } 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/future/OpenRedFuture.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.future; 2 | 3 | import com.google.common.util.concurrent.Futures; 4 | import com.google.common.util.concurrent.ListenableFuture; 5 | 6 | import java.util.concurrent.Executor; 7 | 8 | /** 9 | * An implementation of {@link RedFuture}, which represents the settable side of the future. 10 | * A future of this type may be resolved or failed, as well as attach with callbacks. 11 | */ 12 | public class OpenRedFuture extends BaseOpenRedFuture { 13 | 14 | // Constructors 15 | 16 | @SuppressWarnings("WeakerAccess") 17 | protected OpenRedFuture() {} 18 | 19 | // Public 20 | 21 | /** 22 | * Resolves the future, marking it successfully completed. 23 | * As result of this method invocation, all registered success and finally callbacks 24 | * will be invoked. 25 | * If the future is already completed, a warning will be logged. @see {@link #resolve(Object, boolean)} 26 | */ 27 | public void resolve() { 28 | resolve(null, true); 29 | } 30 | 31 | /** 32 | * Resolves the future, marking it successfully completed. 33 | * As result of this method invocation, all registered success and finally callbacks 34 | * will be invoked. 35 | * If the future is already completed, this call will be ignored. 36 | */ 37 | public void tryResolve() { 38 | resolve(null, false); 39 | } 40 | 41 | /** 42 | * Tells the current open future to follow the given future status. 43 | * When the given future will be resolved or failed, the current future will respectively 44 | * be directly resolved or failed by the same thread. 45 | * If the given future is already resolved or failed, the current future will respectively 46 | * be directly resolved or failed by the current thread. 47 | * Note that if the future is already completed when trying to follow the given future status, 48 | * a warning will be logged and the second completion invocation will be ignored. 49 | * Thus, trying to follow more than one future is illegal. 50 | * 51 | * @param future future to follow 52 | */ 53 | public void follow(RedFuture future) { 54 | future.addSuccessCallback(this::resolve).addFailureCallback(this::fail); 55 | } 56 | 57 | /** 58 | * Tells the current open future to follow the given future status. 59 | * When the given future will be resolved or failed, the current future will respectively 60 | * queued to be resolved or failed with the given executor. 61 | * If the given future is already resolved or failed, the current future will respectively 62 | * be queued to be resolved or failed with the given executor. 63 | * Note that if the future is already completed when trying to follow the given future status, 64 | * a warning will be logged and the second completion invocation will be ignored. 65 | * Thus, trying to follow more than one future is illegal. 66 | * 67 | * @param executor to execute the completion of this future 68 | * @param future future to follow 69 | */ 70 | public void follow(Executor executor, RedFuture future) { 71 | future.addSuccessCallback(executor, this::resolve).addFailureCallback(executor, this::fail); 72 | } 73 | 74 | /** 75 | * Tells the current open future to follow the given future status. 76 | * When the given future will be resolved or failed, the current future will respectively 77 | * be directly resolved or failed by the same thread. 78 | * If the given future is already resolved or failed, the current future will respectively 79 | * be directly resolved or failed by the current thread. 80 | * Note that if the future is already completed when trying to follow the given future status, 81 | * a warning will be logged and the second completion invocation will be ignored. 82 | * Thus, trying to follow more than one future is illegal. 83 | * 84 | * @param listenableFuture future to follow 85 | */ 86 | public void follow(ListenableFuture listenableFuture) { 87 | Futures.addCallback(listenableFuture, safeCallback(o -> resolve(), this::fail)); 88 | } 89 | 90 | /** 91 | * Tells the current open future to follow the given future status. 92 | * When the given future will be resolved or failed, the current future will respectively 93 | * queued to be resolved or failed with the given executor. 94 | * If the given future is already resolved or failed, the current future will respectively 95 | * be queued to be resolved or failed with the given executor. 96 | * Note that if the future is already completed when trying to follow the given future status, 97 | * a warning will be logged and the second completion invocation will be ignored. 98 | * Thus, trying to follow more than one future is illegal. 99 | * 100 | * @param executor to execute the completion of this future 101 | * @param listenableFuture future to follow 102 | */ 103 | public void follow(Executor executor, ListenableFuture listenableFuture) { 104 | Futures.addCallback(listenableFuture, safeCallback(o -> resolve(), this::fail), executor); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/future/OpenRedFutureOf.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.future; 2 | 3 | import com.google.common.util.concurrent.Futures; 4 | import com.google.common.util.concurrent.ListenableFuture; 5 | import io.github.avivcarmis.javared.future.callbacks.Callback; 6 | import io.github.avivcarmis.javared.future.callbacks.EmptyCallback; 7 | 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.concurrent.Executor; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.TimeoutException; 12 | import java.util.concurrent.atomic.AtomicReference; 13 | 14 | /** 15 | * An implementation of {@link RedFutureOf}, which represents the settable side of the typed future. 16 | * A future of this type may be resolved or failed, as well as attach with callbacks. 17 | * 18 | * @param the type of the future value 19 | */ 20 | public class OpenRedFutureOf extends BaseOpenRedFuture implements RedFutureOf { 21 | 22 | // Fields 23 | 24 | /** 25 | * the resulted value of the future 26 | */ 27 | private final AtomicReference _value; 28 | 29 | // Constructors 30 | 31 | @SuppressWarnings("WeakerAccess") 32 | protected OpenRedFutureOf() { 33 | _value = new AtomicReference<>(null); 34 | } 35 | 36 | // Public 37 | 38 | /** 39 | * Resolves the future, marking it successfully completed. 40 | * As result of this method invocation, all registered success and finally callbacks 41 | * will be invoked. 42 | * If the future is already completed, a warning will be logged. @see {@link #resolve(Object, boolean)} 43 | * 44 | * @param value the value to resolve the future with 45 | */ 46 | public void resolve(T value) { 47 | _value.compareAndSet(null, value); 48 | resolve(value, true); 49 | } 50 | 51 | /** 52 | * Resolves the future, marking it successfully completed. 53 | * As result of this method invocation, all registered success and finally callbacks 54 | * will be invoked. 55 | * If the future is already completed, this call will be ignored. 56 | * 57 | * @param value the value to resolve the future with 58 | */ 59 | public void tryResolve(T value) { 60 | _value.compareAndSet(null, value); 61 | resolve(value, false); 62 | } 63 | 64 | /** 65 | * Tells the current open future to follow the given future status. 66 | * When the given future will be resolved or failed, the current future will respectively 67 | * be directly resolved or failed by the same thread. 68 | * If the given future is already resolved or failed, the current future will respectively 69 | * be directly resolved or failed by the current thread. 70 | * Note that if the future is already completed when trying to follow the given future status, 71 | * a warning will be logged and the second completion invocation will be ignored. 72 | * Thus, trying to follow more than one future is illegal. 73 | * 74 | * @param future future to follow 75 | */ 76 | public void follow(RedFutureOf future) { 77 | future.addSuccessCallback(this::resolve).addFailureCallback(this::fail); 78 | } 79 | 80 | /** 81 | * Tells the current open future to follow the given future status. 82 | * When the given future will be resolved or failed, the current future will respectively 83 | * queued to be resolved or failed with the given executor. 84 | * If the given future is already resolved or failed, the current future will respectively 85 | * be queued to be resolved or failed with the given executor. 86 | * Note that if the future is already completed when trying to follow the given future status, 87 | * a warning will be logged and the second completion invocation will be ignored. 88 | * Thus, trying to follow more than one future is illegal. 89 | * 90 | * @param executor to execute the completion of this future 91 | * @param future future to follow 92 | */ 93 | public void follow(Executor executor, RedFutureOf future) { 94 | future.addSuccessCallback(executor, this::resolve).addFailureCallback(executor, this::fail); 95 | } 96 | 97 | /** 98 | * Tells the current open future to follow the given future status. 99 | * When the given future will be resolved or failed, the current future will respectively 100 | * be directly resolved or failed by the same thread. 101 | * If the given future is already resolved or failed, the current future will respectively 102 | * be directly resolved or failed by the current thread. 103 | * Note that if the future is already completed when trying to follow the given future status, 104 | * a warning will be logged and the second completion invocation will be ignored. 105 | * Thus, trying to follow more than one future is illegal. 106 | * 107 | * @param listenableFuture future to follow 108 | */ 109 | public void follow(ListenableFuture listenableFuture) { 110 | Futures.addCallback(listenableFuture, safeCallback(this::resolve, this::fail)); 111 | } 112 | 113 | /** 114 | * Tells the current open future to follow the given future status. 115 | * When the given future will be resolved or failed, the current future will respectively 116 | * queued to be resolved or failed with the given executor. 117 | * If the given future is already resolved or failed, the current future will respectively 118 | * be queued to be resolved or failed with the given executor. 119 | * Note that if the future is already completed when trying to follow the given future status, 120 | * a warning will be logged and the second completion invocation will be ignored. 121 | * Thus, trying to follow more than one future is illegal. 122 | * 123 | * @param executor to execute the completion of this future 124 | * @param listenableFuture future to follow 125 | */ 126 | public void follow(Executor executor, ListenableFuture listenableFuture) { 127 | Futures.addCallback(listenableFuture, safeCallback(this::resolve, this::fail), executor); 128 | } 129 | 130 | @Override 131 | public RedFutureOf addSuccessCallback(Callback callback) { 132 | Futures.addCallback(getListenableFuture(), safeCallback(callback, null)); 133 | return this; 134 | } 135 | 136 | @Override 137 | public RedFutureOf addSuccessCallback(Executor executor, Callback callback) { 138 | Futures.addCallback(getListenableFuture(), safeCallback(callback, null), executor); 139 | return this; 140 | } 141 | 142 | @Override 143 | public T tryGet() { 144 | return _value.get(); 145 | } 146 | 147 | @Override 148 | public T waitAndGet() throws ExecutionException, InterruptedException { 149 | return getListenableFuture().get(); 150 | } 151 | 152 | @Override 153 | public T waitAndGet(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException { 154 | return getListenableFuture().get(timeout, unit); 155 | } 156 | 157 | @Override 158 | public RedFutureOf addSuccessCallback(EmptyCallback callback) { 159 | super.addSuccessCallback(callback); 160 | return this; 161 | } 162 | 163 | @Override 164 | public RedFutureOf addSuccessCallback(Executor executor, EmptyCallback callback) { 165 | super.addSuccessCallback(executor, callback); 166 | return this; 167 | } 168 | 169 | @Override 170 | public RedFutureOf addFailureCallback(Callback callback) { 171 | super.addFailureCallback(callback); 172 | return this; 173 | } 174 | 175 | @Override 176 | public RedFutureOf addFailureCallback(Executor executor, Callback callback) { 177 | super.addFailureCallback(executor, callback); 178 | return this; 179 | } 180 | 181 | @Override 182 | public RedFutureOf addFinallyCallback(EmptyCallback callback) { 183 | super.addFinallyCallback(callback); 184 | return this; 185 | } 186 | 187 | @Override 188 | public RedFutureOf addFinallyCallback(Executor executor, EmptyCallback callback) { 189 | super.addFinallyCallback(executor, callback); 190 | return this; 191 | } 192 | 193 | @Override 194 | public boolean cancel(boolean mayInterruptIfRunning) { 195 | return getListenableFuture().cancel(mayInterruptIfRunning); 196 | } 197 | 198 | @Override 199 | public boolean isCancelled() { 200 | return getListenableFuture().isCancelled(); 201 | } 202 | 203 | @Override 204 | public T get() throws InterruptedException, ExecutionException { 205 | return getListenableFuture().get(); 206 | } 207 | 208 | @Override 209 | public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 210 | return getListenableFuture().get(timeout, unit); 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/future/RedFuture.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.future; 2 | 3 | import com.google.common.util.concurrent.JdkFutureAdapters; 4 | import com.google.common.util.concurrent.ListenableFuture; 5 | import io.github.avivcarmis.javared.future.callbacks.Callback; 6 | import io.github.avivcarmis.javared.future.callbacks.EmptyCallback; 7 | 8 | import java.util.concurrent.*; 9 | 10 | /** 11 | * A simple {@link java.util.concurrent.Future} like interface, 12 | * which supports trivial, fully asynchronous, Java 8 callbacks. 13 | * 14 | * As opposed to {@link RedFutureOf}, this interface represents a future that is not 15 | * pending value, but only resolved to mark the underlying task complete. 16 | */ 17 | public interface RedFuture { 18 | 19 | /** 20 | * Attach a no-parameter callback to be invoked when the future is successfully resolved. 21 | * When a certain thread resolves the future, it will directly invoke the callback. 22 | * If the future is already resolved, the callback will be directly invoked. 23 | * 24 | * @param callback callback to be invoked when the future is successfully resolved 25 | * @return this, @see Fluent interface 26 | */ 27 | RedFuture addSuccessCallback(EmptyCallback callback); 28 | 29 | /** 30 | * Attach a no-parameter callback to be invoked when the future is successfully resolved. 31 | * When a certain thread resolves the future, it will queue the callback invocation to the 32 | * given executor. 33 | * If the future is already resolved, the callback will be immediately queued. 34 | * 35 | * @param executor executor to invoke the callback 36 | * @param callback callback to be invoked when the future is successfully resolved 37 | * @return this, @see Fluent interface 38 | */ 39 | RedFuture addSuccessCallback(Executor executor, EmptyCallback callback); 40 | 41 | /** 42 | * Attach a callback receiving a throwable to be invoked when the future is failed. 43 | * When a certain thread fails the future, it will directly invoke the callback. 44 | * If the future has already failed, the callback will be directly invoked. 45 | * 46 | * @param callback callback receiving a throwable to be invoked when the future is failed 47 | * @return this, @see Fluent interface 48 | */ 49 | RedFuture addFailureCallback(Callback callback); 50 | 51 | /** 52 | * Attach a callback receiving a throwable to be invoked when the future is failed. 53 | * When a certain thread fails the future, it will queue the callback invocation to the 54 | * given executor. 55 | * If the future has already failed, the callback will be immediately queued. 56 | * 57 | * @param executor executor to invoke the callback 58 | * @param callback callback receiving a throwable to be invoked when the future is failed 59 | * @return this, @see Fluent interface 60 | */ 61 | RedFuture addFailureCallback(Executor executor, Callback callback); 62 | 63 | /** 64 | * Attach a no-parameter callback to be invoked when the future is either successfully 65 | * resolved, or otherwise failed, in resemblance to java finally block. 66 | * When a certain thread resolves the future, it will directly invoke the callback. 67 | * If the future is already complete, the callback will be directly invoked. 68 | * 69 | * @param callback callback to be invoked when the future completes 70 | * @return this, @see Fluent interface 71 | */ 72 | RedFuture addFinallyCallback(EmptyCallback callback); 73 | 74 | /** 75 | * Attach a no-parameter callback to be invoked when the future is either successfully 76 | * resolved, or otherwise failed, in resemblance to java finally block. 77 | * When a certain thread fails the future, it will queue the callback invocation to the 78 | * given executor. 79 | * If the future is already complete, the callback will be immediately queued. 80 | * 81 | * @param executor executor to invoke the callback 82 | * @param callback callback to be invoked when the future completes 83 | * @return this, @see Fluent interface 84 | */ 85 | RedFuture addFinallyCallback(Executor executor, EmptyCallback callback); 86 | 87 | /** 88 | * Test to see whether or not the future is currently completed, either by success 89 | * or failure. This method is not blocking, nor is it synchronizing different thread calls. 90 | * @return true if the future is completed, false otherwise 91 | */ 92 | boolean isDone(); 93 | 94 | /** 95 | * Tries to blocks the thread until the future is completed. 96 | * If the future is already complete, the method returns immediately. 97 | * @throws ExecutionException if the computation threw an exception 98 | * @throws InterruptedException if the current thread was interrupted while waiting 99 | * @see Future#get() 100 | */ 101 | void waitForCompletion() throws ExecutionException, InterruptedException; 102 | 103 | /** 104 | * Tries to blocks the thread for at most the given timeout until the future is completed. 105 | * If the future is already complete, the method returns immediately. 106 | * 107 | * @param timeout the maximum time to wait 108 | * @param unit the time unit of the timeout argument 109 | * @throws ExecutionException if the computation threw an exception 110 | * @throws InterruptedException if the current thread was interrupted while waiting 111 | * @throws TimeoutException if the wait timed out 112 | * @see Future#get(long, TimeUnit) 113 | */ 114 | void waitForCompletion(long timeout, TimeUnit unit) 115 | throws ExecutionException, InterruptedException, TimeoutException; 116 | 117 | /** 118 | * @return the underlying Guava {@link ListenableFuture} 119 | */ 120 | ListenableFuture getListenableFuture(); 121 | 122 | // Package Constructors 123 | 124 | /** 125 | * @return a new instance of {@link OpenRedFuture} 126 | */ 127 | static OpenRedFuture future() { 128 | return new OpenRedFuture(); 129 | } 130 | 131 | /** 132 | * @param type of the future value 133 | * @return a new instance of {@link OpenRedFutureOf} 134 | */ 135 | static OpenRedFutureOf futureOf() { 136 | return new OpenRedFutureOf<>(); 137 | } 138 | 139 | /** 140 | * @return a new instance of {@link OpenRedFuture} which is already resolved 141 | */ 142 | static RedFuture resolved() { 143 | OpenRedFuture future = future(); 144 | future.resolve(); 145 | return future; 146 | } 147 | 148 | /** 149 | * @param value to be resolved with 150 | * @param type of the future value 151 | * @return a new instance of {@link OpenRedFutureOf} which is already resolved with given value 152 | */ 153 | static RedFutureOf resolvedOf(T value) { 154 | OpenRedFutureOf future = futureOf(); 155 | future.resolve(value); 156 | return future; 157 | } 158 | 159 | /** 160 | * @param t to fail the future with 161 | * @return a new instance of {@link OpenRedFuture} which is already failed with given throwable 162 | */ 163 | static RedFuture failed(Throwable t) { 164 | OpenRedFuture future = future(); 165 | future.fail(t); 166 | return future; 167 | } 168 | 169 | /** 170 | * @param t to fail the future with 171 | * @param type of the future value 172 | * @return a new instance of {@link OpenRedFutureOf} which is already failed with given throwable 173 | */ 174 | static RedFutureOf failedOf(Throwable t) { 175 | OpenRedFutureOf future = futureOf(); 176 | future.fail(t); 177 | return future; 178 | } 179 | 180 | /** 181 | * Converts the given {@link Future} object to a {@link RedFuture} 182 | * see {@link JdkFutureAdapters#listenInPoolThread(Future)} for detailed implications 183 | * @param future future to convert 184 | * @param type of the future value 185 | * @return a RedFuture instance tracking the given {@link Future} 186 | */ 187 | static RedFutureOf convert(Future future) { 188 | if (future instanceof RedFutureOf) { 189 | return (RedFutureOf) future; 190 | } 191 | if (future instanceof ListenableFuture) { 192 | OpenRedFutureOf result = futureOf(); 193 | result.follow((ListenableFuture) future); 194 | return result; 195 | } 196 | return convert(JdkFutureAdapters.listenInPoolThread(future)); 197 | } 198 | 199 | /** 200 | * Converts the given {@link Future} object to a {@link RedFuture} with given executor 201 | * see {@link JdkFutureAdapters#listenInPoolThread(Future, Executor)} for detailed implications 202 | * @param future future to convert 203 | * @param executor executor to wait to future to complete and then execute callbacks 204 | * note that if the future is already completed, the callbacks will be 205 | * executed by the current thread 206 | * @param type of the future value 207 | * @return a RedFuture instance tracking the given {@link Future} 208 | */ 209 | static RedFutureOf convert(Future future, Executor executor) { 210 | if (future instanceof RedFutureOf) { 211 | return (RedFutureOf) future; 212 | } 213 | if (future instanceof ListenableFuture) { 214 | OpenRedFutureOf result = futureOf(); 215 | result.follow(executor, (ListenableFuture) future); 216 | return result; 217 | } 218 | return convert(JdkFutureAdapters.listenInPoolThread(future, executor), executor); 219 | } 220 | 221 | /** 222 | * @return a new instance of {@link RedFutureHub} 223 | */ 224 | static RedFutureHub hub() { 225 | return new RedFutureHub(); 226 | } 227 | 228 | } 229 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/future/RedFutureHub.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.future; 2 | 3 | import com.google.common.util.concurrent.Futures; 4 | import com.google.common.util.concurrent.ListenableFuture; 5 | 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.LinkedList; 9 | import java.util.List; 10 | 11 | /** 12 | * An object to track operations of multiple {@link RedFuture} instances. 13 | */ 14 | public class RedFutureHub { 15 | 16 | // Fields 17 | 18 | /** 19 | * All the hub's tracked futures. 20 | */ 21 | private final List> listenableFutures; 22 | 23 | // Constructors 24 | 25 | @SuppressWarnings("WeakerAccess") 26 | protected RedFutureHub() { 27 | listenableFutures = new LinkedList<>(); 28 | } 29 | 30 | // Public 31 | 32 | /** 33 | * Requests the hub to provide and track a single instance of {@link OpenRedFuture}. 34 | * The instance may be resolved, failed or follow other futures, and will be tracked 35 | * by the current hub. 36 | * 37 | * @return a new instance of {@link OpenRedFuture} 38 | */ 39 | public OpenRedFuture provideFuture() { 40 | OpenRedFuture future = RedFuture.future(); 41 | listenableFutures.add(future.getListenableFuture()); 42 | return future; 43 | } 44 | 45 | /** 46 | * Requests the hub to provide and track a single instance of {@link OpenRedFutureOf}. 47 | * The instance may be resolved, failed or follow other futures, and will be tracked 48 | * by the current hub. 49 | * 50 | * @param type of the future to return 51 | * @return a new instance of {@link OpenRedFutureOf} 52 | */ 53 | public OpenRedFutureOf provideFutureOf() { 54 | OpenRedFutureOf future = RedFuture.futureOf(); 55 | listenableFutures.add(future.getListenableFuture()); 56 | return future; 57 | } 58 | 59 | /** 60 | * Requests the hub to adopt and track an instance of {@link RedFuture}. 61 | * This means that the given instance will be tracked by the hub. 62 | * 63 | * @param future future to adopt 64 | * @return this, @see Fluent interface 65 | */ 66 | public RedFutureHub adoptFuture(RedFuture future) { 67 | listenableFutures.add(future.getListenableFuture()); 68 | return this; 69 | } 70 | 71 | /** 72 | * Requests the hub to adopt and track multiple instances of {@link RedFuture}. 73 | * This means that the given instances will be tracked by the hub. 74 | * 75 | * @param futures a collection of futures to adopt 76 | * @return this, @see Fluent interface 77 | */ 78 | public RedFutureHub adoptFutures(Collection futures) { 79 | for (RedFuture future : futures) { 80 | listenableFutures.add(future.getListenableFuture()); 81 | } 82 | return this; 83 | } 84 | 85 | /** 86 | * Requests the hub to adopt and track multiple instances of {@link RedFuture}. 87 | * This means that the given instances will be tracked by the hub. 88 | * 89 | * @param futures a collection of futures to adopt 90 | * @return this, @see Fluent interface 91 | */ 92 | public RedFutureHub adoptFutures(RedFuture... futures) { 93 | for (RedFuture future : futures) { 94 | listenableFutures.add(future.getListenableFuture()); 95 | } 96 | return this; 97 | } 98 | 99 | /** 100 | * Requests the hub to adopt and track an instance of {@link ListenableFuture}. 101 | * This means that the given instance will be tracked by the hub. 102 | * 103 | * @param future future to adopt 104 | * @return this, @see Fluent interface 105 | */ 106 | public RedFutureHub adoptListenableFuture(ListenableFuture future) { 107 | listenableFutures.add(future); 108 | return this; 109 | } 110 | 111 | /** 112 | * Requests the hub to adopt and track multiple instances of {@link ListenableFuture}. 113 | * This means that the given instances will be tracked by the hub. 114 | * 115 | * @param futures a collection of futures to adopt 116 | * @return this, @see Fluent interface 117 | */ 118 | public RedFutureHub adoptListenableFutures(Collection futures) { 119 | for (ListenableFuture future : futures) { 120 | listenableFutures.add(future); 121 | } 122 | return this; 123 | } 124 | 125 | /** 126 | * Requests the hub to adopt and track multiple instances of {@link ListenableFuture}. 127 | * This means that the given instances will be tracked by the hub. 128 | * 129 | * @param futures a collection of futures to adopt 130 | * @return this, @see Fluent interface 131 | */ 132 | public RedFutureHub adoptListenableFutures(ListenableFuture... futures) { 133 | Collections.addAll(listenableFutures, futures); 134 | return this; 135 | } 136 | 137 | /** 138 | * Requests the hub to return a single united, optimistic {@link RedFuture} instance. 139 | * Optimistic means that the returned future expects all the hub's tracked futures to resolve successfully, 140 | * namely, it will be: 141 | * Successfully resolved if and when all the hub's tracked futures are successfully resolved. 142 | * Failed if and when the first of the hub's tracked futures is failed. 143 | * 144 | * @return the united future. 145 | */ 146 | public RedFuture uniteOptimistically() { 147 | RedFuture validated = validate(); 148 | if (validated != null) { 149 | return validated; 150 | } 151 | ListenableFuture> collection = Futures.allAsList(listenableFutures); 152 | OpenRedFuture future = RedFuture.future(); 153 | future.follow(collection); 154 | return future; 155 | } 156 | 157 | /** 158 | * Requests the hub to return a single united, pessimistic {@link RedFuture} instance. 159 | * Pessimistic means that the returned future expects all the hub's tracked futures to complete at some point, 160 | * namely, it will be successfully resolved once all the hub's tracked futures are completed. 161 | * It will not be failed in any case. 162 | * 163 | * @return the united future. 164 | */ 165 | public RedFuture unitePessimistically() { 166 | RedFuture validated = validate(); 167 | if (validated != null) { 168 | return validated; 169 | } 170 | ListenableFuture> collection = Futures.successfulAsList(listenableFutures); 171 | OpenRedFuture future = RedFuture.future(); 172 | future.follow(collection); 173 | return future; 174 | } 175 | 176 | /** 177 | * Requests the hub to return a single united, cautious {@link RedFuture} instance. 178 | * Cautious means that the returned future expects all the hub's tracked futures to complete at some point, 179 | * but it still tracks their status. Namely, it will be: 180 | * Successfully resolved if and when all the hub's tracked futures are successfully resolved. 181 | * Failed if and when all the hub's tracked futures are completed, but at least one of them has failed, 182 | * in such a case, the failure callback throwable will be the cause of the first tracked future to fail. 183 | * 184 | * @return the united future. 185 | */ 186 | public RedFuture uniteCautiously() { 187 | RedFuture validated = validate(); 188 | if (validated != null) { 189 | return validated; 190 | } 191 | RedFuture optimistic = uniteOptimistically(); 192 | RedFuture pessimistic = unitePessimistically(); 193 | OpenRedFuture future = RedFuture.future(); 194 | pessimistic.addSuccessCallback(() -> future.follow(optimistic)); 195 | return future; 196 | } 197 | 198 | // Private 199 | 200 | private RedFuture validate() { 201 | if (listenableFutures.size() == 0) { 202 | return RedFuture.resolved(); 203 | } 204 | if (listenableFutures.size() == 1) { 205 | OpenRedFuture future = RedFuture.future(); 206 | future.follow(listenableFutures.get(0)); 207 | return future; 208 | } 209 | return null; 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/future/RedFutureOf.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.future; 2 | 3 | import com.google.common.util.concurrent.ListenableFuture; 4 | import io.github.avivcarmis.javared.future.callbacks.Callback; 5 | import io.github.avivcarmis.javared.future.callbacks.EmptyCallback; 6 | 7 | import java.util.concurrent.*; 8 | 9 | /** 10 | * A simple {@link java.util.concurrent.Future} like interface, 11 | * which supports trivial, fully asynchronous, Java 8 callbacks. 12 | * 13 | * As opposed to {@link RedFuture}, this interface represents a future that is pending value, 14 | * and will be resolved with a given value of the future type. 15 | * 16 | * @param type of the future value 17 | */ 18 | public interface RedFutureOf extends RedFuture, Future { 19 | 20 | /** 21 | * Attach a single parameter callback to be invoked when the future is successfully resolved, 22 | * with the parameter containing the resulted value of the future. 23 | * When a certain thread resolves the future, it will directly invoke the callback. 24 | * If the future is already resolved, the callback will be directly invoked. 25 | * 26 | * @param callback callback to be invoked when the future is successfully resolved 27 | * @return this, @see Fluent interface 28 | */ 29 | RedFutureOf addSuccessCallback(Callback callback); 30 | 31 | /** 32 | * Attach a single parameter callback to be invoked when the future is successfully resolved, 33 | * with the parameter containing the resulted value of the future. 34 | * When a certain thread resolves the future, it will queue the callback invocation to the 35 | * given executor. 36 | * If the future is already resolved, the callback will be immediately queued. 37 | * 38 | * @param executor executor to invoke the callback 39 | * @param callback callback to be invoked when the future is successfully resolved 40 | * @return this, @see Fluent interface 41 | */ 42 | RedFutureOf addSuccessCallback(Executor executor, Callback callback); 43 | 44 | /** 45 | * Test to see whether or not the future is currently completed, if it is successfully resolved, 46 | * the resulted value will be returned. 47 | * This method is not blocking, nor is it synchronizing different thread calls. 48 | * Note that a return value of null can either indicate that the future is not yet resolved, 49 | * or that it's has resolved with a null value. To test whether it is resolved or not, 50 | * one can call the inherited {@link RedFuture#isDone()} 51 | * @return the resulted value if the future is successfully resolved, null otherwise 52 | */ 53 | T tryGet(); 54 | 55 | /** 56 | * Tries to blocks the thread until the future is completed. 57 | * If the future is already complete, the method returns immediately. 58 | * 59 | * @return the resulted value of the future 60 | * @throws ExecutionException if the computation threw an exception 61 | * @throws InterruptedException if the current thread was interrupted while waiting 62 | * @see Future#get() 63 | */ 64 | T waitAndGet() throws ExecutionException, InterruptedException; 65 | 66 | /** 67 | * Tries to blocks the thread for at most the given timeout until the future is completed. 68 | * If the future is already complete, the method returns immediately. 69 | * 70 | * @param timeout the maximum time to wait 71 | * @param unit the time unit of the timeout argument 72 | * @return the resulted value of the future 73 | * @throws ExecutionException if the computation threw an exception 74 | * @throws InterruptedException if the current thread was interrupted while waiting 75 | * @throws TimeoutException if the wait timed out 76 | * @see Future#get() 77 | */ 78 | T waitAndGet(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException; 79 | 80 | // Overrides to update return types 81 | 82 | @Override 83 | RedFutureOf addSuccessCallback(EmptyCallback callback); 84 | 85 | @Override 86 | RedFutureOf addSuccessCallback(Executor executor, EmptyCallback callback); 87 | 88 | @Override 89 | RedFutureOf addFailureCallback(Callback callback); 90 | 91 | @Override 92 | RedFutureOf addFailureCallback(Executor executor, Callback callback); 93 | 94 | @Override 95 | RedFutureOf addFinallyCallback(EmptyCallback callback); 96 | 97 | @Override 98 | RedFutureOf addFinallyCallback(Executor executor, EmptyCallback callback); 99 | 100 | @Override 101 | ListenableFuture getListenableFuture(); 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/future/callbacks/Callback.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.future.callbacks; 2 | 3 | /** 4 | * A simple callback interface for receiving a typed value. 5 | * @param the type of the callback parameter 6 | */ 7 | public interface Callback { 8 | 9 | /** 10 | * Implementation of the callback 11 | * @param t the resulted callback parameter 12 | */ 13 | void call(T t); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/future/callbacks/EmptyCallback.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.future.callbacks; 2 | 3 | /** 4 | * A simple callback interface with no parameters. 5 | */ 6 | public interface EmptyCallback { 7 | 8 | /** 9 | * Implementation of the callback 10 | */ 11 | void call(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/test/RedTestContext.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.test; 2 | 3 | import io.github.avivcarmis.javared.future.OpenRedFuture; 4 | import io.github.avivcarmis.javared.future.RedFuture; 5 | import io.github.avivcarmis.javared.future.RedFutureHub; 6 | import io.github.avivcarmis.javared.future.callbacks.EmptyCallback; 7 | import org.junit.Assert; 8 | 9 | import java.util.concurrent.Executors; 10 | import java.util.concurrent.ScheduledExecutorService; 11 | import java.util.concurrent.TimeUnit; 12 | import java.util.concurrent.atomic.AtomicReference; 13 | 14 | /** 15 | * The context of an asynchronous test. 16 | * The test context is responsible for: 17 | *
    18 | *
  • Forking the test, allowing the flow test method to return 19 | * without finishing the test, to enable waiting for async operations 20 | * to finish. Each fork represents a single async operation that must 21 | * be marked as finished or failed for the test to end.
  • 22 | *
  • Assertions - since assertions may be performed on callbacks 23 | * of any async operation, on any thread, assertion errors will be 24 | * thrown, be JUnit won't be there to catch them. For that end, 25 | * the {@link RedTestContext} instance exposes all of JUnit {@link Assert} 26 | * interface, so that assertions can be made from any context, and still 27 | * influence test results.
  • 28 | *
  • Late scheduling - since scheduling late tasks and assertions 29 | * is a common async test practice, the context instance introduces 30 | * a simple utility scheduling method available at 31 | * {@link #scheduleTask(long, TimeUnit, Runnable)}.
  • 32 | *
  • Timing assertions - allowing simple and intuitive interface 33 | * to validate whether certain periods of times passed or didn't pass 34 | * from various points of the execution
  • 35 | *
36 | * 37 | * Note that all the context forks must be made before the test method returns. 38 | * See more at {@link #fork()} 39 | */ 40 | @SuppressWarnings("SameParameterValue") 41 | public class RedTestContext { 42 | 43 | // Constants 44 | 45 | /** 46 | * Scheduler service for scheduling late callbacks 47 | */ 48 | private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1); 49 | 50 | // Fields 51 | 52 | /** 53 | * The context assertion interface 54 | */ 55 | public final Assertions assertions; 56 | 57 | /** 58 | * The thread executing the test 59 | * When the test method returns, the executing thread sleeps until all 60 | * forks are finished. A reference to the thread is required for interrupting 61 | * it in case an assertion error is thrown, to prevent it from keep sleeping. 62 | */ 63 | private final Thread _executingThread; 64 | 65 | /** 66 | * A {@link RedFutureHub} for managing the test forks 67 | * Each fork has an underlying future, managed by the hub. 68 | */ 69 | private final RedFutureHub _hub; 70 | 71 | /** 72 | * Holds the instance of the first throwable thrown anywhere on the test context 73 | */ 74 | private final AtomicReference _firstFailure; 75 | 76 | /** 77 | * Whether or not the test is still active. 78 | */ 79 | private boolean _testActive; 80 | 81 | // Constructors 82 | 83 | RedTestContext(Thread executingThread) { 84 | assertions = new Assertions(); 85 | _executingThread = executingThread; 86 | _hub = RedFuture.hub(); 87 | _firstFailure = new AtomicReference<>(null); 88 | _testActive = true; 89 | } 90 | 91 | // Public 92 | 93 | /** 94 | * Forks the test, and returns the fork instance. 95 | * The returned instance must be held and completed, for the test to successful complete. 96 | * 97 | * NOTE that all the context forks must be made before the test method returns. 98 | * If, for example, we want to perform an async DB call, and when it's done, and only then, 99 | * we want to perform another one. Then in such a case we want to fork the context twice 100 | * at the beginning of the test method (or any other time before it returns), and NOT 101 | * to perform the second fork after the first one is completed. 102 | * 103 | * @return a fork instance 104 | */ 105 | public Fork fork() { 106 | return new Fork(_hub.provideFuture()); 107 | } 108 | 109 | /** 110 | * Fails the test. 111 | * 112 | * @param throwable cause 113 | */ 114 | public void fail(Throwable throwable) { 115 | handleFailure(throwable); 116 | } 117 | 118 | /** 119 | * Fails the test. 120 | * 121 | * @param message reason 122 | */ 123 | public void fail(String message) { 124 | handleFailure(new RuntimeException(message)); 125 | } 126 | 127 | /** 128 | * Fails the test. 129 | */ 130 | public void fail() { 131 | handleFailure(new RuntimeException()); 132 | } 133 | 134 | /** 135 | * Schedules a late runnable to be executed. 136 | * 137 | * @param delay the delay of the execution to apply 138 | * @param unit the time unit the of given delay 139 | * @param runnable the runnable task to execute 140 | */ 141 | public void scheduleTask(long delay, TimeUnit unit, Runnable runnable) { 142 | SCHEDULER.schedule(runnable, delay, unit); 143 | } 144 | 145 | /** 146 | * Schedules a late runnable to be executed with a given delay of milliseconds. 147 | * 148 | * @param delayMillis the delay of the execution to apply in milliseconds 149 | * @param runnable the runnable task to execute 150 | */ 151 | public void scheduleTask(long delayMillis, Runnable runnable) { 152 | scheduleTask(delayMillis, TimeUnit.MILLISECONDS, runnable); 153 | } 154 | 155 | /** 156 | * A {@link RedTestContext} provides a method for validating timing executions. 157 | * A {@link TimingValidator} may be created at a certain point at time, and later, 158 | * this {@link TimingValidator} instance may be used to validate certain period of 159 | * time has passed or not passed since it's creation. 160 | * @return a new instance of {@link TimingValidator} 161 | */ 162 | public TimingValidator timingValidator() { 163 | return new TimingValidator(); 164 | } 165 | 166 | /** 167 | * In some cases, in a late callback of an async operation, 168 | * it may be reasonable to validate whether or not the test is still active, or completed. 169 | * For example, you can create a fork, and then schedule to complete it in 10ms, and schedule 170 | * another callback to run in 100ms. The second callback will get executed, 171 | * but the test will already be finished at this point. 172 | * 173 | * @return whether or not the test is still pending results 174 | */ 175 | public boolean isTestActive() { 176 | return _testActive; 177 | } 178 | 179 | // Private 180 | 181 | /** 182 | * Marks the test as completed. 183 | */ 184 | void close() { 185 | _testActive = false; 186 | } 187 | 188 | /** 189 | * @return an optimistic union of all the forks futures. 190 | */ 191 | RedFuture unite() { 192 | return _hub.uniteOptimistically(); 193 | } 194 | 195 | /** 196 | * @throws Throwable any failure of the context that hasn't been thrown on the executing thread. 197 | */ 198 | void checkAssertions() throws Throwable { 199 | if (_firstFailure.get() != null) { 200 | throw _firstFailure.get(); 201 | } 202 | } 203 | 204 | /** 205 | * Executes a given callback and interrupts the executing thread 206 | * if a throwable is thrown during execution. 207 | * @param callback callback to execute 208 | */ 209 | private void assertion(EmptyCallback callback) { 210 | try { 211 | callback.call(); 212 | } catch (Throwable t) { 213 | handleFailure(t); 214 | throw t; 215 | } 216 | } 217 | 218 | /** 219 | * Marks the given throwable as the cause of failure for the test, 220 | * in case no prior throwable has taken place. 221 | * Then, interrupts the executing thread to prevent it from keep sleeping. 222 | * @param t throwable to handle 223 | */ 224 | private void handleFailure(Throwable t) { 225 | _firstFailure.compareAndSet(null, t); 226 | _executingThread.interrupt(); 227 | } 228 | 229 | /** 230 | * A fork of a {@link RedTestContext} 231 | * 232 | * see {@link RedTestContext#fork()} 233 | */ 234 | public static class Fork { 235 | 236 | // Fields 237 | 238 | /** 239 | * The underlying future to resolve or fail 240 | */ 241 | private final OpenRedFuture _future; 242 | 243 | // Constructors 244 | 245 | private Fork(OpenRedFuture future) { 246 | _future = future; 247 | } 248 | 249 | // Public 250 | 251 | /** 252 | * Mark the fork as completed, allowing the test to successfully complete 253 | */ 254 | public void complete() { 255 | _future.resolve(); 256 | } 257 | 258 | /** 259 | * Fails the entire test, calling this method is logically identical to 260 | * calling {@link RedTestContext#fail(Throwable)} 261 | * @param throwable cause of failure 262 | */ 263 | public void fail(Throwable throwable) { 264 | _future.fail(throwable); 265 | } 266 | 267 | /** 268 | * Fails the entire test, calling this method is logically identical to 269 | * calling {@link RedTestContext#fail(String)} 270 | * @param message reason of failure 271 | */ 272 | public void fail(String message) { 273 | _future.fail(new RuntimeException(message)); 274 | } 275 | 276 | /** 277 | * Fails the entire test, calling this method is logically identical to 278 | * calling {@link RedTestContext#fail()} 279 | */ 280 | public void fail() { 281 | _future.fail(new RuntimeException()); 282 | } 283 | 284 | } 285 | 286 | /** 287 | * A Timing tester for a certain {@link RedTestContext} 288 | * May be used to validate expected periods of time to pass or not pass, 289 | * from the moment of it's creation. 290 | * 291 | * see {@link RedTestContext#timingValidator()} 292 | */ 293 | public class TimingValidator { 294 | 295 | // Fields 296 | 297 | /** 298 | * The system time in nanoseconds at the moment of creation 299 | */ 300 | private final long _startTimeNano; 301 | 302 | // Constructors 303 | 304 | private TimingValidator() { 305 | _startTimeNano = System.nanoTime(); 306 | } 307 | 308 | // Public 309 | 310 | /** 311 | * Validates whether or not the given period of time has passed since 312 | * the creation of the {@link TimingValidator} instance. 313 | * 314 | * If the time has not passed, the test will automatically fail with 315 | * {@link NotPassedException} 316 | * 317 | * @param time period of time expected to have passed 318 | * @param unit time unit to use 319 | */ 320 | public void validatePassed(long time, TimeUnit unit) { 321 | if (timePassedNanos() < unit.toNanos(time)) { 322 | fail(new NotPassedException(time, unit)); 323 | } 324 | } 325 | 326 | /** 327 | * Validates whether or not the given period of time in milliseconds 328 | * has passed since the creation of the {@link TimingValidator} instance. 329 | * 330 | * If the time has not passed, the test will automatically fail with 331 | * {@link NotPassedException} 332 | * 333 | * @param timeMillis milliseconds expected to have passed 334 | */ 335 | public void validatePassed(long timeMillis) { 336 | validatePassed(timeMillis, TimeUnit.MILLISECONDS); 337 | } 338 | 339 | /** 340 | * Validates whether or not the given period of time has not passed 341 | * since the creation of the {@link TimingValidator} instance. 342 | * 343 | * If the time has not passed, the test will automatically fail with 344 | * {@link PassedException} 345 | * 346 | * @param time period of time expected to not have passed 347 | * @param unit time unit to use 348 | */ 349 | public void validateNotPassed(long time, TimeUnit unit) { 350 | if (timePassedNanos() >= unit.toNanos(time)) { 351 | fail(new PassedException(time, unit)); 352 | } 353 | } 354 | 355 | /** 356 | * Validates whether or not the given period of time in milliseconds 357 | * has not passed since the creation of the {@link TimingValidator} instance. 358 | * 359 | * If the time has not passed, the test will automatically fail with 360 | * {@link PassedException} 361 | * 362 | * @param timeMillis milliseconds expected to not have passed 363 | */ 364 | public void validateNotPassed(long timeMillis) { 365 | validateNotPassed(timeMillis, TimeUnit.MILLISECONDS); 366 | } 367 | 368 | // Private 369 | 370 | /** 371 | * @return time in nanoseconds passed since creation 372 | */ 373 | private long timePassedNanos() { 374 | return System.nanoTime() - _startTimeNano; 375 | } 376 | 377 | /** 378 | * An exception thrown in case a {@link TimingValidator} not passed validation has failed 379 | */ 380 | public class PassedException extends Exception { 381 | 382 | private PassedException(long expected, TimeUnit unit) { 383 | super(String.format("expected %d %s not to pass, but %d %s passed", 384 | expected, unit.name(), unit.convert(timePassedNanos(), TimeUnit.NANOSECONDS), unit.name())); 385 | } 386 | 387 | } 388 | 389 | /** 390 | * An exception thrown in case a {@link TimingValidator} passed validation has failed 391 | */ 392 | public class NotPassedException extends Exception { 393 | 394 | private NotPassedException(long expected, TimeUnit unit) { 395 | super(String.format("expected %d %s to pass, but only %d %s passed", 396 | expected, unit.name(), unit.convert(timePassedNanos(), TimeUnit.NANOSECONDS), unit.name())); 397 | } 398 | 399 | } 400 | 401 | } 402 | 403 | /** 404 | * Exposes JUnit {@link Assert} interface, 405 | * wraps each assertion method with a call to {@link #assertion(EmptyCallback)} 406 | */ 407 | public class Assertions { 408 | 409 | public void assertTrue(String message, boolean condition) { 410 | assertion(() -> Assert.assertTrue(message, condition)); 411 | } 412 | 413 | public void assertTrue(boolean condition) { 414 | assertion(() -> Assert.assertTrue(condition)); 415 | } 416 | 417 | public void assertFalse(String message, boolean condition) { 418 | assertion(() -> Assert.assertFalse(message, condition)); 419 | } 420 | 421 | public void assertFalse(boolean condition) { 422 | assertion(() -> Assert.assertFalse(condition)); 423 | } 424 | 425 | public void assertEquals(String message, Object expected, Object actual) { 426 | assertion(() -> Assert.assertEquals(message, expected, actual)); 427 | } 428 | 429 | public void assertEquals(Object expected, Object actual) { 430 | assertion(() -> Assert.assertEquals(expected, actual)); 431 | } 432 | 433 | public void assertNotEquals(String message, Object unexpected, Object actual) { 434 | assertion(() -> Assert.assertNotEquals(message, unexpected, actual)); 435 | } 436 | 437 | public void assertNotEquals(Object unexpected, Object actual) { 438 | assertion(() -> Assert.assertNotEquals(unexpected, actual)); 439 | } 440 | 441 | public void assertNotEquals(String message, long unexpected, long actual) { 442 | assertion(() -> Assert.assertNotEquals(message, unexpected, actual)); 443 | } 444 | 445 | public void assertNotEquals(long unexpected, long actual) { 446 | assertion(() -> Assert.assertNotEquals(unexpected, actual)); 447 | } 448 | 449 | public void assertNotEquals(String message, double unexpected, double actual, double delta) { 450 | assertion(() -> Assert.assertNotEquals(message, unexpected, actual, delta)); 451 | } 452 | 453 | public void assertNotEquals(double unexpected, double actual, double delta) { 454 | assertion(() -> Assert.assertNotEquals(unexpected, actual, delta)); 455 | } 456 | 457 | public void assertNotEquals(float unexpected, float actual, float delta) { 458 | assertion(() -> Assert.assertNotEquals(unexpected, actual, delta)); 459 | } 460 | 461 | public void assertArrayEquals(String message, Object[] expected, Object[] actual) { 462 | assertion(() -> Assert.assertArrayEquals(message, expected, actual)); 463 | } 464 | 465 | public void assertArrayEquals(Object[] expected, Object[] actual) { 466 | assertion(() -> Assert.assertArrayEquals(expected, actual)); 467 | } 468 | 469 | public void assertArrayEquals(String message, boolean[] expected, boolean[] actual) { 470 | assertion(() -> Assert.assertArrayEquals(message, expected, actual)); 471 | } 472 | 473 | public void assertArrayEquals(boolean[] expected, boolean[] actual) { 474 | assertion(() -> Assert.assertArrayEquals(expected, actual)); 475 | } 476 | 477 | public void assertArrayEquals(String message, byte[] expected, byte[] actual) { 478 | assertion(() -> Assert.assertArrayEquals(message, expected, actual)); 479 | } 480 | 481 | public void assertArrayEquals(byte[] expected, byte[] actual) { 482 | assertion(() -> Assert.assertArrayEquals(expected, actual)); 483 | } 484 | 485 | public void assertArrayEquals(String message, char[] expected, char[] actual) { 486 | assertion(() -> Assert.assertArrayEquals(message, expected, actual)); 487 | } 488 | 489 | public void assertArrayEquals(char[] expected, char[] actual) { 490 | assertion(() -> Assert.assertArrayEquals(expected, actual)); 491 | } 492 | 493 | public void assertArrayEquals(String message, short[] expected, short[] actual) { 494 | assertion(() -> Assert.assertArrayEquals(message, expected, actual)); 495 | } 496 | 497 | public void assertArrayEquals(short[] expected, short[] actual) { 498 | assertion(() -> Assert.assertArrayEquals(expected, actual)); 499 | } 500 | 501 | public void assertArrayEquals(String message, int[] expected, int[] actual) { 502 | assertion(() -> Assert.assertArrayEquals(message, expected, actual)); 503 | } 504 | 505 | public void assertArrayEquals(int[] expected, int[] actual) { 506 | assertion(() -> Assert.assertArrayEquals(expected, actual)); 507 | } 508 | 509 | public void assertArrayEquals(String message, long[] expected, long[] actual) { 510 | assertion(() -> Assert.assertArrayEquals(message, expected, actual)); 511 | } 512 | 513 | public void assertArrayEquals(long[] expected, long[] actual) { 514 | assertion(() -> Assert.assertArrayEquals(expected, actual)); 515 | } 516 | 517 | public void assertArrayEquals(String message, double[] expected, double[] actual, double delta) { 518 | assertion(() -> Assert.assertArrayEquals(message, expected, actual, delta)); 519 | } 520 | 521 | public void assertArrayEquals(double[] expected, double[] actual, double delta) { 522 | assertion(() -> Assert.assertArrayEquals(expected, actual, delta)); 523 | } 524 | 525 | public void assertArrayEquals(String message, float[] expected, float[] actual, float delta) { 526 | assertion(() -> Assert.assertArrayEquals(message, expected, actual, delta)); 527 | } 528 | 529 | public void assertArrayEquals(float[] expected, float[] actual, float delta) { 530 | assertion(() -> Assert.assertArrayEquals(expected, actual, delta)); 531 | } 532 | 533 | public void assertEquals(String message, double expected, double actual, double delta) { 534 | assertion(() -> Assert.assertEquals(message, expected, actual, delta)); 535 | } 536 | 537 | public void assertEquals(String message, float expected, float actual, float delta) { 538 | assertion(() -> Assert.assertEquals(message, expected, actual, delta)); 539 | } 540 | 541 | public void assertNotEquals(String message, float unexpected, float actual, float delta) { 542 | assertion(() -> Assert.assertNotEquals(message, unexpected, actual, delta)); 543 | } 544 | 545 | public void assertEquals(long expected, long actual) { 546 | assertion(() -> Assert.assertEquals(expected, actual)); 547 | } 548 | 549 | public void assertEquals(String message, long expected, long actual) { 550 | assertion(() -> Assert.assertEquals(message, expected, actual)); 551 | } 552 | 553 | public void assertEquals(double expected, double actual, double delta) { 554 | assertion(() -> Assert.assertEquals(expected, actual, delta)); 555 | } 556 | 557 | public void assertEquals(float expected, float actual, float delta) { 558 | assertion(() -> Assert.assertEquals(expected, actual, delta)); 559 | } 560 | 561 | public void assertNotNull(String message, Object object) { 562 | assertion(() -> Assert.assertNotNull(message, object)); 563 | } 564 | 565 | public void assertNotNull(Object object) { 566 | assertion(() -> Assert.assertNotNull(object)); 567 | } 568 | 569 | public void assertNull(String message, Object object) { 570 | assertion(() -> Assert.assertNull(message, object)); 571 | } 572 | 573 | public void assertNull(Object object) { 574 | assertion(() -> Assert.assertNull(object)); 575 | } 576 | 577 | public void assertSame(String message, Object expected, Object actual) { 578 | assertion(() -> Assert.assertSame(message, expected, actual)); 579 | } 580 | 581 | public void assertSame(Object expected, Object actual) { 582 | assertion(() -> Assert.assertSame(expected, actual)); 583 | } 584 | 585 | public void assertNotSame(String message, Object unexpected, Object actual) { 586 | assertion(() -> Assert.assertNotSame(message, unexpected, actual)); 587 | } 588 | 589 | public void assertNotSame(Object unexpected, Object actual) { 590 | assertion(() -> Assert.assertNotSame(unexpected, actual)); 591 | } 592 | 593 | } 594 | 595 | } 596 | -------------------------------------------------------------------------------- /src/main/java/io/github/avivcarmis/javared/test/RedTestRunner.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared.test; 2 | 3 | import org.junit.Test; 4 | import org.junit.internal.runners.statements.InvokeMethod; 5 | import org.junit.runners.BlockJUnit4ClassRunner; 6 | import org.junit.runners.model.FrameworkMethod; 7 | import org.junit.runners.model.InitializationError; 8 | import org.junit.runners.model.Statement; 9 | 10 | import java.util.List; 11 | import java.util.concurrent.ExecutionException; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.TimeoutException; 14 | 15 | /** 16 | * Overrides {@link BlockJUnit4ClassRunner} behaviour, so that when a {@link Test} 17 | * method is called, a single {@link RedTestContext} parameter is allowed. 18 | * 19 | * If a {@link RedTestContext} parameter does not exist, the original {@link BlockJUnit4ClassRunner} 20 | * behaviour is invoked. 21 | * 22 | * If a {@link RedTestContext} parameter exists, a {@link RedTestContext} instance is created 23 | * prior to the test, then the test method is invoked with the new context instance. 24 | * Once the test method returns, the runner waits for all the context forks to successfully 25 | * finish (see {@link RedTestContext#fork()}), and eventually it looks for un-thrown assertion errors. 26 | */ 27 | public class RedTestRunner extends BlockJUnit4ClassRunner { 28 | 29 | // Constructors 30 | 31 | public RedTestRunner(Class aClass) throws InitializationError { 32 | super(aClass); 33 | } 34 | 35 | // private 36 | 37 | @Override 38 | protected Statement methodInvoker(FrameworkMethod method, Object test) { 39 | return new AsyncInvokeMethod(method, test); 40 | } 41 | 42 | @Override 43 | protected void validateTestMethods(List errors) { 44 | List methods = getTestClass().getAnnotatedMethods(Test.class); 45 | for (FrameworkMethod method : methods) { 46 | method.validatePublicVoid(false, errors); 47 | if (!isNormalTest(method) && !isAsyncTest(method)) { 48 | errors.add(new Exception("Method " + method.getName() + " can either have no parameters, or" + 49 | "one AsyncTest parameter")); 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * @return true if the method has no parameters, being a simple JUnit test, false otherwise 56 | */ 57 | private static boolean isNormalTest(FrameworkMethod method) { 58 | return method.getMethod().getParameterCount() == 0; 59 | } 60 | 61 | /** 62 | * @return true if the method has one {@link RedTestContext} parameter, being an async test, 63 | * false otherwise. 64 | */ 65 | private static boolean isAsyncTest(FrameworkMethod method) { 66 | Class[] types = method.getMethod().getParameterTypes(); 67 | return types.length == 1 && types[0] == RedTestContext.class; 68 | } 69 | 70 | /** 71 | * Overrides the JUnit method invoker to implement async behaviour 72 | */ 73 | private static class AsyncInvokeMethod extends InvokeMethod { 74 | 75 | // Fields 76 | 77 | private final FrameworkMethod _testMethod; 78 | 79 | private final Object _target; 80 | 81 | // Constructors 82 | 83 | private AsyncInvokeMethod(FrameworkMethod testMethod, Object target) { 84 | super(testMethod, target); 85 | this._testMethod = testMethod; 86 | this._target = target; 87 | } 88 | 89 | // Public 90 | 91 | @Override 92 | public void evaluate() throws Throwable { 93 | if (isNormalTest(_testMethod)) { 94 | super.evaluate(); 95 | } 96 | else { 97 | RedTestContext testContext = new RedTestContext(Thread.currentThread()); 98 | long testTimeout = 60000; 99 | Test testAnnotation = _testMethod.getAnnotation(Test.class); 100 | if (testAnnotation != null) { 101 | if (testAnnotation.timeout() > 0) { 102 | testTimeout = testAnnotation.timeout(); 103 | } 104 | } 105 | _testMethod.invokeExplosively(_target, testContext); 106 | try { 107 | testContext.unite().waitForCompletion(testTimeout, TimeUnit.MILLISECONDS); 108 | } catch (TimeoutException e) { 109 | throw new AssertionError("test took more than " + testTimeout + "ms to complete"); 110 | } catch (ExecutionException e) { 111 | throw e.getCause(); 112 | } catch (InterruptedException ignored) {} 113 | testContext.checkAssertions(); 114 | testContext.close(); 115 | } 116 | } 117 | 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/io/github/avivcarmis/javared/TestRedTest.java: -------------------------------------------------------------------------------- 1 | package io.github.avivcarmis.javared; 2 | 3 | import io.github.avivcarmis.javared.future.callbacks.EmptyCallback; 4 | import io.github.avivcarmis.javared.test.RedTestContext; 5 | import io.github.avivcarmis.javared.test.RedTestRunner; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | 13 | /** 14 | * Test the functionality of the Async Test module. 15 | * Tests the {@link RedTestRunner} to validate correct flow control and handling of failures. 16 | * Tests the {@link RedTestContext} to validate forking, scheduling and assertions. 17 | * Tests the {@link RedTestContext.Fork} to validate completion and failing of tests. 18 | */ 19 | @RunWith(RedTestRunner.class) 20 | public class TestRedTest { 21 | 22 | /** 23 | * Validates execution and success of regular Junit test 24 | * under the {@link RedTestRunner} runner 25 | */ 26 | @Test 27 | public void testSimpleSuccess() {} 28 | 29 | /** 30 | * Validates execution and failing of regular Junit test 31 | * under the {@link RedTestRunner} runner 32 | */ 33 | @Test(expected = TestException.class) 34 | public void testSimpleException() { 35 | throw new TestException(); 36 | } 37 | 38 | /** 39 | * Validates execution and success of async test 40 | */ 41 | @Test 42 | public void testAsyncSuccess(@SuppressWarnings("unused") RedTestContext redTestContext) {} 43 | 44 | /** 45 | * Validates execution and failing of async test 46 | */ 47 | @Test(expected = TestException.class) 48 | public void testAsyncException(@SuppressWarnings("unused") RedTestContext redTestContext) { 49 | throw new TestException(); 50 | } 51 | 52 | // Scheduler 53 | 54 | /** 55 | * Tests {@link RedTestContext} late tasks scheduling with explicit time unit 56 | */ 57 | @Test(timeout = 300) 58 | public void testSchedulerNanos(RedTestContext redTestContext) throws InterruptedException { 59 | long time = System.nanoTime(); 60 | AtomicBoolean tooEarly = new AtomicBoolean(false); 61 | AtomicBoolean enterDelayedBlock = new AtomicBoolean(false); 62 | redTestContext.scheduleTask(100 * 1000 * 1000, TimeUnit.NANOSECONDS, () -> { 63 | tooEarly.set(System.nanoTime() - time < 100 * 1000 * 1000); 64 | enterDelayedBlock.set(true); 65 | }); 66 | Thread.sleep(150); 67 | Assert.assertFalse(tooEarly.get()); 68 | Assert.assertTrue(enterDelayedBlock.get()); 69 | } 70 | 71 | /** 72 | * Tests {@link RedTestContext} late tasks scheduling with default time unit 73 | */ 74 | @Test(timeout = 300) 75 | public void testSchedulerDefaultTimeUnit(RedTestContext redTestContext) throws InterruptedException { 76 | long time = System.currentTimeMillis(); 77 | AtomicBoolean tooEarly = new AtomicBoolean(false); 78 | AtomicBoolean enterDelayedBlock = new AtomicBoolean(false); 79 | redTestContext.scheduleTask(100, () -> { 80 | tooEarly.set(System.currentTimeMillis() - time < 100); 81 | enterDelayedBlock.set(true); 82 | }); 83 | Thread.sleep(150); 84 | Assert.assertFalse(tooEarly.get()); 85 | Assert.assertTrue(enterDelayedBlock.get()); 86 | } 87 | 88 | // Timing validator 89 | 90 | /** 91 | * Tests success of {@link RedTestContext.TimingValidator} 92 | */ 93 | @Test(timeout = 300) 94 | public void testTimingValidatorSuccess(RedTestContext redTestContext) throws InterruptedException { 95 | RedTestContext.TimingValidator validator = redTestContext.timingValidator(); 96 | validator.validateNotPassed(1000); 97 | validator.validateNotPassed(1000 * 1000, TimeUnit.MICROSECONDS); 98 | Thread.sleep(100); 99 | validator.validatePassed(50); 100 | validator.validatePassed(50 * 1000, TimeUnit.MICROSECONDS); 101 | } 102 | 103 | /** 104 | * Tests {@link RedTestContext.TimingValidator} not passed failure 105 | * with explicit time unit 106 | */ 107 | @Test(timeout = 300, expected = RedTestContext.TimingValidator.PassedException.class) 108 | public void testTimingValidatorNotPassedTimeUnit(RedTestContext redTestContext) throws InterruptedException { 109 | RedTestContext.TimingValidator validator = redTestContext.timingValidator(); 110 | Thread.sleep(100); 111 | validator.validateNotPassed(50 * 1000, TimeUnit.MICROSECONDS); 112 | } 113 | 114 | /** 115 | * Tests {@link RedTestContext.TimingValidator} not passed failure 116 | * with default time unit 117 | */ 118 | @Test(timeout = 300, expected = RedTestContext.TimingValidator.PassedException.class) 119 | public void testTimingValidatorNotPassed(RedTestContext redTestContext) throws InterruptedException { 120 | RedTestContext.TimingValidator validator = redTestContext.timingValidator(); 121 | Thread.sleep(100); 122 | validator.validateNotPassed(50); 123 | } 124 | 125 | /** 126 | * Tests {@link RedTestContext.TimingValidator} passed failure 127 | * with explicit time unit 128 | */ 129 | @Test(timeout = 300, expected = RedTestContext.TimingValidator.NotPassedException.class) 130 | public void testTimingValidatorPassedTimeUnit(RedTestContext redTestContext) throws InterruptedException { 131 | RedTestContext.TimingValidator validator = redTestContext.timingValidator(); 132 | validator.validatePassed(50 * 1000, TimeUnit.MICROSECONDS); 133 | } 134 | 135 | /** 136 | * Tests {@link RedTestContext.TimingValidator} passed failure 137 | * with default time unit 138 | */ 139 | @Test(timeout = 300, expected = RedTestContext.TimingValidator.NotPassedException.class) 140 | public void testTimingValidatorPassed(RedTestContext redTestContext) throws InterruptedException { 141 | RedTestContext.TimingValidator validator = redTestContext.timingValidator(); 142 | validator.validatePassed(50); 143 | } 144 | 145 | // Context fail 146 | 147 | /** 148 | * Tests the context direct failing through {@link RedTestContext#fail(Throwable)} 149 | */ 150 | @Test(expected = TestException.class) 151 | public void testDirectContextFail(RedTestContext redTestContext) { 152 | redTestContext.fail(new TestException()); 153 | } 154 | 155 | /** 156 | * Tests a forked task context failing through {@link RedTestContext#fail(Throwable)} 157 | */ 158 | @Test(expected = TestException.class) 159 | public void testForkedContextFail(RedTestContext redTestContext) { 160 | redTestContext.fork(); 161 | redTestContext.scheduleTask(100, TimeUnit.MILLISECONDS, () -> redTestContext.fail(new TestException())); 162 | } 163 | 164 | /** 165 | * Tests a forked task context failing through {@link RedTestContext#fail(String)} 166 | */ 167 | @Test(expected = RuntimeException.class) 168 | public void testForkedContextFailWithMessage(RedTestContext redTestContext) { 169 | redTestContext.fork(); 170 | redTestContext.scheduleTask(100, TimeUnit.MILLISECONDS, () -> redTestContext.fail("message")); 171 | } 172 | 173 | /** 174 | * Tests a forked task context failing through {@link RedTestContext#fail()} 175 | */ 176 | @Test(expected = RuntimeException.class) 177 | public void testForkedContextFailEmpty(RedTestContext redTestContext) { 178 | redTestContext.fork(); 179 | redTestContext.scheduleTask(100, TimeUnit.MILLISECONDS, redTestContext::fail); 180 | } 181 | 182 | // Single fork 183 | 184 | /** 185 | * Tests a forked task fork failing through {@link RedTestContext.Fork#fail(Throwable)} 186 | */ 187 | @Test(expected = TestException.class) 188 | public void testSingleFailingFork(RedTestContext redTestContext) { 189 | RedTestContext.Fork fork = redTestContext.fork(); 190 | redTestContext.scheduleTask(100, TimeUnit.MILLISECONDS, () -> fork.fail(new TestException())); 191 | } 192 | 193 | /** 194 | * Tests a success of a singly forked task who's fork has 195 | * successfully completed after some delay 196 | */ 197 | @Test 198 | public void testSingleSuccessfulFork(RedTestContext redTestContext) { 199 | RedTestContext.Fork fork = redTestContext.fork(); 200 | redTestContext.scheduleTask(100, TimeUnit.MILLISECONDS, () -> { 201 | if (!redTestContext.isTestActive()) { 202 | fork.fail("test is not active"); 203 | } 204 | else { 205 | fork.complete(); 206 | } 207 | }); 208 | } 209 | 210 | // Double fork 211 | 212 | /** 213 | * Tests a success of a multiple-forked task who's fork has 214 | * successfully completed after some delay 215 | */ 216 | @Test 217 | public void testDoubleSuccessfulFork(RedTestContext redTestContext) { 218 | RedTestContext.Fork fork1 = redTestContext.fork(); 219 | redTestContext.scheduleTask(50, TimeUnit.MILLISECONDS, () -> { 220 | if (!redTestContext.isTestActive()) { 221 | fork1.fail("test is not active on fork1"); 222 | } 223 | else { 224 | fork1.complete(); 225 | } 226 | }); 227 | RedTestContext.Fork fork2 = redTestContext.fork(); 228 | redTestContext.scheduleTask(100, TimeUnit.MILLISECONDS, () -> { 229 | if (!redTestContext.isTestActive()) { 230 | fork2.fail("test is not active on fork2"); 231 | } 232 | else { 233 | fork2.complete(); 234 | } 235 | }); 236 | } 237 | 238 | /** 239 | * Tests a failure of a multiple-forked task who's one fork has 240 | * successfully completed and the other failed 241 | */ 242 | @Test(expected = RuntimeException.class) 243 | public void testDoubleSuccessfulAndFailingFork(RedTestContext redTestContext) { 244 | RedTestContext.Fork fork1 = redTestContext.fork(); 245 | redTestContext.scheduleTask(50, TimeUnit.MILLISECONDS, () -> { 246 | if (!redTestContext.isTestActive()) { 247 | fork1.fail("test is not active on fork1"); 248 | } 249 | else { 250 | fork1.complete(); 251 | } 252 | }); 253 | RedTestContext.Fork fork2 = redTestContext.fork(); 254 | redTestContext.scheduleTask(100, TimeUnit.MILLISECONDS, () -> { 255 | if (!redTestContext.isTestActive()) { 256 | fork2.fail("test is not active on fork2"); 257 | } 258 | fork2.fail(); 259 | }); 260 | } 261 | 262 | /** 263 | * Tests a failure of a multiple-forked task who's one fork has 264 | * successfully completed and a nested fork is failing 265 | */ 266 | @Test 267 | public void testDoubleNestedFork(RedTestContext redTestContext) { 268 | RedTestContext.Fork fork1 = redTestContext.fork(); 269 | RedTestContext.Fork fork2 = redTestContext.fork(); 270 | redTestContext.scheduleTask(50, TimeUnit.MILLISECONDS, () -> { 271 | if (!redTestContext.isTestActive()) { 272 | fork1.fail("test is not active on fork1"); 273 | } 274 | else { 275 | fork1.complete(); 276 | redTestContext.scheduleTask(50, TimeUnit.MILLISECONDS, () -> { 277 | if (!redTestContext.isTestActive()) { 278 | fork2.fail("test is not active on fork2"); 279 | } 280 | else { 281 | fork2.complete(); 282 | 283 | } 284 | }); 285 | } 286 | }); 287 | } 288 | 289 | // Assertions 290 | 291 | /** 292 | * Tests all the assertion methods to validate no false negative error. 293 | */ 294 | @Test 295 | public void testPositiveAssertions(RedTestContext redTestContext) { 296 | redTestContext.assertions.assertArrayEquals("OBJECTS", OBJECTS1, OBJECTS2); 297 | redTestContext.assertions.assertArrayEquals(OBJECTS1, OBJECTS2); 298 | redTestContext.assertions.assertArrayEquals("BOOLEANS", BOOLEANS1, BOOLEANS2); 299 | redTestContext.assertions.assertArrayEquals(BOOLEANS1, BOOLEANS2); 300 | redTestContext.assertions.assertArrayEquals("BYTES", BYTES1, BYTES2); 301 | redTestContext.assertions.assertArrayEquals(BYTES1, BYTES2); 302 | redTestContext.assertions.assertArrayEquals("CHARS", CHARS1, CHARS2); 303 | redTestContext.assertions.assertArrayEquals(CHARS1, CHARS2); 304 | redTestContext.assertions.assertArrayEquals("SHORTS", SHORTS1, SHORTS2); 305 | redTestContext.assertions.assertArrayEquals(SHORTS1, SHORTS2); 306 | redTestContext.assertions.assertArrayEquals("INTS", INTS1, INTS2); 307 | redTestContext.assertions.assertArrayEquals(INTS1, INTS2); 308 | redTestContext.assertions.assertArrayEquals("LONGS", LONGS1, LONGS2); 309 | redTestContext.assertions.assertArrayEquals(LONGS1, LONGS2); 310 | redTestContext.assertions.assertArrayEquals("DOUBLES", DOUBLES1, DOUBLES2, 0); 311 | redTestContext.assertions.assertArrayEquals(DOUBLES1, DOUBLES2, 0); 312 | redTestContext.assertions.assertArrayEquals("FLOATS", FLOATS1, FLOATS2, 0); 313 | redTestContext.assertions.assertArrayEquals(FLOATS1, FLOATS2, 0); 314 | redTestContext.assertions.assertEquals("OBJECT", OBJECT1, OBJECT1); 315 | redTestContext.assertions.assertEquals(OBJECT1, OBJECT1); 316 | redTestContext.assertions.assertEquals("BOOLEAN", BOOLEAN2, BOOLEAN3); 317 | redTestContext.assertions.assertEquals(BOOLEAN2, BOOLEAN3); 318 | redTestContext.assertions.assertEquals("BYTE", BYTE2, BYTE3); 319 | redTestContext.assertions.assertEquals(BYTE2, BYTE3); 320 | redTestContext.assertions.assertEquals("CHAR", CHAR2, CHAR3); 321 | redTestContext.assertions.assertEquals(CHAR2, CHAR3); 322 | redTestContext.assertions.assertEquals("SHORT", SHORT2, SHORT3); 323 | redTestContext.assertions.assertEquals(SHORT2, SHORT3); 324 | redTestContext.assertions.assertEquals("INT", INT2, INT3); 325 | redTestContext.assertions.assertEquals(INT2, INT3); 326 | redTestContext.assertions.assertEquals("LONG", LONG2, LONG3); 327 | redTestContext.assertions.assertEquals(LONG2, LONG3); 328 | redTestContext.assertions.assertEquals("FLOAT", FLOAT2, FLOAT3); 329 | redTestContext.assertions.assertEquals(FLOAT2, FLOAT3); 330 | redTestContext.assertions.assertEquals("DOUBLE", DOUBLE2, DOUBLE3); 331 | redTestContext.assertions.assertEquals(DOUBLE2, DOUBLE3); 332 | redTestContext.assertions.assertEquals("FLOAT", FLOAT2, FLOAT3, 0); 333 | redTestContext.assertions.assertEquals(FLOAT2, FLOAT3, 0); 334 | redTestContext.assertions.assertEquals("DOUBLE", DOUBLE2, DOUBLE3, 0); 335 | redTestContext.assertions.assertEquals(DOUBLE2, DOUBLE3, 0); 336 | redTestContext.assertions.assertNotEquals("OBJECT", OBJECT1, OBJECT2); 337 | redTestContext.assertions.assertNotEquals(OBJECT1, OBJECT2); 338 | redTestContext.assertions.assertNotEquals("BOOLEAN", BOOLEAN1, BOOLEAN3); 339 | redTestContext.assertions.assertNotEquals(BOOLEAN1, BOOLEAN3); 340 | redTestContext.assertions.assertNotEquals("BYTE", BYTE1, BYTE3); 341 | redTestContext.assertions.assertNotEquals(BYTE1, BYTE3); 342 | redTestContext.assertions.assertNotEquals("CHAR", CHAR1, CHAR3); 343 | redTestContext.assertions.assertNotEquals(CHAR1, CHAR3); 344 | redTestContext.assertions.assertNotEquals("SHORT", SHORT1, SHORT3); 345 | redTestContext.assertions.assertNotEquals(SHORT1, SHORT3); 346 | redTestContext.assertions.assertNotEquals("INT", INT1, INT3); 347 | redTestContext.assertions.assertNotEquals(INT1, INT3); 348 | redTestContext.assertions.assertNotEquals("LONG", LONG1, LONG3); 349 | redTestContext.assertions.assertNotEquals(LONG1, LONG3); 350 | redTestContext.assertions.assertNotEquals("FLOAT", FLOAT1, FLOAT3); 351 | redTestContext.assertions.assertNotEquals(FLOAT1, FLOAT3); 352 | redTestContext.assertions.assertNotEquals("DOUBLE", DOUBLE1, DOUBLE3); 353 | redTestContext.assertions.assertNotEquals(DOUBLE1, DOUBLE3); 354 | redTestContext.assertions.assertNotEquals("FLOAT", FLOAT1, FLOAT3, 0); 355 | redTestContext.assertions.assertNotEquals(FLOAT1, FLOAT3, 0); 356 | redTestContext.assertions.assertNotEquals("DOUBLE", DOUBLE1, DOUBLE3, 0); 357 | redTestContext.assertions.assertNotEquals(DOUBLE1, DOUBLE3, 0); 358 | redTestContext.assertions.assertNull("null", null); 359 | redTestContext.assertions.assertNull(null); 360 | redTestContext.assertions.assertNotNull("not null", OBJECT1); 361 | redTestContext.assertions.assertNotNull(OBJECT1); 362 | redTestContext.assertions.assertSame("same", OBJECT1, OBJECT1); 363 | redTestContext.assertions.assertSame(OBJECT1, OBJECT1); 364 | redTestContext.assertions.assertNotSame("not same", OBJECT1, OBJECT2); 365 | redTestContext.assertions.assertNotSame(OBJECT1, OBJECT2); 366 | redTestContext.assertions.assertFalse(false); 367 | redTestContext.assertions.assertFalse("false", false); 368 | redTestContext.assertions.assertTrue(true); 369 | redTestContext.assertions.assertTrue("true", true); 370 | } 371 | 372 | /** 373 | * Tests all the assertion methods to validate no false positive error. 374 | */ 375 | @Test(expected = AssertionError.class) 376 | public void testNegativeAssertions(RedTestContext redTestContext) { 377 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals("OBJECTS", OBJECTS1, OBJECTS3)); 378 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals(OBJECTS1, OBJECTS3)); 379 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals("BOOLEANS", BOOLEANS1, BOOLEANS3)); 380 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals(BOOLEANS1, BOOLEANS3)); 381 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals("BYTES", BYTES1, BYTES3)); 382 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals(BYTES1, BYTES3)); 383 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals("CHARS", CHARS1, CHARS3)); 384 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals(CHARS1, CHARS3)); 385 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals("SHORTS", SHORTS1, SHORTS3)); 386 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals(SHORTS1, SHORTS3)); 387 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals("INTS", INTS1, INTS3)); 388 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals(INTS1, INTS3)); 389 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals("LONGS", LONGS1, LONGS3)); 390 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals(LONGS1, LONGS3)); 391 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals("DOUBLES", DOUBLES1, DOUBLES3, 0)); 392 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals(DOUBLES1, DOUBLES3, 0)); 393 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals("FLOATS", FLOATS1, FLOATS3, 0)); 394 | negativeAssert(() -> redTestContext.assertions.assertArrayEquals(FLOATS1, FLOATS3, 0)); 395 | negativeAssert(() -> redTestContext.assertions.assertEquals("OBJECT", OBJECT2, OBJECT1)); 396 | negativeAssert(() -> redTestContext.assertions.assertEquals(OBJECT2, OBJECT1)); 397 | negativeAssert(() -> redTestContext.assertions.assertEquals("BOOLEAN", BOOLEAN2, BOOLEAN1)); 398 | negativeAssert(() -> redTestContext.assertions.assertEquals(BOOLEAN2, BOOLEAN1)); 399 | negativeAssert(() -> redTestContext.assertions.assertEquals("BYTE", BYTE2, BYTE1)); 400 | negativeAssert(() -> redTestContext.assertions.assertEquals(BYTE2, BYTE1)); 401 | negativeAssert(() -> redTestContext.assertions.assertEquals("CHAR", CHAR2, CHAR1)); 402 | negativeAssert(() -> redTestContext.assertions.assertEquals(CHAR2, CHAR1)); 403 | negativeAssert(() -> redTestContext.assertions.assertEquals("SHORT", SHORT2, SHORT1)); 404 | negativeAssert(() -> redTestContext.assertions.assertEquals(SHORT2, SHORT1)); 405 | negativeAssert(() -> redTestContext.assertions.assertEquals("INT", INT2, INT1)); 406 | negativeAssert(() -> redTestContext.assertions.assertEquals(INT2, INT1)); 407 | negativeAssert(() -> redTestContext.assertions.assertEquals("LONG", LONG2, LONG1)); 408 | negativeAssert(() -> redTestContext.assertions.assertEquals(LONG2, LONG1)); 409 | negativeAssert(() -> redTestContext.assertions.assertEquals("FLOAT", FLOAT2, FLOAT1)); 410 | negativeAssert(() -> redTestContext.assertions.assertEquals(FLOAT2, FLOAT1)); 411 | negativeAssert(() -> redTestContext.assertions.assertEquals("DOUBLE", DOUBLE2, DOUBLE1)); 412 | negativeAssert(() -> redTestContext.assertions.assertEquals(DOUBLE2, DOUBLE1)); 413 | negativeAssert(() -> redTestContext.assertions.assertEquals("FLOAT", FLOAT2, FLOAT1, 0)); 414 | negativeAssert(() -> redTestContext.assertions.assertEquals(FLOAT2, FLOAT1, 0)); 415 | negativeAssert(() -> redTestContext.assertions.assertEquals("DOUBLE", DOUBLE2, DOUBLE1, 0)); 416 | negativeAssert(() -> redTestContext.assertions.assertEquals(DOUBLE2, DOUBLE1, 0)); 417 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("OBJECT", OBJECT2, OBJECT2)); 418 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(OBJECT2, OBJECT2)); 419 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("BOOLEAN", BOOLEAN2, BOOLEAN3)); 420 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(BOOLEAN2, BOOLEAN3)); 421 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("BYTE", BYTE2, BYTE3)); 422 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(BYTE2, BYTE3)); 423 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("CHAR", CHAR2, CHAR3)); 424 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(CHAR2, CHAR3)); 425 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("SHORT", SHORT2, SHORT3)); 426 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(SHORT2, SHORT3)); 427 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("INT", INT2, INT3)); 428 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(INT2, INT3)); 429 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("LONG", LONG2, LONG3)); 430 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(LONG2, LONG3)); 431 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("FLOAT", FLOAT2, FLOAT3)); 432 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(FLOAT2, FLOAT3)); 433 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("DOUBLE", DOUBLE2, DOUBLE3)); 434 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(DOUBLE2, DOUBLE3)); 435 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("FLOAT", FLOAT2, FLOAT3, 0)); 436 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(FLOAT2, FLOAT3, 0)); 437 | negativeAssert(() -> redTestContext.assertions.assertNotEquals("DOUBLE", DOUBLE2, DOUBLE3, 0)); 438 | negativeAssert(() -> redTestContext.assertions.assertNotEquals(DOUBLE2, DOUBLE3, 0)); 439 | negativeAssert(() -> redTestContext.assertions.assertNull("null", OBJECT1)); 440 | negativeAssert(() -> redTestContext.assertions.assertNull(OBJECT1)); 441 | negativeAssert(() -> redTestContext.assertions.assertNotNull("not null", null)); 442 | negativeAssert(() -> redTestContext.assertions.assertNotNull(null)); 443 | negativeAssert(() -> redTestContext.assertions.assertSame("same", OBJECT1, OBJECT2)); 444 | negativeAssert(() -> redTestContext.assertions.assertSame(OBJECT1, OBJECT2)); 445 | negativeAssert(() -> redTestContext.assertions.assertNotSame("not same", OBJECT1, OBJECT1)); 446 | negativeAssert(() -> redTestContext.assertions.assertNotSame(OBJECT1, OBJECT1)); 447 | negativeAssert(() -> redTestContext.assertions.assertFalse(true)); 448 | negativeAssert(() -> redTestContext.assertions.assertFalse("false", true)); 449 | negativeAssert(() -> redTestContext.assertions.assertTrue(false)); 450 | negativeAssert(() -> redTestContext.assertions.assertTrue("true", false)); 451 | } 452 | 453 | /** 454 | * Tests an assertion method which runs on a nested callback, 455 | * to validate no false negative error. 456 | */ 457 | @Test 458 | public void testForkedPositiveAssertion(RedTestContext redTestContext) { 459 | RedTestContext.Fork fork = redTestContext.fork(); 460 | redTestContext.scheduleTask(100, TimeUnit.MILLISECONDS, () -> { 461 | redTestContext.assertions.assertNull(null); 462 | fork.complete(); 463 | }); 464 | } 465 | 466 | /** 467 | * Tests an assertion method which runs on a nested callback, 468 | * to validate no false positive error. 469 | */ 470 | @Test(expected = AssertionError.class) 471 | public void testForkedNegativeAssertion(RedTestContext redTestContext) { 472 | redTestContext.fork(); 473 | redTestContext.scheduleTask(100, TimeUnit.MILLISECONDS, () -> 474 | redTestContext.assertions.assertNull(OBJECT1)); 475 | } 476 | 477 | // Utils 478 | 479 | /** 480 | * Receive a assertion callback to execute, 481 | * validates that the callback is throwing an {@link AssertionError} 482 | */ 483 | private void negativeAssert(EmptyCallback assertion) { 484 | try { 485 | assertion.call(); 486 | } catch (AssertionError e) { 487 | return; 488 | } 489 | throw new RuntimeException("assertion not thrown"); 490 | } 491 | 492 | public static class TestException extends RuntimeException {} 493 | 494 | private static final Object OBJECT1 = new Object(); 495 | private static final Object OBJECT2 = new Object(); 496 | private static final Object[] OBJECTS1 = new Object[] {OBJECT1}; 497 | private static final Object[] OBJECTS2 = new Object[] {OBJECT1}; 498 | private static final Object[] OBJECTS3 = new Object[] {OBJECT2}; 499 | 500 | private static final boolean BOOLEAN1 = false; 501 | private static final boolean BOOLEAN2 = true; 502 | private static final boolean BOOLEAN3 = true; 503 | private static final boolean[] BOOLEANS1 = new boolean[] {BOOLEAN1}; 504 | private static final boolean[] BOOLEANS2 = new boolean[] {BOOLEAN1}; 505 | private static final boolean[] BOOLEANS3 = new boolean[] {BOOLEAN2}; 506 | 507 | private static final byte BYTE1 = 'a'; 508 | private static final byte BYTE2 = 'b'; 509 | private static final byte BYTE3 = 'b'; 510 | private static final byte[] BYTES1 = new byte[] {BYTE1}; 511 | private static final byte[] BYTES2 = new byte[] {BYTE1}; 512 | private static final byte[] BYTES3 = new byte[] {BYTE2}; 513 | 514 | private static final char CHAR1 = 'a'; 515 | private static final char CHAR2 = 'b'; 516 | private static final char CHAR3 = 'b'; 517 | private static final char[] CHARS1 = new char[] {CHAR1}; 518 | private static final char[] CHARS2 = new char[] {CHAR1}; 519 | private static final char[] CHARS3 = new char[] {CHAR2}; 520 | 521 | private static final short SHORT1 = 'a'; 522 | private static final short SHORT2 = 'b'; 523 | private static final short SHORT3 = 'b'; 524 | private static final short[] SHORTS1 = new short[] {SHORT1}; 525 | private static final short[] SHORTS2 = new short[] {SHORT1}; 526 | private static final short[] SHORTS3 = new short[] {SHORT2}; 527 | 528 | private static final int INT1 = 'a'; 529 | private static final int INT2 = 'b'; 530 | private static final int INT3 = 'b'; 531 | private static final int[] INTS1 = new int[] {INT1}; 532 | private static final int[] INTS2 = new int[] {INT1}; 533 | private static final int[] INTS3 = new int[] {INT2}; 534 | 535 | private static final long LONG1 = 'a'; 536 | private static final long LONG2 = 'b'; 537 | private static final long LONG3 = 'b'; 538 | private static final long[] LONGS1 = new long[] {LONG1}; 539 | private static final long[] LONGS2 = new long[] {LONG1}; 540 | private static final long[] LONGS3 = new long[] {LONG2}; 541 | 542 | private static final double DOUBLE1 = 'a'; 543 | private static final double DOUBLE2 = 'b'; 544 | private static final double DOUBLE3 = 'b'; 545 | private static final double[] DOUBLES1 = new double[] {DOUBLE1}; 546 | private static final double[] DOUBLES2 = new double[] {DOUBLE1}; 547 | private static final double[] DOUBLES3 = new double[] {DOUBLE2}; 548 | 549 | private static final float FLOAT1 = 'a'; 550 | private static final float FLOAT2 = 'b'; 551 | private static final float FLOAT3 = 'b'; 552 | private static final float[] FLOATS1 = new float[] {FLOAT1}; 553 | private static final float[] FLOATS2 = new float[] {FLOAT1}; 554 | private static final float[] FLOATS3 = new float[] {FLOAT2}; 555 | 556 | } 557 | --------------------------------------------------------------------------------