]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
selftests/bpf: __not_msg() tag for test_loader framework
authorEduard Zingerman <eddyz87@gmail.com>
Fri, 19 Sep 2025 02:18:44 +0000 (19:18 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Fri, 19 Sep 2025 16:27:23 +0000 (09:27 -0700)
This patch adds tags __not_msg(<msg>) and __not_msg_unpriv(<msg>).
Test fails if <msg> is found in verifier log.

If __msg_not() is situated between __msg() tags framework matches
__msg() tags first, and then checks that <msg> is not present in a
portion of a log between bracketing __msg() tags.
__msg_not() tags bracketed by a same __msg() group are effectively
unordered.

The idea is borrowed from LLVM's CheckFile with its CHECK-NOT syntax.

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20250918-callchain-sensitive-liveness-v3-11-c3cd27bacc60@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/prog_tests/prog_tests_framework.c
tools/testing/selftests/bpf/progs/bpf_misc.h
tools/testing/selftests/bpf/test_loader.c
tools/testing/selftests/bpf/test_progs.h

index 14f2796076e0c2bd056138ee1433a937305b1708..7607cfc2408c2d49a051fd195666a653afb6c6f0 100644 (file)
@@ -54,3 +54,128 @@ void test_prog_tests_framework(void)
                return;
        clear_test_state(state);
 }
+
+static void dummy_emit(const char *buf, bool force) {}
+
+void test_prog_tests_framework_expected_msgs(void)
+{
+       struct expected_msgs msgs;
+       int i, j, error_cnt;
+       const struct {
+               const char *name;
+               const char *log;
+               const char *expected;
+               struct expect_msg *pats;
+       } cases[] = {
+               {
+                       .name = "simple-ok",
+                       .log = "aaabbbccc",
+                       .pats = (struct expect_msg[]) {
+                               { .substr = "aaa" },
+                               { .substr = "ccc" },
+                               {}
+                       }
+               },
+               {
+                       .name = "simple-fail",
+                       .log = "aaabbbddd",
+                       .expected = "MATCHED    SUBSTR: 'aaa'\n"
+                                   "EXPECTED   SUBSTR: 'ccc'\n",
+                       .pats = (struct expect_msg[]) {
+                               { .substr = "aaa" },
+                               { .substr = "ccc" },
+                               {}
+                       }
+               },
+               {
+                       .name = "negative-ok-mid",
+                       .log = "aaabbbccc",
+                       .pats = (struct expect_msg[]) {
+                               { .substr = "aaa" },
+                               { .substr = "foo", .negative = true },
+                               { .substr = "bar", .negative = true },
+                               { .substr = "ccc" },
+                               {}
+                       }
+               },
+               {
+                       .name = "negative-ok-tail",
+                       .log = "aaabbbccc",
+                       .pats = (struct expect_msg[]) {
+                               { .substr = "aaa" },
+                               { .substr = "foo", .negative = true },
+                               {}
+                       }
+               },
+               {
+                       .name = "negative-ok-head",
+                       .log = "aaabbbccc",
+                       .pats = (struct expect_msg[]) {
+                               { .substr = "foo", .negative = true },
+                               { .substr = "ccc" },
+                               {}
+                       }
+               },
+               {
+                       .name = "negative-fail-head",
+                       .log = "aaabbbccc",
+                       .expected = "UNEXPECTED SUBSTR: 'aaa'\n",
+                       .pats = (struct expect_msg[]) {
+                               { .substr = "aaa", .negative = true },
+                               { .substr = "bbb" },
+                               {}
+                       }
+               },
+               {
+                       .name = "negative-fail-tail",
+                       .log = "aaabbbccc",
+                       .expected = "UNEXPECTED SUBSTR: 'ccc'\n",
+                       .pats = (struct expect_msg[]) {
+                               { .substr = "bbb" },
+                               { .substr = "ccc", .negative = true },
+                               {}
+                       }
+               },
+               {
+                       .name = "negative-fail-mid-1",
+                       .log = "aaabbbccc",
+                       .expected = "UNEXPECTED SUBSTR: 'bbb'\n",
+                       .pats = (struct expect_msg[]) {
+                               { .substr = "aaa" },
+                               { .substr = "bbb", .negative = true },
+                               { .substr = "ccc" },
+                               {}
+                       }
+               },
+               {
+                       .name = "negative-fail-mid-2",
+                       .log = "aaabbb222ccc",
+                       .expected = "UNEXPECTED SUBSTR: '222'\n",
+                       .pats = (struct expect_msg[]) {
+                               { .substr = "aaa" },
+                               { .substr = "222", .negative = true },
+                               { .substr = "bbb", .negative = true },
+                               { .substr = "ccc" },
+                               {}
+                       }
+               }
+       };
+
+       for (i = 0; i < ARRAY_SIZE(cases); i++) {
+               if (test__start_subtest(cases[i].name)) {
+                       error_cnt = env.subtest_state->error_cnt;
+                       msgs.patterns = cases[i].pats;
+                       msgs.cnt = 0;
+                       for (j = 0; cases[i].pats[j].substr; j++)
+                               msgs.cnt++;
+                       validate_msgs(cases[i].log, &msgs, dummy_emit);
+                       fflush(stderr);
+                       env.subtest_state->error_cnt = error_cnt;
+                       if (cases[i].expected)
+                               ASSERT_HAS_SUBSTR(env.subtest_state->log_buf, cases[i].expected, "expected output");
+                       else
+                               ASSERT_STREQ(env.subtest_state->log_buf, "", "expected no output");
+                       test__end_subtest();
+               }
+       }
+}
index 1004c4a64aafb9c6d886e25b110139eba688f5f4..a7a1a684eed116e304028b3ff0f32c3de4900dcf 100644 (file)
  *                   e.g. "foo{{[0-9]+}}"  matches strings like "foo007".
  *                   Extended POSIX regular expression syntax is allowed
  *                   inside the brackets.
+ * __not_msg         Message not expected to be found in verifier log.
+ *                   If __msg_not is situated between __msg tags
+ *                   framework matches __msg tags first, and then
+ *                   checks that __msg_not is not present in a portion of
+ *                   a log between bracketing __msg tags.
+ *                   Same regex syntax as for __msg is supported.
  * __msg_unpriv      Same as __msg but for unprivileged mode.
+ * __not_msg_unpriv  Same as __not_msg but for unprivileged mode.
  *
  * __stderr          Message expected to be found in bpf stderr stream. The
  *                   same regex rules apply like __msg.
  * __caps_unpriv     Specify the capabilities that should be set when running the test.
  */
 #define __msg(msg)             __attribute__((btf_decl_tag("comment:test_expect_msg=" XSTR(__COUNTER__) "=" msg)))
+#define __not_msg(msg)         __attribute__((btf_decl_tag("comment:test_expect_not_msg=" XSTR(__COUNTER__) "=" msg)))
 #define __xlated(msg)          __attribute__((btf_decl_tag("comment:test_expect_xlated=" XSTR(__COUNTER__) "=" msg)))
 #define __jited(msg)           __attribute__((btf_decl_tag("comment:test_jited=" XSTR(__COUNTER__) "=" msg)))
 #define __failure              __attribute__((btf_decl_tag("comment:test_expect_failure")))
 #define __success              __attribute__((btf_decl_tag("comment:test_expect_success")))
 #define __description(desc)    __attribute__((btf_decl_tag("comment:test_description=" desc)))
 #define __msg_unpriv(msg)      __attribute__((btf_decl_tag("comment:test_expect_msg_unpriv=" XSTR(__COUNTER__) "=" msg)))
+#define __not_msg_unpriv(msg)  __attribute__((btf_decl_tag("comment:test_expect_not_msg_unpriv=" XSTR(__COUNTER__) "=" msg)))
 #define __xlated_unpriv(msg)   __attribute__((btf_decl_tag("comment:test_expect_xlated_unpriv=" XSTR(__COUNTER__) "=" msg)))
 #define __jited_unpriv(msg)    __attribute__((btf_decl_tag("comment:test_jited=" XSTR(__COUNTER__) "=" msg)))
 #define __failure_unpriv       __attribute__((btf_decl_tag("comment:test_expect_failure_unpriv")))
index e065b467d5090e89c22c5e7a6dba0138a8ebecaf..74ecc281bb8c12435318a06919f4cf6192faa4cd 100644 (file)
@@ -2,7 +2,6 @@
 /* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
 #include <linux/capability.h>
 #include <stdlib.h>
-#include <regex.h>
 #include <test_progs.h>
 #include <bpf/btf.h>
 
 #define TEST_TAG_EXPECT_FAILURE "comment:test_expect_failure"
 #define TEST_TAG_EXPECT_SUCCESS "comment:test_expect_success"
 #define TEST_TAG_EXPECT_MSG_PFX "comment:test_expect_msg="
+#define TEST_TAG_EXPECT_NOT_MSG_PFX "comment:test_expect_not_msg="
 #define TEST_TAG_EXPECT_XLATED_PFX "comment:test_expect_xlated="
 #define TEST_TAG_EXPECT_FAILURE_UNPRIV "comment:test_expect_failure_unpriv"
 #define TEST_TAG_EXPECT_SUCCESS_UNPRIV "comment:test_expect_success_unpriv"
 #define TEST_TAG_EXPECT_MSG_PFX_UNPRIV "comment:test_expect_msg_unpriv="
+#define TEST_TAG_EXPECT_NOT_MSG_PFX_UNPRIV "comment:test_expect_not_msg_unpriv="
 #define TEST_TAG_EXPECT_XLATED_PFX_UNPRIV "comment:test_expect_xlated_unpriv="
 #define TEST_TAG_LOG_LEVEL_PFX "comment:test_log_level="
 #define TEST_TAG_PROG_FLAGS_PFX "comment:test_prog_flags="
@@ -65,18 +66,6 @@ enum load_mode {
        NO_JITED        = 1 << 1,
 };
 
-struct expect_msg {
-       const char *substr; /* substring match */
-       regex_t regex;
-       bool is_regex;
-       bool on_next_line;
-};
-
-struct expected_msgs {
-       struct expect_msg *patterns;
-       size_t cnt;
-};
-
 struct test_subspec {
        char *name;
        bool expect_failure;
@@ -216,7 +205,8 @@ static int compile_regex(const char *pattern, regex_t *regex)
        return 0;
 }
 
-static int __push_msg(const char *pattern, bool on_next_line, struct expected_msgs *msgs)
+static int __push_msg(const char *pattern, bool on_next_line, bool negative,
+                     struct expected_msgs *msgs)
 {
        struct expect_msg *msg;
        void *tmp;
@@ -232,6 +222,7 @@ static int __push_msg(const char *pattern, bool on_next_line, struct expected_ms
        msg = &msgs->patterns[msgs->cnt];
        msg->on_next_line = on_next_line;
        msg->substr = pattern;
+       msg->negative = negative;
        msg->is_regex = false;
        if (strstr(pattern, "{{")) {
                err = compile_regex(pattern, &msg->regex);
@@ -250,16 +241,16 @@ static int clone_msgs(struct expected_msgs *from, struct expected_msgs *to)
 
        for (i = 0; i < from->cnt; i++) {
                msg = &from->patterns[i];
-               err = __push_msg(msg->substr, msg->on_next_line, to);
+               err = __push_msg(msg->substr, msg->on_next_line, msg->negative, to);
                if (err)
                        return err;
        }
        return 0;
 }
 
-static int push_msg(const char *substr, struct expected_msgs *msgs)
+static int push_msg(const char *substr, bool negative, struct expected_msgs *msgs)
 {
-       return __push_msg(substr, false, msgs);
+       return __push_msg(substr, false, negative, msgs);
 }
 
 static int push_disasm_msg(const char *regex_str, bool *on_next_line, struct expected_msgs *msgs)
@@ -270,7 +261,7 @@ static int push_disasm_msg(const char *regex_str, bool *on_next_line, struct exp
                *on_next_line = false;
                return 0;
        }
-       err = __push_msg(regex_str, *on_next_line, msgs);
+       err = __push_msg(regex_str, *on_next_line, false, msgs);
        if (err)
                return err;
        *on_next_line = true;
@@ -482,12 +473,22 @@ static int parse_test_spec(struct test_loader *tester,
                        spec->auxiliary = true;
                        spec->mode_mask |= UNPRIV;
                } else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_MSG_PFX))) {
-                       err = push_msg(msg, &spec->priv.expect_msgs);
+                       err = push_msg(msg, false, &spec->priv.expect_msgs);
+                       if (err)
+                               goto cleanup;
+                       spec->mode_mask |= PRIV;
+               } else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_NOT_MSG_PFX))) {
+                       err = push_msg(msg, true, &spec->priv.expect_msgs);
                        if (err)
                                goto cleanup;
                        spec->mode_mask |= PRIV;
                } else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_MSG_PFX_UNPRIV))) {
-                       err = push_msg(msg, &spec->unpriv.expect_msgs);
+                       err = push_msg(msg, false, &spec->unpriv.expect_msgs);
+                       if (err)
+                               goto cleanup;
+                       spec->mode_mask |= UNPRIV;
+               } else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_NOT_MSG_PFX_UNPRIV))) {
+                       err = push_msg(msg, true, &spec->unpriv.expect_msgs);
                        if (err)
                                goto cleanup;
                        spec->mode_mask |= UNPRIV;
@@ -764,44 +765,141 @@ static void emit_stdout(const char *bpf_stdout, bool force)
        fprintf(stdout, "STDOUT:\n=============\n%s=============\n", bpf_stdout);
 }
 
-static void validate_msgs(char *log_buf, struct expected_msgs *msgs,
-                         void (*emit_fn)(const char *buf, bool force))
+static const char *match_msg(struct expect_msg *msg, const char **log)
 {
-       const char *log = log_buf, *prev_match;
+       const char *match = NULL;
        regmatch_t reg_match[1];
-       int prev_match_line;
-       int match_line;
-       int i, j, err;
+       int err;
+
+       if (!msg->is_regex) {
+               match = strstr(*log, msg->substr);
+               if (match)
+                       *log = match + strlen(msg->substr);
+       } else {
+               err = regexec(&msg->regex, *log, 1, reg_match, 0);
+               if (err == 0) {
+                       match = *log + reg_match[0].rm_so;
+                       *log += reg_match[0].rm_eo;
+               }
+       }
+       return match;
+}
+
+static int count_lines(const char *start, const char *end)
+{
+       const char *tmp;
+       int n = 0;
+
+       for (tmp = start; tmp < end; ++tmp)
+               if (*tmp == '\n')
+                       n++;
+       return n;
+}
+
+struct match {
+       const char *start;
+       const char *end;
+       int line;
+};
+
+/*
+ * Positive messages are matched sequentially, each next message
+ * is looked for starting from the end of a previous matched one.
+ */
+static void match_positive_msgs(const char *log, struct expected_msgs *msgs, struct match *matches)
+{
+       const char *prev_match;
+       int i, line;
 
-       prev_match_line = -1;
-       match_line = 0;
        prev_match = log;
+       line = 0;
+       for (i = 0; i < msgs->cnt; i++) {
+               struct expect_msg *msg = &msgs->patterns[i];
+               const char *match = NULL;
+
+               if (msg->negative)
+                       continue;
+
+               match = match_msg(msg, &log);
+               if (match) {
+                       line += count_lines(prev_match, match);
+                       matches[i].start = match;
+                       matches[i].end   = log;
+                       matches[i].line  = line;
+                       prev_match = match;
+               }
+       }
+}
+
+/*
+ * Each negative messages N located between positive messages P1 and P2
+ * is matched in the span P1.end .. P2.start. Consequently, negative messages
+ * are unordered within the span.
+ */
+static void match_negative_msgs(const char *log, struct expected_msgs *msgs, struct match *matches)
+{
+       const char *start = log, *end, *next, *match;
+       const char *log_end = log + strlen(log);
+       int i, j, next_positive;
+
        for (i = 0; i < msgs->cnt; i++) {
                struct expect_msg *msg = &msgs->patterns[i];
-               const char *match = NULL, *pat_status;
-               bool wrong_line = false;
-
-               if (!msg->is_regex) {
-                       match = strstr(log, msg->substr);
-                       if (match)
-                               log = match + strlen(msg->substr);
-               } else {
-                       err = regexec(&msg->regex, log, 1, reg_match, 0);
-                       if (err == 0) {
-                               match = log + reg_match[0].rm_so;
-                               log += reg_match[0].rm_eo;
+
+               /* positive message bumps span start */
+               if (!msg->negative) {
+                       start = matches[i].end ?: start;
+                       continue;
+               }
+
+               /* count stride of negative patterns and adjust span end */
+               end = log_end;
+               for (next_positive = i + 1; next_positive < msgs->cnt; next_positive++) {
+                       if (!msgs->patterns[next_positive].negative) {
+                               end = matches[next_positive].start;
+                               break;
                        }
                }
 
-               if (match) {
-                       for (; prev_match < match; ++prev_match)
-                               if (*prev_match == '\n')
-                                       ++match_line;
-                       wrong_line = msg->on_next_line && prev_match_line >= 0 &&
-                                    prev_match_line + 1 != match_line;
+               /* try matching negative messages within identified span */
+               for (j = i; j < next_positive; j++) {
+                       next = start;
+                       match = match_msg(msg, &next);
+                       if (match && next <= end) {
+                               matches[j].start = match;
+                               matches[j].end = next;
+                       }
                }
 
-               if (!match || wrong_line) {
+               /* -1 to account for i++ */
+               i = next_positive - 1;
+       }
+}
+
+void validate_msgs(const char *log_buf, struct expected_msgs *msgs,
+                  void (*emit_fn)(const char *buf, bool force))
+{
+       struct match matches[msgs->cnt];
+       struct match *prev_match = NULL;
+       int i, j;
+
+       memset(matches, 0, sizeof(*matches) * msgs->cnt);
+       match_positive_msgs(log_buf, msgs, matches);
+       match_negative_msgs(log_buf, msgs, matches);
+
+       for (i = 0; i < msgs->cnt; i++) {
+               struct expect_msg *msg = &msgs->patterns[i];
+               struct match *match = &matches[i];
+               const char *pat_status;
+               bool unexpected;
+               bool wrong_line;
+               bool no_match;
+
+               no_match   = !msg->negative && !match->start;
+               wrong_line = !msg->negative &&
+                            msg->on_next_line &&
+                            prev_match && prev_match->line + 1 != match->line;
+               unexpected = msg->negative && match->start;
+               if (no_match || wrong_line || unexpected) {
                        PRINT_FAIL("expect_msg\n");
                        if (env.verbosity == VERBOSE_NONE)
                                emit_fn(log_buf, true /*force*/);
@@ -811,8 +909,10 @@ static void validate_msgs(char *log_buf, struct expected_msgs *msgs,
                                        pat_status = "MATCHED   ";
                                else if (wrong_line)
                                        pat_status = "WRONG LINE";
-                               else
+                               else if (no_match)
                                        pat_status = "EXPECTED  ";
+                               else
+                                       pat_status = "UNEXPECTED";
                                msg = &msgs->patterns[j];
                                fprintf(stderr, "%s %s: '%s'\n",
                                        pat_status,
@@ -822,12 +922,13 @@ static void validate_msgs(char *log_buf, struct expected_msgs *msgs,
                        if (wrong_line) {
                                fprintf(stderr,
                                        "expecting match at line %d, actual match is at line %d\n",
-                                       prev_match_line + 1, match_line);
+                                       prev_match->line + 1, match->line);
                        }
                        break;
                }
 
-               prev_match_line = match_line;
+               if (!msg->negative)
+                       prev_match = match;
        }
 }
 
index df2222a1806fd1604fc55ce50038548cd13304de..eebfc18cdcd21d2641bd42870a3364065ab290b5 100644 (file)
@@ -7,6 +7,7 @@
 #include <errno.h>
 #include <string.h>
 #include <assert.h>
+#include <regex.h>
 #include <stdlib.h>
 #include <stdarg.h>
 #include <time.h>
@@ -546,4 +547,20 @@ extern void test_loader_fini(struct test_loader *tester);
        test_loader_fini(&tester);                                             \
 })
 
+struct expect_msg {
+       const char *substr; /* substring match */
+       regex_t regex;
+       bool is_regex;
+       bool on_next_line;
+       bool negative;
+};
+
+struct expected_msgs {
+       struct expect_msg *patterns;
+       size_t cnt;
+};
+
+void validate_msgs(const char *log_buf, struct expected_msgs *msgs,
+                  void (*emit_fn)(const char *buf, bool force));
+
 #endif /* __TEST_PROGS_H */