blob: 8e94b8352e3bb29962a72e948b3128865df498d7 [file] [log] [blame]
/*
* 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;
}
}