Implement a test suite, including Travis-CI integration
This commit is contained in:
Dave Jones
2016-02-11 00:32:42 +00:00
parent 29bcada1f9
commit 1f2140a9f5
22 changed files with 1003 additions and 78 deletions

11
.travis.yml Normal file
View File

@@ -0,0 +1,11 @@
language: python
python:
- "3.5"
- "3.4"
- "3.3"
- "3.2"
- "2.7"
- "pypy"
- "pypy3"
install: "pip install -e .[test]"
script: make test

View File

@@ -98,7 +98,7 @@ develop: tags
@# These have to be done separately to avoid a cockup...
$(PIP) install -U setuptools
$(PIP) install -U pip
$(PIP) install -e .
$(PIP) install -e .[doc,test]
test:
$(COVERAGE) run -m $(PYTEST) tests -v

20
coverage.cfg Normal file
View File

@@ -0,0 +1,20 @@
[run]
branch = True
include = gpiozero/*
;omit = */bar.py,*/baz.py
[report]
ignore_errors = True
show_missing = True
exclude_lines =
pragma: no cover
def __repr__
if self\.debug
raise AssertionError
raise NotImplementedError
if 0:
if __name__ == .__main__.:
[html]
directory = coverage

80
docs/api_exc.rst Normal file
View File

@@ -0,0 +1,80 @@
==========
Exceptions
==========
.. currentmodule:: gpiozero
The following exceptions are defined by GPIO Zero. Please note that multiple
inheritance is heavily used in the exception hierarchy to make testing for
exceptions easier. For example, to capture any exception generated by GPIO
Zero's code::
from gpiozero import *
led = PWMLED(17)
try:
led.value = 2
except GPIOZeroError:
print('A GPIO Zero error occurred')
Since all GPIO Zero's exceptions descend from :exc:`GPIOZeroError`, this will
work. However, certain specific errors have multiple parents. For example, in
the case that an out of range value is passed to :attr:`OutputDevice.value` you
would expect a :exc:`ValueError` to be raised. In fact, a
:exc:`OutputDeviceBadValue` error will be raised. However, note that this
descends from both :exc:`GPIOZeroError` (indirectly) and from :exc:`ValueError`
so you can still do::
from gpiozero import *
led = PWMLED(17)
try:
led.value = 2
except ValueError:
print('Bad value specified')
.. autoexception:: GPIOZeroError
.. autoexception:: CompositeDeviceError
.. autoexception:: GPIODeviceError
.. autoexception:: GPIODeviceClosed
.. autoexception:: GPIOPinInUse
.. autoexception:: GPIOPinMissing
.. autoexception:: GPIOBadQueueLen
.. autoexception:: InputDeviceError
.. autoexception:: OutputDeviceError
.. autoexception:: OutputDeviceBadValue
.. autoexception:: PinError
.. autoexception:: PinFixedFunction
.. autoexception:: PinInvalidFunction
.. autoexception:: PinInvalidState
.. autoexception:: PinInvalidPull
.. autoexception:: PinInvalidEdges
.. autoexception:: PinSetInput
.. autoexception:: PinFixedPull
.. autoexception:: PinEdgeDetectUnsupported
.. autoexception:: PinPWMError
.. autoexception:: PinPWMUnsupported
.. autoexception:: PinPWMFixedValue

View File

@@ -13,5 +13,6 @@ Table of Contents
api_boards
api_generic
api_pins
api_exc
changelog
license

View File

@@ -5,7 +5,20 @@ from __future__ import (
division,
)
from .pins.exc import (
from .pins import (
Pin,
)
from .exc import (
GPIOZeroError,
CompositeDeviceError,
GPIODeviceError,
GPIODeviceClosed,
GPIOPinInUse,
GPIOPinMissing,
GPIOBadQueueLen,
InputDeviceError,
OutputDeviceError,
OutputDeviceBadValue,
PinError,
PinFixedFunction,
PinInvalidFunction,
@@ -19,15 +32,6 @@ from .pins.exc import (
PinPWMUnsupported,
PinPWMFixedValue,
)
from .pins import (
Pin,
)
from .exc import (
GPIODeviceClosed,
GPIODeviceError,
InputDeviceError,
OutputDeviceError,
)
from .devices import (
GPIODevice,
CompositeDevice,

27
gpiozero/compat.py Normal file
View File

@@ -0,0 +1,27 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import cmath
# Back-ported from python 3.5; see
# github.com/PythonCHB/close_pep/blob/master/is_close.py for original
# implementation
def isclose(a, b, rel_tol=1e-9, abs_tol=0.0):
if rel_tol < 0.0 or abs_tol < 0.0:
raise ValueError('error tolerances must be non-negative')
if a == b: # fast-path for exact equality
return True
if cmath.isinf(a) or cmath.isinf(b):
return False
diff = abs(b - a)
return (
(diff <= abs(rel_tol * b)) or
(diff <= abs(rel_tol * a)) or
(diff <= abs_tol)
)

View File

@@ -13,7 +13,12 @@ from threading import Thread, Event, RLock
from collections import deque
from types import FunctionType
from .exc import GPIODeviceError, GPIODeviceClosed, InputDeviceError
from .exc import (
GPIOPinMissing,
GPIOPinInUse,
GPIODeviceClosed,
GPIOBadQueueLen,
)
# Get a pin implementation to use as the default; we prefer RPi.GPIO's here
# as it supports PWM, and all Pi revisions. If no third-party libraries are
@@ -203,7 +208,8 @@ class GPIODevice(ValuesMixin, GPIOBase):
:param int pin:
The GPIO pin (in BCM numbering) that the device is connected to. If
this is ``None`` a :exc:`GPIODeviceError` will be raised.
this is ``None``, :exc:`GPIOPinMissing` will be raised. If the pin is
already in use by another device, :exc:`GPIOPinInUse` will be raised.
"""
def __init__(self, pin=None):
super(GPIODevice, self).__init__()
@@ -212,12 +218,12 @@ class GPIODevice(ValuesMixin, GPIOBase):
# value of pin until we've verified that it isn't already allocated
self._pin = None
if pin is None:
raise GPIODeviceError('No pin given')
raise GPIOPinMissing('No pin given')
if isinstance(pin, int):
pin = DefaultPin(pin)
with _PINS_LOCK:
if pin in _PINS:
raise GPIODeviceError(
raise GPIOPinInUse(
'pin %r is already in use by another gpiozero object' % pin
)
_PINS.add(pin)
@@ -342,7 +348,7 @@ class GPIOQueue(GPIOThread):
assert isinstance(parent, GPIODevice)
super(GPIOQueue, self).__init__(target=self.fill)
if queue_len < 1:
raise InputDeviceError('queue_len must be at least one')
raise GPIOBadQueueLen('queue_len must be at least one')
self.queue = deque(maxlen=queue_len)
self.partial = partial
self.sample_wait = sample_wait

View File

@@ -4,16 +4,72 @@ from __future__ import (
absolute_import,
division,
)
str = type('')
class GPIODeviceError(Exception):
pass
class GPIOZeroError(Exception):
"Base class for all exceptions in GPIO Zero"
class CompositeDeviceError(GPIOZeroError):
"Base class for errors specific to the CompositeDevice hierarchy"
class GPIODeviceError(GPIOZeroError):
"Base class for errors specific to the GPIODevice hierarchy"
class GPIODeviceClosed(GPIODeviceError):
pass
"Error raised when an operation is attempted on a closed device"
class GPIOPinInUse(GPIODeviceError):
"Error raised when attempting to use a pin already in use by another device"
class GPIOPinMissing(GPIODeviceError, ValueError):
"Error raised when a pin number is not specified"
class GPIOBadQueueLen(GPIODeviceError, ValueError):
"Error raised when non-positive queue length is specified"
class InputDeviceError(GPIODeviceError):
pass
"Base class for errors specific to the InputDevice hierarchy"
class OutputDeviceError(GPIODeviceError):
pass
"Base class for errors specified to the OutputDevice hierarchy"
class OutputDeviceBadValue(OutputDeviceError, ValueError):
"Error raised when ``value`` is set to an invalid value"
class PinError(GPIOZeroError):
"Base class for errors related to pin implementations"
class PinFixedFunction(PinError, AttributeError):
"Error raised when attempting to change the function of a fixed type pin"
class PinInvalidFunction(PinError, ValueError):
"Error raised when attempting to change the function of a pin to an invalid value"
class PinInvalidState(PinError, ValueError):
"Error raised when attempting to assign an invalid state to a pin"
class PinInvalidPull(PinError, ValueError):
"Error raised when attempting to assign an invalid pull-up to a pin"
class PinInvalidEdges(PinError, ValueError):
"Error raised when attempting to assign an invalid edge detection to a pin"
class PinSetInput(PinError, AttributeError):
"Error raised when attempting to set a read-only pin"
class PinFixedPull(PinError, AttributeError):
"Error raised when attempting to set the pull of a pin with fixed pull-up"
class PinEdgeDetectUnsupported(PinError, AttributeError):
"Error raised when attempting to use edge detection on unsupported pins"
class PinPWMError(PinError):
"Base class for errors related to PWM implementations"
class PinPWMUnsupported(PinPWMError, AttributeError):
"Error raised when attempting to activate PWM on unsupported pins"
class PinPWMFixedValue(PinPWMError, AttributeError):
"Error raised when attempting to initialize PWM on an input pin"

View File

@@ -10,7 +10,7 @@ from time import sleep
from threading import Lock
from itertools import repeat, cycle, chain
from .exc import OutputDeviceError, GPIODeviceError, GPIODeviceClosed
from .exc import OutputDeviceBadValue, GPIOPinMissing, GPIODeviceClosed
from .devices import GPIODevice, GPIOThread, CompositeDevice, SourceMixin
@@ -24,7 +24,7 @@ class OutputDevice(SourceMixin, GPIODevice):
:param int pin:
The GPIO pin (in BCM numbering) that the device is connected to. If
this is ``None`` a :exc:`GPIODeviceError` will be raised.
this is ``None`` a :exc:`GPIOPinMissing` will be raised.
:param bool active_high:
If ``True`` (the default), the :meth:`on` method will set the GPIO to
@@ -54,7 +54,7 @@ class OutputDevice(SourceMixin, GPIODevice):
value = not value
try:
self.pin.state = bool(value)
except ValueError:
except AttributeError:
self._check_open()
raise
@@ -269,7 +269,7 @@ class PWMOutputDevice(OutputDevice):
def __init__(self, pin=None, active_high=True, initial_value=0, frequency=100):
self._blink_thread = None
if not 0 <= initial_value <= 1:
raise OutputDeviceError("initial_value must be between 0 and 1")
raise OutputDeviceBadValue("initial_value must be between 0 and 1")
super(PWMOutputDevice, self).__init__(pin, active_high)
try:
# XXX need a way of setting these together
@@ -299,7 +299,7 @@ class PWMOutputDevice(OutputDevice):
if not self.active_high:
value = 1 - value
if not 0 <= value <= 1:
raise OutputDeviceError("PWM value must be between 0 and 1")
raise OutputDeviceBadValue("PWM value must be between 0 and 1")
try:
self.pin.state = value
except AttributeError:
@@ -505,7 +505,7 @@ class RGBLED(SourceMixin, CompositeDevice):
self._leds = ()
self._blink_thread = None
if not all([red, green, blue]):
raise OutputDeviceError('red, green, and blue pins must be provided')
raise GPIOPinMissing('red, green, and blue pins must be provided')
super(RGBLED, self).__init__()
self._leds = tuple(PWMLED(pin, active_high) for pin in (red, green, blue))
self.value = initial_value
@@ -680,7 +680,7 @@ class Motor(SourceMixin, CompositeDevice):
"""
def __init__(self, forward=None, backward=None):
if not all([forward, backward]):
raise OutputDeviceError(
raise GPIOPinMissing(
'forward and backward pins must be provided'
)
super(Motor, self).__init__()
@@ -722,7 +722,7 @@ class Motor(SourceMixin, CompositeDevice):
@value.setter
def value(self, value):
if not -1 <= value <= 1:
raise OutputDeviceError("Motor value must be between -1 and 1")
raise OutputDeviceBadValue("Motor value must be between -1 and 1")
if value > 0:
self.forward(value)
elif value < 0:

View File

@@ -6,7 +6,7 @@ from __future__ import (
)
str = type('')
from .exc import (
from ..exc import (
PinFixedFunction,
PinSetInput,
PinFixedPull,

View File

@@ -1,45 +0,0 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
class PinError(Exception):
"Base class for errors related to pin implementations"
class PinFixedFunction(PinError, AttributeError):
"Error raised when attempting to change the function of a fixed type pin"
class PinInvalidFunction(PinError, ValueError):
"Error raised when attempting to change the function of a pin to an invalid value"
class PinInvalidState(PinError, ValueError):
"Error raised when attempting to assign an invalid state to a pin"
class PinInvalidPull(PinError, ValueError):
"Error raised when attempting to assign an invalid pull-up to a pin"
class PinInvalidEdges(PinError, ValueError):
"Error raised when attempting to assign an invalid edge detection to a pin"
class PinSetInput(PinError, AttributeError):
"Error raised when attempting to set a read-only pin"
class PinFixedPull(PinError, AttributeError):
"Error raised when attempting to set the pull of a pin with fixed pull-up"
class PinEdgeDetectUnsupported(PinError, AttributeError):
"Error raised when attempting to use edge detection on unsupported pins"
class PinPWMError(PinError):
"Base class for errors related to PWM implementations"
class PinPWMUnsupported(PinPWMError, AttributeError):
"Error raised when attempting to activate PWM on unsupported pins"
class PinPWMFixedValue(PinPWMError, AttributeError):
"Error raised when attempting to initialize PWM on an input pin"

171
gpiozero/pins/mock.py Normal file
View File

@@ -0,0 +1,171 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
from collections import namedtuple
from time import time
try:
from math import isclose
except ImportError:
from ..compat import isclose
from . import Pin, PINS_CLEANUP
from ..exc import PinSetInput, PinPWMUnsupported
PinState = namedtuple('PinState', ('timestamp', 'state'))
class MockPin(Pin):
"""
A mock pin used primarily for testing. This class does *not* support PWM.
"""
def __init__(self, number):
if not (0 <= number < 54):
raise ValueError('invalid pin %d specified (must be 0..53)' % number)
self._number = number
self._function = 'input'
self._state = False
self._pull = 'floating'
self._bounce = None
self._edges = 'both'
self._when_changed = None
self._last_change = time()
self.states = [PinState(0.0, False)]
def __repr__(self):
return 'MOCK%d' % self._number
@property
def number(self):
return self._number
def close(self):
self.when_changed = None
self.function = 'input'
def _get_function(self):
return self._function
def _set_function(self, value):
assert value in ('input', 'output')
self._function = value
if value == 'input':
# Drive the input to the pull
self._set_pull(self._get_pull())
def _get_state(self):
return self._state
def _set_state(self, value):
if self._function == 'input':
raise PinSetInput()
assert self._function == 'output'
assert 0 <= value <= 1
if self._state != value:
t = time()
self._state = value
self.states.append(PinState(t - self._last_change, value))
self._last_change = t
def _get_frequency(self):
return None
def _set_frequency(self, value):
raise PinPWMUnsupported()
def _get_pull(self):
return self._pull
def _set_pull(self, value):
assert self._function == 'input'
assert value in ('floating', 'up', 'down')
self._pull = value
if value == 'up':
self.drive_high()
elif value == 'down':
self.drive_low()
def _get_bounce(self):
return self._bounce
def _set_bounce(self, value):
# XXX Need to implement this
self._bounce = value
def _get_edges(self):
return self._edges
def _set_edges(self, value):
assert value in ('none', 'falling', 'rising', 'both')
self._edges = value
def _get_when_changed(self):
return self._when_changed
def _set_when_changed(self, value):
self._when_changed = value
def drive_high(self):
assert self._function == 'input'
if not self._state:
t = time()
self._state = True
self.states.append(PinState(t - self._last_change, True))
self._last_change = t
if self._edges in ('both', 'rising') and self._when_changed is not None:
self._when_changed()
def drive_low(self):
assert self._function == 'input'
if self._state:
t = time()
self._state = False
self.states.append(PinState(t - self._last_change, False))
self._last_change = t
if self._edges in ('both', 'falling') and self._when_changed is not None:
self._when_changed()
def clear_states(self):
self._last_change = time()
self.states = [PinState(0.0, self.state)]
def assert_states(self, expected_states):
# Tests that the pin went through the expected states (a list of values)
for actual, expected in zip(self.states, expected_states):
assert actual.state == expected
def assert_states_and_times(self, expected_states):
# Tests that the pin went through the expected states at the expected
# times (times are compared with a tolerance of tens-of-milliseconds as
# that's about all we can reasonably expect in a non-realtime
# environment on a Pi 1)
for actual, expected in zip(self.states, expected_states):
assert isclose(actual.timestamp, expected[0], rel_tol=0.01, abs_tol=0.01)
assert isclose(actual.state, expected[1])
class MockPWMPin(MockPin):
"""
This derivative of :class:`MockPin` adds PWM support.
"""
def __init__(self, number):
super(MockPWMPin, self).__init__(number)
self._frequency = None
def _get_frequency(self):
return self._frequency
def _set_frequency(self, value):
if value is not None:
assert self._function == 'output'
self._frequency = value
if value is None:
self.state = False

View File

@@ -17,7 +17,7 @@ from threading import Thread, Event, Lock
from collections import Counter
from . import Pin, PINS_CLEANUP
from .exc import (
from ..exc import (
PinInvalidPull,
PinInvalidEdges,
PinInvalidFunction,

View File

@@ -9,7 +9,7 @@ str = type('')
from RPi import GPIO
from . import Pin
from .exc import (
from ..exc import (
PinInvalidFunction,
PinSetInput,
PinFixedPull,

View File

@@ -13,7 +13,7 @@ import RPIO
import RPIO.PWM
from . import Pin, PINS_CLEANUP
from .exc import (
from ..exc import (
PinInvalidFunction,
PinSetInput,
PinFixedPull,

View File

@@ -49,8 +49,18 @@ __requires__ = [
]
__extra_requires__ = {
'doc': ['sphinx'],
'test': ['pytest', 'coverage', 'mock'],
}
if sys.version_info[:2] == (3, 2):
# Particular versions are required for Python 3.2 compatibility
__extra_requires__['doc'].extend([
'Jinja2<2.7',
'MarkupSafe<0.16',
])
__extra_requires__['test'][1] = 'coverage<4.0dev'
__entry_points__ = {
}

17
tests/test_boards.py Normal file
View File

@@ -0,0 +1,17 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import pytest
from gpiozero.pins.mock import MockPin
from gpiozero import *
# TODO boards tests!

17
tests/test_devices.py Normal file
View File

@@ -0,0 +1,17 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import pytest
from gpiozero.pins.mock import MockPin
from gpiozero import *
# TODO devices tests!

16
tests/test_inputs.py Normal file
View File

@@ -0,0 +1,16 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import pytest
from gpiozero.pins.mock import MockPin
from gpiozero import *
# TODO input_devices tests!

74
tests/test_mock_pin.py Normal file
View File

@@ -0,0 +1,74 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
from threading import Event
import pytest
from gpiozero.pins.mock import MockPin, MockPWMPin
from gpiozero import *
# Some rough tests to make sure our MockPin is up to snuff. This is just
# enough to get reasonable coverage but it's by no means comprehensive...
def test_mock_pin_init():
with pytest.raises(ValueError):
MockPin(60)
assert MockPin(2).number == 2
def test_mock_pin_frequency_unsupported():
with pytest.raises(AttributeError):
pin = MockPin(3)
pin.frequency = 100
def test_mock_pin_frequency_supported():
pin = MockPWMPin(3)
pin.function = 'output'
assert pin.frequency is None
pin.frequency = 100
pin.state = 0.5
pin.frequency = None
assert not pin.state
def test_mock_pin_pull():
pin = MockPin(4)
pin.function = 'input'
assert pin.pull == 'floating'
pin.pull = 'up'
assert pin.state
pin.pull = 'down'
assert not pin.state
def test_mock_pin_edges():
pin = MockPin(5)
assert pin.when_changed is None
fired = Event()
pin.function = 'input'
pin.edges = 'both'
assert pin.edges == 'both'
pin.drive_low()
assert not pin.state
def changed():
fired.set()
pin.when_changed = changed
pin.drive_high()
assert pin.state
assert fired.wait(0)
fired.clear()
pin.edges = 'falling'
pin.drive_low()
assert not pin.state
assert fired.wait(0)
fired.clear()
pin.drive_high()
assert pin.state
assert not fired.wait(0)
assert pin.edges == 'falling'

460
tests/test_outputs.py Normal file
View File

@@ -0,0 +1,460 @@
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
try:
from math import isclose
except ImportError:
from gpiozero.compat import isclose
import pytest
from time import sleep
from gpiozero.pins.mock import MockPin, MockPWMPin
from gpiozero import *
def test_output_initial_values():
pin = MockPin(2)
device = OutputDevice(pin, initial_value=False)
assert pin.function == 'output'
assert not pin.state
device.close()
device = OutputDevice(pin, initial_value=True)
assert pin.state
device.close()
state = pin.state
device = OutputDevice(pin, initial_value=None)
assert state == pin.state
def test_output_write_active_high():
pin = MockPin(2)
device = OutputDevice(pin)
device.on()
assert pin.state
device.off()
assert not pin.state
def test_output_write_active_low():
pin = MockPin(2)
device = OutputDevice(pin, active_high=False)
device.on()
assert not pin.state
device.off()
assert pin.state
def test_output_write_closed():
device = OutputDevice(MockPin(2))
device.close()
with pytest.raises(GPIODeviceClosed):
device.on()
def test_output_write_silly():
pin = MockPin(2)
device = OutputDevice(pin)
pin.function = 'input'
with pytest.raises(AttributeError):
device.on()
def test_output_value():
pin = MockPin(2)
device = OutputDevice(pin)
assert not device.value
assert not pin.state
device.on()
assert device.value
assert pin.state
device.value = False
assert not device.value
assert not pin.state
def test_output_digital_toggle():
pin = MockPin(2)
device = DigitalOutputDevice(pin)
assert not device.value
assert not pin.state
device.toggle()
assert device.value
assert pin.state
device.toggle()
assert not device.value
assert not pin.state
def test_output_blink_background():
pin = MockPin(2)
device = DigitalOutputDevice(pin)
device.blink(0.1, 0.1, n=2)
device._blink_thread.join() # naughty, but ensures no arbitrary waits in the test
pin.assert_states_and_times([
(0.0, False),
(0.0, True),
(0.1, False),
(0.1, True),
(0.1, False)
])
def test_output_blink_foreground():
pin = MockPin(2)
device = DigitalOutputDevice(pin)
device.blink(0.1, 0.1, n=2, background=False)
pin.assert_states_and_times([
(0.0, False),
(0.0, True),
(0.1, False),
(0.1, True),
(0.1, False)
])
def test_output_blink_interrupt_on():
pin = MockPin(2)
device = DigitalOutputDevice(pin)
device.blink(1, 0.1)
sleep(0.2)
device.off() # should interrupt while on
pin.assert_states([False, True, False])
def test_output_blink_interrupt_off():
pin = MockPin(2)
device = DigitalOutputDevice(pin)
device.blink(0.1, 1)
sleep(0.2)
device.off() # should interrupt while off
pin.assert_states([False, True, False])
def test_output_pwm_bad_initial_value():
with pytest.raises(ValueError):
PWMOutputDevice(MockPin(2), initial_value=2)
def test_output_pwm_not_supported():
with pytest.raises(AttributeError):
PWMOutputDevice(MockPin(2))
def test_output_pwm_states():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin)
device.value = 0.1
device.value = 0.2
device.value = 0.0
pin.assert_states([0.0, 0.1, 0.2, 0.0])
def test_output_pwm_read():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin, frequency=100)
assert device.frequency == 100
device.value = 0.1
assert isclose(device.value, 0.1)
assert isclose(pin.state, 0.1)
assert device.is_active
device.frequency = None
assert not device.value
assert not device.is_active
assert device.frequency is None
def test_output_pwm_write():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin)
device.on()
device.off()
pin.assert_states([False, True, False])
def test_output_pwm_toggle():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin)
device.toggle()
device.value = 0.5
device.value = 0.1
device.toggle()
device.off()
pin.assert_states([False, True, 0.5, 0.1, 0.9, False])
def test_output_pwm_active_high_read():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin, active_high=False)
device.value = 0.1
assert isclose(device.value, 0.1)
assert isclose(pin.state, 0.9)
device.frequency = None
assert device.value
def test_output_pwm_bad_value():
with pytest.raises(ValueError):
PWMOutputDevice(MockPWMPin(2)).value = 2
def test_output_pwm_write_closed():
device = PWMOutputDevice(MockPWMPin(2))
device.close()
with pytest.raises(GPIODeviceClosed):
device.on()
def test_output_pwm_write_silly():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin)
pin.function = 'input'
with pytest.raises(AttributeError):
device.off()
def test_output_pwm_blink_background():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin)
device.blink(0.1, 0.1, n=2)
device._blink_thread.join()
pin.assert_states_and_times([
(0.0, 0),
(0.0, 1),
(0.1, 0),
(0.1, 1),
(0.1, 0)
])
def test_output_pwm_blink_foreground():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin)
device.blink(0.1, 0.1, n=2, background=False)
pin.assert_states_and_times([
(0.0, 0),
(0.0, 1),
(0.1, 0),
(0.1, 1),
(0.1, 0)
])
def test_output_pwm_fade_background():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin)
device.blink(0, 0, 0.1, 0.1, n=2)
device._blink_thread.join()
pin.assert_states_and_times([
(0.0, 0),
(0.02, 0.2),
(0.02, 0.4),
(0.02, 0.6),
(0.02, 0.8),
(0.02, 1),
(0.02, 0.8),
(0.02, 0.6),
(0.02, 0.4),
(0.02, 0.2),
(0.02, 0),
(0.02, 0.2),
(0.02, 0.4),
(0.02, 0.6),
(0.02, 0.8),
(0.02, 1),
(0.02, 0.8),
(0.02, 0.6),
(0.02, 0.4),
(0.02, 0.2),
(0.02, 0),
])
def test_output_pwm_fade_foreground():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin)
device.blink(0, 0, 0.1, 0.1, n=2, background=False)
pin.assert_states_and_times([
(0.0, 0),
(0.02, 0.2),
(0.02, 0.4),
(0.02, 0.6),
(0.02, 0.8),
(0.02, 1),
(0.02, 0.8),
(0.02, 0.6),
(0.02, 0.4),
(0.02, 0.2),
(0.02, 0),
(0.02, 0.2),
(0.02, 0.4),
(0.02, 0.6),
(0.02, 0.8),
(0.02, 1),
(0.02, 0.8),
(0.02, 0.6),
(0.02, 0.4),
(0.02, 0.2),
(0.02, 0),
])
def test_output_pwm_blink_interrupt():
pin = MockPWMPin(2)
device = PWMOutputDevice(pin)
device.blink(1, 0.1)
sleep(0.2)
device.off() # should interrupt while on
pin.assert_states([0, 1, 0])
def test_rgbled_missing_pins():
with pytest.raises(ValueError):
RGBLED()
def test_rgbled_initial_value():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3))
device = RGBLED(r, g, b, initial_value=(0.1, 0.2, 0))
assert r.frequency
assert g.frequency
assert b.frequency
assert isclose(r.state, 0.1)
assert isclose(g.state, 0.2)
assert isclose(b.state, 0.0)
def test_rgbled_value():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3))
device = RGBLED(r, g, b)
assert not device.is_active
assert device.value == (0, 0, 0)
device.on()
assert device.is_active
assert device.value == (1, 1, 1)
device.off()
assert not device.is_active
assert device.value == (0, 0, 0)
def test_rgbled_toggle():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3))
device = RGBLED(r, g, b)
assert not device.is_active
assert device.value == (0, 0, 0)
device.toggle()
assert device.is_active
assert device.value == (1, 1, 1)
device.toggle()
assert not device.is_active
assert device.value == (0, 0, 0)
def test_rgbled_blink_background():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3))
device = RGBLED(r, g, b)
device.blink(0.1, 0.1, n=2)
device._blink_thread.join()
expected = [
(0.0, 0),
(0.0, 1),
(0.1, 0),
(0.1, 1),
(0.1, 0)
]
r.assert_states_and_times(expected)
g.assert_states_and_times(expected)
b.assert_states_and_times(expected)
def test_rgbled_blink_foreground():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3))
device = RGBLED(r, g, b)
device.blink(0.1, 0.1, n=2, background=False)
expected = [
(0.0, 0),
(0.0, 1),
(0.1, 0),
(0.1, 1),
(0.1, 0)
]
r.assert_states_and_times(expected)
g.assert_states_and_times(expected)
b.assert_states_and_times(expected)
def test_rgbled_fade_background():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3))
device = RGBLED(r, g, b)
device.blink(0, 0, 0.1, 0.1, n=2)
device._blink_thread.join()
expected = [
(0.0, 0),
(0.02, 0.2),
(0.02, 0.4),
(0.02, 0.6),
(0.02, 0.8),
(0.02, 1),
(0.02, 0.8),
(0.02, 0.6),
(0.02, 0.4),
(0.02, 0.2),
(0.02, 0),
(0.02, 0.2),
(0.02, 0.4),
(0.02, 0.6),
(0.02, 0.8),
(0.02, 1),
(0.02, 0.8),
(0.02, 0.6),
(0.02, 0.4),
(0.02, 0.2),
(0.02, 0),
]
r.assert_states_and_times(expected)
g.assert_states_and_times(expected)
b.assert_states_and_times(expected)
def test_output_rgbled_blink_interrupt():
r, g, b = (MockPWMPin(i) for i in (1, 2, 3))
device = RGBLED(r, g, b)
device.blink(1, 0.1)
sleep(0.2)
device.off() # should interrupt while on
r.assert_states([0, 1, 0])
g.assert_states([0, 1, 0])
b.assert_states([0, 1, 0])
def test_motor_missing_pins():
with pytest.raises(ValueError):
Motor()
def test_motor_pins():
f = MockPWMPin(1)
b = MockPWMPin(2)
device = Motor(f, b)
assert device.forward_device.pin is f
assert device.backward_device.pin is b
def test_motor_close():
f = MockPWMPin(1)
b = MockPWMPin(2)
device = Motor(f, b)
device.close()
assert device.closed
assert device.forward_device.pin is None
assert device.backward_device.pin is None
def test_motor_value():
f = MockPWMPin(1)
b = MockPWMPin(2)
device = Motor(f, b)
device.value = -1
assert device.is_active
assert device.value == -1
assert b.state == 1 and f.state == 0
device.value = 1
assert device.is_active
assert device.value == 1
assert b.state == 0 and f.state == 1
device.value = 0.5
assert device.is_active
assert device.value == 0.5
assert b.state == 0 and f.state == 0.5
device.value = 0
assert not device.is_active
assert not device.value
assert b.state == 0 and f.state == 0
def test_motor_bad_value():
f = MockPWMPin(1)
b = MockPWMPin(2)
device = Motor(f, b)
with pytest.raises(ValueError):
device.value = 2
def test_motor_reverse():
f = MockPWMPin(1)
b = MockPWMPin(2)
device = Motor(f, b)
device.forward()
assert device.value == 1
assert b.state == 0 and f.state == 1
device.reverse()
assert device.value == -1
assert b.state == 1 and f.state == 0