Skip to content

Commit 5fcec95

Browse files
Add kernel selftest equivalent roadmap tests
1 parent bcca69e commit 5fcec95

9 files changed

Lines changed: 146 additions & 7 deletions

File tree

pythonbpf/maps/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
from .maps import HashMap, PerfEventArray, RingBuffer
1+
from .maps import ArrayMap, HashMap, PerfEventArray, RingBuffer
22
from .maps_pass import maps_proc
33
from .map_types import BPFMapType
44

5-
__all__ = ["HashMap", "PerfEventArray", "maps_proc", "RingBuffer", "BPFMapType"]
5+
__all__ = [
6+
"ArrayMap",
7+
"HashMap",
8+
"PerfEventArray",
9+
"maps_proc",
10+
"RingBuffer",
11+
"BPFMapType",
12+
]

pythonbpf/maps/maps.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,26 @@ def update(self, key, value, flags=None):
2626
raise KeyError(f"Key {key} not found in map")
2727

2828

29+
class ArrayMap:
30+
def __init__(self, key, value, max_entries):
31+
self.key = key
32+
self.value = value
33+
self.max_entries = max_entries
34+
self.entries = {}
35+
36+
def lookup(self, key):
37+
return self.entries.get(key)
38+
39+
def update(self, key, value, flags=None):
40+
self.entries[key] = value
41+
42+
def delete(self, key):
43+
if key in self.entries:
44+
del self.entries[key]
45+
else:
46+
raise KeyError(f"Key {key} not found in map")
47+
48+
2949
class PerfEventArray:
3050
def __init__(self, key_size, value_size):
3151
self.key_type = key_size

pythonbpf/maps/maps_pass.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ def process_hash_map(map_name, rval, compilation_context):
138138
return map_global
139139

140140

141+
@MapProcessorRegistry.register("ArrayMap")
142+
def process_array_map(map_name, rval, compilation_context):
143+
"""Document the planned BPF_ARRAY map support with an explicit failure."""
144+
raise NotImplementedError(
145+
"ArrayMap is not implemented yet; add BPF_MAP_TYPE_ARRAY metadata support"
146+
)
147+
148+
141149
@MapProcessorRegistry.register("PerfEventArray")
142150
def process_perf_event_map(map_name, rval, compilation_context):
143151
"""Process a BPF_PERF_EVENT_ARRAY map declaration"""

tests/README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ All xfails use `strict = True`: if a test starts **passing** it shows up as **XP
8080
2. Run `make test` — the file is discovered and tested automatically at all levels.
8181
3. If the test is expected to fail, add it to `tests/test_config.toml` instead of `passing_tests/`.
8282

83+
## Kernel selftest equivalents
84+
85+
`tests/kernel_selftest_equivalent/` contains PythonBPF versions of important
86+
kernel BPF selftests from `bpf-next/tools/testing/selftests/bpf`. These tests
87+
describe features PythonBPF should grow next. They are collected by default and
88+
must be listed as strict expected failures in `tests/test_config.toml` until the
89+
corresponding feature lands.
90+
8391
## Directory structure
8492

8593
```
@@ -96,5 +104,6 @@ tests/
96104
│ ├── compiler.py ← wrappers around compile_to_ir() + _run_llc()
97105
│ └── verifier.py ← bpftool subprocess wrapper
98106
├── passing_tests/ ← programs that should compile and verify cleanly
99-
└── failing_tests/ ← programs with known issues (declared in test_config.toml)
107+
├── failing_tests/ ← programs with known issues (declared in test_config.toml)
108+
└── kernel_selftest_equivalent/ ← kernel-selftest-inspired feature roadmap tests
100109
```

tests/conftest.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"""
1717

1818
import logging
19+
import warnings
1920

2021
import pytest
2122

@@ -24,11 +25,15 @@
2425
# ── vmlinux availability ────────────────────────────────────────────────────
2526

2627
try:
27-
import vmlinux # noqa: F401
28+
with warnings.catch_warnings():
29+
warnings.simplefilter("ignore", DeprecationWarning)
30+
import vmlinux # noqa: F401
2831

2932
VMLINUX_AVAILABLE = True
30-
except ImportError:
33+
VMLINUX_SKIP_REASON = ""
34+
except Exception as exc:
3135
VMLINUX_AVAILABLE = False
36+
VMLINUX_SKIP_REASON = f"vmlinux.py not usable for current kernel: {exc}"
3237

3338

3439
# ── pytest_generate_tests: parametrize on bpf_test_file ───────────────────
@@ -64,7 +69,10 @@ def pytest_collection_modifyitems(items):
6469
# vmlinux skip
6570
if case.needs_vmlinux and not VMLINUX_AVAILABLE:
6671
item.add_marker(
67-
pytest.mark.skip(reason="vmlinux.py not available for current kernel")
72+
pytest.mark.skip(
73+
reason=VMLINUX_SKIP_REASON
74+
or "vmlinux.py not available for current kernel"
75+
)
6876
)
6977
continue
7078

tests/framework/collector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def collect_all_test_files() -> list[BpfTestCase]:
3333
xfail_map: dict = config.get("xfail", {})
3434

3535
cases = []
36-
for subdir in ("passing_tests", "failing_tests"):
36+
for subdir in ("passing_tests", "failing_tests", "kernel_selftest_equivalent"):
3737
for py_file in sorted((TESTS_DIR / subdir).rglob("*.py")):
3838
rel = str(py_file.relative_to(TESTS_DIR))
3939
needs_vmlinux = _is_vmlinux_test(rel)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Adapted from bpf-next/tools/testing/selftests/bpf/progs/test_map_ops.c
2+
# and bpf-next/tools/testing/selftests/bpf/progs/bpf_iter_bpf_array_map.c.
3+
4+
from ctypes import c_int32, c_uint64, c_void_p
5+
6+
from pythonbpf import bpf, bpfglobal, compile, map, section
7+
from pythonbpf.maps import ArrayMap
8+
9+
10+
@bpf
11+
@map
12+
def counters() -> ArrayMap:
13+
return ArrayMap(key=c_int32, value=c_uint64, max_entries=8)
14+
15+
16+
@bpf
17+
@section("tracepoint/syscalls/sys_enter_getpid")
18+
def array_map_lookup_update(ctx: c_void_p) -> c_int32:
19+
counters.update(0, 1)
20+
21+
current = counters.lookup(0)
22+
if current:
23+
next_value = current + 1
24+
counters.update(0, next_value)
25+
26+
return c_int32(0)
27+
28+
29+
@bpf
30+
@bpfglobal
31+
def LICENSE() -> str:
32+
return "GPL"
33+
34+
35+
compile()
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Adapted from bpf-next/tools/testing/selftests/bpf/progs/test_ringbuf.c.
2+
3+
from ctypes import c_int32, c_uint64, c_void_p
4+
5+
from pythonbpf import bpf, bpfglobal, compile, map, section, struct
6+
from pythonbpf.helper import pid
7+
from pythonbpf.maps import RingBuffer
8+
9+
10+
@bpf
11+
@struct
12+
class sample_t:
13+
pid: c_uint64
14+
seq: c_uint64
15+
value: c_uint64
16+
17+
18+
@bpf
19+
@map
20+
def events() -> RingBuffer:
21+
return RingBuffer(max_entries=4096)
22+
23+
24+
@bpf
25+
@section("tracepoint/syscalls/sys_enter_getpid")
26+
def ringbuf_reserve_submit_discard(ctx: c_void_p) -> c_int32:
27+
first = events.reserve(24)
28+
if first:
29+
sample = sample_t(first)
30+
sample.pid = pid()
31+
sample.seq = 0
32+
sample.value = 7
33+
events.submit(first, 0)
34+
35+
second = events.reserve(24)
36+
if second:
37+
events.discard(second, 0)
38+
39+
return c_int32(0)
40+
41+
42+
@bpf
43+
@bpfglobal
44+
def LICENSE() -> str:
45+
return "GPL"
46+
47+
48+
compile()

tests/test_config.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@
2020
"failing_tests/vmlinux/assignment_handling.py" = {reason = "Assigning vmlinux enum value (XDP_PASS) to a local variable not yet supported", level = "ir"}
2121

2222
"failing_tests/xdp_pass.py" = {reason = "XDP program using vmlinux structs (struct_xdp_md) and complex map/struct interaction not yet supported", level = "ir"}
23+
24+
"kernel_selftest_equivalent/maps/array_map_lookup_update.py" = {reason = "ArrayMap / BPF_MAP_TYPE_ARRAY support is planned but not implemented yet", level = "ir"}
25+
26+
"kernel_selftest_equivalent/ringbuf/reserve_submit_discard.py" = {reason = "RingBuffer reserve/typed record/discard workflow is planned but not implemented yet", level = "ir"}

0 commit comments

Comments
 (0)