| /* |
| * 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.tradefed.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; |
| } |
| } |