1# Copyright (c) 2012 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#
5# This module contains unit tests for the classes in the mtb module
6
7import glob
8import os
9import sys
10import unittest
11
12import common_unittest_utils
13import fuzzy
14import mtb
15import test_conf as conf
16
17from common_unittest_utils import create_mocked_devices
18from firmware_constants import AXIS, GV, MTB, PLATFORM, UNIT, VAL
19from mtb import FingerPath, TidPacket
20from geometry.elements import Point, about_eq
21
22
23unittest_path_lumpy = os.path.join(os.getcwd(), 'tests/logs/lumpy')
24mocked_device = create_mocked_devices()
25
26
27def get_mtb_packets(gesture_filename):
28    """Get mtb_packets object by reading the gesture file."""
29    parser = mtb.MtbParser()
30    packets = parser.parse_file(gesture_filename)
31    mtb_packets = mtb.Mtb(packets=packets)
32    return mtb_packets
33
34
35class FakeMtb(mtb.Mtb):
36    """A fake MTB class to set up x and y positions directly."""
37    def __init__(self, list_x, list_y):
38        self.list_x = list_x
39        self.list_y = list_y
40
41    def get_x_y(self, target_slot):
42        """Return list_x, list_y directly."""
43        return (self.list_x, self.list_y)
44
45
46class MtbTest(unittest.TestCase):
47    """Unit tests for mtb.Mtb class."""
48
49    def setUp(self):
50        self.test_dir = os.path.join(os.getcwd(), 'tests')
51        self.data_dir = os.path.join(self.test_dir, 'data')
52
53    def _get_filepath(self, filename, gesture_dir=''):
54        return os.path.join(self.data_dir, gesture_dir, filename)
55
56    def _get_range_middle(self, criteria):
57        """Get the middle range of the criteria."""
58        fc = fuzzy.FuzzyCriteria(criteria)
59        range_min , range_max = fc.get_criteria_value_range()
60        range_middle = (range_min + range_max) / 2.0
61        return range_middle
62
63    def _call_get_reversed_motions(self, list_x, list_y, expected_x,
64                                   expected_y, direction):
65        mtb = FakeMtb(list_x, list_y)
66        displacement = mtb.get_reversed_motions(0, direction, ratio=0.1)
67        self.assertEqual(displacement[AXIS.X], expected_x)
68        self.assertEqual(displacement[AXIS.Y], expected_y)
69
70    def test_get_reversed_motions_no_reversed(self):
71        list_x = (10, 22 ,36, 54, 100)
72        list_y = (1, 2 ,6, 10, 22)
73        self._call_get_reversed_motions(list_x, list_y, 0, 0, GV.TLBR)
74
75    def test_get_reversed_motions_reversed_x_y(self):
76        list_x = (10, 22 ,36, 154, 100)
77        list_y = (1, 2 ,6, 30, 22)
78        self._call_get_reversed_motions(list_x, list_y, -54, -8, GV.TLBR)
79
80    def _test_get_x_y(self, filename, slot, expected_value):
81        gesture_filename = self._get_filepath(filename)
82        mtb_packets = get_mtb_packets(gesture_filename)
83        list_x, list_y = mtb_packets.get_x_y(slot)
84        points = zip(list_x, list_y)
85        self.assertEqual(len(points), expected_value)
86
87    def test_get_x_y(self):
88        self._test_get_x_y('one_finger_with_slot_0.dat', 0, 12)
89        self._test_get_x_y('one_finger_without_slot_0.dat', 0, 9)
90        self._test_get_x_y('two_finger_with_slot_0.dat', 0, 121)
91        self._test_get_x_y('two_finger_with_slot_0.dat', 1, 59)
92        self._test_get_x_y('two_finger_without_slot_0.dat', 0, 104)
93        self._test_get_x_y('two_finger_without_slot_0.dat', 1, 10)
94
95    def test_get_pressure(self):
96        """Test get pressure"""
97        filename = 'one_finger_with_slot_0.dat'
98        gesture_filename = self._get_filepath(filename)
99        mtb_packets = get_mtb_packets(gesture_filename)
100        finger_paths = mtb_packets.get_ordered_finger_paths()
101
102        # There is only one tracking ID in the file.
103        self.assertEqual(len(finger_paths), 1)
104
105        # Verify some of the pressure values
106        finger_path = finger_paths.values()[0]
107        list_z = finger_path.get('pressure')
108        self.assertEqual(list_z[0:5], [59, 57, 56, 58, 60])
109
110    def test_get_x_y_multiple_slots(self):
111        filename = 'x_y_multiple_slots.dat'
112        filepath = self._get_filepath(filename)
113        mtb_packets = get_mtb_packets(filepath)
114        slots = (0, 1)
115        list_x, list_y = mtb_packets.get_x_y_multiple_slots(slots)
116        expected_list_x = {}
117        expected_list_y = {}
118        expected_list_x[0] = [1066, 1068, 1082, 1183, 1214, 1285, 1322, 1351,
119                              1377, 1391]
120        expected_list_y[0] = [561, 559, 542, 426, 405, 358, 328, 313, 304, 297]
121        expected_list_x[1] = [770, 769, 768, 758, 697, 620, 585, 565, 538, 538]
122        expected_list_y[1] = [894, 894, 895, 898, 927, 968, 996, 1003, 1013,
123                              1013]
124        for slot in slots:
125            self.assertEqual(list_x[slot], expected_list_x[slot])
126            self.assertEqual(list_y[slot], expected_list_y[slot])
127
128    def test_get_x_y_multiple_slots2(self):
129        """Test slot state machine.
130
131        When the last slot in the previous packet is slot 0, and the first
132        slot in the current packet is also slot 0, the slot 0 will not be
133        displayed explicitly. This test ensures that the slot stat machine
134        is tracked properly.
135        """
136        filename = 'pinch_to_zoom.zoom_in.dat'
137        filepath = self._get_filepath(filename)
138        mtb_packets = get_mtb_packets(filepath)
139        slots = (0, 1)
140        list_x, list_y = mtb_packets.get_x_y_multiple_slots(slots)
141        expected_final_x = {}
142        expected_final_y = {}
143        expected_final_x[0] = 1318
144        expected_final_y[0] = 255
145        expected_final_x[1] = 522
146        expected_final_y[1] = 1232
147        for slot in slots:
148            self.assertEqual(list_x[slot][-1], expected_final_x[slot])
149            self.assertEqual(list_y[slot][-1], expected_final_y[slot])
150
151    def _test_get_all_finger_paths_about_numbers_of_packets(
152            self, filename, expected_numbers):
153        mtb_packets = get_mtb_packets(self._get_filepath(filename))
154        finger_paths = mtb_packets.get_ordered_finger_paths()
155        for tid, expected_len in expected_numbers.items():
156            self.assertEqual(len(finger_paths[tid].tid_packets), expected_len)
157
158    def test_get_ordered_finger_paths_about_number_of_packets(self):
159        self._test_get_all_finger_paths_about_numbers_of_packets(
160                'two_finger_with_slot_0.dat', {2101: 122, 2102: 60})
161        self._test_get_all_finger_paths_about_numbers_of_packets(
162                'two_finger_without_slot_0.dat', {2097: 105, 2098: 11})
163
164    def test_data_ready(self):
165        """Test data_ready flag when point.x could be 0."""
166        filename = ('20130506_030025-fw_11.27-robot_sim/'
167                    'one_finger_to_edge.center_to_left.slow-lumpy-fw_11.27-'
168                    'robot_sim-20130506_031554.dat')
169        filepath = os.path.join(unittest_path_lumpy, filename)
170        mtb_packets = get_mtb_packets(filepath)
171        points = mtb_packets.get_ordered_finger_path(0, 'point')
172        # Note:
173        # 1. In the first packet, there exists the event ABS_PRESSURE
174        #    but no ABS_MT_PRESSURE.
175        # 2. The last packet with ABS_MT_TRACKING_ID = -1 is also counted.
176        self.assertEqual(len(points), 78)
177
178    def _test_drumroll(self, filename, expected_max_distance):
179        """expected_max_distance: unit in pixel"""
180        gesture_filename = self._get_filepath(filename)
181        mtb_packets = get_mtb_packets(gesture_filename)
182        actual_max_distance = mtb_packets.get_max_distance_of_all_tracking_ids()
183        self.assertTrue(about_eq(actual_max_distance, expected_max_distance))
184
185    def test_drumroll(self):
186        expected_max_distance = 52.0216301167
187        self._test_drumroll('drumroll_lumpy.dat', expected_max_distance)
188
189    def test_drumroll1(self):
190        expected_max_distance = 43.5660418216
191        self._test_drumroll('drumroll_lumpy_1.dat', expected_max_distance)
192
193    def test_drumroll_link(self):
194        expected_max_distance = 25.6124969497
195        self._test_drumroll('drumroll_link.dat', expected_max_distance)
196
197    def test_no_drumroll_link(self):
198        expected_max_distance = 2.91547594742
199        self._test_drumroll('no_drumroll_link.dat', expected_max_distance)
200
201    def test_no_drumroll_link(self):
202        expected_max_distance = 24.8243831746
203        self._test_drumroll('drumroll_link_2.dat', expected_max_distance)
204
205    def _test_finger_path(self, filename, tid, expected_slot, expected_data,
206                          request_data_ready=True):
207        """Test the data in a finger path"""
208        # Instantiate the expected finger_path
209        expected_finger_path = FingerPath(expected_slot,
210                                          [TidPacket(time, Point(*xy), z)
211                                          for time, xy, z in expected_data])
212
213        # Derive the actual finger_path for the specified tid
214        mtb_packets = get_mtb_packets(self._get_filepath(filename))
215        finger_paths = mtb_packets.get_ordered_finger_paths(request_data_ready)
216        actual_finger_path = finger_paths[tid]
217
218        # Assert that the packet lengths are the same.
219        self.assertEqual(len(expected_finger_path.tid_packets),
220                         len(actual_finger_path.tid_packets))
221
222        # Assert that all tid data (including syn_time, point, pressure, etc.)
223        # in the tid packets are the same.
224        for i in range(len(actual_finger_path.tid_packets)):
225            expected_packet = expected_finger_path.tid_packets[i]
226            actual_packet = actual_finger_path.tid_packets[i]
227            self.assertEqual(expected_packet.syn_time, actual_packet.syn_time)
228            self.assertTrue(expected_packet.point == actual_packet.point)
229            self.assertEqual(expected_packet.pressure, actual_packet.pressure)
230
231    def test_get_ordered_finger_paths(self):
232        """Test get_ordered_finger_paths
233
234        Tracking ID 95: slot 0 (no explicit slot 0 assigned).
235                        This is the only slot in the packet.
236        """
237        filename = 'drumroll_link_2.dat'
238        tid = 95
239        expected_slot = 0
240        expected_data = [# (syn_time,    (x,   y),   z)
241                         (238154.686034, (789, 358), 59),
242                         (238154.691606, (789, 358), 60),
243                         (238154.697058, (789, 358), 57),
244                         (238154.702576, (789, 358), 59),
245                         (238154.713731, (789, 358), 57),
246                         (238154.719160, (789, 359), 57),
247                         (238154.724791, (789, 359), 56),
248                         (238154.730111, (789, 359), 58),
249                         (238154.735588, (788, 359), 53),
250                         (238154.741068, (788, 360), 53),
251                         (238154.746569, (788, 360), 49),
252                         (238154.752108, (787, 360), 40),
253                         (238154.757705, (787, 361), 27),
254                         (238154.763075, (490, 903), 46),
255                         (238154.768532, (486, 892), 61),
256                         (238154.774695, (484, 895), 57),
257                         (238154.780192, (493, 890), 56),
258                         (238154.785651, (488, 893), 55),
259                         (238154.791140, (488, 893), 56),
260                         (238154.802080, (489, 893), 55),
261                         (238154.807578, (490, 893), 50),
262                         (238154.818573, (490, 893), 46),
263                         (238154.824066, (491, 893), 36),
264                         (238154.829525, (492, 893), 22),
265                         (238154.849958, (492, 893), 22),
266        ]
267        self._test_finger_path(filename, tid, expected_slot, expected_data)
268
269    def test_get_ordered_finger_paths2(self):
270        """Test get_ordered_finger_paths
271
272        Tracking ID 104: slot 0 (explicit slot 0 assigned).
273                         This is the 2nd slot in the packet.
274                         A slot 1 has already existed.
275        """
276
277        filename = 'drumroll_link_2.dat'
278        tid = 104
279        expected_slot = 0
280        expected_data = [# (syn_time,    (x,   y),   z)
281                         (238157.994296, (780, 373), 75),
282                         (238158.001110, (780, 372), 75),
283                         (238158.007128, (780, 372), 76),
284                         (238158.012617, (780, 372), 73),
285                         (238158.018112, (780, 373), 69),
286                         (238158.023600, (780, 373), 68),
287                         (238158.029542, (781, 373), 51),
288                         (238158.049605, (781, 373), 51),
289        ]
290        self._test_finger_path(filename, tid, expected_slot, expected_data)
291
292    def test_get_ordered_finger_paths2b(self):
293        """Test get_ordered_finger_paths
294
295        Tracking ID 103: slot 1 (explicit slot 1 assigned).
296                         This tracking ID overlaps with two distinct
297                         tracking IDs of which the slot is the same slot 0.
298                         This is a good test as a multiple-finger case.
299
300                         tid 102, slot 0 arrived
301                         tid 103, slot 1 arrived
302                         tid 102, slot 0 left
303                         tid 104, slot 0 arrived
304                         tid 103, slot 1 left
305                         tid 104, slot 0 left
306        """
307        filename = 'drumroll_link_2.dat'
308        tid = 103
309        expected_slot = 1
310        expected_data = [# (syn_time,    (x,   y),   z)
311                         (238157.906405, (527, 901), 71),
312                         (238157.911749, (527, 901), 74),
313                         (238157.917247, (527, 901), 73),
314                         (238157.923152, (527, 902), 71),
315                         (238157.928317, (527, 902), 72),
316                         (238157.934492, (527, 902), 71),
317                         (238157.939984, (527, 902), 69),
318                         (238157.945485, (527, 902), 65),
319                         (238157.950984, (527, 902), 66),
320                         (238157.956482, (527, 902), 70),
321                         (238157.961976, (527, 902), 65),
322                         (238157.973768, (527, 902), 64),
323                         (238157.980491, (528, 901), 61),
324                         (238157.987140, (529, 899), 60),
325                         (238157.994296, (531, 896), 52),
326                         (238158.001110, (534, 892), 34),
327                         (238158.007128, (534, 892), 34),
328                         (238158.012617, (534, 892), 34),
329                         (238158.018112, (534, 892), 34),
330                         (238158.023600, (534, 892), 34),
331                         (238158.029542, (534, 892), 34),
332        ]
333        self._test_finger_path(filename, tid, expected_slot, expected_data)
334
335    def test_get_ordered_finger_paths3(self):
336        """Test get_ordered_finger_paths
337
338        This is a good test sample.
339        - An unusual slot 9
340        - This is the 2nd slot in the packet. A slot 8 has already existed.
341        - Its ABS_MT_PRESSURE is missing in the first packet.
342        - Slot 8 terminates a few packets earlier than this slot.
343        - Some of the ABS_MT_POSITION_X/Y and ABS_MT_PRESSURE are not shown.
344        """
345        filename = 'drumroll_3.dat'
346        tid = 582
347        expected_slot = 9
348        expected_data = [# (syn_time,  (x,   y),   z)
349                         (6411.371613, (682, 173), None),
350                         (6411.382541, (667, 186), 35),
351                         (6411.393355, (664, 189), 37),
352                         (6411.404310, (664, 190), 38),
353                         (6411.413015, (664, 189), 38),
354                         (6411.422118, (665, 189), 38),
355                         (6411.430792, (665, 189), 37),
356                         (6411.439764, (667, 188), 36),
357                         (6411.448484, (675, 185), 29),
358                         (6411.457212, (683, 181), 17),
359                         (6411.465843, (693, 172), 5),
360                         (6411.474749, (469, 381), 6),
361                         (6411.483702, (471, 395), 26),
362                         (6411.492369, (471, 396), 13),
363                         (6411.499916, (471, 396), 13),
364        ]
365        self._test_finger_path(filename, tid, expected_slot, expected_data,
366                               request_data_ready=False)
367
368    def test_get_ordered_finger_paths4(self):
369        """Test get_ordered_finger_paths
370
371        This test is to verify if it could handle the case when a finger-off
372        event is followed immediately by a finger-on event in the same packet.
373        This situation may occur occasionally in two_close_fingers_tracking
374        gestures. Basically, this could be considered as a firmware bug.
375        However, our test should be able to handle the situation gracefully.
376
377        A problematic packet may look like:
378
379        Event: time .., type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value -1
380        Event: time .., type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 202
381        Event: time .., type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 1577
382        Event: time .., type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 1018
383        Event: time .., type 3 (EV_ABS), code 58 (ABS_MT_PRESSURE), value 99
384        Event: time .., type 3 (EV_ABS), code 48 (ABS_MT_TOUCH_MAJOR), value 19
385        Event: time .., type 3 (EV_ABS), code 49 (ABS_MT_TOUCH_MINOR), value 19
386        Event: time .., type 3 (EV_ABS), code 0 (ABS_X), value 1577
387        Event: time .., type 3 (EV_ABS), code 1 (ABS_Y), value 1018
388        Event: time .., type 3 (EV_ABS), code 24 (ABS_PRESSURE), value 99
389        Event: time .., -------------- SYN_REPORT ------------
390        """
391        # Get the actual finger_paths from the gesture data file.
392        filename = 'two_close_fingers_tracking.dat'
393        mtb_packets = get_mtb_packets(self._get_filepath(filename))
394        finger_paths = mtb_packets.get_ordered_finger_paths(
395                request_data_ready=False)
396
397        data_list = [
398                # (tid, packet_idx, syn_time, (x, y), z, number_packets)
399                (197, -1, 1395784288.323233, (1619, 1019), 98, 435),
400                (202, 0, 1395784288.323233, (1577, 1018), 99, 261),
401        ]
402
403        for tid, packet_idx, syn_time, xy, z, number_packets in data_list:
404            expected_packet = TidPacket(syn_time, Point(*xy), z)
405
406            # Derive the actual finger path and the actual packet.
407            actual_finger_path = finger_paths[tid]
408            actual_packet = actual_finger_path.tid_packets[packet_idx]
409
410            # Assert that the number of packets in the actual finger path
411            # is equal to the specified number.
412            self.assertEqual(number_packets,
413                             len(actual_finger_path.tid_packets))
414
415            # Assert that the expected packet is equal to the actual packet.
416            self.assertEqual(expected_packet.syn_time, actual_packet.syn_time)
417            self.assertTrue(expected_packet.point == actual_packet.point)
418            self.assertEqual(expected_packet.pressure, actual_packet.pressure)
419
420
421    def test_get_slot_data(self):
422        """Test if it can get the data from the correct slot.
423
424        slot 0 and slot 1 start at the same packet. This test verifies if the
425        method uses the correct corresponding slot numbers.
426        """
427        filename = 'two_finger_tracking.diagonal.slow.dat'
428        gesture_filename = self._get_filepath(filename)
429        mtb_packets = get_mtb_packets(gesture_filename)
430
431        # There are more packets. Use just a few of them to verify.
432        xy_pairs = {
433            # Slot 0
434            0: [(1142, 191), (1144, 201), (1144, 200)],
435            # Slot 1
436            1: [(957, 105), (966, 106), (960, 104)],
437        }
438
439        number_packets = {
440            # Slot 0
441            0: 190,
442            # Slot 1
443            1: 189,
444        }
445
446        slots = [0, 1]
447        for slot in slots:
448            points = mtb_packets.get_slot_data(slot, 'point')
449            # Verify the number of packets in each slot
450            self.assertEqual(len(points), number_packets[slot])
451            # Verify a few packets in each slot
452            for i, xy_pair in enumerate(xy_pairs[slot]):
453                self.assertTrue(Point(*xy_pair) == points[i])
454
455    def test_convert_to_evemu_format(self):
456        evemu_filename = self._get_filepath('one_finger_swipe.evemu.dat')
457        mtplot_filename = self._get_filepath('one_finger_swipe.dat')
458        packets = mtb.MtbParser().parse_file(mtplot_filename)
459        evemu_converted_iter = iter(mtb.convert_to_evemu_format(packets))
460        with open(evemu_filename) as evemuf:
461            for line_evemu_original in evemuf:
462                evemu_original = line_evemu_original.split()
463                evemu_converted_str = next(evemu_converted_iter, None)
464                self.assertNotEqual(evemu_converted_str, None)
465                if evemu_converted_str:
466                    evemu_converted = evemu_converted_str.split()
467                self.assertEqual(len(evemu_original), 5)
468                self.assertEqual(len(evemu_converted), 5)
469                # Skip the timestamps for they are different in both formats.
470                # Prefix, type, code, and value should be the same.
471                for i in [0, 2, 3, 4]:
472                    self.assertEqual(evemu_original[i], evemu_converted[i])
473
474    def test_get_largest_gap_ratio(self):
475        """Test get_largest_gap_ratio for one-finger and two-finger gestures."""
476        # The following files come with noticeable large gaps.
477        list_large_ratio = [
478            'one_finger_tracking.left_to_right.slow_1.dat',
479            'two_finger_gaps.vertical.dat',
480            'two_finger_gaps.horizontal.dat',
481            'resting_finger_2nd_finger_moving_segment_gaps.dat',
482            'gap_new_finger_arriving_or_departing.dat',
483            'one_stationary_finger_2nd_finger_moving_gaps.dat',
484            'resting_finger_2nd_finger_moving_gaps.dat',
485        ]
486        gesture_slots = {
487            'one_finger': [0,],
488            'two_finger': [0, 1],
489            'resting_finger': [1,],
490            'gap_new_finger': [0,],
491            'one_stationary_finger': [1,],
492        }
493
494        range_middle = self._get_range_middle(conf.no_gap_criteria)
495        gap_data_dir = self._get_filepath('gaps')
496        gap_data_filenames = glob.glob(os.path.join(gap_data_dir, '*.dat'))
497        for filename in gap_data_filenames:
498            mtb_packets = get_mtb_packets(filename)
499            base_filename = os.path.basename(filename)
500
501            # What slots to check are based on the gesture name.
502            slots = []
503            for gesture in gesture_slots:
504                if base_filename.startswith(gesture):
505                    slots = gesture_slots[gesture]
506                    break
507
508            for slot in slots:
509                largest_gap_ratio = mtb_packets.get_largest_gap_ratio(slot)
510                if base_filename in list_large_ratio:
511                    self.assertTrue(largest_gap_ratio >= range_middle)
512                else:
513                    self.assertTrue(largest_gap_ratio < range_middle)
514
515    def test_get_largest_accumulated_level_jumps(self):
516        """Test get_largest_accumulated_level_jumps."""
517        dir_level_jumps = 'drag_edge_thumb'
518
519        filenames = [
520            # filenames with level jumps
521            # ----------------------------------
522            'drag_edge_thumb.horizontal.dat',
523            'drag_edge_thumb.horizontal_2.dat',
524            # test no points in some tracking ID
525            'drag_edge_thumb.horizontal_3.no_points.dat',
526            'drag_edge_thumb.vertical.dat',
527            'drag_edge_thumb.vertical_2.dat',
528            'drag_edge_thumb.diagonal.dat',
529            # Change tracking IDs quickly.
530            'drag_edge_thumb.horizontal_4.change_ids_quickly.dat',
531
532            # filenames without level jumps
533            # ----------------------------------
534            'drag_edge_thumb.horizontal.curvy.dat',
535            'drag_edge_thumb.horizontal_2.curvy.dat',
536            'drag_edge_thumb.vertical.curvy.dat',
537            'drag_edge_thumb.vertical_2.curvy.dat',
538            # Rather small level jumps
539            'drag_edge_thumb.horizontal_5.small_level_jumps.curvy.dat',
540        ]
541
542        largest_level_jumps = {
543            # Large jumps
544            'drag_edge_thumb.horizontal.dat': {AXIS.X: 0, AXIS.Y: 97},
545            # Smaller jumps
546            'drag_edge_thumb.horizontal_2.dat': {AXIS.X: 0, AXIS.Y: 24},
547            # test no points in some tracking ID
548            'drag_edge_thumb.horizontal_3.no_points.dat':
549                    {AXIS.X: 97, AXIS.Y: 88},
550            # Change tracking IDs quickly.
551            'drag_edge_thumb.horizontal_4.change_ids_quickly.dat':
552                    {AXIS.X: 0, AXIS.Y: 14},
553            # Large jumps
554            'drag_edge_thumb.vertical.dat': {AXIS.X: 54, AXIS.Y: 0},
555            # The first slot 0 comes with smaller jumps only.
556            'drag_edge_thumb.vertical_2.dat': {AXIS.X: 20, AXIS.Y: 0},
557            # Large jumps
558            'drag_edge_thumb.diagonal.dat': {AXIS.X: 84, AXIS.Y: 58},
559        }
560
561        target_slot = 0
562        for filename in filenames:
563            filepath = self._get_filepath(filename, gesture_dir=dir_level_jumps)
564            packets = get_mtb_packets(filepath)
565            displacements = packets.get_displacements_for_slots(target_slot)
566
567            # There are no level jumps in a curvy line.
568            file_with_level_jump = 'curvy' not in filename
569
570            # Check the first slot only
571            tids = displacements.keys()
572            tids.sort()
573            tid = tids[0]
574            # Check both axis X and axis Y
575            for axis in AXIS.LIST:
576                disp = displacements[tid][axis]
577                jump = packets.get_largest_accumulated_level_jumps(disp)
578                # Verify that there are no jumps in curvy files, and
579                #        that there are jumps in the other files.
580                expected_jump = (0 if not file_with_level_jump
581                                   else largest_level_jumps[filename][axis])
582                self.assertTrue(jump == expected_jump)
583
584    def test_get_max_distance_from_points(self):
585        """Test get_max_distance_from_points"""
586        # Two farthest points: (15, 16) and (46, 70)
587        list_coordinates_pairs = [
588            (20, 25), (21, 35), (15, 16), (25, 22), (30, 32), (46, 70),
589            (35, 68), (42, 53), (50, 30), (43, 69), (16, 17), (14, 30),
590        ]
591        points = [Point(*pairs) for pairs in list_coordinates_pairs]
592        mtb_packets = mtb.Mtb(device=mocked_device[PLATFORM.LUMPY])
593
594        # Verify the max distance in pixels
595        max_distance_px = mtb_packets.get_max_distance_from_points(points,
596                                                                   UNIT.PIXEL)
597        expected_max_distance_px = ((46 - 15) ** 2 + (70 - 16) ** 2) ** 0.5
598        self.assertAlmostEqual(max_distance_px, expected_max_distance_px)
599
600        # Verify the max distance in mms
601        max_distance_mm = mtb_packets.get_max_distance_from_points(points,
602                                                                   UNIT.MM)
603        expected_max_distance_mm = (((46 - 15) / 12.0) ** 2 +
604                                    ((70 - 16) / 10.0) ** 2) ** 0.5
605        self.assertAlmostEqual(max_distance_mm, expected_max_distance_mm)
606
607    def _test_get_segments(self, list_t, list_coord, expected_segments, ratio):
608        """Test get_segments
609
610        @param expected_segments: a dictionary of
611                {segment_flag: expected_segment_indexes}
612        """
613        mtb_packets = mtb.Mtb(device=mocked_device[PLATFORM.LUMPY])
614        for segment_flag, (expected_segment_t, expected_segment_coord) in \
615                expected_segments.items():
616            segment_t, segment_coord = mtb_packets.get_segments(
617                    list_t, list_coord, segment_flag, ratio)
618            self.assertEqual(segment_t, expected_segment_t)
619            self.assertEqual(segment_coord, expected_segment_coord)
620
621    def test_get_segments_by_distance(self):
622        """Test get_segments_by_distance
623
624        In the test case below,
625            min_coord = 100
626            max_coord = 220
627            max_distance = max_coord - min_coord = 220 - 100 = 120
628            ratio = 0.1
629            120 * 0.1 = 12
630            begin segment: 100 ~ 112
631            end segment: 208 ~ 220
632        """
633        list_coord = [102, 101, 101, 100, 100, 103, 104, 110, 118, 120,
634                      122, 124, 131, 140, 150, 160, 190, 210, 217, 220]
635        list_t = [1000 + 0.012 * i for i in range(len(list_coord))]
636        ratio = 0.1
637        expected_segments= {
638                VAL.WHOLE: (list_t, list_coord),
639                VAL.MIDDLE: (list_t[8:17], list_coord[8:17]),
640                VAL.BEGIN: (list_t[:8], list_coord[:8]),
641                VAL.END: (list_t[17:], list_coord[17:]),
642        }
643        self._test_get_segments(list_t, list_coord, expected_segments, ratio)
644
645    def test_get_segments_by_length(self):
646        """Test get_segments_by_length"""
647        list_coords = [
648                [105, 105, 105, 105, 105, 105, 105, 105, 105, 105],
649                [104, 105, 105, 105, 105, 105, 105, 105, 105, 105],
650                [105, 105, 105, 105, 105, 105, 105, 105, 105, 106],
651        ]
652        ratio = 0.1
653        for list_c in list_coords:
654            list_t = [1000 + 0.012 * i for i in range(len(list_c))]
655            expected_segments= {
656                    VAL.WHOLE: (list_t, list_c),
657                    VAL.MIDDLE: (list_t[1:9], list_c[1:9]),
658                    VAL.BEGIN: (list_t[:1], list_c[:1]),
659                    VAL.END: (list_t[9:], list_c[9:]),
660            }
661            self._test_get_segments(list_t, list_c, expected_segments, ratio)
662
663
664if __name__ == '__main__':
665  unittest.main()
666