Skip to content
This repository was archived by the owner on Jan 31, 2022. It is now read-only.

Commit 7d1ae3f

Browse files
author
Clément Le Provost
committed
[test][refact] Improve AssertCompletionHandler
- Check that each completion handler is called. - Display all found errors. - Warn if some handlers have not been checked when the process exits. - Inner handlers are no longer needed. - We don’t need a nested handler.
1 parent 6bf4320 commit 7d1ae3f

File tree

1 file changed

+47
-21
lines changed

1 file changed

+47
-21
lines changed

algoliasearch/src/test/java/com/algolia/search/saas/AssertCompletionHandler.java

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,67 @@
55
import java.util.ArrayList;
66
import java.util.List;
77

8+
import static org.junit.Assert.fail;
9+
810
/**
911
* This class helps coping with assertions in callbacks.
1012
* As AssertionErrors are thrown silently when an assertion fails in a callback,
1113
* this class wraps a CompletionHandler to check if an assert failed and expose the error through {@link AssertCompletionHandler#checkAssertions()}.
1214
*/
1315
abstract class AssertCompletionHandler implements CompletionHandler {
1416
private AssertionError error;
15-
private final CompletionHandler handler;
16-
private final List<AssertCompletionHandler> innerHandlers = new ArrayList<>();
17+
private int invocationCount = 0;
18+
private StackTraceElement[] creationStackTrace;
1719

1820
/**
1921
* Global registry of all created completion handlers. It is cleared when the static `checkAllHandlers()` method is
2022
* called.
2123
*/
2224
private static List<AssertCompletionHandler> allHandlers = new ArrayList<>();
2325

24-
public AssertCompletionHandler() {
25-
this.handler = new CompletionHandler() {
26-
@Override public void requestCompleted(JSONObject content, AlgoliaException error) {
27-
doRequestCompleted(content, error);
26+
// Schedule to check all handlers at JVM shutdown.
27+
// NOTE: This is a safety net in case the programmer forgets to check the handlers manually (hence the warning).
28+
static {
29+
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
30+
@Override
31+
public void run() {
32+
if (!allHandlers.isEmpty()) {
33+
System.err.println("WARNING: Some handlers have not been checked! Doing it now.");
34+
checkAllHandlers();
35+
}
2836
}
29-
};
37+
}));
38+
}
39+
40+
public AssertCompletionHandler() {
41+
// Capture the stack trace at creation.
42+
// This is used to indicate where the handler was created in case it was never called.
43+
creationStackTrace = Thread.currentThread().getStackTrace();
44+
45+
// Add the handler to the global registry.
3046
synchronized (AssertCompletionHandler.class) {
3147
allHandlers.add(this);
3248
}
3349
}
3450

3551
abstract public void doRequestCompleted(JSONObject content, AlgoliaException error);
3652

37-
/**
38-
* Add an inner handler to be checked as well in {@link AssertCompletionHandler#checkAssertions()}.
39-
*/
40-
public void addInnerHandler(AssertCompletionHandler handler) {
41-
innerHandlers.add(handler);
42-
}
43-
4453
/**
4554
* Fail if the handler encountered at least one AssertionError.
4655
*/
4756
public void checkAssertions() {
57+
// NOTE: Throwing an exception with an already populated stack trace maintains this stack trace.
58+
// We use this trick to have a meaningful stack trace displayed.
4859
if (error != null) {
49-
// Throwing the original exception maintains the stack trace... though I am not entirely sure why. =:)
50-
// (An alternative would be to chain the exception.)
5160
throw error;
52-
}
53-
for (AssertCompletionHandler h : innerHandlers) {
54-
h.checkAssertions();
61+
} else if (invocationCount == 0) {
62+
AssertionError e = new AssertionError("A completion handler was never called");
63+
e.setStackTrace(creationStackTrace);
64+
throw e;
65+
} else if (invocationCount > 1) {
66+
AssertionError e = new AssertionError(String.format("A completion handler was called more than once (%d times)", invocationCount));
67+
e.setStackTrace(creationStackTrace);
68+
throw e;
5569
}
5670
}
5771

@@ -60,15 +74,27 @@ public void checkAssertions() {
6074
* process if this method was never called).
6175
*/
6276
public synchronized static void checkAllHandlers() {
77+
// Print all errors.
78+
boolean failed = false;
6379
for (AssertCompletionHandler handler : allHandlers) {
64-
handler.checkAssertions();
80+
try {
81+
handler.checkAssertions();
82+
} catch (AssertionError e) {
83+
failed = true;
84+
e.printStackTrace(System.err);
85+
}
6586
}
6687
allHandlers.clear();
88+
// Cause the test to fail if necessary.
89+
if (failed) {
90+
fail("Assertions where caught");
91+
}
6792
}
6893

6994
@Override final public void requestCompleted(JSONObject content, AlgoliaException error) {
95+
invocationCount += 1;
7096
try {
71-
handler.requestCompleted(content, error);
97+
doRequestCompleted(content, error);
7298
} catch (AssertionError e) {
7399
this.error = e;
74100
}

0 commit comments

Comments
 (0)