mirror of
https://github.com/torvalds/linux.git
synced 2026-01-25 15:03:52 +08:00
selftests/bpf: __not_msg() tag for test_loader framework
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>
This commit is contained in:
committed by
Alexei Starovoitov
parent
79f047c7d9
commit
34c513be3d
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,14 @@
|
||||
* 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.
|
||||
@@ -121,12 +128,14 @@
|
||||
* __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")))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -20,10 +19,12 @@
|
||||
#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, *pat_status;
|
||||
bool wrong_line = false;
|
||||
const char *match = NULL;
|
||||
|
||||
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;
|
||||
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];
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user