Skip to content

Commit 1cdfde4

Browse files
committed
Merge pull request #476 from JLLeitschuh/tests/testExceptionHandler
Tests/test exception handler
2 parents 79bdd9d + caca564 commit 1cdfde4

10 files changed

Lines changed: 269 additions & 39 deletions

File tree

core/src/main/java/edu/wpi/grip/core/GRIPCoreModule.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import edu.wpi.grip.core.sources.MultiImageFileSource;
1717
import edu.wpi.grip.core.util.ExceptionWitness;
1818

19+
import javax.annotation.Nullable;
1920
import java.io.IOException;
2021
import java.util.logging.*;
2122

@@ -25,9 +26,9 @@
2526
*/
2627
@SuppressWarnings("PMD.MoreThanOneLogger")
2728
public class GRIPCoreModule extends AbstractModule {
28-
private final Logger logger = Logger.getLogger(GRIPCoreModule.class.getName());
29+
private static final Logger logger = Logger.getLogger(GRIPCoreModule.class.getName());
2930

30-
private final EventBus eventBus = new EventBus(this::onSubscriberException);
31+
private final EventBus eventBus;
3132

3233
// This is in a static initialization block so that we don't create a ton of
3334
// log files when running tests
@@ -75,7 +76,11 @@ public synchronized void publish(final LogRecord record) {
7576
}
7677
}
7778

79+
/*
80+
* This class should not be used in tests. Use GRIPCoreTestModule for tests.
81+
*/
7882
public GRIPCoreModule() {
83+
this.eventBus = new EventBus(this::onSubscriberException);
7984
// TODO: HACK! Don't assign the global thread handler to an instance method. Creates global state.
8085
Thread.setDefaultUncaughtExceptionHandler(this::onThreadException);
8186
}
@@ -111,7 +116,7 @@ public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
111116
install(new FactoryModuleBuilder().build(ExceptionWitness.Factory.class));
112117
}
113118

114-
private void onSubscriberException(Throwable exception, SubscriberExceptionContext context) {
119+
protected void onSubscriberException(Throwable exception, @Nullable SubscriberExceptionContext exceptionContext) {
115120
if (exception instanceof InterruptedException) {
116121
logger.log(Level.FINE, "EventBus Subscriber threw InterruptedException", exception);
117122
Thread.currentThread().interrupt();
@@ -126,7 +131,7 @@ private void onSubscriberException(Throwable exception, SubscriberExceptionConte
126131
* We drop the last throwable because we clearly have a problem beyond our control.
127132
*/
128133
@SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.EmptyCatchBlock"})
129-
private void onThreadException(Thread thread, Throwable exception) {
134+
protected void onThreadException(Thread thread, Throwable exception) {
130135
// Don't do anything outside of a try catch block when dealing with thread death
131136
try {
132137
if (exception instanceof Error && !(exception instanceof AssertionError)) {

core/src/main/java/edu/wpi/grip/core/Pipeline.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ public synchronized void moveStep(Step step, int delta) {
300300
public void onConnectionAdded(ConnectionAddedEvent event) {
301301
final Connection connection = event.getConnection();
302302
this.connections.add(connection);
303+
this.eventBus.register(connection);
303304
}
304305

305306
@Subscribe

core/src/test/java/edu/wpi/grip/core/PipelineTest.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import edu.wpi.grip.core.events.ConnectionRemovedEvent;
88
import edu.wpi.grip.core.events.SourceAddedEvent;
99
import edu.wpi.grip.core.events.SourceRemovedEvent;
10+
import edu.wpi.grip.util.GRIPCoreTestModule;
11+
import org.junit.After;
1012
import org.junit.Before;
1113
import org.junit.Test;
1214

@@ -19,7 +21,7 @@
1921

2022
public class PipelineTest {
2123

22-
private Injector injector;
24+
private GRIPCoreTestModule testModule;
2325
private Step.Factory stepFactory;
2426
private EventBus eventBus;
2527
private Pipeline pipeline;
@@ -38,13 +40,20 @@ public MockConnection(EventBus eventBus, Pipeline pipeline) {
3840

3941
@Before
4042
public void setUp() {
41-
injector = Guice.createInjector(new GRIPCoreModule());
43+
testModule = new GRIPCoreTestModule();
44+
testModule.setUp();
45+
final Injector injector = Guice.createInjector(testModule);
4246
stepFactory = injector.getInstance(Step.Factory.class);
4347
eventBus = injector.getInstance(EventBus.class);
4448
pipeline = injector.getInstance(Pipeline.class);
4549
addition = new AdditionOperation();
4650
}
4751

52+
@After
53+
public void tearDown() {
54+
testModule.tearDown();
55+
}
56+
4857
@Test
4958
public void testAddSource() throws URISyntaxException, IOException {
5059
Source source = new MockSource();

core/src/test/java/edu/wpi/grip/core/serialization/CompatibilityTest.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
import com.google.common.eventbus.EventBus;
44
import com.google.inject.Guice;
55
import com.google.inject.Injector;
6-
import edu.wpi.grip.core.GRIPCoreModule;
76
import edu.wpi.grip.core.Pipeline;
87
import edu.wpi.grip.core.operations.Operations;
9-
import edu.wpi.grip.core.settings.ProjectSettings;
108
import edu.wpi.grip.generated.CVOperations;
119
import edu.wpi.grip.util.Files;
10+
import edu.wpi.grip.util.GRIPCoreTestModule;
11+
import org.junit.After;
1212
import org.junit.Before;
1313
import org.junit.Test;
1414

@@ -26,22 +26,19 @@ public class CompatibilityTest {
2626
private static final URI testphotoURI = Files.testphotoURI; //The location of the photo source for the test
2727
private static final URI testprojectURI = Files.testprojectURI; //The location of the save file for the test
2828

29+
private GRIPCoreTestModule testModule;
2930
private Pipeline pipeline;
30-
private Project project;
31-
private ProjectSettings settings;
32-
33-
private EventBus eventBus;
3431

3532
@Before
3633
public void setUp() throws Exception {
37-
34+
testModule = new GRIPCoreTestModule();
35+
testModule.setUp();
3836
//Set up the stuff we need for the core functionality for GRIP
39-
final Injector injector = Guice.createInjector(new GRIPCoreModule());
37+
final Injector injector = Guice.createInjector(testModule);
4038

41-
eventBus = injector.getInstance(EventBus.class);
39+
final EventBus eventBus = injector.getInstance(EventBus.class);
4240
pipeline = injector.getInstance(Pipeline.class);
43-
project = injector.getInstance(Project.class);
44-
settings = injector.getInstance(ProjectSettings.class);
41+
final Project project = injector.getInstance(Project.class);
4542

4643
//Add the operations so that GRIP will recognize them
4744
Operations.addOperations(eventBus);
@@ -73,6 +70,11 @@ public void setUp() throws Exception {
7370
project.open(file);
7471
}
7572

73+
@After
74+
public void tearDown() {
75+
testModule.tearDown();
76+
}
77+
7678
@Test
7779
public void testFoo() throws Exception {
7880
assertEquals("The expected number of steps were not found", 50, pipeline.getSteps().size());

core/src/test/java/edu/wpi/grip/core/serialization/ProjectTest.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import edu.wpi.grip.core.settings.ProjectSettings;
1515
import edu.wpi.grip.core.sources.ImageFileSource;
1616
import edu.wpi.grip.util.Files;
17+
import edu.wpi.grip.util.GRIPCoreTestModule;
18+
import org.junit.After;
1719
import org.junit.Before;
1820
import org.junit.Test;
1921

@@ -27,6 +29,7 @@
2729

2830
public class ProjectTest {
2931

32+
private GRIPCoreTestModule testModule;
3033
private Connection.Factory<Object> connectionFactory;
3134
private ImageFileSource.Factory imageSourceFactory;
3235
private Step.Factory stepFactory;
@@ -40,7 +43,9 @@ public class ProjectTest {
4043

4144
@Before
4245
public void setUp() throws Exception {
43-
final Injector injector = Guice.createInjector(new GRIPCoreModule());
46+
testModule = new GRIPCoreTestModule();
47+
testModule.setUp();
48+
final Injector injector = Guice.createInjector(testModule);
4449
connectionFactory = injector
4550
.getInstance(Key.get(new TypeLiteral<Connection.Factory<Object>>() {
4651
}));
@@ -70,6 +75,11 @@ public void setUp() throws Exception {
7075
eventBus.post(new OperationAddedEvent(opencvAddOperation));
7176
}
7277

78+
@After
79+
public void tearDown() {
80+
testModule.tearDown();
81+
}
82+
7383
private void serializeAndDeserialize() {
7484
final Writer writer = new StringWriter();
7585
project.save(writer);

core/src/test/java/edu/wpi/grip/core/sources/CameraSourceTest.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import com.google.common.util.concurrent.Service;
88
import com.google.inject.Guice;
99
import com.google.inject.Injector;
10-
import edu.wpi.grip.core.GRIPCoreModule;
1110
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
1211
import edu.wpi.grip.core.util.MockExceptionWitness;
12+
import edu.wpi.grip.util.GRIPCoreTestModule;
1313
import org.bytedeco.javacpp.indexer.Indexer;
1414
import org.bytedeco.javacv.Frame;
1515
import org.bytedeco.javacv.FrameGrabber;
@@ -25,10 +25,8 @@
2525
import static org.junit.Assert.*;
2626

2727
public class CameraSourceTest {
28-
private Injector injector;
28+
private GRIPCoreTestModule testModule;
2929
private CameraSource.Factory cameraSourceFactory;
30-
31-
private EventBus eventBus;
3230
private CameraSource cameraSourceWithMockGrabber;
3331
private MockFrameGrabberFactory mockFrameGrabberFactory;
3432

@@ -108,10 +106,12 @@ public FrameGrabber create(String addressProperty) throws MalformedURLException
108106

109107
@Before
110108
public void setUp() throws Exception {
111-
this.injector = Guice.createInjector(new GRIPCoreModule());
109+
this.testModule = new GRIPCoreTestModule();
110+
testModule.setUp();
111+
final Injector injector = Guice.createInjector(testModule);
112112
this.cameraSourceFactory = injector.getInstance(CameraSource.Factory.class);
113113

114-
this.eventBus = new EventBus();
114+
final EventBus eventBus = new EventBus();
115115
class UnhandledExceptionWitness {
116116
@Subscribe
117117
public void onUnexpectedThrowableEvent(UnexpectedThrowableEvent event) {
@@ -120,7 +120,7 @@ public void onUnexpectedThrowableEvent(UnexpectedThrowableEvent event) {
120120
});
121121
}
122122
}
123-
this.eventBus.register(new UnhandledExceptionWitness());
123+
eventBus.register(new UnhandledExceptionWitness());
124124
this.mockFrameGrabberFactory = new MockFrameGrabberFactory();
125125
this.cameraSourceWithMockGrabber = new CameraSource(
126126
eventBus,
@@ -132,6 +132,7 @@ public void onUnexpectedThrowableEvent(UnexpectedThrowableEvent event) {
132132
@After
133133
public void tearDown() throws Exception {
134134
mockFrameGrabberFactory.frameGrabber.release();
135+
testModule.tearDown();
135136
}
136137

137138
@Test(expected = IOException.class)
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package edu.wpi.grip.util;
2+
3+
4+
import com.google.common.eventbus.SubscriberExceptionContext;
5+
import edu.wpi.grip.core.GRIPCoreModule;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.concurrent.ConcurrentLinkedQueue;
10+
import java.util.stream.Collectors;
11+
12+
import static com.google.common.base.Preconditions.checkNotNull;
13+
14+
/**
15+
* This GRIPCoreTestModule is designed to be used in tests.
16+
* Any thread that uses the DefaultUncaughtExceptionHandler will have exceptions
17+
* dumped into this class. Exceptions thrown by the event bus will also be
18+
* collected using this handler as well.
19+
* When {@link #tearDown()} is called the exceptions that have been accrued
20+
* during the test will be rethrown. This should be done in a tests
21+
* {@link org.junit.After} annotated method.
22+
* If tear down is not called and another TestThrowablesHandler is created
23+
* this class will throw an assertion error.
24+
* This is done to ensure that exceptions always get dumped for the test
25+
* that has just run.
26+
*/
27+
public class GRIPCoreTestModule extends GRIPCoreModule {
28+
private static volatile boolean instanceAlive = false;
29+
30+
private final ConcurrentLinkedQueue<ThreadThrowablePair> threadExceptions = new ConcurrentLinkedQueue<>();
31+
private final ConcurrentLinkedQueue<SubscriberThrowablePair> subscriberExceptions = new ConcurrentLinkedQueue<>();
32+
private boolean setUp = false;
33+
34+
public GRIPCoreTestModule() {
35+
super();
36+
assert !instanceAlive : "There is a GRIPCoreTestModule that did not have it's `tearDown` method called.";
37+
}
38+
39+
public void setUp() {
40+
instanceAlive = true;
41+
setUp = true;
42+
}
43+
44+
/**
45+
* Rethrows any exceptions that were thrown by other threads or the event bus.
46+
* This must be called in the {@link org.junit.After} method for any test that
47+
* uses this class. If this is not called then the next test that tries
48+
* to use an instance of this class will throw an exception.
49+
*/
50+
public void tearDown() {
51+
try {
52+
final List<Throwable> throwables = new ArrayList<>(2);
53+
if (!threadExceptions.isEmpty()) {
54+
throwables.add(MultiException.create(threadExceptions.stream().map(ThreadThrowablePair::getThrowable).collect(Collectors.toList())));
55+
}
56+
if (!subscriberExceptions.isEmpty()) {
57+
throwables.add(MultiException.create(subscriberExceptions.stream().map(SubscriberThrowablePair::getThrowable).collect(Collectors.toList())));
58+
}
59+
if (!throwables.isEmpty()) {
60+
throw MultiException.create(throwables);
61+
}
62+
} finally {
63+
instanceAlive = false;
64+
}
65+
}
66+
67+
68+
@Override
69+
protected void configure() {
70+
assert setUp : "The GRIPCoreTestModule handler was not set up. Call 'setUp' before passing the injector";
71+
super.configure();
72+
}
73+
74+
@Override
75+
protected void onThreadException(Thread thread, Throwable exception) {
76+
// Do not call super, we don't want the default functionality
77+
threadExceptions.add(new ThreadThrowablePair(thread, exception));
78+
}
79+
80+
@Override
81+
protected void onSubscriberException(Throwable exception, SubscriberExceptionContext exceptionContext) {
82+
// Do not call super, we don't want the default functionality
83+
subscriberExceptions.add(new SubscriberThrowablePair(exception, exceptionContext));
84+
}
85+
86+
private static final class ThreadThrowablePair {
87+
private final Thread thread;
88+
private final Throwable throwable;
89+
90+
private ThreadThrowablePair(Thread thread, Throwable throwable) {
91+
this.thread = checkNotNull(thread, "Thread cannot be null");
92+
this.throwable = checkNotNull(throwable, "Throwable cannot be null");
93+
}
94+
95+
private Throwable getThrowable() {
96+
return throwable;
97+
}
98+
}
99+
100+
private static final class SubscriberThrowablePair {
101+
private final Throwable exception;
102+
private final SubscriberExceptionContext exceptionContext;
103+
104+
private SubscriberThrowablePair(Throwable exception, SubscriberExceptionContext exceptionContext) {
105+
this.exception = checkNotNull(exception, "Throwable cannot be null");
106+
this.exceptionContext = checkNotNull(exceptionContext, "SubscriberExceptionContext cannot be null");
107+
}
108+
109+
private Throwable getThrowable() {
110+
return exception;
111+
}
112+
}
113+
114+
}

0 commit comments

Comments
 (0)