1# Copyright 2014 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
5import logging
6import os
7import pyudev
8import re
9
10from autotest_lib.client.bin import test, utils
11from autotest_lib.client.common_lib import error
12from collections import defaultdict
13from operator import attrgetter
14
15def natural_key(string_):
16    """
17    Derive key for natural sorting.
18    @param string_: String to derive sort key for.
19    From http://stackoverflow.com/questions/34518/natural-sorting-algorithm.
20    """
21    return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)]
22
23
24class platform_UdevVars(test.test):
25    """Verify ChromeOS-specific udev variables."""
26    version = 1
27
28
29    def _input_devices(self):
30        """Obtain a list of all /dev/input/event* udev devices."""
31        devices = self.udev.list_devices(subsystem='input')
32        # only consider the event devices
33        devices = filter(attrgetter('device_node'), devices)
34        devices = sorted(devices, key=lambda device: natural_key(device.device_node))
35        return devices
36
37
38    def _get_roles(self):
39        """Get information on input devices and roles from udev."""
40        self.devices_with_role = defaultdict(list)
41
42        logging.debug('Input devices:')
43        for device in self._input_devices():
44            name = device.parent.attributes.get('name', '')
45            logging.debug('  %s [%s]', device.device_node, name)
46            role = device.get('POWERD_ROLE', None)
47            if role:
48                logging.debug('    POWERD_ROLE=%s', role)
49                self.devices_with_role[role].append(device)
50
51
52    def _dump_roles(self):
53        """Log devices grouped by role for easier debugging."""
54        logging.info('Roles:')
55        for role in sorted(self.devices_with_role.keys()):
56            for device in self.devices_with_role[role]:
57                path = device.device_node
58                name = device.parent.attributes.get('name', '')
59                logging.info('  %-21s %s [%s]', role + ':', path, name)
60
61
62    def _dump_udev_attrs(self):
63        """Log udev attributes for selected devices to the debug directory."""
64        for device in self._input_devices():
65            devname = os.path.basename(device.device_node)
66
67            outfile = os.path.join(self.debugdir, "udevattrs.%s" % devname)
68            utils.system('udevadm info --attribute-walk --path=%s > %s' % (
69                    device.sys_path, outfile))
70
71            outfile = os.path.join(self.debugdir, "udevprops.%s" % devname)
72            utils.system('udevadm info --query=property --path=%s > %s' % (
73                    device.sys_path, outfile))
74
75
76    def _verify_roles(self):
77        """Verify that POWERD_ROLE was set on devices as expected."""
78
79        # TODO(chromium:410968): Consider moving this to USE flags instead of
80        # listing devices here.
81        boards_with_touchscreen = ['link', 'samus']
82        boards_maybe_touchscreen = ['rambi', 'peppy', 'glimmer', 'clapper',
83                                    'nyan_big', 'nyan_blaze', 'expresso']
84        boards_chromebox = ['beltino', 'guado', 'mccloud', 'panther', 'rikku',
85                            'stumpy', 'tidus', 'tricky', 'zako']
86        boards_aio = ['nyan_kitty', 'tiny', 'anglar', 'monroe']
87
88        expect_keyboard = None
89        expect_touchpad = None
90        expect_touchscreen = None
91
92        board = utils.get_board()
93        if board in boards_chromebox or board in boards_aio:
94            expect_keyboard = [0]
95            expect_touchpad = [0]
96        else:
97            expect_keyboard = [1]
98            expect_touchpad = [1]
99
100        if board in boards_with_touchscreen:
101            expect_touchscreen = [1]
102        elif board in boards_maybe_touchscreen:
103            expect_touchscreen = [0, 1]
104        else:
105            expect_touchscreen = [0]
106
107        expected_num_per_role = [
108                ('internal_keyboard', expect_keyboard),
109                ('internal_touchpad', expect_touchpad),
110                ('internal_touchscreen', expect_touchscreen),
111            ]
112
113        for role, expected_num in expected_num_per_role:
114            num = len(self.devices_with_role[role])
115            if num not in expected_num:
116                self.errors += 1
117                logging.error('POWERD_ROLE=%s is present %d times, expected '
118                              'one of %s', role, num, repr(expected_num))
119
120        if len(self.devices_with_role['external_input']) != 0:
121            logging.warn('%d external input devices detected',
122                         len(self.devices_with_role['external_input']))
123
124
125    def initialize(self):
126        self.udev = pyudev.Context()
127
128
129    def run_once(self):
130        """
131        Check that udev variables are assigned correctly by udev rules. In
132        particular, verifies that powerd tags are set correctly.
133        """
134        logging.debug('Board: %s', utils.get_board())
135        self._get_roles()
136        self._dump_roles()
137        self._dump_udev_attrs()
138
139        self.errors = 0
140        self._verify_roles()
141
142        if self.errors != 0:
143            raise error.TestFail('Verification of udev variables failed; see '
144                                 'logs for details')
145