/* * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Created by IntelliJ IDEA. * User: mike * Date: Jun 7, 2002 * Time: 8:27:04 PM * To change template for new class use * Code Style | Class Templates options (Tools | IDE Options). */ package com.intellij; import com.intellij.idea.RecordExecution; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.testFramework.*; import com.intellij.util.ArrayUtil; import junit.framework.*; import org.jetbrains.annotations.Nullable; import org.junit.runner.Description; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.NoTestsRemainException; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @SuppressWarnings({"HardCodedStringLiteral", "CallToPrintStackTrace", "UseOfSystemOutOrSystemErr"}) public class TestAll implements Test { static { Logger.setFactory(TestLoggerFactory.class); } private final TestCaseLoader myTestCaseLoader; private long myStartTime = 0; private boolean myInterruptedByOutOfTime = false; private long myLastTestStartTime = 0; private String myLastTestClass; private int myRunTests = -1; private boolean mySavingMemorySnapshot; private static final int SAVE_MEMORY_SNAPSHOT = 1; private static final int START_GUARD = 2; private static final int RUN_GC = 4; private static final int CHECK_MEMORY = 8; private static final int FILTER_CLASSES = 16; public static int ourMode = SAVE_MEMORY_SNAPSHOT /*| START_GUARD | RUN_GC | CHECK_MEMORY*/ | FILTER_CLASSES; private static final boolean PERFORMANCE_TESTS_ONLY = System.getProperty(TestCaseLoader.PERFORMANCE_TESTS_ONLY_FLAG) != null; private int myLastTestTestMethodCount = 0; public static final int MAX_FAILURE_TEST_COUNT = 150; private TestRecorder myTestRecorder; private static final Filter PERFORMANCE_ONLY = new Filter() { @Override public boolean shouldRun(Description description) { String className = description.getClassName(); String methodName = description.getMethodName(); return className != null && hasPerformance(className) || methodName != null && hasPerformance(methodName); } @Override public String describe() { return "Performance Tests Only"; } }; private static final Filter NO_PERFORMANCE = new Filter() { @Override public boolean shouldRun(Description description) { return !PERFORMANCE_ONLY.shouldRun(description); } @Override public String describe() { return "All Except Performance"; } }; @Override public int countTestCases() { List<Class> classes = myTestCaseLoader.getClasses(); int count = 0; for (final Object aClass : classes) { Class testCaseClass = (Class)aClass; Test test = getTest(testCaseClass); if (test != null) count += test.countTestCases(); } return count; } private void beforeFirstTest() { if ((ourMode & START_GUARD) != 0) { Thread timeAndMemoryGuard = new Thread() { @Override public void run() { log("Starting Time and Memory Guard"); while (true) { try { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } // check for time spent on current test if (myLastTestStartTime != 0) { long currTime = System.currentTimeMillis(); long secondsSpent = (currTime - myLastTestStartTime) / 1000L; Thread currentThread = getCurrentThread(); if (!mySavingMemorySnapshot) { if (secondsSpent > PlatformTestCase.ourTestTime * myLastTestTestMethodCount) { UsefulTestCase.printThreadDump(); log("Interrupting current Test (out of time)! Test class: "+ myLastTestClass +" Seconds spent = " + secondsSpent); myInterruptedByOutOfTime = true; if (currentThread != null) { currentThread.interrupt(); if (!currentThread.isInterrupted()) { //noinspection deprecation currentThread.stop(new RuntimeException("Current Test Interrupted: OUT OF TIME!")); } break; } } } } } catch (Exception e) { e.printStackTrace(); } } log("Time and Memory Guard finished."); } }; timeAndMemoryGuard.setDaemon(true); timeAndMemoryGuard.start(); } myStartTime = System.currentTimeMillis(); } private static Thread getCurrentThread() { if (PlatformTestCase.ourTestThread != null) { return PlatformTestCase.ourTestThread; } else return LightPlatformTestCase.ourTestThread; } private void addErrorMessage(TestResult testResult, String message) { String processedTestsMessage = myRunTests <= 0 ? "None of tests was run" : myRunTests + " tests processed"; try { testResult.startTest(this); testResult.addError(this, new Throwable(processedTestsMessage + " before: " + message)); testResult.endTest(this); } catch (Exception e) { e.printStackTrace(); } } @Override public void run(final TestResult testResult) { loadTestRecorder(); List<Class> classes = myTestCaseLoader.getClasses(); int totalTests = classes.size(); for (final Class aClass : classes) { boolean recording = false; if (myTestRecorder != null && shouldRecord(aClass)) { myTestRecorder.beginRecording(aClass, (RecordExecution) aClass.getAnnotation(RecordExecution.class)); recording = true; } try { runNextTest(testResult, totalTests, aClass); } finally { if (recording) { myTestRecorder.endRecording(); } } if (testResult.shouldStop()) break; } tryGc(10); } private boolean shouldRecord(Class aClass) { if (aClass.getAnnotation(RecordExecution.class) != null) { return true; } return false; } private void loadTestRecorder() { String recorderClassName = System.getProperty("test.recorder.class"); if (recorderClassName != null) { try { Class<?> recorderClass = Class.forName(recorderClassName); myTestRecorder = (TestRecorder) recorderClass.newInstance(); } catch (ClassNotFoundException e) { System.out.println("CNFE loading test recorder class: " + e); } catch (InstantiationException e) { System.out.println("InstantiationException loading test recorder class: " + e); } catch (IllegalAccessException e) { System.out.println("IAE loading test recorder class: " + e); } } } private void runNextTest(final TestResult testResult, int totalTests, Class testCaseClass) { myRunTests++; if (!checkAvaliableMemory(35, testResult)) { testResult.stop(); return; } if (testResult.errorCount() + testResult.failureCount() > MAX_FAILURE_TEST_COUNT) { addErrorMessage(testResult, "Too many errors. Tests stopped. Total " + myRunTests + " of " + totalTests + " tests run"); testResult.stop(); return; } if (myStartTime == 0) { boolean ourClassLoader = getClass().getClassLoader().getClass().getName().startsWith("com.intellij."); if (!ourClassLoader) { beforeFirstTest(); } } else { if (myInterruptedByOutOfTime) { addErrorMessage(testResult, "Current Test Interrupted: OUT OF TIME! Class = " + myLastTestClass + " Total " + myRunTests + " of " + totalTests + " tests run"); testResult.stop(); return; } } log("\nRunning " + testCaseClass.getName()); final Test test = getTest(testCaseClass); if (test == null) return; myLastTestClass = null; myLastTestClass = testCaseClass.getName(); myLastTestStartTime = System.currentTimeMillis(); myLastTestTestMethodCount = test.countTestCases(); try { test.run(testResult); } catch (Throwable t) { if (t instanceof OutOfMemoryError) { if ((ourMode & SAVE_MEMORY_SNAPSHOT) != 0) { try { mySavingMemorySnapshot = true; log("OutOfMemoryError detected. Saving memory snapshot started"); } finally { log("Saving memory snapshot finished"); mySavingMemorySnapshot = false; } } } testResult.addError(test, t); } } private boolean checkAvaliableMemory(int neededMemory, TestResult testResult) { if ((ourMode & CHECK_MEMORY) == 0) return true; boolean possibleOutOfMemoryError = possibleOutOfMemory(neededMemory); if (possibleOutOfMemoryError) { tryGc(5); possibleOutOfMemoryError = possibleOutOfMemory(neededMemory); if (possibleOutOfMemoryError) { log("OutOfMemoryError: dumping memory"); Runtime runtime = Runtime.getRuntime(); long total = runtime.totalMemory(); long free = runtime.freeMemory(); String errorMessage = "Too much memory used. Total: " + total + " free: " + free + " used: " + (total - free) + "\n"; addErrorMessage(testResult, errorMessage); } } return !possibleOutOfMemoryError; } private static boolean possibleOutOfMemory(int neededMemory) { Runtime runtime = Runtime.getRuntime(); long maxMemory = runtime.maxMemory(); long realFreeMemory = runtime.freeMemory() + (maxMemory - runtime.totalMemory()); long meg = 1024 * 1024; long needed = neededMemory * meg; return realFreeMemory < needed; } private static boolean isPerformanceTestsRun() { return PERFORMANCE_TESTS_ONLY; } @Nullable private static Test getTest(final Class testCaseClass) { if ((testCaseClass.getModifiers() & Modifier.PUBLIC) == 0) return null; Method suiteMethod = safeFindMethod(testCaseClass, "suite"); if (suiteMethod != null && !isPerformanceTestsRun()) { try { return (Test)suiteMethod.invoke(null, ArrayUtil.EMPTY_CLASS_ARRAY); } catch (Exception e) { System.err.println("Failed to execute suite ()"); e.printStackTrace(); } } else { if (TestRunnerUtil.isJUnit4TestClass(testCaseClass)) { JUnit4TestAdapter adapter = new JUnit4TestAdapter(testCaseClass); if (!hasPerformance(testCaseClass.getSimpleName()) || !isPerformanceTestsRun()) { try { adapter.filter(isPerformanceTestsRun() ? PERFORMANCE_ONLY : NO_PERFORMANCE); } catch (NoTestsRemainException e1) { // Ignore } } return adapter; } final int[] testsCount = {0}; TestSuite suite = new TestSuite(testCaseClass) { @Override public void addTest(Test test) { if (!(test instanceof TestCase)) { testsCount[0]++; super.addTest(test); } else { String name = ((TestCase)test).getName(); if ("warning".equals(name)) return; // Mute TestSuite's "no tests found" warning if (isPerformanceTestsRun() ^ (hasPerformance(name) || hasPerformance(testCaseClass.getSimpleName()))) return; Method method = findTestMethod((TestCase)test); if (method == null || !TestCaseLoader.isBombed(method)) { testsCount[0]++; super.addTest(test); } } } @Nullable private Method findTestMethod(final TestCase testCase) { return safeFindMethod(testCase.getClass(), testCase.getName()); } }; return testsCount[0] > 0 ? suite : null; } return null; } private static boolean hasPerformance(String name) { return name.toLowerCase().contains("performance"); } @Nullable private static Method safeFindMethod(Class klass, String name) { try { return klass.getMethod(name); } catch (NoSuchMethodException e) { return null; } } private static Set<String> normalizePaths(String[] array) { Set<String> answer = new LinkedHashSet<String>(array.length); for (String path : array) { answer.add(path.replace('\\', '/')); } return answer; } public static String[] getClassRoots() { String testRoots = System.getProperty("test.roots"); if (testRoots != null) { System.out.println("Collecting tests from roots specified by test.roots property: " + testRoots); return testRoots.split(";"); } final ClassLoader loader = TestAll.class.getClassLoader(); if (loader instanceof URLClassLoader) { final URL[] urls = ((URLClassLoader)loader).getURLs(); final String[] classLoaderRoots = new String[urls.length]; for (int i = 0; i < urls.length; i++) { classLoaderRoots[i] = VfsUtil.urlToPath(VfsUtil.convertFromUrl(urls[i])); } System.out.println("Collecting tests from classloader: " + Arrays.toString(classLoaderRoots)); return classLoaderRoots; } return System.getProperty("java.class.path").split(File.pathSeparator); } public TestAll(String packageRoot) throws Throwable { this(packageRoot, getClassRoots()); } public TestAll(String packageRoot, String... classRoots) throws IOException, ClassNotFoundException { String classFilterName = "tests/testGroups.properties"; if (Boolean.parseBoolean(System.getProperty("idea.ignore.predefined.groups")) || (ourMode & FILTER_CLASSES) == 0) { classFilterName = ""; } myTestCaseLoader = new TestCaseLoader(classFilterName, isPerformanceTestsRun()); myTestCaseLoader.addFirstTest(Class.forName("_FirstInSuiteTest")); myTestCaseLoader.addLastTest(Class.forName("_LastInSuiteTest")); fillTestCases(myTestCaseLoader, packageRoot, classRoots); } public static void fillTestCases(TestCaseLoader testCaseLoader, String packageRoot, String... classRoots) throws IOException { for (String classRoot : classRoots) { int oldCount = testCaseLoader.getClasses().size(); ClassFinder classFinder = new ClassFinder(new File(FileUtil.toSystemDependentName(classRoot)), packageRoot); testCaseLoader.loadTestCases(classFinder.getClasses()); int newCount = testCaseLoader.getClasses().size(); if (newCount != oldCount) { System.out.println("Loaded " + (newCount - oldCount) + " tests from class root " + classRoot); } } if (testCaseLoader.getClasses().size() == 1) { testCaseLoader.clearClasses(); } log("Number of test classes found: " + testCaseLoader.getClasses().size()); } private static void log(String message) { TeamCityLogger.info(message); } // [myakovlev] Do not delete - it is for debugging public static void tryGc(int times) { if ((ourMode & RUN_GC) == 0) return; for (int qqq = 1; qqq < times; qqq++) { try { Thread.sleep(qqq * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.gc(); //long mem = Runtime.getRuntime().totalMemory(); log("Runtime.getRuntime().totalMemory() = " + Runtime.getRuntime().totalMemory()); } } }