/* * Copyright (C) 2009 The Android Open Source Project * * 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. */ package com.google.coretests; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Enumeration; import java.util.Vector; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestFailure; import junit.framework.TestResult; import junit.framework.TestSuite; import dalvik.annotation.AndroidOnly; import dalvik.annotation.BrokenTest; import dalvik.annotation.KnownFailure; import dalvik.annotation.SideEffect; /** * A special TestSuite implementation that flattens the hierarchy of a given * JUnit TestSuite and removes tests after executing them. This is so the core * tests actually have a chance to succeed, since they do consume quite some * memory and many tests do not (have a chance to) cleanup properly after * themselves. The class also implements our filtering mechanism for tests, so * it becomes easy to only include or exclude tests based on their annotations * (like, say, execute all Android-only tests that are not known to be broken). */ public class CoreTestSuite implements Test { /** * Include all normal tests in the suite. */ public static final int RUN_NORMAL_TESTS = 1; /** * Include all broken tests in the suite. */ public static final int RUN_BROKEN_TESTS = 2; /** * Include all known failures in the suite. */ public static final int RUN_KNOWN_FAILURES = 4; /** * Include all Android-only tests in the suite. */ public static final int RUN_ANDROID_ONLY = 8; /** * Include side-effective tests in the suite. */ public static final int RUN_SIDE_EFFECTS = 16; /** * Include all tests in the suite. */ public static final int RUN_ALL_TESTS = RUN_NORMAL_TESTS | RUN_BROKEN_TESTS | RUN_KNOWN_FAILURES | RUN_SIDE_EFFECTS | RUN_ANDROID_ONLY; /** * Special treatment for known failures: they are expected to fail, so we * throw an Exception if they succeed and accept them failing. */ public static final int INVERT_KNOWN_FAILURES = 32; /** * Run each test in its own VM. */ public static final int ISOLATE_ALL = 64; /** * Run no test in its own VM. */ public static final int ISOLATE_NONE = 128; /** * Be verbose. */ public static final int VERBOSE = 256; public static final int REVERSE = 512; public static final int DRY_RUN = 1024; private final String name; /** * The total number of tests in the original suite. */ protected int fTotalCount; /** * The number of Android-only tests in the original suite. */ protected int fAndroidOnlyCount; /** * The number of broken tests in the original suite. */ protected int fBrokenCount; /** * The number of known failures in the original suite. */ protected int fKnownFailureCount; /** * The number of side-effective tests in the original suite. */ protected int fSideEffectCount; /** * The number of normal (non-annotated) tests in the original suite. */ protected int fNormalCount; /** * The number of ignored tests, that is, the number of tests that were * excluded from this suite due to their annotations. */ protected int fIgnoredCount; /** * Contains the actual test cases in a reverse-ordered, flat list. */ private Vector<Test> fTests = new Vector<Test>(); private TestCase fVictim; private int fStep; private int fFlags; /** * Creates a new CoreTestSuite for the given ordinary JUnit Test (which may * be a TestCase or TestSuite). The CoreTestSuite will be a flattened and * potentially filtered subset of the original JUnit Test. The flags * determine the way we filter. */ public CoreTestSuite(Test suite, int flags, int step, TestCase victim) { super(); name = suite.toString(); fStep = step; addAndFlatten(suite, flags); fVictim = victim; fFlags = flags; } /** * Adds the given ordinary JUnit Test (which may be a TestCase or TestSuite) * to this CoreTestSuite. Note we are storing the tests in reverse order, * so it's easier to remove a finished test from the end of the list. */ private void addAndFlatten(Test test, int flags) { if (test instanceof TestSuite) { TestSuite suite = (TestSuite)test; if ((flags & REVERSE) != 0) { for (int i = suite.testCount() - 1; i >= 0; i--) { addAndFlatten(suite.testAt(i), flags); } } else { for (int i = 0; i < suite.testCount(); i++) { addAndFlatten(suite.testAt(i), flags); } } } else if (test instanceof TestCase) { TestCase testCase = (TestCase)test; boolean ignoreMe = false; boolean isAndroidOnly = hasAnnotation(testCase, AndroidOnly.class); boolean isBrokenTest = hasAnnotation(testCase, BrokenTest.class); boolean isKnownFailure = hasAnnotation(testCase, KnownFailure.class); boolean isSideEffect = hasAnnotation(testCase, SideEffect.class); boolean isNormalTest = !(isAndroidOnly || isBrokenTest || isKnownFailure || isSideEffect); if (isAndroidOnly) { fAndroidOnlyCount++; } if (isBrokenTest) { fBrokenCount++; } if (isKnownFailure) { fKnownFailureCount++; } if (isNormalTest) { fNormalCount++; } if (isSideEffect) { fSideEffectCount++; } if ((flags & RUN_ANDROID_ONLY) == 0 && isAndroidOnly) { ignoreMe = true; } if ((flags & RUN_BROKEN_TESTS) == 0 && isBrokenTest) { ignoreMe = true; } if ((flags & RUN_KNOWN_FAILURES) == 0 && isKnownFailure) { ignoreMe = true; } if (((flags & RUN_NORMAL_TESTS) == 0) && isNormalTest) { ignoreMe = true; } if (((flags & RUN_SIDE_EFFECTS) == 0) && isSideEffect) { ignoreMe = true; } this.fTotalCount++; if (!ignoreMe) { fTests.add(test); } else { this.fIgnoredCount++; } } else { System.out.println("Warning: Don't know how to handle " + test.getClass().getName() + " " + test.toString()); } } /** * Checks whether the given TestCase class has the given annotation. */ @SuppressWarnings("unchecked") private boolean hasAnnotation(TestCase test, Class clazz) { try { Method method = test.getClass().getMethod(test.getName()); return method.getAnnotation(clazz) != null; } catch (Exception e) { // Ignore } return false; } /** * Runs the tests and collects their result in a TestResult. */ public void run(TestResult result) { // Run tests int i = 0; while (fTests.size() != 0 && !result.shouldStop()) { TestCase test = (TestCase)fTests.elementAt(i); Thread.currentThread().setContextClassLoader( test.getClass().getClassLoader()); test.run(result); /* if (fVictim != null) { TestResult dummy = fVictim.run(); if (dummy.failureCount() != 0) { result.addError(fTests.elementAt(i), new RuntimeException( "Probable side effect", ((TestFailure)dummy.failures().nextElement()). thrownException())); } else if (dummy.errorCount() != 0) { result.addError(fTests.elementAt(i), new RuntimeException( "Probable side effect", ((TestFailure)dummy.errors().nextElement()). thrownException())); } } */ fTests.remove(i); if (fTests.size() != 0) { i = (i + fStep - 1) % fTests.size(); } } // Forward overall stats to TestResult, so ResultPrinter can see it. if (result instanceof CoreTestResult) { ((CoreTestResult)result).updateStats( fTotalCount, fAndroidOnlyCount, fBrokenCount, fKnownFailureCount, fNormalCount, fIgnoredCount, fSideEffectCount); } } /** * Nulls all reference fields in the given test object. This method helps * us with those test classes that don't have an explicit tearDown() * method. Normally the garbage collector should take care of everything, * but it can't hurt to support it a bit. */ private void cleanup(TestCase test) { Field[] fields = test.getClass().getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field f = fields[i]; if (!f.getType().isPrimitive() && (f.getModifiers() & Modifier.STATIC) == 0) { try { f.setAccessible(true); f.set(test, null); } catch (Exception ex) { // Nothing we can do about it. } } } } /** * Returns the tests as an enumeration. Note this is empty once the tests * have been executed. */ @SuppressWarnings("unchecked") public Enumeration tests() { return fTests.elements(); } /** * Returns the number of tests in the suite. Note this is zero once the * tests have been executed. */ public int countTestCases() { return fTests.size(); } @Override public String toString() { return name; } }