diff --git a/gpiozero/pins/mock.py b/gpiozero/pins/mock.py index 4b97647..046fdce 100644 --- a/gpiozero/pins/mock.py +++ b/gpiozero/pins/mock.py @@ -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): diff --git a/gpiozero/pins/pigpiod.py b/gpiozero/pins/pigpiod.py index dd3fcd0..beba9d7 100644 --- a/gpiozero/pins/pigpiod.py +++ b/gpiozero/pins/pigpiod.py @@ -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) diff --git a/setup.py b/setup.py index 9801fb8..4647fdc 100644 --- a/setup.py +++ b/setup.py @@ -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', ], diff --git a/tests/test_boards.py b/tests/test_boards.py index 4cc69e1..36e3277 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -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 = [ diff --git a/tests/test_devices.py b/tests/test_devices.py index 858b388..cd28f4a 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -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) == '' % 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 diff --git a/tests/test_inputs.py b/tests/test_inputs.py index dbfd7dc..0b61ddd 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -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) == '' 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) == '' + assert repr(device) == '' pin.drive_low() assert not device.is_active - assert repr(device) == '' + assert repr(device) == '' 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) == '' + assert repr(device) == '' 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): diff --git a/tests/test_mock_pin.py b/tests/test_mock_pin.py index 4d5414e..880f859 100644 --- a/tests/test_mock_pin.py +++ b/tests/test_mock_pin.py @@ -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 diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 4fbcbf4..6908270 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -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) diff --git a/tests/test_real_pins.py b/tests/test_real_pins.py index 94d25ef..7fbe59e 100644 --- a/tests/test_real_pins.py +++ b/tests/test_real_pins.py @@ -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