Exemplo n.º 1
0
    def setup(self):
        self.mock_chip = Mock(spec_set=PiFaceDigital)

        self.opin0 = Mock()
        self.opin1 = Mock()
        self.opin2 = Mock()
        self.opin3 = Mock()
        type(self.mock_chip).output_pins = [
            self.opin0, self.opin1, self.opin2, self.opin3
        ]

        self.ipin0 = Mock()
        type(self.ipin0).value = 10
        self.ipin1 = Mock()
        type(self.ipin1).value = 11
        self.ipin2 = Mock()
        type(self.ipin2).value = 12
        self.ipin3 = Mock()
        type(self.ipin3).value = 13
        type(self.mock_chip).input_pins = [
            self.ipin0, self.ipin1, self.ipin2, self.ipin3
        ]

        with patch('%s.PiFaceDigital' % pbm) as mock_pfd:
            with patch('%s.Config' % pbm) as mock_config:
                type(mock_config.return_value).PINS = [0, 0, 0, 0]
                mock_pfd.return_value = self.mock_chip
                self.cls = Listener()
        self.cls.chip = self.mock_chip
        self.config = mock_config
Exemplo n.º 2
0
    def setup(self):
        self.mock_chip = Mock(spec_set=PiFaceDigital)

        self.opin0 = Mock()
        self.opin1 = Mock()
        self.opin2 = Mock()
        self.opin3 = Mock()
        type(self.mock_chip).output_pins = [
            self.opin0, self.opin1, self.opin2, self.opin3
        ]

        self.ipin0 = Mock()
        type(self.ipin0).value = 10
        self.ipin1 = Mock()
        type(self.ipin1).value = 11
        self.ipin2 = Mock()
        type(self.ipin2).value = 12
        self.ipin3 = Mock()
        type(self.ipin3).value = 13
        type(self.mock_chip).input_pins = [
            self.ipin0, self.ipin1, self.ipin2, self.ipin3
        ]

        with patch('%s.PiFaceDigital' % pbm) as mock_pfd:
            with patch('%s.Config' % pbm) as mock_config:
                type(mock_config.return_value).PINS = [0, 0, 0, 0]
                mock_pfd.return_value = self.mock_chip
                self.cls = Listener()
        self.cls.chip = self.mock_chip
        self.config = mock_config
Exemplo n.º 3
0
 def test_init(self):
     with patch('%s.Config' % pbm) as mock_config:
         type(mock_config.return_value).PINS = [0, 0, 0, 0]
         cls = Listener()
     assert cls.write_files is True
     assert cls.current_values == []
     assert mock_config.mock_calls == [call()]
     assert cls.config == mock_config.return_value
Exemplo n.º 4
0
class TestListener(object):
    def setup(self):
        self.mock_chip = Mock(spec_set=PiFaceDigital)

        self.opin0 = Mock()
        self.opin1 = Mock()
        self.opin2 = Mock()
        self.opin3 = Mock()
        type(self.mock_chip).output_pins = [
            self.opin0, self.opin1, self.opin2, self.opin3
        ]

        self.ipin0 = Mock()
        type(self.ipin0).value = 10
        self.ipin1 = Mock()
        type(self.ipin1).value = 11
        self.ipin2 = Mock()
        type(self.ipin2).value = 12
        self.ipin3 = Mock()
        type(self.ipin3).value = 13
        type(self.mock_chip).input_pins = [
            self.ipin0, self.ipin1, self.ipin2, self.ipin3
        ]

        with patch('%s.PiFaceDigital' % pbm) as mock_pfd:
            with patch('%s.Config' % pbm) as mock_config:
                type(mock_config.return_value).PINS = [0, 0, 0, 0]
                mock_pfd.return_value = self.mock_chip
                self.cls = Listener()
        self.cls.chip = self.mock_chip
        self.config = mock_config

    def test_parse_args(self):
        argv = ['-V']
        res = self.cls.parse_args(argv)
        assert isinstance(res, argparse.Namespace)
        assert res.version is True

    def test_parse_args_parser(self):
        argv = ['-V']
        desc = 'Listen for PiFace input changes and queue them to disk'
        with patch('%s.argparse.ArgumentParser' % pbm,
                   spec_set=argparse.ArgumentParser) as mock_parser:
            self.cls.parse_args(argv)
        assert mock_parser.mock_calls == [
            call(description=desc),
            call().add_argument('-w',
                                '--no-write',
                                dest='write_files',
                                action='store_false',
                                default=True,
                                help='do not write queue files; just log '
                                'changes'),
            call().add_argument('-v',
                                '--verbose',
                                dest='verbose',
                                action='count',
                                default=0,
                                help='verbose output. specify twice '
                                'for debug-level output.'),
            call().add_argument('-V',
                                '--version',
                                dest='version',
                                action='store_true',
                                default=False,
                                help='print version number and exit.'),
            call().parse_args(argv),
        ]

    def test_entry_point_version(self, capsys):
        argv = ['piface-listener', '-V']
        with patch.object(sys, 'argv', argv):
            with patch('%s.run' % pb) as mock_run:
                with pytest.raises(SystemExit) as excinfo:
                    self.cls.console_entry_point()
        out, err = capsys.readouterr()
        assert out == "pyface-webhooks %s " \
            "<https://github.com/jantman/piface_webhooks>\n" % VERSION
        assert err == ''
        assert excinfo.value.code == 0
        assert mock_run.mock_calls == []

    def test_entry_verbose(self, capsys):
        argv = ['piface-listener', '-v']
        with patch.object(sys, 'argv', argv):
            with patch('%s.logger.setLevel' % pbm) as mock_set_level:
                with patch('%s.run' % pb) as mock_run:
                    self.cls.console_entry_point()
        out, err = capsys.readouterr()
        assert out == ''
        assert err == ''
        assert mock_set_level.mock_calls == [call(logging.INFO)]
        assert mock_run.mock_calls == [call()]

    def test_entry_debug(self, capsys):
        argv = ['piface-listener', '-vv']
        with patch.object(sys, 'argv', argv):
            with patch('%s.logger.setLevel' % pbm) as mock_set_level:
                with patch('%s.run' % pb) as mock_run:
                    self.cls.console_entry_point()
        out, err = capsys.readouterr()
        assert out == ''
        assert err == ''
        assert mock_set_level.mock_calls == [call(logging.DEBUG)]
        assert mock_run.mock_calls == [call()]

    def test_entry_none(self, capsys):
        argv = ['piface-listener']
        with patch.object(sys, 'argv', argv):
            with patch('%s.logger.setLevel' % pbm) as mock_set_level:
                with patch('%s.run' % pb) as mock_run:
                    self.cls.console_entry_point()
        out, err = capsys.readouterr()
        assert out == ''
        assert err == ''
        assert mock_set_level.mock_calls == []
        assert mock_run.mock_calls == [call()]
        assert self.cls.write_files is True

    def test_entry_no_write(self, capsys):
        argv = ['piface-listener', '--no-write']
        with patch.object(sys, 'argv', argv):
            with patch('%s.logger.setLevel' % pbm) as mock_set_level:
                with patch('%s.run' % pb) as mock_run:
                    self.cls.console_entry_point()
        out, err = capsys.readouterr()
        assert out == ''
        assert err == ''
        assert mock_set_level.mock_calls == []
        assert mock_run.mock_calls == [call()]
        assert self.cls.write_files is False

    def test_run(self):
        with patch('%s.logger' % pbm) as mock_logger:
            with patch('%s.InputEventListener' % pbm) as mock_listener:
                with patch('%s.PiFaceDigital' % pbm) as mock_io:
                    with patch('%s.register_callbacks' % pb) as mock_reg:
                        self.cls.run()
        assert mock_logger.mock_calls == [
            call.debug("initializing chip"),
            call.debug("chip initialized"),
            call.debug('creating InputEventListener'),
            call.debug('activating listener')
        ]
        assert mock_listener.mock_calls == [
            call(chip=self.cls.chip),
            call().activate()
        ]
        assert mock_reg.mock_calls == [call()]
        assert mock_io.mock_calls == [call()]
        assert self.cls.chip == mock_io.return_value
        assert self.cls.listener == mock_listener.return_value

    def test_register_callbacks(self):
        mock_listener = Mock(spec_set=InputEventListener)
        self.cls.listener = mock_listener
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.register_callbacks()
        assert mock_logger.mock_calls == [
            call.debug("registering callbacks"),
            call.debug('registering callback for %s ON', 0),
            call.debug('registering callback for %s OFF', 0),
            call.debug('registering callback for %s ON', 1),
            call.debug('registering callback for %s OFF', 1),
            call.debug('registering callback for %s ON', 2),
            call.debug('registering callback for %s OFF', 2),
            call.debug('registering callback for %s ON', 3),
            call.debug('registering callback for %s OFF', 3),
            call.debug('done registering callbacks'),
            call.info('Initial pin states: %s', [10, 11, 12, 13])
        ]
        assert mock_listener.mock_calls == [
            call.register(0, IODIR_ON, self.cls.handle_input_on),
            call.register(0, IODIR_OFF, self.cls.handle_input_off),
            call.register(1, IODIR_ON, self.cls.handle_input_on),
            call.register(1, IODIR_OFF, self.cls.handle_input_off),
            call.register(2, IODIR_ON, self.cls.handle_input_on),
            call.register(2, IODIR_OFF, self.cls.handle_input_off),
            call.register(3, IODIR_ON, self.cls.handle_input_on),
            call.register(3, IODIR_OFF, self.cls.handle_input_off),
        ]
        assert self.cls.current_values == [10, 11, 12, 13]

    def test_handle_input_on(self):
        mock_evt = Mock(spec_set=InterruptEvent)
        type(mock_evt).pin_num = 3
        type(mock_evt).timestamp = 1234.5678

        self.cls.current_values = [5, 5, 5, 5]

        with patch('%s.handle_change' % pb) as mock_handle:
            with patch('%s.logger' % pbm) as mock_logger:
                with patch('%s.no_state_change' % pb) as mock_no_change:
                    with patch('%s.set_output' % pb) as mock_set:
                        mock_no_change.return_value = False
                        self.cls.handle_input_on(mock_evt)
        assert mock_logger.mock_calls == [
            call.info("Received ON event for pin %s", 3),
        ]
        assert mock_handle.mock_calls == [call(3, 1, 1234.5678)]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []
        assert self.cls.current_values == [5, 5, 5, 1]
        assert mock_no_change.mock_calls == [call(3, 1)]
        assert mock_set.mock_calls == [call(3, 1)]

    def test_handle_input_on_no_change(self):
        mock_evt = Mock(spec_set=InterruptEvent)
        type(mock_evt).pin_num = 3
        type(mock_evt).timestamp = 1234.5678

        self.cls.current_values = [5, 5, 5, 1]

        with patch('%s.handle_change' % pb) as mock_handle:
            with patch('%s.logger' % pbm) as mock_logger:
                with patch('%s.no_state_change' % pb) as mock_no_change:
                    with patch('%s.set_output' % pb) as mock_set:
                        mock_no_change.return_value = True
                        self.cls.handle_input_on(mock_evt)
        assert mock_logger.mock_calls == [
            call.info("Ignoring duplicate event for pin %s state %s", 3, 1)
        ]
        assert mock_handle.mock_calls == []
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []
        assert self.cls.current_values == [5, 5, 5, 1]
        assert mock_no_change.mock_calls == [call(3, 1)]
        assert mock_set.mock_calls == []

    def test_handle_input_off(self):
        mock_evt = Mock(spec_set=InterruptEvent)
        type(mock_evt).pin_num = 1
        type(mock_evt).timestamp = 1234.5678

        self.cls.current_values = [5, 5, 5, 5]

        with patch('%s.handle_change' % pb) as mock_handle:
            with patch('%s.logger' % pbm) as mock_logger:
                with patch('%s.no_state_change' % pb) as mock_no_change:
                    with patch('%s.set_output' % pb) as mock_set:
                        mock_no_change.return_value = False
                        self.cls.handle_input_off(mock_evt)
        assert mock_logger.mock_calls == [
            call.info("Received OFF event for pin %s", 1),
        ]
        assert mock_handle.mock_calls == [call(1, 0, 1234.5678)]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []
        assert self.cls.current_values == [5, 0, 5, 5]
        assert mock_no_change.mock_calls == [call(1, 0)]
        assert mock_set.mock_calls == [call(1, 0)]

    def test_handle_input_off_no_change(self):
        mock_evt = Mock(spec_set=InterruptEvent)
        type(mock_evt).pin_num = 1
        type(mock_evt).timestamp = 1234.5678

        self.cls.current_values = [5, 0, 5, 5]

        with patch('%s.handle_change' % pb) as mock_handle:
            with patch('%s.logger' % pbm) as mock_logger:
                with patch('%s.no_state_change' % pb) as mock_no_change:
                    with patch('%s.set_output' % pb) as mock_set:
                        mock_no_change.return_value = True
                        self.cls.handle_input_off(mock_evt)
        assert mock_logger.mock_calls == [
            call.info("Ignoring duplicate event for pin %s state %s", 1, 0)
        ]
        assert mock_handle.mock_calls == []
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []
        assert self.cls.current_values == [5, 0, 5, 5]
        assert mock_no_change.mock_calls == [call(1, 0)]
        assert mock_set.mock_calls == []

    def test_no_state_change(self):
        self.cls.current_values = [1, 0, 1]
        assert self.cls.no_state_change(0, 1) is True
        assert self.cls.no_state_change(0, 0) is False

    def test_handle_change_on(self):
        fpath = '/foo/bar/pinevent_123.4567_pin2_state1'
        type(self.cls.config).QUEUE_PATH = '/foo/bar'
        with patch('%s.logger' % pbm) as mock_logger:
            with patch('%s.open' % pbm, mock_open(read_data='')) as mock_opn:
                with patch('%s.os.utime' % pbm) as mock_utime:
                    self.cls.handle_change(2, 1, 123.4567)
        assert mock_logger.mock_calls == [
            call.debug("Created event file: %s", fpath)
        ]
        assert mock_opn.mock_calls == [
            call(fpath, 'a'),
            call().__enter__(),
            call().__exit__(None, None, None)
        ]
        assert mock_utime.mock_calls == [call(fpath, None)]

    def test_handle_change_off_no_write(self):
        self.cls.write_files = False
        fpath = '/foo/bar/pinevent_123.4567_pin2_state0'
        type(self.cls.config).QUEUE_PATH = '/foo/bar'
        with patch('%s.logger' % pbm) as mock_logger:
            with patch('%s.open' % pbm, mock_open(read_data='')) as mock_opn:
                with patch('%s.os.utime' % pbm) as mock_utime:
                    self.cls.handle_change(2, 0, 123.4567)
        assert mock_logger.mock_calls == [
            call.warning("Would create event file: %s", fpath)
        ]
        assert mock_opn.mock_calls == []
        assert mock_utime.mock_calls == []

    def test_set_output_no_leds(self):
        type(self.cls.config).NO_LEDS = True
        type(self.cls.config).INVERT_LED = False
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.set_output(0, 1)
        assert mock_logger.mock_calls == [call.debug("Not lighting LEDs")]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []

    def test_set_output_on(self):
        type(self.cls.config).NO_LEDS = False
        type(self.cls.config).INVERT_LED = False
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.set_output(0, 1)
        assert mock_logger.mock_calls == [
            call.debug("Setting output %s on", 0)
        ]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == [call.turn_on()]
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []

    def test_set_output_off(self):
        type(self.cls.config).NO_LEDS = False
        type(self.cls.config).INVERT_LED = False
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.set_output(2, 0)
        assert mock_logger.mock_calls == [
            call.debug("Setting output %s off", 2)
        ]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == [call.turn_off()]
        assert self.opin3.mock_calls == []

    def test_set_output_on_invert(self):
        type(self.cls.config).NO_LEDS = False
        type(self.cls.config).INVERT_LED = True
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.set_output(0, 1)
        assert mock_logger.mock_calls == [
            call.debug("Setting output %s off", 0)
        ]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == [call.turn_off()]
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []

    def test_set_output_off_invert(self):
        type(self.cls.config).NO_LEDS = False
        type(self.cls.config).INVERT_LED = True
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.set_output(2, 0)
        assert mock_logger.mock_calls == [
            call.debug("Setting output %s on", 2)
        ]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == [call.turn_on()]
        assert self.opin3.mock_calls == []
Exemplo n.º 5
0
 def test_init_too_few_pins(self):
     with patch('%s.Config' % pbm) as mock_config:
         type(mock_config.return_value).PINS = [0]
         with pytest.raises(SystemExit):
             Listener()
Exemplo n.º 6
0
class TestListener(object):

    def setup(self):
        self.mock_chip = Mock(spec_set=PiFaceDigital)

        self.opin0 = Mock()
        self.opin1 = Mock()
        self.opin2 = Mock()
        self.opin3 = Mock()
        type(self.mock_chip).output_pins = [
            self.opin0, self.opin1, self.opin2, self.opin3
        ]

        self.ipin0 = Mock()
        type(self.ipin0).value = 10
        self.ipin1 = Mock()
        type(self.ipin1).value = 11
        self.ipin2 = Mock()
        type(self.ipin2).value = 12
        self.ipin3 = Mock()
        type(self.ipin3).value = 13
        type(self.mock_chip).input_pins = [
            self.ipin0, self.ipin1, self.ipin2, self.ipin3
        ]

        with patch('%s.PiFaceDigital' % pbm) as mock_pfd:
            with patch('%s.Config' % pbm) as mock_config:
                type(mock_config.return_value).PINS = [0, 0, 0, 0]
                mock_pfd.return_value = self.mock_chip
                self.cls = Listener()
        self.cls.chip = self.mock_chip
        self.config = mock_config

    def test_parse_args(self):
        argv = ['-V']
        res = self.cls.parse_args(argv)
        assert isinstance(res, argparse.Namespace)
        assert res.version is True

    def test_parse_args_parser(self):
        argv = ['-V']
        desc = 'Listen for PiFace input changes and queue them to disk'
        with patch('%s.argparse.ArgumentParser' % pbm,
                   spec_set=argparse.ArgumentParser) as mock_parser:
                self.cls.parse_args(argv)
        assert mock_parser.mock_calls == [
            call(description=desc),
            call().add_argument('-w', '--no-write', dest='write_files',
                                action='store_false', default=True,
                                help='do not write queue files; just log '
                                'changes'),
            call().add_argument('-v', '--verbose', dest='verbose',
                                action='count',
                                default=0,
                                help='verbose output. specify twice '
                                'for debug-level output.'),
            call().add_argument('-V', '--version', dest='version',
                                action='store_true',
                                default=False,
                                help='print version number and exit.'),
            call().parse_args(argv),
        ]

    def test_entry_point_version(self, capsys):
        argv = ['piface-listener', '-V']
        with patch.object(sys, 'argv', argv):
            with patch('%s.run' % pb) as mock_run:
                with pytest.raises(SystemExit) as excinfo:
                    self.cls.console_entry_point()
        out, err = capsys.readouterr()
        assert out == "pyface-webhooks %s " \
            "<https://github.com/jantman/piface_webhooks>\n" % VERSION
        assert err == ''
        assert excinfo.value.code == 0
        assert mock_run.mock_calls == []

    def test_entry_verbose(self, capsys):
        argv = ['piface-listener', '-v']
        with patch.object(sys, 'argv', argv):
            with patch('%s.logger.setLevel' % pbm) as mock_set_level:
                with patch('%s.run' % pb) as mock_run:
                    self.cls.console_entry_point()
        out, err = capsys.readouterr()
        assert out == ''
        assert err == ''
        assert mock_set_level.mock_calls == [call(logging.INFO)]
        assert mock_run.mock_calls == [call()]

    def test_entry_debug(self, capsys):
        argv = ['piface-listener', '-vv']
        with patch.object(sys, 'argv', argv):
            with patch('%s.logger.setLevel' % pbm) as mock_set_level:
                with patch('%s.run' % pb) as mock_run:
                    self.cls.console_entry_point()
        out, err = capsys.readouterr()
        assert out == ''
        assert err == ''
        assert mock_set_level.mock_calls == [call(logging.DEBUG)]
        assert mock_run.mock_calls == [call()]

    def test_entry_none(self, capsys):
        argv = ['piface-listener']
        with patch.object(sys, 'argv', argv):
            with patch('%s.logger.setLevel' % pbm) as mock_set_level:
                with patch('%s.run' % pb) as mock_run:
                    self.cls.console_entry_point()
        out, err = capsys.readouterr()
        assert out == ''
        assert err == ''
        assert mock_set_level.mock_calls == []
        assert mock_run.mock_calls == [call()]
        assert self.cls.write_files is True

    def test_entry_no_write(self, capsys):
        argv = ['piface-listener', '--no-write']
        with patch.object(sys, 'argv', argv):
            with patch('%s.logger.setLevel' % pbm) as mock_set_level:
                with patch('%s.run' % pb) as mock_run:
                    self.cls.console_entry_point()
        out, err = capsys.readouterr()
        assert out == ''
        assert err == ''
        assert mock_set_level.mock_calls == []
        assert mock_run.mock_calls == [call()]
        assert self.cls.write_files is False

    def test_run(self):
        with patch('%s.logger' % pbm) as mock_logger:
            with patch('%s.InputEventListener' % pbm) as mock_listener:
                with patch('%s.PiFaceDigital' % pbm) as mock_io:
                    with patch('%s.register_callbacks' % pb) as mock_reg:
                        self.cls.run()
        assert mock_logger.mock_calls == [
            call.debug("initializing chip"),
            call.debug("chip initialized"),
            call.debug('creating InputEventListener'),
            call.debug('activating listener')
        ]
        assert mock_listener.mock_calls == [
            call(chip=self.cls.chip),
            call().activate()
        ]
        assert mock_reg.mock_calls == [call()]
        assert mock_io.mock_calls == [call()]
        assert self.cls.chip == mock_io.return_value
        assert self.cls.listener == mock_listener.return_value

    def test_register_callbacks(self):
        mock_listener = Mock(spec_set=InputEventListener)
        self.cls.listener = mock_listener
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.register_callbacks()
        assert mock_logger.mock_calls == [
            call.debug("registering callbacks"),
            call.debug('registering callback for %s ON', 0),
            call.debug('registering callback for %s OFF', 0),
            call.debug('registering callback for %s ON', 1),
            call.debug('registering callback for %s OFF', 1),
            call.debug('registering callback for %s ON', 2),
            call.debug('registering callback for %s OFF', 2),
            call.debug('registering callback for %s ON', 3),
            call.debug('registering callback for %s OFF', 3),
            call.debug('done registering callbacks'),
            call.info('Initial pin states: %s', [10, 11, 12, 13])
        ]
        assert mock_listener.mock_calls == [
            call.register(0, IODIR_ON, self.cls.handle_input_on),
            call.register(0, IODIR_OFF, self.cls.handle_input_off),
            call.register(1, IODIR_ON, self.cls.handle_input_on),
            call.register(1, IODIR_OFF, self.cls.handle_input_off),
            call.register(2, IODIR_ON, self.cls.handle_input_on),
            call.register(2, IODIR_OFF, self.cls.handle_input_off),
            call.register(3, IODIR_ON, self.cls.handle_input_on),
            call.register(3, IODIR_OFF, self.cls.handle_input_off),
        ]
        assert self.cls.current_values == [10, 11, 12, 13]

    def test_handle_input_on(self):
        mock_evt = Mock(spec_set=InterruptEvent)
        type(mock_evt).pin_num = 3
        type(mock_evt).timestamp = 1234.5678

        self.cls.current_values = [5, 5, 5, 5]

        with patch('%s.handle_change' % pb) as mock_handle:
            with patch('%s.logger' % pbm) as mock_logger:
                with patch('%s.no_state_change' % pb) as mock_no_change:
                    with patch('%s.set_output' % pb) as mock_set:
                        mock_no_change.return_value = False
                        self.cls.handle_input_on(mock_evt)
        assert mock_logger.mock_calls == [
            call.info("Received ON event for pin %s", 3),
        ]
        assert mock_handle.mock_calls == [call(3, 1, 1234.5678)]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []
        assert self.cls.current_values == [5, 5, 5, 1]
        assert mock_no_change.mock_calls == [call(3, 1)]
        assert mock_set.mock_calls == [call(3, 1)]

    def test_handle_input_on_no_change(self):
        mock_evt = Mock(spec_set=InterruptEvent)
        type(mock_evt).pin_num = 3
        type(mock_evt).timestamp = 1234.5678

        self.cls.current_values = [5, 5, 5, 1]

        with patch('%s.handle_change' % pb) as mock_handle:
            with patch('%s.logger' % pbm) as mock_logger:
                with patch('%s.no_state_change' % pb) as mock_no_change:
                    with patch('%s.set_output' % pb) as mock_set:
                        mock_no_change.return_value = True
                        self.cls.handle_input_on(mock_evt)
        assert mock_logger.mock_calls == [
            call.info("Ignoring duplicate event for pin %s state %s",
                      3, 1)
        ]
        assert mock_handle.mock_calls == []
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []
        assert self.cls.current_values == [5, 5, 5, 1]
        assert mock_no_change.mock_calls == [call(3, 1)]
        assert mock_set.mock_calls == []

    def test_handle_input_off(self):
        mock_evt = Mock(spec_set=InterruptEvent)
        type(mock_evt).pin_num = 1
        type(mock_evt).timestamp = 1234.5678

        self.cls.current_values = [5, 5, 5, 5]

        with patch('%s.handle_change' % pb) as mock_handle:
            with patch('%s.logger' % pbm) as mock_logger:
                with patch('%s.no_state_change' % pb) as mock_no_change:
                    with patch('%s.set_output' % pb) as mock_set:
                        mock_no_change.return_value = False
                        self.cls.handle_input_off(mock_evt)
        assert mock_logger.mock_calls == [
            call.info("Received OFF event for pin %s", 1),
        ]
        assert mock_handle.mock_calls == [call(1, 0, 1234.5678)]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []
        assert self.cls.current_values == [5, 0, 5, 5]
        assert mock_no_change.mock_calls == [call(1, 0)]
        assert mock_set.mock_calls == [call(1, 0)]

    def test_handle_input_off_no_change(self):
        mock_evt = Mock(spec_set=InterruptEvent)
        type(mock_evt).pin_num = 1
        type(mock_evt).timestamp = 1234.5678

        self.cls.current_values = [5, 0, 5, 5]

        with patch('%s.handle_change' % pb) as mock_handle:
            with patch('%s.logger' % pbm) as mock_logger:
                with patch('%s.no_state_change' % pb) as mock_no_change:
                    with patch('%s.set_output' % pb) as mock_set:
                        mock_no_change.return_value = True
                        self.cls.handle_input_off(mock_evt)
        assert mock_logger.mock_calls == [
            call.info("Ignoring duplicate event for pin %s state %s",
                      1, 0)
        ]
        assert mock_handle.mock_calls == []
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []
        assert self.cls.current_values == [5, 0, 5, 5]
        assert mock_no_change.mock_calls == [call(1, 0)]
        assert mock_set.mock_calls == []

    def test_no_state_change(self):
        self.cls.current_values = [1, 0, 1]
        assert self.cls.no_state_change(0, 1) is True
        assert self.cls.no_state_change(0, 0) is False

    def test_handle_change_on(self):
        fpath = '/foo/bar/pinevent_123.4567_pin2_state1'
        type(self.cls.config).QUEUE_PATH = '/foo/bar'
        with patch('%s.logger' % pbm) as mock_logger:
            with patch('%s.open' % pbm,
                       mock_open(read_data='')) as mock_opn:
                with patch('%s.os.utime' % pbm) as mock_utime:
                    self.cls.handle_change(2, 1, 123.4567)
        assert mock_logger.mock_calls == [
            call.debug("Created event file: %s", fpath)
        ]
        assert mock_opn.mock_calls == [
            call(fpath, 'a'),
            call().__enter__(),
            call().__exit__(None, None, None)
        ]
        assert mock_utime.mock_calls == [
            call(fpath, None)
        ]

    def test_handle_change_off_no_write(self):
        self.cls.write_files = False
        fpath = '/foo/bar/pinevent_123.4567_pin2_state0'
        type(self.cls.config).QUEUE_PATH = '/foo/bar'
        with patch('%s.logger' % pbm) as mock_logger:
            with patch('%s.open' % pbm,
                       mock_open(read_data='')) as mock_opn:
                with patch('%s.os.utime' % pbm) as mock_utime:
                    self.cls.handle_change(2, 0, 123.4567)
        assert mock_logger.mock_calls == [
            call.warning("Would create event file: %s", fpath)
        ]
        assert mock_opn.mock_calls == []
        assert mock_utime.mock_calls == []

    def test_set_output_no_leds(self):
        type(self.cls.config).NO_LEDS = True
        type(self.cls.config).INVERT_LED = False
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.set_output(0, 1)
        assert mock_logger.mock_calls == [
            call.debug("Not lighting LEDs")
        ]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []

    def test_set_output_on(self):
        type(self.cls.config).NO_LEDS = False
        type(self.cls.config).INVERT_LED = False
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.set_output(0, 1)
        assert mock_logger.mock_calls == [
            call.debug("Setting output %s on", 0)
        ]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == [call.turn_on()]
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []

    def test_set_output_off(self):
        type(self.cls.config).NO_LEDS = False
        type(self.cls.config).INVERT_LED = False
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.set_output(2, 0)
        assert mock_logger.mock_calls == [
            call.debug("Setting output %s off", 2)
        ]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == [call.turn_off()]
        assert self.opin3.mock_calls == []

    def test_set_output_on_invert(self):
        type(self.cls.config).NO_LEDS = False
        type(self.cls.config).INVERT_LED = True
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.set_output(0, 1)
        assert mock_logger.mock_calls == [
            call.debug("Setting output %s off", 0)
        ]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == [call.turn_off()]
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == []
        assert self.opin3.mock_calls == []

    def test_set_output_off_invert(self):
        type(self.cls.config).NO_LEDS = False
        type(self.cls.config).INVERT_LED = True
        with patch('%s.logger' % pbm) as mock_logger:
            self.cls.set_output(2, 0)
        assert mock_logger.mock_calls == [
            call.debug("Setting output %s on", 2)
        ]
        assert self.mock_chip.mock_calls == []
        assert self.opin0.mock_calls == []
        assert self.opin1.mock_calls == []
        assert self.opin2.mock_calls == [call.turn_on()]
        assert self.opin3.mock_calls == []