#!/usr/bin/env python3 # tools/gcov.py # SPDX-License-Identifier: Apache-2.0 # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. The # ASF licenses this file to you 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. import argparse import os import shutil import subprocess import sys def copy_file_endswith(endswith, source_dir, target_dir): print(f"Collect {endswith} files {source_dir} -> {target_dir}") if not os.path.exists(target_dir): os.makedirs(target_dir) for root, _, files in os.walk(source_dir): for file in files: if file.endswith(endswith): source_file = os.path.join(root, file) target_file = os.path.join(target_dir, file) shutil.copy2(source_file, target_file) def arg_parser(): parser = argparse.ArgumentParser( description="Code coverage generation tool.", add_help=False ) parser.add_argument("-t", dest="gcov_tool", help="Path to gcov tool") parser.add_argument("-s", dest="gcno_dir", help="Directory containing gcno files") parser.add_argument("-a", dest="gcda_dir", help="Directory containing gcda files") parser.add_argument("--debug", action="store_true", help="Enable debug mode") parser.add_argument( "-x", dest="only_copy", action="store_true", help="Only copy *.gcno and *.gcda files", ) parser.add_argument( "gcov_dir", nargs="?", default=os.getcwd(), help="Directory to store gcov data and report", ) return parser.parse_args() def main(): args = arg_parser() root_dir = os.getcwd() gcov_dir = os.path.abspath(args.gcov_dir) gcno_dir = os.path.abspath(args.gcno_dir) if args.gcno_dir else root_dir gcda_dir = os.path.abspath(args.gcda_dir) if args.gcda_dir else root_dir coverage_file = os.path.join(gcov_dir, "coverage.info") result_dir = os.path.join(gcov_dir, "result") gcov_data_dir = os.path.join(gcov_dir, "data") if args.debug: debug_file = os.path.join(gcov_dir, "debug.log") sys.stdout = open(debug_file, "w+") os.makedirs(os.path.join(gcov_dir, "data"), exist_ok=True) # Collect gcno, gcda files copy_file_endswith(".gcno", gcno_dir, gcov_data_dir) copy_file_endswith(".gcda", gcda_dir, gcov_data_dir) # Only copy files if args.only_copy: sys.exit(0) # lcov tool is required if shutil.which("lcov") is None: print( "Error: Code coverage generation tool is not detected, please install lcov." ) sys.exit(1) try: # lcov collect coverage data to coverage_file subprocess.run( [ "lcov", "-c", "-d", gcov_data_dir, "-o", coverage_file, "--rc", "lcov_branch_coverage=1", "--gcov-tool", args.gcov_tool, "--ignore-errors", "gcov", ], check=True, stdout=sys.stdout, stderr=sys.stdout, ) # genhtml generate coverage report subprocess.run( [ "genhtml", "--branch-coverage", "-o", result_dir, coverage_file, "--ignore-errors", "source", ], check=True, stdout=sys.stdout, stderr=sys.stdout, ) print( "Copy the following link and open it in the browser to view the coverage report:" ) print(f"file://{os.path.join(result_dir, 'index.html')}") except subprocess.CalledProcessError: print("Failed to generate coverage file.") sys.exit(1) shutil.rmtree(gcov_data_dir) os.remove(coverage_file) if __name__ == "__main__": main()