summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/bpf/test_loader.c
blob: 679efb3aa785e31aa490dd1c895d2889eafcf98a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include <stdlib.h>
#include <test_progs.h>
#include <bpf/btf.h>

#define str_has_pfx(str, pfx) \
	(strncmp(str, pfx, __builtin_constant_p(pfx) ? sizeof(pfx) - 1 : strlen(pfx)) == 0)

#define TEST_LOADER_LOG_BUF_SZ 1048576

#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_LOG_LEVEL_PFX "comment:test_log_level="

struct test_spec {
	const char *name;
	bool expect_failure;
	const char *expect_msg;
	int log_level;
};

static int tester_init(struct test_loader *tester)
{
	if (!tester->log_buf) {
		tester->log_buf_sz = TEST_LOADER_LOG_BUF_SZ;
		tester->log_buf = malloc(tester->log_buf_sz);
		if (!ASSERT_OK_PTR(tester->log_buf, "tester_log_buf"))
			return -ENOMEM;
	}

	return 0;
}

void test_loader_fini(struct test_loader *tester)
{
	if (!tester)
		return;

	free(tester->log_buf);
}

static int parse_test_spec(struct test_loader *tester,
			   struct bpf_object *obj,
			   struct bpf_program *prog,
			   struct test_spec *spec)
{
	struct btf *btf;
	int func_id, i;

	memset(spec, 0, sizeof(*spec));

	spec->name = bpf_program__name(prog);

	btf = bpf_object__btf(obj);
	if (!btf) {
		ASSERT_FAIL("BPF object has no BTF");
		return -EINVAL;
	}

	func_id = btf__find_by_name_kind(btf, spec->name, BTF_KIND_FUNC);
	if (func_id < 0) {
		ASSERT_FAIL("failed to find FUNC BTF type for '%s'", spec->name);
		return -EINVAL;
	}

	for (i = 1; i < btf__type_cnt(btf); i++) {
		const struct btf_type *t;
		const char *s;

		t = btf__type_by_id(btf, i);
		if (!btf_is_decl_tag(t))
			continue;

		if (t->type != func_id || btf_decl_tag(t)->component_idx != -1)
			continue;

		s = btf__str_by_offset(btf, t->name_off);
		if (strcmp(s, TEST_TAG_EXPECT_FAILURE) == 0) {
			spec->expect_failure = true;
		} else if (strcmp(s, TEST_TAG_EXPECT_SUCCESS) == 0) {
			spec->expect_failure = false;
		} else if (str_has_pfx(s, TEST_TAG_EXPECT_MSG_PFX)) {
			spec->expect_msg = s + sizeof(TEST_TAG_EXPECT_MSG_PFX) - 1;
		} else if (str_has_pfx(s, TEST_TAG_LOG_LEVEL_PFX)) {
			errno = 0;
			spec->log_level = strtol(s + sizeof(TEST_TAG_LOG_LEVEL_PFX) - 1, NULL, 0);
			if (errno) {
				ASSERT_FAIL("failed to parse test log level from '%s'", s);
				return -EINVAL;
			}
		}
	}

	return 0;
}

static void prepare_case(struct test_loader *tester,
			 struct test_spec *spec,
			 struct bpf_object *obj,
			 struct bpf_program *prog)
{
	int min_log_level = 0;

	if (env.verbosity > VERBOSE_NONE)
		min_log_level = 1;
	if (env.verbosity > VERBOSE_VERY)
		min_log_level = 2;

	bpf_program__set_log_buf(prog, tester->log_buf, tester->log_buf_sz);

	/* Make sure we set at least minimal log level, unless test requirest
	 * even higher level already. Make sure to preserve independent log
	 * level 4 (verifier stats), though.
	 */
	if ((spec->log_level & 3) < min_log_level)
		bpf_program__set_log_level(prog, (spec->log_level & 4) | min_log_level);
	else
		bpf_program__set_log_level(prog, spec->log_level);

	tester->log_buf[0] = '\0';
}

static void emit_verifier_log(const char *log_buf, bool force)
{
	if (!force && env.verbosity == VERBOSE_NONE)
		return;
	fprintf(stdout, "VERIFIER LOG:\n=============\n%s=============\n", log_buf);
}

static void validate_case(struct test_loader *tester,
			  struct test_spec *spec,
			  struct bpf_object *obj,
			  struct bpf_program *prog,
			  int load_err)
{
	if (spec->expect_msg) {
		char *match;

		match = strstr(tester->log_buf, spec->expect_msg);
		if (!ASSERT_OK_PTR(match, "expect_msg")) {
			/* if we are in verbose mode, we've already emitted log */
			if (env.verbosity == VERBOSE_NONE)
				emit_verifier_log(tester->log_buf, true /*force*/);
			fprintf(stderr, "EXPECTED MSG: '%s'\n", spec->expect_msg);
			return;
		}
	}
}

/* this function is forced noinline and has short generic name to look better
 * in test_progs output (in case of a failure)
 */
static noinline
void run_subtest(struct test_loader *tester,
		 const char *skel_name,
		 skel_elf_bytes_fn elf_bytes_factory)
{
	LIBBPF_OPTS(bpf_object_open_opts, open_opts, .object_name = skel_name);
	struct bpf_object *obj = NULL, *tobj;
	struct bpf_program *prog, *tprog;
	const void *obj_bytes;
	size_t obj_byte_cnt;
	int err;

	if (tester_init(tester) < 0)
		return; /* failed to initialize tester */

	obj_bytes = elf_bytes_factory(&obj_byte_cnt);
	obj = bpf_object__open_mem(obj_bytes, obj_byte_cnt, &open_opts);
	if (!ASSERT_OK_PTR(obj, "obj_open_mem"))
		return;

	bpf_object__for_each_program(prog, obj) {
		const char *prog_name = bpf_program__name(prog);
		struct test_spec spec;

		if (!test__start_subtest(prog_name))
			continue;

		/* if we can't derive test specification, go to the next test */
		err = parse_test_spec(tester, obj, prog, &spec);
		if (!ASSERT_OK(err, "parse_test_spec"))
			continue;

		tobj = bpf_object__open_mem(obj_bytes, obj_byte_cnt, &open_opts);
		if (!ASSERT_OK_PTR(tobj, "obj_open_mem")) /* shouldn't happen */
			continue;

		bpf_object__for_each_program(tprog, tobj)
			bpf_program__set_autoload(tprog, false);

		bpf_object__for_each_program(tprog, tobj) {
			/* only load specified program */
			if (strcmp(bpf_program__name(tprog), prog_name) == 0) {
				bpf_program__set_autoload(tprog, true);
				break;
			}
		}

		prepare_case(tester, &spec, tobj, tprog);

		err = bpf_object__load(tobj);
		if (spec.expect_failure) {
			if (!ASSERT_ERR(err, "unexpected_load_success")) {
				emit_verifier_log(tester->log_buf, false /*force*/);
				goto tobj_cleanup;
			}
		} else {
			if (!ASSERT_OK(err, "unexpected_load_failure")) {
				emit_verifier_log(tester->log_buf, true /*force*/);
				goto tobj_cleanup;
			}
		}

		emit_verifier_log(tester->log_buf, false /*force*/);
		validate_case(tester, &spec, tobj, tprog, err);

tobj_cleanup:
		bpf_object__close(tobj);
	}

	bpf_object__close(obj);
}

void test_loader__run_subtests(struct test_loader *tester,
			       const char *skel_name,
			       skel_elf_bytes_fn elf_bytes_factory)
{
	/* see comment in run_subtest() for why we do this function nesting */
	run_subtest(tester, skel_name, elf_bytes_factory);
}