diff --git a/circuitpython_mocks/__init__.py b/circuitpython_mocks/__init__.py index 1bad66d..e69de29 100644 --- a/circuitpython_mocks/__init__.py +++ b/circuitpython_mocks/__init__.py @@ -1,16 +0,0 @@ -from pathlib import Path -import pytest - - -@pytest.fixture(autouse=True) -def monkey_patch_sys_paths(monkeypatch: pytest.MonkeyPatch): - """A pytest fixture that monkey patches the Python runtime's import paths, such - that this package's mock modules can be imported first (instead from the - adafruit-blinka package). - - .. important:: - - This fixture is automatically used once imported into the test module. - """ - root_pkg = Path(__file__).parent - monkeypatch.syspath_prepend(str(root_pkg)) diff --git a/circuitpython_mocks/_mixins.py b/circuitpython_mocks/_mixins.py index 9bf63b2..714ad13 100644 --- a/circuitpython_mocks/_mixins.py +++ b/circuitpython_mocks/_mixins.py @@ -1,9 +1,17 @@ -from typing import Self from collections import deque -from typing import TYPE_CHECKING +from typing import Self, Deque, Union, TYPE_CHECKING if TYPE_CHECKING: - from circuitpython_mocks.busio.operations import Read, Write, Transfer + from circuitpython_mocks.busio.operations import ( + I2CRead, + I2CWrite, + I2CTransfer, + SPIRead, + SPIWrite, + SPITransfer, + UARTRead, + UARTWrite, + ) from circuitpython_mocks.digitalio.operations import SetState, GetState @@ -13,40 +21,84 @@ class ContextManaged: def __enter__(self) -> Self: return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type, exc_value, traceback) -> None: self.deinit() - def deinit(self): + def deinit(self) -> None: """Free any hardware used by the object.""" return class Lockable(ContextManaged): - """An object that must be locked to prevent collisions on a microcontroller resource.""" + """An object that must be locked to prevent collisions on a microcontroller + resource.""" _locked = False - def try_lock(self): - """Attempt to grab the lock. Return True on success, False if the lock is already taken.""" + def try_lock(self) -> bool: + """Attempt to grab the lock. Return `True` on success, `False` if the lock is + already taken.""" if self._locked: return False self._locked = True return True - def unlock(self): + def unlock(self) -> None: """Release the lock so others may use the resource.""" if self._locked: self._locked = False class Expecting: - """A base class for the mock classes used to assert expected behaviors.""" + """A base class for the mock classes used to assert expected behaviors. + + .. seealso:: + :title: Mocks that derive from this mixin class + + - `busio.I2C` + - `board.I2C()` + - `board.STEMMA_I2C()` + - `busio.SPI` + - `board.SPI()` + - `board.SPI1()` + - `busio.UART` + - `board.UART()` + - `digitalio.DigitalInOut` + """ def __init__(self, **kwargs) -> None: - #: A double ended queue used to assert expected behavior - self.expectations: deque[Read | Write | Transfer | SetState | GetState] = ( - deque() - ) + self.expectations: Deque[ + Union[ + I2CRead, + I2CWrite, + I2CTransfer, + SPIRead, + SPIWrite, + SPITransfer, + UARTRead, + UARTWrite, + SetState, + GetState, + ] + ] = deque() + """A double-ended queue (:py:class:`~collections.deque`) used to assert + expected behavior. + + .. example:: + + Examples that use `expectations` can be found in the + + - :doc:`busio` documentation + - :doc:`digitalio` documentation + - :py:func:`~circuitpython_mocks.fixtures.mock_blinka_imports` + (pytest fixture) documentation + + .. _this package's tests files: + https://github.com/2bndy5/CircuitPython-mocks/tree/main/tests + + All examples' source is located in `this package's tests files`_. + """ + super().__init__(**kwargs) def done(self): diff --git a/circuitpython_mocks/board.py b/circuitpython_mocks/board.py index e4285d2..761685f 100644 --- a/circuitpython_mocks/board.py +++ b/circuitpython_mocks/board.py @@ -178,6 +178,13 @@ def SPI(): return ImplSPI(SCK, MOSI, MISO) +def SPI1(): + """Creates a default instance (singleton) of :py:class:`~busio.SPI` (secondary bus)""" + from circuitpython_mocks.busio import SPI as ImplSPI + + return ImplSPI(SCK_1, MOSI_1, MISO_1) + + def I2C(): """Creates a default instance (singleton) of :py:class:`~busio.I2C`""" from circuitpython_mocks.busio import I2C as ImplI2C @@ -186,7 +193,7 @@ def I2C(): def STEMMA_I2C(): - """Creates a default instance (singleton) of :py:class:`~busio.I2C`""" + """Creates a default instance (singleton) of :py:class:`~busio.I2C` (secondary bus)""" from circuitpython_mocks.busio import I2C as ImplI2C return ImplI2C(SCL1, SDA1) diff --git a/circuitpython_mocks/busio/__init__.py b/circuitpython_mocks/busio/__init__.py index 3777ba0..059ba36 100644 --- a/circuitpython_mocks/busio/__init__.py +++ b/circuitpython_mocks/busio/__init__.py @@ -1,4 +1,22 @@ -"""A module to mock the data bus transactions.""" +"""A mock of the :external:py:mod:`busio` module. + +.. md-tab-set:: + + .. md-tab-item:: I2C + + .. literalinclude:: ../tests/test_i2c.py + :language: python + + .. md-tab-item:: SPI + + .. literalinclude:: ../tests/test_spi.py + :language: python + + .. md-tab-item:: UART + + .. literalinclude:: ../tests/test_uart.py + :language: python +""" from enum import Enum, auto import sys @@ -77,8 +95,12 @@ def readfrom_into( end: int = sys.maxsize, ) -> None: """A mock imitation of :external:py:meth:`busio.I2C.readfrom_into()`. - This function checks against `I2CRead` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`""" + + .. mock-expects:: + + This function checks against `I2CRead` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "no expectation found for I2C.readfrom_into()" op = self.expectations.popleft() assert isinstance(op, I2CRead), f"Read operation expected, found {repr(op)}" @@ -95,8 +117,12 @@ def writeto( end: int = sys.maxsize, ) -> None: """A mock imitation of :external:py:meth:`busio.I2C.writeto()`. - This function checks against `I2CWrite` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`""" + + .. mock-expects:: + + This function checks against `I2CWrite` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "no expectation found for I2C.writeto()" op = self.expectations.popleft() assert isinstance(op, I2CWrite), f"Read operation expected, found {repr(op)}" @@ -115,8 +141,12 @@ def writeto_then_readfrom( in_end: int = sys.maxsize, ) -> None: """A mock imitation of :external:py:meth:`busio.I2C.writeto_then_readfrom()`. - This function checks against `I2CTransfer` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`""" + + .. mock-expects:: + + This function checks against `I2CTransfer` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "no expectation found for I2C.writeto_then_readfrom()" op = self.expectations.popleft() assert isinstance( @@ -180,8 +210,12 @@ def write( end: int = sys.maxsize, ) -> None: """A function that mocks :external:py:meth:`busio.SPI.write()`. - This function checks against `SPIWrite` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`""" + + .. mock-expects:: + + This function checks against `SPIWrite` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "no expectation found for SPI.write()" op = self.expectations.popleft() assert isinstance(op, SPIWrite), f"Read operation expected, found {repr(op)}" @@ -196,8 +230,12 @@ def readinto( write_value: int = 0, ) -> None: """A function that mocks :external:py:meth:`busio.SPI.readinto()`. - This function checks against `SPIRead` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`""" + + .. mock-expects:: + + This function checks against `SPIRead` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "no expectation found for SPI.readinto()" op = self.expectations.popleft() assert isinstance(op, SPIRead), f"Read operation expected, found {repr(op)}" @@ -214,8 +252,12 @@ def write_readinto( in_end: int = sys.maxsize, ) -> None: """A function that mocks :external:py:meth:`busio.SPI.write_readinto()`. - This function checks against `SPITransfer` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`""" + + .. mock-expects:: + + This function checks against `SPITransfer` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "no expectation found for SPI.write_readinto()" op = self.expectations.popleft() assert isinstance( @@ -262,8 +304,12 @@ def __init__( def read(self, nbytes: int | None = None) -> bytes | None: """A function that mocks :external:py:meth:`busio.UART.read()`. - This function checks against `UARTRead` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`""" + + .. mock-expects:: + + This function checks against `UARTRead` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "no expectation found for UART.read()" op = self.expectations.popleft() assert isinstance(op, UARTRead), f"Read operation expected, found {repr(op)}" @@ -274,8 +320,12 @@ def read(self, nbytes: int | None = None) -> bytes | None: def readinto(self, buf: circuitpython_typing.WriteableBuffer) -> int | None: """A function that mocks :external:py:meth:`busio.UART.readinto()`. - This function checks against `UARTRead` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`""" + + .. mock-expects:: + + This function checks against `UARTRead` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "no expectation found for UART.readinto()" op = self.expectations.popleft() assert isinstance(op, UARTRead), f"Read operation expected, found {repr(op)}" @@ -285,8 +335,12 @@ def readinto(self, buf: circuitpython_typing.WriteableBuffer) -> int | None: def readline(self) -> bytes: """A function that mocks :external:py:meth:`busio.UART.readline()`. - This function checks against `UARTRead` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`""" + + .. mock-expects:: + + This function checks against `UARTRead` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "no expectation found for UART.readline()" op = self.expectations.popleft() assert isinstance(op, UARTRead), f"Read operation expected, found {repr(op)}" @@ -297,14 +351,15 @@ def readline(self) -> bytes: def write(self, buf: circuitpython_typing.ReadableBuffer) -> int | None: """A function that mocks :external:py:meth:`busio.UART.write()`. - This function checks against `UARTWrite` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`""" + + .. mock-expects:: + + This function checks against `UARTWrite` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "no expectation found for UART.write()" op = self.expectations.popleft() assert isinstance(op, UARTWrite), f"Read operation expected, found {repr(op)}" len_buf = len(op.expected) op.assert_expected(buf, 0, len_buf) return len(buf) or None - - -_UART = UART(TX, RX) diff --git a/circuitpython_mocks/busio/operations.py b/circuitpython_mocks/busio/operations.py index c9aff14..a4588bd 100644 --- a/circuitpython_mocks/busio/operations.py +++ b/circuitpython_mocks/busio/operations.py @@ -2,7 +2,7 @@ import circuitpython_typing as cir_py_types -class Write: +class _Write: """A class to identify a write operation over a data bus.""" def __init__(self, expected: bytearray, **kwargs) -> None: @@ -26,7 +26,7 @@ def assert_expected( ), "Write.response does not match given buffer (or slice)" -class Read: +class _Read: """A class to identify a read operation over a data bus.""" def __init__(self, response: bytearray, **kwargs) -> None: @@ -51,7 +51,7 @@ def assert_response( buffer[start:end] = self.response -class Transfer(Read, Write): +class _Transfer(_Read, _Write): """A class to identify a read/write (transfer) operation over a data bus.""" def __init__(self, expected: bytearray, response: bytearray, **kwargs) -> None: @@ -98,8 +98,9 @@ def assert_address(self, address: int): assert address == self.address, "I2C address does not match given address" -class I2CWrite(Write, _I2CAddress): - """A class to identify a write operation over a I2C bus.""" +class I2CWrite(_Write, _I2CAddress): + """A class to identify a write operation over a + :py:class:`~circuitpython_mocks.busio.I2C` bus.""" def __init__(self, address: int, expected: bytearray) -> None: super().__init__(expected=expected, address=address) @@ -110,8 +111,9 @@ def __repr__(self) -> str: return f"" -class I2CRead(Read, _I2CAddress): - """A class to identify a read operation over a I2C bus.""" +class I2CRead(_Read, _I2CAddress): + """A class to identify a read operation over a + :py:class:`~circuitpython_mocks.busio.I2C` bus.""" def __init__(self, address: int, response: bytearray) -> None: super().__init__(response=response, address=address) @@ -122,8 +124,9 @@ def __repr__(self) -> str: return f"" -class I2CTransfer(Transfer, _I2CAddress): - """A class to identify a write operation over a I2C bus.""" +class I2CTransfer(_Transfer, _I2CAddress): + """A class to identify a write operation over a + :py:class:`~circuitpython_mocks.busio.I2C` bus.""" def __init__(self, address: int, expected: bytearray, response: bytearray) -> None: super().__init__(expected=expected, response=response, address=address) @@ -137,20 +140,23 @@ def __repr__(self) -> str: ) -class SPIRead(Read): - """A class to identify a read operation over a SPI bus.""" +class SPIRead(_Read): + """A class to identify a read operation over a + :py:class:`~circuitpython_mocks.busio.SPI` bus.""" pass -class SPIWrite(Write): - """A class to identify a write operation over a SPI bus.""" +class SPIWrite(_Write): + """A class to identify a write operation over a + :py:class:`~circuitpython_mocks.busio.SPI` bus.""" pass -class SPITransfer(Transfer): - """A class to identify a read/write (transfer) operation over a SPI bus.""" +class SPITransfer(_Transfer): + """A class to identify a read/write (transfer) operation over a + :py:class:`~circuitpython_mocks.busio.SPI` bus.""" def assert_transaction( self, @@ -170,13 +176,15 @@ def assert_transaction( ) -class UARTRead(Read): - """A class to identify a read operation over a UART bus.""" +class UARTRead(_Read): + """A class to identify a read operation over a + :py:class:`~circuitpython_mocks.busio.UART` bus.""" pass -class UARTWrite(Write): - """A class to identify a write operation over a UART bus.""" +class UARTWrite(_Write): + """A class to identify a write operation over a + :py:class:`~circuitpython_mocks.busio.UART` bus.""" pass diff --git a/circuitpython_mocks/digitalio/__init__.py b/circuitpython_mocks/digitalio/__init__.py index 1cd455f..ad90722 100644 --- a/circuitpython_mocks/digitalio/__init__.py +++ b/circuitpython_mocks/digitalio/__init__.py @@ -1,3 +1,11 @@ +"""A mock of the :external:py:mod:`digitalio` module. + +.. literalinclude:: ../tests/test_dio.py + :language: python + :end-at: dio.done() + :start-after: import pytest +""" + from enum import Enum, auto from typing import Union, Optional from circuitpython_mocks._mixins import ContextManaged, Expecting @@ -39,7 +47,14 @@ def switch_to_output( value: Union[bool, int] = False, drive_mode: DriveMode = DriveMode.PUSH_PULL, ): - """Switch the Digital Pin Mode to Output""" + """Switch the Digital Pin Mode to Output. + + .. mock-expects:: + + This function also changes the state of the pin's `value`. + So, this function will check against `SetState` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ self.direction = Direction.OUTPUT self.value = value self.drive_mode = drive_mode @@ -72,8 +87,12 @@ def direction(self, value: Direction): @property def value(self) -> Union[bool, int]: """The Digital Pin Value. - This property will check against `SetState` and `GetState` - :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.""" + + .. mock-expects:: + + This property will check against `SetState` and `GetState` + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. + """ assert self.expectations, "No expectations found for DigitalInOut.value.getter" op = self.expectations.popleft() assert isinstance( diff --git a/circuitpython_mocks/fixtures.py b/circuitpython_mocks/fixtures.py new file mode 100644 index 0000000..efec1c4 --- /dev/null +++ b/circuitpython_mocks/fixtures.py @@ -0,0 +1,47 @@ +"""A module that contains pytest fixtures. + +.. _pytest_plugins: https://docs.pytest.org/en/latest/how-to/fixtures.html#using-fixtures-from-other-projects + +These fixtures are made available by defining a `pytest_plugins`_ global attribute in +the test module (or in conftest.py module). + +.. code-block:: python + :caption: conftest.py + + pytest_plugins = ["circuitpython_mocks.fixtures"] +""" + +from pathlib import Path +import pytest + + +@pytest.fixture() +def mock_blinka_imports(monkeypatch: pytest.MonkeyPatch): + """A pytest fixture that monkey patches the Python runtime's import paths, such + that this package's mock modules can be imported first (instead of using the + adafruit-blinka package). + + .. md-tab-set:: + + .. md-tab-item:: I2C + + .. literalinclude:: ../tests/test_i2c_fixture.py + :language: python + + .. md-tab-item:: SPI + + .. literalinclude:: ../tests/test_spi_fixture.py + :language: python + + .. md-tab-item:: UART + + .. literalinclude:: ../tests/test_uart_fixture.py + :language: python + + .. md-tab-item:: DigitalInOut + + .. literalinclude:: ../tests/test_dio_fixture.py + :language: python + """ + root_pkg = Path(__file__).parent + monkeypatch.syspath_prepend(str(root_pkg)) diff --git a/docs/busio.rst b/docs/busio.rst index fae3ad9..b7401da 100644 --- a/docs/busio.rst +++ b/docs/busio.rst @@ -4,9 +4,9 @@ .. automodule:: circuitpython_mocks.busio .. autoclass:: circuitpython_mocks.busio.I2C - :members: readfrom_into, writeto, writeto_then_readfrom, scan + :members: readfrom_into, writeto, writeto_then_readfrom, scan, try_lock, unlock, deinit .. autoclass:: circuitpython_mocks.busio.SPI - :members: readinto, write, write_readinto, configure, frequency + :members: readinto, write, write_readinto, configure, frequency, try_lock, unlock, deinit .. autoclass:: circuitpython_mocks.busio.UART :members: readinto, readline, write diff --git a/docs/conf.py b/docs/conf.py index 07d417c..9438cd0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,6 +28,7 @@ autodoc_default_options = { "exclude-members": "__new__", } +add_function_parentheses = False templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] @@ -37,7 +38,9 @@ "pins": [ x for x in dir(circuitpython_mocks.board) - if not x.startswith("_") and x not in ("Pin", "board_id") + if not x.startswith("_") + and x != "board_id" + and not callable(getattr(circuitpython_mocks.board, x)) ] } } @@ -93,3 +96,18 @@ }, ], } + +sphinx_immaterial_custom_admonitions = [ + { + "name": "seealso", + "color": "#e30e7c", + "override": True, + "icon": "material/eye-outline", + }, + { + "name": "mock-expects", + "title": "Mock Expectations", + "color": "#0fe344", + "icon": "material/code-tags-check", + }, +] diff --git a/docs/index.rst b/docs/index.rst index 3a68f06..bf99d75 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,56 +1,23 @@ CircuitPython-mocks documentation ================================= -.. |pytest-used-import| replace:: Some linters may object about "unused imports". - This should be ignored as pytest does actually use the imported fixture. - This library contains mock data structures to be used when soft-testing CircuitPython-based -projects (with pytest). - -.. autoclass:: circuitpython_mocks._mixins.Expecting - :members: - -.. autofunction:: circuitpython_mocks.monkey_patch_sys_paths - - .. md-tab-set:: - - .. md-tab-item:: I2C - - .. literalinclude:: ../tests/test_i2c.py - :language: python - :end-before: def test_default(): +projects (with `pytest `_). - .. code-annotations:: - #. |pytest-used-import| +Mocking expected behavior +------------------------- - .. md-tab-item:: SPI - - .. literalinclude:: ../tests/test_spi.py - :language: python - :end-before: def test_default(): - - .. code-annotations:: - #. |pytest-used-import| - - .. md-tab-item:: UART - - .. literalinclude:: ../tests/test_uart.py - :language: python - :end-before: def test_default(): - - .. code-annotations:: - #. |pytest-used-import| - - .. md-tab-item:: DigitalInOut +.. autoclass:: circuitpython_mocks._mixins.Expecting + :members: expectations, done - .. literalinclude:: ../tests/test_dio.py - :language: python +Pytest fixtures +--------------- - .. code-annotations:: - #. |pytest-used-import| +.. automodule:: circuitpython_mocks.fixtures + :members: Mocked API -************ +---------- .. toctree:: :maxdepth: 2 diff --git a/pyproject.toml b/pyproject.toml index b538954..009f1c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,12 +73,14 @@ show_column_numbers = true [tool.coverage] [tool.coverage.run] dynamic_context = "test_function" +branch = true [tool.coverage.json] pretty_print = true [tool.coverage.html] show_contexts = true +skip_empty = true [tool.coverage.report] # Regexes for lines to exclude from consideration diff --git a/tests/test_dio.py b/tests/test_dio.py index 65aaeaa..dd01747 100644 --- a/tests/test_dio.py +++ b/tests/test_dio.py @@ -1,16 +1,27 @@ -from circuitpython_mocks import monkey_patch_sys_paths # noqa: F401 (1) import pytest +from circuitpython_mocks import board +from circuitpython_mocks.digitalio import DigitalInOut, Direction, DriveMode, Pull from circuitpython_mocks.digitalio.operations import SetState, GetState def test_dio(): - from digitalio import DigitalInOut, Direction, DriveMode, Pull - import board - - # do setup - with DigitalInOut(board.D0) as dio: + with DigitalInOut(board.D42) as dio: assert dio.direction == Direction.INPUT assert dio.pull is None + + # set expectations for the pin state changes + dio.expectations.append(GetState(True)) + assert dio.value + dio.expectations.extend([SetState(False), SetState(True)]) + dio.switch_to_output() + dio.value = True + + # assert all expectation were used + dio.done() + + +def test_dio_errors(): + with DigitalInOut(board.D42) as dio: with pytest.raises(AttributeError): dio.value = True with pytest.raises(AttributeError): @@ -18,14 +29,14 @@ def test_dio(): with pytest.raises(AttributeError): dio.drive_mode - # set expectations for the pin state and do the test - dio.expectations.append(GetState(True)) - assert dio.value - dio.expectations.extend([SetState(False), SetState(True)]) + # set expectations for the pin state changes + dio.expectations.extend([SetState(False)]) dio.switch_to_output() with pytest.raises(AttributeError): dio.pull with pytest.raises(AttributeError): dio.pull = Pull.UP assert dio.drive_mode == DriveMode.PUSH_PULL - dio.value = True + + # assert all expectation were used + dio.done() diff --git a/tests/test_dio_fixture.py b/tests/test_dio_fixture.py new file mode 100644 index 0000000..bedbe20 --- /dev/null +++ b/tests/test_dio_fixture.py @@ -0,0 +1,22 @@ +from circuitpython_mocks.digitalio.operations import SetState, GetState + +pytest_plugins = ["circuitpython_mocks.fixtures"] + + +def test_dio(mock_blinka_imports): + from digitalio import DigitalInOut, Direction + import board + + with DigitalInOut(board.D42) as dio: + assert dio.direction == Direction.INPUT + assert dio.pull is None + + # set expectations for the pin state changes + dio.expectations.append(GetState(True)) + assert dio.value + dio.expectations.extend([SetState(False), SetState(True)]) + dio.switch_to_output() + dio.value = True + + # assert all expectation were used + dio.done() diff --git a/tests/test_i2c.py b/tests/test_i2c.py index 152d52d..3aaa510 100644 --- a/tests/test_i2c.py +++ b/tests/test_i2c.py @@ -1,66 +1,33 @@ -from circuitpython_mocks import monkey_patch_sys_paths # noqa: F401 (1) -from circuitpython_mocks.busio.operations import ( - I2CRead, - I2CWrite, - I2CTransfer, +import pytest +from collections import deque +from circuitpython_mocks import busio, board +from circuitpython_mocks.busio.operations import I2CTransfer + + +@pytest.mark.parametrize( + argnames=["board_i2c", "busio_i2c"], + argvalues=[ + (board.I2C(), busio.I2C(board.SCL, board.SDA)), + (board.STEMMA_I2C(), busio.I2C(board.SCL1, board.SDA1)), + ], + ids=["primary_bus", "secondary_bus"], ) - - -def test_i2c(): - import board - from busio import I2C - from adafruit_bus_device.i2c_device import I2CDevice - +def test_singleton(board_i2c: busio.I2C, busio_i2c: busio.I2C): address = 0x42 - # do setup - with I2C(board.SCL, board.SDA) as i2c_bus: - assert i2c_bus.scan() == [] - - # set expectation for probing performed by I2CDevice.__init__() - i2c_bus.expectations.append(I2CWrite(address, b"")) - # set expectations for I2C bus - i2c_bus.expectations.extend( - [ - I2CRead(address, bytearray(1)), - I2CWrite(address, bytearray(1)), - I2CTransfer(address, bytearray(1), bytearray(1)), - ] - ) - i2c_dev = I2CDevice(i2c_bus, device_address=address) - - # do test - buf = bytearray(1) - with i2c_dev as i2c: - assert not i2c_bus.try_lock() - i2c.readinto(buf, end=1) - with i2c_dev as i2c: - i2c.write(buf, end=1) - with i2c_dev as i2c: - i2c.write_then_readinto(buf, buf, out_end=1, in_end=1) - - -def test_default(): - # here we cannot import from the monkey-patched sys path because - # the mock modules use absolute imports. - from circuitpython_mocks import board, busio - from collections import deque - - i2c = board.I2C() - assert hasattr(i2c, "expectations") - assert isinstance(i2c.expectations, deque) - i2c_dupe = busio.I2C(board.SCL, board.SDA) - assert i2c == i2c_dupe - i2c.expectations.append(I2CRead(0x42, bytearray(1))) - assert i2c_dupe.expectations == i2c.expectations - op = i2c_dupe.expectations.popleft() - assert not i2c.expectations - i2c1 = board.STEMMA_I2C() - assert hasattr(i2c1, "expectations") - assert isinstance(i2c1.expectations, deque) - i2c1_dupe = busio.I2C(board.SCL1, board.SDA1) - assert i2c1 == i2c1_dupe - i2c1.expectations.append(op) - assert i2c1_dupe.expectations == i2c1.expectations - op = i2c1_dupe.expectations.popleft() - assert not i2c1.expectations + assert hasattr(board_i2c, "expectations") + assert isinstance(board_i2c.expectations, deque) + assert board_i2c == busio_i2c + + board_i2c.expectations.append(I2CTransfer(address, bytearray(1), bytearray(1))) + assert busio_i2c.expectations == board_i2c.expectations + buffer = bytearray(1) + assert board_i2c.try_lock() + assert not busio_i2c.try_lock() + board_i2c.writeto_then_readfrom(address, buffer, buffer) + board_i2c.unlock() + busio_i2c.unlock() # for coverage measurement only + + # assert all expectations were used + board_i2c.done() + assert board_i2c.expectations == busio_i2c.expectations diff --git a/tests/test_i2c_fixture.py b/tests/test_i2c_fixture.py new file mode 100644 index 0000000..0847ce4 --- /dev/null +++ b/tests/test_i2c_fixture.py @@ -0,0 +1,43 @@ +from circuitpython_mocks.busio.operations import ( + I2CRead, + I2CWrite, + I2CTransfer, +) + +pytest_plugins = ["circuitpython_mocks.fixtures"] + + +def test_i2c(mock_blinka_imports): + import board + from busio import I2C + from adafruit_bus_device.i2c_device import I2CDevice + + address = 0x42 + # do setup + with I2C(board.SCL, board.SDA) as i2c_bus: + assert i2c_bus.scan() == [] + + # set expectation for probing performed by I2CDevice.__init__() + i2c_bus.expectations.append(I2CWrite(address, b"")) + # set expectations for I2C bus + i2c_bus.expectations.extend( + [ + I2CRead(address, bytearray(1)), + I2CWrite(address, bytearray(1)), + I2CTransfer(address, bytearray(1), bytearray(1)), + ] + ) + i2c_dev = I2CDevice(i2c_bus, device_address=address) + + # do test + buf = bytearray(1) + with i2c_dev as i2c: + assert not i2c_bus.try_lock() + i2c.readinto(buf, end=1) + with i2c_dev as i2c: + i2c.write(buf, end=1) + with i2c_dev as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_end=1) + + # assert all expectation were used + i2c_bus.done() diff --git a/tests/test_spi.py b/tests/test_spi.py index de64262..d5de474 100644 --- a/tests/test_spi.py +++ b/tests/test_spi.py @@ -1,69 +1,31 @@ -from circuitpython_mocks import monkey_patch_sys_paths # noqa: F401 (1) -from circuitpython_mocks.busio.operations import ( - SPIRead, - SPIWrite, - SPITransfer, +import pytest +from collections import deque +from circuitpython_mocks import board, busio +from circuitpython_mocks.busio.operations import SPITransfer + + +@pytest.mark.parametrize( + argnames=["board_spi", "busio_spi"], + argvalues=[ + (board.SPI(), busio.SPI(board.SCK, board.MOSI, board.MISO)), + (board.SPI1(), busio.SPI(board.SCK_1, board.MOSI_1, board.MISO_1)), + ], + ids=["primary_bus", "secondary_bus"], ) -from circuitpython_mocks.digitalio.operations import SetState - - -def test_spi(): - from busio import SPI - from digitalio import DigitalInOut - import board - from adafruit_bus_device.spi_device import SPIDevice - - # do setup - with SPI(board.SCK, board.MOSI, board.MISO) as spi_bus: - assert spi_bus.frequency == 1000000 - cs = DigitalInOut(board.CE0) - - # set expectations for the CS pin. We'll be doing 3 transactions (toggling pin each time) - cs.expectations.append(SetState(True)) - cs.expectations.extend([SetState(False), SetState(True)] * 3) - # set expectations for SPI bus - spi_bus.expectations.extend( - [ - SPIRead(bytearray(1)), - SPIWrite(bytearray(1)), - SPITransfer(bytearray(1), bytearray(1)), - ] - ) - spi_dev = SPIDevice(spi_bus, chip_select=cs) - - # do test - buf = bytearray(1) - with spi_dev as spi: - assert not spi_bus.try_lock() - spi.readinto(buf, end=1) - with spi_dev as spi: - spi.write(buf, end=1) - with spi_dev as spi: - spi.write_readinto(buf, buf, out_end=1, in_end=1) - - -def test_default(): - # here we cannot import from the monkey-patched sys path because - # the mock modules use absolute imports. - from circuitpython_mocks import board, busio - from collections import deque - - spi = board.SPI() - assert hasattr(spi, "expectations") - assert isinstance(spi.expectations, deque) - spi_dupe = busio.SPI(board.SCK, board.MOSI, board.MISO) - assert spi == spi_dupe - spi.expectations.append(SPIRead(bytearray(1))) - assert spi_dupe.expectations == spi.expectations - op = spi_dupe.expectations.popleft() - assert not spi.expectations - - spi1 = busio.SPI(board.SCK_1, board.MOSI_1, board.MISO_1) - assert hasattr(spi1, "expectations") - assert isinstance(spi1.expectations, deque) - spi1_dupe = busio.SPI(board.SCK_1, board.MOSI_1, board.MISO_1) - assert spi1 == spi1_dupe - spi1.expectations.append(op) - assert spi1_dupe.expectations == spi1.expectations - _ = spi1_dupe.expectations.popleft() - assert not spi1.expectations +def test_singleton(board_spi: busio.SPI, busio_spi: busio.SPI): + assert hasattr(board_spi, "expectations") + assert isinstance(busio_spi.expectations, deque) + assert board_spi == busio_spi + + board_spi.expectations.append(SPITransfer(bytearray(1), bytearray(1))) + assert busio_spi.expectations == board_spi.expectations + buffer = bytearray(1) + assert board_spi.try_lock() + assert not busio_spi.try_lock() + board_spi.write_readinto(buffer, buffer) + board_spi.unlock() + busio_spi.unlock() # for coverage measurement only + + # assert all expectations were used + board_spi.done() + assert busio_spi.expectations == board_spi.expectations diff --git a/tests/test_spi_fixture.py b/tests/test_spi_fixture.py new file mode 100644 index 0000000..b6341a2 --- /dev/null +++ b/tests/test_spi_fixture.py @@ -0,0 +1,45 @@ +from circuitpython_mocks.busio.operations import ( + SPIRead, + SPIWrite, + SPITransfer, +) +from circuitpython_mocks.digitalio.operations import SetState + +pytest_plugins = ["circuitpython_mocks.fixtures"] + + +def test_spi(mock_blinka_imports): + from busio import SPI + from digitalio import DigitalInOut + import board + from adafruit_bus_device.spi_device import SPIDevice + + # do setup + with SPI(board.SCK, board.MOSI, board.MISO) as spi_bus: + assert spi_bus.frequency == 1000000 + cs = DigitalInOut(board.CE0) + + # set expectations for the CS pin. We'll be doing 3 transactions (toggling pin each time) + cs.expectations.append(SetState(True)) + cs.expectations.extend([SetState(False), SetState(True)] * 3) + # set expectations for SPI bus + spi_bus.expectations.extend( + [ + SPIRead(bytearray(1)), + SPIWrite(bytearray(1)), + SPITransfer(bytearray(1), bytearray(1)), + ] + ) + spi_dev = SPIDevice(spi_bus, chip_select=cs) + + # do test + buf = bytearray(1) + with spi_dev as spi: + spi.readinto(buf, end=1) + with spi_dev as spi: + spi.write(buf, end=1) + with spi_dev as spi: + spi.write_readinto(buf, buf, out_end=1, in_end=1) + + # assert all expectation were used + spi_bus.done() diff --git a/tests/test_uart.py b/tests/test_uart.py index c4fa92e..b9e7bd4 100644 --- a/tests/test_uart.py +++ b/tests/test_uart.py @@ -1,46 +1,26 @@ -from circuitpython_mocks import monkey_patch_sys_paths # noqa: F401 (1) -from circuitpython_mocks.busio.operations import ( - UARTRead, - UARTWrite, -) +from collections import deque +from circuitpython_mocks import board, busio +from circuitpython_mocks.busio.operations import UARTRead, UARTWrite -def test_uart(): - from busio import UART - import board +def test_singleton(): + board_uart = board.UART() + assert hasattr(board_uart, "expectations") + assert isinstance(board_uart.expectations, deque) - # do setup - with UART(board.TX, board.RX) as serial: - # set expectations for SPI bus - serial.expectations.extend( - [ - UARTRead(bytearray(1)), - UARTRead(bytearray(1)), - UARTRead(bytearray(1)), - UARTWrite(bytearray(1)), - ] - ) + busio_uart = busio.UART(board.TX, board.RX) + assert board_uart == busio_uart - # do test - buf = bytearray(1) - _result = serial.read(1) - serial.readinto(buf) - _result = serial.readline() - assert serial.write(buf) == 1 + board_uart.expectations.append(UARTRead(bytearray(1))) + assert busio_uart.expectations == board_uart.expectations + buffer = bytearray(1) + board_uart.readinto(buffer) + assert not busio_uart.expectations + busio_uart.expectations.append(UARTWrite(bytearray(1))) + assert busio_uart.expectations == board_uart.expectations + busio_uart.write(bytearray(1)) -def test_default(): - # here we cannot import from the monkey-patched sys path because - # the mock modules use absolute imports. - from circuitpython_mocks import board, busio - from collections import deque - - uart = board.UART() - assert hasattr(uart, "expectations") - assert isinstance(uart.expectations, deque) - uart_dupe = busio.UART(board.TX, board.RX) - assert uart == uart_dupe - uart.expectations.append(UARTRead(bytearray(1))) - assert uart_dupe.expectations == uart.expectations - _ = uart_dupe.expectations.popleft() - assert not uart.expectations + # assert all expectations were used + board_uart.done() + assert busio_uart.expectations == board_uart.expectations diff --git a/tests/test_uart_fixture.py b/tests/test_uart_fixture.py new file mode 100644 index 0000000..f6d1a45 --- /dev/null +++ b/tests/test_uart_fixture.py @@ -0,0 +1,30 @@ +from circuitpython_mocks.busio.operations import UARTRead, UARTWrite + +pytest_plugins = ["circuitpython_mocks.fixtures"] + + +def test_uart(mock_blinka_imports): + from busio import UART + import board + + # do setup + with UART(board.TX, board.RX) as serial: + # set expectations for UART bus + serial.expectations.extend( + [ + UARTRead(bytearray(1)), + UARTRead(bytearray(1)), + UARTRead(bytearray(1)), + UARTWrite(bytearray(1)), + ] + ) + + # do test + buf = bytearray(1) + _result = serial.read(1) + serial.readinto(buf) + _result = serial.readline() + assert serial.write(buf) == 1 + + # assert all expectation were used + serial.done()