From a0ac1b3aba1c6bc9b7b837bd62e4f2b466f73439 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 14 Sep 2020 17:56:42 -0400 Subject: [PATCH 1/7] Bump up zigpy dependency --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 23fc1c64..3798a80a 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ "pure_pcapy3==1.0.1", "pyserial-asyncio", "voluptuous", - "zigpy>=0.20.1a3", + "zigpy>=0.21.0", ], dependency_links=["https://codeload.github.com/rcloran/pure-pcapy-3/zip/master"], tests_require=["asynctest", "pytest", "pytest-asyncio"], From 4b81b35e991a748feda40763305dd2adc4e0baa0 Mon Sep 17 00:00:00 2001 From: Hedda Date: Tue, 15 Sep 2020 14:12:26 +0200 Subject: [PATCH 2/7] Update README.md with EmberZNet and EZSP Protocol Version information (#343) * Update README.md with EmberZNet and EZSP Protocol Version information Update README.md with EmberZNet and EZSP Protocol Version information * Update README.md with summery of what is new in EZSP v8 Update README.md with summery of what is new in EZSP v8 Co-authored-by: Alexei Chetroi Co-authored-by: Alexei Chetroi --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index d02e695f..3e8c57ac 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,14 @@ Silabs did use to provide two main NCP images pre-build with firmware for EM35x, Silicon Labs no longer provide pre-build firmware images, so now you have to build and compile firmware with their Simplicity Studio SDK for EmberZNet PRO Zigbee Protocol Stack Software. Simplicity Studio is a free download but building and compiling EmberZNet PRO Zigbee firmware images required that you have the serialnumber of an official Zigbee devkit registered to your Silicon Labs user account. +### EmberZNet and EZSP Protocol Version + +Silicon Labs do not currently have a consolidated list of changes by EmberZNet SDK or EZSP protocol version. The EZSP additions, changes and deletions have only ever been listed in the "Zigbee EmberZNet Release Notes" (EmberZNet SDK) under the "New items" section as well as the matching UG100 EZSP Reference Guide included with each EmberZNet SDK release. + +The largest change was between EZSP v4 (first added in EmberZNet 4.7.2 SDK) and EZSP v5 that was added in EmberZNet 5.9.0 SDK which requires the Legacy Frame ID 0xFF. The change from EZSP v5 to EZSP v6 was done in EmberZNet 6.0.0 SDK. The change from EZSP v6 to EZSP v7 was in EmberZNet 6.4.0 SDK. EmberZNet 6.7.0 SDK added EZSP v8 (and Secure EZSP Protocol Version 2). + +Perhaps more important to know today is that EZSP v5, v6 and v7 (EmberZNet 6.6.x.x) use the same framing format, but EmberZNet 6.7.x.x/EZSP v8 introduced new framing format and expanded command id field from 8 bits to 16 bits. + ## Project status This project is in early stages, so it is likely that APIs will change. From 0eedd267c30ac471c2392e02575b4ed860c43d36 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 15 Sep 2020 12:44:32 -0400 Subject: [PATCH 3/7] Update isort version (#349) * Update isort version * Sort import correctly --- .pre-commit-config.yaml | 4 ++-- bellows/cli/application.py | 9 +++++---- bellows/cli/backup.py | 3 ++- bellows/cli/ncp.py | 3 ++- bellows/cli/network.py | 3 ++- bellows/cli/util.py | 5 +++-- bellows/ezsp/__init__.py | 5 +++-- bellows/ezsp/v4/__init__.py | 3 ++- bellows/ezsp/v4/config.py | 3 ++- bellows/ezsp/v5/__init__.py | 3 ++- bellows/ezsp/v5/config.py | 3 ++- bellows/ezsp/v6/__init__.py | 3 ++- bellows/ezsp/v6/config.py | 3 ++- bellows/ezsp/v7/__init__.py | 3 ++- bellows/ezsp/v7/config.py | 3 ++- bellows/ezsp/v8/__init__.py | 3 ++- bellows/ezsp/v8/config.py | 3 ++- bellows/uart.py | 5 +++-- bellows/zigbee/application.py | 17 +++++++++-------- bellows/zigbee/util.py | 3 ++- setup.cfg | 16 ++-------------- setup.py | 3 ++- tests/test_application.py | 9 +++++---- tests/test_commands.py | 3 ++- tests/test_ezsp.py | 5 +++-- tests/test_ezsp_protocol.py | 3 ++- tests/test_ezsp_v4.py | 3 ++- tests/test_ezsp_v5.py | 3 ++- tests/test_ezsp_v6.py | 3 ++- tests/test_ezsp_v7.py | 3 ++- tests/test_ezsp_v8.py | 3 ++- tests/test_multicast.py | 5 +++-- tests/test_thread.py | 3 ++- tests/test_types_struct.py | 3 ++- tests/test_uart.py | 5 +++-- tox.ini | 4 ++-- 36 files changed, 91 insertions(+), 70 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1239af49..eec65002 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: rev: 3.8.3 hooks: - id: flake8 - - repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.21 + - repo: https://github.com/PyCQA/isort + rev: 5.5.2 hooks: - id: isort diff --git a/bellows/cli/application.py b/bellows/cli/application.py index 51b63a70..b705c491 100644 --- a/bellows/cli/application.py +++ b/bellows/cli/application.py @@ -3,15 +3,16 @@ import asyncio import binascii -import bellows.config -import bellows.ezsp -import bellows.types as t -import bellows.zigbee.application import click import zigpy.config import zigpy.endpoint import zigpy.exceptions +import bellows.config +import bellows.ezsp +import bellows.types as t +import bellows.zigbee.application + from . import opts, util from .main import main diff --git a/bellows/cli/backup.py b/bellows/cli/backup.py index 72880141..d84d7736 100644 --- a/bellows/cli/backup.py +++ b/bellows/cli/backup.py @@ -3,13 +3,14 @@ import logging import os -import bellows.types as t import click import voluptuous as vol from zigpy.config.validators import cv_hex, cv_key import zigpy.types import zigpy.zdo.types +import bellows.types as t + from . import util from .main import main diff --git a/bellows/cli/ncp.py b/bellows/cli/ncp.py index b9db6377..9cc1509f 100644 --- a/bellows/cli/ncp.py +++ b/bellows/cli/ncp.py @@ -1,6 +1,7 @@ -import bellows.types as t import click +import bellows.types as t + from . import util from .main import main diff --git a/bellows/cli/network.py b/bellows/cli/network.py index 23f6cbcc..da60db63 100644 --- a/bellows/cli/network.py +++ b/bellows/cli/network.py @@ -3,9 +3,10 @@ import logging import math +import click + import bellows.types as t import bellows.zigbee.util as zutil -import click from . import opts, util from .main import main diff --git a/bellows/cli/util.py b/bellows/cli/util.py index 7e8d7c18..85edbbeb 100644 --- a/bellows/cli/util.py +++ b/bellows/cli/util.py @@ -2,11 +2,12 @@ import functools import logging +import click +import zigpy.config as zigpy_conf + import bellows.config as config import bellows.ezsp import bellows.types as t -import click -import zigpy.config as zigpy_conf LOGGER = logging.getLogger(__name__) diff --git a/bellows/ezsp/__init__.py b/bellows/ezsp/__init__.py index 1c4892d5..1f8703b5 100644 --- a/bellows/ezsp/__init__.py +++ b/bellows/ezsp/__init__.py @@ -5,6 +5,9 @@ import logging from typing import Any, Awaitable, Callable, Dict, Tuple +import serial +from zigpy.typing import DeviceType + from bellows.config import ( CONF_DEVICE, CONF_DEVICE_PATH, @@ -14,8 +17,6 @@ from bellows.exception import APIException, EzspError import bellows.types as t import bellows.uart -import serial -from zigpy.typing import DeviceType from . import v4, v5, v6, v7, v8 diff --git a/bellows/ezsp/v4/__init__.py b/bellows/ezsp/v4/__init__.py index afa0a94d..38d76c10 100644 --- a/bellows/ezsp/v4/__init__.py +++ b/bellows/ezsp/v4/__init__.py @@ -2,9 +2,10 @@ import logging from typing import Tuple -import bellows.config import voluptuous +import bellows.config + from . import commands, config, types as v4_types from .. import protocol diff --git a/bellows/ezsp/v4/config.py b/bellows/ezsp/v4/config.py index 93139ae5..bb65aac3 100644 --- a/bellows/ezsp/v4/config.py +++ b/bellows/ezsp/v4/config.py @@ -1,6 +1,7 @@ +import voluptuous as vol + from bellows.config import cv_uint16 import bellows.multicast -import voluptuous as vol from . import types diff --git a/bellows/ezsp/v5/__init__.py b/bellows/ezsp/v5/__init__.py index b84b1177..c4b7390b 100644 --- a/bellows/ezsp/v5/__init__.py +++ b/bellows/ezsp/v5/__init__.py @@ -2,9 +2,10 @@ import logging from typing import Tuple -import bellows.config import voluptuous +import bellows.config + from . import commands, config, types as v5_types from ..v4 import EZSPv4 diff --git a/bellows/ezsp/v5/config.py b/bellows/ezsp/v5/config.py index c804fd53..2cc357fd 100644 --- a/bellows/ezsp/v5/config.py +++ b/bellows/ezsp/v5/config.py @@ -1,6 +1,7 @@ -from bellows.config import cv_uint16 import voluptuous as vol +from bellows.config import cv_uint16 + from ..v4 import config as v4_config, types _deletions = ("CONFIG_BROADCAST_ALARM_DATA_SIZE", "CONFIG_UNICAST_ALARM_DATA_SIZE") diff --git a/bellows/ezsp/v6/__init__.py b/bellows/ezsp/v6/__init__.py index 8f2319e2..bd9aea21 100644 --- a/bellows/ezsp/v6/__init__.py +++ b/bellows/ezsp/v6/__init__.py @@ -1,9 +1,10 @@ """"EZSP Protocol version 6 protocol handler.""" import logging -import bellows.config import voluptuous +import bellows.config + from . import commands, config, types as v6_types from ..v5 import EZSPv5 diff --git a/bellows/ezsp/v6/config.py b/bellows/ezsp/v6/config.py index 9d234a01..6f3b8697 100644 --- a/bellows/ezsp/v6/config.py +++ b/bellows/ezsp/v6/config.py @@ -1,6 +1,7 @@ -from bellows.config import cv_uint16 import voluptuous as vol +from bellows.config import cv_uint16 + from ..v4.config import EZSP_POLICIES_SHARED from ..v5 import config as v5_config from .types import EzspConfigId, EzspPolicyId diff --git a/bellows/ezsp/v7/__init__.py b/bellows/ezsp/v7/__init__.py index cad8d748..e7f4f264 100644 --- a/bellows/ezsp/v7/__init__.py +++ b/bellows/ezsp/v7/__init__.py @@ -1,9 +1,10 @@ """"EZSP Protocol version 7 protocol handler.""" import logging -import bellows.config import voluptuous +import bellows.config + from . import commands, config, types as v7_types from ..v5 import EZSPv5 diff --git a/bellows/ezsp/v7/config.py b/bellows/ezsp/v7/config.py index d3ddf14a..85772f11 100644 --- a/bellows/ezsp/v7/config.py +++ b/bellows/ezsp/v7/config.py @@ -1,6 +1,7 @@ +import voluptuous as vol + from bellows.config import cv_uint16 import bellows.multicast -import voluptuous as vol from ..v4.config import EZSP_POLICIES_SHARED from .types import EmberZdoConfigurationFlags, EzspConfigId, EzspPolicyId diff --git a/bellows/ezsp/v8/__init__.py b/bellows/ezsp/v8/__init__.py index 82632a76..b645b73a 100644 --- a/bellows/ezsp/v8/__init__.py +++ b/bellows/ezsp/v8/__init__.py @@ -3,9 +3,10 @@ import logging from typing import Tuple -import bellows.config import voluptuous +import bellows.config + from . import commands, config, types as v8_types from .. import protocol diff --git a/bellows/ezsp/v8/config.py b/bellows/ezsp/v8/config.py index fcdd1d47..ffb6780f 100644 --- a/bellows/ezsp/v8/config.py +++ b/bellows/ezsp/v8/config.py @@ -1,6 +1,7 @@ +import voluptuous as vol + from bellows.config import cv_uint16 import bellows.multicast -import voluptuous as vol from ..v4.config import EZSP_POLICIES_SHARED from .types import ( diff --git a/bellows/uart.py b/bellows/uart.py index 27377846..3ecd5626 100644 --- a/bellows/uart.py +++ b/bellows/uart.py @@ -2,11 +2,12 @@ import binascii import logging +import serial +import serial_asyncio + from bellows.config import CONF_DEVICE_BAUDRATE, CONF_DEVICE_PATH from bellows.thread import EventLoopThread, ThreadsafeProxy import bellows.types as t -import serial -import serial_asyncio LOGGER = logging.getLogger(__name__) RESET_TIMEOUT = 5 diff --git a/bellows/zigbee/application.py b/bellows/zigbee/application.py index f12ed39d..35cfba5c 100644 --- a/bellows/zigbee/application.py +++ b/bellows/zigbee/application.py @@ -3,6 +3,15 @@ import os from typing import Dict +from serial import SerialException +import zigpy.application +import zigpy.config +import zigpy.device +from zigpy.quirks import CustomDevice, CustomEndpoint +from zigpy.types import BroadcastAddress +import zigpy.util +import zigpy.zdo.types as zdo_t + from bellows.config import ( CONF_PARAM_SRC_RTG, CONF_PARAM_UNK_DEV, @@ -15,14 +24,6 @@ import bellows.multicast import bellows.types as t import bellows.zigbee.util -from serial import SerialException -import zigpy.application -import zigpy.config -import zigpy.device -from zigpy.quirks import CustomDevice, CustomEndpoint -from zigpy.types import BroadcastAddress -import zigpy.util -import zigpy.zdo.types as zdo_t APS_ACK_TIMEOUT = 120 EZSP_DEFAULT_RADIUS = 0 diff --git a/bellows/zigbee/util.py b/bellows/zigbee/util.py index 00f75271..ac0b9c41 100644 --- a/bellows/zigbee/util.py +++ b/bellows/zigbee/util.py @@ -1,9 +1,10 @@ import os from typing import Any, Dict -import bellows.types as t import zigpy.config +import bellows.types as t + def zha_security( config: Dict[str, Any], controller: bool = False, hashed_tclk: bool = True diff --git a/setup.cfg b/setup.cfg index 3f10a9b3..3f801541 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,21 +13,9 @@ ignore = D202 [isort] -# https://github.com/timothycrosley/isort -# https://github.com/timothycrosley/isort/wiki/isort-Settings -# splits long import on multiple lines indented by 4 spaces -multi_line_output = 3 -include_trailing_comma=True -force_grid_wrap=0 -use_parentheses=True -line_length=88 -indent = " " -# by default isort don't check module indexes -not_skip = __init__.py +profile = black # will group `import x` and `from x import` of the same module. force_sort_within_sections = true -sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER -default_section = THIRDPARTY -known_first_party = homeassistant,tests +known_first_party = bellows,tests forced_separate = tests combine_as_imports = true diff --git a/setup.py b/setup.py index 3798a80a..1ac5c998 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,9 @@ """Setup module for bellows""" -import bellows from setuptools import find_packages, setup +import bellows + setup( name="bellows", version=bellows.__version__, diff --git a/tests/test_application.py b/tests/test_application.py index 04431234..67a14f86 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -3,16 +3,17 @@ import asynctest from asynctest import CoroutineMock, mock +import pytest +import zigpy.config +from zigpy.device import Device +from zigpy.zcl.clusters import security + import bellows.config as config from bellows.exception import ControllerError, EzspError import bellows.ezsp as ezsp import bellows.ezsp.v4.types as t import bellows.uart as uart import bellows.zigbee.application -import pytest -import zigpy.config -from zigpy.device import Device -from zigpy.zcl.clusters import security APP_CONFIG = { config.CONF_DEVICE: { diff --git a/tests/test_commands.py b/tests/test_commands.py index c21e76c4..0792929d 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -1,11 +1,12 @@ import string +import pytest + import bellows.ezsp.v4.commands import bellows.ezsp.v5.commands import bellows.ezsp.v6.commands import bellows.ezsp.v7.commands import bellows.ezsp.v8.commands -import pytest @pytest.fixture( diff --git a/tests/test_ezsp.py b/tests/test_ezsp.py index 71ce45f0..f34f516d 100644 --- a/tests/test_ezsp.py +++ b/tests/test_ezsp.py @@ -2,11 +2,12 @@ import functools from asynctest import CoroutineMock, mock +import pytest +import serial + from bellows import config, ezsp, uart from bellows.exception import EzspError import bellows.ezsp.v4.types as t -import pytest -import serial DEVICE_CONFIG = { config.CONF_DEVICE_PATH: "/dev/null", diff --git a/tests/test_ezsp_protocol.py b/tests/test_ezsp_protocol.py index ef5a992e..fdb3e57e 100644 --- a/tests/test_ezsp_protocol.py +++ b/tests/test_ezsp_protocol.py @@ -2,9 +2,10 @@ import logging from asynctest import CoroutineMock, mock +import pytest + import bellows.ezsp.v4 import bellows.ezsp.v4.types as t -import pytest class _DummyProtocolHandler(bellows.ezsp.v4.EZSPv4): diff --git a/tests/test_ezsp_v4.py b/tests/test_ezsp_v4.py index 2b31f0a8..b6939096 100644 --- a/tests/test_ezsp_v4.py +++ b/tests/test_ezsp_v4.py @@ -1,7 +1,8 @@ from asynctest import mock -import bellows.ezsp.v4 import pytest +import bellows.ezsp.v4 + @pytest.fixture def ezsp_f(): diff --git a/tests/test_ezsp_v5.py b/tests/test_ezsp_v5.py index a7878304..a3495ebb 100644 --- a/tests/test_ezsp_v5.py +++ b/tests/test_ezsp_v5.py @@ -1,7 +1,8 @@ from asynctest import CoroutineMock, mock -import bellows.ezsp.v5 import pytest +import bellows.ezsp.v5 + @pytest.fixture def ezsp_f(): diff --git a/tests/test_ezsp_v6.py b/tests/test_ezsp_v6.py index bf3f1f57..c4697e2c 100644 --- a/tests/test_ezsp_v6.py +++ b/tests/test_ezsp_v6.py @@ -1,7 +1,8 @@ from asynctest import mock -import bellows.ezsp.v6 import pytest +import bellows.ezsp.v6 + @pytest.fixture def ezsp_f(): diff --git a/tests/test_ezsp_v7.py b/tests/test_ezsp_v7.py index 77bdba6f..34a5c730 100644 --- a/tests/test_ezsp_v7.py +++ b/tests/test_ezsp_v7.py @@ -1,7 +1,8 @@ from asynctest import mock -import bellows.ezsp.v7 import pytest +import bellows.ezsp.v7 + @pytest.fixture def ezsp_f(): diff --git a/tests/test_ezsp_v8.py b/tests/test_ezsp_v8.py index 39db082f..8b424a3b 100644 --- a/tests/test_ezsp_v8.py +++ b/tests/test_ezsp_v8.py @@ -1,7 +1,8 @@ from asynctest import CoroutineMock, mock -import bellows.ezsp.v8 import pytest +import bellows.ezsp.v8 + @pytest.fixture def ezsp_f(): diff --git a/tests/test_multicast.py b/tests/test_multicast.py index 31045288..1843c7a9 100644 --- a/tests/test_multicast.py +++ b/tests/test_multicast.py @@ -1,9 +1,10 @@ from asynctest import CoroutineMock, mock +import pytest +from zigpy.endpoint import Endpoint + import bellows.ezsp import bellows.multicast import bellows.types as t -import pytest -from zigpy.endpoint import Endpoint CUSTOM_SIZE = 12 diff --git a/tests/test_thread.py b/tests/test_thread.py index 70a642c8..3683c023 100644 --- a/tests/test_thread.py +++ b/tests/test_thread.py @@ -3,9 +3,10 @@ import threading from unittest import mock -from bellows.thread import EventLoopThread, ThreadsafeProxy import pytest +from bellows.thread import EventLoopThread, ThreadsafeProxy + @pytest.mark.asyncio async def test_thread_start(monkeypatch): diff --git a/tests/test_types_struct.py b/tests/test_types_struct.py index 807e80a9..132267bb 100644 --- a/tests/test_types_struct.py +++ b/tests/test_types_struct.py @@ -1,9 +1,10 @@ import enum import typing -import bellows.types as t import pytest +import bellows.types as t + def test_struct_fields(): class TestStruct(t.EzspStruct): diff --git a/tests/test_uart.py b/tests/test_uart.py index ab7b6435..b44ca47c 100644 --- a/tests/test_uart.py +++ b/tests/test_uart.py @@ -2,11 +2,12 @@ import threading from asynctest import CoroutineMock, mock -from bellows import uart -import bellows.config as conf import pytest import serial_asyncio +from bellows import uart +import bellows.config as conf + @pytest.mark.asyncio async def test_connect(monkeypatch): diff --git a/tox.ini b/tox.ini index a0132186..cdd4fb82 100644 --- a/tox.ini +++ b/tox.ini @@ -24,10 +24,10 @@ basepython = python3 deps = flake8==3.8.3 pep8-naming==0.4.1 - isort==4.3.21 + isort==5.5.2 commands = flake8 - isort --check -rc {toxinidir}/bellows {toxinidir}/tests {toxinidir}/setup.py + isort --check {toxinidir}/bellows {toxinidir}/tests {toxinidir}/setup.py [testenv:black] deps = From 02c5312c665bdf0f222197a8c71d38d076c929c2 Mon Sep 17 00:00:00 2001 From: Hedda Date: Wed, 16 Sep 2020 02:29:19 +0200 Subject: [PATCH 4/7] Update README.md with recommendation to upgrade EmberZNet firmware (#346) Update zha.markdown with recommendations to upgrade EmberZNet NCP application firmware on Silicon Labs based Zigbee adapter and links to information about how to do so where it is available. Looks like quite a few users in the Home Assistant community have reported that some paring issues or instability with old firmware are resolved simply by upgrading EmberZNet firmware. Does someone, by the way, have a link to good instructions on how-to upgrade firmware on Telegesis ETRX357USB adapters as well as links to publicly available firmware images for them? Also submit PR https://github.com/home-assistant/home-assistant.io/pull/14503 to update https://www.home-assistant.io/integrations/zha/ Upgrading firmware might not be the solution for everyone or even possible on all adapters but if at least some people do so then it might off-load some bug-reports. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3e8c57ac..7cb58206 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ bellows interacts with the Zigbee Network Coprocessor (NCP) with EmberZNet PRO Z EmberZNet based Zigbee radios using the EZSP protocol (via the [bellows](https://github.com/zigpy/bellows) library for zigpy) - [ITEAD Sonoff ZBBridge](https://www.itead.cc/smart-home/sonoff-zbbridge.html) (Note! This first have to be flashed with [Tasmota firmware and EmberZNet firmware](https://www.digiblur.com/2020/07/how-to-use-sonoff-zigbee-bridge-with.html)) - - [Nortek GoControl QuickStick Combo Model HUSBZB-1 (Z-Wave & Zigbee USB Adapter)](https://www.nortekcontrol.com/products/2gig/husbzb-1-gocontrol-quickstick-combo/) - - [Elelabs Zigbee USB Adapter](https://elelabs.com/products/elelabs_usb_adapter.html) - - [Elelabs Zigbee Raspberry Pi Shield](https://elelabs.com/products/elelabs_zigbee_shield.html) + - [Nortek GoControl QuickStick Combo Model HUSBZB-1 (Z-Wave & Zigbee Ember 3581 USB Adapter)](https://www.nortekcontrol.com/products/2gig/husbzb-1-gocontrol-quickstick-combo/) (Note! Not a must but recommend [upgrade the EmberZNet NCP application firmware](https://github.com/walthowd/husbzb-firmware)) + - [Elelabs Zigbee USB Adapter](https://elelabs.com/products/elelabs_usb_adapter.html) (Note! Not a must but recommend [upgrade the EmberZNet NCP application firmware](https://github.com/Elelabs/elelabs-zigbee-ezsp-utility)) + - [Elelabs Zigbee Raspberry Pi Shield](https://elelabs.com/products/elelabs_zigbee_shield.html) (Note! Not a must but recommend [upgrade the EmberZNet NCP application firmware](https://github.com/Elelabs/elelabs-zigbee-ezsp-utility)) - Telegesis ETRX357USB (Note! This first have to be flashed with other EmberZNet firmware) - Telegesis ETRX357USB-LRS (Note! This first have to be flashed with other EmberZNet firmware) - Telegesis ETRX357USB-LRS+8M (Note! This first have to be flashed with other EmberZNet firmware) From 88b9fe9f3a6563808b715b356f7bbb2bfeb7c090 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Wed, 16 Sep 2020 14:20:15 -0400 Subject: [PATCH 5/7] Use async_mock helper to transition to AsyncMock testing (#350) * Use async_mock helper for migration to AsyncMock. * Update ezsp tests * Update ezsp tests * Update multicast tests * Update thread tests * Update uart tests * Update app test --- tests/__init__.py | 1 + tests/async_mock.py | 9 + tests/test_application.py | 476 +++++++++++++++--------------------- tests/test_ezsp.py | 114 ++++----- tests/test_ezsp_protocol.py | 15 +- tests/test_ezsp_v4.py | 3 +- tests/test_ezsp_v5.py | 10 +- tests/test_ezsp_v6.py | 3 +- tests/test_ezsp_v7.py | 3 +- tests/test_ezsp_v8.py | 15 +- tests/test_multicast.py | 29 ++- tests/test_thread.py | 12 +- tests/test_uart.py | 75 +++--- 13 files changed, 340 insertions(+), 425 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/async_mock.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..adfa6fae --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests modules.""" diff --git a/tests/async_mock.py b/tests/async_mock.py new file mode 100644 index 00000000..8257ddd3 --- /dev/null +++ b/tests/async_mock.py @@ -0,0 +1,9 @@ +"""Mock utilities that are async aware.""" +import sys + +if sys.version_info[:2] < (3, 8): + from asynctest.mock import * # noqa + + AsyncMock = CoroutineMock # noqa: F405 +else: + from unittest.mock import * # noqa diff --git a/tests/test_application.py b/tests/test_application.py index 67a14f86..2400f65a 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -1,8 +1,6 @@ import asyncio import logging -import asynctest -from asynctest import CoroutineMock, mock import pytest import zigpy.config from zigpy.device import Device @@ -15,6 +13,10 @@ import bellows.uart as uart import bellows.zigbee.application +from .async_mock import AsyncMock, MagicMock, PropertyMock, patch, sentinel + +pytestmark = pytest.mark.asyncio + APP_CONFIG = { config.CONF_DEVICE: { config.CONF_DEVICE_PATH: "/dev/null", @@ -26,23 +28,21 @@ @pytest.fixture -def app(monkeypatch): - ezsp = mock.MagicMock() +def app(monkeypatch, event_loop): + ezsp = MagicMock() ezsp.ezsp_version = 7 - ezsp.set_source_route = asynctest.CoroutineMock( - return_value=[t.EmberStatus.SUCCESS] - ) - ezsp.get_board_info = CoroutineMock( + ezsp.set_source_route = AsyncMock(return_value=[t.EmberStatus.SUCCESS]) + ezsp.get_board_info = AsyncMock( return_value=("Mock Manufacturer", "Mock board", "Mock version") ) - type(ezsp).is_ezsp_running = mock.PropertyMock(return_value=True) + type(ezsp).is_ezsp_running = PropertyMock(return_value=True) config = bellows.zigbee.application.ControllerApplication.SCHEMA(APP_CONFIG) ctrl = bellows.zigbee.application.ControllerApplication(config) ctrl._ezsp = ezsp monkeypatch.setattr(bellows.zigbee.application, "APS_ACK_TIMEOUT", 0.1) ctrl._ctrl_event.set() ctrl._in_flight_msg = asyncio.Semaphore() - ctrl.handle_message = mock.MagicMock() + ctrl.handle_message = MagicMock() return ctrl @@ -62,92 +62,83 @@ def ieee(init=0): return t.EmberEUI64(map(t.uint8_t, range(init, init + 8))) -def get_mock_coro(return_value): - async def mock_coro(*args, **kwargs): - return return_value - - return mock.Mock(wraps=mock_coro) - - -def _test_startup(app, nwk_type, ieee, auto_form=False, init=0, ezsp_version=4): +@patch("zigpy.device.Device._initialize", new=AsyncMock()) +@patch("bellows.zigbee.application.ControllerApplication._watchdog", new=AsyncMock()) +async def _test_startup(app, nwk_type, ieee, auto_form=False, init=0, ezsp_version=4): async def mockezsp(*args, **kwargs): - return [0, nwk_type, mock.sentinel.nework_parameters] + return [0, nwk_type, sentinel.nework_parameters] async def mockinit(*args, **kwargs): return [init] app._in_flight_msg = None - ezsp_mock = mock.MagicMock() - type(ezsp_mock.return_value).ezsp_version = mock.PropertyMock( - return_value=ezsp_version - ) - ezsp_mock.initialize = CoroutineMock(return_value=ezsp_mock) - ezsp_mock.connect = CoroutineMock() - ezsp_mock.setConcentrator = CoroutineMock() + ezsp_mock = MagicMock() + type(ezsp_mock.return_value).ezsp_version = PropertyMock(return_value=ezsp_version) + ezsp_mock.initialize = AsyncMock(return_value=ezsp_mock) + ezsp_mock.connect = AsyncMock() + ezsp_mock.setConcentrator = AsyncMock() ezsp_mock._command = mockezsp ezsp_mock.addEndpoint = mockezsp ezsp_mock.setConfigurationValue = mockezsp ezsp_mock.networkInit = mockinit ezsp_mock.getNetworkParameters = mockezsp - ezsp_mock.get_board_info = CoroutineMock( + ezsp_mock.get_board_info = AsyncMock( return_value=("Mock Manufacturer", "Mock board", "Mock version") ) ezsp_mock.setPolicy = mockezsp - ezsp_mock.getMfgToken = CoroutineMock(return_value=(b"Some token\xff",)) + ezsp_mock.getMfgToken = AsyncMock(return_value=(b"Some token\xff",)) ezsp_mock.getNodeId = mockezsp - ezsp_mock.getEui64.side_effect = CoroutineMock(return_value=[ieee]) - ezsp_mock.getValue = CoroutineMock(return_value=(0, b"\x01" * 6)) + ezsp_mock.getEui64 = AsyncMock(return_value=[ieee]) + ezsp_mock.getValue = AsyncMock(return_value=(0, b"\x01" * 6)) ezsp_mock.leaveNetwork = mockezsp - app.form_network = CoroutineMock() - ezsp_mock.reset.side_effect = CoroutineMock() - ezsp_mock.version.side_effect = CoroutineMock() - ezsp_mock.getConfigurationValue.side_effect = CoroutineMock(return_value=(0, 1)) - ezsp_mock.update_policies = CoroutineMock() + app.form_network = AsyncMock() + ezsp_mock.reset = AsyncMock() + ezsp_mock.version = AsyncMock() + ezsp_mock.getConfigurationValue = AsyncMock(return_value=(0, 1)) + ezsp_mock.update_policies = AsyncMock() - loop = asyncio.get_event_loop() - p1 = mock.patch.object(bellows.ezsp, "EZSP", new=ezsp_mock) - p2 = mock.patch.object(bellows.multicast.Multicast, "startup") + p1 = patch.object(bellows.ezsp, "EZSP", new=ezsp_mock) + p2 = patch.object(bellows.multicast.Multicast, "startup") with p1, p2 as multicast_mock: - loop.run_until_complete(app.startup(auto_form=auto_form)) + await app.startup(auto_form=auto_form) assert multicast_mock.await_count == 1 -def test_startup(app, ieee): - return _test_startup(app, t.EmberNodeType.COORDINATOR, ieee) +async def test_startup(app, ieee): + await _test_startup(app, t.EmberNodeType.COORDINATOR, ieee) -def test_startup_ezsp_ver7(app, ieee): - return _test_startup(app, t.EmberNodeType.COORDINATOR, ieee, ezsp_version=7) +async def test_startup_ezsp_ver7(app, ieee): + await _test_startup(app, t.EmberNodeType.COORDINATOR, ieee, ezsp_version=7) -def test_startup_no_status(app, ieee): +async def test_startup_no_status(app, ieee): with pytest.raises(Exception): - return _test_startup(app, None, ieee, init=1) + await _test_startup(app, None, ieee, init=1) -def test_startup_no_status_form(app, ieee): - return _test_startup(app, None, ieee, auto_form=True, init=1) +async def test_startup_no_status_form(app, ieee): + await _test_startup(app, None, ieee, auto_form=True, init=1) -def test_startup_end(app, ieee): +async def test_startup_end(app, ieee): with pytest.raises(Exception): - return _test_startup(app, t.EmberNodeType.SLEEPY_END_DEVICE, ieee) + await _test_startup(app, t.EmberNodeType.SLEEPY_END_DEVICE, ieee) -def test_startup_end_form(app, ieee): - return _test_startup(app, t.EmberNodeType.SLEEPY_END_DEVICE, ieee, True) +async def test_startup_end_form(app, ieee): + await _test_startup(app, t.EmberNodeType.SLEEPY_END_DEVICE, ieee, True) -def test_form_network(app): +async def test_form_network(app): f = asyncio.Future() f.set_result([0]) app._ezsp.setInitialSecurityState.side_effect = [f] - app._ezsp.formNetwork.side_effect = asyncio.coroutine(mock.MagicMock()) - app._ezsp.setValue.side_effect = asyncio.coroutine(mock.MagicMock()) + app._ezsp.formNetwork = AsyncMock() + app._ezsp.setValue = AsyncMock() - loop = asyncio.get_event_loop() - loop.run_until_complete(app.form_network()) + await app.form_network() def _frame_handler(app, aps, ieee, src_ep, cluster=0, data=b"\x01\x00\x00"): @@ -160,11 +151,10 @@ def _frame_handler(app, aps, ieee, src_ep, cluster=0, data=b"\x01\x00\x00"): ) -@pytest.mark.asyncio async def test_frame_handler_unknown_device(app, aps, ieee): - app.handle_join = mock.MagicMock() + app.handle_join = MagicMock() app.add_device(ieee, 99) - with mock.patch.object(app, "_handle_no_such_device") as no_dev_mock: + with patch.object(app, "_handle_no_such_device") as no_dev_mock: _frame_handler(app, aps, ieee, 0) await asyncio.sleep(0) assert no_dev_mock.call_count == 1 @@ -174,7 +164,7 @@ async def test_frame_handler_unknown_device(app, aps, ieee): def test_frame_handler(app, aps, ieee): - app.handle_join = mock.MagicMock() + app.handle_join = MagicMock() data = b"\x18\x19\x22\xaa\x55" _frame_handler(app, aps, ieee, 0, data=data) assert app.handle_message.call_count == 1 @@ -184,7 +174,7 @@ def test_frame_handler(app, aps, ieee): def test_frame_handler_zdo_annce(app, aps, ieee): aps.destinationEndpoint = 0 - app.handle_join = mock.MagicMock() + app.handle_join = MagicMock() nwk = t.uint16_t(0xAA55) data = b"\x18" + nwk.serialize() + ieee.serialize() _frame_handler(app, aps, ieee, 0, cluster=0x0013, data=data) @@ -196,20 +186,20 @@ def test_frame_handler_zdo_annce(app, aps, ieee): def test_send_failure(app, aps, ieee): - req = app._pending[254] = mock.MagicMock() + req = app._pending[254] = MagicMock() app.ezsp_callback_handler( - "messageSentHandler", [None, 0xBEED, aps, 254, mock.sentinel.status, b""] + "messageSentHandler", [None, 0xBEED, aps, 254, sentinel.status, b""] ) assert req.result.set_exception.call_count == 0 assert req.result.set_result.call_count == 1 - assert req.result.set_result.call_args[0][0][0] is mock.sentinel.status + assert req.result.set_result.call_args[0][0][0] is sentinel.status def test_dup_send_failure(app, aps, ieee): - req = app._pending[254] = mock.MagicMock() + req = app._pending[254] = MagicMock() req.result.set_result.side_effect = asyncio.InvalidStateError() app.ezsp_callback_handler( - "messageSentHandler", [None, 0xBEED, aps, 254, mock.sentinel.status, b""] + "messageSentHandler", [None, 0xBEED, aps, 254, sentinel.status, b""] ) assert req.result.set_exception.call_count == 0 assert req.result.set_result.call_count == 1 @@ -220,13 +210,13 @@ def test_send_failure_unexpected(app, aps, ieee): def test_send_success(app, aps, ieee): - req = app._pending[253] = mock.MagicMock() + req = app._pending[253] = MagicMock() app.ezsp_callback_handler( - "messageSentHandler", [None, 0xBEED, aps, 253, mock.sentinel.success, b""] + "messageSentHandler", [None, 0xBEED, aps, 253, sentinel.success, b""] ) assert req.result.set_exception.call_count == 0 assert req.result.set_result.call_count == 1 - assert req.result.set_result.call_args[0][0][0] is mock.sentinel.success + assert req.result.set_result.call_args[0][0][0] is sentinel.success def test_unexpected_send_success(app, aps, ieee): @@ -234,18 +224,17 @@ def test_unexpected_send_success(app, aps, ieee): def test_dup_send_success(app, aps, ieee): - req = app._pending[253] = mock.MagicMock() + req = app._pending[253] = MagicMock() req.result.set_result.side_effect = asyncio.InvalidStateError() app.ezsp_callback_handler("messageSentHandler", [None, 0xBEED, aps, 253, 0, b""]) assert req.result.set_exception.call_count == 0 assert req.result.set_result.call_count == 1 -@pytest.mark.asyncio async def test_join_handler(app, ieee): # Calls device.initialize, leaks a task - app.handle_join = mock.MagicMock() - app.cleanup_tc_link_key = CoroutineMock() + app.handle_join = MagicMock() + app.cleanup_tc_link_key = AsyncMock() app.ezsp_callback_handler( "trustCenterJoinHandler", [ @@ -253,7 +242,7 @@ async def test_join_handler(app, ieee): ieee, t.EmberDeviceUpdate.STANDARD_SECURITY_UNSECURED_JOIN, t.EmberJoinDecision.NO_ACTION, - mock.sentinel.parent, + sentinel.parent, ], ) await asyncio.sleep(0) @@ -261,7 +250,7 @@ async def test_join_handler(app, ieee): assert app.handle_join.call_count == 1 assert app.handle_join.call_args[0][0] == 1 assert app.handle_join.call_args[0][1] == ieee - assert app.handle_join.call_args[0][2] is mock.sentinel.parent + assert app.handle_join.call_args[0][2] is sentinel.parent assert app.cleanup_tc_link_key.await_count == 1 assert app.cleanup_tc_link_key.call_args[0][0] is ieee @@ -275,7 +264,7 @@ async def test_join_handler(app, ieee): ieee, t.EmberDeviceUpdate.STANDARD_SECURITY_UNSECURED_JOIN, t.EmberJoinDecision.DENY_JOIN, - mock.sentinel.parent, + sentinel.parent, ], ) await asyncio.sleep(0) @@ -285,8 +274,8 @@ async def test_join_handler(app, ieee): def test_leave_handler(app, ieee): - app.handle_join = mock.MagicMock() - app.devices[ieee] = mock.MagicMock() + app.handle_join = MagicMock() + app.devices[ieee] = MagicMock() app.ezsp_callback_handler( "trustCenterJoinHandler", [1, ieee, t.EmberDeviceUpdate.DEVICE_LEFT, None, None] ) @@ -294,11 +283,10 @@ def test_leave_handler(app, ieee): assert app.handle_join.call_count == 0 -def test_force_remove(app, ieee): - app._ezsp.removeDevice.side_effect = asyncio.coroutine(mock.MagicMock()) - dev = mock.MagicMock() - loop = asyncio.get_event_loop() - loop.run_until_complete(app.force_remove(dev)) +async def test_force_remove(app, ieee): + app._ezsp.removeDevice = AsyncMock() + dev = MagicMock() + await app.force_remove(dev) def test_sequence(app): @@ -313,83 +301,67 @@ def test_permit_ncp(app): assert app._ezsp.permitJoining.call_count == 1 -def test_permit_with_key(app): - app._ezsp.addTransientLinkKey = get_mock_coro([0]) - app._ezsp.setPolicy = get_mock_coro([0]) - app.permit = get_mock_coro([0]) +async def test_permit_with_key(app): + app._ezsp.addTransientLinkKey = AsyncMock(return_value=[0]) + app._ezsp.setPolicy = AsyncMock(return_value=[0]) + app.permit = AsyncMock(return_value=[0]) - loop = asyncio.get_event_loop() - loop.run_until_complete( - app.permit_with_key( - bytes([1, 2, 3, 4, 5, 6, 7, 8]), - bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), - 60, - ) + await app.permit_with_key( + bytes([1, 2, 3, 4, 5, 6, 7, 8]), + bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), + 60, ) - assert app._ezsp.addTransientLinkKey.call_count == 1 - assert app.permit.call_count == 1 + assert app._ezsp.addTransientLinkKey.await_count == 1 + assert app.permit.await_count == 1 -def test_permit_with_key_ieee(app, ieee): - app._ezsp.addTransientLinkKey = get_mock_coro([0]) - app._ezsp.setPolicy = get_mock_coro([0]) - app.permit = get_mock_coro([0]) +async def test_permit_with_key_ieee(app, ieee): + app._ezsp.addTransientLinkKey = AsyncMock(return_value=[0]) + app._ezsp.setPolicy = AsyncMock(return_value=[0]) + app.permit = AsyncMock(return_value=[0]) - loop = asyncio.get_event_loop() - loop.run_until_complete( - app.permit_with_key( - ieee, - bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), - 60, - ) + await app.permit_with_key( + ieee, + bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), + 60, ) - assert app._ezsp.addTransientLinkKey.call_count == 1 - assert app.permit.call_count == 1 + assert app._ezsp.addTransientLinkKey.await_count == 1 + assert app.permit.await_count == 1 -def test_permit_with_key_invalid_install_code(app, ieee): - loop = asyncio.get_event_loop() +async def test_permit_with_key_invalid_install_code(app, ieee): with pytest.raises(Exception): - loop.run_until_complete( - app.permit_with_key( - ieee, bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]), 60 - ) + await app.permit_with_key( + ieee, bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]), 60 ) -def test_permit_with_key_failed_add_key(app, ieee): - app._ezsp.addTransientLinkKey = get_mock_coro([1, 1]) +async def test_permit_with_key_failed_add_key(app, ieee): + app._ezsp.addTransientLinkKey = AsyncMock(return_value=[1, 1]) - loop = asyncio.get_event_loop() with pytest.raises(Exception): - loop.run_until_complete( - app.permit_with_key( - ieee, - bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), - 60, - ) + await app.permit_with_key( + ieee, + bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), + 60, ) -def test_permit_with_key_failed_set_policy(app, ieee): - app._ezsp.addTransientLinkKey = get_mock_coro([0]) - app._ezsp.setPolicy = get_mock_coro([1]) +async def test_permit_with_key_failed_set_policy(app, ieee): + app._ezsp.addTransientLinkKey = AsyncMock(return_value=[0]) + app._ezsp.setPolicy = AsyncMock(return_value=[1]) - loop = asyncio.get_event_loop() with pytest.raises(Exception): - loop.run_until_complete( - app.permit_with_key( - ieee, - bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), - 60, - ) + await app.permit_with_key( + ieee, + bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), + 60, ) -@pytest.mark.asyncio async def _request( app, send_status=0, @@ -411,9 +383,9 @@ async def mocksend(method, nwk, aps_frame, seq, data): req.result.set_result((102, "failure")) return [send_status, "send message"] - app._ezsp.sendUnicast = mock.CoroutineMock(side_effect=mocksend) - app._ezsp.setExtendedTimeout = mock.CoroutineMock() - device = mock.MagicMock() + app._ezsp.sendUnicast = AsyncMock(side_effect=mocksend) + app._ezsp.setExtendedTimeout = AsyncMock() + device = MagicMock() device.relays = relays device.node_desc.is_end_device = is_end_device res = await app.request(device, 9, 8, 7, 6, 5, b"", **kwargs) @@ -421,52 +393,44 @@ async def mocksend(method, nwk, aps_frame, seq, data): return res -@pytest.mark.asyncio async def test_request(app): res = await _request(app) assert res[0] == 0 -@pytest.mark.asyncio async def test_request_ack_timeout(app, aps): with pytest.raises(asyncio.TimeoutError): await _request(app, send_ack_received=False) -@pytest.mark.asyncio async def test_request_send_unicast_fail(app): res = await _request(app, send_status=2) assert res[0] != 0 -@pytest.mark.asyncio async def test_request_ezsp_failed(app): with pytest.raises(EzspError): await _request(app, ezsp_operational=False) assert len(app._pending) == 0 -@pytest.mark.asyncio async def test_request_reply_timeout_send_timeout(app): with pytest.raises(asyncio.TimeoutError): await _request(app, send_ack_received=False) assert app._pending == {} -@pytest.mark.asyncio async def test_request_ctrl_not_running(app): app._ctrl_event.clear() with pytest.raises(ControllerError): await _request(app) -@pytest.mark.asyncio async def test_request_use_ieee(app): res = await _request(app, use_ieee=True) assert res[0] == 0 -@pytest.mark.asyncio async def test_request_extended_timeout(app): res = await _request(app) assert res[0] == 0 @@ -484,7 +448,6 @@ async def test_request_extended_timeout(app): @pytest.mark.parametrize("relays", [None, [], [0x1234]]) -@pytest.mark.asyncio async def test_request_src_rtg_not_enabled(relays, app): app.use_source_routing = False res = await _request(app, relays=relays) @@ -498,7 +461,6 @@ async def test_request_src_rtg_not_enabled(relays, app): @pytest.mark.parametrize("relays", [[], [0x1234]]) -@pytest.mark.asyncio async def test_request_src_rtg_success(relays, app): app.use_source_routing = True res = await _request(app, relays=relays) @@ -512,7 +474,6 @@ async def test_request_src_rtg_success(relays, app): @pytest.mark.parametrize("relays", [[], [0x1234]]) -@pytest.mark.asyncio async def test_request_src_rtg_fail(relays, app): app.use_source_routing = True app._ezsp.set_source_route.return_value = [1] @@ -529,7 +490,6 @@ async def test_request_src_rtg_fail(relays, app): @pytest.mark.parametrize( "send_status, sleep_count, send_unicast_count", ((0, 0, 1), (114, 3, 4), (2, 0, 1)) ) -@pytest.mark.asyncio async def test_request_max_message_limit( send_status, sleep_count, send_unicast_count, app ): @@ -539,19 +499,18 @@ async def mocksend(method, nwk, aps_frame, seq, data): req.result.set_result((0, "success")) return [send_status, "send message"] - app._ezsp.sendUnicast = mock.CoroutineMock(side_effect=mocksend) - app._ezsp.setExtendedTimeout = mock.CoroutineMock() - device = mock.MagicMock() + app._ezsp.sendUnicast = AsyncMock(side_effect=mocksend) + app._ezsp.setExtendedTimeout = AsyncMock() + device = MagicMock() device.relays = [] device.node_desc.is_end_device = False - with mock.patch("asyncio.sleep") as sleep_mock: + with patch("asyncio.sleep") as sleep_mock: await app.request(device, 9, 8, 7, 6, 5, b"", expect_reply=False) assert sleep_mock.await_count == sleep_count assert app._ezsp.sendUnicast.await_count == send_unicast_count -@pytest.mark.asyncio async def _test_broadcast( app, broadcast_success=True, send_timeout=False, ezsp_running=True ): @@ -577,47 +536,42 @@ async def mock_send(nwk, aps, radius, tsn, data): return [t.EmberStatus.ERR_FATAL] app._ezsp.sendBroadcast.side_effect = mock_send - app.get_sequence = mock.MagicMock(return_value=mock.sentinel.msg_tag) + app.get_sequence = MagicMock(return_value=sentinel.msg_tag) res = await app.broadcast( profile, cluster, src_ep, dst_ep, grpid, radius, tsn, data ) assert app._ezsp.sendBroadcast.call_count == 1 assert app._ezsp.sendBroadcast.call_args[0][2] == radius - assert app._ezsp.sendBroadcast.call_args[0][3] == mock.sentinel.msg_tag + assert app._ezsp.sendBroadcast.call_args[0][3] == sentinel.msg_tag assert app._ezsp.sendBroadcast.call_args[0][4] == data assert len(app._pending) == 0 return res -@pytest.mark.asyncio async def test_broadcast(app): await _test_broadcast(app) assert len(app._pending) == 0 -@pytest.mark.asyncio async def test_broadcast_fail(app): res = await _test_broadcast(app, broadcast_success=False) assert res[0] != 0 assert len(app._pending) == 0 -@pytest.mark.asyncio async def test_broadcast_send_timeout(app): with pytest.raises(asyncio.TimeoutError): await _test_broadcast(app, send_timeout=True) assert len(app._pending) == 0 -@pytest.mark.asyncio async def test_broadcast_ezsp_fail(app): with pytest.raises(EzspError): await _test_broadcast(app, ezsp_running=False) assert len(app._pending) == 0 -@pytest.mark.asyncio async def test_broadcast_ctrl_not_running(app): app._ctrl_event.clear() (profile, cluster, src_ep, dst_ep, grpid, radius, tsn, data) = ( @@ -643,7 +597,7 @@ async def mocksend(nwk, aps, radiusm, tsn, data): def test_is_controller_running(app): - ezsp_running = mock.PropertyMock(return_value=False) + ezsp_running = PropertyMock(return_value=False) type(app._ezsp).is_ezsp_running = ezsp_running app._ctrl_event.clear() assert app.is_controller_running is False @@ -651,7 +605,7 @@ def test_is_controller_running(app): assert app.is_controller_running is False assert ezsp_running.call_count == 1 - ezsp_running = mock.PropertyMock(return_value=True) + ezsp_running = PropertyMock(return_value=True) type(app._ezsp).is_ezsp_running = ezsp_running app._ctrl_event.clear() assert app.is_controller_running is False @@ -661,21 +615,20 @@ def test_is_controller_running(app): def test_reset_frame(app): - app._handle_reset_request = mock.MagicMock(spec_set=app._handle_reset_request) - app.ezsp_callback_handler("_reset_controller_application", (mock.sentinel.error,)) + app._handle_reset_request = MagicMock(spec_set=app._handle_reset_request) + app.ezsp_callback_handler("_reset_controller_application", (sentinel.error,)) assert app._handle_reset_request.call_count == 1 - assert app._handle_reset_request.call_args[0][0] is mock.sentinel.error + assert app._handle_reset_request.call_args[0][0] is sentinel.error -@pytest.mark.asyncio async def test_handle_reset_req(app): # no active reset task, no reset task preemption app._ctrl_event.set() assert app._reset_task is None - reset_ctrl_mock = asyncio.coroutine(mock.MagicMock()) - app._reset_controller_loop = mock.MagicMock(side_effect=reset_ctrl_mock) + reset_ctrl_mock = AsyncMock() + app._reset_controller_loop = MagicMock(side_effect=reset_ctrl_mock) - app._handle_reset_request(mock.sentinel.error) + app._handle_reset_request(sentinel.error) assert asyncio.isfuture(app._reset_task) assert app._ctrl_event.is_set() is False @@ -683,17 +636,16 @@ async def test_handle_reset_req(app): assert app._reset_controller_loop.call_count == 1 -@pytest.mark.asyncio async def test_handle_reset_req_existing_preempt(app): # active reset task, preempt reset task app._ctrl_event.set() assert app._reset_task is None old_reset = asyncio.Future() app._reset_task = old_reset - reset_ctrl_mock = asyncio.coroutine(mock.MagicMock()) - app._reset_controller_loop = mock.MagicMock(side_effect=reset_ctrl_mock) + reset_ctrl_mock = AsyncMock() + app._reset_controller_loop = MagicMock(side_effect=reset_ctrl_mock) - app._handle_reset_request(mock.sentinel.error) + app._handle_reset_request(sentinel.error) assert asyncio.isfuture(app._reset_task) await app._reset_task @@ -703,7 +655,6 @@ async def test_handle_reset_req_existing_preempt(app): assert old_reset.cancelled() is True -@pytest.mark.asyncio async def test_reset_controller_loop(app, monkeypatch): from bellows.zigbee import application @@ -720,7 +671,7 @@ async def reset_controller_mock(): raise asyncio.TimeoutError return - app._reset_controller = mock.MagicMock(side_effect=reset_controller_mock) + app._reset_controller = AsyncMock(side_effect=reset_controller_mock) await app._reset_controller_loop() @@ -729,12 +680,9 @@ async def reset_controller_mock(): assert app._reset_task is None -@pytest.mark.asyncio async def test_reset_controller_routine(app): - reconn_mock = asyncio.coroutine(mock.MagicMock()) - app._ezsp.reconnect = mock.MagicMock(side_effect=reconn_mock) - startup_mock = asyncio.coroutine(mock.MagicMock()) - app.startup = mock.MagicMock(side_effect=startup_mock) + app._ezsp.reconnect = AsyncMock() + app.startup = AsyncMock() await app._reset_controller() @@ -742,7 +690,6 @@ async def test_reset_controller_routine(app): assert app.startup.call_count == 1 -@pytest.mark.asyncio async def test_watchdog(app, monkeypatch): from bellows.zigbee import application @@ -759,8 +706,8 @@ async def nop_mock(): return raise asyncio.TimeoutError - app._ezsp.nop = mock.MagicMock(side_effect=nop_mock) - app._handle_reset_request = mock.MagicMock() + app._ezsp.nop = AsyncMock(side_effect=nop_mock) + app._handle_reset_request = MagicMock() app._ctrl_event.set() await app._watchdog() @@ -769,7 +716,6 @@ async def nop_mock(): assert app._handle_reset_request.call_count == 1 -@pytest.mark.asyncio async def test_watchdog_counters(app, monkeypatch, caplog): from bellows.zigbee import application @@ -786,15 +732,14 @@ async def counters_mock(): return ([0, 1, 2, 3],) raise asyncio.TimeoutError - app._ezsp.readCounters = mock.MagicMock(side_effect=counters_mock) - app._handle_reset_request = mock.MagicMock() + app._ezsp.readCounters = AsyncMock(side_effect=counters_mock) + app._handle_reset_request = MagicMock() app._ctrl_event.set() caplog.set_level(logging.DEBUG, "bellows.zigbee.application") await app._watchdog() -@pytest.mark.asyncio async def test_shutdown(app): reset_f = asyncio.Future() watchdog_f = asyncio.Future() @@ -820,13 +765,10 @@ def coordinator(app, ieee): return bellows.zigbee.application.EZSPCoordinator(app, ieee, 0x0000, dev) -@pytest.mark.asyncio async def test_ezsp_add_to_group(coordinator): - coordinator.application._multicast = mock.MagicMock() + coordinator.application._multicast = MagicMock() mc = coordinator.application._multicast - mc.subscribe.side_effect = asyncio.coroutine( - mock.MagicMock(return_value=t.EmberStatus.SUCCESS) - ) + mc.subscribe = AsyncMock(return_value=t.EmberStatus.SUCCESS) grp_id = 0x2345 assert grp_id not in coordinator.endpoints[1].member_of @@ -837,13 +779,10 @@ async def test_ezsp_add_to_group(coordinator): assert grp_id in coordinator.endpoints[1].member_of -@pytest.mark.asyncio async def test_ezsp_add_to_group_ep(coordinator): - coordinator.application._multicast = mock.MagicMock() + coordinator.application._multicast = MagicMock() mc = coordinator.application._multicast - mc.subscribe.side_effect = asyncio.coroutine( - mock.MagicMock(return_value=t.EmberStatus.SUCCESS) - ) + mc.subscribe = AsyncMock(return_value=t.EmberStatus.SUCCESS) grp_id = 0x2345 assert grp_id not in coordinator.endpoints[1].member_of @@ -859,13 +798,10 @@ async def test_ezsp_add_to_group_ep(coordinator): assert mc.subscribe.call_count == 0 -@pytest.mark.asyncio async def test_ezsp_add_to_group_fail(coordinator): - coordinator.application._multicast = mock.MagicMock() + coordinator.application._multicast = MagicMock() mc = coordinator.application._multicast - mc.subscribe.side_effect = asyncio.coroutine( - mock.MagicMock(return_value=t.EmberStatus.ERR_FATAL) - ) + mc.subscribe = AsyncMock(return_value=t.EmberStatus.ERR_FATAL) grp_id = 0x2345 assert grp_id not in coordinator.endpoints[1].member_of @@ -876,13 +812,10 @@ async def test_ezsp_add_to_group_fail(coordinator): assert grp_id not in coordinator.endpoints[1].member_of -@pytest.mark.asyncio async def test_ezsp_add_to_group_ep_fail(coordinator): - coordinator.application._multicast = mock.MagicMock() + coordinator.application._multicast = MagicMock() mc = coordinator.application._multicast - mc.subscribe.side_effect = asyncio.coroutine( - mock.MagicMock(return_value=t.EmberStatus.ERR_FATAL) - ) + mc.subscribe = AsyncMock(return_value=t.EmberStatus.ERR_FATAL) grp_id = 0x2345 assert grp_id not in coordinator.endpoints[1].member_of @@ -894,13 +827,10 @@ async def test_ezsp_add_to_group_ep_fail(coordinator): assert grp_id not in coordinator.endpoints[1].member_of -@pytest.mark.asyncio async def test_ezsp_remove_from_group(coordinator): - coordinator.application._multicast = mock.MagicMock() + coordinator.application._multicast = MagicMock() mc = coordinator.application._multicast - mc.unsubscribe.side_effect = asyncio.coroutine( - mock.MagicMock(return_value=t.EmberStatus.SUCCESS) - ) + mc.unsubscribe = AsyncMock(return_value=t.EmberStatus.SUCCESS) grp_id = 0x2345 grp = coordinator.application.groups.add_group(grp_id) @@ -914,13 +844,10 @@ async def test_ezsp_remove_from_group(coordinator): assert grp_id not in coordinator.endpoints[1].member_of -@pytest.mark.asyncio async def test_ezsp_remove_from_group_ep(coordinator): - coordinator.application._multicast = mock.MagicMock() + coordinator.application._multicast = MagicMock() mc = coordinator.application._multicast - mc.unsubscribe.side_effect = asyncio.coroutine( - mock.MagicMock(return_value=t.EmberStatus.SUCCESS) - ) + mc.unsubscribe = AsyncMock(return_value=t.EmberStatus.SUCCESS) grp_id = 0x2345 grp = coordinator.application.groups.add_group(grp_id) @@ -939,13 +866,10 @@ async def test_ezsp_remove_from_group_ep(coordinator): assert mc.subscribe.call_count == 0 -@pytest.mark.asyncio async def test_ezsp_remove_from_group_fail(coordinator): - coordinator.application._multicast = mock.MagicMock() + coordinator.application._multicast = MagicMock() mc = coordinator.application._multicast - mc.unsubscribe.side_effect = asyncio.coroutine( - mock.MagicMock(return_value=t.EmberStatus.ERR_FATAL) - ) + mc.unsubscribe = AsyncMock(return_value=t.EmberStatus.ERR_FATAL) grp_id = 0x2345 grp = coordinator.application.groups.add_group(grp_id) @@ -958,13 +882,10 @@ async def test_ezsp_remove_from_group_fail(coordinator): assert mc.unsubscribe.call_args[0][0] == grp_id -@pytest.mark.asyncio async def test_ezsp_remove_from_group_fail_ep(coordinator): - coordinator.application._multicast = mock.MagicMock() + coordinator.application._multicast = MagicMock() mc = coordinator.application._multicast - mc.unsubscribe.side_effect = asyncio.coroutine( - mock.MagicMock(return_value=t.EmberStatus.ERR_FATAL) - ) + mc.unsubscribe = AsyncMock(return_value=t.EmberStatus.ERR_FATAL) grp_id = 0x2345 grp = coordinator.application.groups.add_group(grp_id) @@ -984,7 +905,6 @@ def test_coordinator_model_manuf(coordinator): assert coordinator.endpoints[1].model -@pytest.mark.asyncio async def _mrequest( app, send_success=True, @@ -1012,39 +932,33 @@ async def mocksend(method, nwk, aps_frame, seq, data): return res -@pytest.mark.asyncio async def test_mrequest(app): res = await _mrequest(app) assert res[0] == 0 -@pytest.mark.asyncio async def test_mrequest_ack_timeout(app, aps): with pytest.raises(asyncio.TimeoutError): await _mrequest(app, send_ack_received=False) -@pytest.mark.asyncio async def test_mrequest_send_unicast_fail(app): res = await _mrequest(app, send_success=False) assert res[0] != 0 -@pytest.mark.asyncio async def test_mrequest_ezsp_failed(app): with pytest.raises(EzspError): await _mrequest(app, ezsp_operational=False) assert len(app._pending) == 0 -@pytest.mark.asyncio async def test_mrequest_send_timeout(app): with pytest.raises(asyncio.TimeoutError): await _mrequest(app, send_ack_received=False) assert app._pending == {} -@pytest.mark.asyncio async def test_mrequest_ctrl_not_running(app): app._ctrl_event.clear() with pytest.raises(ControllerError): @@ -1053,44 +967,44 @@ async def test_mrequest_ctrl_not_running(app): def test_handle_route_record(app): """Test route record handling for an existing device.""" - s = mock.sentinel - dev = mock.MagicMock() - app.handle_join = mock.MagicMock() - app.get_device = mock.MagicMock(return_value=dev) + dev = MagicMock() + app.handle_join = MagicMock() + app.get_device = MagicMock(return_value=dev) app.ezsp_callback_handler( - "incomingRouteRecordHandler", [s.nwk, s.ieee, s.lqi, s.rssi, s.relays] + "incomingRouteRecordHandler", + [sentinel.nwk, sentinel.ieee, sentinel.lqi, sentinel.rssi, sentinel.relays], ) - assert dev.relays is s.relays + assert dev.relays is sentinel.relays assert app.handle_join.call_count == 0 def test_handle_route_record_unkn(app): """Test route record handling for an unknown device.""" - s = mock.sentinel - app.handle_join = mock.MagicMock() - app.get_device = mock.MagicMock(side_effect=KeyError) + app.handle_join = MagicMock() + app.get_device = MagicMock(side_effect=KeyError) app.ezsp_callback_handler( - "incomingRouteRecordHandler", [s.nwk, s.ieee, s.lqi, s.rssi, s.relays] + "incomingRouteRecordHandler", + [sentinel.nwk, sentinel.ieee, sentinel.lqi, sentinel.rssi, sentinel.relays], ) assert app.handle_join.call_count == 1 - assert app.handle_join.call_args[0][0] is s.nwk - assert app.handle_join.call_args[0][1] is s.ieee + assert app.handle_join.call_args[0][0] is sentinel.nwk + assert app.handle_join.call_args[0][1] is sentinel.ieee def test_handle_route_error(app): """Test route error handler.""" - dev = mock.MagicMock() - dev.relays = mock.sentinel.old_relays - app.get_device = mock.MagicMock(return_value=dev) + dev = MagicMock() + dev.relays = sentinel.old_relays + app.get_device = MagicMock(return_value=dev) app.ezsp_callback_handler( - "incomingRouteErrorHandler", [mock.sentinel.status, mock.sentinel.nwk] + "incomingRouteErrorHandler", [sentinel.status, sentinel.nwk] ) assert dev.relays is None - app.get_device = mock.MagicMock(side_effect=KeyError) + app.get_device = MagicMock(side_effect=KeyError) app.ezsp_callback_handler( - "incomingRouteErrorHandler", [mock.sentinel.status, mock.sentinel.nwk] + "incomingRouteErrorHandler", [sentinel.status, sentinel.nwk] ) @@ -1117,9 +1031,8 @@ def test_src_rtg_config(config, result): assert ctrl.use_source_routing is result -@pytest.mark.asyncio -@mock.patch.object(ezsp.EZSP, "reset", new_callable=CoroutineMock) -@mock.patch.object(uart, "connect") +@patch.object(ezsp.EZSP, "reset", new_callable=AsyncMock) +@patch("bellows.uart.connect", return_value=MagicMock(spec_set=uart.Gateway)) async def test_probe_success(mock_connect, mock_reset): """Test device probing.""" @@ -1145,7 +1058,7 @@ def test_handle_id_conflict(app, ieee): """Test handling of an ID confict report.""" nwk = t.EmberNodeId(0x1234) app.add_device(ieee, nwk) - app.handle_leave = mock.MagicMock() + app.handle_leave = MagicMock() app.ezsp_callback_handler("idConflictHandler", [nwk + 1]) assert app.handle_leave.call_count == 0 @@ -1155,66 +1068,61 @@ def test_handle_id_conflict(app, ieee): assert app.handle_leave.call_args[0][0] == nwk -@pytest.mark.asyncio async def test_handle_no_such_device(app, ieee): """Test handling of an unknown device IEEE lookup.""" - p1 = mock.patch.object( + p1 = patch.object( app._ezsp, "lookupEui64ByNodeId", - CoroutineMock(return_value=(t.EmberStatus.ERR_FATAL, ieee)), + AsyncMock(return_value=(t.EmberStatus.ERR_FATAL, ieee)), ) - p2 = mock.patch.object(app, "handle_join") + p2 = patch.object(app, "handle_join") with p1 as lookup_mock, p2 as handle_join_mock: - await app._handle_no_such_device(mock.sentinel.nwk) + await app._handle_no_such_device(sentinel.nwk) assert lookup_mock.await_count == 1 - assert lookup_mock.await_args[0][0] is mock.sentinel.nwk + assert lookup_mock.await_args[0][0] is sentinel.nwk assert handle_join_mock.call_count == 0 - p1 = mock.patch.object( + p1 = patch.object( app._ezsp, "lookupEui64ByNodeId", - CoroutineMock(return_value=(t.EmberStatus.SUCCESS, mock.sentinel.ieee)), + AsyncMock(return_value=(t.EmberStatus.SUCCESS, sentinel.ieee)), ) with p1 as lookup_mock, p2 as handle_join_mock: - await app._handle_no_such_device(mock.sentinel.nwk) + await app._handle_no_such_device(sentinel.nwk) assert lookup_mock.await_count == 1 - assert lookup_mock.await_args[0][0] is mock.sentinel.nwk + assert lookup_mock.await_args[0][0] is sentinel.nwk assert handle_join_mock.call_count == 1 - assert handle_join_mock.call_args[0][0] == mock.sentinel.nwk - assert handle_join_mock.call_args[0][1] == mock.sentinel.ieee + assert handle_join_mock.call_args[0][0] == sentinel.nwk + assert handle_join_mock.call_args[0][1] == sentinel.ieee -@pytest.mark.asyncio async def test_cleanup_tc_link_key(app): """Test cleaning up tc link key.""" ezsp = app._ezsp - ezsp.findKeyTableEntry = CoroutineMock( - side_effect=((0xFF,), (mock.sentinel.index,)) - ) - ezsp.eraseKeyTableEntry = CoroutineMock(return_value=(0x00,)) + ezsp.findKeyTableEntry = AsyncMock(side_effect=((0xFF,), (sentinel.index,))) + ezsp.eraseKeyTableEntry = AsyncMock(return_value=(0x00,)) - await app.cleanup_tc_link_key(mock.sentinel.ieee) + await app.cleanup_tc_link_key(sentinel.ieee) assert ezsp.findKeyTableEntry.await_count == 1 - assert ezsp.findKeyTableEntry.await_args[0][0] is mock.sentinel.ieee + assert ezsp.findKeyTableEntry.await_args[0][0] is sentinel.ieee assert ezsp.eraseKeyTableEntry.await_count == 0 assert ezsp.eraseKeyTableEntry.call_count == 0 ezsp.findKeyTableEntry.reset_mock() - await app.cleanup_tc_link_key(mock.sentinel.ieee2) + await app.cleanup_tc_link_key(sentinel.ieee2) assert ezsp.findKeyTableEntry.await_count == 1 - assert ezsp.findKeyTableEntry.await_args[0][0] is mock.sentinel.ieee2 + assert ezsp.findKeyTableEntry.await_args[0][0] is sentinel.ieee2 assert ezsp.eraseKeyTableEntry.await_count == 1 - assert ezsp.eraseKeyTableEntry.await_args[0][0] is mock.sentinel.index + assert ezsp.eraseKeyTableEntry.await_args[0][0] is sentinel.index -@pytest.mark.asyncio -@mock.patch("zigpy.application.ControllerApplication.permit", new=CoroutineMock()) +@patch("zigpy.application.ControllerApplication.permit", new=AsyncMock()) async def test_permit(app): """Test permit method.""" ezsp = app._ezsp - ezsp.addTransientLinkKey = CoroutineMock(return_value=(t.EmberStatus.SUCCESS,)) - ezsp.pre_permit = CoroutineMock() + ezsp.addTransientLinkKey = AsyncMock(return_value=(t.EmberStatus.SUCCESS,)) + ezsp.pre_permit = AsyncMock() await app.permit(10, None) await asyncio.sleep(0) assert ezsp.addTransientLinkKey.await_count == 0 diff --git a/tests/test_ezsp.py b/tests/test_ezsp.py index f34f516d..9d27a01b 100644 --- a/tests/test_ezsp.py +++ b/tests/test_ezsp.py @@ -1,7 +1,6 @@ import asyncio import functools -from asynctest import CoroutineMock, mock import pytest import serial @@ -9,21 +8,26 @@ from bellows.exception import EzspError import bellows.ezsp.v4.types as t +from .async_mock import AsyncMock, MagicMock, call, patch, sentinel + DEVICE_CONFIG = { config.CONF_DEVICE_PATH: "/dev/null", config.CONF_DEVICE_BAUDRATE: 115200, } +pytestmark = pytest.mark.asyncio + @pytest.fixture async def ezsp_f(): api = ezsp.EZSP(DEVICE_CONFIG) - with mock.patch("bellows.uart.connect"): + gw = MagicMock(spec_set=uart.Gateway) + with patch("bellows.uart.connect", new=AsyncMock(return_value=gw)): await api.connect() yield api -def test_connect(ezsp_f, monkeypatch): +async def test_connect(ezsp_f, monkeypatch): connected = False async def mockconnect(*args, **kwargs): @@ -33,17 +37,15 @@ async def mockconnect(*args, **kwargs): monkeypatch.setattr(uart, "connect", mockconnect) ezsp_f._gw = None - loop = asyncio.get_event_loop() - loop.run_until_complete(ezsp_f.connect()) + await ezsp_f.connect() assert connected -@pytest.mark.asyncio async def test_reset(ezsp_f): - ezsp_f.stop_ezsp = mock.MagicMock() - ezsp_f.start_ezsp = mock.MagicMock() - reset_mock = asyncio.coroutine(mock.MagicMock()) - ezsp_f._gw.reset = mock.MagicMock(side_effect=reset_mock) + ezsp_f.stop_ezsp = MagicMock() + ezsp_f.start_ezsp = MagicMock() + reset_mock = AsyncMock() + ezsp_f._gw.reset = MagicMock(side_effect=reset_mock) await ezsp_f.reset() assert ezsp_f._gw.reset.call_count == 1 @@ -76,10 +78,9 @@ def test_non_existent_attr(ezsp_f): ezsp_f.nonexistentMethod() -@pytest.mark.asyncio def test_command(ezsp_f): ezsp_f.start_ezsp() - with mock.patch.object(ezsp_f._protocol, "command") as cmd_mock: + with patch.object(ezsp_f._protocol, "command") as cmd_mock: ezsp_f.nop() assert cmd_mock.call_count == 1 @@ -143,7 +144,7 @@ async def mockcommand(name, *args): ezsp_f._command = mockcommand loop = asyncio.get_event_loop() - return loop.run_until_complete(ezsp_f.formNetwork(mock.MagicMock())) + return loop.run_until_complete(ezsp_f.formNetwork(MagicMock())) def test_form_network(ezsp_f): @@ -161,14 +162,14 @@ def test_form_network_fail_stack_status(ezsp_f): def test_receive_new(ezsp_f): - callback = mock.MagicMock() + callback = MagicMock() ezsp_f.add_callback(callback) ezsp_f.frame_received(b"\x00\xff\x00\x04\x05\x06") assert callback.call_count == 1 def test_callback(ezsp_f): - testcb = mock.MagicMock() + testcb = MagicMock() cbid = ezsp_f.add_callback(testcb) ezsp_f.handle_callback(1, 2, 3) @@ -181,7 +182,7 @@ def test_callback(ezsp_f): def test_callback_multi(ezsp_f): - testcb = mock.MagicMock() + testcb = MagicMock() cbid1 = ezsp_f.add_callback(testcb) ezsp_f.add_callback(testcb) @@ -193,13 +194,11 @@ def test_callback_multi(ezsp_f): ezsp_f.remove_callback(cbid1) ezsp_f.handle_callback(4, 5, 6) - testcb.assert_has_calls( - [mock.call(1, 2, 3), mock.call(1, 2, 3), mock.call(4, 5, 6)] - ) + testcb.assert_has_calls([call(1, 2, 3), call(1, 2, 3), call(4, 5, 6)]) def test_callback_exc(ezsp_f): - testcb = mock.MagicMock() + testcb = MagicMock() testcb.side_effect = Exception("Testing") ezsp_f.add_callback(testcb) @@ -207,7 +206,6 @@ def test_callback_exc(ezsp_f): assert testcb.call_count == 1 -@pytest.mark.asyncio @pytest.mark.parametrize("version, call_count", ((4, 1), (5, 2), (6, 2))) async def test_change_version(ezsp_f, version, call_count): def mockcommand(name, *args): @@ -217,7 +215,7 @@ def mockcommand(name, *args): fut.set_result([version, 2, 2046]) return fut - ezsp_f._command = mock.MagicMock(side_effect=mockcommand) + ezsp_f._command = MagicMock(side_effect=mockcommand) await ezsp_f.version() assert ezsp_f.ezsp_version == version assert ezsp_f._command.call_count == call_count @@ -236,23 +234,23 @@ def test_start_ezsp(ezsp_f): def test_connection_lost(ezsp_f): - ezsp_f.enter_failed_state = mock.MagicMock(spec_set=ezsp_f.enter_failed_state) - ezsp_f.connection_lost(mock.sentinel.exc) + ezsp_f.enter_failed_state = MagicMock(spec_set=ezsp_f.enter_failed_state) + ezsp_f.connection_lost(sentinel.exc) assert ezsp_f.enter_failed_state.call_count == 1 -def test_enter_failed_state(ezsp_f): - ezsp_f.stop_ezsp = mock.MagicMock(spec_set=ezsp_f.stop_ezsp) - ezsp_f.handle_callback = mock.MagicMock(spec_set=ezsp_f.handle_callback) - ezsp_f.enter_failed_state(mock.sentinel.error) +async def test_enter_failed_state(ezsp_f): + ezsp_f.stop_ezsp = MagicMock(spec_set=ezsp_f.stop_ezsp) + ezsp_f.handle_callback = MagicMock(spec_set=ezsp_f.handle_callback) + ezsp_f.enter_failed_state(sentinel.error) + await asyncio.sleep(0) assert ezsp_f.stop_ezsp.call_count == 1 assert ezsp_f.handle_callback.call_count == 1 - assert ezsp_f.handle_callback.call_args[0][1][0] == mock.sentinel.error + assert ezsp_f.handle_callback.call_args[0][1][0] == sentinel.error -@pytest.mark.asyncio -@mock.patch.object(ezsp.EZSP, "reset", new_callable=CoroutineMock) -@mock.patch.object(uart, "connect") +@patch.object(ezsp.EZSP, "reset", new_callable=AsyncMock) +@patch("bellows.uart.connect", return_value=MagicMock(spec_set=uart.Gateway)) async def test_probe_success(mock_connect, mock_reset): """Test device probing.""" @@ -274,9 +272,8 @@ async def test_probe_success(mock_connect, mock_reset): assert mock_connect.return_value.close.call_count == 1 -@pytest.mark.asyncio -@mock.patch.object(ezsp.EZSP, "reset", new_callable=CoroutineMock) -@mock.patch.object(uart, "connect") +@patch.object(ezsp.EZSP, "reset", new_callable=AsyncMock) +@patch("bellows.uart.connect", return_value=MagicMock(spec_set=uart.Gateway)) @pytest.mark.parametrize( "exception", (asyncio.TimeoutError, serial.SerialException, EzspError) ) @@ -294,12 +291,11 @@ async def test_probe_fail(mock_connect, mock_reset, exception): assert mock_connect.return_value.close.call_count == 1 -@pytest.mark.asyncio -@mock.patch.object(ezsp.EZSP, "set_source_routing", new_callable=CoroutineMock) -@mock.patch("bellows.ezsp.v4.EZSPv4.initialize", new_callable=CoroutineMock) -@mock.patch.object(ezsp.EZSP, "version", new_callable=CoroutineMock) -@mock.patch.object(ezsp.EZSP, "reset", new_callable=CoroutineMock) -@mock.patch.object(uart, "connect") +@patch.object(ezsp.EZSP, "set_source_routing", new_callable=AsyncMock) +@patch("bellows.ezsp.v4.EZSPv4.initialize", new_callable=AsyncMock) +@patch.object(ezsp.EZSP, "version", new_callable=AsyncMock) +@patch.object(ezsp.EZSP, "reset", new_callable=AsyncMock) +@patch("bellows.uart.connect", return_value=MagicMock(spec_set=uart.Gateway)) async def test_ezsp_init( conn_mock, reset_mock, version_mock, prot_handler_mock, src_mock ): @@ -320,16 +316,14 @@ async def test_ezsp_init( assert src_mock.await_count == 1 -@pytest.mark.asyncio async def test_ezsp_newer_version(ezsp_f): """Test newer version of ezsp.""" - with mock.patch.object( - ezsp_f, "_command", new=CoroutineMock(return_value=(9, 0x12, 0x12345)) + with patch.object( + ezsp_f, "_command", new=AsyncMock(return_value=(9, 0x12, 0x12345)) ): await ezsp_f.version() -@pytest.mark.asyncio async def test_board_info(ezsp_f): """Test getting board info.""" @@ -345,13 +339,13 @@ async def cmd_mock(cmd_name, *args): if cmd_name == "getValue": return (status, b"\x01\x02\x03\x04\x05\x06") - with mock.patch.object(ezsp_f, "_command", new=cmd_mock): + with patch.object(ezsp_f, "_command", new=cmd_mock): mfg, brd, ver = await ezsp_f.get_board_info() assert mfg == "Manufacturer" assert brd == b"\xfe" assert ver == "3.4.5.6 build 513" - with mock.patch.object(ezsp_f, "_command", new=cmd_mock): + with patch.object(ezsp_f, "_command", new=cmd_mock): status = 0x01 mfg, brd, ver = await ezsp_f.get_board_info() assert mfg == "Manufacturer" @@ -359,14 +353,13 @@ async def cmd_mock(cmd_name, *args): assert ver == "unknown stack version" -@pytest.mark.asyncio async def test_set_source_route(ezsp_f): """Test setting a src route for device.""" - device = mock.MagicMock() + device = MagicMock() device.relays = None - with mock.patch.object(ezsp_f, "setSourceRoute", new=CoroutineMock()) as src_mock: - src_mock.return_value = (mock.sentinel.success,) + with patch.object(ezsp_f, "setSourceRoute", new=AsyncMock()) as src_mock: + src_mock.return_value = (sentinel.success,) res = await ezsp_f.set_source_route(device) assert src_mock.await_count == 0 assert res == (t.EmberStatus.ERR_FATAL,) @@ -374,25 +367,26 @@ async def test_set_source_route(ezsp_f): device.relays = [] res = await ezsp_f.set_source_route(device) assert src_mock.await_count == 1 - assert res == (mock.sentinel.success,) + assert res == (sentinel.success,) -def test_pre_permit(ezsp_f): - with mock.patch("bellows.ezsp.v4.EZSPv4.pre_permit") as pre_mock: - ezsp_f.pre_permit(mock.sentinel.time) +async def test_pre_permit(ezsp_f): + with patch("bellows.ezsp.v4.EZSPv4.pre_permit") as pre_mock: + await ezsp_f.pre_permit(sentinel.time) assert pre_mock.call_count == 1 + assert pre_mock.await_count == 1 -def test_update_policies(ezsp_f): - with mock.patch("bellows.ezsp.v4.EZSPv4.update_policies") as pol_mock: - ezsp_f.update_policies(mock.sentinel.time) +async def test_update_policies(ezsp_f): + with patch("bellows.ezsp.v4.EZSPv4.update_policies") as pol_mock: + await ezsp_f.update_policies(sentinel.time) assert pol_mock.call_count == 1 + assert pol_mock.await_count == 1 -@pytest.mark.asyncio async def test_set_concentrator(ezsp_f): """Test enabling source routing.""" - with mock.patch.object(ezsp_f, "setConcentrator", new=CoroutineMock()) as cnc_mock: + with patch.object(ezsp_f, "setConcentrator", new=AsyncMock()) as cnc_mock: cnc_mock.return_value = (ezsp_f.types.EmberStatus.SUCCESS,) await ezsp_f.set_source_routing() assert cnc_mock.await_count == 1 diff --git a/tests/test_ezsp_protocol.py b/tests/test_ezsp_protocol.py index fdb3e57e..c790986e 100644 --- a/tests/test_ezsp_protocol.py +++ b/tests/test_ezsp_protocol.py @@ -1,12 +1,15 @@ import asyncio import logging -from asynctest import CoroutineMock, mock import pytest import bellows.ezsp.v4 import bellows.ezsp.v4.types as t +from .async_mock import AsyncMock, MagicMock, patch + +pytestmark = pytest.mark.asyncio + class _DummyProtocolHandler(bellows.ezsp.v4.EZSPv4): """Protocol handler mock.""" @@ -23,7 +26,7 @@ def cb_mock(self): @pytest.fixture def prot_hndl(): """Protocol handler mock.""" - return _DummyProtocolHandler(mock.MagicMock(), mock.MagicMock()) + return _DummyProtocolHandler(MagicMock(), MagicMock()) @pytest.mark.asyncio @@ -35,7 +38,7 @@ async def test_command(prot_hndl): def test_receive_reply(prot_hndl): - callback_mock = mock.MagicMock(spec_set=asyncio.Future) + callback_mock = MagicMock(spec_set=asyncio.Future) prot_hndl._awaiting[0] = (0, prot_hndl.COMMANDS["version"][2], callback_mock) prot_hndl(b"\x00\xff\x00\x04\x05\x06") @@ -47,7 +50,7 @@ def test_receive_reply(prot_hndl): def test_receive_reply_after_timeout(prot_hndl): - callback_mock = mock.MagicMock(spec_set=asyncio.Future) + callback_mock = MagicMock(spec_set=asyncio.Future) callback_mock.set_result.side_effect = asyncio.InvalidStateError() prot_hndl._awaiting[0] = (0, prot_hndl.COMMANDS["version"][2], callback_mock) prot_hndl(b"\x00\xff\x00\x04\x05\x06") @@ -63,7 +66,7 @@ def test_receive_reply_after_timeout(prot_hndl): async def test_cfg_initialize(prot_hndl, caplog): """Test initialization.""" - p1 = mock.patch.object(prot_hndl, "setConfigurationValue", new=CoroutineMock()) + p1 = patch.object(prot_hndl, "setConfigurationValue", new=AsyncMock()) with p1 as cfg_mock: cfg_mock.return_value = (t.EzspStatus.SUCCESS,) await prot_hndl.initialize({"ezsp_config": {}, "source_routing": True}) @@ -78,7 +81,7 @@ async def test_cfg_initialize(prot_hndl, caplog): async def test_update_policies(prot_hndl): """Test update_policies.""" - with mock.patch.object(prot_hndl, "setPolicy", new=CoroutineMock()) as pol_mock: + with patch.object(prot_hndl, "setPolicy", new=AsyncMock()) as pol_mock: pol_mock.return_value = (t.EzspStatus.SUCCESS,) await prot_hndl.update_policies({"ezsp_policies": {}}) diff --git a/tests/test_ezsp_v4.py b/tests/test_ezsp_v4.py index b6939096..54ba08a5 100644 --- a/tests/test_ezsp_v4.py +++ b/tests/test_ezsp_v4.py @@ -1,4 +1,5 @@ -from asynctest import mock +from unittest import mock + import pytest import bellows.ezsp.v4 diff --git a/tests/test_ezsp_v5.py b/tests/test_ezsp_v5.py index a3495ebb..41a78396 100644 --- a/tests/test_ezsp_v5.py +++ b/tests/test_ezsp_v5.py @@ -1,13 +1,16 @@ -from asynctest import CoroutineMock, mock import pytest import bellows.ezsp.v5 +from .async_mock import AsyncMock, MagicMock, patch + +pytestmark = pytest.mark.asyncio + @pytest.fixture def ezsp_f(): """EZSP v5 protocol handler.""" - return bellows.ezsp.v5.EZSPv5(mock.MagicMock(), mock.MagicMock()) + return bellows.ezsp.v5.EZSPv5(MagicMock(), MagicMock()) def test_ezsp_frame(ezsp_f): @@ -24,10 +27,9 @@ def test_ezsp_frame_rx(ezsp_f): assert ezsp_f._handle_callback.call_args[0][1] == [0x01, 0x02, 0x1234] -@pytest.mark.asyncio async def test_pre_permit(ezsp_f): """Test pre permit.""" - p2 = mock.patch.object(ezsp_f, "addTransientLinkKey", new=CoroutineMock()) + p2 = patch.object(ezsp_f, "addTransientLinkKey", new=AsyncMock()) with p2 as tclk_mock: await ezsp_f.pre_permit(1) assert tclk_mock.await_count == 1 diff --git a/tests/test_ezsp_v6.py b/tests/test_ezsp_v6.py index c4697e2c..7e53fbab 100644 --- a/tests/test_ezsp_v6.py +++ b/tests/test_ezsp_v6.py @@ -1,4 +1,5 @@ -from asynctest import mock +from unittest import mock + import pytest import bellows.ezsp.v6 diff --git a/tests/test_ezsp_v7.py b/tests/test_ezsp_v7.py index 34a5c730..d5844524 100644 --- a/tests/test_ezsp_v7.py +++ b/tests/test_ezsp_v7.py @@ -1,4 +1,5 @@ -from asynctest import mock +from unittest import mock + import pytest import bellows.ezsp.v7 diff --git a/tests/test_ezsp_v8.py b/tests/test_ezsp_v8.py index 8b424a3b..6ea14672 100644 --- a/tests/test_ezsp_v8.py +++ b/tests/test_ezsp_v8.py @@ -1,13 +1,16 @@ -from asynctest import CoroutineMock, mock import pytest import bellows.ezsp.v8 +from .async_mock import AsyncMock, MagicMock, patch + +pytestmark = pytest.mark.asyncio + @pytest.fixture def ezsp_f(): """EZSP v8 protocol handler.""" - return bellows.ezsp.v8.EZSPv8(mock.MagicMock(), mock.MagicMock()) + return bellows.ezsp.v8.EZSPv8(MagicMock(), MagicMock()) def test_ezsp_frame(ezsp_f): @@ -27,8 +30,8 @@ def test_ezsp_frame_rx(ezsp_f): @pytest.mark.asyncio async def test_set_source_routing(ezsp_f): """Test setting source routing.""" - with mock.patch.object( - ezsp_f, "setSourceRouteDiscoveryMode", new=CoroutineMock() + with patch.object( + ezsp_f, "setSourceRouteDiscoveryMode", new=AsyncMock() ) as src_mock: await ezsp_f.set_source_routing() assert src_mock.await_count == 1 @@ -37,8 +40,8 @@ async def test_set_source_routing(ezsp_f): @pytest.mark.asyncio async def test_pre_permit(ezsp_f): """Test pre permit.""" - p1 = mock.patch.object(ezsp_f, "setPolicy", new=CoroutineMock()) - p2 = mock.patch.object(ezsp_f, "addTransientLinkKey", new=CoroutineMock()) + p1 = patch.object(ezsp_f, "setPolicy", new=AsyncMock()) + p2 = patch.object(ezsp_f, "addTransientLinkKey", new=AsyncMock()) with p1 as pre_permit_mock, p2 as tclk_mock: await ezsp_f.pre_permit(-1.9) assert pre_permit_mock.await_count == 2 diff --git a/tests/test_multicast.py b/tests/test_multicast.py index 1843c7a9..5ae884c1 100644 --- a/tests/test_multicast.py +++ b/tests/test_multicast.py @@ -1,4 +1,3 @@ -from asynctest import CoroutineMock, mock import pytest from zigpy.endpoint import Endpoint @@ -6,13 +5,17 @@ import bellows.multicast import bellows.types as t +from .async_mock import AsyncMock, MagicMock, sentinel + +pytestmark = pytest.mark.asyncio + CUSTOM_SIZE = 12 @pytest.fixture def ezsp_f(): - e = mock.MagicMock() - e.getConfigurationValue = CoroutineMock(return_value=[0, CUSTOM_SIZE]) + e = MagicMock() + e.getConfigurationValue = AsyncMock(return_value=[0, CUSTOM_SIZE]) return e @@ -79,18 +82,18 @@ async def mock_get(*args): @pytest.mark.asyncio async def test_startup(multicast): - coordinator = mock.MagicMock() - ep1 = mock.MagicMock(spec_set=Endpoint) - ep1.member_of = [mock.sentinel.grp, mock.sentinel.grp, mock.sentinel.grp] - coordinator.endpoints = {0: mock.sentinel.ZDO, 1: ep1} - multicast._initialize = CoroutineMock() - multicast.subscribe = mock.MagicMock() - multicast.subscribe.side_effect = CoroutineMock() + coordinator = MagicMock() + ep1 = MagicMock(spec_set=Endpoint) + ep1.member_of = [sentinel.grp, sentinel.grp, sentinel.grp] + coordinator.endpoints = {0: sentinel.ZDO, 1: ep1} + multicast._initialize = AsyncMock() + multicast.subscribe = MagicMock() + multicast.subscribe.side_effect = AsyncMock() await multicast.startup(coordinator) assert multicast._initialize.await_count == 1 assert multicast.subscribe.call_count == len(ep1.member_of) - assert multicast.subscribe.call_args[0][0] == mock.sentinel.grp + assert multicast.subscribe.call_args[0][0] == sentinel.grp def _subscribe(multicast, group_id, success=True): @@ -99,7 +102,7 @@ async def mock_set(*args): return [t.EmberStatus.SUCCESS] return [t.EmberStatus.ERR_FATAL] - multicast._ezsp.setMulticastTableEntry = mock.MagicMock() + multicast._ezsp.setMulticastTableEntry = MagicMock() multicast._ezsp.setMulticastTableEntry.side_effect = mock_set return multicast.subscribe(group_id) @@ -153,7 +156,7 @@ async def mock_set(*args): return [t.EmberStatus.SUCCESS] return [t.EmberStatus.ERR_FATAL] - multicast._ezsp.setMulticastTableEntry = mock.MagicMock() + multicast._ezsp.setMulticastTableEntry = MagicMock() multicast._ezsp.setMulticastTableEntry.side_effect = mock_set return multicast.unsubscribe(group_id) diff --git a/tests/test_thread.py b/tests/test_thread.py index 3683c023..fa51cfee 100644 --- a/tests/test_thread.py +++ b/tests/test_thread.py @@ -7,8 +7,9 @@ from bellows.thread import EventLoopThread, ThreadsafeProxy +pytestmark = pytest.mark.asyncio + -@pytest.mark.asyncio async def test_thread_start(monkeypatch): current_loop = asyncio.get_event_loop() loopmock = mock.MagicMock() @@ -63,7 +64,6 @@ async def yield_other_thread(thread): raise exception_collector.exceptions[0] -@pytest.mark.asyncio async def test_thread_loop(thread): async def test_coroutine(): return mock.sentinel.result @@ -73,7 +73,6 @@ async def test_coroutine(): assert result is mock.sentinel.result -@pytest.mark.asyncio async def test_thread_double_start(thread): previous_loop = thread.loop await thread.start() @@ -83,13 +82,11 @@ async def test_thread_double_start(thread): assert thread.loop is previous_loop -@pytest.mark.asyncio async def test_thread_already_stopped(thread): thread.force_stop() thread.force_stop() -@pytest.mark.asyncio async def test_thread_run_coroutine_threadsafe(thread): inner_loop = None @@ -103,7 +100,6 @@ async def test_coroutine(): assert inner_loop is thread.loop -@pytest.mark.asyncio async def test_proxy_callback(thread): obj = mock.MagicMock() proxy = ThreadsafeProxy(obj, thread.loop) @@ -113,7 +109,6 @@ async def test_proxy_callback(thread): assert obj.test.call_count == 1 -@pytest.mark.asyncio async def test_proxy_async(thread): obj = mock.MagicMock() proxy = ThreadsafeProxy(obj, thread.loop) @@ -132,7 +127,6 @@ async def magic(): assert result == mock.sentinel.result -@pytest.mark.asyncio async def test_proxy_bad_function(thread): obj = mock.MagicMock() proxy = ThreadsafeProxy(obj, thread.loop) @@ -143,7 +137,6 @@ async def test_proxy_bad_function(thread): await yield_other_thread(thread) -@pytest.mark.asyncio async def test_proxy_not_function(): loop = asyncio.get_event_loop() obj = mock.MagicMock() @@ -153,7 +146,6 @@ async def test_proxy_not_function(): proxy.test -@pytest.mark.asyncio async def test_proxy_no_thread(): loop = asyncio.get_event_loop() obj = mock.MagicMock() diff --git a/tests/test_uart.py b/tests/test_uart.py index b44ca47c..11d208c7 100644 --- a/tests/test_uart.py +++ b/tests/test_uart.py @@ -1,19 +1,21 @@ import asyncio import threading -from asynctest import CoroutineMock, mock import pytest import serial_asyncio from bellows import uart import bellows.config as conf +from .async_mock import AsyncMock, MagicMock, sentinel + +pytestmark = pytest.mark.asyncio + -@pytest.mark.asyncio async def test_connect(monkeypatch): - portmock = mock.MagicMock() - appmock = mock.MagicMock() - transport = mock.MagicMock() + portmock = MagicMock() + appmock = MagicMock() + transport = MagicMock() async def mockconnect(loop, protocol_factory, **kwargs): protocol = protocol_factory() @@ -32,12 +34,11 @@ async def mockconnect(loop, protocol_factory, **kwargs): gw.close() -@pytest.mark.asyncio async def test_connect_threaded(monkeypatch): - portmock = mock.MagicMock() - appmock = mock.MagicMock() - transport = mock.MagicMock() + portmock = MagicMock() + appmock = MagicMock() + transport = MagicMock() async def mockconnect(loop, protocol_factory, **kwargs): protocol = protocol_factory() @@ -63,14 +64,13 @@ def on_transport_close(): assert len(threads) == 0 -@pytest.mark.asyncio async def test_connect_threaded_failure(monkeypatch): - portmock = mock.MagicMock() - appmock = mock.MagicMock() - transport = mock.MagicMock() + portmock = MagicMock() + appmock = MagicMock() + transport = MagicMock() - mockconnect = CoroutineMock() + mockconnect = AsyncMock() mockconnect.side_effect = OSError monkeypatch.setattr(serial_asyncio, "create_serial_connection", mockconnect) @@ -93,8 +93,8 @@ def on_transport_close(): @pytest.fixture def gw(): - gw = uart.Gateway(mock.MagicMock()) - gw._transport = mock.MagicMock() + gw = uart.Gateway(MagicMock()) + gw._transport = MagicMock() return gw @@ -129,7 +129,7 @@ def test_data_frame(gw): def test_cancel_received(gw): - gw.rst_frame_received = mock.MagicMock() + gw.rst_frame_received = MagicMock() gw.data_received(b"garbage") gw.data_received(b"\x1a\xc0\x38\xbc\x7e") assert gw.rst_frame_received.call_count == 1 @@ -137,7 +137,7 @@ def test_cancel_received(gw): def test_substitute_received(gw): - gw.rst_frame_received = mock.MagicMock() + gw.rst_frame_received = MagicMock() gw.data_received(b"garbage") gw.data_received(b"\x18\x38\xbc\x7epart") gw.data_received(b"ial") @@ -146,7 +146,7 @@ def test_substitute_received(gw): def test_partial_data_received(gw): - gw.write = mock.MagicMock() + gw.write = MagicMock() gw.data_received(b"\x54\x79\xa1\xb0") gw.data_received(b"\x50\xf2\x6e\x7e") assert gw.write.call_count == 1 @@ -154,14 +154,14 @@ def test_partial_data_received(gw): def test_crc_error(gw): - gw.write = mock.MagicMock() + gw.write = MagicMock() gw.data_received(b"L\xa1\x8e\x03\xcd\x07\xb9Y\xfbG%\xae\xbd~") assert gw.write.call_count == 1 assert gw._application.frame_received.call_count == 0 def test_crc_error_and_valid_frame(gw): - gw.write = mock.MagicMock() + gw.write = MagicMock() gw.data_received( b"L\xa1\x8e\x03\xcd\x07\xb9Y\xfbG%\xae\xbd~\x54\x79\xa1\xb0\x50\xf2\x6e\x7e" ) @@ -170,7 +170,7 @@ def test_crc_error_and_valid_frame(gw): def test_data_frame_received(gw): - gw.write = mock.MagicMock() + gw.write = MagicMock() gw.data_received(b"\x54\x79\xa1\xb0\x50\xf2\x6e\x7e") assert gw.write.call_count == 1 assert gw._application.frame_received.call_count == 1 @@ -189,21 +189,21 @@ def test_rst_frame_received(gw): def test_rstack_frame_received(gw): - gw._reset_future = mock.MagicMock() - gw._reset_future.done = mock.MagicMock(return_value=False) + gw._reset_future = MagicMock() + gw._reset_future.done = MagicMock(return_value=False) gw.data_received(b"\xc1\x02\x0b\nR\x7e") assert gw._reset_future.done.call_count == 1 assert gw._reset_future.set_result.call_count == 1 def test_wrong_rstack_frame_received(gw): - gw._reset_future = mock.MagicMock() + gw._reset_future = MagicMock() gw.data_received(b"\xc1\x02\x01\xab\x18\x7e") assert gw._reset_future.set_result.call_count == 0 def test_error_rstack_frame_received(gw): - gw._reset_future = mock.MagicMock() + gw._reset_future = MagicMock() gw.data_received(b"\xc1\x02\x81\x3a\x90\x7e") assert gw._reset_future.set_result.call_count == 0 @@ -213,8 +213,8 @@ def test_rstack_frame_received_nofut(gw): def test_rstack_frame_received_out_of_order(gw): - gw._reset_future = mock.MagicMock() - gw._reset_future.done = mock.MagicMock(return_value=True) + gw._reset_future = MagicMock() + gw._reset_future.done = MagicMock(return_value=True) gw.data_received(b"\xc1\x02\x0b\nR\x7e") assert gw._reset_future.done.call_count == 1 assert gw._reset_future.set_result.call_count == 0 @@ -238,14 +238,13 @@ def test_close(gw): assert gw._transport.close.call_count == 1 -@pytest.mark.asyncio async def test_reset(gw): gw._loop = asyncio.get_event_loop() - gw._sendq.put_nowait(mock.sentinel.queue_item) + gw._sendq.put_nowait(sentinel.queue_item) fut = asyncio.Future() - gw._pending = (mock.sentinel.seq, fut) + gw._pending = (sentinel.seq, fut) gw._transport.write.side_effect = lambda *args: gw._reset_future.set_result( - mock.sentinel.reset_result + sentinel.reset_result ) reset_result = await gw.reset() @@ -257,10 +256,9 @@ async def test_reset(gw): assert fut.done() assert gw._pending == (-1, None) - assert reset_result is mock.sentinel.reset_result + assert reset_result is sentinel.reset_result -@pytest.mark.asyncio async def test_reset_timeout(gw, monkeypatch): gw._loop = asyncio.get_event_loop() monkeypatch.setattr(uart, "RESET_TIMEOUT", 0.1) @@ -268,14 +266,13 @@ async def test_reset_timeout(gw, monkeypatch): await gw.reset() -@pytest.mark.asyncio async def test_reset_old(gw): gw._loop = asyncio.get_event_loop() future = asyncio.get_event_loop().create_future() - future.set_result(mock.sentinel.result) + future.set_result(sentinel.result) gw._reset_future = future ret = await gw.reset() - assert ret == mock.sentinel.result + assert ret == sentinel.result gw._transport.write.assert_not_called() @@ -304,11 +301,11 @@ def mockwrite(data): def test_connection_lost_exc(gw): - gw.connection_lost(mock.sentinel.exception) + gw.connection_lost(sentinel.exception) conn_lost = gw._application.connection_lost assert conn_lost.call_count == 1 - assert conn_lost.call_args[0][0] is mock.sentinel.exception + assert conn_lost.call_args[0][0] is sentinel.exception def test_connection_closed(gw): From 28fa74e3a14fca6ce0ee9e202882a74b6aaedf92 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 28 Sep 2020 17:54:03 -0400 Subject: [PATCH 6/7] Update decision bitmask when using install code to join (#353) --- bellows/zigbee/application.py | 14 +++++------ tests/test_application.py | 44 ++++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/bellows/zigbee/application.py b/bellows/zigbee/application.py index 35cfba5c..4479b120 100644 --- a/bellows/zigbee/application.py +++ b/bellows/zigbee/application.py @@ -540,16 +540,14 @@ async def permit_with_key(self, node, code, time_s=60): if v[0] != t.EmberStatus.SUCCESS: raise Exception("Failed to set link key") - v = await self._ezsp.setPolicy( - self._ezsp.types.EzspPolicyId.TC_KEY_REQUEST_POLICY, - self._ezsp.types.EzspDecisionId.GENERATE_NEW_TC_LINK_KEY, - ) - if v[0] != t.EmberStatus.SUCCESS: - raise Exception( - "Failed to change policy to allow generation of new trust center keys" + if self._ezsp.ezsp_version >= 8: + mask_type = self._ezsp.types.EzspDecisionBitmask.ALLOW_JOINS + bitmask = mask_type.ALLOW_JOINS | mask_type.JOINS_USE_INSTALL_CODE_KEY + await self._ezsp.setPolicy( + self._ezsp.types.EzspPolicyId.TRUST_CENTER_POLICY, bitmask ) - return await self.permit(time_s) + return await super().permit(time_s) def _handle_id_conflict(self, nwk: t.EmberNodeId) -> None: LOGGER.warning("NWK conflict is reported for 0x%04x", nwk) diff --git a/tests/test_application.py b/tests/test_application.py index 2400f65a..738359d2 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -301,34 +301,46 @@ def test_permit_ncp(app): assert app._ezsp.permitJoining.call_count == 1 -async def test_permit_with_key(app): +@pytest.mark.parametrize( + "version, tc_policy_count", ((4, 0), (5, 0), (6, 0), (7, 0), (8, 1)) +) +@patch("zigpy.application.ControllerApplication.permit") +async def test_permit_with_key(permit_mock, app, version, tc_policy_count): app._ezsp.addTransientLinkKey = AsyncMock(return_value=[0]) app._ezsp.setPolicy = AsyncMock(return_value=[0]) - app.permit = AsyncMock(return_value=[0]) - await app.permit_with_key( - bytes([1, 2, 3, 4, 5, 6, 7, 8]), - bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), - 60, - ) + permit_mock.reset_mock() + with patch.object(app._ezsp, "ezsp_version", version): + await app.permit_with_key( + bytes([1, 2, 3, 4, 5, 6, 7, 8]), + bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), + 60, + ) assert app._ezsp.addTransientLinkKey.await_count == 1 - assert app.permit.await_count == 1 + assert permit_mock.await_count == 1 + assert app._ezsp.setPolicy.await_count == tc_policy_count -async def test_permit_with_key_ieee(app, ieee): +@pytest.mark.parametrize( + "version, tc_policy_count", ((4, 0), (5, 0), (6, 0), (7, 0), (8, 1)) +) +@patch("zigpy.application.ControllerApplication.permit") +async def test_permit_with_key_ieee(permit_mock, app, ieee, version, tc_policy_count): app._ezsp.addTransientLinkKey = AsyncMock(return_value=[0]) app._ezsp.setPolicy = AsyncMock(return_value=[0]) - app.permit = AsyncMock(return_value=[0]) - await app.permit_with_key( - ieee, - bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), - 60, - ) + permit_mock.reset_mock() + with patch.object(app._ezsp, "ezsp_version", version): + await app.permit_with_key( + ieee, + bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x4A, 0xF7]), + 60, + ) assert app._ezsp.addTransientLinkKey.await_count == 1 - assert app.permit.await_count == 1 + assert permit_mock.await_count == 1 + assert app._ezsp.setPolicy.await_count == tc_policy_count async def test_permit_with_key_invalid_install_code(app, ieee): From b377fb72f5e43d328cb48d996aa117ee263e6f23 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 28 Sep 2020 18:07:25 -0400 Subject: [PATCH 7/7] 0.20.3 Version bump --- bellows/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bellows/__init__.py b/bellows/__init__.py index 2ced9486..d237cdc1 100644 --- a/bellows/__init__.py +++ b/bellows/__init__.py @@ -1,5 +1,5 @@ MAJOR_VERSION = 0 -MINOR_VERSION = 21 -PATCH_VERSION = "0.dev0" +MINOR_VERSION = 20 +PATCH_VERSION = "3" __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION) __version__ = "{}.{}".format(__short_version__, PATCH_VERSION)