Skip to content

Commit 42fe448

Browse files
authored
migrate fixture into a separate module (#11)
* move fixture to fixtures module * segregate tests and add `board.SPI1()` * make base ops private * rm dev artifact from #7 * improve docs
1 parent ce9d917 commit 42fe448

File tree

19 files changed

+524
-305
lines changed

19 files changed

+524
-305
lines changed

circuitpython_mocks/__init__.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +0,0 @@
1-
from pathlib import Path
2-
import pytest
3-
4-
5-
@pytest.fixture(autouse=True)
6-
def monkey_patch_sys_paths(monkeypatch: pytest.MonkeyPatch):
7-
"""A pytest fixture that monkey patches the Python runtime's import paths, such
8-
that this package's mock modules can be imported first (instead from the
9-
adafruit-blinka package).
10-
11-
.. important::
12-
13-
This fixture is automatically used once imported into the test module.
14-
"""
15-
root_pkg = Path(__file__).parent
16-
monkeypatch.syspath_prepend(str(root_pkg))

circuitpython_mocks/_mixins.py

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
from typing import Self
21
from collections import deque
3-
from typing import TYPE_CHECKING
2+
from typing import Self, Deque, Union, TYPE_CHECKING
43

54
if TYPE_CHECKING:
6-
from circuitpython_mocks.busio.operations import Read, Write, Transfer
5+
from circuitpython_mocks.busio.operations import (
6+
I2CRead,
7+
I2CWrite,
8+
I2CTransfer,
9+
SPIRead,
10+
SPIWrite,
11+
SPITransfer,
12+
UARTRead,
13+
UARTWrite,
14+
)
715
from circuitpython_mocks.digitalio.operations import SetState, GetState
816

917

@@ -13,40 +21,84 @@ class ContextManaged:
1321
def __enter__(self) -> Self:
1422
return self
1523

16-
def __exit__(self, exc_type, exc_value, traceback):
24+
def __exit__(self, exc_type, exc_value, traceback) -> None:
1725
self.deinit()
1826

19-
def deinit(self):
27+
def deinit(self) -> None:
2028
"""Free any hardware used by the object."""
2129
return
2230

2331

2432
class Lockable(ContextManaged):
25-
"""An object that must be locked to prevent collisions on a microcontroller resource."""
33+
"""An object that must be locked to prevent collisions on a microcontroller
34+
resource."""
2635

2736
_locked = False
2837

29-
def try_lock(self):
30-
"""Attempt to grab the lock. Return True on success, False if the lock is already taken."""
38+
def try_lock(self) -> bool:
39+
"""Attempt to grab the lock. Return `True` on success, `False` if the lock is
40+
already taken."""
3141
if self._locked:
3242
return False
3343
self._locked = True
3444
return True
3545

36-
def unlock(self):
46+
def unlock(self) -> None:
3747
"""Release the lock so others may use the resource."""
3848
if self._locked:
3949
self._locked = False
4050

4151

4252
class Expecting:
43-
"""A base class for the mock classes used to assert expected behaviors."""
53+
"""A base class for the mock classes used to assert expected behaviors.
54+
55+
.. seealso::
56+
:title: Mocks that derive from this mixin class
57+
58+
- `busio.I2C`
59+
- `board.I2C()`
60+
- `board.STEMMA_I2C()`
61+
- `busio.SPI`
62+
- `board.SPI()`
63+
- `board.SPI1()`
64+
- `busio.UART`
65+
- `board.UART()`
66+
- `digitalio.DigitalInOut`
67+
"""
4468

4569
def __init__(self, **kwargs) -> None:
46-
#: A double ended queue used to assert expected behavior
47-
self.expectations: deque[Read | Write | Transfer | SetState | GetState] = (
48-
deque()
49-
)
70+
self.expectations: Deque[
71+
Union[
72+
I2CRead,
73+
I2CWrite,
74+
I2CTransfer,
75+
SPIRead,
76+
SPIWrite,
77+
SPITransfer,
78+
UARTRead,
79+
UARTWrite,
80+
SetState,
81+
GetState,
82+
]
83+
] = deque()
84+
"""A double-ended queue (:py:class:`~collections.deque`) used to assert
85+
expected behavior.
86+
87+
.. example::
88+
89+
Examples that use `expectations` can be found in the
90+
91+
- :doc:`busio` documentation
92+
- :doc:`digitalio` documentation
93+
- :py:func:`~circuitpython_mocks.fixtures.mock_blinka_imports`
94+
(pytest fixture) documentation
95+
96+
.. _this package's tests files:
97+
https://github.com/2bndy5/CircuitPython-mocks/tree/main/tests
98+
99+
All examples' source is located in `this package's tests files`_.
100+
"""
101+
50102
super().__init__(**kwargs)
51103

52104
def done(self):

circuitpython_mocks/board.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,13 @@ def SPI():
178178
return ImplSPI(SCK, MOSI, MISO)
179179

180180

181+
def SPI1():
182+
"""Creates a default instance (singleton) of :py:class:`~busio.SPI` (secondary bus)"""
183+
from circuitpython_mocks.busio import SPI as ImplSPI
184+
185+
return ImplSPI(SCK_1, MOSI_1, MISO_1)
186+
187+
181188
def I2C():
182189
"""Creates a default instance (singleton) of :py:class:`~busio.I2C`"""
183190
from circuitpython_mocks.busio import I2C as ImplI2C
@@ -186,7 +193,7 @@ def I2C():
186193

187194

188195
def STEMMA_I2C():
189-
"""Creates a default instance (singleton) of :py:class:`~busio.I2C`"""
196+
"""Creates a default instance (singleton) of :py:class:`~busio.I2C` (secondary bus)"""
190197
from circuitpython_mocks.busio import I2C as ImplI2C
191198

192199
return ImplI2C(SCL1, SDA1)

circuitpython_mocks/busio/__init__.py

Lines changed: 79 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
1-
"""A module to mock the data bus transactions."""
1+
"""A mock of the :external:py:mod:`busio` module.
2+
3+
.. md-tab-set::
4+
5+
.. md-tab-item:: I2C
6+
7+
.. literalinclude:: ../tests/test_i2c.py
8+
:language: python
9+
10+
.. md-tab-item:: SPI
11+
12+
.. literalinclude:: ../tests/test_spi.py
13+
:language: python
14+
15+
.. md-tab-item:: UART
16+
17+
.. literalinclude:: ../tests/test_uart.py
18+
:language: python
19+
"""
220

321
from enum import Enum, auto
422
import sys
@@ -77,8 +95,12 @@ def readfrom_into(
7795
end: int = sys.maxsize,
7896
) -> None:
7997
"""A mock imitation of :external:py:meth:`busio.I2C.readfrom_into()`.
80-
This function checks against `I2CRead`
81-
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`"""
98+
99+
.. mock-expects::
100+
101+
This function checks against `I2CRead`
102+
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.
103+
"""
82104
assert self.expectations, "no expectation found for I2C.readfrom_into()"
83105
op = self.expectations.popleft()
84106
assert isinstance(op, I2CRead), f"Read operation expected, found {repr(op)}"
@@ -95,8 +117,12 @@ def writeto(
95117
end: int = sys.maxsize,
96118
) -> None:
97119
"""A mock imitation of :external:py:meth:`busio.I2C.writeto()`.
98-
This function checks against `I2CWrite`
99-
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`"""
120+
121+
.. mock-expects::
122+
123+
This function checks against `I2CWrite`
124+
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.
125+
"""
100126
assert self.expectations, "no expectation found for I2C.writeto()"
101127
op = self.expectations.popleft()
102128
assert isinstance(op, I2CWrite), f"Read operation expected, found {repr(op)}"
@@ -115,8 +141,12 @@ def writeto_then_readfrom(
115141
in_end: int = sys.maxsize,
116142
) -> None:
117143
"""A mock imitation of :external:py:meth:`busio.I2C.writeto_then_readfrom()`.
118-
This function checks against `I2CTransfer`
119-
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`"""
144+
145+
.. mock-expects::
146+
147+
This function checks against `I2CTransfer`
148+
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.
149+
"""
120150
assert self.expectations, "no expectation found for I2C.writeto_then_readfrom()"
121151
op = self.expectations.popleft()
122152
assert isinstance(
@@ -180,8 +210,12 @@ def write(
180210
end: int = sys.maxsize,
181211
) -> None:
182212
"""A function that mocks :external:py:meth:`busio.SPI.write()`.
183-
This function checks against `SPIWrite`
184-
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`"""
213+
214+
.. mock-expects::
215+
216+
This function checks against `SPIWrite`
217+
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.
218+
"""
185219
assert self.expectations, "no expectation found for SPI.write()"
186220
op = self.expectations.popleft()
187221
assert isinstance(op, SPIWrite), f"Read operation expected, found {repr(op)}"
@@ -196,8 +230,12 @@ def readinto(
196230
write_value: int = 0,
197231
) -> None:
198232
"""A function that mocks :external:py:meth:`busio.SPI.readinto()`.
199-
This function checks against `SPIRead`
200-
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`"""
233+
234+
.. mock-expects::
235+
236+
This function checks against `SPIRead`
237+
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.
238+
"""
201239
assert self.expectations, "no expectation found for SPI.readinto()"
202240
op = self.expectations.popleft()
203241
assert isinstance(op, SPIRead), f"Read operation expected, found {repr(op)}"
@@ -214,8 +252,12 @@ def write_readinto(
214252
in_end: int = sys.maxsize,
215253
) -> None:
216254
"""A function that mocks :external:py:meth:`busio.SPI.write_readinto()`.
217-
This function checks against `SPITransfer`
218-
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`"""
255+
256+
.. mock-expects::
257+
258+
This function checks against `SPITransfer`
259+
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.
260+
"""
219261
assert self.expectations, "no expectation found for SPI.write_readinto()"
220262
op = self.expectations.popleft()
221263
assert isinstance(
@@ -262,8 +304,12 @@ def __init__(
262304

263305
def read(self, nbytes: int | None = None) -> bytes | None:
264306
"""A function that mocks :external:py:meth:`busio.UART.read()`.
265-
This function checks against `UARTRead`
266-
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`"""
307+
308+
.. mock-expects::
309+
310+
This function checks against `UARTRead`
311+
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.
312+
"""
267313
assert self.expectations, "no expectation found for UART.read()"
268314
op = self.expectations.popleft()
269315
assert isinstance(op, UARTRead), f"Read operation expected, found {repr(op)}"
@@ -274,8 +320,12 @@ def read(self, nbytes: int | None = None) -> bytes | None:
274320

275321
def readinto(self, buf: circuitpython_typing.WriteableBuffer) -> int | None:
276322
"""A function that mocks :external:py:meth:`busio.UART.readinto()`.
277-
This function checks against `UARTRead`
278-
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`"""
323+
324+
.. mock-expects::
325+
326+
This function checks against `UARTRead`
327+
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.
328+
"""
279329
assert self.expectations, "no expectation found for UART.readinto()"
280330
op = self.expectations.popleft()
281331
assert isinstance(op, UARTRead), f"Read operation expected, found {repr(op)}"
@@ -285,8 +335,12 @@ def readinto(self, buf: circuitpython_typing.WriteableBuffer) -> int | None:
285335

286336
def readline(self) -> bytes:
287337
"""A function that mocks :external:py:meth:`busio.UART.readline()`.
288-
This function checks against `UARTRead`
289-
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`"""
338+
339+
.. mock-expects::
340+
341+
This function checks against `UARTRead`
342+
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.
343+
"""
290344
assert self.expectations, "no expectation found for UART.readline()"
291345
op = self.expectations.popleft()
292346
assert isinstance(op, UARTRead), f"Read operation expected, found {repr(op)}"
@@ -297,14 +351,15 @@ def readline(self) -> bytes:
297351

298352
def write(self, buf: circuitpython_typing.ReadableBuffer) -> int | None:
299353
"""A function that mocks :external:py:meth:`busio.UART.write()`.
300-
This function checks against `UARTWrite`
301-
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`"""
354+
355+
.. mock-expects::
356+
357+
This function checks against `UARTWrite`
358+
:py:attr:`~circuitpython_mocks._mixins.Expecting.expectations`.
359+
"""
302360
assert self.expectations, "no expectation found for UART.write()"
303361
op = self.expectations.popleft()
304362
assert isinstance(op, UARTWrite), f"Read operation expected, found {repr(op)}"
305363
len_buf = len(op.expected)
306364
op.assert_expected(buf, 0, len_buf)
307365
return len(buf) or None
308-
309-
310-
_UART = UART(TX, RX)

0 commit comments

Comments
 (0)