[PATCH v7 4/7] perf test: Introduce storing logs for shell tests

Jakub Brnak
2026-04-16T11:14:37Z

Create temporary directories for storing log files for shell tests
that could help while debugging. The log files are necessary for
perftool testsuite test cases also. If the variable PERFTEST_KEEP_LOGS
is set keep the logs, else delete them.

Signed-off-by: Michael Petlan <mpetlan@redhat.com>
Co-developed-by: Veronika Molnarova <vmolnaro@redhat.com>
Signed-off-by: Veronika Molnarova <vmolnaro@redhat.com>
Signed-off-by: Jakub Brnak <jbrnak@redhat.com>
---
tools/perf/tests/builtin-test.c | 133 ++++++++++++++++++++++++++++++-
tools/perf/tests/tests-scripts.c | 4 +
tools/perf/tests/tests-scripts.h | 3 +
3 files changed, 136 insertions(+), 4 deletions(-)

diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index 76fc37b440be..306ecfe5e22f 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -6,6 +6,7 @@
*/
#include <ctype.h>
#include <fcntl.h>
+#include <ftw.h>
#include <errno.h>
#ifdef HAVE_BACKTRACE_SUPPORT
#include <execinfo.h>
@@ -51,6 +52,9 @@ static unsigned int runs_per_test = 1;
const char *dso_to_test;
const char *test_objdump_path = "objdump";

+/* Number of logged test suites. */
+size_t num_logged_suites;
+
/*
* List of architecture specific tests. Not a weak symbol as the array length is
* dependent on the initialization, as such GCC with LTO complains of
@@ -288,6 +292,91 @@ static bool test_exclusive(const struct test_suite *t, int test_case)
return t->test_cases[test_case].exclusive;
}

+static int delete_file(const char *fpath, const struct stat *sb __maybe_unused,
+ int typeflag, struct FTW *ftwbuf)
+{
+ /* Stop traversal if going too deep */
+ if (ftwbuf->level > 5) {
+ pr_err("Tree traversal reached level %d, stopping.", ftwbuf->level);
+ return 1;
+ }
+
+ /* Remove only expected directories */
+ if (typeflag == FTW_D || typeflag == FTW_DP) {
+ const char *dirname = fpath + ftwbuf->base;
+
+ if (strcmp(dirname, "logs") && strcmp(dirname, "examples") &&
+ strcmp(dirname, "header_tar") && strncmp(dirname, "perf_", 5)) {
+ pr_err("Unknown directory %s", dirname);
+ return 0;
+ }
+ }
+
+ /* Attempt to remove the file, continue on failure */
+ if (remove(fpath))
+ pr_err("Failed to remove file: %s", fpath);
+
+ return 0;
+}
+
+static bool create_logs(struct test_suite *t, int pass)
+{
+ bool store_logs = t->priv && ((struct shell_test_info *)(t->priv))->store_logs;
+ bool exclusive = false;
+ int tc;
+
+ test_suite__for_each_test_case(t, tc) {
+ if (test_exclusive(t, tc)) {
+ exclusive = true;
+ break;
+ }
+ }
+
+ if (pass == 1 && (!exclusive || sequential || dont_fork)) {
+ /* Sequential and non-exclusive tests run on the first pass. */
+ return store_logs;
+ } else if (pass != 1 && exclusive && !sequential && !dont_fork) {
+ /* Exclusive tests without sequential run on the second pass. */
+ return store_logs;
+ }
+ return false;
+}
+
+static char *setup_shell_logs(const char *name)
+{
+ char template[PATH_MAX];
+ char *temp_dir;
+
+ if (snprintf(template, PATH_MAX, "/tmp/perf_test_%s.XXXXXX", name) >= PATH_MAX) {
+ pr_err("Failed to create log dir template");
+ return NULL; /* Skip the testsuite */
+ }
+
+ temp_dir = mkdtemp(template);
+ if (temp_dir) {
+ setenv("PERFSUITE_RUN_DIR", temp_dir, 1);
+ return strdup(temp_dir);
+ }
+
+ pr_err("Failed to create the temporary directory");
+
+ return NULL; /* Skip the testsuite */
+}
+
+static void cleanup_shell_logs(char *dirname)
+{
+ char *keep_logs = getenv("PERFTEST_KEEP_LOGS");
+
+ /* Check if logs should be kept or do cleanup */
+ if (dirname) {
+ if (!keep_logs || strcmp(keep_logs, "y") != 0)
+ nftw(dirname, delete_file, 8, FTW_DEPTH | FTW_PHYS);
+ free(dirname);
+ }
+
+ unsetenv("PERFSUITE_RUN_DIR");
+}
+
static bool perf_test__matches(const char *desc, int suite_num, int argc, const char *argv[])
{
int i;
@@ -627,9 +716,19 @@ static int __cmd_test(struct test_suite **suites, int argc, const char *argv[],
* runs the exclusive tests sequentially. In other modes all tests are
* run in pass 1.
*/
+
for (int pass = 1; pass <= 2; pass++) {
int child_test_num = 0;
int curr_suite = 0;
+ size_t tmpdir_sz = num_logged_suites * runs_per_test;
+ char **tmpdir = tmpdir_sz ? calloc(tmpdir_sz, sizeof(*tmpdir)) : NULL;
+ unsigned int logged_suites = 0;
+
+ if (tmpdir_sz && !tmpdir) {
+ pr_err("Out of memory while allocating log directories\n");
+ err = -ENOMEM;
+ goto err_out;
+ }

for (struct test_suite **t = suites; *t; t++, curr_suite++) {
int curr_test_case;
@@ -663,16 +762,37 @@ static int __cmd_test(struct test_suite **suites, int argc, const char *argv[],
}

for (unsigned int run = 0; run < runs_per_test; run++) {
+ /* Setup temporary log directories for shell test suites */
+ if (create_logs(*t, pass)) {
+ struct shell_test_info *info = (*t)->priv;
+
+ tmpdir[logged_suites] = setup_shell_logs((*t)->desc);
+ /* Couldn't create log dir, skip test suite */
+ if (tmpdir[logged_suites] == NULL) {
+ if (info->has_setup != NO_SETUP)
+ info->has_setup = FAILED_SETUP;
+ } else {
+ logged_suites++;
+ if (info->has_setup == PASSED_SETUP ||
+ info->has_setup == FAILED_SETUP)
+ info->has_setup = RUN_SETUP;
+ }
+ }
+
test_suite__for_each_test_case(*t, curr_test_case) {
if (!suite_matched &&
!perf_test__matches(test_description(*t, curr_test_case),
curr_suite, argc, argv))
continue;
- err = start_test(*t, curr_suite, curr_test_case,
- &child_tests[child_test_num++],
- width, pass);
- if (err)
+ err = start_test(*t, curr_suite, curr_test_case,
+ &child_tests[child_test_num++],
+ width, pass);
+ if (err) {
+ for (size_t x = 0; x < logged_suites; x++)
+ free(tmpdir[x]);
+ free(tmpdir);
goto err_out;
+ }
}
}
}
@@ -681,6 +801,11 @@ static int __cmd_test(struct test_suite **suites, int argc, const char *argv[],
for (size_t x = 0; x < num_tests; x++)
finish_test(child_tests, x, num_tests, width);
}
+
+ /* Cleanup logs; on err_out logs are intentionally kept for debugging */
+ for (size_t x = 0; x < logged_suites; x++)
+ cleanup_shell_logs(tmpdir[x]);
+ free(tmpdir);
}
err_out:
signal(SIGINT, SIG_DFL);
diff --git a/tools/perf/tests/tests-scripts.c b/tools/perf/tests/tests-scripts.c
index 33f1e46267f4..fc5e1b835ecc 100644
--- a/tools/perf/tests/tests-scripts.c
+++ b/tools/perf/tests/tests-scripts.c
@@ -322,6 +322,7 @@ static struct test_suite *prepare_test_suite(int dir_fd)

test_info->base_path = strdup_check(dirpath); /* Absolute path to dir */
test_info->has_setup = NO_SETUP;
+ test_info->store_logs = false;

test_suite->priv = test_info;
test_suite->desc = NULL;
@@ -506,6 +507,9 @@ static void append_suites_in_dir(int dir_fd,
continue;
}

+ /* Store logs for testsuites in sub-directories */
+ ((struct shell_test_info *)(test_suite->priv))->store_logs = true;
+ num_logged_suites++;
if (is_test_script(fd, SHELL_SETUP)) { /* Check for setup existence */
char *desc = shell_test__description(fd, SHELL_SETUP);

diff --git a/tools/perf/tests/tests-scripts.h b/tools/perf/tests/tests-scripts.h
index 013031239883..1dd91f1444e3 100644
--- a/tools/perf/tests/tests-scripts.h
+++ b/tools/perf/tests/tests-scripts.h
@@ -16,8 +16,11 @@ enum shell_setup {
struct shell_test_info {
const char *base_path;
enum shell_setup has_setup;
+ bool store_logs;
};

struct test_suite **create_script_test_suites(void);

+extern size_t num_logged_suites;
+
#endif /* TESTS_SCRIPTS_H */
--
2.52.0