View Javadoc
1   package org.junit.tests.internal.runners.statements;
2   
3   import static java.lang.Long.MAX_VALUE;
4   import static java.lang.Math.atan;
5   import static java.lang.System.currentTimeMillis;
6   import static java.lang.Thread.sleep;
7   import static org.hamcrest.core.Is.is;
8   import static org.junit.Assert.assertEquals;
9   import static org.junit.Assert.assertFalse;
10  import static org.junit.Assert.assertTrue;
11  import static org.junit.Assert.fail;
12  
13  import java.util.concurrent.TimeUnit;
14  
15  import org.junit.Ignore;
16  import org.junit.Rule;
17  import org.junit.Test;
18  import org.junit.internal.runners.statements.FailOnTimeout;
19  import org.junit.rules.ExpectedException;
20  import org.junit.runners.model.Statement;
21  import org.junit.runners.model.TestTimedOutException;
22  
23  /**
24   * @author Asaf Ary, Stefan Birkner
25   */
26  public class FailOnTimeoutTest {
27      private static final int TIMEOUT = 100;
28      private static final int DURATION_THAT_EXCEEDS_TIMEOUT = 60 * 60 * 1000; //1 hour
29  
30      @Rule
31      public final ExpectedException thrown = ExpectedException.none();
32  
33      private final TestStatement statement = new TestStatement();
34  
35      private final FailOnTimeout failOnTimeout = new FailOnTimeout(statement,
36              TIMEOUT);
37  
38      @Test
39      public void throwsTestTimedOutException() throws Throwable {
40          thrown.expect(TestTimedOutException.class);
41          evaluateWithWaitDuration(DURATION_THAT_EXCEEDS_TIMEOUT);
42      }
43  
44      @Test
45      public void throwExceptionWithNiceMessageOnTimeout() throws Throwable {
46          thrown.expectMessage("test timed out after 100 milliseconds");
47          evaluateWithWaitDuration(DURATION_THAT_EXCEEDS_TIMEOUT);
48      }
49  
50      @Test
51      public void sendUpExceptionThrownByStatement() throws Throwable {
52          RuntimeException exception = new RuntimeException();
53          thrown.expect(is(exception));
54          evaluateWithException(exception);
55      }
56  
57      @Test
58      public void throwExceptionIfTheSecondCallToEvaluateNeedsTooMuchTime()
59              throws Throwable {
60          thrown.expect(TestTimedOutException.class);
61          evaluateWithWaitDuration(0);
62          evaluateWithWaitDuration(DURATION_THAT_EXCEEDS_TIMEOUT);
63      }
64  
65      @Test
66      public void throwTimeoutExceptionOnSecondCallAlthoughFirstCallThrowsException()
67              throws Throwable {
68          thrown.expectMessage("test timed out after 100 milliseconds");
69          try {
70              evaluateWithException(new RuntimeException());
71          } catch (Throwable expected) {
72          }
73          evaluateWithWaitDuration(DURATION_THAT_EXCEEDS_TIMEOUT);
74      }
75  
76      @Test
77      public void throwsExceptionWithTimeoutValueAndTimeUnitSet()
78              throws Throwable {
79          try {
80              evaluateWithWaitDuration(DURATION_THAT_EXCEEDS_TIMEOUT);
81              fail("No exception was thrown when test timed out");
82          } catch (TestTimedOutException e) {
83              assertEquals(TIMEOUT, e.getTimeout());
84              assertEquals(TimeUnit.MILLISECONDS, e.getTimeUnit());
85          }
86      }
87  
88      private void evaluateWithException(Exception exception) throws Throwable {
89          statement.nextException = exception;
90          statement.waitDuration = 0;
91          failOnTimeout.evaluate();
92      }
93  
94      private void evaluateWithWaitDuration(int waitDuration) throws Throwable {
95          statement.nextException = null;
96          statement.waitDuration = waitDuration;
97          failOnTimeout.evaluate();
98      }
99  
100     private static final class TestStatement extends Statement {
101         int waitDuration;
102 
103         Exception nextException;
104 
105         @Override
106         public void evaluate() throws Throwable {
107             sleep(waitDuration);
108             if (nextException != null) {
109                 throw nextException;
110             }
111         }
112     }
113 
114     @Test
115     public void stopEndlessStatement() throws Throwable {
116         InfiniteLoopStatement infiniteLoop = new InfiniteLoopStatement();
117         FailOnTimeout infiniteLoopTimeout = new FailOnTimeout(infiniteLoop,
118                 TIMEOUT);
119         try {
120             infiniteLoopTimeout.evaluate();
121         } catch (Exception timeoutException) {
122             sleep(20); // time to interrupt the thread
123             int firstCount = InfiniteLoopStatement.COUNT;
124             sleep(20); // time to increment the count
125             assertTrue("Thread has not been stopped.",
126                     firstCount == InfiniteLoopStatement.COUNT);
127         }
128     }
129 
130     private static final class InfiniteLoopStatement extends Statement {
131         private static int COUNT = 0;
132 
133         @Override
134         public void evaluate() throws Throwable {
135             while (true) {
136                 sleep(10); // sleep in order to enable interrupting thread
137                 ++COUNT;
138             }
139         }
140     }
141 
142     @Test
143     public void stackTraceContainsRealCauseOfTimeout() throws Throwable {
144         StuckStatement stuck = new StuckStatement();
145         FailOnTimeout stuckTimeout = new FailOnTimeout(stuck, TIMEOUT);
146         try {
147             stuckTimeout.evaluate();
148             // We must not get here, we expect a timeout exception
149             fail("Expected timeout exception");
150         } catch (Exception timeoutException) {
151             StackTraceElement[] stackTrace = timeoutException.getStackTrace();
152             boolean stackTraceContainsTheRealCauseOfTheTimeout = false;
153             boolean stackTraceContainsOtherThanTheRealCauseOfTheTimeout = false;
154             for (StackTraceElement element : stackTrace) {
155                 String methodName = element.getMethodName();
156                 if ("theRealCauseOfTheTimeout".equals(methodName)) {
157                     stackTraceContainsTheRealCauseOfTheTimeout = true;
158                 }
159                 if ("notTheRealCauseOfTheTimeout".equals(methodName)) {
160                     stackTraceContainsOtherThanTheRealCauseOfTheTimeout = true;
161                 }
162             }
163             assertTrue(
164                     "Stack trace does not contain the real cause of the timeout",
165                     stackTraceContainsTheRealCauseOfTheTimeout);
166             assertFalse(
167                     "Stack trace contains other than the real cause of the timeout, which can be very misleading",
168                     stackTraceContainsOtherThanTheRealCauseOfTheTimeout);
169         }
170     }
171 
172     private static final class StuckStatement extends Statement {
173 
174         @Override
175         public void evaluate() throws Throwable {
176             try {
177                 // Must show up in stack trace
178                 theRealCauseOfTheTimeout();
179             } catch (InterruptedException e) {
180             } finally {
181                 // Must _not_ show up in stack trace
182                 notTheRealCauseOfTheTimeout();
183             }
184         }
185 
186         private void theRealCauseOfTheTimeout() throws InterruptedException {
187             sleep(MAX_VALUE);
188         }
189 
190         private void notTheRealCauseOfTheTimeout() {
191             for (long now = currentTimeMillis(), eta = now + 1000L; now < eta; now = currentTimeMillis()) {
192                 // Doesn't matter, just pretend to be busy
193                 atan(now);
194             }
195         }
196     }
197 }