1654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin/*
2654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin * Copyright (C) 2016 The Android Open Source Project
3654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin *
4654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin * Licensed under the Apache License, Version 2.0 (the "License");
5654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin * you may not use this file except in compliance with the License.
6654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin * You may obtain a copy of the License at
7654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin *
8654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin *      http://www.apache.org/licenses/LICENSE-2.0
9654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin *
10654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin * Unless required by applicable law or agreed to in writing, software
11654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin * distributed under the License is distributed on an "AS IS" BASIS,
12654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin * See the License for the specific language governing permissions and
14654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin * limitations under the License.
15654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin */
16654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
17654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinpackage vogar.target.junit;
18654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
19654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport java.util.concurrent.Callable;
20654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport java.util.concurrent.ExecutionException;
21654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport java.util.concurrent.ExecutorService;
22654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport java.util.concurrent.Executors;
23654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport java.util.concurrent.Future;
24654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport java.util.concurrent.TimeUnit;
25654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport java.util.concurrent.TimeoutException;
26654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport java.util.concurrent.atomic.AtomicReference;
27654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport org.junit.rules.TestRule;
28654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport org.junit.runner.Description;
29654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport org.junit.runners.model.Statement;
30654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinimport vogar.util.Threads;
31654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
32654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin/**
33654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin * Times a test out and then aborts the test run.
34654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin */
35654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffinpublic class TimeoutAndAbortRunRule implements TestRule {
36654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
37654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    private final ExecutorService executor = Executors.newCachedThreadPool(
38654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            Threads.daemonThreadFactory(getClass().getName()));
39654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
40654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    private final int timeoutSeconds;
41654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
42654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    /**
43654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin     * @param timeoutSeconds the timeout in seconds, if 0 then never times out.
44654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin     */
45654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    public TimeoutAndAbortRunRule(int timeoutSeconds) {
46654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        this.timeoutSeconds = timeoutSeconds;
47654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    }
48654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
49654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    @Override
50654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    public Statement apply(final Statement base, Description description) {
51654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        return new Statement() {
52654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            @Override
53654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            public void evaluate() throws Throwable {
54654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                runWithTimeout(base);
55654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            }
56654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        };
57654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    }
58654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
59654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    /**
60654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin     * Runs the test on another thread. If the test completes before the
61654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin     * timeout, this reports the result normally. But if the test times out,
62654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin     * this reports the timeout stack trace and begins the process of killing
63654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin     * this no-longer-trustworthy process.
64654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin     */
65654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    private void runWithTimeout(final Statement base) throws Throwable {
66654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        // Start the test on a background thread.
67654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        final AtomicReference<Thread> executingThreadReference = new AtomicReference<>();
68654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        Future<Throwable> result = executor.submit(new Callable<Throwable>() {
69654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            public Throwable call() throws Exception {
70654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                executingThreadReference.set(Thread.currentThread());
71654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                try {
72654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                    base.evaluate();
73654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                    return null;
74654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                } catch (Throwable throwable) {
75654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                    return throwable;
76654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                }
77654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            }
78654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        });
79654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
80654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        // Wait until either the result arrives or the test times out.
81654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        Throwable thrown;
82654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        try {
83654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            thrown = getThrowable(result);
84654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        } catch (TimeoutException e) {
85654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            Thread executingThread = executingThreadReference.get();
86654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            if (executingThread != null) {
87654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                executingThread.interrupt();
88654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                e.setStackTrace(executingThread.getStackTrace());
89654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            }
90654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            // Wrap it in an exception that will cause the current run to be aborted.
91654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            thrown = new VmIsUnstableException(e);
92654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        }
93654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
94654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        if (thrown != null) {
95654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            throw thrown;
96654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        }
97654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    }
98654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
99654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
100654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    private Throwable getThrowable(Future<Throwable> result)
101654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin            throws InterruptedException, ExecutionException, TimeoutException {
102654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        Throwable thrown;
103654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        thrown = timeoutSeconds == 0
104654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                ? result.get()
105654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin                : result.get(timeoutSeconds, TimeUnit.SECONDS);
106654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin        return thrown;
107654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin    }
108654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin}
109654f21617c60f23069912900a1e1ef11e9e1c742Paul Duffin
110