Home | History | Annotate | Download | only in tests
      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 
      6 """This module contains unit tests for the classes in the validators module."""
      7 
      8 import glob
      9 import os.path
     10 import unittest
     11 
     12 import common_unittest_utils
     13 import common_util
     14 import test_conf as conf
     15 import validators
     16 
     17 from common_unittest_utils import create_mocked_devices, parse_tests_data
     18 from firmware_constants import AXIS, GV, MTB, PLATFORM, VAL
     19 from firmware_log import MetricNameProps
     20 from geometry.elements import Point
     21 from touch_device import TouchDevice
     22 from validators import (CountPacketsValidator,
     23                         CountTrackingIDValidator,
     24                         DiscardInitialSecondsValidator,
     25                         DrumrollValidator,
     26                         HysteresisValidator,
     27                         LinearityValidator,
     28                         MtbSanityValidator,
     29                         NoGapValidator,
     30                         NoLevelJumpValidator,
     31                         NoReversedMotionValidator,
     32                         PhysicalClickValidator,
     33                         PinchValidator,
     34                         RangeValidator,
     35                         ReportRateValidator,
     36                         StationaryFingerValidator,
     37                         StationaryTapValidator,
     38 )
     39 
     40 
     41 unittest_path_lumpy = os.path.join(os.getcwd(), 'tests/logs/lumpy')
     42 mocked_device = create_mocked_devices()
     43 
     44 # Make short aliases for supported platforms
     45 alex = mocked_device[PLATFORM.ALEX]
     46 lumpy = mocked_device[PLATFORM.LUMPY]
     47 link = mocked_device[PLATFORM.LINK]
     48 # Some tests do not care what device is used.
     49 dontcare = 'dontcare'
     50 
     51 
     52 class CountTrackingIDValidatorTest(unittest.TestCase):
     53     """Unit tests for CountTrackingIDValidator class."""
     54 
     55     def _test_count_tracking_id(self, filename, criteria, device):
     56         packets = parse_tests_data(filename)
     57         validator = CountTrackingIDValidator(criteria, device=device)
     58         vlog = validator.check(packets)
     59         return vlog.score
     60 
     61     def test_two_finger_id_change(self):
     62         """Two two fingers id change.
     63 
     64         Issue 7867: Cyapa : Two finger scroll, tracking ids change
     65         """
     66         filename = 'two_finger_id_change.dat'
     67         score = self._test_count_tracking_id(filename, '== 2', lumpy)
     68         self.assertTrue(score == 0)
     69 
     70     def test_one_finger_fast_swipe_id_split(self):
     71         """One finger fast swipe resulting in IDs split.
     72 
     73         Issue: 7869: Lumpy: Tracking ID reassigned during quick-2F-swipe
     74         """
     75         filename = 'one_finger_fast_swipe_id_split.dat'
     76         score = self._test_count_tracking_id(filename, '== 1', lumpy)
     77         self.assertTrue(score == 0)
     78 
     79     def test_two_fingers_fast_flick_id_split(self):
     80         """Two figners fast flick resulting in IDs split.
     81 
     82         Issue: 7869: Lumpy: Tracking ID reassigned during quick-2F-swipe
     83         """
     84         filename = 'two_finger_fast_flick_id_split.dat'
     85         score = self._test_count_tracking_id(filename, '== 2', lumpy)
     86         self.assertTrue(score == 0)
     87 
     88 
     89 class DrumrollValidatorTest(unittest.TestCase):
     90     """Unit tests for DrumrollValidator class."""
     91 
     92     def setUp(self):
     93         self.criteria = conf.drumroll_criteria
     94 
     95     def _test_drumroll(self, filename, criteria, device):
     96         packets = parse_tests_data(filename)
     97         validator = DrumrollValidator(criteria, device=device)
     98         vlog = validator.check(packets)
     99         return vlog.score
    100 
    101     def _get_drumroll_metrics(self, filename, criteria, device):
    102         packets = parse_tests_data(filename, gesture_dir=unittest_path_lumpy)
    103         validator = DrumrollValidator(criteria, device=device)
    104         metrics = validator.check(packets).metrics
    105         return metrics
    106 
    107     def test_drumroll_lumpy(self):
    108         """Should catch the drumroll on lumpy.
    109 
    110         Issue 7809: Lumpy: Drumroll bug in firmware
    111         Max distance: 52.02 px
    112         """
    113         filename = 'drumroll_lumpy.dat'
    114         score = self._test_drumroll(filename, self.criteria, lumpy)
    115         self.assertTrue(score == 0)
    116 
    117     def test_drumroll_lumpy_1(self):
    118         """Should catch the drumroll on lumpy.
    119 
    120         Issue 7809: Lumpy: Drumroll bug in firmware
    121         Max distance: 43.57 px
    122         """
    123         filename = 'drumroll_lumpy_1.dat'
    124         score = self._test_drumroll(filename, self.criteria, lumpy)
    125         self.assertTrue(score <= 0.15)
    126 
    127     def test_no_drumroll_link(self):
    128         """Should pass (score == 1) when there is no drumroll.
    129 
    130         Issue 7809: Lumpy: Drumroll bug in firmware
    131         Max distance: 2.92 px
    132         """
    133         filename = 'no_drumroll_link.dat'
    134         score = self._test_drumroll(filename, self.criteria, link)
    135         self.assertTrue(score == 1)
    136 
    137     def test_drumroll_metrics(self):
    138         """Test the drumroll metrics."""
    139         expected_max_values = {
    140             '20130506_030025-fw_11.27-robot_sim/'
    141             'drumroll.fast-lumpy-fw_11.27-manual-20130528_044804.dat':
    142             2.29402908535,
    143 
    144             '20130506_030025-fw_11.27-robot_sim/'
    145             'drumroll.fast-lumpy-fw_11.27-manual-20130528_044820.dat':
    146             0.719567771497,
    147 
    148             '20130506_031746-fw_11.27-robot_sim/'
    149             'drumroll.fast-lumpy-fw_11.27-manual-20130528_044728.dat':
    150             0.833491481592,
    151 
    152             '20130506_032458-fw_11.23-robot_sim/'
    153             'drumroll.fast-lumpy-fw_11.23-manual-20130528_044856.dat':
    154             1.18368539364,
    155 
    156             '20130506_032458-fw_11.23-robot_sim/'
    157             'drumroll.fast-lumpy-fw_11.23-manual-20130528_044907.dat':
    158             0.851161282019,
    159 
    160             '20130506_032659-fw_11.23-robot_sim/'
    161             'drumroll.fast-lumpy-fw_11.23-manual-20130528_044933.dat':
    162             2.64245519251,
    163 
    164             '20130506_032659-fw_11.23-robot_sim/'
    165             'drumroll.fast-lumpy-fw_11.23-manual-20130528_044947.dat':
    166             0.910624022916,
    167         }
    168         criteria = self.criteria
    169         for filename, expected_max_value in expected_max_values.items():
    170             metrics = self._get_drumroll_metrics(filename, criteria, lumpy)
    171             actual_max_value = max([m.value for m in metrics])
    172             self.assertAlmostEqual(expected_max_value, actual_max_value)
    173 
    174 
    175 class LinearityValidatorTest(unittest.TestCase):
    176     """Unit tests for LinearityValidator class."""
    177 
    178     def setUp(self):
    179         self.validator = LinearityValidator(conf.linearity_criteria,
    180                                             device=lumpy, finger=0)
    181         self.validator.init_check()
    182 
    183     def test_simple_linear_regression0(self):
    184         """A perfect y-t line from bottom left to top right"""
    185         list_y = [20, 40, 60, 80, 100, 120, 140, 160]
    186         list_t = [i * 0.1 for i in range(len(list_y))]
    187         (max_err_px, rms_err_px) = self.validator._calc_errors_single_axis(
    188                 list_t, list_y)
    189         self.assertAlmostEqual(max_err_px, 0)
    190         self.assertAlmostEqual(rms_err_px, 0)
    191 
    192     def test_simple_linear_regression0b(self):
    193         """An imperfect y-t line from bottom left to top right with
    194         the first and the last entries as outliers.
    195 
    196         In this test case:
    197           begin segment = [1,]
    198           end segment = [188, 190]
    199           middle segment = [20, 40, 60, 80, 100, 120, 140, 160]
    200 
    201           the simple linear regression line is calculated based on the
    202           middle segment, and is
    203             y = 20 * t
    204           the error = [1, 0, 0, 0, 0, 0, 0, 0, 0, 8, 10]
    205         """
    206         list_y = [1, 20, 40, 60, 80, 100, 120, 140, 160, 188, 190]
    207         list_t = range(len(list_y))
    208 
    209         expected_errs_dict = {
    210             VAL.WHOLE: [1, 0, 0, 0, 0, 0, 0, 0, 0, 8, 10],
    211             VAL.BEGIN: [1, ],
    212             VAL.END: [8, 10],
    213             VAL.BOTH_ENDS: [1, 8, 10],
    214         }
    215 
    216         for segment_flag, expected_errs in expected_errs_dict.items():
    217             self.validator._segments= segment_flag
    218             (max_err, rms_err) = self.validator._calc_errors_single_axis(list_t,
    219                                                                          list_y)
    220             expected_max_err = max(expected_errs)
    221             expected_rms_err = (sum([i ** 2 for i in expected_errs]) /
    222                                 len(expected_errs)) ** 0.5
    223             self.assertAlmostEqual(max_err, expected_max_err)
    224             self.assertAlmostEqual(rms_err, expected_rms_err)
    225 
    226     def test_log_details_and_metrics(self):
    227         """Test the axes in _log_details_and_metrics"""
    228         # gesture_dir: tests/data/linearity
    229         gesture_dir = 'linearity'
    230         filenames_axes = {
    231             'two_finger_tracking.right_to_left.slow-lumpy-fw_11.27-robot-'
    232                 '20130227_204458.dat': [AXIS.X],
    233             'one_finger_to_edge.center_to_top.slow-lumpy-fw_11.27-robot-'
    234                 '20130227_203228.dat': [AXIS.Y],
    235             'two_finger_tracking.bottom_left_to_top_right.normal-lumpy-'
    236                 'fw_11.27-robot-20130227_204902.dat': [AXIS.X, AXIS.Y],
    237         }
    238         for filename, expected_axes in filenames_axes.items():
    239             packets = parse_tests_data(filename, gesture_dir=gesture_dir)
    240             # get the direction of the gesture
    241             direction = [filename.split('-')[0].split('.')[1]]
    242             self.validator.check(packets, direction)
    243             actual_axes = sorted(self.validator.list_coords.keys())
    244             self.assertEqual(actual_axes, expected_axes)
    245 
    246     def _test_simple_linear_regression1(self):
    247         """A y-t line taken from a real example.
    248 
    249         Refer to the "Numerical example" in the wiki page:
    250             http://en.wikipedia.org/wiki/Simple_linear_regression
    251         """
    252         list_t = [1.47, 1.50, 1.52, 1.55, 1.57, 1.60, 1.63, 1.65, 1.68, 1.70,
    253                   1.73, 1.75, 1.78, 1.80, 1.83]
    254         list_y = [52.21, 53.12, 54.48, 55.84, 57.20, 58.57, 59.93, 61.29,
    255                   63.11, 64.47, 66.28, 68.10, 69.92, 72.19, 74.46]
    256         expected_max_err = 1.3938545467809007
    257         expected_rms_err = 0.70666155991311708
    258         (max_err, rms_err) = self.validator._calc_errors_single_axis(
    259                 list_t, list_y)
    260         self.assertAlmostEqual(max_err, expected_max_err)
    261         self.assertAlmostEqual(rms_err, expected_rms_err)
    262 
    263 
    264 class NoGapValidatorTest(unittest.TestCase):
    265     """Unit tests for NoGapValidator class."""
    266     GAPS_SUBDIR = 'gaps'
    267 
    268     def setUp(self):
    269         self.criteria = conf.no_gap_criteria
    270 
    271     def _test_no_gap(self, filename, criteria, device, slot):
    272         file_subpath = os.path.join(self.GAPS_SUBDIR, filename)
    273         packets = parse_tests_data(file_subpath)
    274         validator = NoGapValidator(criteria, device=device, slot=slot)
    275         vlog = validator.check(packets)
    276         return vlog.score
    277 
    278     def test_two_finger_scroll_gaps(self):
    279         """Test that there are gaps in the two finger scroll gesture.
    280 
    281         Issue 7552: Cyapa : two finger scroll motion produces gaps in tracking
    282         """
    283         filename = 'two_finger_gaps.horizontal.dat'
    284         score0 = self._test_no_gap(filename, self.criteria, lumpy, 0)
    285         score1 = self._test_no_gap(filename, self.criteria, lumpy, 1)
    286         self.assertTrue(score0 <= 0.1)
    287         self.assertTrue(score1 <= 0.1)
    288 
    289     def test_gap_new_finger_arriving_or_departing(self):
    290         """Test gap when new finger arriving or departing.
    291 
    292         Issue: 8005: Cyapa : gaps appear when new finger arrives or departs
    293         """
    294         filename = 'gap_new_finger_arriving_or_departing.dat'
    295         score = self._test_no_gap(filename, self.criteria, lumpy, 0)
    296         self.assertTrue(score <= 0.3)
    297 
    298     def test_one_stationary_finger_2nd_finger_moving_gaps(self):
    299         """Test one stationary finger resulting in 2nd finger moving gaps."""
    300         filename = 'one_stationary_finger_2nd_finger_moving_gaps.dat'
    301         score = self._test_no_gap(filename, self.criteria, lumpy, 1)
    302         self.assertTrue(score <= 0.1)
    303 
    304     def test_resting_finger_2nd_finger_moving_gaps(self):
    305         """Test resting finger resulting in 2nd finger moving gaps.
    306 
    307         Issue 7648: Cyapa : Resting finger plus one finger move generates a gap
    308         """
    309         filename = 'resting_finger_2nd_finger_moving_gaps.dat'
    310         score = self._test_no_gap(filename, self.criteria, lumpy, 1)
    311         self.assertTrue(score <= 0.3)
    312 
    313 
    314 class PhysicalClickValidatorTest(unittest.TestCase):
    315     """Unit tests for PhysicalClickValidator class."""
    316 
    317     def setUp(self):
    318         self.device = lumpy
    319         self.criteria = '== 1'
    320         self.mnprops = MetricNameProps()
    321 
    322     def _test_physical_clicks(self, gesture_dir, files, expected_score):
    323         gesture_path = os.path.join(unittest_path_lumpy, gesture_dir)
    324         for filename, fingers in files.items():
    325             packets = parse_tests_data(os.path.join(gesture_path, filename))
    326             validator = PhysicalClickValidator(self.criteria,
    327                                                fingers=fingers,
    328                                                device=self.device)
    329             vlog = validator.check(packets)
    330             actual_score = vlog.score
    331             self.assertTrue(actual_score == expected_score)
    332 
    333     def test_physical_clicks_success(self):
    334         """All physcial click files in the gesture_dir should pass."""
    335         gesture_dir = '20130506_030025-fw_11.27-robot_sim'
    336         gesture_path = os.path.join(unittest_path_lumpy, gesture_dir)
    337 
    338         # Get all 1f physical click files.
    339         file_prefix = 'one_finger_physical_click'
    340         fingers = 1
    341         files1 = [(filepath, fingers) for filepath in glob.glob(
    342             os.path.join(gesture_path, file_prefix + '*.dat'))]
    343 
    344         # Get all 2f physical click files.
    345         file_prefix = 'two_fingers_physical_click'
    346         fingers = 2
    347         files2 = [(filepath, fingers) for filepath in glob.glob(
    348             os.path.join(gesture_path, file_prefix + '*.dat'))]
    349 
    350         # files is a dictionary of {filename: fingers}
    351         files = dict(files1 + files2)
    352         expected_score = 1.0
    353         self._test_physical_clicks(gesture_dir, files, expected_score)
    354 
    355     def test_physical_clicks_failure(self):
    356         """All physcial click files specified below should fail."""
    357         gesture_dir = '20130506_032458-fw_11.23-robot_sim'
    358         # files is a dictionary of {filename: fingers}
    359         files = {
    360             'one_finger_physical_click.bottom_side-lumpy-fw_11.23-complete-'
    361                 '20130614_065744.dat': 1,
    362             'one_finger_physical_click.center-lumpy-fw_11.23-complete-'
    363                 '20130614_065727.dat': 1,
    364             'two_fingers_physical_click-lumpy-fw_11.23-complete-'
    365                 '20130614_065757.dat': 2,
    366         }
    367         expected_score = 0.0
    368         self._test_physical_clicks(gesture_dir, files, expected_score)
    369 
    370     def test_physical_clicks_by_finger_IDs(self):
    371         """Test that some physical clicks may come with or without correct
    372         finger IDs.
    373         """
    374         # files is a dictionary of {
    375         #     filename: (number_fingers, (actual clicks, expected clicks))}
    376         files = {
    377                 # An incorrect case with 1 finger: the event sequence comprises
    378                 #   Event: ABS_MT_TRACKING_ID, value 284
    379                 #   Event: ABS_MT_TRACKING_ID, value -1
    380                 #   Event: BTN_LEFT, value 1
    381                 #   Event: BTN_LEFT, value 0
    382                 # In this case, the BTN_LEFT occurs when there is no finger.
    383                 '1f_click_incorrect_behind_tid.dat': (1, (0, 1)),
    384 
    385                 # A correct case with 1 finger: the event sequence comprises
    386                 #   Event: ABS_MT_TRACKING_ID, value 284
    387                 #   Event: BTN_LEFT, value 1
    388                 #   Event: ABS_MT_TRACKING_ID, value -1
    389                 #   Event: BTN_LEFT, value 0
    390                 # In this case, the BTN_LEFT occurs when there is no finger.
    391                 '1f_click.dat': (1, (1, 1)),
    392 
    393                 # An incorrect case with 2 fingers: the event sequence comprises
    394                 #   Event: ABS_MT_TRACKING_ID, value 18
    395                 #   Event: BTN_LEFT, value 1
    396                 #   Event: BTN_LEFT, value 0
    397                 #   Event: ABS_MT_TRACKING_ID, value 19
    398                 #   Event: ABS_MT_TRACKING_ID, value -1
    399                 #   Event: ABS_MT_TRACKING_ID, value -1
    400                 # In this case, the BTN_LEFT occurs when there is only 1 finger.
    401                 '2f_clicks_incorrect_before_2nd_tid.dat': (2, (0, 1)),
    402 
    403                 # An incorrect case with 2 fingers: the event sequence comprises
    404                 #   Event: ABS_MT_TRACKING_ID, value 18
    405                 #   Event: ABS_MT_TRACKING_ID, value 19
    406                 #   Event: ABS_MT_TRACKING_ID, value -1
    407                 #   Event: ABS_MT_TRACKING_ID, value -1
    408                 #   Event: BTN_LEFT, value 1
    409                 #   Event: BTN_LEFT, value 0
    410                 # In this case, the BTN_LEFT occurs when there is only 1 finger.
    411                 '2f_clicks_incorrect_behind_2_tids.dat': (2, (0, 1)),
    412 
    413                 # A correct case with 2 fingers: the event sequence comprises
    414                 #   Event: ABS_MT_TRACKING_ID, value 18
    415                 #   Event: ABS_MT_TRACKING_ID, value 19
    416                 #   Event: BTN_LEFT, value 1
    417                 #   Event: ABS_MT_TRACKING_ID, value -1
    418                 #   Event: ABS_MT_TRACKING_ID, value -1
    419                 #   Event: BTN_LEFT, value 0
    420                 # In this case, the BTN_LEFT occurs when there is only 1 finger.
    421                 '2f_clicks.dat': (2, (1, 1)),
    422         }
    423         for filename, (fingers, expected_value) in files.items():
    424             packets = parse_tests_data(filename)
    425             validator = PhysicalClickValidator(self.criteria, fingers=fingers,
    426                                                device=dontcare)
    427             vlog = validator.check(packets)
    428             metric_name = self.mnprops.CLICK_CHECK_TIDS.format(fingers)
    429             for metric in vlog.metrics:
    430                 if metric.name == metric_name:
    431                     self.assertEqual(metric.value, expected_value)
    432 
    433 
    434 class RangeValidatorTest(unittest.TestCase):
    435     """Unit tests for RangeValidator class."""
    436 
    437     def setUp(self):
    438         self.device = lumpy
    439 
    440     def _test_range(self, filename, expected_short_of_range_px):
    441         filepath = os.path.join(unittest_path_lumpy, filename)
    442         packets = parse_tests_data(filepath)
    443         validator = RangeValidator(conf.range_criteria, device=self.device)
    444 
    445         # Extract the gesture variation from the filename
    446         variation = (filename.split('/')[-1].split('.')[1],)
    447 
    448         # Determine the axis based on the direction in the gesture variation
    449         axis = (self.device.axis_x if validator.is_horizontal(variation)
    450                 else self.device.axis_y if validator.is_vertical(variation)
    451                 else None)
    452         self.assertTrue(axis is not None)
    453 
    454         # Convert from pixels to mms.
    455         expected_short_of_range_mm = self.device.pixel_to_mm_single_axis(
    456                 expected_short_of_range_px, axis)
    457 
    458         vlog = validator.check(packets, variation)
    459 
    460         # There is only one metric in the metrics list.
    461         self.assertEqual(len(vlog.metrics), 1)
    462         actual_short_of_range_mm = vlog.metrics[0].value
    463         self.assertEqual(actual_short_of_range_mm, expected_short_of_range_mm)
    464 
    465     def test_range(self):
    466         """All physical click files specified below should fail."""
    467         # files_px is a dictionary of {filename: short_of_range_px}
    468         files_px = {
    469             '20130506_030025-fw_11.27-robot_sim/'
    470             'one_finger_to_edge.center_to_left.slow-lumpy-fw_11.27-'
    471                 'robot_sim-20130506_031554.dat': 0,
    472 
    473             '20130506_030025-fw_11.27-robot_sim/'
    474             'one_finger_to_edge.center_to_left.slow-lumpy-fw_11.27-'
    475                 'robot_sim-20130506_031608.dat': 0,
    476 
    477             '20130506_032458-fw_11.23-robot_sim/'
    478             'one_finger_to_edge.center_to_left.slow-lumpy-fw_11.23-'
    479                 'robot_sim-20130506_032538.dat': 1,
    480 
    481             '20130506_032458-fw_11.23-robot_sim/'
    482             'one_finger_to_edge.center_to_left.slow-lumpy-fw_11.23-'
    483                 'robot_sim-20130506_032549.dat': 1,
    484         }
    485 
    486         for filename, short_of_range_px in files_px.items():
    487             self._test_range(filename, short_of_range_px)
    488 
    489 
    490 class StationaryFingerValidatorTest(unittest.TestCase):
    491     """Unit tests for StationaryFingerValidator class."""
    492 
    493     def setUp(self):
    494         self.criteria = conf.stationary_finger_criteria
    495 
    496     def _get_max_distance(self, filename, criteria, device):
    497         packets = parse_tests_data(filename)
    498         validator = StationaryFingerValidator(criteria, device=device)
    499         vlog = validator.check(packets)
    500         return vlog.metrics[0].value
    501 
    502     def test_stationary_finger_shift(self):
    503         """Test that the stationary shift due to 2nd finger tapping.
    504 
    505         Issue 7442: Cyapa : Second finger tap events influence stationary finger
    506         position
    507         """
    508         filename = 'stationary_finger_shift_with_2nd_finger_tap.dat'
    509         max_distance = self._get_max_distance(filename, self.criteria, lumpy)
    510         self.assertAlmostEqual(max_distance, 5.464430436926)
    511 
    512     def test_stationary_strongly_affected_by_2nd_moving_finger(self):
    513         """Test stationary finger strongly affected by 2nd moving finger with
    514         gaps.
    515 
    516         Issue 5812: [Cypress] reported positions of stationary finger strongly
    517         affected by nearby moving finger
    518         """
    519         filename = ('stationary_finger_strongly_affected_by_2nd_moving_finger_'
    520                     'with_gaps.dat')
    521         max_distance = self._get_max_distance(filename, self.criteria, lumpy)
    522         self.assertAlmostEqual(max_distance, 4.670861210146)
    523 
    524 
    525 class StationaryTapValidatorTest(unittest.TestCase):
    526     """Unit tests for StationaryTapValidator class."""
    527 
    528     def setUp(self):
    529         self.criteria = conf.stationary_tap_criteria
    530 
    531     def test_stationary_tap(self):
    532         filenames = {'1f_click.dat': 1.718284027744,
    533                      '1f_clickb.dat': 0.577590781705}
    534         for filename, expected_max_distance in filenames.items():
    535             packets = parse_tests_data(filename)
    536             validator = StationaryTapValidator(self.criteria, device=lumpy)
    537             vlog = validator.check(packets)
    538             actual_max_distance = vlog.metrics[0].value
    539             self.assertAlmostEqual(actual_max_distance, expected_max_distance)
    540 
    541 
    542 class NoLevelJumpValidatorTest(unittest.TestCase):
    543     """Unit tests for NoLevelJumpValidator class."""
    544 
    545     def setUp(self):
    546         self.criteria = conf.no_level_jump_criteria
    547         self.gesture_dir = 'drag_edge_thumb'
    548 
    549     def _get_score(self, filename, device):
    550         validator = NoLevelJumpValidator(self.criteria, device=device,
    551                                          slots=[0,])
    552         packets = parse_tests_data(filename, gesture_dir=self.gesture_dir)
    553         vlog = validator.check(packets)
    554         score = vlog.score
    555         return score
    556 
    557     def test_level_jumps(self):
    558         """Test files with level jumps."""
    559         filenames = [
    560             'drag_edge_thumb.horizontal.dat',
    561             'drag_edge_thumb.horizontal_2.dat',
    562             'drag_edge_thumb.horizontal_3.no_points.dat',
    563             'drag_edge_thumb.vertical.dat',
    564             'drag_edge_thumb.vertical_2.dat',
    565             'drag_edge_thumb.diagonal.dat',
    566         ]
    567         for filename in filenames:
    568             self.assertTrue(self._get_score(filename, lumpy) <= 0.6)
    569 
    570     def test_no_level_jumps(self):
    571         """Test files without level jumps."""
    572         filenames = [
    573             'drag_edge_thumb.horizontal.curvy.dat',
    574             'drag_edge_thumb.horizontal_2.curvy.dat',
    575             'drag_edge_thumb.vertical.curvy.dat',
    576             'drag_edge_thumb.vertical_2.curvy.dat',
    577         ]
    578         for filename in filenames:
    579             self.assertTrue(self._get_score(filename, lumpy) == 1.0)
    580 
    581 
    582 class ReportRateValidatorTest(unittest.TestCase):
    583     """Unit tests for ReportRateValidator class."""
    584     def setUp(self):
    585         self.criteria = '>= 60'
    586 
    587     def _get_score(self, filename, device):
    588         validator = ReportRateValidator(self.criteria, device=device,
    589                                         chop_off_pauses=False)
    590         packets = parse_tests_data(filename)
    591         vlog = validator.check(packets)
    592         score = vlog.score
    593         return score
    594 
    595     def test_report_rate_scores(self):
    596         """Test the score of the report rate."""
    597         filename = '2f_scroll_diagonal.dat'
    598         self.assertTrue(self._get_score(filename, device=lumpy) <= 0.5)
    599 
    600         filename = 'one_finger_with_slot_0.dat'
    601         self.assertTrue(self._get_score(filename, device=lumpy) >= 0.9)
    602 
    603         filename = 'two_close_fingers_merging_changed_ids_gaps.dat'
    604         self.assertTrue(self._get_score(filename, device=lumpy) <= 0.5)
    605 
    606     def test_report_rate_without_slot(self):
    607         """Test report rate without specifying any slot."""
    608         filename_report_rate_pair = [
    609             ('2f_scroll_diagonal.dat', 40.31),
    610             ('one_finger_with_slot_0.dat', 148.65),
    611             ('two_close_fingers_merging_changed_ids_gaps.dat', 53.12),
    612         ]
    613         for filename, expected_report_rate in filename_report_rate_pair:
    614             validator = ReportRateValidator(self.criteria, device=dontcare,
    615                                             chop_off_pauses=False)
    616             validator.check(parse_tests_data(filename))
    617             actual_report_rate = round(validator.report_rate, 2)
    618             self.assertAlmostEqual(actual_report_rate, expected_report_rate)
    619 
    620     def test_report_rate_with_slot(self):
    621         """Test report rate with slot=1"""
    622         # Compute actual_report_rate
    623         filename = ('stationary_finger_strongly_affected_by_2nd_moving_finger_'
    624                     'with_gaps.dat')
    625         validator = ReportRateValidator(self.criteria, device=dontcare,
    626                                         finger=1, chop_off_pauses=False)
    627         validator.check(parse_tests_data(filename))
    628         actual_report_rate = validator.report_rate
    629         # Compute expected_report_rate
    630         first_syn_time = 2597.682925
    631         last_syn_time = 2604.543335
    632         num_packets = 592 - 1
    633         expected_report_rate = num_packets / (last_syn_time - first_syn_time)
    634         self.assertAlmostEqual(actual_report_rate, expected_report_rate)
    635 
    636     def _test_report_rate_metrics(self, filename, expected_values):
    637         packets = parse_tests_data(filename)
    638         validator = ReportRateValidator(self.criteria, device=lumpy,
    639                                         chop_off_pauses=False)
    640         vlog = validator.check(packets)
    641 
    642         # Verify that there are 3 metrics
    643         number_metrics = 3
    644         self.assertEqual(len(vlog.metrics), number_metrics)
    645 
    646         # Verify the values of the 3 metrics.
    647         for i in range(number_metrics):
    648             actual_value = vlog.metrics[i].value
    649             if isinstance(actual_value, tuple):
    650                 self.assertEqual(actual_value, expected_values[i])
    651             else:
    652                 self.assertAlmostEqual(actual_value, expected_values[i])
    653 
    654     def test_report_rate_metrics(self):
    655         """Test the metrics of the report rates."""
    656         # files is a dictionary of
    657         #       {filename: ((# long_intervals, # all intervals),
    658         #                    ave_interval, max_interval)}
    659         files = {
    660             '2f_scroll_diagonal.dat':
    661                 ((33, 33), 24.8057272727954, 26.26600000075996),
    662             'one_finger_with_slot_0.dat':
    663                 ((1, 12), 6.727166666678386, 20.411999998032115),
    664             'two_close_fingers_merging_changed_ids_gaps.dat':
    665                 ((13, 58), 18.82680942272318, 40.936946868896484),
    666         }
    667 
    668         for filename, values in files.items():
    669             self._test_report_rate_metrics(filename, values)
    670 
    671     def _test_chop_off_both_ends(self, xy_pairs, distance, expected_middle):
    672         """Verify if the actual middle is equal to the expected middle."""
    673         points = [Point(*xy) for xy in xy_pairs]
    674         validator = ReportRateValidator(self.criteria, device=dontcare)
    675         actual_middle = validator._chop_off_both_ends(points, distance)
    676         self.assertEqual(actual_middle, expected_middle)
    677 
    678     def test_chop_off_both_ends0(self):
    679         """Test chop_off_both_ends() with distinct distances."""
    680         xy_pairs = [
    681                 # pauses
    682                 (100, 20), (100, 21), (101, 22), (102, 24), (103, 26),
    683                 # moving segment
    684                 (120, 30), (122, 29), (123, 32), (123, 33), (126, 35),
    685                 (126, 32), (142, 29), (148, 30), (159, 31), (162, 30),
    686                 (170, 32), (183, 32), (194, 32), (205, 32), (208, 32),
    687                 # pauses
    688                 (230, 30), (231, 31), (232, 30), (231, 30), (230, 30),
    689         ]
    690 
    691         distance = 20
    692         expected_begin_index = 5
    693         expected_end_index = 19
    694         expected_middle = [expected_begin_index, expected_end_index]
    695         self._test_chop_off_both_ends(xy_pairs, distance, expected_middle)
    696 
    697         distance = 0
    698         expected_begin_index = 0
    699         expected_end_index = len(xy_pairs) - 1
    700         expected_middle = [expected_begin_index, expected_end_index]
    701         self._test_chop_off_both_ends(xy_pairs, distance, expected_middle)
    702 
    703     def test_chop_off_both_ends1(self):
    704         """Test chop_off_both_ends() with some corner cases"""
    705         distance = 20
    706         xy_pairs = [(120, 50), (120, 50)]
    707         expected_middle = None
    708         self._test_chop_off_both_ends(xy_pairs, distance, expected_middle)
    709 
    710         xy_pairs = [(120, 50), (150, 52), (200, 51)]
    711         expected_middle = [1, 1]
    712         self._test_chop_off_both_ends(xy_pairs, distance, expected_middle)
    713 
    714         xy_pairs = [(120, 50), (120, 51), (200, 52), (200, 51)]
    715         expected_middle = None
    716         self._test_chop_off_both_ends(xy_pairs, distance, expected_middle)
    717 
    718 
    719 class HysteresisValidatorTest(unittest.TestCase):
    720     """Unit tests for HysteresisValidator class."""
    721 
    722     def setUp(self):
    723         self.criteria = conf.hysteresis_criteria
    724 
    725     def test_hysteresis(self):
    726         """Test that the hysteresis causes an initial jump."""
    727         filenames = {'center_to_right_normal_link.dat': 4.6043458,
    728                      'center_to_right_slow_link.dat': 16.8671278}
    729 
    730         for filename, expected_value in filenames.items():
    731             packets = parse_tests_data(filename)
    732             validator = HysteresisValidator(self.criteria, device=link)
    733             vlog = validator.check(packets)
    734             self.assertAlmostEqual(vlog.metrics[0].value, expected_value)
    735 
    736     def test_click_data(self):
    737         """Test that the validator handles None distances well.
    738 
    739         In this test, distance1 = None and distance2 = None.
    740         This results in ratio = infinity. There should be no error incurred.
    741         """
    742         packets = parse_tests_data('2f_clicks_test_hysteresis.dat')
    743         validator = HysteresisValidator(self.criteria, device=link)
    744         vlog = validator.check(packets)
    745         self.assertEqual(vlog.metrics[0].value, float('infinity'))
    746 
    747 
    748 class MtbSanityValidatorTest(unittest.TestCase):
    749     """Unit tests for MtbSanityValidator class."""
    750 
    751     def setUp(self):
    752         import fake_input_device
    753         self.fake_device_info = fake_input_device.FakeInputDevice()
    754 
    755     def _get_number_errors(self, filename):
    756         packets = parse_tests_data(filename)
    757         validator = MtbSanityValidator(device=link,
    758                                        device_info=self.fake_device_info)
    759         vlog = validator.check(packets)
    760         number_errors, _ = vlog.metrics[1].value
    761         return number_errors
    762 
    763     def test_sanity_found_errors(self):
    764         """Test that the tracking id is set to -1 before being assigned a
    765         positive value.
    766         """
    767         filenames = ['finger_crossing.top_right_to_bottom_left.slow.dat',
    768                      'two_finger_tap.vertical.dat']
    769         for filename in filenames:
    770             number_errors = self._get_number_errors(filename)
    771             self.assertTrue(number_errors > 0)
    772 
    773     def test_sanity_pass(self):
    774         """Test that the MTB format is correct."""
    775         filenames = ['2f_scroll_diagonal.dat',
    776                      'drumroll_lumpy.dat']
    777         for filename in filenames:
    778             number_errors = self._get_number_errors(filename)
    779             self.assertTrue(number_errors == 0)
    780 
    781 
    782 class DiscardInitialSecondsValidatorTest(unittest.TestCase):
    783     """Unit tests for DiscardInitialSecondsValidator class."""
    784 
    785     def setUp(self):
    786         import fake_input_device
    787         self.fake_device_info = fake_input_device.FakeInputDevice()
    788 
    789     def _get_score(self, filename, criteria_str):
    790         packets = parse_tests_data(filename)
    791         validator = DiscardInitialSecondsValidator(
    792             validator=CountTrackingIDValidator(criteria_str, device=link),
    793             device=link)
    794         vlog = validator.check(packets)
    795         return vlog.score
    796 
    797     def test_single_finger_hold(self):
    798         """Test that the state machine reads one finger if
    799         only one finger was held for over a second."""
    800 
    801         filename = 'one_finger_long_hold.dat'
    802         score = self._get_score(filename, '== 1')
    803         self.assertTrue(score == 1)
    804 
    805     def test_double_finger_hold(self):
    806         """Test that the state machine reads two fingers if
    807         two fingers were held for over a second."""
    808 
    809         filename = 'two_finger_long_hold.dat'
    810         score = self._get_score(filename, '== 2')
    811         self.assertTrue(score == 1)
    812 
    813     def test_double_tap_single_hold(self):
    814         """Test that the validator discards the initial double tap and only
    815         validates on the single finger long hold at the end.
    816         """
    817 
    818         filename = 'two_finger_tap_one_finger_hold.dat'
    819         score = self._get_score(filename, '== 1')
    820         self.assertTrue(score == 1)
    821 
    822     def test_discard_initial_seconds(self):
    823         """Test that discard_initial_seconds() cuts at the proper packet.
    824 
    825         Note: to print the final_state_packet, use the following statements:
    826             import mtb
    827             print mtb.make_pretty_packet(final_state_packet)
    828         """
    829         packets = parse_tests_data('noise_stationary_extended.dat')
    830         validator = DiscardInitialSecondsValidator(
    831             validator=CountTrackingIDValidator('== 1', device=link),
    832             device=link)
    833         validator.init_check(packets)
    834         packets = validator._discard_initial_seconds(packets, 1)
    835         final_state_packet = packets[0]
    836 
    837         self.assertTrue(len(final_state_packet) == 11)
    838         # Assert the correctness of the 1st finger data in the order of
    839         #     SLOT, TRACKING_ID, POSITION_X, POSITION_Y, PRESSURE
    840         self.assertTrue(final_state_packet[0][MTB.EV_VALUE] == 2)
    841         self.assertTrue(final_state_packet[1][MTB.EV_VALUE] == 2427)
    842         self.assertTrue(final_state_packet[2][MTB.EV_VALUE] == 670)
    843         self.assertTrue(final_state_packet[3][MTB.EV_VALUE] == 361)
    844         self.assertTrue(final_state_packet[4][MTB.EV_VALUE] == 26)
    845         # Assert the correctness of the 2nd finger data in the order of
    846         #     SLOT, TRACKING_ID, POSITION_X, POSITION_Y, PRESSURE
    847         self.assertTrue(final_state_packet[5][MTB.EV_VALUE] == 3)
    848         self.assertTrue(final_state_packet[6][MTB.EV_VALUE] == 2426)
    849         self.assertTrue(final_state_packet[7][MTB.EV_VALUE] == 670)
    850         self.assertTrue(final_state_packet[8][MTB.EV_VALUE] == 368)
    851         self.assertTrue(final_state_packet[9][MTB.EV_VALUE] == 21)
    852         # EVENT TIME
    853         self.assertTrue(final_state_packet[0][MTB.EV_TIME] == 1412021965.723953)
    854 
    855     def test_get_snapshot_after_discarding_init_packets(self):
    856         """Test that get_snapshot() handles non-ready packet properly
    857         after discard_initial_seconds(). A non-ready packet is one that
    858         the attributes such as X, Y, and Z are not all ready.
    859         """
    860         packets = parse_tests_data('non_ready_events_in_final_state_packet.dat')
    861         validator = DiscardInitialSecondsValidator(
    862             validator=CountTrackingIDValidator('== 1', device=link),
    863             device=link)
    864         validator.init_check(packets)
    865         packets = validator._discard_initial_seconds(packets, 1)
    866         final_state_packet = packets[0]
    867 
    868         self.assertTrue(len(final_state_packet) == 4)
    869         # Assert the correctness of the finger data in the order of
    870         #     SLOT, TRACKING_ID, and POSITION_Y
    871         self.assertTrue(final_state_packet[0][MTB.EV_VALUE] == 0)
    872         self.assertTrue(final_state_packet[1][MTB.EV_VALUE] == 102)
    873         self.assertTrue(final_state_packet[2][MTB.EV_VALUE] == 1316)
    874         # EVENT TIME
    875         self.assertTrue(final_state_packet[0][MTB.EV_TIME] == 1412888977.716634)
    876 
    877     def test_noise_line_with_all_fingers_left(self):
    878         """In this test case, all fingers left. The final_state_packet is []."""
    879         packets=parse_tests_data('noise_line.dat')
    880         validator = DiscardInitialSecondsValidator(ReportRateValidator('>= 60'))
    881         validator.init_check(packets)
    882         packets = validator._discard_initial_seconds(packets, 1)
    883         validator.validator.init_check(packets)
    884         list_syn_time = validator.validator.packets.get_list_syn_time([])
    885         self.assertEqual(len(packets), 84)
    886         self.assertEqual(len(list_syn_time), 84)
    887 
    888 
    889 if __name__ == '__main__':
    890   unittest.main()
    891