mirror of
https://github.com/apache/nuttx.git
synced 2026-05-20 12:33:27 +08:00
gcov: Refactoring the implementation framework of gcov
All implementations of gcov are sunk to the kernel implementation 1. Support three dump modes: serial port output, single file output, standard output Signed-off-by: wangmingrong1 <wangmingrong1@xiaomi.com>
This commit is contained in:
committed by
Xiang Xiao
parent
1cb5077bc1
commit
3648e5db3f
-114
@@ -29,63 +29,6 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
/* The GCOV 12 gcno/gcda format has slight change,
|
||||
* Please refer to gcov-io.h in the GCC 12 for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#if __GNUC__ >= 12
|
||||
# define GCOV_12_FORMAT
|
||||
#endif
|
||||
|
||||
#if __GNUC__ >= 14
|
||||
# define GCOV_COUNTERS 9u
|
||||
#elif __GNUC__ >= 10
|
||||
# define GCOV_COUNTERS 8u
|
||||
#elif __GNUC__ >= 8
|
||||
# define GCOV_COUNTERS 9u
|
||||
#else
|
||||
# define GCOV_COUNTERS 10u
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Public Types
|
||||
****************************************************************************/
|
||||
|
||||
struct gcov_fn_info;
|
||||
typedef uint64_t gcov_type;
|
||||
|
||||
/** Profiling data per object file
|
||||
*
|
||||
* This data is generated by gcc during compilation and doesn't change
|
||||
* at run-time with the exception of the next pointer.
|
||||
*/
|
||||
|
||||
struct gcov_info
|
||||
{
|
||||
unsigned int version; /* Gcov version (same as GCC version) */
|
||||
FAR struct gcov_info *next; /* List head for a singly-linked list */
|
||||
unsigned int stamp; /* Uniquifying time stamp */
|
||||
#ifdef GCOV_12_FORMAT
|
||||
unsigned int checksum; /* unique object checksum */
|
||||
#endif
|
||||
FAR const char *filename; /* Name of the associated gcda data file */
|
||||
void (*merge[GCOV_COUNTERS])(FAR gcov_type *, unsigned int);
|
||||
unsigned int n_functions; /* number of instrumented functions */
|
||||
FAR struct gcov_fn_info **functions; /* function information */
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Public Data
|
||||
****************************************************************************/
|
||||
|
||||
extern FAR struct gcov_info *__gcov_info_start;
|
||||
extern FAR struct gcov_info *__gcov_info_end;
|
||||
|
||||
/****************************************************************************
|
||||
* Public Function Prototypes
|
||||
****************************************************************************/
|
||||
@@ -118,63 +61,6 @@ extern void __gcov_reset(void);
|
||||
|
||||
extern void __gcov_dump(void);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: __gcov_info_to_gcda
|
||||
*
|
||||
* Description:
|
||||
* Convert the gcov information referenced by INFO to a gcda data stream.
|
||||
*
|
||||
* Parameters:
|
||||
* info - Pointer to the gcov information.
|
||||
* filename - Callback function to get the filename.
|
||||
* dump - Callback function to write the gcda data.
|
||||
* allocate - Callback function to allocate memory.
|
||||
* arg - User-provided argument.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
extern void __gcov_info_to_gcda(FAR const struct gcov_info *info,
|
||||
FAR void (*filename)(FAR const char *,
|
||||
FAR void *),
|
||||
FAR void (*dump)(FAR const void *,
|
||||
size_t, FAR void *),
|
||||
FAR void *(*allocate)(unsigned int,
|
||||
FAR void *),
|
||||
FAR void *arg);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: __gcov_filename_to_gcfn
|
||||
*
|
||||
* Description:
|
||||
* Convert the filename to a gcfn data stream.
|
||||
*
|
||||
* Parameters:
|
||||
* filename - Pointer to the filename.
|
||||
* dump - Callback function to write the gcfn data.
|
||||
* arg - User-provided argument.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
extern void __gcov_filename_to_gcfn(FAR const char *filename,
|
||||
FAR void (*dump)(FAR const void *,
|
||||
unsigned int,
|
||||
FAR void *),
|
||||
FAR void *arg);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: __gcov_dump_to_memory
|
||||
*
|
||||
* Description:
|
||||
* Dump gcov data directly to memory
|
||||
*
|
||||
* Parameters:
|
||||
* ptr - Memory Address
|
||||
* size - Memory block size
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
size_t __gcov_dump_to_memory(FAR void *ptr, size_t size);
|
||||
|
||||
#undef EXTERN
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+265
-208
File diff suppressed because it is too large
Load Diff
+153
-118
@@ -30,6 +30,9 @@ def parse_gcda_data(path):
|
||||
with open(path, "r") as file:
|
||||
lines = file.read().strip().splitlines()
|
||||
|
||||
gcda_path = path + "_covert"
|
||||
os.makedirs(gcda_path, exist_ok=True)
|
||||
|
||||
started = False
|
||||
filename = ""
|
||||
output = ""
|
||||
@@ -47,70 +50,147 @@ def parse_gcda_data(path):
|
||||
if not started:
|
||||
continue
|
||||
|
||||
if line.startswith("gcov end"):
|
||||
started = False
|
||||
if size != len(output) // 2:
|
||||
print(
|
||||
f"Size mismatch for {filename}: expected {size} bytes, got {len(output) // 2} bytes"
|
||||
)
|
||||
|
||||
match = re.search(r"checksum:\s*(0x[0-9a-fA-F]+)", line)
|
||||
if match:
|
||||
checksum = int(match.group(1), 16)
|
||||
output = bytearray.fromhex(output)
|
||||
expected = sum(output) % 65536
|
||||
if checksum != expected:
|
||||
print(
|
||||
f"Checksum mismatch for {filename}: expected {checksum}, got {expected}"
|
||||
try:
|
||||
if line.startswith("gcov end"):
|
||||
started = False
|
||||
if size != len(output) // 2:
|
||||
raise ValueError(
|
||||
f"Size mismatch for {filename}: expected {size} bytes, got {len(output) // 2} bytes"
|
||||
)
|
||||
continue
|
||||
|
||||
with open(filename, "wb") as fp:
|
||||
fp.write(output)
|
||||
print(f"write {filename} success")
|
||||
output = ""
|
||||
else:
|
||||
output += line.strip()
|
||||
match = re.search(r"checksum:\s*(0x[0-9a-fA-F]+)", line)
|
||||
if match:
|
||||
checksum = int(match.group(1), 16)
|
||||
output = bytearray.fromhex(output)
|
||||
expected = sum(output) % 65536
|
||||
if checksum != expected:
|
||||
raise ValueError(
|
||||
f"Checksum mismatch for {filename}: expected {checksum}, got {expected}"
|
||||
)
|
||||
|
||||
outfile = os.path.join(gcda_path, "./" + filename)
|
||||
os.makedirs(os.path.dirname(outfile), exist_ok=True)
|
||||
|
||||
with open(outfile, "wb") as fp:
|
||||
fp.write(output)
|
||||
print(f"write {outfile} success")
|
||||
output = ""
|
||||
else:
|
||||
output += line.strip()
|
||||
except Exception as e:
|
||||
print(f"Error processing {path}: {e}")
|
||||
print(f"gcov start filename:{filename} size:{size}")
|
||||
print(output)
|
||||
print(f"gcov end filename:{filename} checksum:{checksum}")
|
||||
|
||||
return gcda_path
|
||||
|
||||
|
||||
def correct_content_path(file, newpath):
|
||||
def correct_content_path(file, shield: list, newpath):
|
||||
with open(file, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
pattern = r"SF:([^\s]*?)/nuttx/include/nuttx"
|
||||
matches = re.findall(pattern, content)
|
||||
for i in shield:
|
||||
content = content.replace(i, "")
|
||||
|
||||
if matches:
|
||||
new_content = content.replace(matches[0], newpath)
|
||||
new_content = content
|
||||
if newpath is not None:
|
||||
pattern = r"SF:([^\s]*?)/nuttx/include/nuttx"
|
||||
matches = re.findall(pattern, content)
|
||||
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
f.write(new_content)
|
||||
if matches:
|
||||
new_content = content.replace(matches[0], newpath)
|
||||
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
f.write(new_content)
|
||||
|
||||
|
||||
def copy_file_endswith(endswith, source_dir, target_dir, skip_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):
|
||||
if skip_dir in root:
|
||||
def copy_file_endswith(endswith, source, target):
|
||||
for root, dirs, files in os.walk(source, topdown=True):
|
||||
if target in root:
|
||||
continue
|
||||
|
||||
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)
|
||||
src_file = os.path.join(root, file)
|
||||
dst_file = os.path.join(target, os.path.relpath(src_file, source))
|
||||
os.makedirs(os.path.dirname(dst_file), exist_ok=True)
|
||||
shutil.copy2(src_file, dst_file)
|
||||
|
||||
|
||||
def run_lcov(data_dir, gcov_tool):
|
||||
output = data_dir + ".info"
|
||||
# lcov collect coverage data to coverage.info
|
||||
command = [
|
||||
"lcov",
|
||||
"-c",
|
||||
"-o",
|
||||
output,
|
||||
"--rc",
|
||||
"lcov_branch_coverage=1",
|
||||
"--gcov-tool",
|
||||
gcov_tool,
|
||||
"--ignore-errors",
|
||||
"gcov",
|
||||
"--directory",
|
||||
data_dir,
|
||||
]
|
||||
print(command)
|
||||
subprocess.run(
|
||||
command,
|
||||
check=True,
|
||||
stdout=sys.stdout,
|
||||
stderr=sys.stdout,
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def run_genhtml(info, report):
|
||||
cmd = [
|
||||
"genhtml",
|
||||
"--branch-coverage",
|
||||
"-o",
|
||||
report,
|
||||
"--ignore-errors",
|
||||
"source",
|
||||
info,
|
||||
]
|
||||
print(cmd)
|
||||
subprocess.run(
|
||||
cmd,
|
||||
check=True,
|
||||
stdout=sys.stdout,
|
||||
stderr=sys.stdout,
|
||||
)
|
||||
|
||||
|
||||
def run_merge(gcda_dir1, gcda_dir2, output, merge_tool):
|
||||
command = [
|
||||
merge_tool,
|
||||
"merge",
|
||||
gcda_dir1,
|
||||
gcda_dir2,
|
||||
"-o",
|
||||
output,
|
||||
]
|
||||
print(command)
|
||||
subprocess.run(
|
||||
command,
|
||||
check=True,
|
||||
stdout=sys.stdout,
|
||||
stderr=sys.stdout,
|
||||
)
|
||||
|
||||
|
||||
def arg_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Code coverage generation tool.", add_help=False
|
||||
)
|
||||
parser.add_argument("-i", "--input", help="Input dump data")
|
||||
parser.add_argument("-t", dest="gcov_tool", help="Path to gcov tool")
|
||||
parser.add_argument("-b", dest="base_dir", help="Compile base directory")
|
||||
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
||||
parser.add_argument("--delete", action="store_true", help="Delete gcda files")
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
dest="gcno_dir",
|
||||
@@ -124,7 +204,6 @@ def arg_parser():
|
||||
nargs="+",
|
||||
help="Directory containing gcda files",
|
||||
)
|
||||
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
||||
parser.add_argument(
|
||||
"-x",
|
||||
dest="only_copy",
|
||||
@@ -133,7 +212,7 @@ def arg_parser():
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
dest="gcov_dir",
|
||||
dest="result_dir",
|
||||
default="gcov",
|
||||
help="Directory to store gcov data and report",
|
||||
)
|
||||
@@ -145,41 +224,19 @@ 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)
|
||||
result_dir = os.path.abspath(args.result_dir)
|
||||
|
||||
os.makedirs(gcov_dir, exist_ok=True)
|
||||
|
||||
gcda_dir = []
|
||||
for i in args.gcda_dir:
|
||||
gcda_dir.append(os.path.abspath(i))
|
||||
|
||||
coverage_file = os.path.join(gcov_dir, "coverage.info")
|
||||
result_dir = os.path.join(gcov_dir, "result")
|
||||
os.makedirs(result_dir, exist_ok=True)
|
||||
merge_tool = args.gcov_tool + "-tool"
|
||||
data_dir = os.path.join(result_dir, "data")
|
||||
report_dir = os.path.join(result_dir, "report")
|
||||
coverage_file = os.path.join(result_dir, "coverage.info")
|
||||
|
||||
if args.debug:
|
||||
debug_file = os.path.join(gcov_dir, "debug.log")
|
||||
debug_file = os.path.join(result_dir, "debug.log")
|
||||
sys.stdout = open(debug_file, "w+")
|
||||
|
||||
if args.input:
|
||||
parse_gcda_data(os.path.join(root_dir, args.input))
|
||||
|
||||
gcov_data_dir = []
|
||||
|
||||
# Collect gcno, gcda files
|
||||
for i in gcda_dir:
|
||||
|
||||
dir = os.path.join(gcov_dir + "/data", os.path.basename(i))
|
||||
gcov_data_dir.append(dir)
|
||||
os.makedirs(dir)
|
||||
|
||||
copy_file_endswith(".gcno", gcno_dir, dir, gcov_dir)
|
||||
copy_file_endswith(".gcda", i, dir, gcov_dir)
|
||||
|
||||
# Only copy files
|
||||
if args.only_copy:
|
||||
sys.exit(0)
|
||||
|
||||
# lcov tool is required
|
||||
if shutil.which("lcov") is None:
|
||||
print(
|
||||
@@ -187,57 +244,35 @@ def main():
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
gcda_dirs = []
|
||||
for i in args.gcda_dir:
|
||||
if os.path.isfile(i):
|
||||
gcda_dirs.append(parse_gcda_data(os.path.join(root_dir, i)))
|
||||
if args.delete:
|
||||
os.remove(i)
|
||||
else:
|
||||
gcda_dirs.append(os.path.abspath(i))
|
||||
|
||||
# Merge all gcda files
|
||||
shutil.copytree(gcda_dirs[0], data_dir)
|
||||
for gcda_dir in gcda_dirs[1:]:
|
||||
run_merge(data_dir, gcda_dir, data_dir, merge_tool)
|
||||
|
||||
# Copy gcno files and run lcov generate coverage info file
|
||||
copy_file_endswith(".gcno", gcno_dir, data_dir)
|
||||
coverage_file = run_lcov(data_dir, args.gcov_tool)
|
||||
|
||||
# Only copy files
|
||||
if args.only_copy:
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
|
||||
# lcov collect coverage data to coverage_file
|
||||
command = [
|
||||
"lcov",
|
||||
"-c",
|
||||
"-o",
|
||||
coverage_file,
|
||||
"--rc",
|
||||
"lcov_branch_coverage=1",
|
||||
"--gcov-tool",
|
||||
args.gcov_tool,
|
||||
"--ignore-errors",
|
||||
"gcov",
|
||||
]
|
||||
for i in gcov_data_dir:
|
||||
command.append("-d")
|
||||
command.append(i)
|
||||
|
||||
print(command)
|
||||
|
||||
subprocess.run(
|
||||
command,
|
||||
check=True,
|
||||
stdout=sys.stdout,
|
||||
stderr=sys.stdout,
|
||||
)
|
||||
|
||||
if args.base_dir:
|
||||
correct_content_path(coverage_file, args.base_dir)
|
||||
|
||||
# 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,
|
||||
)
|
||||
run_genhtml(coverage_file, report_dir)
|
||||
|
||||
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')}")
|
||||
print(f"file://{os.path.join(report_dir, 'index.html')}")
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
print("Failed to generate coverage file.")
|
||||
|
||||
Reference in New Issue
Block a user