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