From c0d70f35f6cf2092fa2808aa795bb9eab1a12b33 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 20 Feb 2016 19:18:41 +0000 Subject: [PATCH] MockPin improvements Change MockPin (and MockPWMPin) to make them behave more like 'real' pins - fixes #206 Add new MockPin tests, and rework some of the existing ones Incorporate #216 --- gpiozero/pins/mock.py | 69 +++++++++++++++------------ tests/test_boards.py | 3 ++ tests/test_devices.py | 3 ++ tests/test_inputs.py | 3 ++ tests/test_mock_pin.py | 103 +++++++++++++++++++++++++++++++++++++++-- tests/test_outputs.py | 3 ++ 6 files changed, 150 insertions(+), 34 deletions(-) diff --git a/gpiozero/pins/mock.py b/gpiozero/pins/mock.py index ee5a6ad..c260c6f 100644 --- a/gpiozero/pins/mock.py +++ b/gpiozero/pins/mock.py @@ -25,18 +25,32 @@ class MockPin(Pin): A mock pin used primarily for testing. This class does *not* support PWM. """ - def __init__(self, number): + _PINS = {} + + @classmethod + def clear_pins(cls): + cls._PINS.clear() + + def __new__(cls, 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)] + try: + old_pin = cls._PINS[number] + except KeyError: + self = super(Pin, cls).__new__(cls) + cls._PINS[number] = self + self._number = number + self._function = 'input' + self._state = False + self._pull = 'floating' + self._bounce = None + self._edges = 'both' + self._when_changed = None + self.clear_states() + return self + if old_pin.__class__ != cls: + raise ValueError('pin %d is already in use as a %s' % (number, old_pin.__class__)) + return old_pin def __repr__(self): return 'MOCK%d' % self._number @@ -67,12 +81,16 @@ class MockPin(Pin): raise PinSetInput('cannot set state of pin %r' % self) assert self._function == 'output' assert 0 <= value <= 1 - value = bool(value) + self._change_state(bool(value)) + + def _change_state(self, value): if self._state != value: t = time() self._state = value self.states.append(PinState(t - self._last_change, value)) self._last_change = t + return True + return False def _get_frequency(self): return None @@ -115,27 +133,19 @@ class MockPin(Pin): 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._change_state(True): 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._change_state(False): 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)] + 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) @@ -158,20 +168,19 @@ class MockPWMPin(MockPin): """ def __init__(self, number): - super(MockPWMPin, self).__init__(number) + super(MockPWMPin, self).__init__() self._frequency = None + def close(self): + self.frequency = None + super(MockPWMPin, self).close() + def _set_state(self, value): if self._function == 'input': raise PinSetInput('cannot set state of pin %r' % self) assert self._function == 'output' assert 0 <= value <= 1 - value = float(value) - if self._state != value: - t = time() - self._state = value - self.states.append(PinState(t - self._last_change, value)) - self._last_change = t + self._change_state(float(value)) def _get_frequency(self): return self._frequency @@ -181,5 +190,5 @@ class MockPWMPin(MockPin): assert self._function == 'output' self._frequency = value if value is None: - self.state = False + self._change_state(0.0) diff --git a/tests/test_boards.py b/tests/test_boards.py index fa1c6d1..6338f54 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -13,5 +13,8 @@ from gpiozero.pins.mock import MockPin from gpiozero import * +def teardown_function(function): + MockPin.clear_pins() + # TODO boards tests! diff --git a/tests/test_devices.py b/tests/test_devices.py index 0312c7e..43b3b92 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -13,6 +13,9 @@ from gpiozero.pins.mock import MockPin from gpiozero import * +def teardown_function(function): + MockPin.clear_pins() + # TODO add more devices tests! def test_device_no_pin(): diff --git a/tests/test_inputs.py b/tests/test_inputs.py index 88c664b..74aebe6 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -13,4 +13,7 @@ from gpiozero.pins.mock import MockPin from gpiozero import * +def teardown_function(function): + MockPin.clear_pins() + # TODO input_devices tests! diff --git a/tests/test_mock_pin.py b/tests/test_mock_pin.py index 418dc40..857e340 100644 --- a/tests/test_mock_pin.py +++ b/tests/test_mock_pin.py @@ -15,6 +15,9 @@ from gpiozero.pins.mock import MockPin, MockPWMPin from gpiozero import * +def teardown_function(function): + MockPin.clear_pins() + # 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... @@ -23,14 +26,80 @@ def test_mock_pin_init(): MockPin(60) assert MockPin(2).number == 2 +def test_mock_pin_defaults(): + pin = MockPin(2) + assert pin.bounce == None + assert pin.edges == 'both' + assert pin.frequency == None + assert pin.function == 'input' + assert pin.pull == 'floating' + assert pin.state == 0 + assert pin.when_changed == None + +def test_mock_pin_open_close(): + pin = MockPin(2) + pin.close() + +def test_mock_pin_init_twice_same_pin(): + pin1 = MockPin(2) + pin2 = MockPin(pin1.number) + assert pin1 is pin2 + +def test_mock_pin_init_twice_different_pin(): + pin1 = MockPin(2) + pin2 = MockPin(pin1.number+1) + assert pin1 != pin2 + assert pin1.number == 2 + assert pin2.number == pin1.number+1 + +def test_mock_pwm_pin_init(): + with pytest.raises(ValueError): + MockPWMPin(60) + assert MockPWMPin(2).number == 2 + +def test_mock_pwm_pin_defaults(): + pin = MockPWMPin(2) + assert pin.bounce == None + assert pin.edges == 'both' + assert pin.frequency == None + assert pin.function == 'input' + assert pin.pull == 'floating' + assert pin.state == 0 + assert pin.when_changed == None + +def test_mock_pwm_pin_open_close(): + pin = MockPWMPin(2) + pin.close() + +def test_mock_pwm_pin_init_twice_same_pin(): + pin1 = MockPWMPin(2) + pin2 = MockPWMPin(pin1.number) + assert pin1 is pin2 + +def test_mock_pwm_pin_init_twice_different_pin(): + pin1 = MockPWMPin(2) + pin2 = MockPWMPin(pin1.number+1) + assert pin1 != pin2 + assert pin1.number == 2 + assert pin2.number == pin1.number+1 + +def test_mock_pin_init_twice_different_modes(): + pin1 = MockPin(2) + pin2 = MockPWMPin(pin1.number+1) + assert pin1 != pin2 + with pytest.raises(ValueError): + pin3 = MockPWMPin(pin1.number) + with pytest.raises(ValueError): + pin4 = MockPin(pin2.number) + def test_mock_pin_frequency_unsupported(): - pin = MockPin(3) + pin = MockPin(2) pin.frequency = None with pytest.raises(PinPWMUnsupported): pin.frequency = 100 def test_mock_pin_frequency_supported(): - pin = MockPWMPin(3) + pin = MockPWMPin(2) pin.function = 'output' assert pin.frequency is None pin.frequency = 100 @@ -39,7 +108,7 @@ def test_mock_pin_frequency_supported(): assert not pin.state def test_mock_pin_pull(): - pin = MockPin(4) + pin = MockPin(2) pin.function = 'input' assert pin.pull == 'floating' pin.pull = 'up' @@ -47,8 +116,34 @@ def test_mock_pin_pull(): pin.pull = 'down' assert not pin.state +def test_mock_pin_state(): + pin = MockPin(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 + assert pin.state == 0 + pin.state = 0.5 + assert pin.state == 1 + +def test_mock_pwm_pin_state(): + pin = MockPWMPin(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 + assert pin.state == 0 + pin.state = 0.5 + assert pin.state == 0.5 + def test_mock_pin_edges(): - pin = MockPin(5) + pin = MockPin(2) assert pin.when_changed is None fired = Event() pin.function = 'input' diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 30eaa4e..0d120bf 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -20,6 +20,9 @@ from gpiozero.pins.mock import MockPin, MockPWMPin from gpiozero import * +def teardown_function(function): + MockPin.clear_pins() + def test_output_initial_values(): pin = MockPin(2) device = OutputDevice(pin, initial_value=False)