1616package androidx .test .internal .runner .junit4 ;
1717
1818import static androidx .test .platform .app .InstrumentationRegistry .getArguments ;
19+ import static java .util .concurrent .TimeUnit .MILLISECONDS ;
1920
2021import androidx .test .internal .runner .RunnerArgs ;
2122import androidx .test .internal .runner .junit4 .statement .RunAfters ;
2223import androidx .test .internal .runner .junit4 .statement .RunBefores ;
2324import androidx .test .internal .runner .junit4 .statement .UiThreadStatement ;
2425import androidx .test .internal .util .AndroidRunnerParams ;
2526import java .util .List ;
27+ import java .util .concurrent .CountDownLatch ;
28+ import java .util .concurrent .atomic .AtomicReference ;
2629import org .junit .After ;
2730import org .junit .Before ;
2831import org .junit .Test ;
29- import org .junit .internal .runners .statements .FailOnTimeout ;
32+ import org .junit .internal .runners .model .ReflectiveCallable ;
33+ import org .junit .internal .runners .statements .Fail ;
3034import org .junit .runners .BlockJUnit4ClassRunner ;
3135import org .junit .runners .model .FrameworkMethod ;
3236import org .junit .runners .model .InitializationError ;
3337import org .junit .runners .model .Statement ;
38+ import org .junit .runners .model .TestTimedOutException ;
3439
3540/** A specialized {@link BlockJUnit4ClassRunner} that can handle timeouts */
3641public class AndroidJUnit4ClassRunner extends BlockJUnit4ClassRunner {
@@ -55,13 +60,35 @@ public AndroidJUnit4ClassRunner(Class<?> klass) throws InitializationError {
5560 this (klass , RunnerArgs .parseTestTimeout (getArguments ()));
5661 }
5762
63+ private static final ThreadLocal <CountDownLatch > currentTestStartedLatch = new ThreadLocal <>();
64+ private static final ThreadLocal <CountDownLatch > currentTestFinishedLatch = new ThreadLocal <>();
65+
5866 /** Returns a {@link Statement} that invokes {@code method} on {@code test} */
5967 @ Override
6068 protected Statement methodInvoker (FrameworkMethod method , Object test ) {
69+ final Statement invoker ;
6170 if (UiThreadStatement .shouldRunOnUiThread (method )) {
62- return new UiThreadStatement (super .methodInvoker (method , test ), true );
71+ invoker = new UiThreadStatement (super .methodInvoker (method , test ), true );
72+ } else {
73+ invoker = super .methodInvoker (method , test );
6374 }
64- return super .methodInvoker (method , test );
75+ return new Statement () {
76+ @ Override
77+ public void evaluate () throws Throwable {
78+ CountDownLatch startLatch = currentTestStartedLatch .get ();
79+ if (startLatch != null ) {
80+ startLatch .countDown ();
81+ }
82+ try {
83+ invoker .evaluate ();
84+ } finally {
85+ CountDownLatch finishLatch = currentTestFinishedLatch .get ();
86+ if (finishLatch != null ) {
87+ finishLatch .countDown ();
88+ }
89+ }
90+ }
91+ };
6592 }
6693
6794 @ Override
@@ -76,28 +103,101 @@ protected Statement withAfters(FrameworkMethod method, Object target, Statement
76103 return afters .isEmpty () ? statement : new RunAfters (method , statement , afters , target );
77104 }
78105
106+ @ Override
107+ protected Statement methodBlock (FrameworkMethod method ) {
108+ Object test ;
109+ try {
110+ test =
111+ new ReflectiveCallable () {
112+ @ Override
113+ protected Object runReflectiveCall () throws Throwable {
114+ return createTest ();
115+ }
116+ }.run ();
117+ } catch (Throwable e ) {
118+ return new Fail (e );
119+ }
120+
121+ Statement statement = methodInvoker (method , test );
122+ statement = possiblyExpectingExceptions (method , test , statement );
123+ statement = withBefores (method , test , statement );
124+ statement = withAfters (method , test , statement );
125+ statement = withPotentialTimeout (method , test , statement );
126+ try {
127+ java .lang .reflect .Method withRulesMethod =
128+ BlockJUnit4ClassRunner .class .getDeclaredMethod (
129+ "withRules" , FrameworkMethod .class , Object .class , Statement .class );
130+ withRulesMethod .setAccessible (true );
131+ statement = (Statement ) withRulesMethod .invoke (this , method , test , statement );
132+ } catch (Exception e ) {
133+ throw new RuntimeException (e );
134+ }
135+ return statement ;
136+ }
137+
79138 /**
80139 * Default to {@link org.junit.Test#timeout()} level timeout if set. Otherwise, set the timeout
81140 * that was passed to the instrumentation via argument.
82141 */
83142 @ Override
84143 protected Statement withPotentialTimeout (FrameworkMethod method , Object test , Statement next ) {
85- // test level timeout i.e @Test(timeout = 123)
86144 long timeout = getTimeout (method .getAnnotation (Test .class ));
87-
88- // use runner arg timeout if test level timeout is not present
89145 if (timeout <= 0 && perTestTimeout > 0 ) {
90146 timeout = perTestTimeout ;
91147 }
148+ final long finalTimeout = timeout ;
92149
93- if (timeout <= 0 ) {
94- // no timeout was set
150+ if (finalTimeout <= 0 || UiThreadStatement .shouldRunOnUiThread (method )) {
95151 return next ;
96152 }
97153
98- // Cannot switch to use builder as that is not supported in JUnit 4.10 which is what is
99- // available in AOSP.
100- return new FailOnTimeout (next , timeout );
154+ return new Statement () {
155+ @ Override
156+ @ SuppressWarnings ("Interruption" ) // We want to interrupt the thread to stop the test.
157+ public void evaluate () throws Throwable {
158+ final AtomicReference <Throwable > failure = new AtomicReference <>();
159+ final CountDownLatch testStartedLatch = new CountDownLatch (1 );
160+ final CountDownLatch testFinishedLatch = new CountDownLatch (1 );
161+ final CountDownLatch doneLatch = new CountDownLatch (1 );
162+
163+ Thread thread =
164+ new Thread (
165+ new Runnable () {
166+ @ Override
167+ public void run () {
168+ currentTestStartedLatch .set (testStartedLatch );
169+ currentTestFinishedLatch .set (testFinishedLatch );
170+ try {
171+ next .evaluate ();
172+ } catch (Throwable t ) {
173+ failure .set (t );
174+ } finally {
175+ testStartedLatch .countDown ();
176+ testFinishedLatch .countDown ();
177+ doneLatch .countDown ();
178+ currentTestStartedLatch .remove ();
179+ currentTestFinishedLatch .remove ();
180+ }
181+ }
182+ },
183+ "Time-limited test" );
184+ thread .setDaemon (true );
185+ thread .start ();
186+
187+ testStartedLatch .await ();
188+ boolean finishedInTime = testFinishedLatch .await (finalTimeout , MILLISECONDS );
189+
190+ if (!finishedInTime ) {
191+ thread .interrupt ();
192+ throw new TestTimedOutException (finalTimeout , MILLISECONDS );
193+ }
194+
195+ doneLatch .await ();
196+ if (failure .get () != null ) {
197+ throw failure .get ();
198+ }
199+ }
200+ };
101201 }
102202
103203 private long getTimeout (Test annotation ) {
0 commit comments