JUnit4-FailOnTimeout.java的源代码 解读与分析

来源:互联网 发布:专业电气制图软件 编辑:程序博客网 时间:2024/05/16 01:31

JUnit4-FailOnTimeout.java的源代码

用于判断测试是否超时
当发生超时异常时,lookForStuckThread的作用,以及ThreadGroup这部分的处理不太了解,在这里做个标记…看将来什么时候,能看懂…插入时间戳(星期四, 17. 八月 2017 02:09上午)

删减版

FailOnTimeoutstatement的执行放到了一个后台线程(特点:如果没有运行的非后台线程,后台线程会被杀死,程序退出)的运行之中

使用Callable<T>, 能够从任务(线程)中获得返回值,泛型T代表的就是方法call()返回的类型。
FutureTask的构造函数接受Callable,同时调用get(long timeout, TimeUnit unit)方法能够用于判断在规定的时间内能否获取Callable返回结果。这里的timeout就是我们设置的超时时间(在@Test中赋值,或者定义规则@Rule@ClassRule)。

为了使执行时间更加精确,需要同步statement和get方法的执行,这里使用到了CountDownLatch
CountDownLatch可以设置一个初始值,任何在这个对象上调用await()方法都将阻塞,直到这个计数为0。同时通过调用countDown()来减小这个值,来触发阻塞的方法的执行。CountDownLatch只能够触发一次。

public class FailOnTimeout extends Statement {    private final Statement originalStatement;    private final TimeUnit timeUnit;    private final long timeout;    @Override    public void evaluate() throws Throwable {        CallableStatement callable = new CallableStatement();        FutureTask<Throwable> task = new FutureTask<Throwable>(callable);        threadGroup = new ThreadGroup("FailOnTimeoutGroup");        Thread thread = new Thread(threadGroup, task, "Time-limited test");        thread.setDaemon(true);        thread.start();        callable.awaitStarted();        Throwable throwable = getResult(task, thread);        if (throwable != null) {            throw throwable;        }    }    private Throwable getResult(FutureTask<Throwable> task, Thread thread) {        try {            if (timeout > 0) {                return task.get(timeout, timeUnit);            } else {                return task.get();            }        } catch (InterruptedException e) {            return e; // caller will re-throw; no need to call Thread.interrupt()        } catch (ExecutionException e) {            // test failed; have caller re-throw the exception thrown by the test            return e.getCause();        } catch (TimeoutException e) {            return createTimeoutException(thread);        }    }    private class CallableStatement implements Callable<Throwable> {        private final CountDownLatch startLatch = new CountDownLatch(1);        public Throwable call() throws Exception {            try {                startLatch.countDown();                originalStatement.evaluate();            } catch (Exception e) {                throw e;            } catch (Throwable e) {                return e;            }            return null;        }        public void awaitStarted() throws InterruptedException {            startLatch.await();        }    }}

未删减版

package org.junit.internal.runners.statements;import java.lang.management.ManagementFactory;import java.lang.management.ThreadMXBean;import java.util.Arrays;import java.util.concurrent.Callable;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;import java.util.concurrent.TimeUnit;import java.util.concurrent.TimeoutException;import org.junit.runners.model.MultipleFailureException;import org.junit.runners.model.Statement;import org.junit.runners.model.TestTimedOutException;public class FailOnTimeout extends Statement {    private final Statement originalStatement;    private final TimeUnit timeUnit;    private final long timeout;    private final boolean lookForStuckThread;    private volatile ThreadGroup threadGroup = null;    /**     * Returns a new builder for building an instance.     *     * @since 4.12     */    public static Builder builder() {        return new Builder();    }    /**     * Creates an instance wrapping the given statement with the given timeout in milliseconds.     *     * @param statement the statement to wrap     * @param timeoutMillis the timeout in milliseconds     * @deprecated use {@link #builder()} instead.     */    @Deprecated    public FailOnTimeout(Statement statement, long timeoutMillis) {        this(builder().withTimeout(timeoutMillis, TimeUnit.MILLISECONDS), statement);    }    private FailOnTimeout(Builder builder, Statement statement) {        originalStatement = statement;        timeout = builder.timeout;        timeUnit = builder.unit;        lookForStuckThread = builder.lookForStuckThread;    }    /**     * Builder for {@link FailOnTimeout}.     *     * @since 4.12     */    public static class Builder {        private boolean lookForStuckThread = false;        private long timeout = 0;        private TimeUnit unit = TimeUnit.SECONDS;        private Builder() {        }        /**         * Specifies the time to wait before timing out the test.         *         * <p>If this is not called, or is called with a {@code timeout} of         * {@code 0}, the returned {@code Statement} will wait forever for the         * test to complete, however the test will still launch from a separate         * thread. This can be useful for disabling timeouts in environments         * where they are dynamically set based on some property.         *         * @param timeout the maximum time to wait         * @param unit the time unit of the {@code timeout} argument         * @return {@code this} for method chaining.         */        public Builder withTimeout(long timeout, TimeUnit unit) {            if (timeout < 0) {                throw new IllegalArgumentException("timeout must be non-negative");            }            if (unit == null) {                throw new NullPointerException("TimeUnit cannot be null");            }            this.timeout = timeout;            this.unit = unit;            return this;        }        /**         * Specifies whether to look for a stuck thread.  If a timeout occurs and this         * feature is enabled, the test will look for a thread that appears to be stuck         * and dump its backtrace.  This feature is experimental.  Behavior may change         * after the 4.12 release in response to feedback.         *         * @param enable {@code true} to enable the feature         * @return {@code this} for method chaining.         */        public Builder withLookingForStuckThread(boolean enable) {            this.lookForStuckThread = enable;            return this;        }        /**         * Builds a {@link FailOnTimeout} instance using the values in this builder,         * wrapping the given statement.         *         * @param statement         */        public FailOnTimeout build(Statement statement) {            if (statement == null) {                throw new NullPointerException("statement cannot be null");            }            return new FailOnTimeout(this, statement);        }    }    @Override    public void evaluate() throws Throwable {        CallableStatement callable = new CallableStatement();        FutureTask<Throwable> task = new FutureTask<Throwable>(callable);        threadGroup = new ThreadGroup("FailOnTimeoutGroup");        Thread thread = new Thread(threadGroup, task, "Time-limited test");        thread.setDaemon(true);        thread.start();        callable.awaitStarted();        Throwable throwable = getResult(task, thread);        if (throwable != null) {            throw throwable;        }    }    /**     * Wait for the test task, returning the exception thrown by the test if the     * test failed, an exception indicating a timeout if the test timed out, or     * {@code null} if the test passed.     */    private Throwable getResult(FutureTask<Throwable> task, Thread thread) {        try {            if (timeout > 0) {                return task.get(timeout, timeUnit);            } else {                return task.get();            }        } catch (InterruptedException e) {            return e; // caller will re-throw; no need to call Thread.interrupt()        } catch (ExecutionException e) {            // test failed; have caller re-throw the exception thrown by the test            return e.getCause();        } catch (TimeoutException e) {            return createTimeoutException(thread);        }    }    private Exception createTimeoutException(Thread thread) {        StackTraceElement[] stackTrace = thread.getStackTrace();        final Thread stuckThread = lookForStuckThread ? getStuckThread(thread) : null;        Exception currThreadException = new TestTimedOutException(timeout, timeUnit);        if (stackTrace != null) {            currThreadException.setStackTrace(stackTrace);            thread.interrupt();        }        if (stuckThread != null) {            Exception stuckThreadException =                 new Exception ("Appears to be stuck in thread " +                               stuckThread.getName());            stuckThreadException.setStackTrace(getStackTrace(stuckThread));            return new MultipleFailureException(                Arrays.<Throwable>asList(currThreadException, stuckThreadException));        } else {            return currThreadException;        }    }    /**     * Retrieves the stack trace for a given thread.     * @param thread The thread whose stack is to be retrieved.     * @return The stack trace; returns a zero-length array if the thread has      * terminated or the stack cannot be retrieved for some other reason.     */    private StackTraceElement[] getStackTrace(Thread thread) {        try {            return thread.getStackTrace();        } catch (SecurityException e) {            return new StackTraceElement[0];        }    }    /**     * Determines whether the test appears to be stuck in some thread other than     * the "main thread" (the one created to run the test).  This feature is experimental.     * Behavior may change after the 4.12 release in response to feedback.     * @param mainThread The main thread created by {@code evaluate()}     * @return The thread which appears to be causing the problem, if different from     * {@code mainThread}, or {@code null} if the main thread appears to be the     * problem or if the thread cannot be determined.  The return value is never equal      * to {@code mainThread}.     */    private Thread getStuckThread(Thread mainThread) {        if (threadGroup == null) {            return null;        }        Thread[] threadsInGroup = getThreadArray(threadGroup);        if (threadsInGroup == null) {            return null;        }        // Now that we have all the threads in the test's thread group: Assume that        // any thread we're "stuck" in is RUNNABLE.  Look for all RUNNABLE threads.         // If just one, we return that (unless it equals threadMain).  If there's more        // than one, pick the one that's using the most CPU time, if this feature is        // supported.        Thread stuckThread = null;        long maxCpuTime = 0;        for (Thread thread : threadsInGroup) {            if (thread.getState() == Thread.State.RUNNABLE) {                long threadCpuTime = cpuTime(thread);                if (stuckThread == null || threadCpuTime > maxCpuTime) {                    stuckThread = thread;                    maxCpuTime = threadCpuTime;                }            }                       }        return (stuckThread == mainThread) ? null : stuckThread;    }    /**     * Returns all active threads belonging to a thread group.       * @param group The thread group.     * @return The active threads in the thread group.  The result should be a     * complete list of the active threads at some point in time.  Returns {@code null}     * if this cannot be determined, e.g. because new threads are being created at an     * extremely fast rate.     */    private Thread[] getThreadArray(ThreadGroup group) {        final int count = group.activeCount(); // this is just an estimate        int enumSize = Math.max(count * 2, 100);        int enumCount;        Thread[] threads;        int loopCount = 0;        while (true) {            threads = new Thread[enumSize];            enumCount = group.enumerate(threads);            if (enumCount < enumSize) {                break;            }            // if there are too many threads to fit into the array, enumerate's result            // is >= the array's length; therefore we can't trust that it returned all            // the threads.  Try again.            enumSize += 100;            if (++loopCount >= 5) {                return null;            }            // threads are proliferating too fast for us.  Bail before we get into             // trouble.        }        return copyThreads(threads, enumCount);    }    /**     * Returns an array of the first {@code count} Threads in {@code threads}.      * (Use instead of Arrays.copyOf to maintain compatibility with Java 1.5.)     * @param threads The source array.     * @param count The maximum length of the result array.     * @return The first {@count} (at most) elements of {@code threads}.     */    private Thread[] copyThreads(Thread[] threads, int count) {        int length = Math.min(count, threads.length);        Thread[] result = new Thread[length];        for (int i = 0; i < length; i++) {            result[i] = threads[i];        }        return result;    }    /**     * Returns the CPU time used by a thread, if possible.     * @param thr The thread to query.     * @return The CPU time used by {@code thr}, or 0 if it cannot be determined.     */    private long cpuTime (Thread thr) {        ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();        if (mxBean.isThreadCpuTimeSupported()) {            try {                return mxBean.getThreadCpuTime(thr.getId());            } catch (UnsupportedOperationException e) {            }        }        return 0;    }    private class CallableStatement implements Callable<Throwable> {        private final CountDownLatch startLatch = new CountDownLatch(1);        public Throwable call() throws Exception {            try {                startLatch.countDown();                originalStatement.evaluate();            } catch (Exception e) {                throw e;            } catch (Throwable e) {                return e;            }            return null;        }        public void awaitStarted() throws InterruptedException {            startLatch.await();        }    }}