From 8ebfd3c63f3b930dc696634babe11a6da207047a Mon Sep 17 00:00:00 2001 From: Adnan Begovic Date: Mon, 13 Jun 2016 10:54:57 -0700 Subject: cmsdk-api-coverage: initial fork of cts-api-coverage. - Add support for -cm argument to include public cyanogenmod classes. - New target binary (cmsdk-api-coverage) --- Android.mk | 43 ++++ etc/cmsdk-api-coverage | 46 ++++ src/Android.mk | 29 +++ src/MANIFEST.mf | 2 + src/com/android/cts/apicoverage/ApiClass.java | 243 +++++++++++++++++++++ .../android/cts/apicoverage/ApiConstructor.java | 64 ++++++ src/com/android/cts/apicoverage/ApiCoverage.java | 49 +++++ src/com/android/cts/apicoverage/ApiMethod.java | 99 +++++++++ src/com/android/cts/apicoverage/ApiPackage.java | 103 +++++++++ .../android/cts/apicoverage/CtsApiCoverage.java | 240 ++++++++++++++++++++ .../android/cts/apicoverage/CurrentXmlHandler.java | 154 +++++++++++++ .../android/cts/apicoverage/DexDepsXmlHandler.java | 90 ++++++++ src/com/android/cts/apicoverage/HasCoverage.java | 52 +++++ src/com/android/cts/apicoverage/HtmlReport.java | 69 ++++++ src/com/android/cts/apicoverage/PackageFilter.java | 57 +++++ src/com/android/cts/apicoverage/TextReport.java | 129 +++++++++++ src/com/android/cts/apicoverage/XmlReport.java | 137 ++++++++++++ src/res/api-coverage.xsl | 190 ++++++++++++++++ 18 files changed, 1796 insertions(+) create mode 100644 Android.mk create mode 100644 etc/cmsdk-api-coverage create mode 100644 src/Android.mk create mode 100644 src/MANIFEST.mf create mode 100644 src/com/android/cts/apicoverage/ApiClass.java create mode 100644 src/com/android/cts/apicoverage/ApiConstructor.java create mode 100644 src/com/android/cts/apicoverage/ApiCoverage.java create mode 100644 src/com/android/cts/apicoverage/ApiMethod.java create mode 100644 src/com/android/cts/apicoverage/ApiPackage.java create mode 100644 src/com/android/cts/apicoverage/CtsApiCoverage.java create mode 100644 src/com/android/cts/apicoverage/CurrentXmlHandler.java create mode 100644 src/com/android/cts/apicoverage/DexDepsXmlHandler.java create mode 100644 src/com/android/cts/apicoverage/HasCoverage.java create mode 100644 src/com/android/cts/apicoverage/HtmlReport.java create mode 100644 src/com/android/cts/apicoverage/PackageFilter.java create mode 100644 src/com/android/cts/apicoverage/TextReport.java create mode 100644 src/com/android/cts/apicoverage/XmlReport.java create mode 100644 src/res/api-coverage.xsl diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..9f257d1 --- /dev/null +++ b/Android.mk @@ -0,0 +1,43 @@ +# Copyright (C) 2010 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. + +LOCAL_PATH := $(call my-dir) + +# We use copy-file-to-new-target so that the installed +# script file's timestamp is at least as new as the +# .jar file it wraps. + +# the hat script +# ============================================================ +include $(CLEAR_VARS) +LOCAL_IS_HOST_MODULE := true +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE := cmsdk-api-coverage +LOCAL_MODULE_TAGS := optional + +include $(BUILD_SYSTEM)/base_rules.mk + +$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/$(LOCAL_MODULE)$(COMMON_JAVA_PACKAGE_SUFFIX) +$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/$(LOCAL_MODULE) | $(ACP) + @echo "Copy: $(PRIVATE_MODULE) ($@)" + $(copy-file-to-new-target) + $(hide) chmod 755 $@ + +# the other stuff +# ============================================================ +subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \ + src \ + )) + +include $(subdirs) diff --git a/etc/cmsdk-api-coverage b/etc/cmsdk-api-coverage new file mode 100644 index 0000000..fe7278a --- /dev/null +++ b/etc/cmsdk-api-coverage @@ -0,0 +1,46 @@ +#!/bin/bash +# +# Copyright (C) 2010, 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. + +# Set up prog to be the path of this script, including following symlinks, +# and set up progdir to be the fully-qualified pathname of its directory. +prog="$0" +while [ -h "${prog}" ]; do + newProg=`/bin/ls -ld "${prog}"` + newProg=`expr "${newProg}" : ".* -> \(.*\)$"` + if expr "x${newProg}" : 'x/' >/dev/null; then + prog="${newProg}" + else + progdir=`dirname "${prog}"` + prog="${progdir}/${newProg}" + fi +done +oldwd=`pwd` +progdir=`dirname "${prog}"` +cd "${progdir}" +progdir=`pwd` +prog="${progdir}"/`basename "${prog}"` +cd "${oldwd}" + +libdir=`dirname $progdir`/framework + +javaOpts="" +while expr "x$1" : 'x-J' >/dev/null; do + opt=`expr "$1" : '-J\(.*\)'` + javaOpts="${javaOpts} -${opt}" + shift +done + +exec java $javaOpts -jar $libdir/cts-api-coverage.jar "$@" diff --git a/src/Android.mk b/src/Android.mk new file mode 100644 index 0000000..75d4afa --- /dev/null +++ b/src/Android.mk @@ -0,0 +1,29 @@ +# Copyright (C) 2010 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. + +LOCAL_PATH := $(call my-dir) + + +# cmsdk-api-coverage java library +# ============================================================ +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_JAVA_RESOURCE_DIRS := res +LOCAL_JAR_MANIFEST := MANIFEST.mf + +LOCAL_MODULE := cmsdk-api-coverage +LOCAL_MODULE_TAGS := optional + +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/src/MANIFEST.mf b/src/MANIFEST.mf new file mode 100644 index 0000000..b6aa831 --- /dev/null +++ b/src/MANIFEST.mf @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 +Main-Class: com.android.cts.apicoverage.CtsApiCoverage diff --git a/src/com/android/cts/apicoverage/ApiClass.java b/src/com/android/cts/apicoverage/ApiClass.java new file mode 100644 index 0000000..73cea67 --- /dev/null +++ b/src/com/android/cts/apicoverage/ApiClass.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** Representation of a class in the API with constructors and methods. */ +class ApiClass implements Comparable, HasCoverage { + + private static final String VOID = "void"; + + private final String mName; + + private final boolean mDeprecated; + + private final boolean mAbstract; + + private final List mApiConstructors = new ArrayList(); + + private final List mApiMethods = new ArrayList(); + + private final String mSuperClassName; + + private ApiClass mSuperClass; + + /** + * @param name The name of the class + * @param deprecated true iff the class is marked as deprecated + * @param classAbstract true iff the class is abstract + * @param superClassName The fully qualified name of the super class + */ + ApiClass( + String name, + boolean deprecated, + boolean classAbstract, + String superClassName) { + mName = name; + mDeprecated = deprecated; + mAbstract = classAbstract; + mSuperClassName = superClassName; + } + + @Override + public int compareTo(ApiClass another) { + return mName.compareTo(another.mName); + } + + @Override + public String getName() { + return mName; + } + + public boolean isDeprecated() { + return mDeprecated; + } + + public String getSuperClassName() { + return mSuperClassName; + } + + public boolean isAbstract() { + return mAbstract; + } + + public void setSuperClass(ApiClass superClass) { mSuperClass = superClass; } + + public void addConstructor(ApiConstructor constructor) { + mApiConstructors.add(constructor); + } + + + public Collection getConstructors() { + return Collections.unmodifiableList(mApiConstructors); + } + + public void addMethod(ApiMethod method) { + mApiMethods.add(method); + } + + /** Look for a matching constructor and mark it as covered */ + public void markConstructorCovered(List parameterTypes) { + if (mSuperClass != null) { + // Mark matching constructors in the superclass + mSuperClass.markConstructorCovered(parameterTypes); + } + ApiConstructor apiConstructor = getConstructor(parameterTypes); + if (apiConstructor != null) { + apiConstructor.setCovered(true); + } + + } + + /** Look for a matching method and if found and mark it as covered */ + public void markMethodCovered(String name, List parameterTypes, String returnType) { + if (mSuperClass != null) { + // Mark matching methods in the super class + mSuperClass.markMethodCovered(name, parameterTypes, returnType); + } + ApiMethod apiMethod = getMethod(name, parameterTypes, returnType); + if (apiMethod != null) { + apiMethod.setCovered(true); + } + } + + public Collection getMethods() { + return Collections.unmodifiableList(mApiMethods); + } + + public int getNumCoveredMethods() { + int numCovered = 0; + for (ApiConstructor constructor : mApiConstructors) { + if (constructor.isCovered()) { + numCovered++; + } + } + for (ApiMethod method : mApiMethods) { + if (method.isCovered()) { + numCovered++; + } + } + return numCovered; + } + + public int getTotalMethods() { + return mApiConstructors.size() + mApiMethods.size(); + } + + @Override + public float getCoveragePercentage() { + if (getTotalMethods() == 0) { + return 100; + } else { + return (float) getNumCoveredMethods() / getTotalMethods() * 100; + } + } + + @Override + public int getMemberSize() { + return getTotalMethods(); + } + + private ApiMethod getMethod(String name, List parameterTypes, String returnType) { + for (ApiMethod method : mApiMethods) { + boolean methodNameMatch = name.equals(method.getName()); + boolean parameterTypeMatch = + compareParameterTypes(method.getParameterTypes(), parameterTypes); + boolean returnTypeMatch = compareType(method.getReturnType(), returnType); + if (methodNameMatch && parameterTypeMatch && returnTypeMatch) { + return method; + } + } + return null; + } + + /** + * The method compares two lists of parameters. If the {@code apiParameterTypeList} contains + * generic types, test parameter types are ignored. + * + * @param apiParameterTypeList The list of parameter types from the API + * @param testParameterTypeList The list of parameter types used in a test + * @return true iff the list of types are the same. + */ + private static boolean compareParameterTypes( + List apiParameterTypeList, List testParameterTypeList) { + if (apiParameterTypeList.equals(testParameterTypeList)) { + return true; + } + if (apiParameterTypeList.size() != testParameterTypeList.size()) { + return false; + } + + for (int i = 0; i < apiParameterTypeList.size(); i++) { + String apiParameterType = apiParameterTypeList.get(i); + String testParameterType = testParameterTypeList.get(i); + if (!compareType(apiParameterType, testParameterType)) { + return false; + } + } + return true; + } + + /** + * Compare class types. + * @param apiType The type as reported by the api + * @param testType The type as found used in a test + * @return true iff the strings are equal, + * or the apiType is generic and the test type is not void + */ + private static boolean compareType(String apiType, String testType) { + return apiType.equals(testType) || + isGenericType(apiType) && !testType.equals(VOID) || + isGenericArrayType(apiType) && isArrayType(testType) ; + } + + /** + * @return true iff the given parameterType is a generic type. + */ + private static boolean isGenericType(String type) { + return type.length() == 1 && + type.charAt(0) >= 'A' && + type.charAt(0) <= 'Z'; + } + + /** + * @return true iff {@code type} ends with an []. + */ + private static boolean isArrayType(String type) { + return type.endsWith("[]"); + } + + /** + * @return true iff the given parameterType is an array of generic type. + */ + private static boolean isGenericArrayType(String type) { + return type.length() == 3 && isGenericType(type.substring(0, 1)) && isArrayType(type); + } + + private ApiConstructor getConstructor(List parameterTypes) { + for (ApiConstructor constructor : mApiConstructors) { + if (compareParameterTypes(constructor.getParameterTypes(), parameterTypes)) { + return constructor; + } + } + return null; + } +} diff --git a/src/com/android/cts/apicoverage/ApiConstructor.java b/src/com/android/cts/apicoverage/ApiConstructor.java new file mode 100644 index 0000000..8d721e6 --- /dev/null +++ b/src/com/android/cts/apicoverage/ApiConstructor.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** Representation of a constructor in the API with parameters (arguments). */ +class ApiConstructor implements Comparable { + + private final String mName; + + private final List mParameterTypes; + + private final boolean mDeprecated; + + private boolean mIsCovered; + + ApiConstructor(String name, List parameterTypes, boolean deprecated) { + mName = name; + mParameterTypes = new ArrayList(parameterTypes); + mDeprecated = deprecated; + } + + @Override + public int compareTo(ApiConstructor another) { + return mParameterTypes.size() - another.mParameterTypes.size(); + } + + public String getName() { + return mName; + } + + public List getParameterTypes() { + return Collections.unmodifiableList(mParameterTypes); + } + + public boolean isDeprecated() { + return mDeprecated; + } + + public boolean isCovered() { + return mIsCovered; + } + + public void setCovered(boolean covered) { + mIsCovered = covered; + } +} \ No newline at end of file diff --git a/src/com/android/cts/apicoverage/ApiCoverage.java b/src/com/android/cts/apicoverage/ApiCoverage.java new file mode 100644 index 0000000..953aab3 --- /dev/null +++ b/src/com/android/cts/apicoverage/ApiCoverage.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import java.lang.String; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** Representation of the entire API containing packages. */ +class ApiCoverage { + + private final Map mPackages = new HashMap(); + + public void addPackage(ApiPackage pkg) { + mPackages.put(pkg.getName(), pkg); + } + + public ApiPackage getPackage(String name) { + return mPackages.get(name); + } + + public Collection getPackages() { + return Collections.unmodifiableCollection(mPackages.values()); + } + + /** Iterate through all packages and update all classes to include its superclass */ + public void resolveSuperClasses() { + for (Map.Entry entry : mPackages.entrySet()) { + ApiPackage pkg = entry.getValue(); + pkg.resolveSuperClasses(mPackages); + } + } +} diff --git a/src/com/android/cts/apicoverage/ApiMethod.java b/src/com/android/cts/apicoverage/ApiMethod.java new file mode 100644 index 0000000..582c2b6 --- /dev/null +++ b/src/com/android/cts/apicoverage/ApiMethod.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** Representation of a method in the API with parameters (arguments) and a return value. */ +class ApiMethod implements Comparable { + + private final String mName; + + private final List mParameterTypes; + + private final String mReturnType; + + private final boolean mDeprecated; + + private final String mVisibility; + + private final boolean mStaticMethod; + + private final boolean mFinalMethod; + + private final boolean mAbstractMethod; + + private boolean mIsCovered; + + ApiMethod( + String name, + List parameterTypes, + String returnType, + boolean deprecated, + String visibility, + boolean staticMethod, + boolean finalMethod, + boolean abstractMethod) { + mName = name; + mParameterTypes = new ArrayList(parameterTypes); + mReturnType = returnType; + mDeprecated = deprecated; + mVisibility = visibility; + mStaticMethod = staticMethod; + mFinalMethod = finalMethod; + mAbstractMethod = abstractMethod; + } + + @Override + public int compareTo(ApiMethod another) { + return mName.compareTo(another.mName); + } + + public String getName() { + return mName; + } + + public List getParameterTypes() { + return Collections.unmodifiableList(mParameterTypes); + } + + public String getReturnType() { + return mReturnType; + } + + public boolean isDeprecated() { + return mDeprecated; + } + + public boolean isCovered() { + return mIsCovered; + } + + public String getVisibility() { return mVisibility; } + + public boolean isAbstractMethod() { return mAbstractMethod; } + + public boolean isStaticMethod() { return mStaticMethod; } + + public boolean isFinalMethod() { return mFinalMethod; } + + public void setCovered(boolean covered) { + mIsCovered = covered; + } +} \ No newline at end of file diff --git a/src/com/android/cts/apicoverage/ApiPackage.java b/src/com/android/cts/apicoverage/ApiPackage.java new file mode 100644 index 0000000..7be7e3c --- /dev/null +++ b/src/com/android/cts/apicoverage/ApiPackage.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +/** Representation of a package in the API containing classes. */ +class ApiPackage implements HasCoverage { + + private final String mName; + + private final Map mApiClassMap = new HashMap(); + + ApiPackage(String name) { + mName = name; + } + + @Override + public String getName() { + return mName; + } + + public void addClass(ApiClass apiClass) { + mApiClassMap.put(apiClass.getName(), apiClass); + } + + public ApiClass getClass(String name) { + return mApiClassMap.get(name); + } + + public Collection getClasses() { + return Collections.unmodifiableCollection(mApiClassMap.values()); + } + + public int getNumCoveredMethods() { + int covered = 0; + for (ApiClass apiClass : mApiClassMap.values()) { + covered += apiClass.getNumCoveredMethods(); + } + return covered; + } + + public int getTotalMethods() { + int total = 0; + for (ApiClass apiClass : mApiClassMap.values()) { + total += apiClass.getTotalMethods(); + } + return total; + } + + @Override + public float getCoveragePercentage() { + return (float) getNumCoveredMethods() / getTotalMethods() * 100; + } + + @Override + public int getMemberSize() { + return getTotalMethods(); + } + + /** Iterate through all classes and add superclass. */ + public void resolveSuperClasses(Map packageMap) { + Iterator> it = mApiClassMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + ApiClass apiClass = entry.getValue(); + if (apiClass.getSuperClassName() != null) { + String superClassName = apiClass.getSuperClassName(); + // Split the fully qualified class name into package and class name. + String packageName = superClassName.substring(0, superClassName.lastIndexOf('.')); + String className = superClassName.substring( + superClassName.lastIndexOf('.') + 1, superClassName.length()); + if (packageMap.containsKey(packageName)) { + ApiPackage apiPackage = packageMap.get(packageName); + ApiClass superClass = apiPackage.getClass(className); + if (superClass != null) { + // Add the super class + apiClass.setSuperClass(superClass); + } + } + } + } + } +} diff --git a/src/com/android/cts/apicoverage/CtsApiCoverage.java b/src/com/android/cts/apicoverage/CtsApiCoverage.java new file mode 100644 index 0000000..9647da7 --- /dev/null +++ b/src/com/android/cts/apicoverage/CtsApiCoverage.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.transform.TransformerException; + +/** + * Tool that generates a report of what Android framework methods are being called from a given + * set of APKS. See the {@link #printUsage()} method for more details. + */ +public class CtsApiCoverage { + + private static final FilenameFilter SUPPORTED_FILE_NAME_FILTER = new FilenameFilter() { + public boolean accept(File dir, String name) { + String fileName = name.toLowerCase(); + return fileName.endsWith(".apk") || fileName.endsWith(".jar"); + } + }; + + private static final int FORMAT_TXT = 0; + + private static final int FORMAT_XML = 1; + + private static final int FORMAT_HTML = 2; + + private static void printUsage() { + System.out.println("Usage: cmsdk-api-coverage [OPTION]... [APK]..."); + System.out.println(); + System.out.println("Generates a report about what Android framework methods are called "); + System.out.println("from the given APKs."); + System.out.println(); + System.out.println("Use the Makefiles rules in CtsTestCoverage.mk to generate the report "); + System.out.println("rather than executing this directly. If you still want to run this "); + System.out.println("directly, then this must be used from the $ANDROID_BUILD_TOP "); + System.out.println("directory and dexdeps must be built via \"make dexdeps\"."); + System.out.println(); + System.out.println("Options:"); + System.out.println(" -o FILE output file or standard out if not given"); + System.out.println(" -f [txt|xml|html] format of output"); + System.out.println(" -d PATH path to dexdeps or expected to be in $PATH"); + System.out.println(" -a PATH path to the API XML file"); + System.out.println(" -p PACKAGENAMEPREFIX report coverage only for package that start with"); + System.out.println(" -t TITLE report title"); + System.out.println(" -cm CYANOGENMOD include cyanogenmod classes"); + System.out.println(); + System.exit(1); + } + + public static void main(String[] args) throws Exception { + List testApks = new ArrayList(); + File outputFile = null; + int format = FORMAT_TXT; + String dexDeps = "dexDeps"; + String apiXmlPath = ""; + PackageFilter packageFilter = new PackageFilter(); + String reportTitle = "CMSDK API Coverage"; + boolean includeCMClasses = false; + + for (int i = 0; i < args.length; i++) { + if (args[i].startsWith("-")) { + if ("-o".equals(args[i])) { + outputFile = new File(getExpectedArg(args, ++i)); + } else if ("-f".equals(args[i])) { + String formatSpec = getExpectedArg(args, ++i); + if ("xml".equalsIgnoreCase(formatSpec)) { + format = FORMAT_XML; + } else if ("txt".equalsIgnoreCase(formatSpec)) { + format = FORMAT_TXT; + } else if ("html".equalsIgnoreCase(formatSpec)) { + format = FORMAT_HTML; + } else { + printUsage(); + } + } else if ("-d".equals(args[i])) { + dexDeps = getExpectedArg(args, ++i); + } else if ("-a".equals(args[i])) { + apiXmlPath = getExpectedArg(args, ++i); + } else if ("-p".equals(args[i])) { + packageFilter.addPrefixToFilter(getExpectedArg(args, ++i)); + } else if ("-t".equals(args[i])) { + reportTitle = getExpectedArg(args, ++i); + } else if ("-cm".equals(args[i])) { + includeCMClasses = true; + } else { + printUsage(); + } + } else { + File file = new File(args[i]); + if (file.isDirectory()) { + testApks.addAll(Arrays.asList(file.listFiles(SUPPORTED_FILE_NAME_FILTER))); + } else { + testApks.add(file); + } + } + } + + /* + * 1. Create an ApiCoverage object that is a tree of Java objects representing the API + * in current.xml. The object will have no information about the coverage for each + * constructor or method yet. + * + * 2. For each provided APK, scan it using dexdeps, parse the output of dexdeps, and + * call methods on the ApiCoverage object to cumulatively add coverage stats. + * + * 3. Output a report based on the coverage stats in the ApiCoverage object. + */ + + ApiCoverage apiCoverage = getEmptyApiCoverage(apiXmlPath); + // Add superclass information into api coverage. + apiCoverage.resolveSuperClasses(); + for (File testApk : testApks) { + addApiCoverage(apiCoverage, testApk, dexDeps, includeCMClasses); + } + outputCoverageReport(apiCoverage, testApks, outputFile, format, packageFilter, reportTitle); + } + + /** Get the argument or print out the usage and exit. */ + private static String getExpectedArg(String[] args, int index) { + if (index < args.length) { + return args[index]; + } else { + printUsage(); + return null; // Never will happen because printUsage will call exit(1) + } + } + + /** + * Creates an object representing the API that will be used later to collect coverage + * statistics as we iterate over the test APKs. + * + * @param apiXmlPath to the API XML file + * @return an {@link ApiCoverage} object representing the API in current.xml without any + * coverage statistics yet + */ + private static ApiCoverage getEmptyApiCoverage(String apiXmlPath) + throws SAXException, IOException { + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + CurrentXmlHandler currentXmlHandler = new CurrentXmlHandler(); + xmlReader.setContentHandler(currentXmlHandler); + + File currentXml = new File(apiXmlPath); + FileReader fileReader = null; + try { + fileReader = new FileReader(currentXml); + xmlReader.parse(new InputSource(fileReader)); + } finally { + if (fileReader != null) { + fileReader.close(); + } + } + + return currentXmlHandler.getApi(); + } + + /** + * Adds coverage information gleamed from running dexdeps on the APK to the + * {@link ApiCoverage} object. + * + * @param apiCoverage object to which the coverage statistics will be added to + * @param testApk containing the tests that will be scanned by dexdeps + */ + private static void addApiCoverage(ApiCoverage apiCoverage, File testApk, String dexdeps, + boolean includeCmClasses) throws SAXException, IOException { + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + DexDepsXmlHandler dexDepsXmlHandler = new DexDepsXmlHandler(apiCoverage); + xmlReader.setContentHandler(dexDepsXmlHandler); + + String apkPath = testApk.getPath(); + Process process; + if (includeCmClasses) { + process = new ProcessBuilder(dexdeps, "--format=xml", "--include-cm-classes", + apkPath).start(); + } else { + process = new ProcessBuilder(dexdeps, "--format=xml", apkPath).start(); + } + try { + xmlReader.parse(new InputSource(process.getInputStream())); + } catch (SAXException e) { + // Catch this exception, but continue. SAXException is acceptable in cases + // where the apk does not contain a classes.dex and therefore parsing won't work. + System.err.println("warning: dexdeps failed for: " + apkPath); + } + } + + private static void outputCoverageReport(ApiCoverage apiCoverage, List testApks, + File outputFile, int format, PackageFilter packageFilter, String reportTitle) + throws IOException, TransformerException, InterruptedException { + + OutputStream out = outputFile != null + ? new FileOutputStream(outputFile) + : System.out; + + try { + switch (format) { + case FORMAT_TXT: + TextReport.printTextReport(apiCoverage, packageFilter, out); + break; + + case FORMAT_XML: + XmlReport.printXmlReport(testApks, apiCoverage, packageFilter, reportTitle, out); + break; + + case FORMAT_HTML: + HtmlReport.printHtmlReport(testApks, apiCoverage, packageFilter, reportTitle, out); + break; + } + } finally { + out.close(); + } + } +} diff --git a/src/com/android/cts/apicoverage/CurrentXmlHandler.java b/src/com/android/cts/apicoverage/CurrentXmlHandler.java new file mode 100644 index 0000000..de9f5d5 --- /dev/null +++ b/src/com/android/cts/apicoverage/CurrentXmlHandler.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link DefaultHandler} that builds an empty {@link ApiCoverage} object from scanning current.xml. + */ +class CurrentXmlHandler extends DefaultHandler { + + private String mCurrentPackageName; + + private String mCurrentClassName; + + private boolean mIgnoreCurrentClass; + + private String mCurrentMethodName; + + private String mCurrentMethodReturnType; + + private boolean mCurrentMethodIsAbstract; + + private String mCurrentMethodVisibility; + + private boolean mCurrentMethodStaticMethod; + + private boolean mCurrentMethodFinalMethod; + + private boolean mDeprecated; + + + private List mCurrentParameterTypes = new ArrayList(); + + private ApiCoverage mApiCoverage = new ApiCoverage(); + + public ApiCoverage getApi() { + return mApiCoverage; + } + + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) + throws SAXException { + super.startElement(uri, localName, name, attributes); + if ("package".equalsIgnoreCase(localName)) { + mCurrentPackageName = getValue(attributes, "name"); + + ApiPackage apiPackage = new ApiPackage(mCurrentPackageName); + mApiCoverage.addPackage(apiPackage); + + } else if ("class".equalsIgnoreCase(localName)) { + if (isEnum(attributes)) { + mIgnoreCurrentClass = true; + return; + } + mIgnoreCurrentClass = false; + mCurrentClassName = getValue(attributes, "name"); + mDeprecated = isDeprecated(attributes); + String superClass = attributes.getValue("extends"); + ApiClass apiClass = new ApiClass( + mCurrentClassName, mDeprecated, is(attributes, "abstract"), superClass); + ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName); + apiPackage.addClass(apiClass); + } else if ("interface".equalsIgnoreCase(localName)) { + // don't add interface + mIgnoreCurrentClass = true; + } else if ("constructor".equalsIgnoreCase(localName)) { + mDeprecated = isDeprecated(attributes); + mCurrentParameterTypes.clear(); + } else if ("method".equalsIgnoreCase(localName)) { + mDeprecated = isDeprecated(attributes); + mCurrentMethodName = getValue(attributes, "name"); + mCurrentMethodReturnType = getValue(attributes, "return"); + mCurrentMethodIsAbstract = is(attributes, "abstract"); + mCurrentMethodVisibility = getValue(attributes, "visibility"); + mCurrentMethodStaticMethod = is(attributes, "static"); + mCurrentMethodFinalMethod = is(attributes, "final"); + mCurrentParameterTypes.clear(); + } else if ("parameter".equalsIgnoreCase(localName)) { + mCurrentParameterTypes.add(getValue(attributes, "type")); + } + } + + @Override + public void endElement(String uri, String localName, String name) throws SAXException { + super.endElement(uri, localName, name); + if (mIgnoreCurrentClass) { + // do not add anything for interface + return; + } + if ("constructor".equalsIgnoreCase(localName)) { + if (mCurrentParameterTypes.isEmpty()) { + // Don't add empty default constructors... + return; + } + ApiConstructor apiConstructor = new ApiConstructor(mCurrentClassName, + mCurrentParameterTypes, mDeprecated); + ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName); + ApiClass apiClass = apiPackage.getClass(mCurrentClassName); + apiClass.addConstructor(apiConstructor); + } else if ("method".equalsIgnoreCase(localName)) { + ApiMethod apiMethod = new ApiMethod( + mCurrentMethodName, + mCurrentParameterTypes, + mCurrentMethodReturnType, + mDeprecated, + mCurrentMethodVisibility, + mCurrentMethodStaticMethod, + mCurrentMethodFinalMethod, + mCurrentMethodIsAbstract); + ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName); + ApiClass apiClass = apiPackage.getClass(mCurrentClassName); + apiClass.addMethod(apiMethod); + } + } + + static String getValue(Attributes attributes, String key) { + // Strip away generics <...> and make inner classes always use a "." rather than "$". + return attributes.getValue(key) + .replaceAll("<.+>", "") + .replace("$", "."); + } + + private boolean isDeprecated(Attributes attributes) { + return "deprecated".equals(attributes.getValue("deprecated")); + } + + private static boolean is(Attributes attributes, String valueName) { + return "true".equals(attributes.getValue(valueName)); + } + + private boolean isEnum(Attributes attributes) { + return "java.lang.Enum".equals(attributes.getValue("extends")); + } +} \ No newline at end of file diff --git a/src/com/android/cts/apicoverage/DexDepsXmlHandler.java b/src/com/android/cts/apicoverage/DexDepsXmlHandler.java new file mode 100644 index 0000000..3df532e --- /dev/null +++ b/src/com/android/cts/apicoverage/DexDepsXmlHandler.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link DefaultHander} that parses the output of dexdeps and adds the coverage information to + * an {@link ApiCoverage} object. + */ +class DexDepsXmlHandler extends DefaultHandler { + + private final ApiCoverage mPackageMap; + + private String mCurrentPackageName; + + private String mCurrentClassName; + + private String mCurrentMethodName; + + private String mCurrentMethodReturnType; + + private List mCurrentParameterTypes = new ArrayList(); + + DexDepsXmlHandler(ApiCoverage packageMap) { + this.mPackageMap = packageMap; + } + + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) + throws SAXException { + super.startElement(uri, localName, name, attributes); + if ("package".equalsIgnoreCase(localName)) { + mCurrentPackageName = CurrentXmlHandler.getValue(attributes, "name"); + } else if ("class".equalsIgnoreCase(localName) + || "interface".equalsIgnoreCase(localName)) { + mCurrentClassName = CurrentXmlHandler.getValue(attributes, "name"); + } else if ("constructor".equalsIgnoreCase(localName)) { + mCurrentParameterTypes.clear(); + } else if ("method".equalsIgnoreCase(localName)) { + mCurrentMethodName = CurrentXmlHandler.getValue(attributes, "name"); + mCurrentMethodReturnType = CurrentXmlHandler.getValue(attributes, "return"); + mCurrentParameterTypes.clear(); + } else if ("parameter".equalsIgnoreCase(localName)) { + mCurrentParameterTypes.add(CurrentXmlHandler.getValue(attributes, "type")); + } + } + + @Override + public void endElement(String uri, String localName, String name) throws SAXException { + super.endElement(uri, localName, name); + if ("constructor".equalsIgnoreCase(localName)) { + ApiPackage apiPackage = mPackageMap.getPackage(mCurrentPackageName); + if (apiPackage != null) { + ApiClass apiClass = apiPackage.getClass(mCurrentClassName); + if (apiClass != null) { + apiClass.markConstructorCovered(mCurrentParameterTypes); + } + } + } else if ("method".equalsIgnoreCase(localName)) { + ApiPackage apiPackage = mPackageMap.getPackage(mCurrentPackageName); + if (apiPackage != null) { + ApiClass apiClass = apiPackage.getClass(mCurrentClassName); + if (apiClass != null) { + apiClass.markMethodCovered( + mCurrentMethodName, mCurrentParameterTypes, mCurrentMethodReturnType); + } + } + } + } +} \ No newline at end of file diff --git a/src/com/android/cts/apicoverage/HasCoverage.java b/src/com/android/cts/apicoverage/HasCoverage.java new file mode 100644 index 0000000..f8d8054 --- /dev/null +++ b/src/com/android/cts/apicoverage/HasCoverage.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import java.util.Comparator; + +interface HasCoverage { + float getCoveragePercentage(); + int getMemberSize(); + String getName(); +} + +class CoverageComparator implements Comparator { + public int compare(HasCoverage entity, HasCoverage otherEntity) { + int lhsPct = Math.round(entity.getCoveragePercentage()); + int rhsPct = Math.round(otherEntity.getCoveragePercentage()); + int diff = Integer.compare(getCoveragePercentageSegment(lhsPct), + getCoveragePercentageSegment(rhsPct)); + return diff != 0 ? diff : + Integer.compare(otherEntity.getMemberSize(), entity.getMemberSize()); + } + + /** + * Distill coverage percentage down to 3 major segments + * @param coveragePercentage + * @return + */ + private int getCoveragePercentageSegment(int coveragePercentage) { + // note that this segmentation logic is duplicated in api-coverage.xsl + if (coveragePercentage <= 50) { + return 0; + } else if (coveragePercentage <= 80) { + return 1; + } else { + return 2; + } + } +} diff --git a/src/com/android/cts/apicoverage/HtmlReport.java b/src/com/android/cts/apicoverage/HtmlReport.java new file mode 100644 index 0000000..0e6b54a --- /dev/null +++ b/src/com/android/cts/apicoverage/HtmlReport.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.List; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * Class that outputs an HTML report of the {@link ApiCoverage} collected. It is the XML report + * transformed into HTML. + */ +class HtmlReport { + + public static void printHtmlReport(final List testApks, final ApiCoverage apiCoverage, + final PackageFilter packageFilter, final String reportTitle, final OutputStream out) + throws IOException, TransformerException { + final PipedOutputStream xmlOut = new PipedOutputStream(); + final PipedInputStream xmlIn = new PipedInputStream(xmlOut); + + Thread t = new Thread(new Runnable() { + @Override + public void run() { + XmlReport.printXmlReport(testApks, apiCoverage, packageFilter, reportTitle, xmlOut); + + // Close the output stream to avoid "Write dead end" errors. + try { + xmlOut.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + t.start(); + + InputStream xsl = CtsApiCoverage.class.getResourceAsStream("/api-coverage.xsl"); + StreamSource xslSource = new StreamSource(xsl); + TransformerFactory factory = TransformerFactory.newInstance(); + Transformer transformer = factory.newTransformer(xslSource); + + StreamSource xmlSource = new StreamSource(xmlIn); + StreamResult result = new StreamResult(out); + transformer.transform(xmlSource, result); + } +} diff --git a/src/com/android/cts/apicoverage/PackageFilter.java b/src/com/android/cts/apicoverage/PackageFilter.java new file mode 100644 index 0000000..b196d8f --- /dev/null +++ b/src/com/android/cts/apicoverage/PackageFilter.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 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.android.cts.apicoverage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Util class to support package filtering logic + *

+ * A list of package prefixes can be added to the filter, and {{@link #accept(String)} method will + * decide if the provided package name matches any of the prefixes. + */ +public class PackageFilter { + + private List mFilters = new ArrayList<>(); + + /** + * Check if a particular package name matches any of the package prefixes configured in filter. + * If no filters are configured, any package names will be accepted + * @param packageName + * @return + */ + public boolean accept(String packageName) { + if (mFilters.isEmpty()) { + return true; + } + for (String filter : mFilters) { + if (packageName.startsWith(filter)) { + return true; + } + } + return false; + } + + public void addPrefixToFilter(String prefix) { + mFilters.add(prefix); + } + + public void clearFilter() { + mFilters.clear(); + } +} diff --git a/src/com/android/cts/apicoverage/TextReport.java b/src/com/android/cts/apicoverage/TextReport.java new file mode 100644 index 0000000..3adc020 --- /dev/null +++ b/src/com/android/cts/apicoverage/TextReport.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Class that outputs a text report of {@link ApiCoverage}. + */ +class TextReport { + + public static void printTextReport(ApiCoverage api, PackageFilter packageFilter, + OutputStream outputStream) { + PrintStream out = new PrintStream(outputStream); + + CoverageComparator comparator = new CoverageComparator(); + List packages = new ArrayList(api.getPackages()); + Collections.sort(packages, comparator); + + for (ApiPackage apiPackage : packages) { + if (packageFilter.accept(apiPackage.getName()) && apiPackage.getTotalMethods() > 0) { + printPackage(apiPackage, out); + } + } + + out.println(); + out.println(); + + for (ApiPackage apiPackage : packages) { + if (packageFilter.accept(apiPackage.getName())) { + printPackage(apiPackage, out); + + List classes = new ArrayList(apiPackage.getClasses()); + Collections.sort(classes, comparator); + for (ApiClass apiClass : classes) { + if (apiClass.getTotalMethods() > 0) { + printClass(apiClass, out); + + List constructors = + new ArrayList(apiClass.getConstructors()); + Collections.sort(constructors); + for (ApiConstructor constructor : constructors) { + printConstructor(constructor, out); + } + + List methods = new ArrayList(apiClass.getMethods()); + Collections.sort(methods); + for (ApiMethod method : methods) { + printMethod(method, out); + } + } + } + } + } + } + + private static void printPackage(ApiPackage apiPackage, PrintStream out) { + out.println(apiPackage.getName() + " " + + Math.round(apiPackage.getCoveragePercentage()) + "% (" + + apiPackage.getNumCoveredMethods() + "/" + apiPackage.getTotalMethods() + ")"); + } + + private static void printClass(ApiClass apiClass, PrintStream out) { + out.println(" " + apiClass.getName() + " " + + Math.round(apiClass.getCoveragePercentage()) + "% (" + + apiClass.getNumCoveredMethods() + "/" + apiClass.getTotalMethods() + ") "); + } + + private static void printConstructor(ApiConstructor constructor, PrintStream out) { + StringBuilder builder = new StringBuilder(" [") + .append(constructor.isCovered() ? "X" : " ") + .append("] ").append(constructor.getName()).append("("); + + List parameterTypes = constructor.getParameterTypes(); + int numParameterTypes = parameterTypes.size(); + for (int i = 0; i < numParameterTypes; i++) { + builder.append(parameterTypes.get(i)); + if (i + 1 < numParameterTypes) { + builder.append(", "); + } + } + out.println(builder.append(")")); + } + + private static void printMethod(ApiMethod method, PrintStream out) { + StringBuilder builder = new StringBuilder(" [") + .append(method.isCovered() ? "X" : " ") + .append("] ") + .append(method.getVisibility()).append(" "); + if (method.isAbstractMethod()) { + builder.append("abstract "); + } + if (method.isStaticMethod()) { + builder.append("static "); + } + if (method.isFinalMethod()) { + builder.append("final "); + } + builder.append(method.getReturnType()).append(" ") + .append(method.getName()).append("("); + List parameterTypes = method.getParameterTypes(); + int numParameterTypes = parameterTypes.size(); + for (int i = 0; i < numParameterTypes; i++) { + builder.append(parameterTypes.get(i)); + if (i + 1 < numParameterTypes) { + builder.append(", "); + } + } + out.println(builder.append(")")); + } +} diff --git a/src/com/android/cts/apicoverage/XmlReport.java b/src/com/android/cts/apicoverage/XmlReport.java new file mode 100644 index 0000000..4310d20 --- /dev/null +++ b/src/com/android/cts/apicoverage/XmlReport.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2010 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.android.cts.apicoverage; + +import java.io.File; +import java.io.OutputStream; +import java.io.PrintStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** + * Class that outputs an XML report of the {@link ApiCoverage} collected. It can be viewed in + * a browser when used with the api-coverage.css and api-coverage.xsl files. + */ +class XmlReport { + + public static void printXmlReport(List testApks, ApiCoverage apiCoverage, + PackageFilter packageFilter, String reportTitle, OutputStream outputStream) { + PrintStream out = new PrintStream(outputStream); + out.println(""); + out.println(""); + + SimpleDateFormat format = new SimpleDateFormat("EEE, MMM d, yyyy h:mm a z"); + String date = format.format(new Date(System.currentTimeMillis())); + out.println(""); + + out.println(""); + out.println(""); + for (File testApk : testApks) { + out.println(""); + } + out.println(""); + out.println(""); + + out.println(""); + + CoverageComparator comparator = new CoverageComparator(); + List packages = new ArrayList(apiCoverage.getPackages()); + Collections.sort(packages, comparator); + int totalMethods = 0; + int totalCoveredMethods = 0; + for (ApiPackage pkg : packages) { + if (packageFilter.accept(pkg.getName()) && pkg.getTotalMethods() > 0) { + int pkgTotal = pkg.getTotalMethods(); + totalMethods += pkgTotal; + int pkgTotalCovered = pkg.getNumCoveredMethods(); + totalCoveredMethods += pkgTotalCovered; + out.println(""); + + List classes = new ArrayList(pkg.getClasses()); + Collections.sort(classes, comparator); + + for (ApiClass apiClass : classes) { + if (apiClass.getTotalMethods() > 0) { + out.println(""); + + for (ApiConstructor constructor : apiClass.getConstructors()) { + out.println(""); + if (constructor.isDeprecated()) { + if (constructor.isCovered()) { + totalCoveredMethods -= 1; + } + totalMethods -= 1; + } + for (String parameterType : constructor.getParameterTypes()) { + out.println(""); + } + + out.println(""); + } + + for (ApiMethod method : apiClass.getMethods()) { + out.println(""); + if (method.isDeprecated()) { + if (method.isCovered()) { + totalCoveredMethods -= 1; + } + totalMethods -= 1; + } + for (String parameterType : method.getParameterTypes()) { + out.println(""); + } + + out.println(""); + } + out.println(""); + } + } + out.println(""); + } + } + + out.println(""); + out.println(""); + out.println(""); + } +} diff --git a/src/res/api-coverage.xsl b/src/res/api-coverage.xsl new file mode 100644 index 0000000..1a56eb0 --- /dev/null +++ b/src/res/api-coverage.xsl @@ -0,0 +1,190 @@ + + + + ]> + + + + + + <xsl:value-of select="api-coverage/@title" /> + + + + +

+
+ Generated: +
+
+ Total: % +  (/) +
+
+ Source Modules () +
+ +
    + + + + + + + +
+ + + + + + + + + + + red + yellow + green + + + + + + deprecated + + + + +
  • + + +  % +  (/) + +
  • +
    + + + + + + deprecated + + + + + + + [X] + [ ] + +   +  abstract +  static +  final +   +   + +
    +
    + + ( + + + ) + + + + -- cgit v1.2.3