Skip to content

Commit 39e4112

Browse files
r41k0uclaude
andcommitted
Tests: Port three kernel BPF selftests
Ports of programs from tools/testing/selftests/bpf/progs/ in the Linux tree, each naming its upstream original in a header comment. tracing/tracepoint_sched_switch.py test_tracepoint.c tracing/get_cgroup_id.py get_cgroup_id_kern.c tracing/autoattach.py test_autoattach.c Deliberately a small spike rather than bulk coverage. The three sample different global-variable shapes, since substituting for globals is what porting the rest of the corpus will mostly consist of: none at all (the control case), a scalar read plus a scalar write, and two flags written from two programs on two different attach points. They also widen the range of program types under test. tracepoint/sched/sched_switch and raw_tp/sys_enter were previously unexercised; @section writes its string straight into the ELF with no allowlist, so that pass-through had only ever been tested against a handful of types. Only the BPF half of each selftest is ported -- upstream pairs every program with a userspace driver in prog_tests/ that loads and asserts, whereas this framework compiles and verifies but never runs. tests/README.md now says so explicitly, along with the WORKAROUND(globals) convention marking each map substituted for a global. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 463622b commit 39e4112

4 files changed

Lines changed: 159 additions & 5 deletions

File tree

tests/README.md

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,48 @@ All xfails use `strict = True`: if a test starts **passing** it shows up as **XP
9191
## Kernel selftest equivalents
9292

9393
`tests/kernel_selftest_equivalent/` contains PythonBPF versions of important
94-
kernel BPF selftests from `bpf-next/tools/testing/selftests/bpf`. These tests
95-
describe features PythonBPF should grow next. They are collected by default and
96-
must be listed as strict expected failures in `tests/test_config.toml` until the
97-
corresponding feature lands.
94+
kernel BPF selftests from `bpf-next/tools/testing/selftests/bpf`. Each file names
95+
its upstream original in a header comment.
96+
97+
The directory holds two kinds of test, and both are useful:
98+
99+
- **Ports that pass.** A program PythonBPF can already express. These widen the
100+
range of program types under test — `raw_tp`, `perf_event`,
101+
`tracepoint/sched/*` and others that nothing else exercises.
102+
- **Roadmap tests that fail.** A program describing a feature PythonBPF should
103+
grow next. These must be listed as **strict** expected failures in
104+
`tests/test_config.toml` until the feature lands, at which point they turn up
105+
as XPASS and should be promoted.
106+
107+
### What a passing port proves — and does not
108+
109+
A kernel selftest is two halves: the BPF program under `progs/`, and a userspace
110+
driver under `prog_tests/` that loads it through a skeleton, triggers it and
111+
asserts on the result. **Only the BPF half is ported**, because this framework
112+
compiles and verifies programs but never runs them.
113+
114+
So a passing test here says PythonBPF emits a loadable, verifiable object for
115+
that program type and feature mix. It does not say the program behaves the way
116+
the kernel's version does. Treat it as a compiler assertion, not a semantic one.
117+
118+
### `WORKAROUND(globals)`
119+
120+
The selftest corpus overwhelmingly reports results through global variables: the
121+
program writes a global and the driver reads it back. PythonBPF has no global
122+
variable support, so each becomes a one-entry `HashMap` keyed by index, tagged in
123+
a comment naming the variable it replaces:
124+
125+
```bash
126+
grep -rn "WORKAROUND(globals)" tests/kernel_selftest_equivalent/
127+
```
128+
129+
This is deliberate scaffolding, not the intended shape — the tag exists so the
130+
sweep is mechanical once real globals land. It is not a cosmetic substitution
131+
either: it changes what a future userspace driver would read.
132+
133+
Anything importing from `vmlinux` belongs in `vmlinux/`, which is registered in
134+
`VMLINUX_TEST_DIRS_PASSING` so it is skipped rather than failed where no
135+
`vmlinux.py` has been generated.
98136

99137
## Directory structure
100138

@@ -113,5 +151,5 @@ tests/
113151
│ └── verifier.py ← bpftool subprocess wrapper
114152
├── passing_tests/ ← programs that should compile and verify cleanly
115153
├── failing_tests/ ← programs with known issues (declared in test_config.toml)
116-
└── kernel_selftest_equivalent/ ← kernel-selftest-inspired feature roadmap tests
154+
└── kernel_selftest_equivalent/ ← ports of kernel selftests + feature roadmap tests
117155
```
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Ported from Linux tools/testing/selftests/bpf/progs/test_autoattach.c
2+
#
3+
# Two programs on different raw tracepoints, each recording that it ran. The
4+
# upstream test asserts both fired after bpf_object__attach_skeleton().
5+
#
6+
# WORKAROUND(globals): upstream uses `bool prog1_called` / `bool prog2_called`.
7+
# PythonBPF has no global variable support yet, so both live in one HashMap
8+
# keyed by program number. Replace with real globals once they land.
9+
10+
from pythonbpf import bpf, map, section, bpfglobal, compile
11+
from pythonbpf.maps import HashMap
12+
from ctypes import c_void_p, c_int64, c_int32, c_uint64
13+
14+
15+
# WORKAROUND(globals): key 1 -> prog1_called, key 2 -> prog2_called
16+
@bpf
17+
@map
18+
def called() -> HashMap:
19+
return HashMap(key=c_int32, value=c_uint64, max_entries=2)
20+
21+
22+
@bpf
23+
@section("raw_tp/sys_enter")
24+
def prog1(ctx: c_void_p) -> c_int64:
25+
called.update(1, 1)
26+
return c_int64(0)
27+
28+
29+
@bpf
30+
@section("raw_tp/sys_exit")
31+
def prog2(ctx: c_void_p) -> c_int64:
32+
called.update(2, 1)
33+
return c_int64(0)
34+
35+
36+
@bpf
37+
@bpfglobal
38+
def LICENSE() -> str:
39+
return "GPL"
40+
41+
42+
compile()
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Ported from Linux tools/testing/selftests/bpf/progs/get_cgroup_id_kern.c
2+
#
3+
# Upstream records the cgroup id of a process whose pid matches one the
4+
# userspace half of the test set beforehand.
5+
#
6+
# WORKAROUND(globals): upstream uses the file-scope variables `cg_id` and
7+
# `expected_pid` to pass values in and out. PythonBPF has no global variable
8+
# support yet, so each becomes a one-entry HashMap keyed by 0. Replace these
9+
# with real globals once they land; grep for WORKAROUND(globals).
10+
11+
from pythonbpf import bpf, map, section, bpfglobal, compile
12+
from pythonbpf.maps import HashMap
13+
from pythonbpf.helper import pid, get_current_cgroup_id
14+
from ctypes import c_void_p, c_int64, c_int32, c_uint64
15+
16+
17+
# WORKAROUND(globals): stands in for `__u64 expected_pid;`
18+
@bpf
19+
@map
20+
def expected_pid() -> HashMap:
21+
return HashMap(key=c_int32, value=c_uint64, max_entries=1)
22+
23+
24+
# WORKAROUND(globals): stands in for `__u64 cg_id;`
25+
@bpf
26+
@map
27+
def cg_id() -> HashMap:
28+
return HashMap(key=c_int32, value=c_uint64, max_entries=1)
29+
30+
31+
@bpf
32+
@section("tracepoint/syscalls/sys_enter_nanosleep")
33+
def trace(ctx: c_void_p) -> c_int64:
34+
process_id = pid()
35+
want = expected_pid.lookup(0)
36+
if want == process_id:
37+
cg_id.update(0, get_current_cgroup_id())
38+
return c_int64(0)
39+
40+
41+
@bpf
42+
@bpfglobal
43+
def LICENSE() -> str:
44+
return "GPL"
45+
46+
47+
compile()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Ported from Linux tools/testing/selftests/bpf/progs/test_tracepoint.c
2+
#
3+
# Upstream is a bare handler on sched/sched_switch, used to prove the program
4+
# attaches to a non-syscall tracepoint. Kept faithful: the point is the
5+
# attachment surface, not the body.
6+
#
7+
# Upstream declares the tracepoint argument layout as a struct taken from
8+
# /sys/kernel/tracing/events/sched/sched_switch/format. PythonBPF does not read
9+
# tracepoint formats, so the context stays opaque.
10+
11+
from pythonbpf import bpf, section, bpfglobal, compile
12+
from ctypes import c_void_p, c_int64
13+
14+
15+
@bpf
16+
@section("tracepoint/sched/sched_switch")
17+
def oncpu(ctx: c_void_p) -> c_int64:
18+
return c_int64(0)
19+
20+
21+
@bpf
22+
@bpfglobal
23+
def LICENSE() -> str:
24+
return "GPL"
25+
26+
27+
compile()

0 commit comments

Comments
 (0)