Fix real pin tests ... and some other bits

The real pin tests were broken by the new factory stuff. This commit
fixes them up, and fixes up a few other bits besides (like why the
pigpio PWM tests were failing, why RPi.GPIO sometimes segfaulted on PWM
tests, etc.)

It also causes the real pin tests to run against MockPin (thanks to
@lurch for the suggestion!). This required some tweaks to MockPin to
make it emulate physically pulled up pins itself (which in turn
necessitated changing quite a few pin numbers in the main test suite
because we were using 2 and 3 everywhere), and to allow one MockPin to
drive another. Anyway, everything's working now including all the tests
on a Pi (haven't tried RPIO yet, but only because I'm on a Pi3 -
everything else works with overall coverage of 88% :).
This commit is contained in:
Dave Jones
2016-10-22 22:28:33 +01:00
parent 4049ef5094
commit 2495939603
9 changed files with 265 additions and 159 deletions

View File

@@ -18,7 +18,13 @@ except ImportError:
import pkg_resources
from ..exc import PinPWMUnsupported, PinSetInput, PinFixedPull
from ..exc import (
PinPWMUnsupported,
PinSetInput,
PinFixedPull,
PinInvalidFunction,
PinInvalidPull,
)
from ..devices import Device
from .pi import PiPin
from .local import LocalPiFactory
@@ -34,8 +40,8 @@ class MockPin(PiPin):
def __init__(self, factory, number):
super(MockPin, self).__init__(factory, number)
self._function = 'input'
self._state = False
self._pull = 'floating'
self._pull = 'up' if factory.pi_info.pulled_up(self.address[-1]) else 'floating'
self._state = self._pull == 'up'
self._bounce = None
self._edges = 'both'
self._when_changed = None
@@ -49,7 +55,8 @@ class MockPin(PiPin):
return self._function
def _set_function(self, value):
assert value in ('input', 'output')
if value not in ('input', 'output'):
raise PinInvalidFunction('function must be input or output')
self._function = value
if value == 'input':
# Drive the input to the pull
@@ -85,8 +92,12 @@ class MockPin(PiPin):
return self._pull
def _set_pull(self, value):
assert self._function == 'input'
assert value in ('floating', 'up', 'down')
if self.function != 'input':
raise PinFixedPull('cannot set pull on non-input pin %r' % self)
if value != 'up' and self.factory.pi_info.pulled_up(self.address[-1]):
raise PinFixedPull('%r has a physical pull-up resistor' % self)
if value not in ('floating', 'up', 'down'):
raise PinInvalidPull('pull must be floating, up, or down')
self._pull = value
if value == 'up':
self.drive_high()
@@ -132,7 +143,12 @@ class MockPin(PiPin):
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
try:
assert actual.state == expected
except AssertionError:
print('Actual states', self.states)
print('Expected states', expected_states)
raise
def assert_states_and_times(self, expected_states):
# Tests that the pin went through the expected states at the expected
@@ -140,8 +156,32 @@ class MockPin(PiPin):
# 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.05, abs_tol=0.05)
assert isclose(actual.state, expected[1])
try:
assert isclose(actual.timestamp, expected[0], rel_tol=0.05, abs_tol=0.05)
assert isclose(actual.state, expected[1])
except AssertionError:
print('Actual states', self.states)
print('Expected states', expected_states)
raise
class MockConnectedPin(MockPin):
"""
This derivative of :class:`MockPin` emulates a pin connected to another
mock pin. This is used in the "real pins" portion of the test suite to
check that one pin can influence another.
"""
def __init__(self, factory, number):
super(MockConnectedPin, self).__init__(factory, number)
self.input_pin = None
def _change_state(self, value):
if self.input_pin:
if value:
self.input_pin.drive_high()
else:
self.input_pin.drive_low()
return super(MockConnectedPin, self)._change_state(value)
class MockPulledUpPin(MockPin):

View File

@@ -236,6 +236,12 @@ class PiGPIOPin(PiPin):
def _set_frequency(self, value):
if not self._pwm and value is not None:
if self.function != 'output':
raise PinPWMFixedValue('cannot start PWM on pin %r' % self)
# NOTE: the pin's state *must* be set to zero; if it's currently
# high, starting PWM and setting a 0 duty-cycle *doesn't* bring
# the pin low; it stays high!
self.factory.connection.write(self.number, 0)
self.factory.connection.set_PWM_frequency(self.number, value)
self.factory.connection.set_PWM_range(self.number, 10000)
self.factory.connection.set_PWM_dutycycle(self.number, 0)

View File

@@ -77,7 +77,6 @@ __entry_points__ = {
'gpiozero_mock_pin_classes': [
'mockpin = gpiozero.pins.mock:MockPin',
'mockpwmpin = gpiozero.pins.mock:MockPWMPin',
'mockpulleduppin = gpiozero.pins.mock:MockPulledUpPin',
'mockchargingpin = gpiozero.pins.mock:MockChargingPin',
'mocktriggerpin = gpiozero.pins.mock:MockTriggerPin',
],

View File

@@ -246,10 +246,15 @@ def test_led_board_bad_blink():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_blink_background():
pin1 = Device._pin_factory.pin(2)
pin2 = Device._pin_factory.pin(3)
pin3 = Device._pin_factory.pin(4)
pin1 = Device._pin_factory.pin(4)
pin2 = Device._pin_factory.pin(5)
pin3 = Device._pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
# Instantiation takes a long enough time that it throws off our timing
# here!
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board.blink(0.1, 0.1, n=2)
board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test
test = [
@@ -266,10 +271,13 @@ def test_led_board_blink_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_blink_foreground():
pin1 = Device._pin_factory.pin(2)
pin2 = Device._pin_factory.pin(3)
pin3 = Device._pin_factory.pin(4)
pin1 = Device._pin_factory.pin(4)
pin2 = Device._pin_factory.pin(5)
pin3 = Device._pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board.blink(0.1, 0.1, n=2, background=False)
test = [
(0.0, False),
@@ -285,10 +293,13 @@ def test_led_board_blink_foreground():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_blink_control():
pin1 = Device._pin_factory.pin(2)
pin2 = Device._pin_factory.pin(3)
pin3 = Device._pin_factory.pin(4)
pin1 = Device._pin_factory.pin(4)
pin2 = Device._pin_factory.pin(5)
pin3 = Device._pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board.blink(0.1, 0.1, n=2)
# make sure the blink thread's started
while not board._blink_leds:
@@ -310,10 +321,13 @@ def test_led_board_blink_control():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_blink_take_over():
pin1 = Device._pin_factory.pin(2)
pin2 = Device._pin_factory.pin(3)
pin3 = Device._pin_factory.pin(4)
pin1 = Device._pin_factory.pin(4)
pin2 = Device._pin_factory.pin(5)
pin3 = Device._pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board[1].blink(0.1, 0.1, n=2)
board.blink(0.1, 0.1, n=2) # immediately take over blinking
board[1]._blink_thread.join()
@@ -332,10 +346,13 @@ def test_led_board_blink_take_over():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_blink_control_all():
pin1 = Device._pin_factory.pin(2)
pin2 = Device._pin_factory.pin(3)
pin3 = Device._pin_factory.pin(4)
pin1 = Device._pin_factory.pin(4)
pin2 = Device._pin_factory.pin(5)
pin3 = Device._pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board.blink(0.1, 0.1, n=2)
# make sure the blink thread's started
while not board._blink_leds:
@@ -354,9 +371,9 @@ def test_led_board_blink_control_all():
pin3.assert_states_and_times(test)
def test_led_board_blink_interrupt_on():
pin1 = Device._pin_factory.pin(2)
pin2 = Device._pin_factory.pin(3)
pin3 = Device._pin_factory.pin(4)
pin1 = Device._pin_factory.pin(4)
pin2 = Device._pin_factory.pin(5)
pin3 = Device._pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
board.blink(1, 0.1)
sleep(0.2)
@@ -366,10 +383,13 @@ def test_led_board_blink_interrupt_on():
pin3.assert_states([False, True, False])
def test_led_board_blink_interrupt_off():
pin1 = Device._pin_factory.pin(2)
pin2 = Device._pin_factory.pin(3)
pin3 = Device._pin_factory.pin(4)
pin1 = Device._pin_factory.pin(4)
pin2 = Device._pin_factory.pin(5)
pin3 = Device._pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3)) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board.blink(0.1, 1)
sleep(0.2)
board.off() # should interrupt while off
@@ -380,10 +400,13 @@ def test_led_board_blink_interrupt_off():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_led_board_fade_background():
pin1 = Device._pin_factory.pin(2)
pin2 = Device._pin_factory.pin(3)
pin3 = Device._pin_factory.pin(4)
pin1 = Device._pin_factory.pin(4)
pin2 = Device._pin_factory.pin(5)
pin3 = Device._pin_factory.pin(6)
with LEDBoard(pin1, LEDBoard(pin2, pin3, pwm=True), pwm=True) as board:
pin1.clear_states()
pin2.clear_states()
pin3.clear_states()
board.blink(0, 0, 0.2, 0.2, n=2)
board._blink_thread.join()
test = [

View File

@@ -65,7 +65,7 @@ def test_device_reopen_same_pin():
assert device.pin is None
def test_device_repr():
with GPIODevice(2) as device:
with GPIODevice(4) as device:
assert repr(device) == '<gpiozero.GPIODevice object on pin %s, is_active=False>' % device.pin
def test_device_repr_after_close():
@@ -85,18 +85,18 @@ def test_device_context_manager():
def test_composite_device_sequence():
with CompositeDevice(
InputDevice(2),
InputDevice(3)
InputDevice(4),
InputDevice(5)
) as device:
assert len(device) == 2
assert device[0].pin.number == 2
assert device[1].pin.number == 3
assert device[0].pin.number == 4
assert device[1].pin.number == 5
assert device.namedtuple._fields == ('_0', '_1')
def test_composite_device_values():
with CompositeDevice(
InputDevice(2),
InputDevice(3)
InputDevice(4),
InputDevice(5)
) as device:
assert device.value == (0, 0)
assert not device.is_active
@@ -106,8 +106,8 @@ def test_composite_device_values():
def test_composite_device_named():
with CompositeDevice(
foo=InputDevice(2),
bar=InputDevice(3),
foo=InputDevice(4),
bar=InputDevice(5),
_order=('foo', 'bar')
) as device:
assert device.namedtuple._fields == ('foo', 'bar')
@@ -126,8 +126,8 @@ def test_composite_device_bad_init():
def test_composite_device_read_only():
with CompositeDevice(
foo=InputDevice(2),
bar=InputDevice(3)
foo=InputDevice(4),
bar=InputDevice(5)
) as device:
with pytest.raises(AttributeError):
device.foo = 1

View File

@@ -12,7 +12,7 @@ import pytest
from threading import Event
from functools import partial
from gpiozero.pins.mock import MockPulledUpPin, MockChargingPin, MockTriggerPin
from gpiozero.pins.mock import MockChargingPin, MockTriggerPin
from gpiozero import *
@@ -22,7 +22,7 @@ def teardown_function(function):
def test_input_initial_values():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with InputDevice(pin, pull_up=True) as device:
assert pin.function == 'input'
assert pin.pull == 'up'
@@ -42,23 +42,23 @@ def test_input_is_active_low():
assert repr(device) == '<gpiozero.InputDevice object on pin GPIO2, pull_up=True, is_active=True>'
def test_input_is_active_high():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with InputDevice(pin, pull_up=False) as device:
pin.drive_high()
assert device.is_active
assert repr(device) == '<gpiozero.InputDevice object on pin GPIO2, pull_up=False, is_active=True>'
assert repr(device) == '<gpiozero.InputDevice object on pin GPIO4, pull_up=False, is_active=True>'
pin.drive_low()
assert not device.is_active
assert repr(device) == '<gpiozero.InputDevice object on pin GPIO2, pull_up=False, is_active=False>'
assert repr(device) == '<gpiozero.InputDevice object on pin GPIO4, pull_up=False, is_active=False>'
def test_input_pulled_up():
pin = Device._pin_factory.pin(2, pin_class=MockPulledUpPin)
pin = Device._pin_factory.pin(2)
with pytest.raises(PinFixedPull):
InputDevice(pin, pull_up=False)
def test_input_event_activated():
event = Event()
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with DigitalInputDevice(pin) as device:
device.when_activated = lambda: event.set()
assert not event.is_set()
@@ -67,7 +67,7 @@ def test_input_event_activated():
def test_input_event_deactivated():
event = Event()
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with DigitalInputDevice(pin) as device:
device.when_deactivated = lambda: event.set()
assert not event.is_set()
@@ -78,7 +78,7 @@ def test_input_event_deactivated():
def test_input_partial_callback():
event = Event()
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
def foo(a, b):
event.set()
return a + b
@@ -91,22 +91,22 @@ def test_input_partial_callback():
assert event.is_set()
def test_input_wait_active():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with DigitalInputDevice(pin) as device:
pin.drive_high()
assert device.wait_for_active(1)
assert not device.wait_for_inactive(0)
def test_input_wait_inactive():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with DigitalInputDevice(pin) as device:
assert device.wait_for_inactive(1)
assert not device.wait_for_active(0)
def test_input_smoothed_attrib():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with SmoothedInputDevice(pin, threshold=0.5, queue_len=5, partial=False) as device:
assert repr(device) == '<gpiozero.SmoothedInputDevice object on pin GPIO2, pull_up=False>'
assert repr(device) == '<gpiozero.SmoothedInputDevice object on pin GPIO4, pull_up=False>'
assert device.threshold == 0.5
assert device.queue_len == 5
assert not device.partial
@@ -116,7 +116,7 @@ def test_input_smoothed_attrib():
device.threshold = 1
def test_input_smoothed_values():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with SmoothedInputDevice(pin) as device:
device._queue.start()
assert not device.is_active
@@ -138,7 +138,7 @@ def test_input_button():
assert button.wait_for_release(1)
def test_input_line_sensor():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with LineSensor(pin) as sensor:
pin.drive_low() # logic is inverted for line sensor
assert sensor.wait_for_line(1)
@@ -148,7 +148,7 @@ def test_input_line_sensor():
assert not sensor.line_detected
def test_input_motion_sensor():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with MotionSensor(pin) as sensor:
pin.drive_high()
assert sensor.wait_for_motion(1)
@@ -160,7 +160,7 @@ def test_input_motion_sensor():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_input_light_sensor():
pin = Device._pin_factory.pin(2, pin_class=MockChargingPin)
pin = Device._pin_factory.pin(4, pin_class=MockChargingPin)
with LightSensor(pin) as sensor:
pin.charge_time = 0.1
assert sensor.wait_for_dark(1)
@@ -170,8 +170,8 @@ def test_input_light_sensor():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_input_distance_sensor():
echo_pin = Device._pin_factory.pin(2)
trig_pin = Device._pin_factory.pin(3, pin_class=MockTriggerPin)
echo_pin = Device._pin_factory.pin(4)
trig_pin = Device._pin_factory.pin(5, pin_class=MockTriggerPin)
trig_pin.echo_pin = echo_pin
trig_pin.echo_time = 0.02
with pytest.raises(ValueError):

View File

@@ -28,7 +28,7 @@ def test_mock_pin_init():
assert Device._pin_factory.pin(2).number == 2
def test_mock_pin_defaults():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
assert pin.bounce == None
assert pin.edges == 'both'
assert pin.frequency == None
@@ -36,6 +36,9 @@ def test_mock_pin_defaults():
assert pin.pull == 'floating'
assert pin.state == 0
assert pin.when_changed == None
pin.close()
pin = Device._pin_factory.pin(2)
assert pin.pull == 'up'
def test_mock_pin_open_close():
pin = Device._pin_factory.pin(2)
@@ -54,7 +57,7 @@ def test_mock_pin_init_twice_different_pin():
assert pin2.number == pin1.number+1
def test_mock_pwm_pin_defaults():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
assert pin.bounce == None
assert pin.edges == 'both'
assert pin.frequency == None
@@ -62,6 +65,9 @@ def test_mock_pwm_pin_defaults():
assert pin.pull == 'floating'
assert pin.state == 0
assert pin.when_changed == None
pin.close()
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
assert pin.pull == 'up'
def test_mock_pwm_pin_open_close():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
@@ -104,20 +110,25 @@ def test_mock_pin_frequency_supported():
assert not pin.state
def test_mock_pin_pull():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
pin.function = 'input'
assert pin.pull == 'floating'
pin.pull = 'up'
assert pin.state
pin.pull = 'down'
assert not pin.state
pin.close()
pin = Device._pin_factory.pin(2)
pin.function = 'input'
assert pin.pull == 'up'
with pytest.raises(PinFixedPull):
pin.pull = 'floating'
def test_mock_pin_state():
pin = Device._pin_factory.pin(2)
with pytest.raises(PinSetInput):
pin.state = 1
pin.function = 'output'
assert pin.state == 0
pin.state = 1
assert pin.state == 1
pin.state = 0
@@ -130,7 +141,6 @@ def test_mock_pwm_pin_state():
with pytest.raises(PinSetInput):
pin.state = 1
pin.function = 'output'
assert pin.state == 0
pin.state = 1
assert pin.state == 1
pin.state = 0

View File

@@ -95,7 +95,7 @@ def test_output_digital_toggle():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_output_blink_background():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with DigitalOutputDevice(pin) as device:
start = time()
device.blink(0.1, 0.1, n=2)
@@ -113,7 +113,7 @@ def test_output_blink_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_output_blink_foreground():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with DigitalOutputDevice(pin) as device:
start = time()
device.blink(0.1, 0.1, n=2, background=False)
@@ -127,7 +127,7 @@ def test_output_blink_foreground():
])
def test_output_blink_interrupt_on():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with DigitalOutputDevice(pin) as device:
device.blink(1, 0.1)
sleep(0.2)
@@ -135,7 +135,7 @@ def test_output_blink_interrupt_on():
pin.assert_states([False, True, False])
def test_output_blink_interrupt_off():
pin = Device._pin_factory.pin(2)
pin = Device._pin_factory.pin(4)
with DigitalOutputDevice(pin) as device:
device.blink(0.1, 1)
sleep(0.2)
@@ -151,7 +151,7 @@ def test_output_pwm_not_supported():
PWMOutputDevice(Device._pin_factory.pin(2))
def test_output_pwm_states():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device:
device.value = 0.1
device.value = 0.2
@@ -172,14 +172,14 @@ def test_output_pwm_read():
assert device.frequency is None
def test_output_pwm_write():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device:
device.on()
device.off()
pin.assert_states([False, True, False])
def test_output_pwm_toggle():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device:
device.toggle()
device.value = 0.5
@@ -218,7 +218,7 @@ def test_output_pwm_write_silly():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_output_pwm_blink_background():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device:
start = time()
device.blink(0.1, 0.1, n=2)
@@ -236,7 +236,7 @@ def test_output_pwm_blink_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_output_pwm_blink_foreground():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device:
start = time()
device.blink(0.1, 0.1, n=2, background=False)
@@ -252,7 +252,7 @@ def test_output_pwm_blink_foreground():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_output_pwm_fade_background():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device:
start = time()
device.blink(0, 0, 0.2, 0.2, n=2)
@@ -286,7 +286,7 @@ def test_output_pwm_fade_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_output_pwm_fade_foreground():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device:
start = time()
device.blink(0, 0, 0.2, 0.2, n=2, background=False)
@@ -318,7 +318,7 @@ def test_output_pwm_fade_foreground():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_output_pwm_pulse_background():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device:
start = time()
device.pulse(0.2, 0.2, n=2)
@@ -352,7 +352,7 @@ def test_output_pwm_pulse_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_output_pwm_pulse_foreground():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device:
start = time()
device.pulse(0.2, 0.2, n=2, background=False)
@@ -382,7 +382,7 @@ def test_output_pwm_pulse_foreground():
])
def test_output_pwm_blink_interrupt():
pin = Device._pin_factory.pin(2, pin_class=MockPWMPin)
pin = Device._pin_factory.pin(4, pin_class=MockPWMPin)
with PWMOutputDevice(pin) as device:
device.blink(1, 0.1)
sleep(0.2)
@@ -515,7 +515,7 @@ def test_rgbled_blink_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_rgbled_blink_background():
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device:
start = time()
device.blink(0.1, 0.1, n=2)
@@ -536,7 +536,7 @@ def test_rgbled_blink_background():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_rgbled_blink_background_nonpwm():
r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3))
r, g, b = (Device._pin_factory.pin(i) for i in (4, 5, 6))
with RGBLED(r, g, b, pwm=False) as device:
start = time()
device.blink(0.1, 0.1, n=2)
@@ -557,7 +557,7 @@ def test_rgbled_blink_background_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_rgbled_blink_foreground():
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device:
start = time()
device.blink(0.1, 0.1, n=2, background=False)
@@ -576,7 +576,7 @@ def test_rgbled_blink_foreground():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_rgbled_blink_foreground_nonpwm():
r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3))
r, g, b = (Device._pin_factory.pin(i) for i in (4, 5, 6))
with RGBLED(r, g, b, pwm=False) as device:
start = time()
device.blink(0.1, 0.1, n=2, background=False)
@@ -595,7 +595,7 @@ def test_rgbled_blink_foreground_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_rgbled_fade_background():
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device:
start = time()
device.blink(0, 0, 0.2, 0.2, n=2)
@@ -638,7 +638,7 @@ def test_rgbled_fade_background_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_rgbled_fade_foreground():
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device:
start = time()
device.blink(0, 0, 0.2, 0.2, n=2, background=False)
@@ -679,7 +679,7 @@ def test_rgbled_fade_foreground_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_rgbled_pulse_background():
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device:
start = time()
device.pulse(0.2, 0.2, n=2)
@@ -722,7 +722,7 @@ def test_rgbled_pulse_background_nonpwm():
@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'),
reason='timing is too random on pypy')
def test_rgbled_pulse_foreground():
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device:
start = time()
device.pulse(0.2, 0.2, n=2, background=False)
@@ -761,7 +761,7 @@ def test_rgbled_pulse_foreground_nonpwm():
device.pulse(0.2, 0.2, n=2, background=False)
def test_rgbled_blink_interrupt():
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (1, 2, 3))
r, g, b = (Device._pin_factory.pin(i, pin_class=MockPWMPin) for i in (4, 5, 6))
with RGBLED(r, g, b) as device:
device.blink(1, 0.1)
sleep(0.2)
@@ -771,7 +771,7 @@ def test_rgbled_blink_interrupt():
b.assert_states([0, 1, 0])
def test_rgbled_blink_interrupt_nonpwm():
r, g, b = (Device._pin_factory.pin(i) for i in (1, 2, 3))
r, g, b = (Device._pin_factory.pin(i) for i in (4, 5, 6))
with RGBLED(r, g, b, pwm=False) as device:
device.blink(1, 0.1)
sleep(0.2)

View File

@@ -11,20 +11,33 @@ except NameError:
pass
import io
import os
import subprocess
from time import sleep
import pytest
import pkg_resources
from gpiozero import PinFixedPull, PinInvalidPull, PinInvalidFunction
from gpiozero import (
PinFixedPull,
PinInvalidPull,
PinInvalidFunction,
PinPWMUnsupported,
Device,
)
from gpiozero.pins.mock import MockConnectedPin, MockFactory
try:
from math import isclose
except ImportError:
from gpiozero.compat import isclose
# This module assumes you've wired the following GPIO pins together
TEST_PIN = 22
INPUT_PIN = 27
# This module assumes you've wired the following GPIO pins together. The pins
# can be re-configured via the listed environment variables (useful for when
# your testing rig requires different pins because the defaults interfere with
# attached hardware).
TEST_PIN = int(os.getenv('GPIOZERO_TEST_PIN', '22'))
INPUT_PIN = int(os.getenv('GPIOZERO_INPUT_PIN', '27'))
# Skip the entire module if we're not on a Pi
@@ -40,51 +53,48 @@ pytestmark = pytest.mark.skipif(not is_a_pi(), reason='tests cannot run on non-P
del is_a_pi
# Try and import as many pin libraries as possible
PIN_CLASSES = []
try:
from gpiozero.pins.rpigpio import RPiGPIOPin
PIN_CLASSES.append(RPiGPIOPin)
except ImportError:
RPiGPIOPin = None
try:
from gpiozero.pins.rpio import RPIOPin
PIN_CLASSES.append(RPIOPin)
except ImportError:
RPIOPin = None
try:
from gpiozero.pins.pigpiod import PiGPIOPin
PIN_CLASSES.append(PiGPIOPin)
except ImportError:
PiGPIOPin = None
try:
from gpiozero.pins.native import NativePin
PIN_CLASSES.append(NativePin)
except ImportError:
NativePin = None
@pytest.fixture(
scope='module',
params=('rpigpio',))
#params=pkg_resources.get_distribution('gpiozero').get_entry_map('gpiozero_pin_factories').keys())
def pin_factory(request):
# Constructs each pin factory in turn with some extra logic to ensure
# the pigpiod daemon gets started and stopped around the pigpio factory
if request.param == 'pigpio':
try:
subprocess.check_call(['sudo', 'systemctl', 'start', 'pigpiod'])
except subprocess.CalledProcessError:
pytest.skip("skipped factory pigpio: failed to start pigpiod")
try:
factory = pkg_resources.load_entry_point('gpiozero', 'gpiozero_pin_factories', request.param)()
except Exception as e:
if request.param == 'pigpio':
subprocess.check_call(['sudo', 'systemctl', 'stop', 'pigpiod'])
pytest.skip("skipped factory %s: %s" % (request.param, str(e)))
else:
Device._set_pin_factory(factory)
def fin():
Device._set_pin_factory(MockFactory())
if request.param == 'pigpio':
subprocess.check_call(['sudo', 'systemctl', 'stop', 'pigpiod'])
request.addfinalizer(fin)
return factory
@pytest.fixture(scope='module', params=PIN_CLASSES)
def pin_class(request):
# pigpiod needs to be running for PiGPIOPin
if request.param.__name__ == 'PiGPIOPin':
subprocess.check_call(['sudo', 'pigpiod'])
# Unfortunately, pigpiod provides no option for running in the
# foreground, so we have to use the sledgehammer of killall to get shot
# of it
def kill_pigpiod():
subprocess.check_call(['sudo', 'killall', 'pigpiod'])
request.addfinalizer(kill_pigpiod)
return request.param
@pytest.fixture(scope='function')
def pins(request, pin_class):
def pins(request, pin_factory):
# Why return both pins in a single fixture? If we defined one fixture for
# each pin then pytest will (correctly) test RPiGPIOPin(22) against
# NativePin(27) and so on. This isn't supported, so we don't test it
test_pin = pin_class(TEST_PIN)
input_pin = pin_class(INPUT_PIN)
if pin_factory.__class__.__name__ == 'MockFactory':
test_pin = pin_factory.pin(TEST_PIN, pin_class=MockConnectedPin)
else:
test_pin = pin_factory.pin(TEST_PIN)
input_pin = pin_factory.pin(INPUT_PIN)
input_pin.function = 'input'
input_pin.pull = 'down'
if pin_factory.__class__.__name__ == 'MockFactory':
test_pin.input_pin = input_pin
def fin():
test_pin.close()
input_pin.close()
@@ -134,17 +144,18 @@ def test_pull_bad(pins):
with pytest.raises(PinInvalidPull):
test_pin.input_with_pull('foo')
def test_pull_down_warning(pin_class):
# XXX This assumes we're on a vaguely modern Pi and not a compute module
# Might want to refine this with the pi-info database
pin = pin_class(2)
try:
with pytest.raises(PinFixedPull):
pin.pull = 'down'
with pytest.raises(PinFixedPull):
pin.input_with_pull('down')
finally:
pin.close()
def test_pull_down_warning(pin_factory):
if pin_factory.pi_info.pulled_up('GPIO2'):
pin = pin_factory.pin(2)
try:
with pytest.raises(PinFixedPull):
pin.pull = 'down'
with pytest.raises(PinFixedPull):
pin.input_with_pull('down')
finally:
pin.close()
else:
pytest.skip("GPIO2 isn't pulled up on this pi")
def test_input_with_pull(pins):
test_pin, input_pin = pins
@@ -153,25 +164,42 @@ def test_input_with_pull(pins):
test_pin.input_with_pull('down')
assert input_pin.state == 0
@pytest.mark.skipif(True, reason='causes segfaults')
def test_bad_duty_cycle(pins):
test_pin, input_pin = pins
if test_pin.__class__.__name__ == 'NativePin':
pytest.skip("native pin doesn't support PWM")
test_pin.function = 'output'
test_pin.frequency = 100
with pytest.raises(ValueError):
test_pin.state = 1.1
try:
# NOTE: There's some race in RPi.GPIO that causes a segfault if we
# don't pause before starting PWM; only seems to happen when stopping
# and restarting PWM very rapidly (i.e. between test cases).
if Device._pin_factory.__class__.__name__ == 'RPiGPIOFactory':
sleep(0.1)
test_pin.frequency = 100
except PinPWMUnsupported:
pytest.skip("%r doesn't support PWM" % test_pin.factory)
else:
try:
with pytest.raises(ValueError):
test_pin.state = 1.1
finally:
test_pin.frequency = None
def test_duty_cycles(pins):
test_pin, input_pin = pins
if test_pin.__class__.__name__ == 'NativePin':
pytest.skip("native pin doesn't support PWM")
test_pin.function = 'output'
test_pin.frequency = 100
for duty_cycle in (0.0, 0.1, 0.5, 1.0):
test_pin.state = duty_cycle
assert test_pin.state == duty_cycle
total = sum(input_pin.state for i in range(20000))
assert isclose(total / 20000, duty_cycle, rel_tol=0.1, abs_tol=0.1)
try:
# NOTE: see above
if Device._pin_factory.__class__.__name__ == 'RPiGPIOFactory':
sleep(0.1)
test_pin.frequency = 100
except PinPWMUnsupported:
pytest.skip("%r doesn't support PWM" % test_pin.factory)
else:
try:
for duty_cycle in (0.0, 0.1, 0.5, 1.0):
test_pin.state = duty_cycle
assert test_pin.state == duty_cycle
total = sum(input_pin.state for i in range(20000))
assert isclose(total / 20000, duty_cycle, rel_tol=0.1, abs_tol=0.1)
finally:
test_pin.frequency = None