def _SetupOutputDir(): """Setup output directory.""" if os.path.exists(OUTPUT_DIR): shutil.rmtree(OUTPUT_DIR) # Creates |OUTPUT_DIR| and its platform sub-directory. os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
def _ExecuteCommand(target, command, output_file_path): """Runs a single command and generates a profraw data file.""" # Per Clang "Source-based Code Coverage" doc: # # "%p" expands out to the process ID. It's not used by this scripts due to: # 1) If a target program spawns too many processess, it may exhaust all disk # space available. For example, unit_tests writes thousands of .profraw # files each of size 1GB+. # 2) If a target binary uses shared libraries, coverage profile data for them # will be missing, resulting in incomplete coverage reports. # # "%Nm" expands out to the instrumented binary's signature. When this pattern # is specified, the runtime creates a pool of N raw profiles which are used # for on-line profile merging. The runtime takes care of selecting a raw # profile from the pool, locking it, and updating it before the program exits. # N must be between 1 and 9. The merge pool specifier can only occur once per # filename pattern. # # "%1m" is used when tests run in single process, such as fuzz targets. # # For other cases, "%4m" is chosen as it creates some level of parallelism, # but it's not too big to consume too much computing resource or disk space. profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m' expected_profraw_file_name = os.extsep.join( [target, profile_pattern_string, PROFRAW_FILE_EXTENSION]) expected_profraw_file_path = os.path.join( coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), expected_profraw_file_name) command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION, expected_profraw_file_path) try: # Some fuzz targets or tests may write into stderr, redirect it as well. with open(output_file_path, 'wb') as output_file_handle: subprocess.check_call( shlex.split(command), stdout=output_file_handle, stderr=subprocess.STDOUT, env=_GetEnvironmentVars(expected_profraw_file_path)) except subprocess.CalledProcessError as e: logging.warning('Command: "%s" exited with non-zero return code.', command) return open(output_file_path, 'rb').read()
def _GetTargetProfDataPathsByExecutingCommands(targets, commands): """Runs commands and returns the relative paths to the profraw data files. Args: targets: A list of targets built with coverage instrumentation. commands: A list of commands used to run the targets. Returns: A list of relative paths to the generated profraw data files. """ logging.debug('Executing the test commands.') # Remove existing profraw data files. report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR) for file_or_dir in os.listdir(report_root_dir): if file_or_dir.endswith(PROFRAW_FILE_EXTENSION): os.remove(os.path.join(report_root_dir, file_or_dir)) # Ensure that logs directory exists. if not os.path.exists(_GetLogsDirectoryPath()): os.makedirs(_GetLogsDirectoryPath()) profdata_file_paths = [] # Run all test targets to generate profraw data files. for target, command in zip(targets, commands): output_file_name = os.extsep.join([target + '_output', 'log']) output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name) profdata_file_path = None for _ in xrange(MERGE_RETRIES): logging.info( 'Running command: "%s", the output is redirected to "%s".', command, output_file_path) if _IsIOSCommand(command): # On iOS platform, due to lack of write permissions, profraw files are # generated outside of the OUTPUT_DIR, and the exact paths are contained # in the output of the command execution. output = _ExecuteIOSCommand(command, output_file_path) else: # On other platforms, profraw files are generated inside the OUTPUT_DIR. output = _ExecuteCommand(target, command, output_file_path) profraw_file_paths = [] if _IsIOS(): profraw_file_paths = [ _GetProfrawDataFileByParsingOutput(output) ] else: for file_or_dir in os.listdir(report_root_dir): if file_or_dir.endswith(PROFRAW_FILE_EXTENSION): profraw_file_paths.append( os.path.join(report_root_dir, file_or_dir)) assert profraw_file_paths, ( 'Running target "%s" failed to generate any profraw data file, ' 'please make sure the binary exists, is properly instrumented and ' 'does not crash. %s' % (target, FILE_BUG_MESSAGE)) assert isinstance(profraw_file_paths, list), ( 'Variable \'profraw_file_paths\' is expected to be of type \'list\', ' 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE)) try: profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles( target, profraw_file_paths) break except Exception: logging.info('Retrying...') finally: # Remove profraw files now so that they are not used in next iteration. for profraw_file_path in profraw_file_paths: os.remove(profraw_file_path) assert profdata_file_path, ( 'Failed to merge target "%s" profraw files after %d retries. %s' % (target, MERGE_RETRIES, FILE_BUG_MESSAGE)) profdata_file_paths.append(profdata_file_path) logging.debug('Finished executing the test commands.') return profdata_file_paths
def _GetSummaryFilePath(): """The JSON file that contains coverage summary written by llvm-cov export.""" return os.path.join( coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), SUMMARY_FILE_NAME)
def _GetProfdataFilePath(): """Path to the resulting .profdata file.""" return os.path.join( coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), PROFDATA_FILE_NAME)
def _GetLogsDirectoryPath(): """Path to the logs directory.""" return os.path.join( coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
def _GetLcovFilePath(): """The LCOV file that contains coverage data written by llvm-cov export.""" return os.path.join( coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LCOV_FILE_NAME)