This is an automated email from the ASF dual-hosted git repository. xiaoxiang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 3648e5db3fc3deae22b08bffa5401ef832fc47e6 Author: wangmingrong1 <wangmingro...@xiaomi.com> AuthorDate: Tue Mar 11 14:29:21 2025 +0800 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 <wangmingro...@xiaomi.com> --- include/gcov.h | 114 ----------- libs/libbuiltin/libgcc/gcov.c | 435 ++++++++++++++++++++++++------------------ tools/gcov.py | 271 ++++++++++++++------------ 3 files changed, 399 insertions(+), 421 deletions(-) diff --git a/include/gcov.h b/include/gcov.h index 3c6251b82c..5dc38d07dd 100644 --- a/include/gcov.h +++ b/include/gcov.h @@ -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 } diff --git a/libs/libbuiltin/libgcc/gcov.c b/libs/libbuiltin/libgcc/gcov.c index e9571ce97f..6d6680382c 100644 --- a/libs/libbuiltin/libgcc/gcov.c +++ b/libs/libbuiltin/libgcc/gcov.c @@ -27,25 +27,30 @@ #include <errno.h> #include <string.h> #include <syslog.h> -#include <unistd.h> #include <sys/stat.h> +#include <sys/types.h> -#include <nuttx/lib/lib.h> +#include <nuttx/streams.h> #include <nuttx/reboot_notifier.h> /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ -#define GCOV_DATA_MAGIC (0x67636461) -#define GCOV_NOTE_MAGIC (0x67636e6f) -#define GCOV_FILENAME_MAGIC (0x6763666e) +/* The GCOV 12 gcno/gcda format has slight change, + * Please refer to gcov-io.h in the GCC 12 for + * more details. + */ -#define GCOV_TAG_FUNCTION (0x01000000) -#define GCOV_TAG_COUNTER_BASE (0x01a10000) +#if __GNUC__ >= 12 +# define GCOV_12_FORMAT +#endif -#define GCOV_TAG_FOR_COUNTER(count) \ - (GCOV_TAG_COUNTER_BASE + ((uint32_t)(count) << 17)) +#ifdef GCOV_12_FORMAT +# define GCOV_UNIT_SIZE 4 +#else +# define GCOV_UNIT_SIZE 1 +#endif #ifdef GCOV_12_FORMAT # define GCOV_TAG_FUNCTION_LENGTH 12 @@ -53,18 +58,55 @@ # define GCOV_TAG_FUNCTION_LENGTH 3 #endif -#ifdef GCOV_12_FORMAT -# define GCOV_UNIT_SIZE 4 +#if __GNUC__ >= 14 +# define GCOV_COUNTERS 9u +#elif __GNUC__ >= 10 +# define GCOV_COUNTERS 8u +#elif __GNUC__ >= 8 +# define GCOV_COUNTERS 9u #else -# define GCOV_UNIT_SIZE 1 +# define GCOV_COUNTERS 10u #endif +#define GCOV_DATA_MAGIC (0x67636461) +#define GCOV_NOTE_MAGIC (0x67636e6f) +#define GCOV_FILENAME_MAGIC (0x6763666e) + +#define GCOV_TAG_FUNCTION (0x01000000) +#define GCOV_TAG_COUNTER_BASE (0x01a10000) + +#define GCOV_TAG_FOR_COUNTER(count) \ + (GCOV_TAG_COUNTER_BASE + ((uint32_t)(count) << 17)) + +#define GCOV_PATH_MAX_TOKENS 64 + /**************************************************************************** * Private Types ****************************************************************************/ +typedef uint64_t gcov_type; typedef unsigned int gcov_unsigned_t; +/** 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 */ +}; + /* Information about counters for a single function * * This data is generated by gcc during compilation and doesn't change @@ -92,6 +134,20 @@ struct gcov_fn_info struct gcov_ctr_info ctrs[1]; /* Instrumented counters */ }; +struct dump_args +{ + FAR CODE int (*mkdir)(FAR const char *); + FAR struct lib_outstream_s *stream; + FAR char *prefix; + FAR char *tokens[GCOV_PATH_MAX_TOKENS]; + size_t token_cnt; + FAR char *path_buffer; + int strip; +}; + +typedef CODE int (*dump_fn_t)(FAR struct dump_args *, + FAR char *, FAR uint8_t *, size_t); + /**************************************************************************** * Public Data ****************************************************************************/ @@ -123,6 +179,28 @@ static void dump_unsigned(FAR void *buffer, FAR size_t *off, uint32_t v) *off += sizeof(uint32_t); } +static int get_path_tokens(FAR char *path, + FAR char **tokens, size_t max_tokens) +{ + size_t token_count = 0; + FAR char *token; + + token = strtok(path, "/"); + while (token != NULL) + { + if (token_count >= max_tokens) + { + return -ENAMETOOLONG; + } + + tokens[token_count++] = token; + token = strtok(NULL, "/"); + } + + tokens[token_count] = NULL; + return token_count; +} + static size_t gcov_convert(FAR uint8_t *buffer, FAR const struct gcov_info *info) { @@ -182,130 +260,150 @@ static size_t gcov_convert(FAR uint8_t *buffer, return pos; } -static int gcov_process_path(FAR char *prefix, int strip, - FAR char *path, FAR char *new_path, - size_t len) +static int gcov_mkdir(FAR const char *path) { - FAR char *tokens[64]; - FAR char *filename; - FAR char *token; - int token_count = 0; - int prefix_count; - int level = 0; - int ret; - int i; - - token = strtok(prefix, "/"); - while (token != NULL) + if (access(path, F_OK) != 0) { - tokens[token_count++] = token; - token = strtok(NULL, "/"); + return mkdir(path, 0777); } - /* Split the path into directories and filename */ + return OK; +} - prefix_count = token_count; - token = strtok(path, "/"); - if (token == NULL) +#ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT +static int gcov_reboot_notify(FAR struct notifier_block *nb, + unsigned long action, FAR void *data) +{ + __gcov_dump(); + return OK; +} +#endif + +static int gcov_standard_dump(FAR struct dump_args *args, + FAR char *path, FAR uint8_t *data, size_t size) +{ + int written; + int fd; + + fd = _NX_OPEN(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { - return -EINVAL; + return -errno; } - while (token != NULL) + written = _NX_WRITE(fd, data, size); + if (written != size) { - filename = token; - if (level++ >= strip) - { - /* Skip the specified number of leading directories */ + return -errno; + } - if (token_count >= sizeof(tokens) / sizeof(tokens[0])) - { - return -ENAMETOOLONG; - } + _NX_CLOSE(fd); + return OK; +} - tokens[token_count++] = token; - } +static int gcov_onefile_dump(FAR struct dump_args *args, + FAR char *path, + FAR uint8_t *data, size_t size) +{ + FAR struct lib_outstream_s *stream = args->stream; + struct lib_hexdumpstream_s hexstream; + uint16_t checksum = 0; + int i; - token = strtok(NULL, "/"); + lib_hexdumpstream(&hexstream, stream); + for (i = 0; i < size; i++) + { + checksum += ((FAR const uint8_t *)data)[i]; } - /* Add the filename */ + lib_sprintf(stream, + "gcov start filename:%s size: %zuByte\n", + path, size); - if (prefix_count == token_count) - { - tokens[token_count++] = filename; - } + lib_stream_puts(&hexstream, data, size); + lib_stream_flush(&hexstream); - new_path[0] = '\0'; - tokens[token_count] = NULL; + lib_sprintf(stream, + "gcov end filename:%s checksum: %#0x\n", + path, checksum); + lib_stream_flush(stream); + + return OK; +} - /* Check and create directories */ +static int gcov_dump_foreach(dump_fn_t handler, FAR struct dump_args *args) +{ + FAR struct gcov_info *info; + FAR char *filename; + FAR uint8_t *data; + int token_count; + int size; + int i; - for (i = 0; i < token_count - 1; i++) + for (info = __gcov_info_start; info; info = info->next) { - strcat(new_path, "/"); - strcat(new_path, tokens[i]); - if (access(new_path, F_OK) != 0) + memset(args->path_buffer, 0x00, PATH_MAX); + + size = gcov_convert(NULL, info); + data = lib_malloc(size); + if (data == NULL) + { + syslog(LOG_ERR, "gcov alloc failed!"); + return -ERROR; + } + + gcov_convert(data, info); + + filename = strdup(info->filename); + if (filename == NULL) + { + syslog(LOG_ERR, "gcov alloc failed! skip %s", info->filename); + lib_free(data); + return -ERROR; + } + + token_count = args->token_cnt + + get_path_tokens(filename, + &args->tokens[args->token_cnt], + GCOV_PATH_MAX_TOKENS); + if (token_count < args->token_cnt) + { + syslog(LOG_ERR, "gcov get path tokens failed! skip %s", + info->filename); + goto exit; + } + + for (i = 0; i < token_count - 1; i++) { - ret = mkdir(new_path, 0777); - if (ret != 0) + strcat(args->path_buffer, "/"); + strcat(args->path_buffer, args->tokens[i]); + if (args->mkdir) { - return -errno; + args->mkdir(args->path_buffer); } } - } - strcat(new_path, "/"); - strcat(new_path, filename); - return 0; -} - -static int gcov_write_file(FAR const char *filename, - FAR const struct gcov_info *info) -{ - FAR uint8_t *buffer; - size_t written; - int ret = OK; - size_t size; - int fd; + strcat(args->path_buffer, "/"); + strcat(args->path_buffer, args->tokens[token_count - 1]); - fd = _NX_OPEN(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd < 0) - { - syslog(LOG_ERR, "open %s failed!", filename); - return -errno; - } + if (handler(args, args->path_buffer, data, size) < 0) + { + syslog(LOG_ERR, "gcov dump %s failed!\n", args->path_buffer); + goto exit; + } - size = gcov_convert(NULL, info); - buffer = lib_malloc(size); - if (buffer == NULL) - { - syslog(LOG_ERR, "gcov alloc failed!"); - _NX_CLOSE(fd); - return -errno; + lib_free(filename); + lib_free(data); } - gcov_convert(buffer, info); - written = _NX_WRITE(fd, buffer, size); - if (written != size) - { - syslog(LOG_ERR, "gcov write file failed!"); - ret = -errno; - } + return OK; - _NX_CLOSE(fd); - lib_free(buffer); - return ret; -} +exit: + lib_free(filename); + lib_free(data); -#ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT -static int gcov_reboot_notify(FAR struct notifier_block *nb, - unsigned long action, FAR void *data) -{ - __gcov_dump(); - return 0; + return -ERROR; } -#endif /**************************************************************************** * Public Functions @@ -332,6 +430,7 @@ void __gcov_init(FAR struct gcov_info *info) tm_info); setenv("GCOV_PREFIX_STRIP", CONFIG_COVERAGE_DEFAULT_PREFIX_STRIP, 1); + setenv("GCOV_DUMP_ONEFILE", "1", 1); setenv("GCOV_PREFIX", path, 1); #ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT @@ -373,66 +472,68 @@ pid_t __gcov_fork(void) void __gcov_dump(void) { - FAR struct gcov_info *info; - FAR const char *strip = getenv("GCOV_PREFIX_STRIP"); - FAR const char *prefix = getenv("GCOV_PREFIX"); - FAR char *new_path; - FAR char *prefix2; - int ret; - + int ret = -1; + bool onefile; + FAR char *prefix; + struct dump_args args = + { + 0 + }; + + prefix = getenv("GCOV_PREFIX"); if (prefix == NULL) { syslog(LOG_ERR, "No path prefix specified"); - return; } - prefix2 = strdup(prefix); - if (prefix2 == NULL) + args.path_buffer = lib_get_tempbuffer(PATH_MAX); + if (args.path_buffer == NULL) { syslog(LOG_ERR, "gcov alloc failed!"); return; } - new_path = lib_get_tempbuffer(PATH_MAX); - for (info = __gcov_info_start; info; info = info->next) + onefile = strcmp(getenv("GCOV_DUMP_ONEFILE"), "1") == 0; + if (onefile) { - FAR char *filename; - - filename = strdup(info->filename); - if (filename == NULL) + struct lib_rawoutstream_s stream; + int fd = _NX_OPEN(prefix, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { - syslog(LOG_ERR, "gcov alloc failed! skip %s", info->filename); - continue; + syslog(LOG_ERR, "open %s failed! ret: %d\n", prefix, ret); + goto exit; } - /* Process the path, add the prefix and strip the leading directories */ + lib_rawoutstream(&stream, fd); + args.stream = &stream.common; - strcpy(prefix2, prefix); - ret = gcov_process_path(prefix2, atoi(strip), filename, - new_path, PATH_MAX); - if (ret != 0) - { - syslog(LOG_ERR, "gcov process path failed! skip %s", - new_path); - lib_free(filename); - continue; - } + ret = gcov_dump_foreach(gcov_onefile_dump, &args); - /* Convert the data and write to the file */ + _NX_CLOSE(fd); + } + else + { + args.mkdir = gcov_mkdir; + args.strip = atoi(getenv("GCOV_PREFIX_STRIP")); + args.token_cnt = get_path_tokens(prefix, + args.tokens, + GCOV_PATH_MAX_TOKENS); - ret = gcov_write_file(new_path, info); - if (ret != 0) - { - syslog(LOG_ERR, "gcov write file failed! skip %s", new_path); - lib_free(filename); - continue; - } + ret = gcov_dump_foreach(gcov_standard_dump, &args); + } - lib_free(filename); +exit: + + if (ret < 0) + { + syslog(LOG_INFO, "Gcov dump failed\n"); + } + else + { + syslog(LOG_INFO, "Gcov dump complete\n"); } - lib_put_tempbuffer(new_path); - lib_free(prefix2); + lib_put_tempbuffer(args.path_buffer); } void __gcov_reset(void) @@ -461,47 +562,3 @@ void __gcov_reset(void) } } } - -void __gcov_filename_to_gcfn(FAR const char *filename, - FAR void (*dump_fn)(FAR const void *, - unsigned, FAR void *), - FAR void *arg) -{ - if (dump_fn) - { - size_t len = strlen(filename); - dump_fn(filename, len, arg); - } -} - -void __gcov_info_to_gcda(FAR const struct gcov_info *info, - FAR void (*filename_fn)(FAR const char *, - FAR void *), - FAR void (*dump_fn)(FAR const void *, size_t, - FAR void *), - FAR void *(*allocate_fn)(unsigned int, FAR void *), - FAR void *arg) -{ - FAR const char *name = info->filename; - FAR uint8_t *buffer; - size_t size; - - filename_fn(name, arg); - - /* Get the size of the buffer */ - - size = gcov_convert(NULL, info); - - buffer = lib_malloc(size); - if (!buffer) - { - syslog(LOG_ERR, "gcov alloc failed!"); - return; - } - - /* Convert the data */ - - gcov_convert(buffer, info); - dump_fn(buffer, size, arg); - lib_free(buffer); -} diff --git a/tools/gcov.py b/tools/gcov.py index 946b73d6b0..9be55e2b37 100755 --- a/tools/gcov.py +++ b/tools/gcov.py @@ -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() - -def correct_content_path(file, newpath): + 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, 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) - try: + 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)) - # 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, - ) + # 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) - 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, - ) + # 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: + 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.")