diff --git a/circuitpython_mocks/_mixins.py b/circuitpython_mocks/_mixins.py index 7e819ab..82ad256 100644 --- a/circuitpython_mocks/_mixins.py +++ b/circuitpython_mocks/_mixins.py @@ -12,6 +12,7 @@ SPITransfer, UARTRead, UARTWrite, + UARTFlush, ) from circuitpython_mocks.digitalio.operations import SetState, GetState @@ -79,6 +80,7 @@ def __init__(self, **kwargs) -> None: SPITransfer, UARTRead, UARTWrite, + UARTFlush, SetState, GetState, ] @@ -90,7 +92,7 @@ def __init__(self, **kwargs) -> None: Examples that use `expectations` can be found in the - - :doc:`busio` documentation + - :doc:`busio/index` documentation - :doc:`digitalio` documentation - :py:func:`~circuitpython_mocks.fixtures.mock_blinka_imports` (pytest fixture) documentation diff --git a/circuitpython_mocks/busio/__init__.py b/circuitpython_mocks/busio/__init__.py index aa2cc0f..6fa50c9 100644 --- a/circuitpython_mocks/busio/__init__.py +++ b/circuitpython_mocks/busio/__init__.py @@ -4,17 +4,17 @@ .. md-tab-item:: I2C - .. literalinclude:: ../tests/test_i2c.py + .. literalinclude:: ../../tests/test_i2c.py :language: python .. md-tab-item:: SPI - .. literalinclude:: ../tests/test_spi.py + .. literalinclude:: ../../tests/test_spi.py :language: python .. md-tab-item:: UART - .. literalinclude:: ../tests/test_uart.py + .. literalinclude:: ../../tests/test_uart.py :language: python """ @@ -27,6 +27,7 @@ from circuitpython_mocks.busio.operations import ( UARTRead, UARTWrite, + UARTFlush, I2CRead, I2CWrite, I2CTransfer, @@ -310,9 +311,11 @@ def __init__( timeout: float = 1, receiver_buffer_size: int = 64, ) -> None: + self._baudrate = baudrate + self._timeout = timeout super().__init__() - def read(self, nbytes: int | None = None) -> bytes | None: + def read(self, nbytes: int | None = None) -> Optional[bytes]: """A function that mocks :external:py:meth:`busio.UART.read()`. .. mock-expects:: @@ -323,12 +326,12 @@ def read(self, nbytes: int | None = None) -> bytes | None: assert self.expectations, "no expectation found for UART.read()" op = self.expectations.popleft() assert isinstance(op, UARTRead), f"Read operation expected, found {repr(op)}" - length = nbytes or len(op.response) + length = nbytes or 0 if not op.response else len(op.response) buffer = bytearray(length) op.assert_response(buffer, 0, length) - return bytes(buffer) + return None if not buffer else bytes(buffer) - def readinto(self, buf: circuitpython_typing.WriteableBuffer) -> int | None: + def readinto(self, buf: circuitpython_typing.WriteableBuffer) -> Optional[int]: """A function that mocks :external:py:meth:`busio.UART.readinto()`. .. mock-expects:: @@ -339,11 +342,11 @@ def readinto(self, buf: circuitpython_typing.WriteableBuffer) -> int | None: assert self.expectations, "no expectation found for UART.readinto()" op = self.expectations.popleft() assert isinstance(op, UARTRead), f"Read operation expected, found {repr(op)}" - len_buf = len(op.response) + len_buf = 0 if not op.response else len(op.response) op.assert_response(buf, 0, len_buf) return len_buf - def readline(self) -> bytes: + def readline(self) -> Optional[bytes]: """A function that mocks :external:py:meth:`busio.UART.readline()`. .. mock-expects:: @@ -354,10 +357,10 @@ def readline(self) -> bytes: assert self.expectations, "no expectation found for UART.readline()" op = self.expectations.popleft() assert isinstance(op, UARTRead), f"Read operation expected, found {repr(op)}" - len_buf = len(op.response) + len_buf = 0 if not op.response else len(op.response) buf = bytearray(len_buf) op.assert_response(buf, 0, len_buf) - return bytes(buf) + return None if buf else bytes(buf) def write(self, buf: circuitpython_typing.ReadableBuffer) -> int | None: """A function that mocks :external:py:meth:`busio.UART.write()`. @@ -373,3 +376,48 @@ def write(self, buf: circuitpython_typing.ReadableBuffer) -> int | None: len_buf = len(op.expected) op.assert_expected(buf, 0, len_buf) return len(buf) or None + + @property + def baudrate(self) -> int: + """The current baudrate.""" + return self._baudrate + + @baudrate.setter + def baudrate(self, val: int): + self._baudrate = int(val) + + @property + def timeout(self) -> float: + """The current timeout, in seconds.""" + return self._timeout + + @timeout.setter + def timeout(self, val: float): + self._timeout = float(val) + + @property + def in_waiting(self) -> int: + """The number of bytes in the input buffer, available to be read. + + .. mock-expects:: + + This property peeks at the number of bytes in next available operation in + :py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`. If a + `UARTRead` operation is not immediately expected, then ``0`` is returned. + """ + if self.expectations and isinstance(self.expectations[0], UARTRead): + return len(self.expectations[0].response) + return 0 + + def reset_input_buffer(self) -> None: + """Discard any unread characters in the input buffer. + + .. mock-expects:: + + This function merely checks the immediately queued + :py:class:`~circuitpython_mocks._mixins.Expecting.expectations` for + a `UARTFlush` operation. It does not actually discard any data. + """ + assert self.expectations + op = self.expectations.popleft() + assert isinstance(op, UARTFlush), f"Flush operation expected, found {repr(op)}" diff --git a/circuitpython_mocks/busio/operations.py b/circuitpython_mocks/busio/operations.py index 23538f1..d479d47 100644 --- a/circuitpython_mocks/busio/operations.py +++ b/circuitpython_mocks/busio/operations.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional import sys import circuitpython_typing as cir_py_types @@ -196,9 +196,31 @@ def assert_transaction( class UARTRead(_Read): """A class to identify a read operation over a - :py:class:`~circuitpython_mocks.busio.UART` bus.""" + :py:class:`~circuitpython_mocks.busio.UART` bus. - pass + .. tip:: + To emulate a timeout condition, pass a `None` value to the ``response`` + parameter. + """ + + def __init__(self, response: Optional[bytearray], **kwargs) -> None: + super().__init__(response, **kwargs) # type: ignore[arg-type] + + def __repr__(self) -> str: + if not self.response: + return "" + return super().__repr__() + + def assert_response( + self, + buffer: cir_py_types.ReadableBuffer, + start: int = 0, + end: int = sys.maxsize, + ): + if not self.response: + buffer = self.response + return + return super().assert_response(buffer, start, end) class UARTWrite(_Write): @@ -206,3 +228,14 @@ class UARTWrite(_Write): :py:class:`~circuitpython_mocks.busio.UART` bus.""" pass + + +class UARTFlush: + """A class to identify a flush operation over a + :py:class:`~circuitpython_mocks.busio.UART` bus. + + This operation corresponds to the function + :py:meth:`~circuitpython_mocks.busio.UART.reset_input_buffer()`. + """ + + pass diff --git a/docs/busio.rst b/docs/busio.rst deleted file mode 100644 index c04ef58..0000000 --- a/docs/busio.rst +++ /dev/null @@ -1,46 +0,0 @@ -``busio`` -================= - -.. automodule:: circuitpython_mocks.busio - - .. autoclass:: circuitpython_mocks.busio.I2C - :members: readfrom_into, writeto, writeto_then_readfrom, scan, try_lock, unlock, deinit - .. autoclass:: circuitpython_mocks.busio.SPI - :members: readinto, write, write_readinto, configure, frequency, try_lock, unlock, deinit - .. autoclass:: circuitpython_mocks.busio.UART - :members: readinto, readline, write - - .. py:class:: circuitpython_mocks.busio.UART.Parity - - A mock enumeration of :external:py:class:`busio.Parity`. - - .. py:attribute:: ODD - :type: Parity - .. py:attribute:: EVEN - :type: Parity - -``busio.operations`` --------------------- - -.. automodule:: circuitpython_mocks.busio.operations - - I2C operations - ************** - - .. autoclass:: circuitpython_mocks.busio.operations.I2CRead - .. autoclass:: circuitpython_mocks.busio.operations.I2CWrite - .. autoclass:: circuitpython_mocks.busio.operations.I2CTransfer - .. autoclass:: circuitpython_mocks.busio.operations.I2CScan - - SPI operations - ************** - - .. autoclass:: circuitpython_mocks.busio.operations.SPIRead - .. autoclass:: circuitpython_mocks.busio.operations.SPIWrite - .. autoclass:: circuitpython_mocks.busio.operations.SPITransfer - - UART operations - *************** - - .. autoclass:: circuitpython_mocks.busio.operations.UARTRead - .. autoclass:: circuitpython_mocks.busio.operations.UARTWrite diff --git a/docs/busio/i2c.rst b/docs/busio/i2c.rst new file mode 100644 index 0000000..dcb5fcc --- /dev/null +++ b/docs/busio/i2c.rst @@ -0,0 +1,13 @@ +``busio.I2C`` +============= + +.. autoclass:: circuitpython_mocks.busio.I2C + :members: readfrom_into, writeto, writeto_then_readfrom, scan, try_lock, unlock, deinit + +I2C operations +************** + +.. autoclass:: circuitpython_mocks.busio.operations.I2CRead +.. autoclass:: circuitpython_mocks.busio.operations.I2CWrite +.. autoclass:: circuitpython_mocks.busio.operations.I2CTransfer +.. autoclass:: circuitpython_mocks.busio.operations.I2CScan diff --git a/docs/busio/index.rst b/docs/busio/index.rst new file mode 100644 index 0000000..7100d1d --- /dev/null +++ b/docs/busio/index.rst @@ -0,0 +1,11 @@ +``busio`` +================= + +.. automodule:: circuitpython_mocks.busio + +.. toctree:: + :maxdepth: 2 + + i2c + spi + uart diff --git a/docs/busio/spi.rst b/docs/busio/spi.rst new file mode 100644 index 0000000..8ecb5c8 --- /dev/null +++ b/docs/busio/spi.rst @@ -0,0 +1,12 @@ +``busio.SPI`` +============= + +.. autoclass:: circuitpython_mocks.busio.SPI + :members: readinto, write, write_readinto, configure, frequency, try_lock, unlock, deinit + +SPI operations +************** + +.. autoclass:: circuitpython_mocks.busio.operations.SPIRead +.. autoclass:: circuitpython_mocks.busio.operations.SPIWrite +.. autoclass:: circuitpython_mocks.busio.operations.SPITransfer diff --git a/docs/busio/uart.rst b/docs/busio/uart.rst new file mode 100644 index 0000000..60ac7ea --- /dev/null +++ b/docs/busio/uart.rst @@ -0,0 +1,21 @@ +``busio.UART`` +============== + +.. autoclass:: circuitpython_mocks.busio.UART + :members: readinto, readline, write, in_waiting, reset_input_buffer, timeout, baudrate + + .. py:class:: circuitpython_mocks.busio.UART.Parity + + A mock enumeration of :external:py:class:`busio.Parity`. + + .. py:attribute:: ODD + :type: Parity + .. py:attribute:: EVEN + :type: Parity + +UART operations +*************** + +.. autoclass:: circuitpython_mocks.busio.operations.UARTRead +.. autoclass:: circuitpython_mocks.busio.operations.UARTWrite +.. autoclass:: circuitpython_mocks.busio.operations.UARTFlush diff --git a/docs/index.rst b/docs/index.rst index bf99d75..115fc5f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,9 +19,13 @@ Pytest fixtures Mocked API ---------- +.. toctree:: + :maxdepth: 3 + + busio/index + .. toctree:: :maxdepth: 2 - busio digitalio board diff --git a/tests/test_uart.py b/tests/test_uart.py index b9e7bd4..f3c53bf 100644 --- a/tests/test_uart.py +++ b/tests/test_uart.py @@ -1,26 +1,35 @@ from collections import deque from circuitpython_mocks import board, busio -from circuitpython_mocks.busio.operations import UARTRead, UARTWrite +from circuitpython_mocks.busio.operations import UARTRead, UARTWrite, UARTFlush def test_singleton(): board_uart = board.UART() assert hasattr(board_uart, "expectations") assert isinstance(board_uart.expectations, deque) - busio_uart = busio.UART(board.TX, board.RX) assert board_uart == busio_uart + board_uart.timeout = 0.5 + assert 0.5 == busio_uart.timeout + busio_uart.baudrate = 115200 + assert 115200 == board_uart.baudrate board_uart.expectations.append(UARTRead(bytearray(1))) assert busio_uart.expectations == board_uart.expectations buffer = bytearray(1) + assert 1 == board_uart.in_waiting board_uart.readinto(buffer) assert not busio_uart.expectations busio_uart.expectations.append(UARTWrite(bytearray(1))) assert busio_uart.expectations == board_uart.expectations + assert not busio_uart.in_waiting busio_uart.write(bytearray(1)) + busio_uart.expectations.append(UARTFlush()) + assert busio_uart.expectations == board_uart.expectations + busio_uart.reset_input_buffer() + # 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 index f6d1a45..83f1f8f 100644 --- a/tests/test_uart_fixture.py +++ b/tests/test_uart_fixture.py @@ -1,4 +1,4 @@ -from circuitpython_mocks.busio.operations import UARTRead, UARTWrite +from circuitpython_mocks.busio.operations import UARTRead, UARTWrite, UARTFlush pytest_plugins = ["circuitpython_mocks.fixtures"] @@ -16,15 +16,20 @@ def test_uart(mock_blinka_imports): UARTRead(bytearray(1)), UARTRead(bytearray(1)), UARTWrite(bytearray(1)), + UARTFlush(), + UARTRead(None), # timeout condition ] ) # do test buf = bytearray(1) + assert 1 == serial.in_waiting _result = serial.read(1) serial.readinto(buf) _result = serial.readline() assert serial.write(buf) == 1 + serial.reset_input_buffer() + assert serial.read() is None # assert all expectation were used serial.done()