1# Copyright (c) 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
6
7from autotest_lib.client.bin import utils
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.cros import touch_playback_test_base
10
11
12class touch_UpdateErrors(touch_playback_test_base.touch_playback_test_base):
13    """Check that touch update is tried and that there are no update errors."""
14    version = 1
15
16    # Older devices with Synaptics touchpads do not report firmware updates.
17    _INVALID_BOARDS = ['x86-alex', 'x86-alex_he', 'x86-zgb', 'x86-zgb_he',
18                       'x86-mario', 'stout']
19
20    # Devices which have errors in older builds but not newer ones.
21    _IGNORE_OLDER_LOGS = ['expresso', 'enguarde', 'cyan', 'wizpig']
22
23    # Devices which have errors in the first build after update.
24    _IGNORE_AFTER_UPDATE_LOGS = ['link']
25
26    def _find_logs_start_line(self):
27        """Find where in /var/log/messages this build's logs start.
28
29        Prevent bugs such as crosbug.com/p/31012, where unfixable errors from
30        FSI builds remain in the logs.
31
32        For devices where this applies, split the logs by Linux version.  Since
33        this line can repeat, find the last chunk of logs where the version is
34        all the same - all for the build under test.
35
36        @returns: string of the line number to start looking at logs
37
38        """
39        if not (self._platform in self._IGNORE_OLDER_LOGS or
40                self._platform in self._IGNORE_AFTER_UPDATE_LOGS):
41            return '0'
42
43        log_cmd = 'grep -ni "Linux version " /var/log/messages'
44
45        version_entries = utils.run(log_cmd).stdout.strip().split('\n')
46
47        # Separate the line number and the version date (i.e. remove timestamp).
48        lines, dates = [], []
49        for entry in version_entries:
50            lines.append(entry[:entry.find(':')])
51            dates.append(entry[entry.find('Linux version '):])
52        latest = dates[-1]
53        start_line = lines[-1]
54        start_line_index = -1
55
56        # Find where logs from this build start by checking backwards for the
57        # first change in build.  Some of these dates may be duplicated.
58        for i in xrange(len(lines)-1, -1, -1):
59            if dates[i] != latest:
60                break
61            start_line = lines[i]
62            start_line_index = i
63
64        if start_line_index == 0:
65            return '0'
66
67        logging.info('This build has an older build; skipping some logs, '
68                     'as was hardcoded for this platform.')
69
70        # Ignore the first build after update if required.
71        if self._platform in self._IGNORE_AFTER_UPDATE_LOGS:
72            start_line_index += 1
73            if start_line_index >= len(lines):
74                raise error.TestError(
75                        'Insufficent logs: aborting test to avoid a known '
76                        'issue!  Please reboot and try again.')
77            start_line = lines[start_line_index]
78
79        return start_line
80
81    def _check_updates(self, input_type):
82        """Fail the test if device has problems with touch firmware update.
83
84        @param input_type: string of input type, e.g. 'touchpad'
85
86        @raises: TestFail if no update attempt occurs or if there is an error.
87
88        """
89        hw_id = self.player.devices[input_type].hw_id
90        if not hw_id:
91            raise error.TestError('%s has no valid hw_id!' % input_type)
92
93        updater_name = 'touch-firmware-update'
94        start_line = self._find_logs_start_line()
95        # Null characters sometimes slip into /var/log/messages, causing grep to
96        # treat it as a binary file (and output "binary file matches" rather
97        # than the matching text). --text forces grep to treat it as text file.
98        log_cmd = (r'tail -n +%s /var/log/messages '
99                   r"| grep --text -i '\(%s\|chromeos-touch-update\)'") % (
100                           start_line, updater_name)
101
102        pass_terms = [
103                '%s.*%s' % (updater_name, hw_id),
104                r'chromeos-touch-update\[[[:digit:]]\+\]: Running updater for '
105                r'.* ([[:xdigit:]]\+:%s)$' % hw_id
106        ]
107
108        fail_terms = ['error[^s]', 'err[^a-z]']
109        ignore_terms = ['touchview','autotest']
110
111        # Remove lines that match ignore_terms.
112        for term in ignore_terms:
113            log_cmd += ' | grep -v -i %s' % term
114
115        # Check for key terms in touch logs.
116        found_pass_term = False
117        for term in pass_terms + fail_terms:
118            search_cmd = "%s | grep -i '%s'" % (log_cmd, term)
119            log_entries = utils.run(search_cmd, ignore_status=True).stdout
120            if term in fail_terms and len(log_entries) > 0:
121                error_msg = log_entries.split('\n')[0]
122                error_msg = error_msg[error_msg.find(term)+len(term):].strip()
123                raise error.TestFail(error_msg)
124            if term in pass_terms and len(log_entries) > 0:
125                logging.info('Matched "%s" on these pass terms: "%s"', term,
126                             log_entries)
127                found_pass_term = True
128
129        if not found_pass_term:
130            logging.info('Did not find any pass terms! (looked for "%s")',
131                         '", "'.join(pass_terms))
132            raise error.TestFail('Touch firmware did not attempt update.')
133
134    def run_once(self, input_type='touchpad'):
135        """Entry point of this test."""
136        if not self.player.has(input_type):
137            raise error.TestError('No %s found on this device!' % input_type)
138
139        # Skip run on invalid touch inputs.
140        if self._platform in self._INVALID_BOARDS:
141            logging.info('This touchpad is not supported for this test.')
142            return
143
144        self._check_updates(input_type)
145