| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import copy |
| import os |
| import re |
| import subprocess |
| import threading |
| import time |
| from autotest_lib.client.bin import test |
| from autotest_lib.client.common_lib import error |
| |
| POSITION_X_VALUATOR = '0' |
| POSITION_Y_VALUATOR = '1' |
| POSITION_VALUATORS = [POSITION_X_VALUATOR, POSITION_Y_VALUATOR] |
| PROPERTY_EVENT = 'PropertyEvent' |
| TOUCH_BEGIN_EVENT = 'RawTouchBegin' |
| TOUCH_END_EVENT = 'RawTouchEnd' |
| TOUCH_UPDATE_EVENT = 'RawTouchUpdate' |
| TOUCH_EVENTS = [TOUCH_BEGIN_EVENT, TOUCH_END_EVENT, TOUCH_UPDATE_EVENT] |
| |
| |
| class platform_EvdevSynDropTest(test.test): |
| """Test SYN_DROPPED event handling in xf86-input-evdev. |
| |
| When there is a SYN_DROPPED seen in xf86-input-evdev driver, it tries to |
| sync with the kernel evdev state and injects finger arriving, finger |
| leaving or axes update events accordingly to make sure upper layer |
| (such as X server) is seamless of SYN_DROPPED event. The test mainly |
| focuses on testing finger arriving and leaving events generated in |
| SYN_DROPPED handling. |
| |
| Currently we have four basic SYN_DROPPED tests to be performed : |
| a. Finger leaving, i.e., tracking id changes from a non-negative |
| number to -1 after SYN_DROPPED. |
| b. Finger arriving, i.e., tracking id changes from -1 to a |
| non-negative number. |
| c. Finger changing, i.e., original finger leaving and new finger |
| arriving, tracking id changes from a non-negative number to |
| another one. |
| d. Same finger, but axes change, i.e., no tracking id changes, but some |
| axes values have changed. |
| |
| For each test case, a pre-recorded event log is chopped into four |
| pieces: |
| |
| 1. original finger set |
| 2. finger events(leaving/arriving) to be dropped due to queue full |
| 3. events playbacked to push xinput events out |
| 4. cleanup events |
| |
| After playing-back the first piece, we block the evdev event reading in |
| order to generate SYN_DROPPED when we inject the second piece of events. |
| Then, we unblock the evdev event reading and inject the third piece of |
| events to collect the finger leaving/arriving XI2 events. These XI2 |
| events could be used to verify if SYN_DROPPED handling is processed |
| correctly. |
| """ |
| version = 1 |
| |
| def setUp(self): |
| self.stop_parser = False |
| self.syndrop_detected = False |
| self.stop_xi2_parser = False |
| self.finger_set = {} |
| |
| os.environ['DISPLAY'] = ':0' |
| os.environ['XAUTHORITY'] = '/home/chronos/.Xauthority' |
| self.data_dir = os.path.join(self.bindir, 'data') |
| device_file = os.path.join(self.data_dir, 'device') |
| self.device_ready = threading.Event() |
| self.dev_emu_proc = subprocess.Popen(['evemu-device', device_file], |
| stdout=subprocess.PIPE) |
| self.device_id = self.get_evemu_device_id(self.dev_emu_proc) |
| self.device = '/dev/input/event' + self.device_id |
| |
| # Xorg log parser for detection of SYN_DROPPED |
| self.xlog_parser_thread = threading.Thread(target=self.xlog_parser) |
| self.xlog_parser_thread.start() |
| |
| # Wait until the device is available in X server |
| self.device_ready.wait() |
| |
| # xinput 2 event parser for finger tracking above xf86-input-evdev |
| self.xi2_parser_ready = threading.Event() |
| self.xi2_proc = subprocess.Popen(['stdbuf', '-o0', 'xinput', 'test-xi2', |
| self.device_id], |
| stdout=subprocess.PIPE) |
| self.xi2_parser_thread = threading.Thread(target=self.xi2_parser, |
| args=(self.xi2_proc,)) |
| self.xi2_parser_thread.start() |
| |
| # Wait until XI2 parser is ready |
| self.xi2_parser_ready.wait() |
| |
| def tearDown(self): |
| self.dev_emu_proc.kill() |
| self.xi2_proc.kill() |
| self.stop_parser = True |
| self.xlog_parser_thread.join() |
| self.stop_xi2_parser = True |
| self.xi2_parser_thread.join() |
| |
| def xi2_parser(self, proc): |
| event_type = 0 |
| self.position = {} |
| while not self.stop_xi2_parser: |
| line = proc.stdout.readline() |
| if not self.xi2_parser_ready.is_set(): |
| if re.search('Reporting', line): |
| self.xi2_parser_ready.set() |
| # EVENT type 23 (RawTouchUpdate) |
| type_line = re.match('EVENT\stype\s\d+\s\((\w+)\)', line) |
| if type_line: |
| event_type = type_line.group(1) |
| if event_type == PROPERTY_EVENT: |
| self.finger_set = {} |
| if event_type in TOUCH_EVENTS: |
| detail = re.match('\s+detail:\s(\d+)', line) |
| if detail: |
| finger_id = detail.group(1) |
| if event_type not in self.finger_set.keys(): |
| self.finger_set[event_type] = [] |
| if finger_id not in self.finger_set[event_type]: |
| self.finger_set[event_type].append(finger_id) |
| # For valuators |
| # 0: 1902.00 (1902.00) |
| valuator = re.match('\s+(\d+):\s(\d+.\d+)\s+\(\d+.\d+\)', line) |
| if valuator and valuator.group(1) in POSITION_VALUATORS: |
| self.position[valuator.group(1)] = valuator.group(2) |
| |
| def xlog_parser(self): |
| log = open('/var/log/Xorg.0.log', 'r') |
| log.seek(0, 2) |
| while not self.stop_parser: |
| line = log.readline() |
| if not line: |
| time.sleep(0.001) |
| continue |
| if not self.device_ready.is_set(): |
| if re.search('ChromeOS-MT-Device:\sSync_State:', line): |
| self.device_ready.set() |
| if re.match('[^\+]+\++\sSYN_DROPPED\s\++', line): |
| self.syndrop_detected = True |
| |
| def get_evemu_device_id(self, proc): |
| output = proc.stdout.readline() |
| match = re.match('[^ ]+\s\/dev\/input\/event(\d+)', output) |
| return match.group(1) |
| |
| def test_condition(self, condition=None): |
| # Inject the original finger events |
| step1 = open(os.path.join(self.data_dir, condition + '.1'), 'r') |
| subprocess.call(['evemu-play', self.device], stdin=step1) |
| |
| self.original_set = copy.deepcopy(self.finger_set) |
| |
| # Blocking xf86-input-evdev event reading |
| step2 = open(os.path.join(self.data_dir, condition + '.2'), 'r') |
| subprocess.call(['xinput', 'set-prop', self.device_id, |
| 'Block Event Reading', '1']) |
| |
| # Inject events to fill up the evdev queue to generate SYN_DROPPED |
| subprocess.call(['evemu-play', self.device], stdin=step2) |
| |
| # Unblock xf86-input-evdev event processing |
| step3 = open(os.path.join(self.data_dir, condition + '.3'), 'r') |
| subprocess.call(['xinput', 'set-prop', self.device_id, |
| 'Block Event Reading', '0']) |
| subprocess.call(['evemu-play', self.device], stdin=step3) |
| |
| # Check if SYN_DROPPED event is seen in Xorg.0.log |
| if not self.syndrop_detected: |
| raise error.TestError('Did not see SYN_DROPPED event') |
| |
| # Store the finger event seen in xinput after SYN_DROPPED |
| self.syndrop_set = copy.deepcopy(self.finger_set) |
| |
| # Store the latest position valuators especially for test case (d) |
| self.syndrop_position = copy.deepcopy(self.position) |
| |
| # Continue on injecting remaining events for cleanup |
| step4 = open(os.path.join(self.data_dir, condition + '.4'), 'r') |
| subprocess.call(['evemu-play', self.device], stdin=step4) |
| |
| def verify_finger_leaving(self): |
| """Verify if finger leaving event is injected after SYN_DROPPED. |
| |
| Suppose there are two fingers with tracking id '1' and '2' in the |
| beginning. Finger '2' leaves after SYN_DROPPED, then we should get |
| the finger sets in xi2_parser: |
| original_set: |
| {'RawTouchBegin': ['1', '2'], 'RawTouchUpdate': ['1', '2']} |
| syndrop_set: |
| {'RawTouchEnd': ['2'], 'RawTouchUpdate': ['1']} |
| """ |
| syndrop_end_set = set(self.syndrop_set[TOUCH_END_EVENT]) |
| syndrop_update_set = set(self.syndrop_set[TOUCH_UPDATE_EVENT]) |
| union = syndrop_end_set | syndrop_update_set |
| intersection = syndrop_end_set & syndrop_update_set |
| if union != set(self.original_set[TOUCH_BEGIN_EVENT]): |
| raise error.TestFail('The union should include both fingers') |
| if intersection: |
| raise error.TestFail('The intersection of the set should be empty') |
| |
| def verify_finger_arriving(self): |
| """Verify if finger arriving event is injected after SYN_DROPPED. |
| |
| Suppose there is only one finger with tracking id '3' in the |
| beginning. Finger '4' appears after SYN_DROPPED, then we should get |
| the finger sets in xi2_parser: |
| original_set: |
| {'RawTouchBegin': ['3'], 'RawTouchUpdate': ['3']} |
| syndrop_set: |
| {'RawTouchBegin': ['4'], 'RawTouchUpdate': ['3', '4']} |
| """ |
| original_begin_set = set(self.original_set[TOUCH_BEGIN_EVENT]) |
| syndrop_begin_set = set(self.syndrop_set[TOUCH_BEGIN_EVENT]) |
| union = original_begin_set | syndrop_begin_set |
| intersection = original_begin_set & syndrop_begin_set |
| if union != set(self.syndrop_set[TOUCH_UPDATE_EVENT]): |
| raise error.TestFail('The union should include both fingers') |
| if intersection: |
| raise error.TestFail('The intersection of the set should be empty') |
| |
| def verify_finger_changing(self): |
| """Verify if finger changing events are injected after SYN_DROPPED. |
| |
| Suppose there are two fingers with tracking id '5' and '6' in the |
| beginning. Finger '6' leaves and Finger '7' appears after |
| SYN_DROPPED, then we should get the finger sets in xi2_parser: |
| original_set: |
| {'RawTouchBegin': ['5', '6'], 'RawTouchUpdate': ['5', '6']} |
| syndrop_set: |
| {'RawTouchEnd': ['6'], 'RawTouchBegin': ['7'], |
| 'RawTouchUpdate': ['5', '7']} |
| """ |
| original_update_set = set(self.original_set[TOUCH_UPDATE_EVENT]) |
| syndrop_update_set = set(self.syndrop_set[TOUCH_UPDATE_EVENT]) |
| finger_leaving = original_update_set - syndrop_update_set |
| if not finger_leaving: |
| raise error.TestFail('One original finger should disappear') |
| if finger_leaving != set(self.syndrop_set[TOUCH_END_EVENT]): |
| raise error.TestFail('Should see finger leaving event') |
| |
| finger_arriving = syndrop_update_set - original_update_set |
| if not finger_arriving: |
| raise error.TestFail('One new finger should arrive') |
| if finger_arriving != set(self.syndrop_set[TOUCH_BEGIN_EVENT]): |
| raise error.TestFail('Should see finger arriving event') |
| |
| def verify_same_finger(self): |
| """Verify if finger position are updated after SYN_DROPPED. |
| |
| Suppose there is one finger in the beginning. Between block reading |
| event in step 2 and unblock reading event in step3, the finger moves |
| to (1897, 725). And there is no POSITION_X and POSITION_Y update in |
| step 3. The test is to verify the position should be updated with |
| (1897, 725) in xi2_parser after SYN_DROPPED. |
| """ |
| if self.syndrop_position[POSITION_X_VALUATOR] != '1897.00': |
| raise error.TestFail('Incorrect POSITION_X after SYN_DROPPED') |
| if self.syndrop_position[POSITION_Y_VALUATOR] != '725.00': |
| raise error.TestFail('Incorrect POSITION_Y after SYN_DROPPED') |
| |
| def run_once(self): |
| self.setUp() |
| self.test_condition('finger_leaving') |
| self.tearDown() |
| self.verify_finger_leaving() |
| |
| self.setUp() |
| self.test_condition('finger_arriving') |
| self.tearDown() |
| self.verify_finger_arriving() |
| |
| self.setUp() |
| self.test_condition('finger_changing') |
| self.tearDown() |
| self.verify_finger_changing() |
| |
| self.setUp() |
| self.test_condition('same_finger') |
| self.tearDown() |
| self.verify_same_finger() |