1# 2# Copyright (C) 2016 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15import argparse 16import io 17import json 18import logging 19import os 20import shutil 21import sys 22import time 23import zipfile 24 25from vts.proto import VtsReportMessage_pb2 as ReportMsg 26from vts.runners.host import keys 27from vts.utils.python.archive import archive_parser 28from vts.utils.python.common import cmd_utils 29from vts.utils.python.controllers.adb import AdbError 30from vts.utils.python.coverage import coverage_report 31from vts.utils.python.coverage import gcda_parser 32from vts.utils.python.coverage import gcno_parser 33from vts.utils.python.coverage.parser import FileFormatError 34from vts.utils.python.os import path_utils 35from vts.utils.python.web import feature_utils 36 37FLUSH_PATH_VAR = "GCOV_PREFIX" # environment variable for gcov flush path 38TARGET_COVERAGE_PATH = "/data/misc/trace/" # location to flush coverage 39LOCAL_COVERAGE_PATH = "/tmp/vts-test-coverage" # location to pull coverage to host 40 41# Environment for test process 42COVERAGE_TEST_ENV = "GCOV_PREFIX_OVERRIDE=true GCOV_PREFIX=/data/misc/trace/self" 43 44GCNO_SUFFIX = ".gcno" 45GCDA_SUFFIX = ".gcda" 46COVERAGE_SUFFIX = ".gcnodir" 47GIT_PROJECT = "git_project" 48MODULE_NAME = "module_name" 49NAME = "name" 50PATH = "path" 51GEN_TAG = "/gen/" 52 53_BUILD_INFO = "BUILD_INFO" # name of build info artifact 54_GCOV_ZIP = "gcov.zip" # name of gcov artifact zip 55_REPO_DICT = "repo-dict" # name of dictionary from project to revision in BUILD_INFO 56 57_CLEAN_TRACE_COMMAND = "rm -rf /data/misc/trace/*" 58_FLUSH_COMMAND = ( 59 "GCOV_PREFIX_OVERRIDE=true GCOV_PREFIX=/data/local/tmp/flusher " 60 "/data/local/tmp/vts_coverage_configure flush") 61_SP_COVERAGE_PATH = "self" # relative location where same-process coverage is dumped. 62 63_CHECKSUM_GCNO_DICT = "checksum_gcno_dict" 64_COVERAGE_ZIP = "coverage_zip" 65_REVISION_DICT = "revision_dict" 66 67 68class CoverageFeature(feature_utils.Feature): 69 """Feature object for coverage functionality. 70 71 Attributes: 72 enabled: boolean, True if coverage is enabled, False otherwise 73 web: (optional) WebFeature, object storing web feature util for test run 74 local_coverage_path: path to store the coverage files. 75 _device_resource_dict: a map from device serial number to host resources directory. 76 _hal_names: the list of hal names for which to process coverage. 77 _coverage_report_file_prefix: prefix of the output coverage report file. 78 """ 79 80 _TOGGLE_PARAM = keys.ConfigKeys.IKEY_ENABLE_COVERAGE 81 _REQUIRED_PARAMS = [keys.ConfigKeys.IKEY_ANDROID_DEVICE] 82 _OPTIONAL_PARAMS = [ 83 keys.ConfigKeys.IKEY_MODULES, 84 keys.ConfigKeys.IKEY_OUTPUT_COVERAGE_REPORT, 85 keys.ConfigKeys.IKEY_GLOBAL_COVERAGE, 86 keys.ConfigKeys.IKEY_EXCLUDE_COVERAGE_PATH, 87 keys.ConfigKeys.IKEY_COVERAGE_REPORT_PATH, 88 ] 89 90 _DEFAULT_EXCLUDE_PATHS = [ 91 "bionic", "external/libcxx", "system/core", "system/libhidl", 92 "system/libfmq" 93 ] 94 95 def __init__(self, user_params, web=None): 96 """Initializes the coverage feature. 97 98 Args: 99 user_params: A dictionary from parameter name (String) to parameter value. 100 web: (optional) WebFeature, object storing web feature util for test run 101 local_coverage_path: (optional) path to store the .gcda files and coverage reports. 102 """ 103 self.ParseParameters(self._TOGGLE_PARAM, self._REQUIRED_PARAMS, 104 self._OPTIONAL_PARAMS, user_params) 105 self.web = web 106 self._device_resource_dict = {} 107 self._hal_names = None 108 109 timestamp_seconds = str(int(time.time() * 1000000)) 110 self.local_coverage_path = os.path.join(LOCAL_COVERAGE_PATH, 111 timestamp_seconds) 112 if os.path.exists(self.local_coverage_path): 113 logging.debug("removing existing coverage path: %s", 114 self.local_coverage_path) 115 shutil.rmtree(self.local_coverage_path) 116 os.makedirs(self.local_coverage_path) 117 118 self._coverage_report_dir = getattr( 119 self, keys.ConfigKeys.IKEY_COVERAGE_REPORT_PATH, None) 120 121 self._coverage_report_file_prefix = "" 122 123 self.global_coverage = getattr( 124 self, keys.ConfigKeys.IKEY_GLOBAL_COVERAGE, True) 125 if self.enabled: 126 android_devices = getattr(self, 127 keys.ConfigKeys.IKEY_ANDROID_DEVICE) 128 if not isinstance(android_devices, list): 129 logging.warn("Android device information not available.") 130 self.enabled = False 131 for device in android_devices: 132 serial = device.get(keys.ConfigKeys.IKEY_SERIAL) 133 coverage_resource_path = device.get( 134 keys.ConfigKeys.IKEY_GCOV_RESOURCES_PATH) 135 if not serial: 136 logging.error("Missing serial information in device: %s", 137 device) 138 continue 139 if not coverage_resource_path: 140 logging.error( 141 "Missing coverage resource path in device: %s", device) 142 continue 143 self._device_resource_dict[str(serial)] = str( 144 coverage_resource_path) 145 146 if self.enabled: 147 logging.info("Coverage is enabled") 148 else: 149 logging.debug("Coverage is disabled.") 150 151 def _FindGcnoSummary(self, gcda_file_path, gcno_file_parsers): 152 """Find the corresponding gcno summary for given gcda file. 153 154 Identify the corresponding gcno summary for given gcda file from a list 155 of gcno files with the same checksum as the gcda file by matching 156 the the gcda file path. 157 Note: if none of the gcno summary contains the source file same as the 158 given gcda_file_path (e.g. when the corresponding source file does not 159 contain any executable codes), just return the last gcno summary in the 160 list as a fall back solution. 161 162 Args: 163 gcda_file_path: the path of gcda file (without extensions). 164 gcno_file_parsers: a list of gcno file parser that has the same 165 chechsum. 166 167 Returns: 168 The corresponding gcno summary for given gcda file. 169 """ 170 gcno_summary = None 171 # For each gcno files with the matched checksum, compare the 172 # gcda_file_path to find the corresponding gcno summary. 173 for gcno_file_parser in gcno_file_parsers: 174 try: 175 gcno_summary = gcno_file_parser.Parse() 176 except FileFormatError: 177 logging.error("Error parsing gcno for gcda %s", gcda_file_path) 178 break 179 legacy_build = "soong/.intermediates" not in gcda_file_path 180 for key in gcno_summary.functions: 181 src_file_path = gcno_summary.functions[key].src_file_name 182 src_file_name = src_file_path.rsplit(".", 1)[0] 183 # If build with legacy compile system, compare only the base 184 # source file name. Otherwise, compare the full source file name 185 # (with path info). 186 if legacy_build: 187 base_src_file_name = os.path.basename(src_file_name) 188 if gcda_file_path.endswith(base_src_file_name): 189 return gcno_summary 190 else: 191 if gcda_file_path.endswith(src_file_name): 192 return gcno_summary 193 # If no gcno file matched with the gcda_file_name, return the last 194 # gcno summary as a fall back solution. 195 return gcno_summary 196 197 def _GetChecksumGcnoDict(self, cov_zip): 198 """Generates a dictionary from gcno checksum to GCNOParser object. 199 200 Processes the gcnodir files in the zip file to produce a mapping from gcno 201 checksum to the GCNOParser object wrapping the gcno content. 202 Note there might be multiple gcno files corresponds to the same checksum. 203 204 Args: 205 cov_zip: the zip file containing gcnodir files from the device build 206 207 Returns: 208 the dictionary of gcno checksums to GCNOParser objects 209 """ 210 checksum_gcno_dict = dict() 211 fnames = cov_zip.namelist() 212 instrumented_modules = [ 213 f for f in fnames if f.endswith(COVERAGE_SUFFIX) 214 ] 215 for instrumented_module in instrumented_modules: 216 # Read the gcnodir file 217 archive = archive_parser.Archive( 218 cov_zip.open(instrumented_module).read()) 219 try: 220 archive.Parse() 221 except ValueError: 222 logging.error("Archive could not be parsed: %s", name) 223 continue 224 225 for gcno_file_path in archive.files: 226 gcno_stream = io.BytesIO(archive.files[gcno_file_path]) 227 gcno_file_parser = gcno_parser.GCNOParser(gcno_stream) 228 if gcno_file_parser.checksum in checksum_gcno_dict: 229 checksum_gcno_dict[gcno_file_parser.checksum].append( 230 gcno_file_parser) 231 else: 232 checksum_gcno_dict[gcno_file_parser.checksum] = [ 233 gcno_file_parser 234 ] 235 return checksum_gcno_dict 236 237 def _ClearTargetGcov(self, dut, serial, path_suffix=None): 238 """Removes gcov data from the device. 239 240 Finds and removes all gcda files relative to TARGET_COVERAGE_PATH. 241 Args: 242 dut: the device under test. 243 path_suffix: optional string path suffix. 244 """ 245 path = TARGET_COVERAGE_PATH 246 if path_suffix: 247 path = path_utils.JoinTargetPath(path, path_suffix) 248 self._ExecuteOneAdbShellCommand(dut, serial, _CLEAN_TRACE_COMMAND) 249 250 def _GetHalPids(self, dut, hal_names): 251 """Get the process id for the given hal names. 252 253 Args: 254 dut: the device under test. 255 hal_names: list of strings for targeting hal names. 256 257 Returns: 258 list of strings for the corresponding pids. 259 """ 260 logging.debug("hal_names: %s", str(hal_names)) 261 searchString = "|".join(hal_names) 262 entries = [] 263 try: 264 dut.rootAdb() 265 entries = dut.adb.shell( 266 "lshal -itp 2> /dev/null | grep -E \"{0}\"".format( 267 searchString)).splitlines() 268 except AdbError as e: 269 logging.error("failed to get pid entries") 270 271 pids = set(pid.strip() 272 for pid in map(lambda entry: entry.split()[-1], entries) 273 if pid.isdigit()) 274 return pids 275 276 def InitializeDeviceCoverage(self, dut=None, serial=None): 277 """Initializes the device for coverage before tests run. 278 279 Flushes, then finds and removes all gcda files under 280 TARGET_COVERAGE_PATH before tests run. 281 282 Args: 283 dut: the device under test. 284 """ 285 self._ExecuteOneAdbShellCommand(dut, serial, "setenforce 0") 286 self._ExecuteOneAdbShellCommand(dut, serial, _FLUSH_COMMAND) 287 logging.debug("Removing existing gcda files.") 288 self._ClearTargetGcov(dut, serial) 289 290 # restart HALs to include coverage for initialization code. 291 if self._hal_names: 292 pids = self._GetHalPids(dut, self._hal_names) 293 for pid in pids: 294 cmd = "kill -9 " + pid 295 self._ExecuteOneAdbShellCommand(dut, serial, cmd) 296 297 def _GetGcdaDict(self, dut, serial): 298 """Retrieves GCDA files from device and creates a dictionary of files. 299 300 Find all GCDA files on the target device, copy them to the host using 301 adb, then return a dictionary mapping from the gcda basename to the 302 temp location on the host. 303 304 Args: 305 dut: the device under test. 306 307 Returns: 308 A dictionary with gcda basenames as keys and contents as the values. 309 """ 310 logging.debug("Creating gcda dictionary") 311 gcda_dict = {} 312 logging.debug("Storing gcda tmp files to: %s", 313 self.local_coverage_path) 314 315 self._ExecuteOneAdbShellCommand(dut, serial, _FLUSH_COMMAND) 316 317 gcda_files = set() 318 if self._hal_names: 319 pids = self._GetHalPids(dut, self._hal_names) 320 pids.add(_SP_COVERAGE_PATH) 321 for pid in pids: 322 path = path_utils.JoinTargetPath(TARGET_COVERAGE_PATH, pid) 323 try: 324 files = dut.adb.shell("find %s -name \"*.gcda\"" % path) 325 gcda_files.update(files.split("\n")) 326 except AdbError as e: 327 logging.info("No gcda files found in path: \"%s\"", path) 328 else: 329 cmd = ("find %s -name \"*.gcda\"" % TARGET_COVERAGE_PATH) 330 result = self._ExecuteOneAdbShellCommand(dut, serial, cmd) 331 if result: 332 gcda_files.update(result.split("\n")) 333 334 for gcda in gcda_files: 335 if gcda: 336 basename = os.path.basename(gcda.strip()) 337 file_name = os.path.join(self.local_coverage_path, basename) 338 if dut is None: 339 results = cmd_utils.ExecuteShellCommand( 340 "adb -s %s pull %s %s " % (serial, gcda, file_name)) 341 if (results[cmd_utils.EXIT_CODE][0]): 342 logging.error( 343 "Fail to execute command: %s. error: %s" % 344 (cmd, str(results[cmd_utils.STDERR][0]))) 345 else: 346 dut.adb.pull("%s %s" % (gcda, file_name)) 347 gcda_content = open(file_name, "rb").read() 348 gcda_dict[gcda.strip()] = gcda_content 349 self._ClearTargetGcov(dut, serial) 350 return gcda_dict 351 352 def _OutputCoverageReport(self, isGlobal, coverage_report_msg=None): 353 logging.info("Outputing coverage data") 354 timestamp_seconds = str(int(time.time() * 1000000)) 355 coverage_report_file_name = "coverage_report_" + timestamp_seconds + ".txt" 356 if self._coverage_report_file_prefix: 357 coverage_report_file_name = "coverage_report_" + self._coverage_report_file_prefix + ".txt" 358 359 coverage_report_file = None 360 if (self._coverage_report_dir): 361 if not os.path.exists(self._coverage_report_dir): 362 os.makedirs(self._coverage_report_dir) 363 coverage_report_file = os.path.join(self._coverage_report_dir, 364 coverage_report_file_name) 365 else: 366 coverage_report_file = os.path.join(self.local_coverage_path, 367 coverage_report_file_name) 368 369 logging.info("Storing coverage report to: %s", coverage_report_file) 370 if self.web and self.web.enabled: 371 coverage_report_msg = ReportMsg.TestReportMessage() 372 if isGlobal: 373 for c in self.web.report_msg.coverage: 374 coverage = coverage_report_msg.coverage.add() 375 coverage.CopyFrom(c) 376 else: 377 for c in self.web.current_test_report_msg.coverage: 378 coverage = coverage_report_msg.coverage.add() 379 coverage.CopyFrom(c) 380 if coverage_report_msg is not None: 381 with open(coverage_report_file, "w+") as f: 382 f.write(str(coverage_report_msg)) 383 384 def _AutoProcess(self, cov_zip, revision_dict, gcda_dict, isGlobal): 385 """Process coverage data and appends coverage reports to the report message. 386 387 Matches gcno files with gcda files and processes them into a coverage report 388 with references to the original source code used to build the system image. 389 Coverage information is appended as a CoverageReportMessage to the provided 390 report message. 391 392 Git project information is automatically extracted from the build info and 393 the source file name enclosed in each gcno file. Git project names must 394 resemble paths and may differ from the paths to their project root by at 395 most one. If no match is found, then coverage information will not be 396 be processed. 397 398 e.g. if the project path is test/vts, then its project name may be 399 test/vts or <some folder>/test/vts in order to be recognized. 400 401 Args: 402 cov_zip: the ZipFile object containing the gcno coverage artifacts. 403 revision_dict: the dictionary from project name to project version. 404 gcda_dict: the dictionary of gcda basenames to gcda content (binary string) 405 isGlobal: boolean, True if the coverage data is for the entire test, False if only for 406 the current test case. 407 """ 408 checksum_gcno_dict = self._GetChecksumGcnoDict(cov_zip) 409 output_coverage_report = getattr( 410 self, keys.ConfigKeys.IKEY_OUTPUT_COVERAGE_REPORT, False) 411 exclude_coverage_path = getattr( 412 self, keys.ConfigKeys.IKEY_EXCLUDE_COVERAGE_PATH, []) 413 for idx, path in enumerate(exclude_coverage_path): 414 base_name = os.path.basename(path) 415 if base_name and "." not in base_name: 416 path = path if path.endswith("/") else path + "/" 417 exclude_coverage_path[idx] = path 418 exclude_coverage_path.extend(self._DEFAULT_EXCLUDE_PATHS) 419 420 coverage_dict = dict() 421 coverage_report_message = ReportMsg.TestReportMessage() 422 423 for gcda_name in gcda_dict: 424 if GEN_TAG in gcda_name: 425 # skip coverage measurement for intermediate code. 426 logging.warn("Skip for gcda file: %s", gcda_name) 427 continue 428 429 gcda_stream = io.BytesIO(gcda_dict[gcda_name]) 430 gcda_file_parser = gcda_parser.GCDAParser(gcda_stream) 431 file_name = gcda_name.rsplit(".", 1)[0] 432 433 if not gcda_file_parser.checksum in checksum_gcno_dict: 434 logging.info("No matching gcno file for gcda: %s", gcda_name) 435 continue 436 gcno_file_parsers = checksum_gcno_dict[gcda_file_parser.checksum] 437 gcno_summary = self._FindGcnoSummary(file_name, gcno_file_parsers) 438 if gcno_summary is None: 439 logging.error("No gcno file found for gcda %s.", gcda_name) 440 continue 441 442 # Process and merge gcno/gcda data 443 try: 444 gcda_file_parser.Parse(gcno_summary) 445 except FileFormatError: 446 logging.error("Error parsing gcda file %s", gcda_name) 447 continue 448 449 coverage_report.GenerateLineCoverageVector( 450 gcno_summary, exclude_coverage_path, coverage_dict) 451 452 for src_file_path in coverage_dict: 453 # Get the git project information 454 # Assumes that the project name and path to the project root are similar 455 revision = None 456 for project_name in revision_dict: 457 # Matches cases when source file root and project name are the same 458 if src_file_path.startswith(str(project_name)): 459 git_project_name = str(project_name) 460 git_project_path = str(project_name) 461 revision = str(revision_dict[project_name]) 462 logging.debug("Source file '%s' matched with project '%s'", 463 src_file_path, git_project_name) 464 break 465 466 parts = os.path.normpath(str(project_name)).split(os.sep, 1) 467 # Matches when project name has an additional prefix before the 468 # project path root. 469 if len(parts) > 1 and src_file_path.startswith(parts[-1]): 470 git_project_name = str(project_name) 471 git_project_path = parts[-1] 472 revision = str(revision_dict[project_name]) 473 logging.debug("Source file '%s' matched with project '%s'", 474 src_file_path, git_project_name) 475 break 476 477 if not revision: 478 logging.info("Could not find git info for %s", src_file_path) 479 continue 480 481 coverage_vec = coverage_dict[src_file_path] 482 total_count, covered_count = coverage_report.GetCoverageStats( 483 coverage_vec) 484 if self.web and self.web.enabled: 485 self.web.AddCoverageReport(coverage_vec, src_file_path, 486 git_project_name, git_project_path, 487 revision, covered_count, 488 total_count, isGlobal) 489 else: 490 coverage = coverage_report_message.coverage.add() 491 coverage.total_line_count = total_count 492 coverage.covered_line_count = covered_count 493 coverage.line_coverage_vector.extend(coverage_vec) 494 495 src_file_path = os.path.relpath(src_file_path, 496 git_project_path) 497 coverage.file_path = src_file_path 498 coverage.revision = revision 499 coverage.project_name = git_project_name 500 501 if output_coverage_report: 502 self._OutputCoverageReport(isGlobal, coverage_report_message) 503 504 # TODO: consider to deprecate the manual process. 505 def _ManualProcess(self, cov_zip, revision_dict, gcda_dict, isGlobal): 506 """Process coverage data and appends coverage reports to the report message. 507 508 Opens the gcno files in the cov_zip for the specified modules and matches 509 gcno/gcda files. Then, coverage vectors are generated for each set of matching 510 gcno/gcda files and appended as a CoverageReportMessage to the provided 511 report message. Unlike AutoProcess, coverage information is only processed 512 for the modules explicitly defined in 'modules'. 513 514 Args: 515 cov_zip: the ZipFile object containing the gcno coverage artifacts. 516 revision_dict: the dictionary from project name to project version. 517 gcda_dict: the dictionary of gcda basenames to gcda content (binary string) 518 isGlobal: boolean, True if the coverage data is for the entire test, False if only for 519 the current test case. 520 """ 521 output_coverage_report = getattr( 522 self, keys.ConfigKeys.IKEY_OUTPUT_COVERAGE_REPORT, True) 523 modules = getattr(self, keys.ConfigKeys.IKEY_MODULES, None) 524 covered_modules = set(cov_zip.namelist()) 525 for module in modules: 526 if MODULE_NAME not in module or GIT_PROJECT not in module: 527 logging.error( 528 "Coverage module must specify name and git project: %s", 529 module) 530 continue 531 project = module[GIT_PROJECT] 532 if PATH not in project or NAME not in project: 533 logging.error("Project name and path not specified: %s", 534 project) 535 continue 536 537 name = str(module[MODULE_NAME]) + COVERAGE_SUFFIX 538 git_project = str(project[NAME]) 539 git_project_path = str(project[PATH]) 540 541 if name not in covered_modules: 542 logging.error("No coverage information for module %s", name) 543 continue 544 if git_project not in revision_dict: 545 logging.error( 546 "Git project not present in device revision dict: %s", 547 git_project) 548 continue 549 550 revision = str(revision_dict[git_project]) 551 archive = archive_parser.Archive(cov_zip.open(name).read()) 552 try: 553 archive.Parse() 554 except ValueError: 555 logging.error("Archive could not be parsed: %s", name) 556 continue 557 558 for gcno_file_path in archive.files: 559 file_name_path = gcno_file_path.rsplit(".", 1)[0] 560 file_name = os.path.basename(file_name_path) 561 gcno_content = archive.files[gcno_file_path] 562 gcno_stream = io.BytesIO(gcno_content) 563 try: 564 gcno_summary = gcno_parser.GCNOParser(gcno_stream).Parse() 565 except FileFormatError: 566 logging.error("Error parsing gcno file %s", gcno_file_path) 567 continue 568 src_file_path = None 569 570 # Match gcno file with gcda file 571 gcda_name = file_name + GCDA_SUFFIX 572 if gcda_name not in gcda_dict: 573 logging.error("No gcda file found %s.", gcda_name) 574 continue 575 576 src_file_path = self._ExtractSourceName( 577 gcno_summary, file_name) 578 579 if not src_file_path: 580 logging.error("No source file found for %s.", 581 gcno_file_path) 582 continue 583 584 # Process and merge gcno/gcda data 585 gcda_content = gcda_dict[gcda_name] 586 gcda_stream = io.BytesIO(gcda_content) 587 try: 588 gcda_parser.GCDAParser(gcda_stream).Parse(gcno_summary) 589 except FileFormatError: 590 logging.error("Error parsing gcda file %s", gcda_content) 591 continue 592 593 if self.web and self.web.enabled: 594 coverage_vec = coverage_report.GenerateLineCoverageVector( 595 src_file_path, gcno_summary) 596 total_count, covered_count = coverage_report.GetCoverageStats( 597 coverage_vec) 598 self.web.AddCoverageReport(coverage_vec, src_file_path, 599 git_project, git_project_path, 600 revision, covered_count, 601 total_count, isGlobal) 602 603 if output_coverage_report: 604 self._OutputCoverageReport(isGlobal) 605 606 def SetCoverageData(self, dut=None, serial=None, isGlobal=False): 607 """Sets and processes coverage data. 608 609 Organizes coverage data and processes it into a coverage report in the 610 current test case 611 612 Requires feature to be enabled; no-op otherwise. 613 614 Args: 615 dut: the device object for which to pull coverage data 616 isGlobal: True if the coverage data is for the entire test, False if 617 if the coverage data is just for the current test case. 618 """ 619 if not self.enabled: 620 return 621 622 if serial is None: 623 serial = "default" if dut is None else dut.adb.shell( 624 "getprop ro.serialno").strip() 625 626 if not serial in self._device_resource_dict: 627 logging.error("Invalid device provided: %s", serial) 628 return 629 630 resource_path = self._device_resource_dict[serial] 631 if not resource_path: 632 logging.error("Coverage resource path not found.") 633 return 634 635 gcda_dict = self._GetGcdaDict(dut, serial) 636 logging.debug("Coverage file paths %s", str([fp for fp in gcda_dict])) 637 638 cov_zip = zipfile.ZipFile(os.path.join(resource_path, _GCOV_ZIP)) 639 640 revision_dict = json.load( 641 open(os.path.join(resource_path, _BUILD_INFO)))[_REPO_DICT] 642 643 if not hasattr(self, keys.ConfigKeys.IKEY_MODULES): 644 # auto-process coverage data 645 self._AutoProcess(cov_zip, revision_dict, gcda_dict, isGlobal) 646 else: 647 # explicitly process coverage data for the specified modules 648 self._ManualProcess(cov_zip, revision_dict, gcda_dict, isGlobal) 649 650 # cleanup the downloaded gcda files. 651 logging.debug("Cleaning up gcda files.") 652 files = os.listdir(self.local_coverage_path) 653 for item in files: 654 if item.endswith(".gcda"): 655 os.remove(os.path.join(self.local_coverage_path, item)) 656 657 def SetHalNames(self, names=[]): 658 """Sets the HAL names for which to process coverage. 659 660 Args: 661 names: list of strings, names of hal (e.g. android.hardware.light@2.0) 662 """ 663 self._hal_names = list(names) 664 665 def SetCoverageReportFilePrefix(self, prefix): 666 """Sets the prefix for outputting the coverage report file. 667 668 Args: 669 prefix: strings, prefix of the coverage report file. 670 """ 671 self._coverage_report_file_prefix = prefix 672 673 def SetCoverageReportDirectory(self, corverage_report_dir): 674 """Sets the path for storing the coverage report file. 675 676 Args: 677 corverage_report_dir: strings, dir to store the coverage report file. 678 """ 679 self._coverage_report_dir = corverage_report_dir 680 681 def _ExecuteOneAdbShellCommand(self, dut, serial, cmd): 682 """Helper method to execute a shell command and return results. 683 684 Args: 685 dut: the device under test. 686 cmd: string, command to execute. 687 Returns: 688 stdout result of the command, None if command fails. 689 """ 690 if dut is None: 691 results = cmd_utils.ExecuteShellCommand("adb -s %s shell %s" % 692 (serial, cmd)) 693 if (results[cmd_utils.EXIT_CODE][0]): 694 logging.error("Fail to execute command: %s. error: %s" % 695 (cmd, str(results[cmd_utils.STDERR][0]))) 696 return None 697 else: 698 return results[cmd_utils.STDOUT][0] 699 else: 700 try: 701 return dut.adb.shell(cmd) 702 except AdbError as e: 703 logging.warn("Fail to execute command: %s. error: %s" % 704 (cmd, str(e))) 705 return None 706 707 708if __name__ == '__main__': 709 """ Tools to process coverage data. 710 711 Usage: 712 python coverage_utils.py operation [--serial=device_serial_number] 713 [--report_prefix=prefix_of_coverage_report] 714 715 Example: 716 python coverage_utils.py init_coverage 717 python coverage_utils.py get_coverage --serial HT7821A00243 718 python coverage_utils.py get_coverage --serial HT7821A00243 --report_prefix=test 719 """ 720 logging.basicConfig(level=logging.INFO) 721 parser = argparse.ArgumentParser(description="Coverage process tool.") 722 parser.add_argument( 723 "--report_prefix", 724 dest="report_prefix", 725 required=False, 726 help="Prefix of the coverage report.") 727 parser.add_argument( 728 "--report_path", 729 dest="report_path", 730 required=False, 731 help="directory to store the coverage reports.") 732 parser.add_argument( 733 "--serial", dest="serial", required=True, help="Device serial number.") 734 parser.add_argument( 735 "--gcov_rescource_path", 736 dest="gcov_rescource_path", 737 required=True, 738 help="Directory that stores gcov resource files.") 739 parser.add_argument( 740 "operation", 741 help= 742 "Operation for processing coverage data, e.g. 'init_coverage', get_coverage'" 743 ) 744 args = parser.parse_args() 745 746 if args.operation != "init_coverage" and args.operation != "get_coverage": 747 print "Unsupported operation. Exiting..." 748 sys.exit(1) 749 user_params = { 750 keys.ConfigKeys.IKEY_ENABLE_COVERAGE: 751 True, 752 keys.ConfigKeys.IKEY_ANDROID_DEVICE: [{ 753 keys.ConfigKeys.IKEY_SERIAL: 754 args.serial, 755 keys.ConfigKeys.IKEY_GCOV_RESOURCES_PATH: 756 args.gcov_rescource_path, 757 }], 758 keys.ConfigKeys.IKEY_OUTPUT_COVERAGE_REPORT: 759 True, 760 keys.ConfigKeys.IKEY_GLOBAL_COVERAGE: 761 True 762 } 763 coverage = CoverageFeature(user_params) 764 if args.operation == "init_coverage": 765 coverage.InitializeDeviceCoverage(serial=args.serial) 766 elif args.operation == "get_coverage": 767 if args.report_prefix: 768 coverage.SetCoverageReportFilePrefix(args.report_prefix) 769 if args.report_path: 770 coverage.SetCoverageReportDirectory(args.report_path) 771 coverage.SetCoverageData(serial=args.serial, isGlobal=True) 772