Example #1
0
    def test_recv_message_separator_time(self):
        """
        Verify that message_separator_time works correctly.
        Receive a message over time t, where t > message_separator_time, and each part of the message is delayed by
        t' < message_separator_time.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 60ms.
          and: message_separator_time set 20ms

        When: User calls SerialConnection.recv(max_bytes=60).
         and: MockSerial.recv set to return increasing bytes.
         and: MockSerial.recv set to delay very briefly on each call (0.001ms (1 microsecond)).

        Then: SerialConnection.recv calls MockSerial.recv multiple times (more than 2).
         and: SerialConnection.recv returns data with multiple bytes (more than 2).
        """
        # Given
        self.uut = SerialConnection(timeout=.060, message_separator_time=.020)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [b'1'] * 60
        self.mock.recv_wait_times = [0.000001] * 60
        data = self.uut.recv(max_bytes=60)

        # Then
        self.assertGreater(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertGreater(len(data), 2)
Example #2
0
    def test_recv_message_separator_time_2(self):
        """
        Verify that message_separator_time works correctly.
        Receive a message that times out with message_separator_time, but which would not time out with only a timeout.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 60ms.
          and: message_separator_time set 20ms

        When: User calls SerialConnection.recv(60).
         and: MockSerial.recv set to return 1 byte, then 1 byte, then 58 bytes.
         and: MockSerial.recv set to delay 1ms, then 40ms, then 1ms.

        Then: SerialConnection.recv calls MockSerial.recv twice.
         and: SerialConnection.recv returns only the first two bytes.
        """
        # Given
        self.uut = SerialConnection(timeout=.060, message_separator_time=.020)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [b'1', b'2', b'3' * 58]
        self.mock.recv_wait_times = [.001, .040, .001]
        data = self.uut.recv(max_bytes=60)

        # Then
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertEqual(data, b'12')
    def test_recv_message_content_checker(self):
        """
        Verify that content_checker is used correctly.
        The content_checker indicates how much of a message is valid, if any.
        Verify behavior when the content_checker consumes a part of the buffer, the full buffer, and then part of it
        again.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 100ms.
          and: message_separator_time set 20ms
          and: content_checker set to a function that returns 0, 3, 0, 5, 0, 3

        When: User calls SerialConnection.recv(100) 3 times.
         and: MockSerial.recv set to return 2 bytes repeatedly.

        Then: SerialConnection.recv calls MockSerial.recv 6 times.
         and: SerialConnection.recv returns only the first 3 bytes, then the next 5 bytes, then the next 3.
        """

        # Given
        # PyUnusedLocal suppression: args/kwargs make the method callable by SerialConnection, but are not used.
        # noinspection PyUnusedLocal
        def test_checker(*args, **kwargs):
            """
            :param args:   Ignored. Makes method callable with arguments.
            :param kwargs: Ignored. Makes method callable with arguments.

            :return: 0, 3, 0, 5, 0, 3, 0, 0...
            """
            if not hasattr(test_checker, "counter"):
                test_checker.counter = 0

            test_checker.counter += 1

            if test_checker.counter == 2:
                return 3
            elif test_checker.counter == 4:
                return 5
            elif test_checker.counter == 6:
                return 3
            else:
                return 0

        self.uut = SerialConnection(timeout=0.100, message_separator_time=0.020, content_checker=test_checker)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [b"12", b"34", b"56", b"78", b"9A", b"BC"]

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertEqual(data, b"123")

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 4)
        self.assertEqual(data, b"45678")

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 6)
        self.assertEqual(data, b"9AB")
    def test_recv_message_separator_time_2(self):
        """
        Verify that message_separator_time works correctly.
        Receive a message that times out with message_separator_time, but which would not time out with only a timeout.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 60ms.
          and: message_separator_time set 20ms

        When: User calls SerialConnection.recv(60).
         and: MockSerial.recv set to return 1 byte, then 1 byte, then 58 bytes.
         and: MockSerial.recv set to delay 1ms, then 40ms, then 1ms.

        Then: SerialConnection.recv calls MockSerial.recv twice.
         and: SerialConnection.recv returns only the first two bytes.
        """
        # Given
        self.uut = SerialConnection(timeout=0.060, message_separator_time=0.020)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [b"1", b"2", b"3" * 58]
        self.mock.recv_wait_times = [0.001, 0.040, 0.001]
        data = self.uut.recv(max_bytes=60)

        # Then
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertEqual(data, b"12")
    def test_recv_message_separator_time(self):
        """
        Verify that message_separator_time works correctly.
        Receive a message over time t, where t > message_separator_time, and each part of the message is delayed by
        t' < message_separator_time.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 60ms.
          and: message_separator_time set 20ms

        When: User calls SerialConnection.recv(max_bytes=60).
         and: MockSerial.recv set to return increasing bytes.
         and: MockSerial.recv set to delay very briefly on each call (0.001ms (1 microsecond)).

        Then: SerialConnection.recv calls MockSerial.recv multiple times (more than 2).
         and: SerialConnection.recv returns data with multiple bytes (more than 2).
        """
        # Given
        self.uut = SerialConnection(timeout=0.060, message_separator_time=0.020)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [b"1"] * 60
        self.mock.recv_wait_times = [0.000001] * 60
        data = self.uut.recv(max_bytes=60)

        # Then
        self.assertGreater(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertGreater(len(data), 2)
Example #6
0
    def test_recv_timeout(self):
        """
        Verify that recv() returns partial messages after the timeout expires.

        Given: A SerialConnection using MockSerial,
               with timeout set to a smallish value.

        When: User calls SerialConnection.recv(n) several times with different values of n.
          and: MockSerial.recv set to return a single message, then repeatedly return nothing.

        Then: SerialConnection.recv calls MockSerial.recv at least once.
         and: SerialConnection.recv returns the MockSerial.recv() return value after the timeout.

        Note: Timeout functionality is tested, but not the precise timing.
        """
        self.uut = SerialConnection(timeout=.001)  # 1ms
        self.uut._connection = self.mock

        # n == 1
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b'']
        data = self.uut.recv(max_bytes=1)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b'')

        # n == 2
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b'1']
        data = self.uut.recv(max_bytes=2)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b'1')

        # n == 3, len(data) == 1
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b'1']
        data = self.uut.recv(max_bytes=5)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b'1')

        # n == 3, len(data) == 2
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b'12']
        data = self.uut.recv(max_bytes=3)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b'12')
    def test_recv_timeout(self):
        """
        Verify that recv() returns partial messages after the timeout expires.

        Given: A SerialConnection using MockSerial,
               with timeout set to a smallish value.

        When: User calls SerialConnection.recv(n) several times with different values of n.
          and: MockSerial.recv set to return a single message, then repeatedly return nothing.

        Then: SerialConnection.recv calls MockSerial.recv at least once.
         and: SerialConnection.recv returns the MockSerial.recv() return value after the timeout.

        Note: Timeout functionality is tested, but not the precise timing.
        """
        self.uut = SerialConnection(timeout=0.001)  # 1ms
        self.uut._connection = self.mock

        # n == 1
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b""]
        data = self.uut.recv(max_bytes=1)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b"")

        # n == 2
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b"1"]
        data = self.uut.recv(max_bytes=2)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b"1")

        # n == 3, len(data) == 1
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b"1"]
        data = self.uut.recv(max_bytes=5)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b"1")

        # n == 3, len(data) == 2
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b"12"]
        data = self.uut.recv(max_bytes=3)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b"12")
Example #8
0
 def setUp(self):
     self.mock = MockSerial()
     self.uut = SerialConnection()
     self.uut._connection = self.mock
Example #9
0
class TestSerialConnection(unittest.TestCase):
    def setUp(self):
        self.mock = MockSerial()
        self.uut = SerialConnection()
        self.uut._connection = self.mock

    def test_open(self):
        """
        Given: A SerialConnection using MockSerial.
        When: Calling SerialConnection.open().
        Then: MockSerial.open() is called.
        """
        self.uut.open()
        self.assertTrue(self.mock.open_called)

    def test_close(self):
        """
        Given: A SerialConnection using MockSerial.
        When: Calling SerialConnection.close().
        Then: MockSerial.close() is called.
        """
        self.uut.close()
        self.assertTrue(self.mock.close_called)

    ###########################################################################
    # Send tests
    ###########################################################################
    def test_send_basic(self):
        """
        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data)
        and: MockSerial.send() returns len(data).

        Then: Verify MockSerial.send() was called only once.
        and: Verify MockSerial.send() received the expected data.
        """
        # When
        data = b'ABCDEFG'
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 1)
        self.assertEqual(self.mock.send_data_list[0], b'ABCDEFG')

    def test_send_return_none(self):
        """
        Verify that MockSerial.send() is called again when it returns None.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 10 bytes.
        and: MockSerial.send() returns: None, 10.

        Then: Verify MockSerial.send() was called exactly 2 times.
        and: Verify MockSerial.send() received the expected data each time.
        """
        # When
        data = b'123456789A'
        self.mock.send_return_queue = [None, 10]
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 2)
        self.assertEqual(self.mock.send_data_list,
                         [b'123456789A', b'123456789A'])

    def test_send_multiple(self):
        """
        Verify that MockSerial.send() is called repeatedly until it sends all the data.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 9 bytes.
        and: MockSerial.send() returns: 0, None, 0, 1, 2, 3, 2, 1.

        Then: Verify MockSerial.send() was called exactly 7 times.
        and: Verify MockSerial.send() received the expected data each time.
        """
        # When
        data = b'123456789'
        self.mock.send_return_queue = [0, None, 0, 1, 2, 3, 2, 1]
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 8)
        self.assertEqual(self.mock.send_data_list, [
            b'123456789', b'123456789', b'123456789', b'123456789',
            b'23456789', b'456789', b'789', b'9'
        ])

    def test_send_off_by_one(self):
        """
        Verify that MockSerial.send() is called again when it sends all but 1 byte.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 9 bytes.
        and: MockSerial.send() returns: 8, 1.

        Then: Verify MockSerial.send() was called exactly 2 times.
        and: Verify MockSerial.send() received the expected data each time.
        """
        # When
        data = b'123456789'
        self.mock.send_return_queue = [8, 1]
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 2)
        self.assertEqual(self.mock.send_data_list, [b'123456789', b'9'])

    def test_send_one_byte(self):
        """
        Verify that MockSerial.send() is called again when it returns 0 after being given 1 byte.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 1 byte.
        and: MockSerial.send() returns: 0, 1.

        Then: Verify MockSerial.send() was called exactly 2 times.
        and: Verify MockSerial.send() received the expected data each time.
        """
        # When
        data = b'1'
        self.mock.send_return_queue = [0, 1]
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 2)
        self.assertEqual(self.mock.send_data_list, [b'1', b'1'])

    def test_send_many(self):
        """
        Verify that send works properly when MockSerial.send() sends 1 byte at a time.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 9 bytes.
        and: MockSerial.send() returns: 0, 500 times, followed by len(data).

        Then: Verify MockSerial.send() was called exactly 501 times.
        and: Verify MockSerial.send() received the expected data each time.
        """
        # When
        data = b'123456789'
        self.mock.send_return_queue = [0] * 500 + [len(data)]
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 501)
        self.assertEqual(self.mock.send_data_list, [b'123456789'] * 501)

    def test_send_zero_bytes(self):
        """
        Verify that send() doesn't fail when given 0 bytes.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 0 bytes.
        and:  MockSerial.send() set to return len(data).

        Then: Verify MockSerial.send() was called either 0 or 1 times.
        and:  Verify MockSerial.send() received 0 bytes, if anything.
        """
        # When
        data = b''
        self.mock.send_return_queue = [0, 1]
        self.uut.send(data=data)
        # Then
        self.assertLessEqual(len(self.mock.send_data_list), 1)
        if len(self.mock.send_data_list) == 0:
            self.assertEqual(self.mock.send_data_list, [])
        else:
            self.assertEqual(self.mock.send_data_list, [b''])

    ###########################################################################
    # Receive tests
    ###########################################################################
    def test_recv_simple(self):
        """
        Verify that recv() works in the normal case.

        Given: A SerialConnection using MockSerial,
               with no timeout/message_separator_time/content_checker.

        When: User calls SerialConnection.recv.
          and: MockSerial.recv set to return data of length max_bytes.

        Then: SerialConnection calls MockSerial.recv exactly once.
         and: SerialConnection.recv returns exactly what MockSerial.recv returned.
        """
        # When
        self.mock.recv_return_queue = [b'0123456']
        data = self.uut.recv(max_bytes=7)
        # Then
        self.assertEqual(self.mock.recv_max_bytes_lengths, [7])
        self.assertEqual(data, b'0123456')

    def test_recv_max_bytes_only(self):
        """
        Verify that recv() calls MockSerial.recv() repeatedly until it gets max_bytes of data.

        Given: A SerialConnection using MockSerial,
               with no timeout/message_separator_time/content_checker.

        When: User calls SerialConnection.recv(10).
          and: MockSerial.recv set to return 0, 0, 0, 1, 2, 3, 4 bytes.

        Then: SerialConnection calls MockSerial.recv exactly 7 times,
              with max_bytes decreasing as appropriate.
         and: SerialConnection.recv returns the concatenation of MockSerial.recv() return values.
        """
        # When
        self.mock.recv_return_queue = [
            b'', b'', b'', b'1', b'22', b'123', b'1234'
        ]
        data = self.uut.recv(max_bytes=10)
        # Then
        self.assertEqual(self.mock.recv_max_bytes_lengths,
                         [10, 10, 10, 10, 9, 7, 4])
        self.assertEqual(data, b'1221231234')

    def test_recv_timeout(self):
        """
        Verify that recv() returns partial messages after the timeout expires.

        Given: A SerialConnection using MockSerial,
               with timeout set to a smallish value.

        When: User calls SerialConnection.recv(n) several times with different values of n.
          and: MockSerial.recv set to return a single message, then repeatedly return nothing.

        Then: SerialConnection.recv calls MockSerial.recv at least once.
         and: SerialConnection.recv returns the MockSerial.recv() return value after the timeout.

        Note: Timeout functionality is tested, but not the precise timing.
        """
        self.uut = SerialConnection(timeout=.001)  # 1ms
        self.uut._connection = self.mock

        # n == 1
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b'']
        data = self.uut.recv(max_bytes=1)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b'')

        # n == 2
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b'1']
        data = self.uut.recv(max_bytes=2)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b'1')

        # n == 3, len(data) == 1
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b'1']
        data = self.uut.recv(max_bytes=5)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b'1')

        # n == 3, len(data) == 2
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b'12']
        data = self.uut.recv(max_bytes=3)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b'12')

        # # n == 2**16, len(data) == 2**16 - 1
        # self.mock.recv_return_nothing_by_default = True
        # self.mock.recv_return_queue = [b'\0'] * (2**16 - 1)
        # data = uut.recv(max_bytes=2**16)
        # self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        # self.assertEqual(data, [b'\0'] * (2**16 - 1))

    def test_recv_message_separator_time(self):
        """
        Verify that message_separator_time works correctly.
        Receive a message over time t, where t > message_separator_time, and each part of the message is delayed by
        t' < message_separator_time.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 60ms.
          and: message_separator_time set 20ms

        When: User calls SerialConnection.recv(max_bytes=60).
         and: MockSerial.recv set to return increasing bytes.
         and: MockSerial.recv set to delay very briefly on each call (0.001ms (1 microsecond)).

        Then: SerialConnection.recv calls MockSerial.recv multiple times (more than 2).
         and: SerialConnection.recv returns data with multiple bytes (more than 2).
        """
        # Given
        self.uut = SerialConnection(timeout=.060, message_separator_time=.020)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [b'1'] * 60
        self.mock.recv_wait_times = [0.000001] * 60
        data = self.uut.recv(max_bytes=60)

        # Then
        self.assertGreater(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertGreater(len(data), 2)

    def test_recv_message_separator_time_2(self):
        """
        Verify that message_separator_time works correctly.
        Receive a message that times out with message_separator_time, but which would not time out with only a timeout.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 60ms.
          and: message_separator_time set 20ms

        When: User calls SerialConnection.recv(60).
         and: MockSerial.recv set to return 1 byte, then 1 byte, then 58 bytes.
         and: MockSerial.recv set to delay 1ms, then 40ms, then 1ms.

        Then: SerialConnection.recv calls MockSerial.recv twice.
         and: SerialConnection.recv returns only the first two bytes.
        """
        # Given
        self.uut = SerialConnection(timeout=.060, message_separator_time=.020)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [b'1', b'2', b'3' * 58]
        self.mock.recv_wait_times = [.001, .040, .001]
        data = self.uut.recv(max_bytes=60)

        # Then
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertEqual(data, b'12')

    def test_recv_message_content_checker(self):
        """
        Verify that content_checker is used correctly.
        The content_checker indicates how much of a message is valid, if any.
        Verify behavior when the content_checker consumes a part of the buffer, the full buffer, and then part of it
        again.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 100ms.
          and: message_separator_time set 20ms
          and: content_checker set to a function that returns 0, 3, 0, 5, 0, 3

        When: User calls SerialConnection.recv(100) 3 times.
         and: MockSerial.recv set to return 2 bytes repeatedly.

        Then: SerialConnection.recv calls MockSerial.recv 6 times.
         and: SerialConnection.recv returns only the first 3 bytes, then the next 5 bytes, then the next 3.
        """

        # Given
        # PyUnusedLocal suppression: args/kwargs make the method callable by SerialConnection, but are not used.
        # noinspection PyUnusedLocal
        def test_checker(*args, **kwargs):
            """
            :param args:   Ignored. Makes method callable with arguments.
            :param kwargs: Ignored. Makes method callable with arguments.

            :return: 0, 3, 0, 5, 0, 3, 0, 0...
            """
            if not hasattr(test_checker, "counter"):
                test_checker.counter = 0

            test_checker.counter += 1

            if test_checker.counter == 2:
                return 3
            elif test_checker.counter == 4:
                return 5
            elif test_checker.counter == 6:
                return 3
            else:
                return 0

        self.uut = SerialConnection(timeout=.100,
                                    message_separator_time=.020,
                                    content_checker=test_checker)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [
            b'12', b'34', b'56', b'78', b'9A', b'BC'
        ]

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertEqual(data, b'123')

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 4)
        self.assertEqual(data, b'45678')

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 6)
        self.assertEqual(data, b'9AB')
Example #10
0
    def test_recv_message_content_checker(self):
        """
        Verify that content_checker is used correctly.
        The content_checker indicates how much of a message is valid, if any.
        Verify behavior when the content_checker consumes a part of the buffer, the full buffer, and then part of it
        again.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 100ms.
          and: message_separator_time set 20ms
          and: content_checker set to a function that returns 0, 3, 0, 5, 0, 3

        When: User calls SerialConnection.recv(100) 3 times.
         and: MockSerial.recv set to return 2 bytes repeatedly.

        Then: SerialConnection.recv calls MockSerial.recv 6 times.
         and: SerialConnection.recv returns only the first 3 bytes, then the next 5 bytes, then the next 3.
        """

        # Given
        # PyUnusedLocal suppression: args/kwargs make the method callable by SerialConnection, but are not used.
        # noinspection PyUnusedLocal
        def test_checker(*args, **kwargs):
            """
            :param args:   Ignored. Makes method callable with arguments.
            :param kwargs: Ignored. Makes method callable with arguments.

            :return: 0, 3, 0, 5, 0, 3, 0, 0...
            """
            if not hasattr(test_checker, "counter"):
                test_checker.counter = 0

            test_checker.counter += 1

            if test_checker.counter == 2:
                return 3
            elif test_checker.counter == 4:
                return 5
            elif test_checker.counter == 6:
                return 3
            else:
                return 0

        self.uut = SerialConnection(timeout=.100,
                                    message_separator_time=.020,
                                    content_checker=test_checker)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [
            b'12', b'34', b'56', b'78', b'9A', b'BC'
        ]

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertEqual(data, b'123')

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 4)
        self.assertEqual(data, b'45678')

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 6)
        self.assertEqual(data, b'9AB')
 def setUp(self):
     self.mock = MockSerial()
     self.uut = SerialConnection()
     self.uut._connection = self.mock
class TestSerialConnection(unittest.TestCase):
    def setUp(self):
        self.mock = MockSerial()
        self.uut = SerialConnection()
        self.uut._connection = self.mock

    def test_open(self):
        """
        Given: A SerialConnection using MockSerial.
        When: Calling SerialConnection.open().
        Then: MockSerial.open() is called.
        """
        self.uut.open()
        self.assertTrue(self.mock.open_called)

    def test_close(self):
        """
        Given: A SerialConnection using MockSerial.
        When: Calling SerialConnection.close().
        Then: MockSerial.close() is called.
        """
        self.uut.close()
        self.assertTrue(self.mock.close_called)

    ###########################################################################
    # Send tests
    ###########################################################################
    def test_send_basic(self):
        """
        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data)
        and: MockSerial.send() returns len(data).

        Then: Verify MockSerial.send() was called only once.
        and: Verify MockSerial.send() received the expected data.
        """
        # When
        data = b"ABCDEFG"
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 1)
        self.assertEqual(self.mock.send_data_list[0], b"ABCDEFG")

    def test_send_return_none(self):
        """
        Verify that MockSerial.send() is called again when it returns None.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 10 bytes.
        and: MockSerial.send() returns: None, 10.

        Then: Verify MockSerial.send() was called exactly 2 times.
        and: Verify MockSerial.send() received the expected data each time.
        """
        # When
        data = b"123456789A"
        self.mock.send_return_queue = [None, 10]
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 2)
        self.assertEqual(self.mock.send_data_list, [b"123456789A", b"123456789A"])

    def test_send_multiple(self):
        """
        Verify that MockSerial.send() is called repeatedly until it sends all the data.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 9 bytes.
        and: MockSerial.send() returns: 0, None, 0, 1, 2, 3, 2, 1.

        Then: Verify MockSerial.send() was called exactly 7 times.
        and: Verify MockSerial.send() received the expected data each time.
        """
        # When
        data = b"123456789"
        self.mock.send_return_queue = [0, None, 0, 1, 2, 3, 2, 1]
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 8)
        self.assertEqual(
            self.mock.send_data_list,
            [b"123456789", b"123456789", b"123456789", b"123456789", b"23456789", b"456789", b"789", b"9"],
        )

    def test_send_off_by_one(self):
        """
        Verify that MockSerial.send() is called again when it sends all but 1 byte.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 9 bytes.
        and: MockSerial.send() returns: 8, 1.

        Then: Verify MockSerial.send() was called exactly 2 times.
        and: Verify MockSerial.send() received the expected data each time.
        """
        # When
        data = b"123456789"
        self.mock.send_return_queue = [8, 1]
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 2)
        self.assertEqual(self.mock.send_data_list, [b"123456789", b"9"])

    def test_send_one_byte(self):
        """
        Verify that MockSerial.send() is called again when it returns 0 after being given 1 byte.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 1 byte.
        and: MockSerial.send() returns: 0, 1.

        Then: Verify MockSerial.send() was called exactly 2 times.
        and: Verify MockSerial.send() received the expected data each time.
        """
        # When
        data = b"1"
        self.mock.send_return_queue = [0, 1]
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 2)
        self.assertEqual(self.mock.send_data_list, [b"1", b"1"])

    def test_send_many(self):
        """
        Verify that send works properly when MockSerial.send() sends 1 byte at a time.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 9 bytes.
        and: MockSerial.send() returns: 0, 500 times, followed by len(data).

        Then: Verify MockSerial.send() was called exactly 501 times.
        and: Verify MockSerial.send() received the expected data each time.
        """
        # When
        data = b"123456789"
        self.mock.send_return_queue = [0] * 500 + [len(data)]
        self.uut.send(data=data)
        # Then
        self.assertEqual(len(self.mock.send_data_list), 501)
        self.assertEqual(self.mock.send_data_list, [b"123456789"] * 501)

    def test_send_zero_bytes(self):
        """
        Verify that send() doesn't fail when given 0 bytes.

        Given: A SerialConnection using MockSerial.

        When: Calling SerialConnection.send(data) with 0 bytes.
        and:  MockSerial.send() set to return len(data).

        Then: Verify MockSerial.send() was called either 0 or 1 times.
        and:  Verify MockSerial.send() received 0 bytes, if anything.
        """
        # When
        data = b""
        self.mock.send_return_queue = [0, 1]
        self.uut.send(data=data)
        # Then
        self.assertLessEqual(len(self.mock.send_data_list), 1)
        if len(self.mock.send_data_list) == 0:
            self.assertEqual(self.mock.send_data_list, [])
        else:
            self.assertEqual(self.mock.send_data_list, [b""])

    ###########################################################################
    # Receive tests
    ###########################################################################
    def test_recv_simple(self):
        """
        Verify that recv() works in the normal case.

        Given: A SerialConnection using MockSerial,
               with no timeout/message_separator_time/content_checker.

        When: User calls SerialConnection.recv.
          and: MockSerial.recv set to return data of length max_bytes.

        Then: SerialConnection calls MockSerial.recv exactly once.
         and: SerialConnection.recv returns exactly what MockSerial.recv returned.
        """
        # When
        self.mock.recv_return_queue = [b"0123456"]
        data = self.uut.recv(max_bytes=7)
        # Then
        self.assertEqual(self.mock.recv_max_bytes_lengths, [7])
        self.assertEqual(data, b"0123456")

    def test_recv_max_bytes_only(self):
        """
        Verify that recv() calls MockSerial.recv() repeatedly until it gets max_bytes of data.

        Given: A SerialConnection using MockSerial,
               with no timeout/message_separator_time/content_checker.

        When: User calls SerialConnection.recv(10).
          and: MockSerial.recv set to return 0, 0, 0, 1, 2, 3, 4 bytes.

        Then: SerialConnection calls MockSerial.recv exactly 7 times,
              with max_bytes decreasing as appropriate.
         and: SerialConnection.recv returns the concatenation of MockSerial.recv() return values.
        """
        # When
        self.mock.recv_return_queue = [b"", b"", b"", b"1", b"22", b"123", b"1234"]
        data = self.uut.recv(max_bytes=10)
        # Then
        self.assertEqual(self.mock.recv_max_bytes_lengths, [10, 10, 10, 10, 9, 7, 4])
        self.assertEqual(data, b"1221231234")

    def test_recv_timeout(self):
        """
        Verify that recv() returns partial messages after the timeout expires.

        Given: A SerialConnection using MockSerial,
               with timeout set to a smallish value.

        When: User calls SerialConnection.recv(n) several times with different values of n.
          and: MockSerial.recv set to return a single message, then repeatedly return nothing.

        Then: SerialConnection.recv calls MockSerial.recv at least once.
         and: SerialConnection.recv returns the MockSerial.recv() return value after the timeout.

        Note: Timeout functionality is tested, but not the precise timing.
        """
        self.uut = SerialConnection(timeout=0.001)  # 1ms
        self.uut._connection = self.mock

        # n == 1
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b""]
        data = self.uut.recv(max_bytes=1)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b"")

        # n == 2
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b"1"]
        data = self.uut.recv(max_bytes=2)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b"1")

        # n == 3, len(data) == 1
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b"1"]
        data = self.uut.recv(max_bytes=5)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b"1")

        # n == 3, len(data) == 2
        self.mock.recv_return_nothing_by_default = True
        self.mock.recv_return_queue = [b"12"]
        data = self.uut.recv(max_bytes=3)
        self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        self.assertEqual(data, b"12")

        # # n == 2**16, len(data) == 2**16 - 1
        # self.mock.recv_return_nothing_by_default = True
        # self.mock.recv_return_queue = [b'\0'] * (2**16 - 1)
        # data = uut.recv(max_bytes=2**16)
        # self.assertGreaterEqual(len(self.mock.recv_max_bytes_lengths), 1)
        # self.assertEqual(data, [b'\0'] * (2**16 - 1))

    def test_recv_message_separator_time(self):
        """
        Verify that message_separator_time works correctly.
        Receive a message over time t, where t > message_separator_time, and each part of the message is delayed by
        t' < message_separator_time.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 60ms.
          and: message_separator_time set 20ms

        When: User calls SerialConnection.recv(max_bytes=60).
         and: MockSerial.recv set to return increasing bytes.
         and: MockSerial.recv set to delay very briefly on each call (0.001ms (1 microsecond)).

        Then: SerialConnection.recv calls MockSerial.recv multiple times (more than 2).
         and: SerialConnection.recv returns data with multiple bytes (more than 2).
        """
        # Given
        self.uut = SerialConnection(timeout=0.060, message_separator_time=0.020)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [b"1"] * 60
        self.mock.recv_wait_times = [0.000001] * 60
        data = self.uut.recv(max_bytes=60)

        # Then
        self.assertGreater(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertGreater(len(data), 2)

    def test_recv_message_separator_time_2(self):
        """
        Verify that message_separator_time works correctly.
        Receive a message that times out with message_separator_time, but which would not time out with only a timeout.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 60ms.
          and: message_separator_time set 20ms

        When: User calls SerialConnection.recv(60).
         and: MockSerial.recv set to return 1 byte, then 1 byte, then 58 bytes.
         and: MockSerial.recv set to delay 1ms, then 40ms, then 1ms.

        Then: SerialConnection.recv calls MockSerial.recv twice.
         and: SerialConnection.recv returns only the first two bytes.
        """
        # Given
        self.uut = SerialConnection(timeout=0.060, message_separator_time=0.020)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [b"1", b"2", b"3" * 58]
        self.mock.recv_wait_times = [0.001, 0.040, 0.001]
        data = self.uut.recv(max_bytes=60)

        # Then
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertEqual(data, b"12")

    def test_recv_message_content_checker(self):
        """
        Verify that content_checker is used correctly.
        The content_checker indicates how much of a message is valid, if any.
        Verify behavior when the content_checker consumes a part of the buffer, the full buffer, and then part of it
        again.

        Given: A SerialConnection using MockSerial,
          and: timeout set to 100ms.
          and: message_separator_time set 20ms
          and: content_checker set to a function that returns 0, 3, 0, 5, 0, 3

        When: User calls SerialConnection.recv(100) 3 times.
         and: MockSerial.recv set to return 2 bytes repeatedly.

        Then: SerialConnection.recv calls MockSerial.recv 6 times.
         and: SerialConnection.recv returns only the first 3 bytes, then the next 5 bytes, then the next 3.
        """

        # Given
        # PyUnusedLocal suppression: args/kwargs make the method callable by SerialConnection, but are not used.
        # noinspection PyUnusedLocal
        def test_checker(*args, **kwargs):
            """
            :param args:   Ignored. Makes method callable with arguments.
            :param kwargs: Ignored. Makes method callable with arguments.

            :return: 0, 3, 0, 5, 0, 3, 0, 0...
            """
            if not hasattr(test_checker, "counter"):
                test_checker.counter = 0

            test_checker.counter += 1

            if test_checker.counter == 2:
                return 3
            elif test_checker.counter == 4:
                return 5
            elif test_checker.counter == 6:
                return 3
            else:
                return 0

        self.uut = SerialConnection(timeout=0.100, message_separator_time=0.020, content_checker=test_checker)
        self.uut._connection = self.mock

        # When
        self.mock.recv_return_queue = [b"12", b"34", b"56", b"78", b"9A", b"BC"]

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 2)
        self.assertEqual(data, b"123")

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 4)
        self.assertEqual(data, b"45678")

        data = self.uut.recv(max_bytes=100)
        self.assertEqual(len(self.mock.recv_max_bytes_lengths), 6)
        self.assertEqual(data, b"9AB")