5
5
import java .util .ArrayList ;
6
6
import java .util .List ;
7
7
8
+ import static org .junit .Assert .fail ;
9
+
8
10
/**
9
11
* This class helps coping with assertions in callbacks.
10
12
* As AssertionErrors are thrown silently when an assertion fails in a callback,
11
13
* this class wraps a CompletionHandler to check if an assert failed and expose the error through {@link AssertCompletionHandler#checkAssertions()}.
12
14
*/
13
15
abstract class AssertCompletionHandler implements CompletionHandler {
14
16
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 ;
17
19
18
20
/**
19
21
* Global registry of all created completion handlers. It is cleared when the static `checkAllHandlers()` method is
20
22
* called.
21
23
*/
22
24
private static List <AssertCompletionHandler > allHandlers = new ArrayList <>();
23
25
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
+ }
28
36
}
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.
30
46
synchronized (AssertCompletionHandler .class ) {
31
47
allHandlers .add (this );
32
48
}
33
49
}
34
50
35
51
abstract public void doRequestCompleted (JSONObject content , AlgoliaException error );
36
52
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
-
44
53
/**
45
54
* Fail if the handler encountered at least one AssertionError.
46
55
*/
47
56
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.
48
59
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.)
51
60
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 ;
55
69
}
56
70
}
57
71
@@ -60,15 +74,27 @@ public void checkAssertions() {
60
74
* process if this method was never called).
61
75
*/
62
76
public synchronized static void checkAllHandlers () {
77
+ // Print all errors.
78
+ boolean failed = false ;
63
79
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
+ }
65
86
}
66
87
allHandlers .clear ();
88
+ // Cause the test to fail if necessary.
89
+ if (failed ) {
90
+ fail ("Assertions where caught" );
91
+ }
67
92
}
68
93
69
94
@ Override final public void requestCompleted (JSONObject content , AlgoliaException error ) {
95
+ invocationCount += 1 ;
70
96
try {
71
- handler . requestCompleted (content , error );
97
+ doRequestCompleted (content , error );
72
98
} catch (AssertionError e ) {
73
99
this .error = e ;
74
100
}
0 commit comments