You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2025/07/02 16:18:09 UTC
(nuttx) 05/11: gcov: Refactoring the implementation framework of gcov
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 <wa...@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 <wa...@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.")