Skip to content

Instantly share code, notes, and snippets.

@radekg
Last active April 13, 2017 21:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save radekg/53b5852bfaa7560817cc3a7acb0b9559 to your computer and use it in GitHub Desktop.
Save radekg/53b5852bfaa7560817cc3a7acb0b9559 to your computer and use it in GitHub Desktop.
Java AsyncUtil.eventually(...) tests
package com.gruchalski.testing;
import java.util.UUID;
import java.util.concurrent.*;
import static org.testng.Assert.fail;
public class AsyncUtil {
protected AsyncUtil() {}
private static ExecutorService executor = Executors.newFixedThreadPool(10);
private static ConcurrentHashMap<UUID, AssertionError> errorMap = new ConcurrentHashMap<>();
public static void eventually(Runnable r) {
eventually(r, 10000);
}
public static void eventually(Runnable r, long timeout) {
eventually(r, timeout, 100);
}
public static void eventually(Runnable r, long timeout, long interval) {
UUID execution = UUID.randomUUID();
long end = System.currentTimeMillis() + timeout;
long tries = 0;
long failed = 0;
boolean success = false;
AssertionError lastError = null;
while (System.currentTimeMillis() < end) {
tries++;
Future<Void> f = executor.submit(new Invocation(r, execution));
try {
f.get(timeout, TimeUnit.MILLISECONDS);
if (errorMap.containsKey(execution)) {
throw errorMap.get(execution);
}
success = true;
break;
} catch (InterruptedException ex) {
failed++;
try { Thread.sleep(interval); } catch (InterruptedException ex2) {}
} catch (ExecutionException ex) {
failed++;
try { Thread.sleep(interval); } catch (InterruptedException ex2) {}
} catch (TimeoutException ex) {
failed++;
try { Thread.sleep(interval); } catch (InterruptedException ex2) {}
} catch (AssertionError ex) {
failed++;
lastError = ex;
try { Thread.sleep(interval); } catch (InterruptedException ex2) {}
} finally {
errorMap.remove(execution);
}
}
if (!success) {
fail("Eventually executed " + tries + " times and failed " + failed + " times after " + timeout + " milliseconds. Underlying reason: " + lastError + ".");
}
}
private static class Invocation implements Callable<Void> {
private final Runnable task;
private final UUID execution;
public Invocation(Runnable r, UUID exec) {
this.task = r;
this.execution = exec;
}
public Void call() {
InvocationUncaughtExceptionHandler handler = new InvocationUncaughtExceptionHandler();
Thread t = new Thread(task);
t.setUncaughtExceptionHandler(handler);
t.start();
try { t.join(); } catch (InterruptedException ex) {}
if (handler.hadException()) {
errorMap.put(execution, handler.getException());
}
return null;
}
}
private static class InvocationUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private AssertionError ae;
@Override
public void uncaughtException(Thread t, Throwable e) {
if (e.getClass().isAssignableFrom(AssertionError.class)) {
ae = (AssertionError) e;
}
}
public boolean hadException() {
return ae != null;
}
public AssertionError getException() {
return ae;
}
}
}
package com.gruchalski.testing;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
public class AsyncUtilTests {
@Test
public void testSuccess() {
AsyncUtil.eventually(() -> {
assertEquals(true, true);
});
}
@Test(expectedExceptions = AssertionError.class)
public void testFailure() {
AsyncUtil.eventually(() -> {
assertEquals(false, true);
}, 3000);
}
}
@radekg
Copy link
Author

radekg commented Sep 28, 2016

This comes with an obligatory it works on my computer note.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment