diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 537a1c18..4c1ba3b9 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -34,3 +34,27 @@ jobs: with: mode: instrumentation run: uv run pytest tests/test_benchmark.py --codspeed + + memory-benchmarks: + name: Run memory benchmarks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + python-version: "3.13" + enable-cache: true + + - name: Install dependencies + run: scripts/install + shell: bash + + - name: Run the benchmarks + uses: CodSpeedHQ/action@f99becdce5e5d51fd556489ebef684f4ecfd6286 # v4.18.5 + with: + mode: memory + run: uv run pytest tests/test_benchmark_memory.py --codspeed diff --git a/pyproject.toml b/pyproject.toml index 898503ee..fc432a3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,12 +79,12 @@ filterwarnings = [ markers = [ "copied_from(source, changes=None): mark test as copied from somewhere else, along with a description of changes made to accommodate e.g. our test setup", "network: marks tests which require network connection. Used in 3rd-party build environments that have network disabled.", - "benchmark: marks CodSpeed benchmark tests under tests/test_benchmark.py.", + "benchmark: marks CodSpeed benchmark tests under tests/test_benchmark*.py.", ] [tool.coverage.run] source_pkgs = ["httpx2", "httpcore2", "tests"] -omit = ["src/httpcore2/httpcore2/_sync/*", "tests/test_benchmark.py"] +omit = ["src/httpcore2/httpcore2/_sync/*", "tests/test_benchmark*.py"] [tool.coverage.report] exclude_also = [ diff --git a/tests/test_benchmark_memory.py b/tests/test_benchmark_memory.py new file mode 100644 index 00000000..4beb0cf6 --- /dev/null +++ b/tests/test_benchmark_memory.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import hpack +import hyperframe.frame +import pytest + +import httpcore2 + +if TYPE_CHECKING: + from pytest_codspeed import BenchmarkFixture + +pytestmark = pytest.mark.benchmark + +H2_WINDOW = 2**31 - 1 +H2_BODY = b"x" * (8 * 1024 * 1024) + + +def _h2_server_frames() -> list[bytes]: + return [ + hyperframe.frame.SettingsFrame( + settings={hyperframe.frame.SettingsFrame.INITIAL_WINDOW_SIZE: H2_WINDOW} + ).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=H2_WINDOW - 65535).serialize(), + hyperframe.frame.HeadersFrame( + stream_id=1, + data=hpack.Encoder().encode([(b":status", b"200")]), + flags=["END_HEADERS"], + ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"", flags=["END_STREAM"]).serialize(), + ] + + +def test_bench_http2_send_large_body(benchmark: BenchmarkFixture) -> None: + origin = httpcore2.Origin(b"https", b"example.com", 443) + + def send() -> int: + stream = httpcore2.MockStream(_h2_server_frames()) + with httpcore2.HTTP2Connection(origin=origin, stream=stream) as conn: + response = conn.request("POST", "https://example.com/", content=H2_BODY) + return response.status + + assert benchmark(send) == 200