gcov/script: gcov.sh is implemented using Python

Signed-off-by: wangmingrong1 <wangmingrong1@xiaomi.com>
This commit is contained in:
wangmingrong1 2024-12-18 18:14:52 +08:00 committed by Xiang Xiao
parent 7548db1980
commit 7d6b2e4804
2 changed files with 152 additions and 147 deletions

152
tools/gcov.py Executable file
View file

@ -0,0 +1,152 @@
#!/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()

View file

@ -1,147 +0,0 @@
#!/usr/bin/env bash
# tools/gcov.sh
#
# 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.
#
ROOT_DIR=$(pwd)
GCNO_DIR=$ROOT_DIR
GCDA_DIR=$ROOT_DIR
show_help() {
echo "Usage: $0 [-t gcov_tool] [-s gcno_dir] [-a gcda_dir] [-x] [gcov_dir]"
echo " -t gcov_tool: path to gcov tool, e.g. ./nuttx/tools/gcov.sh -t arm-none-eabi-gcov"
echo " -s gcno_dir: directory containing gcno files (relative or absolute path allowed)"
echo " -a gcda_dir: directory containing gcda files (relative or absolute path allowed)"
echo " -x: only copy *.gcno and *.gcda files"
echo " gcov_dir: directory to store gcov data and report (optional)"
exit 1
}
convert_to_absolute_path() {
local dir_path=$1
if [ -z "$dir_path" ]; then
echo "Error: Directory path is empty."
exit 1
fi
# Convert to absolute path if not already
if [ ! "${dir_path:0:1}" = "/" ]; then
dir_path=$(realpath "$dir_path" 2>/dev/null)
fi
echo "$dir_path"
}
while getopts "a:s:t:xh" opt
do
case $opt in
a)
GCDA_DIR=$(convert_to_absolute_path "$OPTARG")
if [ ! -d "$GCDA_DIR" ]; then
echo "Error: Invalid gcda directory: $OPTARG"
exit 1
fi
;;
s)
GCNO_DIR=$(convert_to_absolute_path "$OPTARG")
if [ ! -d "$GCNO_DIR" ]; then
echo "Error: Invalid gcno directory: $OPTARG"
exit 1
fi
;;
t)
GCOV_TOOL="--gcov-tool $OPTARG"
;;
x)
ONLY_COPY=1
;;
h)
show_help
;;
?)
show_help
;;
esac
done
# Handle gcov_dir as the last positional argument
shift $((OPTIND - 1))
if [ $# -gt 1 ]; then
echo "Error: Too many arguments."
show_help
fi
if [ $# -eq 1 ]; then
GCOV_DIR=$(convert_to_absolute_path "$1")
else
GCOV_DIR=${ROOT_DIR}/gcov
fi
mkdir -p ${GCOV_DIR} ${GCOV_DIR}/data
cd ${GCOV_DIR}
# Collect gcno files
find ${GCNO_DIR}/ -name "*.gcno" -exec cp {} ${GCOV_DIR}/data > /dev/null 2>&1 \;
# Collect gcda files
find ${GCDA_DIR}/ -name "*.gcda" -exec cp {} ${GCOV_DIR}/data > /dev/null 2>&1 \;
# Ensure ONLY_COPY is initialized as an integer
ONLY_COPY=${ONLY_COPY:-0}
if [ "$ONLY_COPY" -eq 1 ]; then
exit 0
fi
if [ -z "$GCOV_TOOL" ]; then
echo "Error: -t is a required option."
show_help
exit 1
fi
type lcov > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Code coverage generation tool is not detected, please install lcov"
exit 1
fi
files=$(find ${GCOV_DIR}/data -name "*.gcda" 2> /dev/null | wc -l)
if [ "$files" == "0" ] ;then
echo "gcda file not found in directory ${ROOT_DIR}"
echo "Please run ./nuttx before using gcov.sh to generate the coverage report"
echo "Or copy the gcda file in the device to ${ROOT_DIR}"
exit 1
fi
# Generate coverage text report
lcov -c -d ${GCOV_DIR}/data -o coverage.info --rc lcov_branch_coverage=1 ${GCOV_TOOL} --ignore-errors gcov
# Generate coverage page report
genhtml --branch-coverage -o result coverage.info --ignore-errors source
if [ $? -ne 0 ]; then
echo "Failed to generate coverage file"
exit 1
fi
echo -e "Copy the following link and open it in the browser to view the coverage report"
echo "file://${GCOV_DIR}/result/index.html"