1# Copyright (c) 2015 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from __future__ import absolute_import 6 7import array 8import logging 9import multiprocessing 10import struct 11import unittest 12from unittest.mock import patch 13 14import common 15 16from autotest_lib.client.cros.cellular.mbim_compliance import mbim_channel 17from autotest_lib.client.cros.cellular.mbim_compliance import mbim_errors 18 19 20class MBIMChannelTestCase(unittest.TestCase): 21 """ Test cases for the MBIMChannel class. """ 22 23 def setUp(self): 24 # Arguments passed to MBIMChannel. Irrelevant for these tests, mostly. 25 self._device = None 26 self._interface_number = 0 27 self._interrupt_endpoint_address = 0x01 28 self._in_buffer_size = 100 29 30 self._setup_mock_subprocess() 31 32 patcher = patch('multiprocessing.Queue') 33 self._mock_request_queue = patcher.start() 34 self.addCleanup(patcher.stop) 35 36 self._channel._request_queue = self._mock_request_queue 37 38 # On the other hand, just grab the real response queue. 39 self._response_queue = self._channel._response_queue 40 41 # Decrease timeouts to small values to speed up tests. 42 self._channel.FRAGMENT_TIMEOUT_S = 0.2 43 self._channel.TRANSACTION_TIMEOUT_S = 0.5 44 45 46 def tearDown(self): 47 self._channel.close() 48 49 50 def _setup_mock_subprocess(self): 51 """ 52 Setup long-term expectations on the mocked out subprocess. 53 54 These expectations are only met when |self._channel.close| is called in 55 |tearDown|. 56 57 """ 58 patcher = patch.object(multiprocessing, 'Process') 59 mock_process = patcher.start() 60 self.addCleanup(patcher.stop) 61 mock_process.return_value = mock_process 62 63 # Each API call into MBIMChannel results in an aliveness ping to the 64 # subprocess. 65 # Finally, when |self._channel| is destructed, it will attempt to 66 # terminate the |mock_process|, with increasingly drastic actions. 67 mock_process.is_alive.return_value = True 68 69 self._channel = mbim_channel.MBIMChannel( 70 self._device, 71 self._interface_number, 72 self._interrupt_endpoint_address, 73 self._in_buffer_size, 74 mock_process) 75 76 77 def test_creation(self): 78 """ A trivial test that we mocked out the |Process| class correctly. """ 79 self._setup_mock_subprocess() 80 81 82 def test_unfragmented_packet_successful(self): 83 """ Test that we can synchronously send an unfragmented packet. """ 84 packet = self._get_unfragmented_packet(1) 85 response_packet = self._get_unfragmented_packet(1) 86 self._expect_transaction([packet], [response_packet]) 87 self._verify_transaction_successful([packet], [response_packet]) 88 89 90 def test_unfragmented_packet_timeout(self): 91 """ Test the case when an unfragmented packet receives no response. """ 92 packet = self._get_unfragmented_packet(1) 93 self._expect_transaction([packet]) 94 self._verify_transaction_failed([packet]) 95 96 97 def test_single_fragment_successful(self): 98 """ Test that we can synchronously send a fragmented packet. """ 99 fragment = self._get_fragment(1, 1, 0) 100 response_fragment = self._get_fragment(1, 1, 0) 101 self._expect_transaction([fragment], [response_fragment]) 102 self._verify_transaction_successful([fragment], [response_fragment]) 103 104 105 def test_single_fragment_timeout(self): 106 """ Test the case when a fragmented packet receives no response. """ 107 fragment = self._get_fragment(1, 1, 0) 108 self._expect_transaction([fragment]) 109 self._verify_transaction_failed([fragment]) 110 111 112 def test_single_fragment_corrupted_reply(self): 113 """ Test the case when the response has a corrupted fragment header. """ 114 fragment = self._get_fragment(1, 1, 0) 115 response_fragment = self._get_fragment(1, 1, 0) 116 response_fragment = response_fragment[:len(response_fragment)-1] 117 self._expect_transaction([fragment], [response_fragment]) 118 self._verify_transaction_failed([fragment]) 119 120 121 def test_multiple_fragments_successful(self): 122 """ Test that we can send/recieve multi-fragment packets. """ 123 fragment_0 = self._get_fragment(1, 2, 0) 124 fragment_1 = self._get_fragment(1, 2, 1) 125 response_fragment_0 = self._get_fragment(1, 2, 0) 126 response_fragment_1 = self._get_fragment(1, 2, 1) 127 self._expect_transaction([fragment_0, fragment_1], 128 [response_fragment_0, response_fragment_1]) 129 self._verify_transaction_successful( 130 [fragment_0, fragment_1], 131 [response_fragment_0, response_fragment_1]) 132 133 134 def test_multiple_fragments_incorrect_total_fragments(self): 135 """ Test the case when one of the fragment reports incorrect total. """ 136 fragment = self._get_fragment(1, 1, 0) 137 response_fragment_0 = self._get_fragment(1, 2, 0) 138 # total_fragment should have been 2, but is 99. 139 response_fragment_1 = self._get_fragment(1, 99, 1) 140 self._expect_transaction([fragment], 141 [response_fragment_0, response_fragment_1]) 142 self._verify_transaction_failed([fragment]) 143 144 145 def test_multiple_fragments_reordered_reply_1(self): 146 """ Test the case when the first fragemnt reports incorrect index. """ 147 fragment = self._get_fragment(1, 1, 0) 148 # Incorrect first fragment number. 149 response_fragment = self._get_fragment(1, 2, 1) 150 self._expect_transaction([fragment], [response_fragment]) 151 self._verify_transaction_failed([fragment]) 152 153 154 def test_multiple_fragments_reordered_reply_2(self): 155 """ Test the case when a follow up fragment reports incorrect index. """ 156 fragment = self._get_fragment(1, 1, 0) 157 response_fragment_0 = self._get_fragment(1, 2, 0) 158 # Incorrect second fragment number. 159 response_fragment_1 = self._get_fragment(1, 2, 99) 160 self._expect_transaction([fragment], 161 [response_fragment_0, response_fragment_1]) 162 self._verify_transaction_failed([fragment]) 163 164 165 def test_multiple_fragments_insufficient_reply_timeout(self): 166 """ Test the case when we recieve only part of the response. """ 167 fragment = self._get_fragment(1, 1, 0) 168 # The second fragment will never arrive. 169 response_fragment_0 = self._get_fragment(1, 2, 0) 170 self._expect_transaction([fragment], [response_fragment_0]) 171 self._verify_transaction_successful([fragment], [response_fragment_0]) 172 173 174 def test_unfragmented_packet_notification(self): 175 """ Test the case when a notification comes before the response. """ 176 packet = self._get_unfragmented_packet(1) 177 response = self._get_unfragmented_packet(1) 178 notification = self._get_unfragmented_packet(0) 179 self._expect_transaction([packet], [notification, response]) 180 self._verify_transaction_successful([packet], [response]) 181 self.assertEqual([[notification]], 182 self._channel.get_outstanding_packets()) 183 184 185 def test_fragmented_notification(self): 186 """ Test the case when a fragmented notification preceeds response. """ 187 packet_fragment_0 = self._get_fragment(1, 2, 0) 188 packet_fragment_1 = self._get_fragment(1, 2, 1) 189 response_fragment_0 = self._get_fragment(1, 2, 0) 190 response_fragment_1 = self._get_fragment(1, 2, 1) 191 notification_0_fragment_0 = self._get_fragment(0, 2, 0) 192 notification_0_fragment_1 = self._get_fragment(0, 2, 1) 193 notification_1_fragment_0 = self._get_fragment(99, 2, 0) 194 notification_1_fragment_1 = self._get_fragment(99, 2, 1) 195 196 self._expect_transaction( 197 [packet_fragment_0, packet_fragment_1], 198 [notification_0_fragment_0, notification_0_fragment_1, 199 notification_1_fragment_0, notification_1_fragment_1, 200 response_fragment_0, response_fragment_1]) 201 self._verify_transaction_successful( 202 [packet_fragment_0, packet_fragment_1], 203 [response_fragment_0, response_fragment_1]) 204 self.assertEqual( 205 [[notification_0_fragment_0, notification_0_fragment_1], 206 [notification_1_fragment_0, notification_1_fragment_1]], 207 self._channel.get_outstanding_packets()) 208 209 210 def test_multiple_packets_rollover_notification(self): 211 """ 212 Test the case when we receive incomplete response, followed by 213 fragmented notifications. 214 215 We have to be smart enough to realize that the incorrect fragment 216 recieved at the end of the response belongs to the next notification 217 instead. 218 219 """ 220 packet = self._get_fragment(1, 1, 0) 221 # The second fragment never comes, instead we get a notification 222 # fragment. 223 response_fragment_0 = self._get_fragment(1, 2, 0) 224 notification_0_fragment_0 = self._get_fragment(0, 2, 0) 225 notification_0_fragment_1 = self._get_fragment(0, 2, 1) 226 notification_1_fragment_0 = self._get_fragment(99, 2, 0) 227 notification_1_fragment_1 = self._get_fragment(99, 2, 1) 228 229 self._expect_transaction( 230 [packet], 231 [response_fragment_0, 232 notification_0_fragment_0, notification_0_fragment_1, 233 notification_1_fragment_0, notification_1_fragment_1]) 234 self._verify_transaction_successful( 235 [packet], 236 [response_fragment_0]) 237 self.assertEqual( 238 [[notification_0_fragment_0, notification_0_fragment_1], 239 [notification_1_fragment_0, notification_1_fragment_1]], 240 self._channel.get_outstanding_packets()) 241 242 243 def test_data(self): 244 """ Test that data is transferred transaperntly. """ 245 packet = self._get_unfragmented_packet(1) 246 packet.fromlist([0xFF, 0xFF, 0xFF, 0xFF, 0xDD, 0xDD, 0xDD, 0xDD]) 247 response_packet = self._get_unfragmented_packet(1) 248 response_packet.fromlist([0xAA, 0xAA, 0xBB, 0xBB]) 249 self._expect_transaction([packet], [response_packet]) 250 self._verify_transaction_successful([packet], [response_packet]) 251 252 253 def test_flush_successful(self): 254 """ Test that flush clears all queues. """ 255 packet = self._get_unfragmented_packet(1) 256 response = self._get_unfragmented_packet(1) 257 notification_1 = self._get_fragment(0, 1, 0) 258 self._response_queue.put_nowait(notification_1) 259 self._mock_request_queue.qsize.return_value = 1 260 self._mock_request_queue.empty.return_value = False 261 262 def put_response(): 263 """Side effect for mock""" 264 self._response_queue.put_nowait(response) 265 266 self._mock_request_queue.empty.side_effect = [None, put_response] 267 self._channel.flush() 268 self.assertEqual(0, self._response_queue.qsize()) 269 270 271 def test_flush_failed(self): 272 """ Test the case when the request queue fails to empty out. """ 273 packet = self._get_unfragmented_packet(1) 274 self._mock_request_queue.qsize.return_value = 1 275 self._mock_request_queue.empty.return_value = False 276 self.assertRaises( 277 mbim_errors.MBIMComplianceChannelError, 278 self._channel.flush) 279 280 281 def _queue_responses(self, responses): 282 """ Helper method for |_expect_transaction|. Do not use directly. """ 283 for response in responses: 284 self._response_queue.put_nowait(response) 285 286 287 def _expect_transaction(self, requests, responses=None): 288 """ 289 Helper method to setup expectations on the queues. 290 291 @param requests: A list of packets to expect on the |_request_queue|. 292 @param respones: An optional list of packets to respond with after the 293 last request. 294 295 """ 296 297 last_request = requests[len(requests) - 1] 298 if responses: 299 300 def put_if_last_request(msg): 301 """Side effect for mock""" 302 if msg == last_request: 303 self._queue_responses(responses) 304 305 self._mock_request_queue.put_nowait.side_effect = put_if_last_request 306 307 308 def _verify_transaction_successful(self, requests, responses): 309 """ 310 Helper method to assert that the transaction was successful. 311 312 @param requests: List of packets sent. 313 @param responses: List of packets expected back. 314 """ 315 self.assertEqual(responses, 316 self._channel.bidirectional_transaction(*requests)) 317 318 319 def _verify_transaction_failed(self, requests): 320 """ 321 Helper method to assert that the transaction failed. 322 323 @param requests: List of packets sent. 324 325 """ 326 self.assertRaises(mbim_errors.MBIMComplianceChannelError, 327 self._channel.bidirectional_transaction, 328 *requests) 329 330 331 def _get_unfragmented_packet(self, transaction_id): 332 """ Creates a packet that has no fragment header. """ 333 packet_format = '<LLL' # This does not contain a fragment header. 334 packet = self._create_buffer(struct.calcsize(packet_format)) 335 struct.pack_into(packet_format, 336 packet, 337 0, 338 0x00000000, # 0x0 does not need fragments. 339 struct.calcsize(packet_format), 340 transaction_id) 341 return packet 342 343 344 def _get_fragment(self, transaction_id, total_fragments, current_fragment): 345 """ Creates a fragment with the given fields. """ 346 fragment_header_format = '<LLLLL' 347 message_type = 0x00000003 # MBIM_COMMAND_MSG has fragments. 348 fragment = self._create_buffer(struct.calcsize(fragment_header_format)) 349 struct.pack_into(fragment_header_format, 350 fragment, 351 0, 352 message_type, 353 struct.calcsize(fragment_header_format), 354 transaction_id, 355 total_fragments, 356 current_fragment) 357 return fragment 358 359 360 def _create_buffer(self, size): 361 """ Create an array of the give size initialized to 0x00. """ 362 return array.array('B', b'\x00' * size) 363 364 365if __name__ == '__main__': 366 logging.basicConfig(level=logging.DEBUG) 367 unittest.main() 368