diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88c3fa0..e41f584 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,25 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} + benchmarks: + name: Run benchmarks + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + cache: "pip" # caching pip dependencies + - uses: snok/install-poetry@v1.3.4 + - name: Install Dependencies + run: poetry install + shell: bash + - name: Run benchmarks + uses: CodSpeedHQ/action@v3 + with: + token: ${{ secrets.CODSPEED_TOKEN }} + run: poetry run pytest tests/ --codspeed + release: needs: - test diff --git a/poetry.lock b/poetry.lock index c9b2931..82c2fd8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "aioesphomeapi" @@ -1229,6 +1229,36 @@ pluggy = ">=0.12,<2.0" [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-codspeed" +version = "3.1.0" +description = "Pytest plugin to create CodSpeed benchmarks" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest_codspeed-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cb7c16e5a64cb30bad30f5204c7690f3cbc9ae5b9839ce187ef1727aa5d2d9c"}, + {file = "pytest_codspeed-3.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d23910893c22ceef6efbdf85d80e803b7fb4a231c9e7676ab08f5ddfc228438"}, + {file = "pytest_codspeed-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb1495a633a33e15268a1f97d91a4809c868de06319db50cf97b4e9fa426372c"}, + {file = "pytest_codspeed-3.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd8a54b99207bd25a4c3f64d9a83ac0f3def91cdd87204ca70a49f822ba919c"}, + {file = "pytest_codspeed-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4d1ac896ebaea5b365e69b41319b4d09b57dab85ec6234f6ff26116b3795f03"}, + {file = "pytest_codspeed-3.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f0c1857a0a6cce6a23c49f98c588c2eef66db353c76ecbb2fb65c1a2b33a8d5"}, + {file = "pytest_codspeed-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4731a7cf1d8d38f58140d51faa69b7c1401234c59d9759a2507df570c805b11"}, + {file = "pytest_codspeed-3.1.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f2e4b63260f65493b8d42c8167f831b8ed90788f81eb4eb95a103ee6aa4294"}, + {file = "pytest_codspeed-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db44099b3f1ec1c9c41f0267c4d57d94e31667f4cb3fb4b71901561e8ab8bc98"}, + {file = "pytest_codspeed-3.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a533c1ad3cc60f07be432864c83d1769ce2877753ac778e1bfc5a9821f5c6ddf"}, + {file = "pytest_codspeed-3.1.0.tar.gz", hash = "sha256:f29641d27b4ded133b1058a4c859e510a2612ad4217ef9a839ba61750abd2f8a"}, +] + +[package.dependencies] +cffi = ">=1.17.1" +pytest = ">=3.8" +rich = ">=13.8.1" + +[package.extras] +compat = ["pytest-benchmark (>=5.0.0,<5.1.0)", "pytest-xdist (>=3.6.1,<3.7.0)"] +lint = ["mypy (>=1.11.2,<1.12.0)", "ruff (>=0.6.5,<0.7.0)"] +test = ["pytest (>=7.0,<8.0)", "pytest-cov (>=4.0.0,<4.1.0)"] + [[package]] name = "pytest-cov" version = "6.0.0" @@ -1328,6 +1358,24 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rich" +version = "13.9.4" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "six" version = "1.16.0" @@ -1919,4 +1967,4 @@ ifaddr = ">=0.1.7" [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.14" -content-hash = "aa07a400287b4c7680043fd6169ae078bdc10cde58ff43ca9dadda89213eb391" +content-hash = "f17d34d0bff842bccd4192ccb42da49bdc8bd87306c43f40a385ffafa8922f21" diff --git a/pyproject.toml b/pyproject.toml index b67c883..86d3681 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ lru-dict = ">=1.2.0" [tool.poetry.group.dev.dependencies] pytest = "^7.0" pytest-cov = ">=3,<7" +pytest-codspeed = "^3.1.0" [tool.poetry.group.docs] optional = true diff --git a/tests/backend/test_scanner.py b/tests/backend/test_scanner.py index 79d94e5..cb2aa05 100644 --- a/tests/backend/test_scanner.py +++ b/tests/backend/test_scanner.py @@ -1,7 +1,9 @@ -from habluetooth import ( - BaseHaRemoteScanner, - HaBluetoothConnector, +from aioesphomeapi import ( + BluetoothLERawAdvertisement, + BluetoothLERawAdvertisementsResponse, ) +from habluetooth import BaseHaRemoteScanner, HaBluetoothConnector +from pytest_codspeed import BenchmarkFixture from bleak_esphome.backend.client import ESPHomeClientData from bleak_esphome.backend.scanner import ESPHomeScanner @@ -10,7 +12,33 @@ ESP_NAME = "proxy" -def test_scanner(): +def test_scanner() -> None: connector = HaBluetoothConnector(ESPHomeClientData, ESP_MAC_ADDRESS, lambda: True) scanner = ESPHomeScanner(ESP_MAC_ADDRESS, ESP_NAME, connector, True) assert isinstance(scanner, BaseHaRemoteScanner) + + +def test_scanner_async_on_advertisement(benchmark: BenchmarkFixture) -> None: + connector = HaBluetoothConnector(ESPHomeClientData, ESP_MAC_ADDRESS, lambda: True) + scanner = ESPHomeScanner(ESP_MAC_ADDRESS, ESP_NAME, connector, True) + adv = BluetoothLERawAdvertisementsResponse( + advertisements=[ + BluetoothLERawAdvertisement( + address=261602360644300, + rssi=-96, + address_type=1, + data=b"\x02\x01\x04\x03\x03\x07\xfe\x18\xff\x97\x05\x06\x00\x16p%\x00\xca\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x02\n\x00", + ), + BluetoothLERawAdvertisement( + address=246965243285491, + rssi=-88, + address_type=1, + data=b"\x02\x01\x1a\x1b\xffu\x00B\x04\x01\x01o\xe0\x8d\x17\xe7\x0f\xf3\xe2\x8d\x17\xe7\x0f\xf2(\x00\x00\x00\x00\x00\x00", + ), + ] + ) + + @benchmark + def _benchmark(): + for _ in range(1000): + scanner.async_on_raw_advertisements(adv) diff --git a/tests/backend/test_scanner_benchmarks.py b/tests/backend/test_scanner_benchmarks.py new file mode 100644 index 0000000..c4cd270 --- /dev/null +++ b/tests/backend/test_scanner_benchmarks.py @@ -0,0 +1,42 @@ +from aioesphomeapi import ( + BluetoothLERawAdvertisement, + BluetoothLERawAdvertisementsResponse, +) +from habluetooth import ( + BaseHaRemoteScanner, + HaBluetoothConnector, +) + +from bleak_esphome.backend.client import ESPHomeClientData +from bleak_esphome.backend.scanner import ESPHomeScanner + +ESP_MAC_ADDRESS = "AA:BB:CC:DD:EE:FF" +ESP_NAME = "proxy" + + +def test_scanner() -> None: + connector = HaBluetoothConnector(ESPHomeClientData, ESP_MAC_ADDRESS, lambda: True) + scanner = ESPHomeScanner(ESP_MAC_ADDRESS, ESP_NAME, connector, True) + assert isinstance(scanner, BaseHaRemoteScanner) + + +def test_scanner_async_on_advertisement() -> None: + connector = HaBluetoothConnector(ESPHomeClientData, ESP_MAC_ADDRESS, lambda: True) + scanner = ESPHomeScanner(ESP_MAC_ADDRESS, ESP_NAME, connector, True) + adv = BluetoothLERawAdvertisementsResponse( + advertisements=[ + BluetoothLERawAdvertisement( + address=261602360644300, + rssi=-96, + address_type=1, + data=b"\x02\x01\x04\x03\x03\x07\xfe\x18\xff\x97\x05\x06\x00\x16p%\x00\xca\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x02\n\x00", + ), + BluetoothLERawAdvertisement( + address=246965243285491, + rssi=-88, + address_type=1, + data=b"\x02\x01\x1a\x1b\xffu\x00B\x04\x01\x01o\xe0\x8d\x17\xe7\x0f\xf3\xe2\x8d\x17\xe7\x0f\xf2(\x00\x00\x00\x00\x00\x00", + ), + ] + ) + scanner.async_on_raw_advertisements(adv)