|
8 | 8 | import static java.util.concurrent.TimeUnit.MILLISECONDS; |
9 | 9 | import static org.junit.Assert.assertEquals; |
10 | 10 | import static org.junit.Assert.assertFalse; |
| 11 | +import static org.junit.Assert.assertNotNull; |
11 | 12 | import static org.junit.Assert.assertNotSame; |
12 | 13 | import static org.junit.Assert.assertSame; |
13 | 14 | import static org.junit.Assert.assertThrows; |
14 | 15 | import static org.junit.Assert.assertTrue; |
15 | | -import static org.junit.Assert.fail; |
16 | 16 | import static org.junit.Assume.assumeFalse; |
17 | 17 | import static org.junit.Assume.assumeTrue; |
18 | 18 |
|
19 | 19 | import java.util.Arrays; |
20 | | -import java.util.concurrent.TimeUnit; |
| 20 | +import java.util.concurrent.atomic.AtomicBoolean; |
21 | 21 | import java.util.concurrent.atomic.AtomicReference; |
22 | 22 |
|
23 | 23 | import org.junit.Test; |
24 | 24 | import org.junit.function.ThrowingRunnable; |
25 | | -import org.junit.internal.runners.statements.Fail; |
26 | 25 | import org.junit.runner.RunWith; |
27 | 26 | import org.junit.runners.Parameterized; |
| 27 | +import org.junit.runners.Parameterized.Parameter; |
| 28 | +import org.junit.runners.Parameterized.Parameters; |
28 | 29 | import org.junit.runners.model.Statement; |
29 | 30 | import org.junit.runners.model.TestTimedOutException; |
30 | 31 |
|
|
34 | 35 | */ |
35 | 36 | @RunWith(Parameterized.class) |
36 | 37 | public class FailOnTimeoutTest { |
37 | | - private static final long TIMEOUT = 100; |
38 | | - private static final long DURATION_THAT_EXCEEDS_TIMEOUT = 60 * 60 * 1000; //1 hour |
39 | 38 |
|
40 | | - private final TestStatement statement = new TestStatement(); |
41 | | - |
42 | | - private final boolean lookingForStuckThread; |
43 | | - private final FailOnTimeout failOnTimeout; |
44 | | - |
45 | | - @Parameterized.Parameters(name = "lookingForStuckThread = {0}") |
| 39 | + @Parameters(name = "lookingForStuckThread = {0}") |
46 | 40 | public static Iterable<Boolean> getParameters() { |
47 | 41 | return Arrays.asList(Boolean.TRUE, Boolean.FALSE); |
48 | 42 | } |
49 | 43 |
|
50 | | - public FailOnTimeoutTest(Boolean lookingForStuckThread) { |
51 | | - this.lookingForStuckThread = lookingForStuckThread; |
52 | | - this.failOnTimeout = builder().withTimeout(TIMEOUT, MILLISECONDS).build(statement); |
53 | | - } |
| 44 | + @Parameter |
| 45 | + public boolean lookingForStuckThread; |
| 46 | + |
| 47 | + @Test |
| 48 | + public void noExceptionIsThrownWhenWrappedStatementFinishesBeforeTimeoutWithoutThrowingException() |
| 49 | + throws Throwable { |
| 50 | + FailOnTimeout failOnTimeout = failAfter50Ms(new FastStatement()); |
54 | 51 |
|
55 | | - private FailOnTimeout.Builder builder() { |
56 | | - return FailOnTimeout.builder().withLookingForStuckThread(lookingForStuckThread); |
| 52 | + failOnTimeout.evaluate(); |
| 53 | + |
| 54 | + // test is successful when no exception is thrown |
57 | 55 | } |
58 | 56 |
|
59 | 57 | @Test |
60 | 58 | public void throwsTestTimedOutException() { |
61 | 59 | assertThrows( |
62 | 60 | TestTimedOutException.class, |
63 | | - evaluateWithWaitDuration(DURATION_THAT_EXCEEDS_TIMEOUT)); |
| 61 | + run(failAfter50Ms(new InfiniteLoop()))); |
64 | 62 | } |
65 | 63 |
|
66 | 64 | @Test |
67 | 65 | public void throwExceptionWithNiceMessageOnTimeout() { |
68 | | - TestTimedOutException e = assertThrows( |
69 | | - TestTimedOutException.class, |
70 | | - evaluateWithWaitDuration(DURATION_THAT_EXCEEDS_TIMEOUT)); |
71 | | - assertEquals("test timed out after 100 milliseconds", e.getMessage()); |
| 66 | + Exception e = assertThrows( |
| 67 | + Exception.class, |
| 68 | + run(failAfter50Ms(new InfiniteLoop()))); |
| 69 | + assertEquals("test timed out after 50 milliseconds", e.getMessage()); |
72 | 70 | } |
73 | 71 |
|
74 | 72 | @Test |
75 | 73 | public void sendUpExceptionThrownByStatement() { |
76 | | - RuntimeException exception = new RuntimeException(); |
77 | | - RuntimeException e = assertThrows( |
78 | | - RuntimeException.class, |
79 | | - evaluateWithException(exception)); |
| 74 | + Exception exception = new RuntimeException(); |
| 75 | + Exception e = assertThrows( |
| 76 | + Exception.class, |
| 77 | + run(failAfter50Ms(new Fail(exception)))); |
80 | 78 | assertSame(exception, e); |
81 | 79 | } |
82 | 80 |
|
83 | 81 | @Test |
84 | 82 | public void throwExceptionIfTheSecondCallToEvaluateNeedsTooMuchTime() |
85 | 83 | throws Throwable { |
86 | | - evaluateWithWaitDuration(0).run(); |
| 84 | + DelegateStatement statement = new DelegateStatement(); |
| 85 | + FailOnTimeout failOnTimeout = failAfter50Ms(statement); |
| 86 | + |
| 87 | + statement.delegate = new FastStatement(); |
| 88 | + failOnTimeout.evaluate(); |
| 89 | + |
| 90 | + statement.delegate = new InfiniteLoop(); |
87 | 91 | assertThrows( |
88 | 92 | TestTimedOutException.class, |
89 | | - evaluateWithWaitDuration(DURATION_THAT_EXCEEDS_TIMEOUT)); |
| 93 | + run(failOnTimeout)); |
90 | 94 | } |
91 | 95 |
|
92 | 96 | @Test |
93 | 97 | public void throwTimeoutExceptionOnSecondCallAlthoughFirstCallThrowsException() { |
94 | | - try { |
95 | | - evaluateWithException(new RuntimeException()).run(); |
96 | | - } catch (Throwable expected) { |
97 | | - } |
| 98 | + DelegateStatement statement = new DelegateStatement(); |
| 99 | + FailOnTimeout failOnTimeout = failAfter50Ms(statement); |
98 | 100 |
|
99 | | - TestTimedOutException e = assertThrows( |
| 101 | + statement.delegate = new Fail(new AssertionError("first execution failed")); |
| 102 | + assertThrows( |
| 103 | + AssertionError.class, |
| 104 | + run(failOnTimeout) |
| 105 | + ); |
| 106 | + |
| 107 | + statement.delegate = new InfiniteLoop(); |
| 108 | + assertThrows( |
100 | 109 | TestTimedOutException.class, |
101 | | - evaluateWithWaitDuration(DURATION_THAT_EXCEEDS_TIMEOUT)); |
102 | | - assertEquals("test timed out after 100 milliseconds", e.getMessage()); |
| 110 | + run(failOnTimeout)); |
103 | 111 | } |
104 | 112 |
|
105 | 113 | @Test |
106 | 114 | public void throwsExceptionWithTimeoutValueAndTimeUnitSet() { |
107 | 115 | TestTimedOutException e = assertThrows( |
108 | 116 | TestTimedOutException.class, |
109 | | - evaluateWithWaitDuration(DURATION_THAT_EXCEEDS_TIMEOUT)); |
110 | | - assertEquals(TIMEOUT, e.getTimeout()); |
111 | | - assertEquals(TimeUnit.MILLISECONDS, e.getTimeUnit()); |
112 | | - } |
113 | | - |
114 | | - private ThrowingRunnable evaluateWithDelegate(final Statement delegate) { |
115 | | - return new ThrowingRunnable() { |
116 | | - public void run() throws Throwable { |
117 | | - statement.nextStatement = delegate; |
118 | | - statement.waitDuration = 0; |
119 | | - failOnTimeout.evaluate(); |
120 | | - } |
121 | | - }; |
122 | | - } |
123 | | - |
124 | | - private ThrowingRunnable evaluateWithException(Exception exception) { |
125 | | - return evaluateWithDelegate(new Fail(exception)); |
126 | | - } |
127 | | - |
128 | | - private ThrowingRunnable evaluateWithWaitDuration(final long waitDuration) { |
129 | | - return new ThrowingRunnable() { |
130 | | - public void run() throws Throwable { |
131 | | - statement.nextStatement = null; |
132 | | - statement.waitDuration = waitDuration; |
133 | | - failOnTimeout.evaluate(); |
134 | | - } |
135 | | - }; |
136 | | - } |
137 | | - |
138 | | - private static final class TestStatement extends Statement { |
139 | | - long waitDuration; |
140 | | - |
141 | | - Statement nextStatement; |
142 | | - |
143 | | - @Override |
144 | | - public void evaluate() throws Throwable { |
145 | | - sleep(waitDuration); |
146 | | - if (nextStatement != null) { |
147 | | - nextStatement.evaluate(); |
148 | | - } |
149 | | - } |
| 117 | + run(failAfter50Ms(new InfiniteLoop()))); |
| 118 | + assertEquals(50, e.getTimeout()); |
| 119 | + assertEquals(MILLISECONDS, e.getTimeUnit()); |
150 | 120 | } |
151 | 121 |
|
152 | 122 | @Test |
153 | 123 | public void stopEndlessStatement() throws Throwable { |
154 | | - InfiniteLoopStatement infiniteLoop = new InfiniteLoopStatement(); |
155 | | - FailOnTimeout infiniteLoopTimeout = builder().withTimeout(TIMEOUT, MILLISECONDS).build(infiniteLoop); |
156 | | - try { |
157 | | - infiniteLoopTimeout.evaluate(); |
158 | | - } catch (Exception timeoutException) { |
159 | | - sleep(20); // time to interrupt the thread |
160 | | - int firstCount = InfiniteLoopStatement.COUNT; |
161 | | - sleep(20); // time to increment the count |
162 | | - assertTrue("Thread has not been stopped.", |
163 | | - firstCount == InfiniteLoopStatement.COUNT); |
164 | | - } |
165 | | - } |
166 | | - |
167 | | - private static final class InfiniteLoopStatement extends Statement { |
168 | | - private static int COUNT = 0; |
169 | | - |
170 | | - @Override |
171 | | - public void evaluate() throws Throwable { |
172 | | - while (true) { |
173 | | - sleep(10); // sleep in order to enable interrupting thread |
174 | | - ++COUNT; |
175 | | - } |
176 | | - } |
| 124 | + InfiniteLoop infiniteLoop = new InfiniteLoop(); |
| 125 | + assertThrows( |
| 126 | + TestTimedOutException.class, |
| 127 | + run(failAfter50Ms(infiniteLoop))); |
| 128 | + |
| 129 | + sleep(20); // time to interrupt the thread |
| 130 | + infiniteLoop.stillExecuting.set(false); |
| 131 | + sleep(20); // time to increment the count |
| 132 | + assertFalse( |
| 133 | + "Thread has not been stopped.", |
| 134 | + infiniteLoop.stillExecuting.get()); |
177 | 135 | } |
178 | 136 |
|
179 | 137 | @Test |
180 | | - public void stackTraceContainsRealCauseOfTimeout() throws Throwable { |
181 | | - StuckStatement stuck = new StuckStatement(); |
182 | | - FailOnTimeout stuckTimeout = builder().withTimeout(TIMEOUT, MILLISECONDS).build(stuck); |
183 | | - try { |
184 | | - stuckTimeout.evaluate(); |
185 | | - // We must not get here, we expect a timeout exception |
186 | | - fail("Expected timeout exception"); |
187 | | - } catch (Exception timeoutException) { |
188 | | - StackTraceElement[] stackTrace = timeoutException.getStackTrace(); |
189 | | - boolean stackTraceContainsTheRealCauseOfTheTimeout = false; |
190 | | - boolean stackTraceContainsOtherThanTheRealCauseOfTheTimeout = false; |
191 | | - for (StackTraceElement element : stackTrace) { |
192 | | - String methodName = element.getMethodName(); |
193 | | - if ("theRealCauseOfTheTimeout".equals(methodName)) { |
194 | | - stackTraceContainsTheRealCauseOfTheTimeout = true; |
195 | | - } |
196 | | - if ("notTheRealCauseOfTheTimeout".equals(methodName)) { |
197 | | - stackTraceContainsOtherThanTheRealCauseOfTheTimeout = true; |
198 | | - } |
| 138 | + public void stackTraceContainsRealCauseOfTimeout() { |
| 139 | + TestTimedOutException timedOutException = assertThrows( |
| 140 | + TestTimedOutException.class, |
| 141 | + run(failAfter50Ms(new StuckStatement()))); |
| 142 | + |
| 143 | + StackTraceElement[] stackTrace = timedOutException.getStackTrace(); |
| 144 | + boolean stackTraceContainsTheRealCauseOfTheTimeout = false; |
| 145 | + boolean stackTraceContainsOtherThanTheRealCauseOfTheTimeout = false; |
| 146 | + for (StackTraceElement element : stackTrace) { |
| 147 | + String methodName = element.getMethodName(); |
| 148 | + if ("theRealCauseOfTheTimeout".equals(methodName)) { |
| 149 | + stackTraceContainsTheRealCauseOfTheTimeout = true; |
| 150 | + } |
| 151 | + if ("notTheRealCauseOfTheTimeout".equals(methodName)) { |
| 152 | + stackTraceContainsOtherThanTheRealCauseOfTheTimeout = true; |
199 | 153 | } |
200 | | - assertTrue( |
201 | | - "Stack trace does not contain the real cause of the timeout", |
202 | | - stackTraceContainsTheRealCauseOfTheTimeout); |
203 | | - assertFalse( |
204 | | - "Stack trace contains other than the real cause of the timeout, which can be very misleading", |
205 | | - stackTraceContainsOtherThanTheRealCauseOfTheTimeout); |
206 | 154 | } |
| 155 | + assertTrue( |
| 156 | + "Stack trace does not contain the real cause of the timeout", |
| 157 | + stackTraceContainsTheRealCauseOfTheTimeout); |
| 158 | + assertFalse( |
| 159 | + "Stack trace contains other than the real cause of the timeout, which can be very misleading", |
| 160 | + stackTraceContainsOtherThanTheRealCauseOfTheTimeout); |
207 | 161 | } |
208 | 162 |
|
209 | 163 | private static final class StuckStatement extends Statement { |
@@ -238,36 +192,85 @@ public void lookingForStuckThread_threadGroupNotLeaked() throws Throwable { |
238 | 192 | final AtomicReference<ThreadGroup> innerThreadGroup = new AtomicReference<ThreadGroup>(); |
239 | 193 | final AtomicReference<Thread> innerThread = new AtomicReference<Thread>(); |
240 | 194 | final ThreadGroup outerThreadGroup = currentThread().getThreadGroup(); |
241 | | - ThrowingRunnable runnable = evaluateWithDelegate(new Statement() { |
| 195 | + FailOnTimeout failOnTimeout = failAfter50Ms(new Statement() { |
242 | 196 | @Override |
243 | 197 | public void evaluate() { |
244 | 198 | innerThread.set(currentThread()); |
245 | 199 | ThreadGroup group = currentThread().getThreadGroup(); |
246 | | - assertNotSame("inner thread should use a different thread group", outerThreadGroup, group); |
| 200 | + assertNotSame("inner thread should use a different thread group", |
| 201 | + outerThreadGroup, group); |
247 | 202 | innerThreadGroup.set(group); |
248 | | - assertTrue("the 'FailOnTimeoutGroup' thread group should be a daemon thread group", group.isDaemon()); |
| 203 | + assertTrue("the 'FailOnTimeoutGroup' thread group should be a daemon thread group", |
| 204 | + group.isDaemon()); |
249 | 205 | } |
250 | 206 | }); |
251 | 207 |
|
252 | | - runnable.run(); |
| 208 | + failOnTimeout.evaluate(); |
253 | 209 |
|
254 | | - assertTrue("the Statement was never run", innerThread.get() != null); |
| 210 | + assertNotNull("the Statement was never run", innerThread.get()); |
255 | 211 | innerThread.get().join(); |
256 | | - assertTrue("the 'FailOnTimeoutGroup' thread group should be destroyed after running the test", innerThreadGroup.get().isDestroyed()); |
| 212 | + assertTrue("the 'FailOnTimeoutGroup' thread group should be destroyed after running the test", |
| 213 | + innerThreadGroup.get().isDestroyed()); |
257 | 214 | } |
258 | 215 |
|
259 | 216 | @Test |
260 | 217 | public void notLookingForStuckThread_usesSameThreadGroup() throws Throwable { |
261 | 218 | assumeFalse(lookingForStuckThread); |
| 219 | + final AtomicBoolean statementWasExecuted = new AtomicBoolean(); |
262 | 220 | final ThreadGroup outerThreadGroup = currentThread().getThreadGroup(); |
263 | | - ThrowingRunnable runnable = evaluateWithDelegate(new Statement() { |
| 221 | + FailOnTimeout failOnTimeout = failAfter50Ms(new Statement() { |
264 | 222 | @Override |
265 | 223 | public void evaluate() { |
| 224 | + statementWasExecuted.set(true); |
266 | 225 | ThreadGroup group = currentThread().getThreadGroup(); |
267 | 226 | assertSame("inner thread should use the same thread group", outerThreadGroup, group); |
268 | 227 | } |
269 | 228 | }); |
270 | 229 |
|
271 | | - runnable.run(); |
| 230 | + failOnTimeout.evaluate(); |
| 231 | + |
| 232 | + assertTrue("the Statement was never run", statementWasExecuted.get()); |
| 233 | + } |
| 234 | + |
| 235 | + private FailOnTimeout failAfter50Ms(Statement statement) { |
| 236 | + return FailOnTimeout.builder() |
| 237 | + .withTimeout(50, MILLISECONDS) |
| 238 | + .withLookingForStuckThread(lookingForStuckThread) |
| 239 | + .build(statement); |
| 240 | + } |
| 241 | + |
| 242 | + private ThrowingRunnable run(final FailOnTimeout failOnTimeout) { |
| 243 | + return new ThrowingRunnable() { |
| 244 | + public void run() throws Throwable { |
| 245 | + failOnTimeout.evaluate(); |
| 246 | + } |
| 247 | + }; |
| 248 | + } |
| 249 | + |
| 250 | + private static class DelegateStatement extends Statement { |
| 251 | + Statement delegate; |
| 252 | + |
| 253 | + @Override |
| 254 | + public void evaluate() throws Throwable { |
| 255 | + delegate.evaluate(); |
| 256 | + } |
| 257 | + } |
| 258 | + |
| 259 | + private static class FastStatement extends Statement { |
| 260 | + @Override |
| 261 | + public void evaluate() throws Throwable { |
| 262 | + } |
| 263 | + } |
| 264 | + |
| 265 | + private static final class InfiniteLoop extends Statement { |
| 266 | + final AtomicBoolean stillExecuting = new AtomicBoolean(); |
| 267 | + |
| 268 | + @Override |
| 269 | + public void evaluate() throws Throwable { |
| 270 | + while (true) { |
| 271 | + sleep(10); // sleep in order to enable interrupting thread |
| 272 | + stillExecuting.set(true); |
| 273 | + } |
| 274 | + } |
272 | 275 | } |
273 | 276 | } |
0 commit comments