/** * Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite * contributors * * This file is part of EvoSuite. * * EvoSuite is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3.0 of the License, or * (at your option) any later version. * * EvoSuite is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>. */ package org.evosuite.testcase.execution; import java.io.PrintStream; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.evosuite.Properties; import org.evosuite.TestGenerationContext; import org.evosuite.ga.stoppingconditions.MaxStatementsStoppingCondition; import org.evosuite.ga.stoppingconditions.MaxTestsStoppingCondition; import org.evosuite.runtime.LoopCounter; import org.evosuite.runtime.Runtime; import org.evosuite.runtime.javaee.db.DBManager; import org.evosuite.runtime.sandbox.PermissionStatistics; import org.evosuite.runtime.sandbox.Sandbox; import org.evosuite.runtime.util.JOptionPaneInputs; import org.evosuite.runtime.util.SystemInUtil; import org.evosuite.setup.TestCluster; import org.evosuite.testcase.TestCase; import org.evosuite.testcase.execution.reset.ClassReInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p> * The test case executor manages thread creation/deletion to execute a test * case * </p> * * <p> * WARNING: never give "privileged" rights in MSecurityManager to any of the * threads generated here * </p> * * @author Gordon Fraser */ public class TestCaseExecutor implements ThreadFactory { /** * Used to identify the threads spawn by the SUT */ public static final String TEST_EXECUTION_THREAD_GROUP = "Test_Execution_Group"; /** * Name used to define the threads spawn by this factory */ public static final String TEST_EXECUTION_THREAD = "TEST_EXECUTION_THREAD"; private static final Logger logger = LoggerFactory.getLogger(TestCaseExecutor.class); private static final PrintStream systemOut = System.out; private static final PrintStream systemErr = System.err; private static TestCaseExecutor instance = null; private ExecutorService executor; private Thread currentThread = null; private ThreadGroup threadGroup = null; // private static ExecutorService executor = // Executors.newCachedThreadPool(); private Set<ExecutionObserver> observers; private final Set<Thread> stalledThreads = new HashSet<Thread>(); /** Constant <code>timeExecuted=0</code> */ public static long timeExecuted = 0; /** Constant <code>testsExecuted=0</code> */ public static int testsExecuted = 0; /** * Used when we spawn a new thread to give a unique name */ public volatile int threadCounter; static { PermissionStatistics.getInstance().setThreadGroupToMonitor(TEST_EXECUTION_THREAD_GROUP); } /** * <p> * Getter for the field <code>instance</code>. * </p> * * @return a {@link org.evosuite.testcase.execution.TestCaseExecutor} * object. */ public static synchronized TestCaseExecutor getInstance() { if (instance == null) instance = new TestCaseExecutor(); return instance; } /** * Execute a test case * * @param test * The test case to execute * @return Result of the execution */ public static ExecutionResult runTest(TestCase test) { ExecutionResult result = new ExecutionResult(test, null); try { TestCaseExecutor executor = getInstance(); logger.debug("Executing test"); result = executor.execute(test); MaxStatementsStoppingCondition.statementsExecuted(result.getExecutedStatements()); } catch (Exception e) { logger.error("TG: Exception caught: ", e); throw new Error(e); } return result; } private TestCaseExecutor() { executor = Executors.newSingleThreadExecutor(this); newObservers(); } public static class TimeoutExceeded extends RuntimeException { private static final long serialVersionUID = -5314228165430676893L; } /** * <p> * setup * </p> */ public void setup() { // start own thread } /** * <p> * pullDown * </p> */ public static void pullDown() { if (instance != null) { if (instance.executor != null) { instance.executor.shutdownNow(); instance.executor = null; } } } /** * <p> * initExecutor * </p> */ public static void initExecutor() { if (instance != null) { if (instance.executor == null) { logger.info("TestCaseExecutor instance is non-null, but its actual executor is null"); instance.executor = Executors.newSingleThreadExecutor(instance); } else { instance.executor = Executors.newSingleThreadExecutor(instance); } } } /** * <p> * addObserver * </p> * * @param observer * a {@link org.evosuite.testcase.execution.ExecutionObserver} * object. */ public void addObserver(ExecutionObserver observer) { if (!observers.contains(observer)) { logger.debug("Adding observer"); observers.add(observer); } // FIXXME: Find proper solution for this // for (ExecutionObserver o : observers) // if (o.getClass().equals(observer.getClass())) // return; } /** * <p> * removeObserver * </p> * * @param observer * a {@link org.evosuite.testcase.execution.ExecutionObserver} * object. */ public void removeObserver(ExecutionObserver observer) { if (observers.contains(observer)) { logger.debug("Removing observer"); observers.remove(observer); } } /** * <p> * newObservers * </p> */ public void newObservers() { observers = new LinkedHashSet<>(); } public Set<ExecutionObserver> getExecutionObservers() { return new LinkedHashSet<ExecutionObserver>(observers); } private void resetObservers() { for (ExecutionObserver observer : observers) { observer.clear(); } } /** * Execute a test case on a new scope * * @param tc * a {@link org.evosuite.testcase.TestCase} object. * @return a {@link org.evosuite.testcase.execution.ExecutionResult} object. */ public ExecutionResult execute(TestCase tc) { ExecutionResult result = execute(tc, Properties.TIMEOUT); return result; } /** * Execute a test case on a new scope * * @param tc * a {@link org.evosuite.testcase.TestCase} object. * @return a {@link org.evosuite.testcase.execution.ExecutionResult} object. */ public ExecutionResult execute(TestCase tc, int timeout) { Scope scope = new Scope(); ExecutionResult result = execute(tc, scope, timeout); if (Properties.RESET_STATIC_FIELDS) { logger.debug("Resetting classes after execution"); ClassReInitializer.getInstance().reInitializeClassesAfterTestExecution(tc, result); } return result; } /** * Execute a test case on an existing scope * * @param tc * a {@link org.evosuite.testcase.TestCase} object. * @param scope * a {@link org.evosuite.testcase.execution.Scope} object. * @return a {@link org.evosuite.testcase.execution.ExecutionResult} object. */ @SuppressWarnings("deprecation") private ExecutionResult execute(TestCase tc, Scope scope, int timeout) { ExecutionTracer.getExecutionTracer().clear(); // TODO: Re-insert! resetObservers(); ExecutionObserver.setCurrentTest(tc); MaxTestsStoppingCondition.testExecuted(); Runtime.getInstance().resetRuntime(); long startTime = System.currentTimeMillis(); TimeoutHandler<ExecutionResult> handler = new TimeoutHandler<ExecutionResult>(); // #TODO steenbuck could be nicer (TestRunnable should be an interface TestRunnable callable = new TestRunnable(tc, scope, observers); callable.storeCurrentThreads(); /* * FIXME: the sequence of "catch" with calls to "result.set" should be * re-factored, as these things should be (already) handled in * TestRunnable.call. If not, it should be explained, as it is not * necessarily obvious why some checks are done here, and others in * TestRunnable */ try { // ExecutionResult result = task.get(timeout, // TimeUnit.MILLISECONDS); ExecutionResult result = null; // important to call it before setting up the sandbox SystemInUtil.getInstance().initForTestCase(); JOptionPaneInputs.getInstance().initForTestCase(); Sandbox.goingToExecuteSUTCode(); TestGenerationContext.getInstance().goingToExecuteSUTCode(); try { result = handler.execute(callable, executor, timeout, Properties.CPU_TIMEOUT); } finally { Sandbox.doneWithExecutingSUTCode(); TestGenerationContext.getInstance().doneWithExecutingSUTCode(); } PermissionStatistics.getInstance().countThreads(threadGroup.activeCount()); result.setSecurityException(PermissionStatistics.getInstance().getAndResetExceptionInfo()); /* * TODO: this will need proper care when we ll start to handle * threads in the search. */ callable.killAndJoinClientThreads(); /* * TODO: we might want to initialize the ExecutionResult here, once * we waited for all SUT threads to finish */ long endTime = System.currentTimeMillis(); timeExecuted += endTime - startTime; testsExecuted++; return result; } catch (ThreadDeath t) { logger.warn("Caught ThreadDeath during test execution"); ExecutionResult result = new ExecutionResult(tc, null); result.setThrownExceptions(callable.getExceptionsThrown()); result.setTrace(ExecutionTracer.getExecutionTracer().getTrace()); ExecutionTracer.getExecutionTracer().clear(); return result; } catch (InterruptedException e1) { logger.info("InterruptedException"); ExecutionResult result = new ExecutionResult(tc, null); result.setThrownExceptions(callable.getExceptionsThrown()); result.setTrace(ExecutionTracer.getExecutionTracer().getTrace()); ExecutionTracer.getExecutionTracer().clear(); return result; } catch (ExecutionException e1) { /* * An ExecutionException at this point, is most likely an error in * evosuite. As exceptions from the tested code are caught before * this. */ System.setOut(systemOut); System.setErr(systemErr); logger.error("ExecutionException (this is likely a serious error in the framework)", e1); ExecutionResult result = new ExecutionResult(tc, null); result.setThrownExceptions(callable.getExceptionsThrown()); result.setTrace(ExecutionTracer.getExecutionTracer().getTrace()); ExecutionTracer.getExecutionTracer().clear(); if (e1.getCause() instanceof Error) { // an error was thrown // somewhere in evosuite // code throw (Error) e1.getCause(); } else if (e1.getCause() instanceof RuntimeException) { throw (RuntimeException) e1.getCause(); } return result; // FIXME: is this reachable? } catch (TimeoutException e1) { // System.setOut(systemOut); // System.setErr(systemErr); if (Properties.LOG_TIMEOUT) { logger.warn("Timeout occurred for " + Properties.TARGET_CLASS); } logger.info("TimeoutException, need to stop runner", e1); ExecutionTracer.setKillSwitch(true); try { handler.getLastTask().get(Properties.SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS); } catch (InterruptedException e2) { } catch (ExecutionException e2) { } catch (TimeoutException e2) { } // task.cancel(true); if (!callable.isRunFinished()) { logger.info("Cancelling thread:"); for (StackTraceElement elem : currentThread.getStackTrace()) { logger.info(elem.toString()); } logger.info(tc.toCode()); boolean loopCounter = LoopCounter.getInstance().isActivated(); while (isInStaticInit()) { // LoopCounter and killswitch check the stacktrace often // and that is costly - to speed things up we deactivate it // until we're outside the static constructor LoopCounter.getInstance().setActive(false); ExecutionTracer.setKillSwitch(false); logger.info("Run still not finished, but awaiting for static initializer to finish."); try { executor.awaitTermination(Properties.SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { logger.info("Interrupted"); e.printStackTrace(); } } LoopCounter.getInstance().setActive(loopCounter); ExecutionTracer.setKillSwitch(true); if (!callable.isRunFinished()) { handler.getLastTask().cancel(true); logger.info("Run not finished, waiting..."); try { executor.awaitTermination(Properties.SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { logger.info("Interrupted"); e.printStackTrace(); } } if (!callable.isRunFinished()) { logger.info("Run still not finished, replacing executor."); try { executor.shutdownNow(); if (currentThread.isAlive()) { logger.info("Thread survived - unsafe operation."); for (StackTraceElement element : currentThread.getStackTrace()) { logger.info(element.toString()); } logger.info("Killing thread:"); for (StackTraceElement elem : currentThread.getStackTrace()) { logger.info(elem.toString()); } currentThread.stop(); } } catch (ThreadDeath t) { logger.info("ThreadDeath."); } catch (Throwable t) { logger.info("Throwable: " + t); } ExecutionTracer.disable(); executor = Executors.newSingleThreadExecutor(this); } } else { logger.info("Run is finished - " + currentThread.isAlive() + ": " + getNumStalledThreads()); } ExecutionTracer.disable(); // TODO: If this is true, is this problematic? if (Sandbox.isOnAndExecutingSUTCode()) { Sandbox.doneWithExecutingSUTCode(); TestGenerationContext.getInstance().doneWithExecutingSUTCode(); } ExecutionResult result = new ExecutionResult(tc, null); result.setThrownExceptions(callable.getExceptionsThrown()); result.reportNewThrownException(tc.size(), new TestCaseExecutor.TimeoutExceeded()); result.setTrace(ExecutionTracer.getExecutionTracer().getTrace()); ExecutionTracer.getExecutionTracer().clear(); ExecutionTracer.setKillSwitch(false); ExecutionTracer.enable(); System.setOut(systemOut); System.setErr(systemErr); return result; } finally { if (threadGroup != null) PermissionStatistics.getInstance().countThreads(threadGroup.activeCount()); TestCluster.getInstance().handleRuntimeAccesses(tc); } } private boolean isInStaticInit() { for (StackTraceElement elem : currentThread.getStackTrace()) { if (elem.getMethodName().equals("<clinit>")) return true; if (elem.getMethodName().equals("loadClass") && elem.getClassName() .equals(org.evosuite.instrumentation.InstrumentingClassLoader.class.getCanonicalName())) return true; // CFontManager is responsible for loading fonts // which can take seconds if (elem.getClassName().equals("sun.font.CFontManager")) return true; // JDBC initialisation can take a while, and interrupting can mess things up if (elem.getClassName().equals(DBManager.class.getCanonicalName()) && elem.getMethodName().equals("initDB")) return true; } return false; } /** * <p> * getNumStalledThreads * </p> * * @return a int. */ public int getNumStalledThreads() { Iterator<Thread> iterator = stalledThreads.iterator(); while (iterator.hasNext()) { Thread t = iterator.next(); if (!t.isAlive()) { iterator.remove(); } } return stalledThreads.size(); } /** {@inheritDoc} */ @Override public Thread newThread(Runnable r) { if (currentThread != null && currentThread.isAlive()) { currentThread.setPriority(Thread.MIN_PRIORITY); stalledThreads.add(currentThread); logger.info("Current number of stalled threads: " + getNumStalledThreads()); } else { logger.info("No stalled threads"); } if (threadGroup != null) { PermissionStatistics.getInstance().countThreads(threadGroup.activeCount()); } threadGroup = new ThreadGroup(TEST_EXECUTION_THREAD_GROUP); currentThread = new Thread(threadGroup, r); currentThread.setName(TEST_EXECUTION_THREAD + "_" + threadCounter); threadCounter++; currentThread.setContextClassLoader(TestGenerationContext.getInstance().getClassLoaderForSUT()); ExecutionTracer.setThread(currentThread); return currentThread; } public void setExecutionObservers(Set<ExecutionObserver> observers) { this.observers = observers; } }