Commit d9862b84 authored by Ben Joyce's avatar Ben Joyce Committed by Commit Bot

Remove combined reports option after moving it.

Adds option to choose to use device or host files. The combined reports
functionality has been moved to code_coverage generation in the
tools/build repo.

Won't land until after verifying the bot script is working as expected
in:
https://chromium-review.googlesource.com/c/chromium/tools/build/+/2348094

Bug: 1107004
Change-Id: I628217c9249a0d69c3ec7d82c63aefd59087d6ad
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2353367Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Reviewed-by: default avatarYuke Liao <liaoyuke@chromium.org>
Commit-Queue: benjamin joyce <bjoyce@chromium.org>
Cr-Commit-Position: refs/heads/master@{#797825}
parent 5f9e51f8
...@@ -73,7 +73,6 @@ def CommonChecks(input_api, output_api): ...@@ -73,7 +73,6 @@ def CommonChecks(input_api, output_api):
output_api, output_api,
unit_tests=[ unit_tests=[
J('.', 'emma_coverage_stats_test.py'), J('.', 'emma_coverage_stats_test.py'),
J('.', 'generate_jacoco_report_test.py'),
J('.', 'list_class_verification_failures_test.py'), J('.', 'list_class_verification_failures_test.py'),
J('gyp', 'util', 'build_utils_test.py'), J('gyp', 'util', 'build_utils_test.py'),
J('gyp', 'util', 'manifest_utils_test.py'), J('gyp', 'util', 'manifest_utils_test.py'),
......
...@@ -12,23 +12,12 @@ import argparse ...@@ -12,23 +12,12 @@ import argparse
import fnmatch import fnmatch
import json import json
import os import os
import shutil
import sys import sys
from xml.dom import minidom
import devil_chromium import devil_chromium
from devil.utils import cmd_helper from devil.utils import cmd_helper
from pylib.constants import host_paths from pylib.constants import host_paths
_BUILD_UTILS_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, 'build', 'android',
'gyp')
with host_paths.SysPath(_BUILD_UTILS_PATH, 0):
from util import build_utils
_JAVA_COVERAGE_METRICS = [
'instruction', 'branch', 'line', 'complexity', 'method', 'class'
]
# Source paths should be passed to Jacoco in a way that the relative file paths # Source paths should be passed to Jacoco in a way that the relative file paths
# reflect the class package name. # reflect the class package name.
_PARTIAL_PACKAGE_NAMES = ['com/google', 'org/chromium'] _PARTIAL_PACKAGE_NAMES = ['com/google', 'org/chromium']
...@@ -45,24 +34,12 @@ _PARTIAL_PACKAGE_NAMES = ['com/google', 'org/chromium'] ...@@ -45,24 +34,12 @@ _PARTIAL_PACKAGE_NAMES = ['com/google', 'org/chromium']
# obj/chrome/android/features/tab_ui/java__process_prebuilt-filtered.jar' # obj/chrome/android/features/tab_ui/java__process_prebuilt-filtered.jar'
_SOURCES_JSON_FILES_SUFFIX = '__jacoco_sources.json' _SOURCES_JSON_FILES_SUFFIX = '__jacoco_sources.json'
# These should match the jar class files generated in internal_rules.gni # These should match the jar class files generated in internal_rules.gni
_DEVICE_CLASS_EXCLUDE_SUFFIX = 'host_filter.jar' _DEVICE_CLASS_EXCLUDE_SUFFIX = 'host_filter.jar'
_HOST_CLASS_EXCLUDE_SUFFIX = 'device_filter.jar' _HOST_CLASS_EXCLUDE_SUFFIX = 'device_filter.jar'
def _AddMissingNodes(device_dict, host_dict, root_node, attribute):
# Adds any node in host that are not in device.
for key in host_dict:
node_attribute = host_dict[key].getAttribute(attribute)
if node_attribute not in device_dict:
added_node = root_node.appendChild(host_dict[key])
device_dict[node_attribute] = added_node
def _CreateAttributeToObjectDict(element_list, attrib):
return {e.getAttribute(attrib): e for e in element_list}
def _CreateClassfileArgs(class_files, exclude_suffix=None): def _CreateClassfileArgs(class_files, exclude_suffix=None):
"""Returns a list of files that don't have a given suffix. """Returns a list of files that don't have a given suffix.
...@@ -84,130 +61,26 @@ def _CreateClassfileArgs(class_files, exclude_suffix=None): ...@@ -84,130 +61,26 @@ def _CreateClassfileArgs(class_files, exclude_suffix=None):
return result_class_files return result_class_files
def _CombineXmlFiles(combined_file_path, device_file_path, host_file_path): def _GenerateReportOutputArgs(args, class_files):
"""Combines two xml jacoco report files into one. class_jar_exclude = None
if args.device_or_host == 'device':
Expected input is two jacoco coverage report xml files. class_jar_exclude = _DEVICE_CLASS_EXCLUDE_SUFFIX
The report is composed of a tree of nodes, the root node is the "report" node elif args.device_or_host == 'host':
which contains counters and packages. class_jar_exclude = _HOST_CLASS_EXCLUDE_SUFFIX
-The package nodes contain class nodes, sourcefile nodes, and counters.
-The sourcefile nodes contain the file name, line nodes, and counters
-The class nodes contain the class's name, method nodes, and counters
-The method nodes contain the method's name and counters.
-The line nodes contain 4 attributes,
nr=line number corresponding to the physical line in the sourcefile.
mi=missed instructions for the line
ci=covered instructiosn for the line
mb=missed branch for the line
cb=covered branch for the line
The counters contain an instruction type as found in _JAVA_COVERAGE_METRICS
and the total number of "covered" or "missed" items on that metric in that
node. So a class node's counters contain the sum of counters in the methods.
The package node's counters contain the sum total of the counters in the
sourcefile nodes. The sourcefile nodes contain the sum total of the counters
in the line node.
The code walks down to each method in the device report and finds
the matching method in the host_report. It then chooses to use the coverage
data from whichever node is higher. This is not perfect as device can have
10 covered and 10 missed, and host can have 15 covered and 5 missed, so
maybe it should be 20 covered and 0 missed if the coverage overlaps properly.
If a method/class/package is in the host report and not in the device report,
it adds it into hte device_report.
The code then walks down the sourcefiles and adds to device_report any lines
that are in host_report and not already present. It then compares every line
in device_report to host_report and chooses to use the line that has higher
ci. It then calculates a new sum for the counters in sourcefile and packages
and reports (not classes and methods).
Args:
combined_file_path: Where to write the combined report file to.
device_file_path: The location of the device coverage report.
host_file_path:The location of the device coverage report.
Returns:
Results are written to combined_file_path
"""
device_tree = minidom.parse(device_file_path)
host_tree = minidom.parse(host_file_path)
device_report_node = device_tree.getElementsByTagName('report')[0]
device_packages = device_tree.getElementsByTagName('package')
host_packages = host_tree.getElementsByTagName('package')
device_name_to_package_dict = _CreateAttributeToObjectDict(
device_packages, 'name')
host_name_to_package_dict = _CreateAttributeToObjectDict(
host_packages, 'name')
_UpdateAllNodesToHigherCoverage(device_name_to_package_dict,
host_name_to_package_dict)
_AddMissingNodes(device_name_to_package_dict, host_name_to_package_dict,
device_report_node, 'name')
# Only updates one layer of counters, ie doesn't do grandchildren.
_UpdateChildrenCounters(device_report_node, 'package')
with open(combined_file_path, 'w') as xmlfile:
device_tree.writexml(xmlfile)
def _CreateCounterMap(counter_list):
# Creates a map of counter types to the counter node.
counter_map = {
counter.getAttribute('type').lower(): counter
for counter in counter_list
}
return counter_map
def _GetCoveredAndMissedFromCounter(counter):
return (counter.getAttribute('covered'), counter.getAttribute('missed'))
def _GetDictForEachElement(device_node, host_node, children_tag, attribute_tag):
# Returns dictionaries mapping the attribute tag to the node's children.
device_children = device_node.getElementsByTagName(children_tag)
host_children = host_node.getElementsByTagName(children_tag)
device_mapping_dict = _CreateAttributeToObjectDict(device_children,
attribute_tag)
host_mapping_dict = _CreateAttributeToObjectDict(host_children, attribute_tag)
return (device_mapping_dict, host_mapping_dict)
def _CreateTotalDicts():
total_dicts = {}
for metric in _JAVA_COVERAGE_METRICS:
new_dict = {'covered': 0, 'missed': 0}
total_dicts[metric] = new_dict
return total_dicts
def _GenerateReportOutputArgs(args,
class_files,
class_jar_exclude,
report_name=None,
report_file=None):
cmd = _CreateClassfileArgs(class_files, class_jar_exclude) cmd = _CreateClassfileArgs(class_files, class_jar_exclude)
if args.format == 'html': if args.format == 'html':
report_dir = os.path.join(args.output_dir, report_name) if not os.path.exists(args.output_dir):
if not os.path.exists(report_dir): os.makedirs(args.output_dir)
os.makedirs(report_dir) cmd += ['--html', args.output_dir]
cmd += ['--html', report_dir]
elif args.format == 'xml': elif args.format == 'xml':
cmd += ['--xml', report_file] cmd += ['--xml', args.output_file]
elif args.format == 'csv': elif args.format == 'csv':
cmd += ['--csv', report_file] cmd += ['--csv', args.output_file]
return cmd return cmd
def _GetCountersList(root_node):
return [node for node in root_node.childNodes if node.tagName == 'counter']
def _GetFilesWithSuffix(root_dir, suffix): def _GetFilesWithSuffix(root_dir, suffix):
"""Gets all files with a given suffix. """Gets all files with a given suffix.
...@@ -226,184 +99,6 @@ def _GetFilesWithSuffix(root_dir, suffix): ...@@ -226,184 +99,6 @@ def _GetFilesWithSuffix(root_dir, suffix):
return files return files
def _SetHigherCounter(device_counter, host_counter):
# Ideally would use min/max on covered and missed, but want to make sure
# to use the variables from the same counter.
if int(device_counter.getAttribute('covered')) >= int(
host_counter.getAttribute('covered')):
chosen_counter = device_counter
else:
chosen_counter = host_counter
covered, missed = _GetCoveredAndMissedFromCounter(chosen_counter)
device_counter.setAttribute('covered', covered)
device_counter.setAttribute('missed', missed)
def _SetHigherMethodCoverage(device_method_dict, host_method_dict):
for method_key in device_method_dict:
device_method = device_method_dict[method_key]
if method_key not in host_method_dict:
continue
device_counter_map = _CreateCounterMap(
device_method.getElementsByTagName('counter'))
host_counter_map = _CreateCounterMap(
host_method_dict[method_key].getElementsByTagName('counter'))
for metric in device_counter_map:
if metric in host_counter_map:
_SetHigherCounter(device_counter_map[metric], host_counter_map[metric])
def _UpdateAllNodesToHigherCoverage(device_package_dict, host_package_dict):
# Go to every package, then every class in the package, then every method
# in the class and choose the coverage that is higher.
for key in device_package_dict:
device_package = device_package_dict[key]
if key not in host_package_dict:
continue
host_package = host_package_dict[key]
device_class_dict, host_class_dict = _GetDictForEachElement(
device_package, host_package, 'class', 'name')
_AddMissingNodes(device_class_dict, host_class_dict, device_package, 'name')
for class_key in device_class_dict:
device_class = device_class_dict[class_key]
if class_key not in host_class_dict:
continue
host_class = host_class_dict[class_key]
device_method_dict, host_method_dict = _GetDictForEachElement(
device_class, host_class, 'method', 'line')
_AddMissingNodes(device_method_dict, host_method_dict, device_class,
'line')
# Rewrite the values in method coverage based on which is higher.
# Then update the counter at the class level.
_SetHigherMethodCoverage(device_method_dict, host_method_dict)
_UpdateChildrenCounters(device_class, 'method')
_UpdatePackageSourceFiles(device_package, host_package)
_UpdateChildrenCounters(device_package, 'sourcefile')
def _UpdateChildrenCounters(root_node, tag_name):
# Updates the children (not deeper, ie grandchildren) of the node. This is to
# avoid double counting counters (as a class's total counter is the summation
# of the method counters.)
counters = _GetCountersList(root_node)
total_dicts = _GetCounterTotalsForTagName(root_node, tag_name)
_UpdateCountersFromTotal(counters, total_dicts)
def _UpdateCountersFromTotal(counter_nodes, total_dicts):
for counter in counter_nodes:
counter_type = counter.getAttribute('type').lower()
counter.setAttribute('covered', str(total_dicts[counter_type]['covered']))
counter.setAttribute('missed', str(total_dicts[counter_type]['missed']))
def _GetCounterTotalsForTagName(root_node, tag_name):
# Gets a diciontary of the totals of the counters in the children's counters.
total_dicts = _CreateTotalDicts()
# Cannot just use getElementByTagName as that will go more than one layer
# deep. Want to avoid double counting the counters.
nodes = [node for node in root_node.childNodes if node.tagName == tag_name]
for node in nodes:
for counter in _GetCountersList(node):
covered_lines, missed_lines = _GetCoveredAndMissedFromCounter(counter)
counter_type = counter.getAttribute('type').lower()
total_dicts[counter_type]['covered'] += int(covered_lines)
total_dicts[counter_type]['missed'] += int(missed_lines)
return total_dicts
def _UpdateLineCodeCoverageNodes(device_dict, host_dict, device_source_node,
host_source_node):
# Gets the nodes that are in the host_tree and not in the device_tree.
# If the node exists in both trees, choose the one that higher
# covered instructions (ci).
instruction_list = ['ci', 'cb', 'mi', 'mb']
total_dict = {inst: 0 for inst in instruction_list}
# Add any nodes that are in host, that aren't in dict. This adds the entry
# to device_dict.
_AddMissingNodes(device_dict, host_dict, device_source_node, 'nr')
# Check all the lines that are the same. Set the device_node to have the
# fields that are higher.
for key in device_dict:
device_line = device_dict[key]
if key not in host_dict:
continue
host_line = host_dict[key]
host_line_ci = int(host_line.getAttribute('ci'))
device_line_ci = int(device_line.getAttribute('ci'))
# We'll take all the data from the host line if ci is better.
if device_line_ci < host_line_ci:
for inst in instruction_list:
device_line.setAttribute(inst, host_line.getAttribute(inst))
# Sum up all the coverage numbers.
for key in device_dict:
device_line = device_dict[key]
for inst in instruction_list:
total_dict[inst] += int(device_line.getAttribute(inst))
_UpdateSourceFileCounters(device_source_node, host_source_node, total_dict)
def _UpdatePackageSourceFiles(device_package, host_package):
# Adds any source files in the host_tree that are not in the device_tree.
# One the source file that are the same, combine the source files
# based on "nr"(line number)
device_sources = device_package.getElementsByTagName('sourcefile')
host_sources = host_package.getElementsByTagName('sourcefile')
device_name_to_sources_dict = _CreateAttributeToObjectDict(
device_sources, 'name')
host_name_to_sources_dict = _CreateAttributeToObjectDict(host_sources, 'name')
# Adds any source files that are in the host package that
# are not in the device package.
_AddMissingNodes(device_name_to_sources_dict, host_name_to_sources_dict,
device_package, 'name')
for key in device_name_to_sources_dict:
device_source_node = device_name_to_sources_dict[key]
host_source_node = host_name_to_sources_dict[key]
device_line_dict = _CreateAttributeToObjectDict(
device_source_node.getElementsByTagName('line'), 'nr')
host_line_dict = _CreateAttributeToObjectDict(
host_source_node.getElementsByTagName('line'), 'nr')
# Takes all the "lines" in the source file, then compares them and chooses
# the "line" that has higher coverage.
_UpdateLineCodeCoverageNodes(device_line_dict, host_line_dict,
device_source_node, host_source_node)
def _UpdateSourceFileCounters(device_source_node, host_source_node, total_dict):
# Update the counter nodes of the source file.
device_counter_dict = _CreateAttributeToObjectDict(
device_source_node.getElementsByTagName('counter'), 'type')
host_counter_dict = _CreateAttributeToObjectDict(
host_source_node.getElementsByTagName('counter'), 'type')
for inst in device_counter_dict:
device_counter = device_counter_dict[inst]
if inst == 'INSTRUCTION':
device_counter.setAttribute('covered', str(total_dict['ci']))
device_counter.setAttribute('missed', str(total_dict['mi']))
elif inst == 'BRANCH':
device_counter.setAttribute('covered', str(total_dict['cb']))
device_counter.setAttribute('missed', str(total_dict['mb']))
else:
covered_val = max(int(device_counter.getAttribute('covered')),
int(host_counter_dict[inst].getAttribute('covered')))
missed_val = min(int(device_counter.getAttribute('missed')),
int(host_counter_dict[inst].getAttribute('missed')))
device_counter.setAttribute('covered', str(covered_val))
device_counter.setAttribute('missed', str(missed_val))
def _ParseArguments(parser): def _ParseArguments(parser):
"""Parses the command line arguments. """Parses the command line arguments.
...@@ -418,6 +113,12 @@ def _ParseArguments(parser): ...@@ -418,6 +113,12 @@ def _ParseArguments(parser):
required=True, required=True,
choices=['html', 'xml', 'csv'], choices=['html', 'xml', 'csv'],
help='Output report format. Choose one from html, xml and csv.') help='Output report format. Choose one from html, xml and csv.')
parser.add_argument(
'--device-or-host',
choices=['device', 'host'],
help='Selection on whether to use the device classpath files or the '
'host classpath files. Host would typically be used for junit tests '
' and device for tests that run on the device.')
parser.add_argument('--output-dir', help='html report output directory.') parser.add_argument('--output-dir', help='html report output directory.')
parser.add_argument('--output-file', parser.add_argument('--output-file',
help='xml file to write device coverage results.') help='xml file to write device coverage results.')
...@@ -435,8 +136,8 @@ def _ParseArguments(parser): ...@@ -435,8 +136,8 @@ def _ParseArguments(parser):
nargs='+', nargs='+',
help='Location of Java non-instrumented class files. ' help='Location of Java non-instrumented class files. '
'Use non-instrumented jars instead of instrumented jars. ' 'Use non-instrumented jars instead of instrumented jars. '
'e.g. use chrome_java__process_prebuilt-filtered.jar instead of' 'e.g. use chrome_java__process_prebuilt_(host/device)_filter.jar instead'
'chrome_java__process_prebuilt-instrumented.jar') 'of chrome_java__process_prebuilt-instrumented.jar')
parser.add_argument( parser.add_argument(
'--sources', '--sources',
nargs='+', nargs='+',
...@@ -460,6 +161,8 @@ def _ParseArguments(parser): ...@@ -460,6 +161,8 @@ def _ParseArguments(parser):
if not (args.sources_json_dir or args.class_files): if not (args.sources_json_dir or args.class_files):
parser.error('At least either --sources-json-dir or --class-files needed.') parser.error('At least either --sources-json-dir or --class-files needed.')
if not args.device_or_host and args.sources_json_dir:
parser.error('--device-or-host selection needed with --sources-json-dir')
return args return args
...@@ -510,28 +213,8 @@ def main(): ...@@ -510,28 +213,8 @@ def main():
for source in fixed_source_dirs: for source in fixed_source_dirs:
cmd += ['--sourcefiles', source] cmd += ['--sourcefiles', source]
with build_utils.TempDir() as temp_dir: cmd = cmd + _GenerateReportOutputArgs(args, class_files)
temp_device_file = os.path.join(temp_dir, 'temp_device') exit_code = cmd_helper.RunCmd(cmd)
temp_host_file = os.path.join(temp_dir, 'temp_host')
device_cmd = cmd + _GenerateReportOutputArgs(
args, class_files, _DEVICE_CLASS_EXCLUDE_SUFFIX, 'device_report',
temp_device_file)
host_cmd = cmd + _GenerateReportOutputArgs(
args, class_files, _HOST_CLASS_EXCLUDE_SUFFIX, 'host_report',
temp_host_file)
device_exit_code = cmd_helper.RunCmd(device_cmd)
host_exit_code = cmd_helper.RunCmd(host_cmd)
exit_code = device_exit_code or host_exit_code
if args.format == 'xml':
_CombineXmlFiles(args.output_file, temp_device_file, temp_host_file)
print('Combined device and junit reports.')
elif args.format == 'csv':
shutil.copyfile(temp_device_file,
os.path.join(args.output_dir, 'device.csv'))
shutil.copyfile(temp_host_file, os.path.join(args.output_dir, 'host.csv'))
if args.cleanup: if args.cleanup:
for f in coverage_files: for f in coverage_files:
...@@ -539,7 +222,7 @@ def main(): ...@@ -539,7 +222,7 @@ def main():
# Command tends to exit with status 0 when it actually failed. # Command tends to exit with status 0 when it actually failed.
if not exit_code: if not exit_code:
if args.format in ('html', 'csv'): if args.format == 'html':
if not os.path.isdir(args.output_dir) or not os.listdir(args.output_dir): if not os.path.isdir(args.output_dir) or not os.listdir(args.output_dir):
print('No report generated at %s' % args.output_dir) print('No report generated at %s' % args.output_dir)
exit_code = 1 exit_code = 1
......
#!/usr/bin/env python
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# pylint: disable=protected-access
import os
import unittest
from xml.dom import minidom
import generate_jacoco_report
from pylib.constants import host_paths
_BUILD_UTILS_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, 'build', 'android',
'gyp')
with host_paths.SysPath(_BUILD_UTILS_PATH, 0):
from util import build_utils
# Does not work as """ string because of newlines indent whitespace in
# the xml parseString().
DEVICE_XML = (
'<report name="Fake_Device_report">'
'<sessioninfo id="123" start="456" dump="789"/>'
'<package name="package1">'
'<class name="class1" sourcefilename="class1.java">'
# The coverage in these methods is less than the coverage in the HOST_XML.
# Will make sure the higher coverage is set in the combined report.
'<method name="method1" desc="method1_desc" line="19">'
'<counter type="INSTRUCTION" missed="7" covered="0"/>'
'<counter type="BRANCH" missed="2" covered="0"/>'
'<counter type="LINE" missed="2" covered="0"/>'
'<counter type="COMPLEXITY" missed="2" covered="0"/>'
'<counter type="METHOD" missed="1" covered="0"/>'
'</method>'
'<method name="method2" desc="method2_desc" line="15">'
'<counter type="INSTRUCTION" missed="8" covered="0"/>'
'<counter type="BRANCH" missed="2" covered="0"/>'
'<counter type="LINE" missed="1" covered="0"/>'
'<counter type="COMPLEXITY" missed="2" covered="0"/>'
'<counter type="METHOD" missed="1" covered="0"/>'
'</method>'
'<counter type="INSTRUCTION" missed="15" covered="0"/>'
'<counter type="BRANCH" missed="4" covered="0"/>'
'<counter type="LINE" missed="3" covered="0"/>'
'<counter type="COMPLEXITY" missed="4" covered="0"/>'
'<counter type="METHOD" missed="2" covered="0"/>'
'<counter type="CLASS" missed="1" covered="0"/>'
'</class>'
# This class is not in the HOST_XML, will make sure it's still in the
# combined report.
'<class name="class2" sourcefilename="class2.java"/>'
# This source file matches the one in HOST_XML.
'<sourcefile name="class1.java">'
# nr15 matches the HOST_XML but has less coverage. nr19/20 are not in
# HOST_XML.
'<line nr="15" mi="9" ci="0" mb="2" cb="0"/>'
'<line nr="19" mi="5" ci="0" mb="2" cb="0"/>'
'<line nr="20" mi="1" ci="1" mb="0" cb="0"/>'
'<counter type="INSTRUCTION" missed="15" covered="1"/>'
'<counter type="BRANCH" missed="4" covered="0"/>'
'<counter type="LINE" missed="3" covered="0"/>'
'<counter type="COMPLEXITY" missed="4" covered="0"/>'
'<counter type="METHOD" missed="2" covered="0"/>'
'<counter type="CLASS" missed="1" covered="0"/>'
'</sourcefile>'
'<counter type="INSTRUCTION" missed="15" covered="1"/>'
'<counter type="BRANCH" missed="4" covered="0"/>'
'<counter type="LINE" missed="3" covered="0"/>'
'<counter type="COMPLEXITY" missed="4" covered="0"/>'
'<counter type="METHOD" missed="2" covered="0"/>'
'<counter type="CLASS" missed="1" covered="0"/>'
'</package>'
# This device package 3 contains more coverage in its sourcefile
# than the one in HOST_XML. We make sure its coverage is chosen.
'<package name="package3">'
'<class name="class5" sourcefilename="class1.java">'
'<method name="method1" desc="method1_desc" line="19">'
'<counter type="INSTRUCTION" missed="100" covered="100"/>'
'<counter type="BRANCH" missed="100" covered="100"/>'
'<counter type="LINE" missed="100" covered="100"/>'
'<counter type="COMPLEXITY" missed="100" covered="100"/>'
'<counter type="METHOD" missed="100" covered="100"/>'
'</method>'
'<method name="method2" desc="method2_desc" line="15">'
'<counter type="INSTRUCTION" missed="100" covered="100"/>'
'<counter type="BRANCH" missed="100" covered="100"/>'
'<counter type="LINE" missed="100" covered="100"/>'
'<counter type="COMPLEXITY" missed="100" covered="100"/>'
'<counter type="METHOD" missed="100" covered="100"/>'
'</method>'
'</class>'
'<class name="class20" sourcefilename="class20.java"/>'
'<sourcefile name="sourcefile_xxx1">'
'<line nr="1" mi="100" ci="100" mb="100" cb="100"/>'
'<line nr="12" mi="50" ci="50" mb="50" cb="50"/>'
'<line nr="123" mi="50" ci="50" mb="50" cb="50"/>'
'<counter type="INSTRUCTION" missed="200" covered="200"/>'
'<counter type="BRANCH" missed="200" covered="200"/>'
'<counter type="LINE" missed="200" covered="200"/>'
'<counter type="COMPLEXITY" missed="200" covered="200"/>'
'<counter type="METHOD" missed="200" covered="200"/>'
'<counter type="CLASS" missed="1" covered="1"/>'
'</sourcefile>'
'<counter type="INSTRUCTION" missed="200" covered="200"/>'
'<counter type="BRANCH" missed="200" covered="200"/>'
'<counter type="LINE" missed="200" covered="200"/>'
'<counter type="COMPLEXITY" missed="200" covered="200"/>'
'<counter type="METHOD" missed="200" covered="200"/>'
'<counter type="CLASS" missed="0" covered="1"/>'
'</package>'
# These counters don't matter, but would ideally be a sum of all the
# source file counters, which should match the sum of all the method
# coverages
'<counter type="INSTRUCTION" missed="15" covered="1"/>'
'<counter type="BRANCH" missed="4" covered="0"/>'
'<counter type="LINE" missed="3" covered="0"/>'
'<counter type="COMPLEXITY" missed="4" covered="0"/>'
'<counter type="METHOD" missed="2" covered="0"/>'
'<counter type="CLASS" missed="1" covered="0"/>'
'</report>')
HOST_XML = (
'<report name="Fake_Device_report">'
'<sessioninfo id="789" start="456" dump="123"/>'
'<package name="package1">'
# This class and methods match those in DEVICE_XML, but have higher
# coverage numbers so we'll check they make it into the combined
# report.
'<class name="class1" sourcefilename="class1.java">'
'<method name="method1" desc="method1_desc" line="19">'
'<counter type="INSTRUCTION" missed="6" covered="1"/>'
'<counter type="BRANCH" missed="1" covered="1"/>'
'<counter type="LINE" missed="1" covered="1"/>'
'<counter type="COMPLEXITY" missed="1" covered="1"/>'
'<counter type="METHOD" missed="0" covered="1"/>'
'</method>'
'<method name="method2" desc="method2_desc" line="15">'
'<counter type="INSTRUCTION" missed="7" covered="1"/>'
'<counter type="BRANCH" missed="1" covered="1"/>'
'<counter type="LINE" missed="0" covered="1"/>'
'<counter type="COMPLEXITY" missed="1" covered="1"/>'
'<counter type="METHOD" missed="0" covered="1"/>'
'</method>'
# Method 3 is not in DEVICE_XML, we'll make sure it's in the
# combined report.
'<method name="method3" desc="method3_desc" line="70"/>'
# This is a total of the above method coverage. The numbers don't actually
# matter as the combination report resums them.
'<counter type="INSTRUCTION" missed="13" covered="2"/>'
'<counter type="BRANCH" missed="2" covered="2"/>'
'<counter type="LINE" missed="1" covered="2"/>'
'<counter type="COMPLEXITY" missed="2" covered="2"/>'
'<counter type="METHOD" missed="0" covered="2"/>'
'<counter type="CLASS" missed="0" covered="1"/>'
'</class>'
# This class 3 is not in the other package so we make sure it is
# present after the combination.
'<class name="not_in_dev_package" sourcefilename="class3.java"/>'
'<sourcefile name="class1.java">'
# nr15 has higher coverage than DEVICE_XML's nr15, we'll make sure
# the combined report uses it.
'<line nr="15" mi="7" ci="2" mb="1" cb="1"/>'
# nr190/200 are not in DEVICE_XML. We'll make sure they're included
# in the combined report.
'<line nr="190" mi="5" ci="0" mb="1" cb="1"/>'
'<line nr="200" mi="1" ci="0" mb="0" cb="0"/>'
# These counters get summed after the report combination so don't
# really matter for us, but normally they would reflect the totals
# from the above 3 lines of mi,ci,cb,mb.
'<counter type="INSTRUCTION" missed="13" covered="2"/>'
'<counter type="BRANCH" missed="2" covered="2"/>'
'<counter type="LINE" missed="1" covered="2"/>'
'<counter type="COMPLEXITY" missed="2" covered="2"/>'
'<counter type="METHOD" missed="0" covered="2"/>'
'<counter type="CLASS" missed="0" covered="1"/>'
'</sourcefile>'
# This source file is not found in DEVICE_XML. We'll want to make
# sure it's in the combined report, and that it's coverage
# numbers are included.
'<sourcefile name="new sourcefile.java">'
'<line nr="1" mi="5" ci="6" mb="2" cb="3"/>'
'<line nr="2" mi="3" ci="4" mb="1" cb="2"/>'
'<line nr="3" mi="100" ci="0" mb="0" cb="0"/>'
'<counter type="INSTRUCTION" missed="108" covered="10"/>'
'<counter type="BRANCH" missed="3" covered="5"/>'
'<counter type="LINE" missed="0" covered="0"/>'
'<counter type="COMPLEXITY" missed="0" covered="0"/>'
'<counter type="METHOD" missed="1" covered="3"/>'
'<counter type="CLASS" missed="0" covered="0"/>'
'</sourcefile>'
'<counter type="INSTRUCTION" missed="13" covered="2"/>'
'<counter type="BRANCH" missed="2" covered="2"/>'
'<counter type="LINE" missed="1" covered="2"/>'
'<counter type="COMPLEXITY" missed="2" covered="2"/>'
'<counter type="METHOD" missed="0" covered="2"/>'
'<counter type="CLASS" missed="0" covered="1"/>'
'</package>'
'<package name="not_in_device_xml"/>'
'<package name="package3">'
'<class name="class5" sourcefilename="class1.java">'
'<method name="method1" desc="method1_desc" line="19">'
'<counter type="INSTRUCTION" missed="100" covered="100"/>'
'<counter type="BRANCH" missed="100" covered="100"/>'
'<counter type="LINE" missed="100" covered="100"/>'
'<counter type="COMPLEXITY" missed="100" covered="100"/>'
'<counter type="METHOD" missed="100" covered="100"/>'
'</method>'
'<method name="method2" desc="method2_desc" line="15">'
'<counter type="INSTRUCTION" missed="100" covered="100"/>'
'<counter type="BRANCH" missed="100" covered="100"/>'
'<counter type="LINE" missed="100" covered="100"/>'
'<counter type="COMPLEXITY" missed="100" covered="100"/>'
'<counter type="METHOD" missed="100" covered="100"/>'
'</method>'
'</class>'
'<class name="class20" sourcefilename="class20.java"/>'
# This sourcefile has coverage less than that of DEVICE_XML so we
# want to ensure that DEVICE_XML's coverage numbers are used.
'<sourcefile name="sourcefile_xxx1">'
'<line nr="1" mi="50" ci="50" mb="50" cb="50"/>'
'<line nr="12" mi="50" ci="50" mb="50" cb="50"/>'
'<line nr="123" mi="50" ci="50" mb="50" cb="50"/>'
# These coverage numbers don't matter as the report sums up new
# ones.
'<counter type="INSTRUCTION" missed="150" covered="150"/>'
'<counter type="BRANCH" missed="150" covered="150"/>'
'<counter type="LINE" missed="200" covered="200"/>'
'<counter type="COMPLEXITY" missed="200" covered="200"/>'
'<counter type="METHOD" missed="200" covered="200"/>'
'<counter type="CLASS" missed="1" covered="1"/>'
'</sourcefile>'
# These coverage numbers don't matter as the report sums up new
# ones.
'<counter type="INSTRUCTION" missed="200" covered="200"/>'
'<counter type="BRANCH" missed="200" covered="200"/>'
'<counter type="LINE" missed="200" covered="200"/>'
'<counter type="COMPLEXITY" missed="200" covered="200"/>'
'<counter type="METHOD" missed="200" covered="200"/>'
'<counter type="CLASS" missed="0" covered="1"/>'
'</package>'
# These coverage numbers don't matter as the report sums up new
# ones.
'<counter type="INSTRUCTION" missed="163" covered="327"/>'
'<counter type="BRANCH" missed="207" covered="207"/>'
'<counter type="LINE" missed="1" covered="2"/>'
'<counter type="COMPLEXITY" missed="2" covered="2"/>'
'<counter type="METHOD" missed="205" covered="205"/>'
'<counter type="CLASS" missed="0" covered="1"/>'
'</report>')
class GenerateJacocoReportTest(unittest.TestCase):
"""Tests for _GenerateJacocoReport.
"""
def setUp(self):
super(GenerateJacocoReportTest, self).setUp()
# Need to reparse every test as some functions modify the tree inplace.
self.dev_tree = minidom.parseString(DEVICE_XML)
self.host_tree = minidom.parseString(HOST_XML)
self.dev_root_node = self.dev_tree.getElementsByTagName('report')[0]
self.host_root_node = self.host_tree.getElementsByTagName('report')[0]
def verify_counters(self, node, expected_num_of_counters, answer_dict):
# Takes a node, the expected counters for the node, and answer dict.
# answer_dict maps an instruction to a tuple of covered and missed lines.
counters = generate_jacoco_report._GetCountersList(node)
self.assertEqual(len(counters), expected_num_of_counters)
counter_map = generate_jacoco_report._CreateCounterMap(counters)
for key in answer_dict:
covered, missed = answer_dict[key]
self.assertEqual(counter_map[key].getAttribute('covered'), covered)
self.assertEqual(counter_map[key].getAttribute('missed'), missed)
def testAddMissingNodes(self):
device_packages = self.dev_root_node.getElementsByTagName('package')
host_packages = self.host_root_node.getElementsByTagName('package')
device_dict = generate_jacoco_report._CreateAttributeToObjectDict(
device_packages, 'name')
host_dict = generate_jacoco_report._CreateAttributeToObjectDict(
host_packages, 'name')
self.assertEqual(len(device_dict), 2)
self.assertEqual(len(host_dict), 3)
self.assertNotIn('not_in_device_xml', device_dict)
generate_jacoco_report._AddMissingNodes(device_dict, host_dict,
self.dev_root_node, 'name')
self.assertEqual(len(device_dict), 3)
self.assertIn('not_in_device_xml', device_dict)
self.assertEqual(len(self.dev_root_node.getElementsByTagName('package')), 3)
def testCombineXmlFiles(self):
with build_utils.TempDir() as temp_dir:
temp_device_f = os.path.join(temp_dir, 'temp_device')
temp_host_f = os.path.join(temp_dir, 'temp_host')
temp_result_f = os.path.join(temp_dir, 'temp_result')
with open(temp_device_f, 'w') as dev_f, open(temp_host_f, 'w') as host_f:
dev_f.write(DEVICE_XML)
host_f.write(HOST_XML)
generate_jacoco_report._CombineXmlFiles(temp_result_f, temp_device_f,
temp_host_f)
result_tree = minidom.parse(temp_result_f)
result_root = result_tree.getElementsByTagName('report')[0]
report_ans_dict = {
'instruction': ('213', '327'),
'branch': ('207', '207'),
'method': ('205', '201'),
}
self.verify_counters(result_root, 6, report_ans_dict)
self.assertEqual(len(result_root.getElementsByTagName('package')), 3)
result_package = result_root.getElementsByTagName('package')[0]
classes = result_package.getElementsByTagName('class')
self.assertEqual(len(classes), 3)
result_class_dict = generate_jacoco_report._CreateAttributeToObjectDict(
classes, 'name')
for key in result_class_dict:
expected_classes = {'class1', 'class2', 'not_in_dev_package'}
self.assertIn(key, expected_classes)
class_ans_dict = {
'instruction': ('2', '13'),
'branch': ('2', '2'),
'method': ('2', '0'),
}
self.verify_counters(result_class_dict['class1'], 6, class_ans_dict)
methods = result_package.getElementsByTagName('method')
self.assertEqual(len(methods), 3)
result_method_dict = generate_jacoco_report._CreateAttributeToObjectDict(
methods, 'name')
for key in result_method_dict:
expected_methods = {'method1', 'method2', 'method3'}
self.assertIn(key, expected_methods)
method_ans_dict = {
'instruction': ('1', '6'),
'branch': ('1', '1'),
'line': ('1', '1'),
}
self.verify_counters(result_method_dict['method1'], 5, method_ans_dict)
self.assertEqual(len(result_root.getElementsByTagName('sourcefile')), 3)
source_file_node = result_root.getElementsByTagName('sourcefile')[0]
source_ans_dict = {
'instruction': ('3', '19'),
'branch': ('2', '4'),
'line': ('2', '1'),
'class': ('1', '0'),
}
self.verify_counters(source_file_node, 6, source_ans_dict)
def testCreateClassfileArgs(self):
class_files = ['b17.plane', 'a10.plane', 'gato.sub', 'balao.sub', 'A.bomb']
answer = [
'--classfiles', 'b17.plane', '--classfiles', 'a10.plane',
'--classfiles', 'gato.sub', '--classfiles', 'balao.sub', '--classfiles',
'A.bomb'
]
self.assertEqual(
generate_jacoco_report._CreateClassfileArgs(class_files, ''), answer)
self.assertEqual(
generate_jacoco_report._CreateClassfileArgs(class_files, 'not_found'),
answer)
answer = [
'--classfiles', 'gato.sub', '--classfiles', 'balao.sub', '--classfiles',
'A.bomb'
]
self.assertEqual(
generate_jacoco_report._CreateClassfileArgs(class_files, 'plane'),
answer)
def testGetCountersList(self):
node_ls = generate_jacoco_report._GetCountersList(self.dev_root_node)
self.assertEqual(len(node_ls), 6)
node = self.dev_root_node.childNodes[1]
answers = [6, 6, 5]
for ans in answers:
node_ls = generate_jacoco_report._GetCountersList(node)
self.assertEqual(len(node_ls), ans)
node = node.firstChild
def testCreateAttributeToObjectDict(self):
device_packages = self.dev_root_node.getElementsByTagName('package')
device_dict = generate_jacoco_report._CreateAttributeToObjectDict(
device_packages, 'name')
self.assertEqual(len(device_dict), 2)
self.assertIn('package1', device_dict)
self.assertIn('package3', device_dict)
def testGetCounterTotalsForTagName(self):
host_package = self.host_root_node.getElementsByTagName('package')[0]
host_class = host_package.getElementsByTagName('class')[0]
total_dicts = generate_jacoco_report._GetCounterTotalsForTagName(
host_class, 'method')
self.assertEqual(total_dicts['instruction']['covered'], 2)
self.assertEqual(total_dicts['instruction']['missed'], 13)
self.assertEqual(total_dicts['line']['covered'], 2)
self.assertEqual(total_dicts['line']['missed'], 1)
self.assertEqual(total_dicts['method']['covered'], 2)
self.assertEqual(total_dicts['method']['missed'], 0)
self.assertEqual(total_dicts['branch']['covered'], 2)
self.assertEqual(total_dicts['branch']['missed'], 2)
def testGetDictForEachElement(self):
dev_package = self.dev_root_node.getElementsByTagName('package')[0]
host_package = self.host_root_node.getElementsByTagName('package')[0]
dev_dict, host_dict = generate_jacoco_report._GetDictForEachElement(
dev_package, host_package, 'class', 'name')
self.assertEqual(len(dev_dict), 2)
self.assertEqual(len(host_dict), 2)
self.assertIn('class1', dev_dict)
self.assertIn('class2', dev_dict)
self.assertIn('not_in_dev_package', host_dict)
self.assertNotIn('not_in_dev_package', dev_dict)
self.assertNotIn('class2', host_dict)
dev_class_node = dev_dict['class1']
host_class_node = host_dict['class1']
# Verifies the node objects class1 is mapping to the ones we expected.
dev_dict, host_dict = generate_jacoco_report._GetDictForEachElement(
dev_class_node, host_class_node, 'method', 'line')
self.assertEqual(len(dev_dict), 2)
self.assertEqual(len(host_dict), 3)
self.assertTrue('15' in dev_dict)
self.assertTrue('19' in dev_dict)
self.assertTrue('15' in host_dict)
def testCreateTotalDicts(self):
dicts = generate_jacoco_report._CreateTotalDicts()
self.assertEqual(len(dicts),
len(generate_jacoco_report._JAVA_COVERAGE_METRICS))
for metric in generate_jacoco_report._JAVA_COVERAGE_METRICS:
self.assertIn(metric, dicts)
def testSetHigherCounter(self):
dev_package = self.dev_root_node.getElementsByTagName('package')[0]
host_package = self.host_root_node.getElementsByTagName('package')[0]
ans_dict = {'instruction': ('1', '15')}
self.verify_counters(dev_package, 6, ans_dict)
ans_dict = {'instruction': ('2', '13')}
self.verify_counters(host_package, 6, ans_dict)
dev_package_counters = generate_jacoco_report._GetCountersList(dev_package)
host_package_counters = generate_jacoco_report._GetCountersList(
host_package)
dev_counter_map = generate_jacoco_report._CreateCounterMap(
dev_package_counters)
host_counter_map = generate_jacoco_report._CreateCounterMap(
host_package_counters)
for metric in dev_counter_map:
generate_jacoco_report._SetHigherCounter(dev_counter_map[metric],
host_counter_map[metric])
ans_dict = {'instruction': ('2', '13')}
self.verify_counters(dev_package, 6, ans_dict)
ans_dict = {'instruction': ('2', '13')}
self.verify_counters(host_package, 6, ans_dict)
def testUpdateAllNodesToHigherCoverage(self):
dev_package = self.dev_root_node.getElementsByTagName('package')[0]
host_package = self.host_root_node.getElementsByTagName('package')[0]
dev_name_to_package_dict = {'package1': dev_package}
host_name_to_package_dict = {'package1': host_package}
self.assertEqual(len(dev_package.getElementsByTagName('class')), 2)
methods = dev_package.getElementsByTagName('method')
ans_dict = {
'instruction': ('0', '7'),
'branch': ('0', '2'),
'method': ('0', '1')
}
self.verify_counters(methods[0], 5, ans_dict)
generate_jacoco_report._UpdateAllNodesToHigherCoverage(
dev_name_to_package_dict, host_name_to_package_dict)
self.assertEqual(len(dev_package.getElementsByTagName('class')), 3)
self.assertEqual(len(methods), 2)
ans_dict = {
'instruction': ('1', '6'),
'branch': ('1', '1'),
'method': ('1', '0')
}
self.verify_counters(methods[0], 5, ans_dict)
def testUpdateCountersFromTotal(self):
dev_package = self.dev_root_node.getElementsByTagName('package')[0]
dev_package_counters = generate_jacoco_report._GetCountersList(dev_package)
dev_counter_map = generate_jacoco_report._CreateCounterMap(
dev_package_counters)
total_dicts = generate_jacoco_report._CreateTotalDicts()
# Sanity check to make sure attribute will have changed.
self.assertEqual(dev_counter_map['instruction'].getAttribute('missed'),
'15')
for key in total_dicts:
total_dicts[key]['covered'] = '5'
total_dicts[key]['missed'] = '1337'
generate_jacoco_report._UpdateCountersFromTotal(dev_package_counters,
total_dicts)
for key in dev_counter_map:
counter = dev_counter_map[key]
covered, missed = generate_jacoco_report._GetCoveredAndMissedFromCounter(
counter)
self.assertEqual(covered, '5')
self.assertEqual(missed, '1337')
def testUpdatePackageSourceFiles(self):
dev_package = self.dev_root_node.getElementsByTagName('package')[0]
host_package = self.host_root_node.getElementsByTagName('package')[0]
dev_source_node = dev_package.getElementsByTagName('sourcefile')[0]
dev_line_dict = generate_jacoco_report._CreateAttributeToObjectDict(
dev_source_node.getElementsByTagName('line'), 'nr')
self.assertEqual(len(dev_line_dict), 3)
self.assertIn('19', dev_line_dict)
# The nr in host_package are now added to the dev_package.s
generate_jacoco_report._UpdatePackageSourceFiles(dev_package, host_package)
dev_line_dict = generate_jacoco_report._CreateAttributeToObjectDict(
dev_source_node.getElementsByTagName('line'), 'nr')
self.assertEqual(len(dev_line_dict), 5)
for num in [15, 19, 20, 190, 200]:
self.assertIn(str(num), dev_line_dict)
def testUpdateSourceFileCounters(self):
dev_package = self.dev_root_node.getElementsByTagName('package')[0]
host_package = self.host_root_node.getElementsByTagName('package')[0]
dev_source_node = dev_package.getElementsByTagName('sourcefile')[0]
host_source_node = host_package.getElementsByTagName('sourcefile')[0]
dev_counter_dict = generate_jacoco_report._CreateAttributeToObjectDict(
dev_source_node.getElementsByTagName('counter'), 'type')
self.assertEqual(dev_counter_dict['INSTRUCTION'].getAttribute('covered'),
'1')
self.assertEqual(dev_counter_dict['INSTRUCTION'].getAttribute('missed'),
'15')
self.assertEqual(dev_counter_dict['LINE'].getAttribute('covered'), '0')
self.assertEqual(dev_counter_dict['LINE'].getAttribute('missed'), '3')
total_dict = {'ci': 101, 'mi': 202, 'cb': 303, 'mb': 404}
generate_jacoco_report._UpdateSourceFileCounters(dev_source_node,
host_source_node,
total_dict)
self.assertEqual(dev_counter_dict['INSTRUCTION'].getAttribute('covered'),
'101')
self.assertEqual(dev_counter_dict['INSTRUCTION'].getAttribute('missed'),
'202')
self.assertEqual(dev_counter_dict['BRANCH'].getAttribute('covered'), '303')
self.assertEqual(dev_counter_dict['BRANCH'].getAttribute('missed'), '404')
self.assertEqual(dev_counter_dict['LINE'].getAttribute('covered'), '2')
self.assertEqual(dev_counter_dict['LINE'].getAttribute('missed'), '1')
if __name__ == '__main__':
unittest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment