mirror of
https://github.com/varun-r-mallya/Python-BPF.git
synced 2026-03-31 17:41:27 +00:00
Tests: Add automated testing framework with coverage support
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
0
tests/framework/__init__.py
Normal file
0
tests/framework/__init__.py
Normal file
17
tests/framework/bpf_test_case.py
Normal file
17
tests/framework/bpf_test_case.py
Normal file
@ -0,0 +1,17 @@
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass
|
||||
class BpfTestCase:
|
||||
path: Path
|
||||
rel_path: str
|
||||
is_expected_fail: bool = False
|
||||
xfail_reason: str = ""
|
||||
xfail_level: str = "ir" # "ir" or "llc"
|
||||
needs_vmlinux: bool = False
|
||||
skip_reason: str = ""
|
||||
|
||||
@property
|
||||
def test_id(self) -> str:
|
||||
return self.rel_path.replace("/", "::")
|
||||
60
tests/framework/collector.py
Normal file
60
tests/framework/collector.py
Normal file
@ -0,0 +1,60 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
import tomli as tomllib
|
||||
|
||||
from .bpf_test_case import BpfTestCase
|
||||
|
||||
TESTS_DIR = Path(__file__).parent.parent
|
||||
CONFIG_FILE = TESTS_DIR / "test_config.toml"
|
||||
|
||||
VMLINUX_TEST_DIRS = {"passing_tests/vmlinux"}
|
||||
VMLINUX_TEST_PREFIXES = {
|
||||
"failing_tests/vmlinux",
|
||||
"failing_tests/xdp",
|
||||
}
|
||||
|
||||
|
||||
def _is_vmlinux_test(rel_path: str) -> bool:
|
||||
for prefix in VMLINUX_TEST_DIRS | VMLINUX_TEST_PREFIXES:
|
||||
if rel_path.startswith(prefix):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _load_config() -> dict:
|
||||
if not CONFIG_FILE.exists():
|
||||
return {}
|
||||
with open(CONFIG_FILE, "rb") as f:
|
||||
return tomllib.load(f)
|
||||
|
||||
|
||||
def collect_all_test_files() -> list[BpfTestCase]:
|
||||
config = _load_config()
|
||||
xfail_map: dict = config.get("xfail", {})
|
||||
|
||||
cases = []
|
||||
for subdir in ("passing_tests", "failing_tests"):
|
||||
for py_file in sorted((TESTS_DIR / subdir).rglob("*.py")):
|
||||
rel = str(py_file.relative_to(TESTS_DIR))
|
||||
needs_vmlinux = _is_vmlinux_test(rel)
|
||||
|
||||
xfail_entry = xfail_map.get(rel)
|
||||
is_expected_fail = xfail_entry is not None
|
||||
xfail_reason = xfail_entry.get("reason", "") if xfail_entry else ""
|
||||
xfail_level = xfail_entry.get("level", "ir") if xfail_entry else "ir"
|
||||
|
||||
cases.append(
|
||||
BpfTestCase(
|
||||
path=py_file,
|
||||
rel_path=rel,
|
||||
is_expected_fail=is_expected_fail,
|
||||
xfail_reason=xfail_reason,
|
||||
xfail_level=xfail_level,
|
||||
needs_vmlinux=needs_vmlinux,
|
||||
)
|
||||
)
|
||||
return cases
|
||||
23
tests/framework/compiler.py
Normal file
23
tests/framework/compiler.py
Normal file
@ -0,0 +1,23 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from pythonbpf.codegen import compile_to_ir, _run_llc
|
||||
|
||||
|
||||
def run_ir_generation(test_path: Path, output_ll: Path):
|
||||
"""Run compile_to_ir on a BPF test file.
|
||||
|
||||
Returns the (output, structs_sym_tab, maps_sym_tab) tuple from compile_to_ir.
|
||||
Raises on exception. Any logging.ERROR records captured by pytest caplog
|
||||
indicate a compile failure even when no exception is raised.
|
||||
"""
|
||||
return compile_to_ir(str(test_path), str(output_ll), loglevel=logging.WARNING)
|
||||
|
||||
|
||||
def run_llc(ll_path: Path, obj_path: Path) -> bool:
|
||||
"""Compile a .ll file to a BPF .o using llc.
|
||||
|
||||
Raises subprocess.CalledProcessError on failure (llc uses check=True).
|
||||
Returns True on success.
|
||||
"""
|
||||
return _run_llc(str(ll_path), str(obj_path))
|
||||
25
tests/framework/verifier.py
Normal file
25
tests/framework/verifier.py
Normal file
@ -0,0 +1,25 @@
|
||||
import subprocess
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def verify_object(obj_path: Path) -> tuple[bool, str]:
|
||||
"""Run bpftool prog load -d to verify a BPF object file against the kernel verifier.
|
||||
|
||||
Pins the program temporarily at /sys/fs/bpf/bpf_prog_test_<uuid>, then removes it.
|
||||
Returns (success, combined_output). Requires sudo / root.
|
||||
"""
|
||||
pin_path = f"/sys/fs/bpf/bpf_prog_test_{uuid.uuid4().hex[:8]}"
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["sudo", "bpftool", "prog", "load", "-d", str(obj_path), pin_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
output = result.stdout + result.stderr
|
||||
return result.returncode == 0, output
|
||||
except subprocess.TimeoutExpired:
|
||||
return False, "bpftool timed out after 30s"
|
||||
finally:
|
||||
subprocess.run(["sudo", "rm", "-f", pin_path], check=False, capture_output=True)
|
||||
Reference in New Issue
Block a user