* commit '834218e7fb82ddb86375682c2fba3bd24074a427': Make coverage work without test defs.
This commit is contained in:
@@ -115,6 +115,10 @@ class AndroidMK(object):
|
||||
"""
|
||||
return identifier in self._includes
|
||||
|
||||
def IncludesMakefilesUnder(self):
|
||||
"""Check if makefile has a 'include makefiles under here' rule"""
|
||||
return self.HasInclude('call all-makefiles-under,$(LOCAL_PATH)')
|
||||
|
||||
def HasJavaLibrary(self, library_name):
|
||||
"""Check if library is specified as a local java library in makefile.
|
||||
|
||||
|
||||
1
testrunner/coverage/__init__.py
Normal file
1
testrunner/coverage/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__all__ = ['coverage', 'coverage_targets', 'coverage_target']
|
||||
@@ -24,6 +24,8 @@ import os
|
||||
|
||||
# local imports
|
||||
import android_build
|
||||
import android_mk
|
||||
import coverage_target
|
||||
import coverage_targets
|
||||
import errors
|
||||
import logger
|
||||
@@ -62,7 +64,9 @@ class CoverageGenerator(object):
|
||||
self._adb = adb_interface
|
||||
self._targets_manifest = self._ReadTargets()
|
||||
|
||||
def ExtractReport(self, test_suite,
|
||||
def ExtractReport(self,
|
||||
test_suite_name,
|
||||
target,
|
||||
device_coverage_path,
|
||||
output_path=None,
|
||||
test_qualifier=None):
|
||||
@@ -70,7 +74,8 @@ class CoverageGenerator(object):
|
||||
|
||||
Assumes test has just been executed.
|
||||
Args:
|
||||
test_suite: TestSuite to generate coverage data for
|
||||
test_suite_name: name of TestSuite to generate coverage data for
|
||||
target: the CoverageTarget to use as basis for coverage calculation
|
||||
device_coverage_path: location of coverage file on device
|
||||
output_path: path to place output files in. If None will use
|
||||
<android_root_path>/<_COVERAGE_REPORT_PATH>/<target>/<test[-qualifier]>
|
||||
@@ -81,12 +86,12 @@ class CoverageGenerator(object):
|
||||
absolute file path string of generated html report file.
|
||||
"""
|
||||
if output_path is None:
|
||||
report_name = test_suite.GetName()
|
||||
report_name = test_suite_name
|
||||
if test_qualifier:
|
||||
report_name = report_name + "-" + test_qualifier
|
||||
output_path = os.path.join(self._root_path,
|
||||
self._COVERAGE_REPORT_PATH,
|
||||
test_suite.GetTargetName(),
|
||||
target.GetName(),
|
||||
report_name)
|
||||
|
||||
coverage_local_name = "%s.%s" % (report_name,
|
||||
@@ -97,13 +102,6 @@ class CoverageGenerator(object):
|
||||
|
||||
report_path = os.path.join(output_path,
|
||||
report_name)
|
||||
target = self._targets_manifest.GetTarget(test_suite.GetTargetName())
|
||||
if target is None:
|
||||
msg = ["Error: test %s references undefined target %s."
|
||||
% (test_suite.GetName(), test_suite.GetTargetName())]
|
||||
msg.append(" Ensure target is defined in %s" % self._TARGET_DEF_FILE)
|
||||
logger.Log("".join(msg))
|
||||
else:
|
||||
return self._GenerateReport(report_path, coverage_local_path, [target],
|
||||
do_src=True)
|
||||
return None
|
||||
@@ -283,12 +281,34 @@ class CoverageGenerator(object):
|
||||
self._CombineTestCoverage()
|
||||
self._CombineTargetCoverage()
|
||||
|
||||
def GetCoverageTarget(self, name):
|
||||
"""Find the CoverageTarget for given name"""
|
||||
target = self._targets_manifest.GetTarget(name)
|
||||
if target is None:
|
||||
msg = ["Error: test references undefined target %s." % name]
|
||||
msg.append(" Ensure target is defined in %s" % self._TARGET_DEF_FILE)
|
||||
raise errors.AbortError(msg)
|
||||
return target
|
||||
|
||||
def GetCoverageTargetForPath(self, path):
|
||||
"""Find the CoverageTarget for given file system path"""
|
||||
android_mk_path = os.path.join(path, "Android.mk")
|
||||
if os.path.exists(android_mk_path):
|
||||
android_mk_parser = android_mk.CreateAndroidMK(path)
|
||||
target = coverage_target.CoverageTarget()
|
||||
target.SetBuildPath(os.path.join(path, "src"))
|
||||
target.SetName(android_mk_parser.GetVariable(android_mk_parser.PACKAGE_NAME))
|
||||
target.SetType("APPS")
|
||||
return target
|
||||
else:
|
||||
msg = "No Android.mk found at %s" % path
|
||||
raise errors.AbortError(msg)
|
||||
|
||||
|
||||
def EnableCoverageBuild():
|
||||
"""Enable building an Android target with code coverage instrumentation."""
|
||||
os.environ["EMMA_INSTRUMENT"] = "true"
|
||||
|
||||
|
||||
def Run():
|
||||
"""Does coverage operations based on command line args."""
|
||||
# TODO: do we want to support combining coverage for a single target
|
||||
48
testrunner/coverage/coverage_target.py
Normal file
48
testrunner/coverage/coverage_target.py
Normal file
@@ -0,0 +1,48 @@
|
||||
#
|
||||
# Copyright 2012, 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.
|
||||
|
||||
class CoverageTarget:
|
||||
""" Represents a code coverage target definition"""
|
||||
|
||||
def __init__(self):
|
||||
self._name = None
|
||||
self._type = None
|
||||
self._build_path = None
|
||||
self._paths = []
|
||||
|
||||
def GetName(self):
|
||||
return self._name
|
||||
|
||||
def SetName(self, name):
|
||||
self._name = name
|
||||
|
||||
def GetPaths(self):
|
||||
return self._paths
|
||||
|
||||
def AddPath(self, path):
|
||||
self._paths.append(path)
|
||||
|
||||
def GetType(self):
|
||||
return self._type
|
||||
|
||||
def SetType(self, buildtype):
|
||||
self._type = buildtype
|
||||
|
||||
def GetBuildPath(self):
|
||||
return self._build_path
|
||||
|
||||
def SetBuildPath(self, build_path):
|
||||
self._build_path = build_path
|
||||
|
||||
@@ -18,6 +18,8 @@ import xml.dom.minidom
|
||||
import xml.parsers
|
||||
import os
|
||||
|
||||
|
||||
import coverage_target
|
||||
import logger
|
||||
import errors
|
||||
|
||||
@@ -38,12 +40,17 @@ class CoverageTargets:
|
||||
"""
|
||||
|
||||
_TARGET_TAG_NAME = 'coverage_target'
|
||||
_NAME_ATTR = 'name'
|
||||
_TYPE_ATTR = 'type'
|
||||
_BUILD_ATTR = 'build_path'
|
||||
_SRC_TAG = 'src'
|
||||
_PATH_ATTR = 'path'
|
||||
|
||||
def __init__(self, ):
|
||||
self._target_map= {}
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._target_map.values())
|
||||
return iter(self._target_map.values()
|
||||
|
||||
def Parse(self, file_path):
|
||||
"""Parse the coverage target data from from given file path, and add it to
|
||||
@@ -66,7 +73,8 @@ class CoverageTargets:
|
||||
target_elements = doc.getElementsByTagName(self._TARGET_TAG_NAME)
|
||||
|
||||
for target_element in target_elements:
|
||||
target = CoverageTarget(target_element)
|
||||
target = coverage_target.CoverageTarget()
|
||||
self._ParseCoverageTarget(target, target_element)
|
||||
self._AddTarget(target)
|
||||
|
||||
def _AddTarget(self, target):
|
||||
@@ -90,42 +98,28 @@ class CoverageTargets:
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
class CoverageTarget:
|
||||
""" Represents one coverage target definition parsed from xml """
|
||||
def _ParseCoverageTarget(self, target, target_element):
|
||||
"""Parse coverage data from XML.
|
||||
|
||||
_NAME_ATTR = 'name'
|
||||
_TYPE_ATTR = 'type'
|
||||
_BUILD_ATTR = 'build_path'
|
||||
_SRC_TAG = 'src'
|
||||
_PATH_ATTR = 'path'
|
||||
|
||||
def __init__(self, target_element):
|
||||
self._name = target_element.getAttribute(self._NAME_ATTR)
|
||||
self._type = target_element.getAttribute(self._TYPE_ATTR)
|
||||
self._build_path = target_element.getAttribute(self._BUILD_ATTR)
|
||||
Args:
|
||||
target: the Coverage object to populate
|
||||
target_element: the XML element to get data from
|
||||
"""
|
||||
target.SetName(target_element.getAttribute(self._NAME_ATTR))
|
||||
target.SetType(target_element.getAttribute(self._TYPE_ATTR))
|
||||
target.SetBuildPath(target_element.getAttribute(self._BUILD_ATTR))
|
||||
self._paths = []
|
||||
self._ParsePaths(target_element)
|
||||
self._ParsePaths(target, target_element)
|
||||
|
||||
def GetName(self):
|
||||
return self._name
|
||||
|
||||
def GetPaths(self):
|
||||
return self._paths
|
||||
|
||||
def GetType(self):
|
||||
return self._type
|
||||
|
||||
def GetBuildPath(self):
|
||||
return self._build_path
|
||||
|
||||
def _ParsePaths(self, target_element):
|
||||
def _ParsePaths(self, target, target_element):
|
||||
src_elements = target_element.getElementsByTagName(self._SRC_TAG)
|
||||
if len(src_elements) <= 0:
|
||||
# no src tags specified. Assume build_path + src
|
||||
self._paths.append(os.path.join(self.GetBuildPath(), "src"))
|
||||
target.AddPath(os.path.join(target.GetBuildPath(), "src"))
|
||||
for src_element in src_elements:
|
||||
rel_path = src_element.getAttribute(self._PATH_ATTR)
|
||||
self._paths.append(os.path.join(self.GetBuildPath(), rel_path))
|
||||
target.AddPath(os.path.join(target.GetBuildPath(), rel_path))
|
||||
|
||||
|
||||
def Parse(xml_file_path):
|
||||
"""parses out a file_path class from given path to xml"""
|
||||
116
testrunner/make_tree.py
Normal file
116
testrunner/make_tree.py
Normal file
@@ -0,0 +1,116 @@
|
||||
#
|
||||
# Copyright 2012, 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.
|
||||
|
||||
"""Data structure for processing makefiles."""
|
||||
|
||||
import os
|
||||
|
||||
import android_build
|
||||
import android_mk
|
||||
import errors
|
||||
|
||||
class MakeNode(object):
|
||||
"""Represents single node in make tree."""
|
||||
|
||||
def __init__(self, name, parent):
|
||||
self._name = name
|
||||
self._children_map = {}
|
||||
self._is_leaf = False
|
||||
self._parent = parent
|
||||
self._includes_submake = None
|
||||
if parent:
|
||||
self._path = os.path.join(parent._GetPath(), name)
|
||||
else:
|
||||
self._path = ""
|
||||
|
||||
def _AddPath(self, path_segs):
|
||||
"""Adds given path to this node.
|
||||
|
||||
Args:
|
||||
path_segs: list of path segments
|
||||
"""
|
||||
if not path_segs:
|
||||
# done processing path
|
||||
return self
|
||||
current_seg = path_segs.pop(0)
|
||||
child = self._children_map.get(current_seg)
|
||||
if not child:
|
||||
child = MakeNode(current_seg, self)
|
||||
self._children_map[current_seg] = child
|
||||
return child._AddPath(path_segs)
|
||||
|
||||
def _SetLeaf(self, is_leaf):
|
||||
self._is_leaf = is_leaf
|
||||
|
||||
def _GetPath(self):
|
||||
return self._path
|
||||
|
||||
def _DoesIncludesSubMake(self):
|
||||
if self._includes_submake is None:
|
||||
if self._is_leaf:
|
||||
path = os.path.join(android_build.GetTop(), self._path)
|
||||
mk_parser = android_mk.CreateAndroidMK(path)
|
||||
self._includes_submake = mk_parser.IncludesMakefilesUnder()
|
||||
else:
|
||||
self._includes_submake = False
|
||||
return self._includes_submake
|
||||
|
||||
def _DoesParentIncludeMe(self):
|
||||
return self._parent and self._parent._DoesIncludesSubMake()
|
||||
|
||||
def _BuildPrunedMakeList(self, make_list):
|
||||
if self._is_leaf and not self._DoesParentIncludeMe():
|
||||
make_list.append(os.path.join(self._path, "Android.mk"))
|
||||
for child in self._children_map.itervalues():
|
||||
child._BuildPrunedMakeList(make_list)
|
||||
|
||||
|
||||
class MakeTree(MakeNode):
|
||||
"""Data structure for building a non-redundant set of Android.mk paths.
|
||||
|
||||
Used to collapse set of Android.mk files to use to prevent issuing make
|
||||
command that include same module multiple times due to include rules.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(MakeTree, self).__init__("", None)
|
||||
|
||||
def AddPath(self, path):
|
||||
"""Adds make directory path to tree.
|
||||
|
||||
Will have no effect if path is already included in make set.
|
||||
|
||||
Args:
|
||||
path: filesystem path to directory to build, relative to build root.
|
||||
"""
|
||||
path = os.path.normpath(path)
|
||||
mk_path = os.path.join(android_build.GetTop(), path, "Android.mk")
|
||||
if not os.path.isfile(mk_path):
|
||||
raise errors.AbortError("%s does not exist" % mk_path)
|
||||
path_segs = path.split(os.sep)
|
||||
child = self._AddPath(path_segs)
|
||||
child._SetLeaf(True)
|
||||
|
||||
def GetPrunedMakeList(self):
|
||||
"""Return as list of the minimum set of Android.mk files necessary to
|
||||
build all leaf nodes in tree.
|
||||
"""
|
||||
make_list = []
|
||||
self._BuildPrunedMakeList(make_list)
|
||||
return make_list
|
||||
|
||||
def IsEmpty(self):
|
||||
return not self._children_map
|
||||
|
||||
@@ -41,9 +41,10 @@ import time
|
||||
# local imports
|
||||
import adb_interface
|
||||
import android_build
|
||||
import coverage
|
||||
from coverage import coverage
|
||||
import errors
|
||||
import logger
|
||||
import make_tree
|
||||
import run_command
|
||||
from test_defs import test_defs
|
||||
from test_defs import test_walker
|
||||
@@ -143,6 +144,9 @@ class TestRunner(object):
|
||||
parser.add_option("-o", "--coverage", dest="coverage",
|
||||
default=False, action="store_true",
|
||||
help="Generate code coverage metrics for test(s)")
|
||||
parser.add_option("--coverage-target", dest="coverage_target_path",
|
||||
default=None,
|
||||
help="Path to app to collect code coverage target data for.")
|
||||
parser.add_option("-x", "--path", dest="test_path",
|
||||
help="Run test(s) at given file system path")
|
||||
parser.add_option("-t", "--all-tests", dest="all_tests",
|
||||
@@ -191,6 +195,9 @@ class TestRunner(object):
|
||||
if self._options.verbose:
|
||||
logger.SetVerbose(True)
|
||||
|
||||
if self._options.coverage_target_path:
|
||||
self._options.coverage = True
|
||||
|
||||
self._known_tests = self._ReadTests()
|
||||
|
||||
self._options.host_lib_path = android_build.GetHostLibraryPath()
|
||||
@@ -241,23 +248,24 @@ class TestRunner(object):
|
||||
self._TurnOffVerifier(tests)
|
||||
self._DoFullBuild(tests)
|
||||
|
||||
target_set = []
|
||||
target_tree = make_tree.MakeTree()
|
||||
|
||||
extra_args_set = []
|
||||
for test_suite in tests:
|
||||
self._AddBuildTarget(test_suite, target_set, extra_args_set)
|
||||
self._AddBuildTarget(test_suite, target_tree, extra_args_set)
|
||||
|
||||
if not self._options.preview:
|
||||
self._adb.EnableAdbRoot()
|
||||
else:
|
||||
logger.Log("adb root")
|
||||
if target_set:
|
||||
|
||||
if not target_tree.IsEmpty():
|
||||
if self._options.coverage:
|
||||
coverage.EnableCoverageBuild()
|
||||
target_set.append("external/emma/Android.mk")
|
||||
# TODO: detect if external/emma exists
|
||||
target_tree.AddPath("external/emma")
|
||||
|
||||
target_build_string = " ".join(target_set)
|
||||
target_list = target_tree.GetPrunedMakeList()
|
||||
target_build_string = " ".join(target_list)
|
||||
extra_args_string = " ".join(extra_args_set)
|
||||
|
||||
# mmm cannot be used from python, so perform a similar operation using
|
||||
@@ -330,22 +338,18 @@ class TestRunner(object):
|
||||
os.chdir(old_dir)
|
||||
self._DoInstall(output)
|
||||
|
||||
def _AddBuildTarget(self, test_suite, target_set, extra_args_set):
|
||||
def _AddBuildTarget(self, test_suite, target_tree, extra_args_set):
|
||||
if not test_suite.IsFullMake():
|
||||
build_dir = test_suite.GetBuildPath()
|
||||
if self._AddBuildTargetPath(build_dir, target_set):
|
||||
if self._AddBuildTargetPath(build_dir, target_tree):
|
||||
extra_args_set.append(test_suite.GetExtraBuildArgs())
|
||||
for path in test_suite.GetBuildDependencies(self._options):
|
||||
self._AddBuildTargetPath(path, target_set)
|
||||
self._AddBuildTargetPath(path, target_tree)
|
||||
|
||||
def _AddBuildTargetPath(self, build_dir, target_set):
|
||||
def _AddBuildTargetPath(self, build_dir, target_tree):
|
||||
if build_dir is not None:
|
||||
build_file_path = os.path.join(build_dir, "Android.mk")
|
||||
if os.path.isfile(os.path.join(self._root_path, build_file_path)):
|
||||
target_set.append(build_file_path)
|
||||
target_tree.AddPath(build_dir)
|
||||
return True
|
||||
else:
|
||||
logger.Log("%s has no Android.mk, skipping" % build_dir)
|
||||
return False
|
||||
|
||||
def _GetTestsToRun(self):
|
||||
|
||||
@@ -22,7 +22,7 @@ import re
|
||||
|
||||
# local imports
|
||||
import android_manifest
|
||||
import coverage
|
||||
from coverage import coverage
|
||||
import errors
|
||||
import logger
|
||||
import test_suite
|
||||
@@ -84,6 +84,8 @@ class InstrumentationTestSuite(test_suite.AbstractTestSuite):
|
||||
return self
|
||||
|
||||
def GetBuildDependencies(self, options):
|
||||
if options.coverage_target_path:
|
||||
return [options.coverage_target_path]
|
||||
return []
|
||||
|
||||
def Run(self, options, adb):
|
||||
@@ -140,6 +142,10 @@ class InstrumentationTestSuite(test_suite.AbstractTestSuite):
|
||||
logger.Log(adb_cmd)
|
||||
elif options.coverage:
|
||||
coverage_gen = coverage.CoverageGenerator(adb)
|
||||
if options.coverage_target_path:
|
||||
coverage_target = coverage_gen.GetCoverageTargetForPath(options.coverage_target_path)
|
||||
elif self.GetTargetName():
|
||||
coverage_target = coverage_gen.GetCoverageTarget(self.GetTargetName())
|
||||
self._CheckInstrumentationInstalled(adb)
|
||||
# need to parse test output to determine path to coverage file
|
||||
logger.Log("Running in coverage mode, suppressing test output")
|
||||
@@ -158,7 +164,8 @@ class InstrumentationTestSuite(test_suite.AbstractTestSuite):
|
||||
return
|
||||
|
||||
coverage_file = coverage_gen.ExtractReport(
|
||||
self, device_coverage_path, test_qualifier=options.test_size)
|
||||
self.GetName(), coverage_target, device_coverage_path,
|
||||
test_qualifier=options.test_size)
|
||||
if coverage_file is not None:
|
||||
logger.Log("Coverage report generated at %s" % coverage_file)
|
||||
|
||||
|
||||
@@ -141,6 +141,9 @@ class TestWalker(object):
|
||||
else:
|
||||
tests.extend(self._CreateSuites(android_mk_parser, path,
|
||||
upstream_build_path))
|
||||
# TODO: remove this logic, and rely on caller to collapse build
|
||||
# paths via make_tree
|
||||
|
||||
# Try to build as much of original path as possible, so
|
||||
# keep track of upper-most parent directory where Android.mk was found
|
||||
# that has rule to build sub-directory makefiles.
|
||||
@@ -148,7 +151,7 @@ class TestWalker(object):
|
||||
# ie if a test exists at 'foo' directory and 'foo/sub', attempting to
|
||||
# build both 'foo' and 'foo/sub' will fail.
|
||||
|
||||
if android_mk_parser.HasInclude('call all-makefiles-under,$(LOCAL_PATH)'):
|
||||
if android_mk_parser.IncludesMakefilesUnder():
|
||||
# found rule to build sub-directories. The parent path can be used,
|
||||
# or if not set, use current path
|
||||
if not upstream_build_path:
|
||||
|
||||
Reference in New Issue
Block a user