Skip to content

Commit 59a040f

Browse files
authored
Merge pull request #1406 from sancppp/ebpf_ut
eBPF unit test: Remove expired codes & Add framework doc
2 parents d878050 + 8ec0436 commit 59a040f

File tree

5 files changed

+240
-558
lines changed

5 files changed

+240
-558
lines changed

docs/ebpf_unit_test_zh.md

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Kmesh eBPF 单元测试框架文档
2+
3+
# 1. 框架概述
4+
5+
Kmesh eBPF 单元测试框架是一个用于测试 eBPF 内核态程序的工具,支持多种 eBPF 程序类型的单元测试。该框架基于 Go 语言的单元测试框架,能够独立运行单个 eBPF 程序的测试,而无需加载整个 Kmesh 系统,从而提高测试效率和覆盖率。
6+
7+
# 2. 目录结构
8+
9+
测试框架的目录结构如下:
10+
11+
```
12+
test/bpf_ut/
13+
├── bpftest/ # Go 语言实现的单元测试框架
14+
│ ├── bpf_test.go # 测试框架核心逻辑以及辅助函数
15+
│ ├── trf.pb.go # 由 trf.proto 生成的 Go 代码
16+
│ ├── trf.proto # 测试结果格式定义
17+
│ ├── general_test.go # bpf/kmesh/general相关测试用例以及辅助函数
18+
│ └── workload_test.go # bpf/kmesh/workload相关测试用例以及辅助函数
19+
├── include/ # 测试相关头文件
20+
│ ├── ut_common.h # 通用测试宏和函数
21+
│ ├── xdp_common.h # XDP 测试相关函数和宏定义
22+
│ └── tc_common.h # TC 测试相关函数和宏定义
23+
├── Makefile # 构建和运行测试的脚本
24+
└── *_test.c # 测试文件,如 xdp_authz_offload_test.c
25+
```
26+
27+
# 3. 核心组件
28+
29+
## 3.1 Go 语言实现的单元测试框架 (`bpftest/`)
30+
31+
Go 语言实现的单元测试框架负责加载和执行 eBPF 程序,捕获测试结果并生成报告:
32+
33+
- `bpf_test.go`:框架的核心组件,包含两种主要测试类型:
34+
- **`unitTest_BPF_PROG_TEST_RUN`**:基于 `BPF_PROG_TEST_RUN` 机制,适用于 [BPF_PROG_TEST_RUN 文档](https://docs.ebpf.io/linux/syscall/BPF_PROG_TEST_RUN/)列出的支持测试 eBPF 程序类型。
35+
- **`unitTest_BUILD_CONTEXT`**:针对 `BPF_PROG_TEST_RUN` 不支持的程序类型,需要在 Go 代码中构造自定义上下文,通过 `cilium/ebpf` 加载、执行并验证 eBPF 程序的行为。
36+
- **`unitTests_*`**:针对每一个 `*_test.c` 测试文件,维护多个具体的测试项。
37+
- `trf.proto`:定义测试结果和测试过程日志格式的 Protocol Buffers 文件。
38+
39+
## 3.2 测试头文件 (`include/`)
40+
41+
- `ut_common.h`:提供测试宏和辅助函数,支持测试初始化、断言、日志记录等功能。
42+
- `xdp_common.h`:提供 XDP 程序测试相关的辅助函数,如构建和验证 XDP 数据包。
43+
- `tc_common.h`:提供 TC 程序测试相关的辅助函数,如构建和验证 TC 数据包。
44+
45+
## 3.3 测试文件 (`*_test.c`)
46+
47+
- 测试文件的核心在于引入单元测试相关的头文件(如 `ut_common.h``xdp_common.h``tc_common.h`);mock 需要 mock 的下游函数;直接 include 需要测试的 eBPF 程序;编写内核态的测试逻辑。
48+
- 对于 `unittest_BPF_PROG_TEST_RUN` 类型的测试,一个测试项通常包含以下部分:
49+
- mock:mock 需要 mock 的下游函数。
50+
- include:直接 include 需要测试的 eBPF 程序。
51+
- tail_call:使用 `tail_call` 机制调用被测试的 eBPF 程序。
52+
- PKTGEN:生成测试数据包的函数,使用 `PKTGEN` 宏定义。
53+
- JUMP:调用被测试的 eBPF 程序的函数,使用 `JUMP` 宏定义。
54+
- CHECK:验证测试结果的函数,使用 `CHECK` 宏定义。
55+
- 对于 `unitTest_BUILD_CONTEXT` 类型的测试,测试项通常包含以下部分:
56+
- mock:mock 需要 mock 的下游函数。
57+
- include:直接 include 需要测试的 eBPF 程序。
58+
59+
## 3.4 Makefile
60+
61+
`Makefile` 负责使用 clang 编译 `*_test.c` 文件并生成相应的 `*_test.o` 文件,随后在执行时通过 Go 测试框架加载这些对象文件来进行测试。
62+
63+
# 4. 测试框架工作原理
64+
65+
## 4.1 `unitTest_BPF_PROG_TEST_RUN`
66+
67+
该测试类型基于内核提供的 `BPF_PROG_TEST_RUN` 机制,用于直接验证支持 `BPF_PROG_TEST_RUN` 的 eBPF 程序。通过内核态快速调用,可方便地测试针对 XDP、TC 等常见程序类型的逻辑。
68+
对应的用户态 Go 测试代码会加载生成的 `.o` 文件,遍历被测程序并执行测试。对于每个测试项,还可在 `setupInUserSpace` 函数中做额外的初始化(如设置全局配置或更新 eBPF Map)。
69+
70+
## 4.2 `unitTest_BUILD_CONTEXT`
71+
72+
当需要测试不支持 `BPF_PROG_TEST_RUN` 的 eBPF 程序类型时,可使用 `unitTest_BUILD_CONTEXT`。这种测试模式需要在用户态自行构造上下文并载入 eBPF 对象文件,完成特殊场景下的验证。
73+
对应的用户态 Go 测试代码在 `workFunc` 中执行实际测试逻辑,如挂载 cgroup、加载并附加 sockops 程序,然后通过各种方式(如向 TCP 服务器发起连接)触发 eBPF 程序运行并进行验证。
74+
75+
## 4.3 用户态 Go 测试代码
76+
77+
无论采用哪种测试类型,都会在用户态利用 Go 测试框架对编译得到的 eBPF 程序进行加载、执行与结果校验。`bpf_test.go` 中定义了各类工具函数,如:
78+
- `loadAndRunSpec`:载入并初始化 `.o` 文件中的程序、Map。
79+
- `startLogReader`:读取 eBPF Map 中的 ringbuf 或日志输出。
80+
- `registerTailCall`:为特定测试场景注册 tail call。
81+
这样可以结合内核测试程序与用户态测试逻辑,为 eBPF 程序提供更完善的验证能力。
82+
83+
# 5. 编写测试
84+
85+
## 5.1 `unitTest_BPF_PROG_TEST_RUN`
86+
87+
对于使用 `BPF_PROG_TEST_RUN` 机制的 eBPF 程序,测试文件通常包含 mock、include、tail_call、PKTGEN、JUMP、CHECK 等部分。
88+
89+
eBPF 测试文件通常遵循以下结构:
90+
91+
```c
92+
// sample_test.c
93+
#include "ut_common.h"
94+
#include "xdp_common.h"
95+
96+
// 1. 定义必要的 eBPF 映射和常量
97+
98+
// 2. 实现 PKTGEN 函数(生成测试数据包)
99+
PKTGEN("program_type", "test_name")
100+
int test_pktgen(struct xdp_md *ctx)
101+
{
102+
// 设置测试数据
103+
return build_xdp_packet(...);
104+
}
105+
106+
// 3. 实现 JUMP 函数(调用被测试的 eBPF 程序)
107+
JUMP("program_type", "test_name")
108+
int test_jump(struct xdp_md *ctx)
109+
{
110+
// 调用被测试的 eBPF 程序
111+
bpf_tail_call(...);
112+
return TEST_ERROR;
113+
}
114+
115+
// 4. 实现 CHECK 函数(验证测试结果)
116+
CHECK("program_type", "test_name")
117+
int test_check(const struct xdp_md *ctx)
118+
{
119+
// 验证测试结果
120+
test_init();
121+
check_xdp_packet(...);
122+
test_finish();
123+
}
124+
```
125+
126+
用户态 Go 代码需要在 `bpf_test.go` 中实现对应的测试逻辑。以下是一个简单的示例:
127+
128+
```go
129+
func test(t *testing.T) {
130+
tests := []unitTests_BPF_PROG_TEST_RUN{
131+
{
132+
objFilename: "sample_test.o",
133+
uts: []unitTest_BPF_PROG_TEST_RUN{
134+
{
135+
name: "test_name", // 需要与 C 代码中的测试名称一致
136+
setupInUserSpace: func(t *testing.T, coll *ebpf.Collection) {},
137+
},
138+
},
139+
},
140+
}
141+
142+
for _, tt := range tests {
143+
t.Run(tt.objFilename, tt.run())
144+
}
145+
}
146+
```
147+
148+
## 5.2 `unitTest_BUILD_CONTEXT`
149+
150+
当需要测试不支持 `BPF_PROG_TEST_RUN` 的 eBPF 程序时,可使用 `unitTest_BUILD_CONTEXT`。在这种模式下,用户态测试会主动在 `workFunc` 中挂载 cgroup 并加载 eBPF 对象进行验证。例如:
151+
152+
```c
153+
// workload_sockops_test.c
154+
#include <linux/bpf.h>
155+
#include <bpf/bpf_helpers.h>
156+
#include "bpf_log.h"
157+
#include "bpf_common.h"
158+
159+
// mock bpf_sk_storage_get
160+
struct sock_storage_data mock_storage = {
161+
.via_waypoint = 1,
162+
};
163+
164+
static void *mock_bpf_sk_storage_get(void *map, void *sk, void *value, __u64 flags)
165+
{
166+
void *storage = NULL;
167+
storage = bpf_sk_storage_get(map, sk, value, flags);
168+
if (!storage && map == &map_of_sock_storage) {
169+
storage = &mock_storage;
170+
}
171+
return storage;
172+
}
173+
174+
#define bpf_sk_storage_get mock_bpf_sk_storage_get
175+
176+
// 直接 include 需要测试的 eBPF 程序
177+
#include "workload/sockops.c"
178+
```
179+
180+
对应的 Go 测试逻辑可能在 `workload_test.go` 里,通过附加到 cgroup 并建立 TCP 连接来触发 sockops:
181+
182+
```go
183+
func TestWorkloadSockOps(t *testing.T) {
184+
tests := []unitTests_BUILD_CONTEXT{
185+
{
186+
objFilename: "workload_sockops_test.o",
187+
uts: []unitTest_BUILD_CONTEXT{
188+
{
189+
name: "sample_test",
190+
workFunc: func(t *testing.T, cgroupPath, objFilePath string) {
191+
// 加载 ebpf 内核态程序
192+
coll, lk := load_bpf_2_cgroup(t, objFilePath, cgroupPath)
193+
defer coll.Close()
194+
defer lk.Close()
195+
196+
// 触发连接操作,检查 bpf_map 中记录结果
197+
},
198+
},
199+
},
200+
},
201+
}
202+
for _, tt := range tests {
203+
t.Run(tt.objFilename, tt.run())
204+
}
205+
}
206+
```
207+
208+
## 5.3 测试宏
209+
210+
框架提供了多种测试宏简化测试编写:
211+
212+
- `test_log(fmt, ...)`:记录测试日志。
213+
- `assert(cond)`:断言条件为真,否则测试失败。
214+
- `test_fail() / test_fail_now()`:标记测试失败。
215+
- `test_skip() / test_skip_now()`:跳过当前测试。
216+
217+
## 5.4 测试辅助宏
218+
219+
对于 XDP 程序测试,框架提供了专门的辅助宏:
220+
221+
- `build_xdp_packet`:构建 XDP 测试数据包。
222+
- `check_xdp_packet`:验证 XDP 数据包处理结果。
223+
224+
对于 TC 程序测试,框架提供了专门的辅助宏:
225+
226+
- `build_tc_packet`:构建 TC 测试数据包。
227+
- `check_tc_packet`:验证 TC 数据包处理结果。
228+
229+
# 6. 运行测试
230+
231+
测试可以通过 `Makefile` 中定义的命令运行:
232+
233+
```bash
234+
cd kmesh
235+
make ebpf_unit_test
236+
```
237+
238+
可以使用以下参数控制测试执行:
239+
240+
- `V=1`:启用详细测试输出

test/unittest/workload/Makefile

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)