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