[PATCH 2/2] perf test: Add --skip-name to skip tests by name

From: James Clark

Date: Fri Oct 23 2026 - 05:03:36 EST


--skip takes test numbers which change whenever tests are added or
removed, so a list of tests to skip in CI has to be updated as perf
changes, or it silently skips the wrong tests.

Add --skip-name to skip a test by its name which should be more stable.
The option can be repeated to skip more than one test instead of taking
a comma separated list, as some descriptions contain commas, e.g.:

$ perf test --skip-name 'Add fd to a fdarray, making it autogrow' \
--skip-name 'Simple expression parser' 7 32 33
7: Simple expression parser : Skip (user override)
33: Add fd to a fdarray, making it autogrow : Skip (user override)
32: Filter fds with revents mask in a fdarray : Ok

The match is exact rather than substring so that adding a new test
containing an existing skip substring doesn't get skipped.

A name that matches no test exits with an error so that a renamed test
isn't silently run again:

$ perf test --skip-name 'Add fd to a fdarray'
No test matches --skip-name 'Add fd to a fdarray'
$ echo $?
234

Signed-off-by: James Clark <james.clark@xxxxxxxxxx>
---
tools/perf/Documentation/perf-test.txt | 4 ++
tools/perf/tests/builtin-test.c | 71 ++++++++++++++++++++++++++++++++--
2 files changed, 71 insertions(+), 4 deletions(-)

diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt
index f66ccd77bfe7..bc3426705a80 100644
--- a/tools/perf/Documentation/perf-test.txt
+++ b/tools/perf/Documentation/perf-test.txt
@@ -57,6 +57,10 @@ OPTIONS
--skip::
Tests to skip (comma separated numeric list).

+--skip-name=<name>::
+ Skip the test whose description is exactly <name>, as printed by
+ 'perf test list'. Can be provided multiple times.
+
-v::
-vv::
-vvv::
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index cd0fc3dedc3e..433534b8e182 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -42,6 +42,7 @@
#include "hist.h"
#include "intlist.h"
#include "string2.h"
+#include "strlist.h"
#include "symbol.h"
#include "tests-scripts.h"
#include "tests.h"
@@ -70,6 +71,8 @@ static unsigned int runs_per_test = 1;
static unsigned int failure_snippet_lines = 10;
/* Numbers of the test suites to skip, from --skip. */
static struct intlist *skiplist;
+/* Exact descriptions of the tests to skip, from --skip-name. */
+static struct strlist *skip_names;
/*
* The tests mapping ring buffers take turns using their share of
* kernel.perf_event_mlock_kb via this mutex, shared with the forked test
@@ -344,9 +347,12 @@ static bool test_exclusive(const struct test_suite *t, int test_case)
return t->test_cases[test_case].exclusive;
}

-static bool perf_test__skip_suite(int suite_num)
+static bool perf_test__skip_suite(const struct test_suite *t, int suite_num)
{
- return intlist__find(skiplist, suite_num + 1) != NULL;
+ if (intlist__find(skiplist, suite_num + 1))
+ return true;
+
+ return skip_names && strlist__has_entry(skip_names, test_description(t, -1));
}

static bool perf_test__matches(const char *desc, int suite_num, int argc, const char *argv[])
@@ -1466,7 +1472,7 @@ static int __cmd_test(struct test_suite **suites, int argc, const char *argv[])
suite_matched = true;
}

- if (perf_test__skip_suite(curr_suite)) {
+ if (perf_test__skip_suite(*t, curr_suite)) {
if (pass == 1)
print_suite_user_override(*t, curr_suite, width);
continue;
@@ -1510,6 +1516,52 @@ static int __cmd_test(struct test_suite **suites, int argc, const char *argv[])
return err;
}

+static int parse_skip_name(const struct option *opt __maybe_unused, const char *str,
+ int unset)
+{
+ int err;
+
+ /* --no-skip-name clears the list, like --no-skip does. */
+ if (unset) {
+ strlist__delete(skip_names);
+ skip_names = NULL;
+ return 0;
+ }
+
+ if (!skip_names) {
+ skip_names = strlist__new(NULL, NULL);
+ if (!skip_names)
+ return -ENOMEM;
+ }
+ err = strlist__add(skip_names, str);
+
+ /* Asking to skip a test twice is harmless. */
+ return err == -EEXIST ? 0 : err;
+}
+
+/*
+ * A --skip-name that matches nothing is likely a typo or a test that was
+ * renamed, report it rather than silently running the test.
+ */
+static int perf_test__check_skip_names(struct test_suite **suites)
+{
+ struct str_node *pos;
+ int err = 0;
+
+ strlist__for_each_entry(pos, skip_names) {
+ bool found = false;
+
+ for (struct test_suite **t = suites; *t && !found; t++)
+ found = !strcmp(test_description(*t, -1), pos->s);
+
+ if (!found) {
+ pr_err("No test matches --skip-name '%s'\n", pos->s);
+ err = -EINVAL;
+ }
+ }
+ return err;
+}
+
static int perf_test__list(FILE *fp, struct test_suite **suites, int argc, const char **argv)
{
int curr_suite = 0;
@@ -1831,7 +1883,7 @@ static bool perf_test__shares_mlock_budget(struct test_suite **suites, int argc,
int curr_test_case;
bool suite_matched;

- if (!t->needs_mlock || perf_test__skip_suite(curr_suite))
+ if (!t->needs_mlock || perf_test__skip_suite(t, curr_suite))
continue;

suite_matched = perf_test__matches(test_description(t, /*test_case=*/-1),
@@ -1918,6 +1970,9 @@ int cmd_test(int argc, const char **argv)
bool list_workloads = false;
const struct option test_options[] = {
OPT_STRING('s', "skip", &skip, "tests", "tests to skip"),
+ OPT_CALLBACK(0, "skip-name", NULL, "name",
+ "test to skip, by its exact name, can be repeated",
+ parse_skip_name),
OPT_INCR('v', "verbose", &verbose,
"be more verbose (show symbol address, etc)"),
OPT_BOOLEAN('F', "dont-fork", &dont_fork,
@@ -2011,12 +2066,20 @@ int cmd_test(int argc, const char **argv)
int err = errno;

intlist__delete(skiplist);
+ strlist__delete(skip_names);
return err ? -err : -ENOMEM;
}
+ if (skip_names) {
+ ret = perf_test__check_skip_names(suites);
+ if (ret)
+ goto out;
+ }
perf_test__setup_mlock_budget(suites, argc, argv);
ret = __cmd_test(suites, argc, argv);
perf_test__teardown_mlock_budget();
+out:
free(suites);
intlist__delete(skiplist);
+ strlist__delete(skip_names);
return ret;
}

--
2.43.0