From 4b33eb331980d61be53f909cd0208e55bea81b4f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 22 Oct 2024 14:58:10 -1000 Subject: [PATCH] Add codspeed benchmarks (#60) --- .github/workflows/ci-cd.yml | 83 +++++++++++++++++++++++++++++++++++++ .mypy.ini | 5 +++ .pre-commit-config.yaml | 4 ++ requirements/test.txt | 1 + tests/test_benchmarks.py | 45 ++++++++++++++++++++ 5 files changed, 138 insertions(+) create mode 100644 tests/test_benchmarks.py diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index c38f0cb..6af58ec 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -343,6 +343,89 @@ jobs: Py-${{ steps.python-install.outputs.python-version }} fail_ci_if_error: false + benchmark: + name: Benchmark + needs: + - build-pure-python-dists # transitive, for accessing settings + - build-wheels-for-tested-arches + - pre-setup # transitive, for accessing settings + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout project + uses: actions/checkout@v4 + - name: Retrieve the project source from an sdist inside the GHA artifact + uses: re-actors/checkout-python-sdist@release/v2 + with: + source-tarball-name: >- + ${{ needs.build-pure-python-dists.outputs.sdist-filename }} + workflow-artifact-name: >- + ${{ needs.pre-setup.outputs.dists-artifact-name }} + - name: Download distributions + uses: actions/download-artifact@v4 + with: + path: dist + pattern: ${{ needs.pre-setup.outputs.dists-artifact-name }}* + merge-multiple: true + + - name: Setup Python 3.12 + id: python-install + uses: actions/setup-python@v5 + with: + python-version: 3.12 + cache: pip + cache-dependency-path: requirements/*.txt + - name: Install dependencies + uses: py-actions/py-dependency-install@v4 + with: + path: requirements/test.txt + - name: Determine pre-compiled compatible wheel + env: + # NOTE: When `pip` is forced to colorize output piped into `jq`, + # NOTE: the latter can't parse it. So we're overriding the color + # NOTE: preference here via https://no-color.org. + # NOTE: Setting `FORCE_COLOR` to any value (including 0, an empty + # NOTE: string, or a "YAML null" `~`) doesn't have any effect and + # NOTE: `pip` (through its verndored copy of `rich`) treats the + # NOTE: presence of the variable as "force-color" regardless. + # + # NOTE: This doesn't actually work either, so we'll resort to unsetting + # NOTE: in the Bash script. + # NOTE: Ref: https://github.com/Textualize/rich/issues/2622 + NO_COLOR: 1 + id: wheel-file + run: > + echo -n path= | tee -a "${GITHUB_OUTPUT}" + + + unset FORCE_COLOR + + + python + -X utf8 + -u -I + -m pip install + --find-links=./dist + --no-index + '${{ env.PROJECT_NAME }}' + --force-reinstall + --no-color + --no-deps + --only-binary=:all: + --dry-run + --report=- + --quiet + | jq --raw-output .install[].download_info.url + | tee -a "${GITHUB_OUTPUT}" + shell: bash + - name: Self-install + run: python -Im pip install '${{ steps.wheel-file.outputs.path }}' + - name: Run benchmarks + uses: CodSpeedHQ/action@v3 + with: + token: ${{ secrets.CODSPEED_TOKEN }} + run: python -Im pytest --no-cov -vvvvv --codspeed + test-summary: name: Test matrix status if: always() diff --git a/.mypy.ini b/.mypy.ini index 5bd5c19..138d0ab 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -30,6 +30,11 @@ warn_no_return = true warn_redundant_casts = true warn_unused_ignores = true +[mypy-test_benchmarks] +disable_error_code = + no-any-unimported, + misc + [mypy-Cython.*] ignore_missing_imports = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 002e243..8f668c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -115,6 +115,7 @@ repos: additional_dependencies: - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` - pytest + - pytest_codspeed - tomli # requirement of packaging/pep517_backend/ - types-setuptools # requirement of packaging/pep517_backend/ args: @@ -129,6 +130,7 @@ repos: additional_dependencies: - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` - pytest + - pytest_codspeed - tomli # requirement of packaging/pep517_backend/ - types-setuptools # requirement of packaging/pep517_backend/ args: @@ -143,6 +145,7 @@ repos: additional_dependencies: - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` - pytest + - pytest_codspeed - tomli # requirement of packaging/pep517_backend/ - types-setuptools # requirement of packaging/pep517_backend/ - types-Pygments @@ -159,6 +162,7 @@ repos: additional_dependencies: - lxml # dep of `--txt-report`, `--cobertura-xml-report` & `--html-report` - pytest + - pytest_codspeed - tomli # requirement of packaging/pep517_backend/ - types-setuptools # requirement of packaging/pep517_backend/ - types-Pygments diff --git a/requirements/test.txt b/requirements/test.txt index 3b916e3..e9b118f 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,5 +1,6 @@ -r cython.txt covdefaults pytest==8.3.3 +pytest-codspeed==2.2.1 pytest-cov>=2.3.1 pytest-xdist diff --git a/tests/test_benchmarks.py b/tests/test_benchmarks.py new file mode 100644 index 0000000..1322a34 --- /dev/null +++ b/tests/test_benchmarks.py @@ -0,0 +1,45 @@ +"""codspeed benchmarks for propcache.""" + +from pytest_codspeed import BenchmarkFixture # type: ignore[import-untyped] + +from propcache import cached_property, under_cached_property + + +def test_under_cached_property_caching(benchmark: BenchmarkFixture) -> None: + """Benchmark for under_cached_property caching.""" + + class Test: + def __init__(self) -> None: + self._cache = {"prop": 42} + + @under_cached_property + def prop(self) -> int: + """Return the value of the property.""" + raise NotImplementedError + + t = Test() + + @benchmark + def _run() -> None: + for _ in range(100): + t.prop + + +def test_cached_property_caching(benchmark: BenchmarkFixture) -> None: + """Benchmark for cached_property caching.""" + + class Test: + def __init__(self) -> None: + self.__dict__["prop"] = 42 + + @cached_property + def prop(self) -> int: + """Return the value of the property.""" + raise NotImplementedError + + t = Test() + + @benchmark + def _run() -> None: + for _ in range(100): + t.prop