blob: 61f5f208aea4366629992f9105d746e355c4f909 [file] [log] [blame]
"""Converts Jacoco XMl to LCOV.
- Resolves classes to sourcefiles
- Identifies classes without sourcefiles
- Identifies uninstrumented sourcefiles
- Reports packages and classes with most uncovered lines
"""
import itertools as IT
import os
import xml.etree.ElementTree as ET
def main():
# directory subtrees omitted from consideration
skip = [
# tests
'./tools/adt/idea/adt-ui-model/testSrc',
'./tools/adt/idea/adt-ui/src/test',
'./tools/adt/idea/android-adb/testSrc',
'./tools/adt/idea/android-common/test',
'./tools/adt/idea/android-debuggers/testSrc',
'./tools/adt/idea/android-kotlin/android-extensions-idea/testData',
'./tools/adt/idea/android-kotlin/android-extensions-idea/tests',
'./tools/adt/idea/android-kotlin/android-extensions-jps/testData',
'./tools/adt/idea/android-kotlin/idea-android/testData',
'./tools/adt/idea/android-kotlin/idea-android/tests',
'./tools/adt/idea/android-lang-databinding/testSrc',
'./tools/adt/idea/android-lang/testSrc',
'./tools/adt/idea/android-layout-inspector/testSrc',
'./tools/adt/idea/android-test-framework/testSrc',
'./tools/adt/idea/android-uitests/testData',
'./tools/adt/idea/android-uitests/testSrc',
'./tools/adt/idea/android/testData',
'./tools/adt/idea/android/testSrc',
'./tools/adt/idea/apkanalyzer/testSrc',
'./tools/adt/idea/assistant/testSrc',
'./tools/adt/idea/build-common/testSrc',
'./tools/adt/idea/connection-assistant/testSrc',
'./tools/adt/idea/databinding/testData',
'./tools/adt/idea/databinding/testSrc',
'./tools/adt/idea/deploy/testSrc',
'./tools/adt/idea/designer/testData',
'./tools/adt/idea/designer/testSrc',
'./tools/adt/idea/jps-plugin/testData',
'./tools/adt/idea/jps-plugin/testSrc',
'./tools/adt/idea/kotlin-integration/testData',
'./tools/adt/idea/kotlin-integration/testSrc',
'./tools/adt/idea/native-symbolizer/testSrc',
'./tools/adt/idea/observable-ui/testSrc',
'./tools/adt/idea/observable/testSrc',
'./tools/adt/idea/transport-database/testSrc',
'./tools/adt/idea/profilers-android/testSrc',
'./tools/adt/idea/profilers-ui/testSrc',
'./tools/adt/idea/profilers/testSrc',
'./tools/adt/idea/project-system-gradle/testSrc',
'./tools/adt/idea/project-system/testSrc',
'./tools/adt/idea/sdk-updates/testSrc',
'./tools/adt/idea/smali/testSrc',
'./tools/adt/idea/swingp/testSrc',
'./tools/adt/idea/uitest-framework-bazel/testSrc',
'./tools/adt/idea/uitest-framework-gradle/testSrc',
'./tools/adt/idea/uitest-framework/testSrc',
'./tools/adt/idea/whats-new-assistant/testSrc',
'./tools/adt/idea/wizard-model/testSrc',
'./tools/analytics-library/crash/src/test',
'./tools/analytics-library/publisher/src/test',
'./tools/analytics-library/shared/src/test',
'./tools/analytics-library/testing/src/test',
'./tools/analytics-library/tracker/src/test',
'./tools/apksig/src/test',
'./tools/apkzlib/src/test',
'./tools/base/apkparser/analyzer/src/test',
'./tools/base/apkparser/binary-resources/src/test',
'./tools/base/apkparser/cli/src/test',
'./tools/base/bazel/test',
'./tools/base/bazel/testSrc',
'./tools/base/build-system/builder-model/src/test',
'./tools/base/build-system/builder-test-api/src/test',
'./tools/base/build-system/builder/src/test',
'./tools/base/build-system/gradle-api/src/test',
'./tools/base/build-system/gradle-core/src/test',
'./tools/base/build-system/gradle-experimental/src/test',
'./tools/base/build-system/integration-test/application/src/test',
'./tools/base/build-system/integration-test/databinding/src/test',
'./tools/base/build-system/integration-test/framework/src/test',
'./tools/base/build-system/integration-test/java-library-model-builder/src/test',
'./tools/base/build-system/integration-test/test-projects',
'./tools/base/build-system/java-lib-plugin/java-lib-model-builder/src/test',
'./tools/base/build-system/manifest-merger/src/test',
'./tools/base/build-system/profile/src/test',
'./tools/base/common/src/test',
'./tools/base/ddmlib/src/test',
'./tools/base/deploy/deployer/src/test',
'./tools/base/deploy/test',
'./tools/base/devicelib/src/test',
'./tools/base/draw9patch/src/test',
'./tools/base/fakeadbserver/src/test',
'./tools/base/flags/src/test',
'./tools/base/layoutinspector/testSrc',
'./tools/base/layoutlib-api/sample/testproject',
'./tools/base/layoutlib-api/src/test',
'./tools/base/lint/libs/lint-gradle/src/test',
'./tools/base/lint/libs/lint-tests/src/test',
'./tools/base/ninepatch/src/test',
'./tools/base/perf-logger/src/test',
'./tools/base/perflib/src/test',
'./tools/base/pixelprobe/src/test',
'./tools/base/profiler/app/supportlib/src/test',
'./tools/base/profiler/integration-tests',
'./tools/base/profiler/tests',
'./tools/base/repository/src/test',
'./tools/base/sdk-common/src/test',
'./tools/base/sdklib/src/test',
'./tools/base/testutils/src/test',
'./tools/base/tracer/agent/testSrc',
'./tools/base/usb-devices/testSrc',
'./tools/base/vector-drawable-tool/src/test',
'./tools/buildSrc/src/test',
'./tools/data-binding/compilationTests/src/test',
'./tools/data-binding/compiler/src/test',
'./tools/data-binding/compilerCommon/src/test',
'./tools/data-binding/exec/src/test',
'./tools/data-binding/extensions-support/library/src/androidTest',
'./tools/data-binding/extensions/library/src/androidTest',
'./tools/data-binding/integration-tests-support',
'./tools/data-binding/integration-tests',
'./tools/data-binding/samples',
'./tools/dx/dalvik/dx/junit-tests',
'./tools/dx/dalvik/dx/tests',
# generated
'./tools/adt/idea/android-lang-databinding/gen',
'./tools/adt/idea/android-lang/gen',
'./tools/adt/idea/artwork/gen',
'./tools/adt/idea/smali/gen',
# external
'./tools/base/jobb',
'./tools/dx',
'./tools/external',
'./tools/idea',
'./tools/sherpa',
'./tools/studio',
'./tools/swing-testing',
'./tools/swt',
'./tools/vendor',
]
# file extensions we care about
ends = [
'.java',
'.kt',
]
print 'create a filename -> dirpath map of ./tools'
# we're using a filename -> directory path because it makes resolving to
# sourcefiles very fast
filetree = {}
# walk down the source file tree
for (dirpath, _, filenames) in os.walk('./tools'):
# ignore skipped directories
if any(IT.imap(dirpath.startswith, skip)):
continue
# otherwise add files to the map
for name in filenames:
if not any(IT.imap(name.endswith, ends)):
continue
if name in filetree:
filetree[name].add(dirpath)
else:
filetree[name] = set([dirpath])
print 'parse jacoco xml report'
root = ET.parse(
'./out/agent-coverage/tools/base/coverage_report/report.xml').getroot()
print 'resolve report package/file combos to directory paths'
# we're going to build a nest map like package -> (file -> path/coverage info)
# this makes it easy to do package or file level sorting for output
data = {}
for pkg in root.iter('package'):
pkg_name = pkg.get('name')
for sfile in pkg.iter('sourcefile'):
sfile_name = sfile.get('name')
if sfile_name in filetree:
# if there's a path with the package/class as a suffix, then that's the
# real sourcefile
matches = list(
IT.ifilter(lambda path: path.endswith(pkg_name),
filetree[sfile_name]))
if matches:
# the directory path upto the package portion
path = matches[0][0:matches[0].find(pkg_name)]
# the list of instrumented line numbers
instrumented = [int(line.get('nr')) for line in sfile.iter('line')]
# the set of covered line numbers
covered = set(
int(line.get('nr'))
for line in sfile.iter('line')
if line.get('ci') != '0')
if pkg_name not in data:
data[pkg_name] = {}
data[pkg_name][sfile_name] = {
'path': path,
'instrumented': instrumented,
'covered': covered,
}
print 'write to lcov file'
lcov = open('./out/lcov', 'w')
for pkg_name in data:
for sfile_name in data[pkg_name]:
filepath = os.path.join(data[pkg_name][sfile_name]['path'], pkg_name,
sfile_name)
lcov.write('SF:{}\n'.format(filepath))
for line_num in data[pkg_name][sfile_name]['instrumented']:
# format is DA:{line number},{number of hits}, but we don't care about
# detailed hit numbers so just use 1 for covered lines
lcov.write('DA:{},{}\n'.format(
line_num, int(line_num in data[pkg_name][sfile_name]['covered'])))
lcov.write('end_of_record\n')
if __name__ == '__main__':
main()