From 32e61e93912d8ec3b1f3318e28fa77ed6939d10b Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Tue, 13 Jan 2026 14:47:08 +0100 Subject: [PATCH] Add basic benchmarks --- .github/workflows/dissect-ci.yml | 3 ++ tests/conftest.py | 20 +++++++++++- tests/test_benchmark.py | 56 ++++++++++++++++++++++++++++++++ tox.ini | 12 ++++++- 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 tests/test_benchmark.py diff --git a/.github/workflows/dissect-ci.yml b/.github/workflows/dissect-ci.yml index b7b844ab..b75ca07e 100644 --- a/.github/workflows/dissect-ci.yml +++ b/.github/workflows/dissect-ci.yml @@ -12,6 +12,9 @@ jobs: ci: uses: fox-it/dissect-workflow-templates/.github/workflows/dissect-ci-template.yml@main secrets: inherit + with: + run-benchmarks: true + publish: if: ${{ github.ref_name == 'main' || github.ref_type == 'tag' }} diff --git a/tests/conftest.py b/tests/conftest.py index 60d4f24c..1e4d998d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,31 @@ +from __future__ import annotations + +import importlib.util + import pytest from dissect.cstruct.cstruct import cstruct +HAS_BENCHMARK = importlib.util.find_spec("pytest_benchmark") is not None + + +def pytest_configure(config: pytest.Config) -> None: + if not HAS_BENCHMARK: + # If we don't have pytest-benchmark (or pytest-codspeed) installed, register the benchmark marker ourselves + # to avoid pytest warnings + config.addinivalue_line("markers", "benchmark: mark test for benchmarking (requires pytest-benchmark)") + + +def pytest_runtest_setup(item: pytest.Item) -> None: + if not HAS_BENCHMARK and item.get_closest_marker("benchmark") is not None: + pytest.skip("pytest-benchmark is not installed") + @pytest.fixture def cs() -> cstruct: return cstruct() -@pytest.fixture(params=[True, False]) +@pytest.fixture(params=[True, False], ids=["compiled", "interpreted"]) def compiled(request: pytest.FixtureRequest) -> bool: return request.param diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py new file mode 100644 index 00000000..528a2f1b --- /dev/null +++ b/tests/test_benchmark.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +if TYPE_CHECKING: + from pytest_benchmark.fixture import BenchmarkFixture + + from dissect.cstruct.cstruct import cstruct + + +@pytest.mark.benchmark +def test_benchmark_basic(cs: cstruct, compiled: bool, benchmark: BenchmarkFixture) -> None: + cdef = """ + struct test { + uint32 a; + uint64 b; + uint16 c; + uint8 d; + }; + """ + cs.load(cdef, compiled=compiled) + + benchmark(lambda: cs.test(b"\x01\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x04")) + + +@pytest.mark.benchmark +def test_benchmark_union(cs: cstruct, compiled: bool, benchmark: BenchmarkFixture) -> None: + cdef = """ + union test { + uint32 a; + uint64 b; + uint16 c; + uint8 d; + }; + """ + cs.load(cdef, compiled=compiled) + + benchmark(lambda: cs.test(b"\x01\x02\x03\x04\x05\x06\x07\x08")) + + +@pytest.mark.benchmark +def test_benchmark_attribute_access(cs: cstruct, benchmark: BenchmarkFixture) -> None: + cdef = """ + struct test { + uint32 a; + uint64 b; + uint16 c; + uint8 d; + }; + """ + cs.load(cdef) + obj = cs.test(b"\x01\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x04") + + benchmark(lambda: (obj.a, obj.b, obj.c, obj.d)) diff --git a/tox.ini b/tox.ini index 284a4ba0..39d4d531 100644 --- a/tox.ini +++ b/tox.ini @@ -18,10 +18,20 @@ deps = coverage dependency_groups = test commands = - pytest --basetemp="{envtmpdir}" {posargs:--color=yes --cov=dissect --cov-report=term-missing -v tests} + pytest --basetemp="{envtmpdir}" --import-mode="append" {posargs:--color=yes --cov=dissect --cov-report=term-missing -v tests} coverage report coverage xml +[testenv:benchmark] +deps = + pytest-benchmark + pytest-codspeed +dependency_groups = test +passenv = + CODSPEED_ENV +commands = + pytest --basetemp="{envtmpdir}" --import-mode="append" -m benchmark {posargs:--color=yes -v tests} + [testenv:build] package = skip dependency_groups = build