blob: cf98302efc8b645b840290cd52e634d783ed5002 [file] [log] [blame]
# 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()