/*
* Copyright (C) 2008 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.
*/
import com.android.compatibility.common.util.AbiUtils;
import org.junit.runner.RunWith;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import vogar.Expectation;
import vogar.ExpectationStore;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import junit.framework.TestCase;
public class CollectAllTests extends DescriptionGenerator {
private static final String ATTRIBUTE_RUNNER = "runner";
private static final String ATTRIBUTE_PACKAGE = "appPackageName";
private static final String ATTRIBUTE_NS = "appNameSpace";
private static final String ATTRIBUTE_TARGET = "targetNameSpace";
private static final String ATTRIBUTE_TARGET_BINARY = "targetBinaryName";
private static final String ATTRIBUTE_HOST_SIDE_ONLY = "hostSideOnly";
private static final String ATTRIBUTE_VM_HOST_TEST = "vmHostTest";
private static final String ATTRIBUTE_JAR_PATH = "jarPath";
private static final String ATTRIBUTE_JAVA_PACKAGE_FILTER = "javaPackageFilter";
private static final String JAR_PATH = "LOCAL_JAR_PATH :=";
private static final String TEST_TYPE = "LOCAL_TEST_TYPE :";
public static void main(String[] args) {
if (args.length < 5 || args.length > 7) {
System.err.println("usage: CollectAllTests <output-file> <manifest-file> <jar-file> "
+ "<java-package> <architecture> [expectation-dir [makefile-file]]");
if (args.length != 0) {
System.err.println("received:");
for (String arg : args) {
System.err.println(" " + arg);
}
}
System.exit(1);
}
final String outputPathPrefix = args[0];
File manifestFile = new File(args[1]);
String jarFileName = args[2];
final String javaPackageFilterArg = args[3] != null ? args[3].replaceAll("\\s+", "") : "";
final String[] javaPackagePrefixes;
// Validate the javaPackageFilter value if non-empty.
if (!javaPackageFilterArg.isEmpty()) {
javaPackagePrefixes = javaPackageFilterArg.split(":");
for (int i = 0; i < javaPackagePrefixes.length; ++i) {
final String javaPackageFilter = javaPackagePrefixes[i];
if (!isValidJavaPackage(javaPackageFilter)) {
System.err.println("Invalid " + ATTRIBUTE_JAVA_PACKAGE_FILTER + ": " +
javaPackageFilter);
System.exit(1);
return;
} else {
javaPackagePrefixes[i] = javaPackageFilter.trim() + ".";
}
}
} else {
javaPackagePrefixes = new String[0];
}
String architecture = args[4];
if (architecture == null || architecture.equals("")) {
System.err.println("Invalid architecture");
System.exit(1);
return;
}
String libcoreExpectationDir = (args.length > 5) ? args[5] : null;
String androidMakeFile = (args.length > 6) ? args[6] : null;
final TestType testType = TestType.getTestType(androidMakeFile);
Document manifest;
try {
manifest = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new FileInputStream(manifestFile));
} catch (Exception e) {
System.err.println("cannot open manifest " + manifestFile);
e.printStackTrace();
System.exit(1);
return;
}
Element documentElement = manifest.getDocumentElement();
documentElement.getAttribute("package");
final String runner = getElementAttribute(documentElement,
"instrumentation",
"android:name");
final String packageName = documentElement.getAttribute("package");
final String target = getElementAttribute(documentElement,
"instrumentation",
"android:targetPackage");
String outputXmlFile = outputPathPrefix + ".xml";
final String xmlName = new File(outputPathPrefix).getName();
XMLGenerator xmlGenerator;
try {
xmlGenerator = new XMLGenerator(outputXmlFile) {
{
Node testPackageElem = mDoc.getDocumentElement();
setAttribute(testPackageElem, ATTRIBUTE_NAME, xmlName);
setAttribute(testPackageElem, ATTRIBUTE_RUNNER, runner);
setAttribute(testPackageElem, ATTRIBUTE_PACKAGE, packageName);
setAttribute(testPackageElem, ATTRIBUTE_NS, packageName);
setAttribute(testPackageElem, ATTRIBUTE_JAVA_PACKAGE_FILTER, javaPackageFilterArg);
if (testType.type == TestType.HOST_SIDE_ONLY) {
setAttribute(testPackageElem, ATTRIBUTE_HOST_SIDE_ONLY, "true");
setAttribute(testPackageElem, ATTRIBUTE_JAR_PATH, testType.jarPath);
}
if (testType.type == TestType.VM_HOST_TEST) {
setAttribute(testPackageElem, ATTRIBUTE_VM_HOST_TEST, "true");
setAttribute(testPackageElem, ATTRIBUTE_JAR_PATH, testType.jarPath);
}
if (!packageName.equals(target)) {
setAttribute(testPackageElem, ATTRIBUTE_TARGET, target);
setAttribute(testPackageElem, ATTRIBUTE_TARGET_BINARY, target);
}
}
};
} catch (ParserConfigurationException e) {
System.err.println("Can't initialize XML Generator " + outputXmlFile);
System.exit(1);
return;
}
ExpectationStore libcoreVogarExpectationStore;
ExpectationStore ctsVogarExpectationStore;
try {
libcoreVogarExpectationStore
= VogarUtils.provideExpectationStore(libcoreExpectationDir);
ctsVogarExpectationStore = VogarUtils.provideExpectationStore(CTS_EXPECTATION_DIR);
} catch (IOException e) {
System.err.println("Can't initialize vogar expectation store from "
+ libcoreExpectationDir);
e.printStackTrace(System.err);
System.exit(1);
return;
}
ExpectationStore[] expectations = new ExpectationStore[] {
libcoreVogarExpectationStore, ctsVogarExpectationStore
};
JarFile jarFile = null;
try {
jarFile = new JarFile(jarFileName);
} catch (Exception e) {
System.err.println("cannot open jarfile " + jarFileName);
e.printStackTrace();
System.exit(1);
}
Map<String,TestClass> testCases = new LinkedHashMap<String, TestClass>();
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String name = jarEntry.getName();
if (!name.endsWith(".class")) {
continue;
}
String className
= name.substring(0, name.length() - ".class".length()).replace('/', '.');
boolean matchesPrefix = false;
if (javaPackagePrefixes.length > 0) {
for (String javaPackagePrefix : javaPackagePrefixes) {
if (className.startsWith(javaPackagePrefix)) {
matchesPrefix = true;
}
}
} else {
matchesPrefix = true;
}
if (!matchesPrefix) {
continue;
}
// Avoid inner classes: they should not have tests and often they can have dependencies
// on test frameworks that need to be resolved and would need to be on the classpath.
// e.g. Mockito.
if (className.contains("$")) {
continue;
}
try {
Class<?> klass = Class.forName(className,
false,
CollectAllTests.class.getClassLoader());
final int modifiers = klass.getModifiers();
if (Modifier.isAbstract(modifiers) || !Modifier.isPublic(modifiers)) {
continue;
}
final boolean isJunit4Class = isJunit4Class(klass);
if (!isJunit4Class && !isJunit3Test(klass)) {
continue;
}
try {
klass.getConstructor(new Class<?>[] { String.class } );
addToTests(expectations, architecture, testCases, klass);
continue;
} catch (NoSuchMethodException e) {
} catch (SecurityException e) {
System.out.println("Known bug (Working as intended): problem with class "
+ className);
e.printStackTrace();
}
try {
klass.getConstructor(new Class<?>[0]);
addToTests(expectations, architecture, testCases, klass);
continue;
} catch (NoSuchMethodException e) {
} catch (SecurityException e) {
System.out.println("Known bug (Working as intended): problem with class "
+ className);
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
System.out.println("class not found " + className);
e.printStackTrace();
System.exit(1);
}
}
for (Iterator<TestClass> iterator = testCases.values().iterator(); iterator.hasNext();) {
TestClass type = iterator.next();
xmlGenerator.addTestClass(type);
}
try {
xmlGenerator.dump();
} catch (Exception e) {
System.err.println("cannot dump xml to " + outputXmlFile);
e.printStackTrace();
System.exit(1);
}
}
private static class TestType {
private static final int HOST_SIDE_ONLY = 1;
private static final int DEVICE_SIDE_ONLY = 2;
private static final int VM_HOST_TEST = 3;
private final int type;
private final String jarPath;
private TestType (int type, String jarPath) {
this.type = type;
this.jarPath = jarPath;
}
private static TestType getTestType(String makeFileName) {
if (makeFileName == null || makeFileName.isEmpty()) {
return new TestType(DEVICE_SIDE_ONLY, null);
}
int type = TestType.DEVICE_SIDE_ONLY;
String jarPath = null;
try {
BufferedReader reader = new BufferedReader(new FileReader(makeFileName));
String line;
while ((line = reader.readLine())!=null) {
if (line.startsWith(TEST_TYPE)) {
if (line.indexOf(ATTRIBUTE_VM_HOST_TEST) >= 0) {
type = VM_HOST_TEST;
} else {
type = HOST_SIDE_ONLY;
}
} else if (line.startsWith(JAR_PATH)) {
jarPath = line.substring(JAR_PATH.length(), line.length()).trim();
}
}
reader.close();
} catch (IOException e) {
}
return new TestType(type, jarPath);
}
}
private static Element getElement(Element element, String tagName) {
NodeList elements = element.getElementsByTagName(tagName);
if (elements.getLength() > 0) {
return (Element) elements.item(0);
} else {
return null;
}
}
private static String getElementAttribute(Element element,
String elementName,
String attributeName) {
Element e = getElement(element, elementName);
if (e != null) {
return e.getAttribute(attributeName);
} else {
return "";
}
}
private static String getKnownFailure(final Class<?> testClass,
final String testName) {
return getAnnotation(testClass, testName, KNOWN_FAILURE);
}
private static boolean isKnownFailure(final Class<?> testClass,
final String testName) {
return getAnnotation(testClass, testName, KNOWN_FAILURE) != null;
}
private static boolean isSuppressed(final Class<?> testClass,
final String testName) {
return getAnnotation(testClass, testName, SUPPRESSED_TEST) != null;
}
private static String getAnnotation(final Class<?> testClass,
final String testName, final String annotationName) {
try {
Method testMethod = testClass.getMethod(testName, (Class[])null);
Annotation[] annotations = testMethod.getAnnotations();
for (Annotation annot : annotations) {
if (annot.annotationType().getName().equals(annotationName)) {
String annotStr = annot.toString();
String knownFailure = null;
if (annotStr.contains("(value=")) {
knownFailure =
annotStr.substring(annotStr.indexOf("=") + 1,
annotStr.length() - 1);
}
if (knownFailure == null) {
knownFailure = "true";
}
return knownFailure;
}
}
} catch (NoSuchMethodException e) {
}
return null;
}
private static void addToTests(ExpectationStore[] expectations,
String architecture,
Map<String,TestClass> testCases,
Class<?> testClass) {
Set<String> testNames = new HashSet<String>();
boolean isJunit3Test = isJunit3Test(testClass);
Method[] testMethods = testClass.getMethods();
for (Method testMethod : testMethods) {
String testName = testMethod.getName();
if (testNames.contains(testName)) {
continue;
}
/* Make sure the method has the right signature. */
if (!Modifier.isPublic(testMethod.getModifiers())) {
continue;
}
if (!testMethod.getReturnType().equals(Void.TYPE)) {
continue;
}
if (testMethod.getParameterTypes().length != 0) {
continue;
}
if ((isJunit3Test && !testName.startsWith("test"))
|| (!isJunit3Test && !isJunit4TestMethod(testMethod))) {
continue;
}
testNames.add(testName);
addToTests(expectations, architecture, testCases, testClass, testName);
}
}
private static void addToTests(ExpectationStore[] expectations,
String architecture,
Map<String,TestClass> testCases,
Class<?> test,
String testName) {
String testClassName = test.getName();
String knownFailure = getKnownFailure(test, testName);
if (isKnownFailure(test, testName)) {
System.out.println("ignoring known failure: " + test + "#" + testName);
return;
} else if (isSuppressed(test, testName)) {
System.out.println("ignoring suppressed test: " + test + "#" + testName);
return;
} else if (VogarUtils.isVogarKnownFailure(expectations,
testClassName,
testName)) {
System.out.println("ignoring expectation known failure: " + test
+ "#" + testName);
return;
}
Set<String> supportedAbis = VogarUtils.extractSupportedAbis(architecture,
expectations,
testClassName,
testName);
int timeoutInMinutes = VogarUtils.timeoutInMinutes(expectations,
testClassName,
testName);
TestClass testClass;
if (testCases.containsKey(testClassName)) {
testClass = testCases.get(testClassName);
} else {
testClass = new TestClass(testClassName, new ArrayList<TestMethod>());
testCases.put(testClassName, testClass);
}
testClass.mCases.add(new TestMethod(testName, "", "", supportedAbis,
knownFailure, false, false, timeoutInMinutes));
}
private static boolean isJunit3Test(Class<?> klass) {
return TestCase.class.isAssignableFrom(klass);
}
private static boolean isJunit4Class(Class<?> klass) {
for (Annotation a : klass.getAnnotations()) {
if (RunWith.class.isAssignableFrom(a.annotationType())) {
// @RunWith is currently not supported for CTS tests because tradefed cannot handle
// a single test spawning other tests with different names.
System.out.println("Skipping test class " + klass.getName()
+ ": JUnit4 @RunWith is not supported");
return false;
}
}
for (Method m : klass.getMethods()) {
if (isJunit4TestMethod(m)) {
return true;
}
}
return false;
}
private static boolean isJunit4TestMethod(Method method) {
for (Annotation a : method.getAnnotations()) {
if (org.junit.Test.class.isAssignableFrom(a.annotationType())) {
return true;
}
}
return false;
}
/**
* Determines if a given string is a valid java package name
* @param javaPackageName
* @return true if it is valid, false otherwise
*/
private static boolean isValidJavaPackage(String javaPackageName) {
String[] strSections = javaPackageName.split(".");
for (String strSection : strSections) {
if (!isValidJavaIdentifier(strSection)) {
return false;
}
}
return true;
}
/**
* Determines if a given string is a valid java identifier.
* @param javaIdentifier
* @return true if it is a valid identifier, false otherwise
*/
private static boolean isValidJavaIdentifier(String javaIdentifier) {
if (javaIdentifier.length() == 0 ||
!Character.isJavaIdentifierStart(javaIdentifier.charAt(0))) {
return false;
}
for (int i = 1; i < javaIdentifier.length(); i++) {
if (!Character.isJavaIdentifierPart(javaIdentifier.charAt(i))) {
return false;
}
}
return true;
}
}