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"""Some utility classes and functions."""
6
7import glob
8import os
9import re
10import sys
11import time
12
13import common_util
14import test_conf as conf
15
16
17def get_display_name():
18    """Return the display name."""
19    return ':0'
20
21
22def get_screen_size():
23    """Get the screen size using xwininfo."""
24    cmd = 'DISPLAY=:0 xwininfo -root'
25    wininfo = common_util.simple_system_output(cmd)
26    # the geometry string looks like:
27    #     "  -geometry 3840x1200+0+0"
28    geometry_pattern = re.compile('\s*-geometry\s*(\d+)x(\d+)+.*', re.I)
29    for line in wininfo.splitlines():
30        result = geometry_pattern.search(line)
31        if result:
32            width = int(result.group(1))
33            height = int(result.group(2))
34            return (width, height)
35    return None
36
37
38def get_current_time_str():
39    """Get the string of current time."""
40    time_format = '%Y%m%d_%H%M%S'
41    return time.strftime(time_format, time.gmtime())
42
43
44def get_board():
45    """Get board of the Chromebook machine."""
46    with open('/etc/lsb-release') as lsb:
47        context = lsb.read()
48    board = None
49    if context is not None:
50        for line in context.splitlines():
51            if line.startswith('CHROMEOS_RELEASE_BOARD'):
52                board_str = line.split('=')[1]
53                if '-' in board_str:
54                    board = board_str.split('-')[1]
55                elif '_' in board_str:
56                    board = board_str.split('_')[1]
57                else:
58                    board = board_str
59                # Some boards, e.g. alex, may have board name as alex32
60                board = re.search('(\D+)\d*', board, re.I).group(1)
61                break
62    return board
63
64
65def get_board_from_filename(filename):
66    """Get the board name from a given string which is usually a log file.
67
68    A log file looks like:
69        touch_firmware_report-lumpy-fw_11.27-complete-20130610_150540.log
70
71    @param filename: the filename used to extract the board
72    """
73    pieces = filename.split('-')
74    return pieces[1] if len(pieces) >= 2 else None
75
76def get_board_from_directory(directory):
77    """Get the board name from the log in the replay directory.
78
79    @param directory: the log directory
80    """
81    log_files = glob.glob(os.path.join(conf.log_root_dir, directory, '*.log'))
82    for log_file in log_files:
83        board = get_board_from_filename(os.path.basename(log_file))
84        if board:
85            return board
86    return None
87
88
89def get_cpu():
90    """Get the processor of the machine."""
91    return common_util.simple_system_output('uname -m')
92
93
94def install_pygtk():
95    """A temporary dirty hack of installing pygtk related packages."""
96    pygtk_dict = {'x86_64': ['pygtk_x86_64.tbz2', 'lib64'],
97                  'i686': ['pygtk_x86_32.tbz2', 'lib'],
98                  'armv7l': ['pygtk_armv7l.tbz2', 'lib'],
99    }
100    pygtk_info = pygtk_dict.get(get_cpu().lower())
101
102    if get_board() is None:
103        print 'This does not look like a chromebook.'
104    elif pygtk_info:
105        cmd_remount = 'mount -o remount,rw /'
106        if common_util.simple_system(cmd_remount) == 0:
107            pygtk_tarball, lib_path = pygtk_info
108            cmd_untar = 'tar -jxf pygtk/%s -C /' % pygtk_tarball
109            if common_util.simple_system(cmd_untar) == 0:
110                # Need to add the gtk path manually. Otherwise, the path
111                # may not be in the sys.path for the first time when the
112                # tarball is extracted.
113                gtk_path = ('/usr/local/%s/python2.7/site-packages/gtk-2.0' %
114                            lib_path)
115                sys.path.append(gtk_path)
116                print 'Successful installation of pygtk.'
117                return True
118            else:
119                print 'Error: Failed to install pygtk.'
120        else:
121            print 'Failed to remount. Have you removed the write protect?'
122    else:
123        print 'The pygtk is only supported for %s so far.' % pygtk_dict.keys()
124        print 'The other cpus will be supported on demand.'
125        print 'The plan is to remove gtk totally and upgrade to Chrome browser.'
126    return False
127
128
129def get_fw_and_date(filename):
130    """Get the firmware version and the test date from a log directory
131       or a log file.
132
133    An example html filename looks like
134        'touch_firmware_report-link-fw_1.0.170-manual-20130426_064849.log'
135        return (fw_1.0.170, 20130426_064849)
136
137    An example log directory looks like
138        '20130422_020631-fw_1.0.170-manual'
139        return (fw_1.0.170, 20130422_020631)
140    """
141    # The firmware could be fw_1.0.170 or fw_1.0.AA which always comes with
142    # 'fw_' as its prefix. The character '-' is used to separate components
143    # in the filename.
144    result = re.search('-(%s[^-]+?)-' % conf.fw_prefix, filename)
145    fw = result.group(1) if result else None
146
147    result = re.search('(\d{8}_\d{6})[-.]', filename)
148    date = result.group(1) if result else None
149
150    return (fw, date)
151
152
153def create_log_dir(firmware_version, mode):
154    """Create a directory to save the report and device event files."""
155    dir_basename = conf.filename.sep.join([get_current_time_str(),
156                                           conf.fw_prefix + firmware_version,
157                                           mode])
158    log_root_dir = conf.log_root_dir
159    log_dir = os.path.join(log_root_dir, dir_basename)
160    latest_symlink = os.path.join(log_root_dir, 'latest')
161
162    # Create the log directory.
163    try:
164        os.makedirs(log_dir)
165    except OSError, e:
166        print 'Error in create the directory (%s): %s' % (log_dir, e)
167        sys.exit(1)
168
169    # Set up the latest symbolic link to the newly created log directory.
170    try:
171        if os.path.islink(latest_symlink):
172            os.remove(latest_symlink)
173        os.symlink(log_dir, latest_symlink)
174    except OSError, e:
175        print 'Error in setup latest symlink (%s): %s' % (latest_symlink, e)
176        sys.exit(1)
177    return log_dir
178
179
180def stop_power_management():
181    """Stop the power daemon management."""
182    ret_d = common_util.simple_system('stop -q powerd')
183    if ret_d:
184        print 'Error in stopping powerd.'
185        print 'The screen may dim during the test.'
186
187
188def start_power_management():
189    """Start the power daemon management."""
190    ret_d = common_util.simple_system('start -q powerd')
191    if ret_d:
192        print 'Error in starting powerd.'
193        print 'The screen may not go into suspend mode.'
194        print 'If this is a problem, you could reboot the machine.'
195
196
197class GestureList:
198    """A class defines the gesture list."""
199
200    def __init__(self, gesture_names=None):
201        self.gesture_names = (gesture_names if gesture_names
202                                            else conf.gesture_names_complete)
203
204    def get_gesture_list(self, key=None):
205        """Get the list of Gesture objects based on the gesture names."""
206        gesture_dict = conf.get_gesture_dict()
207        gesture_list = []
208        for name in self.gesture_names:
209            gesture = gesture_dict.get(name)
210            if gesture is None:
211                msg = 'Error: the gesture "%s" is not defined in the config.'
212                print msg % name
213                return []
214            gesture_list.append(gesture)
215        return sorted(gesture_list, key=key) if key else gesture_list
216
217
218class Output:
219    """A class to handle outputs to the window and to the report."""
220    def __init__(self, log_dir, report_name, win, report_html):
221        self.log_dir = log_dir
222        self.report_name = report_name
223        self.report = open(report_name, 'w')
224        self.win = win
225        self.prefix_space = ' ' * 4
226        self.msg = None
227        self.report_html = report_html
228
229    def __del__(self):
230        self.stop()
231
232    def stop(self):
233        """Close the report file and print it on stdout."""
234        self.report.close()
235        with open(self.report_name) as f:
236            for line in f.read().splitlines():
237                print line
238        report_msg = '\n*** This test report is saved in the file: %s\n'
239        print report_msg % self.report_name
240
241    def get_prefix_space(self):
242        """Get the prefix space when printing the report."""
243        return self.prefix_space
244
245    def print_report_line(self, msg):
246        """Print the line with proper indentation."""
247        self.report.write(self.prefix_space + str(msg) + os.linesep)
248
249    def print_window(self, msg):
250        """Print the message to the result window."""
251        if type(msg) is list:
252            msg = os.linesep.join(msg)
253        self.win.set_result(msg)
254        print msg
255
256    def _print_report(self, msg):
257        """Print the message to the report."""
258        if type(msg) is list:
259            for line in msg:
260                self.print_report_line(line)
261        else:
262            self.print_report_line(msg)
263
264    def buffer_report(self, msg):
265        """Buffer the message and print it later if not over-written.
266
267        Usage of the method: the validator test result of a gesture may
268        be discarded because the user chooses to re-perform the gesture
269        again. So it should be able to over-write the message.
270        """
271        self.msg = msg
272
273    def flush_report(self):
274        """Print the buffered message if any."""
275        if self.msg:
276            self._print_report(self.msg)
277            self.msg = None
278
279    def print_report(self, msg):
280        """Print the message to the report."""
281        # Print any buffered message first.
282        self.flush_report()
283        # Print this incoming message
284        self._print_report(msg)
285
286    def print_all(self, msg):
287        """Print the message to both report and to the window."""
288        self.print_window(msg)
289        self.buffer_report(msg)
290
291
292class ScreenShot:
293    """Handle screen shot."""
294
295    def __init__(self, geometry_str):
296        self.geometry_str = geometry_str
297        environment_str = 'DISPLAY=:0.0 XAUTHORITY=/home/chronos/.Xauthority '
298        dump_util = '/usr/local/bin/import -quality 20'
299        self.dump_window_format = ' '.join([environment_str, dump_util,
300                                           '-window %s %s.png'])
301        self.dump_root_format = ' '.join([environment_str, dump_util,
302                                         '-window root -crop %s %s.png'])
303        self.get_id_cmd = 'DISPLAY=:0 xwininfo -root -tree'
304
305    def dump_window(self, filename):
306        """Dump the screenshot of a window to the specified file name."""
307        win_id = self._get_win_id()
308        if win_id:
309            dump_cmd = self.dump_window_format % (win_id, filename)
310            common_util.simple_system(dump_cmd)
311        else:
312            print 'Warning: cannot get the window id.'
313
314    def dump_root(self, filename):
315        """Dump the screenshot of root to the specified file name."""
316        dump_cmd = self.dump_root_format % (self.geometry_str, filename)
317        common_util.simple_system(dump_cmd)
318
319    def _get_win_id(self):
320        """Get the window ID based on the characteristic string."""
321        result = common_util.simple_system_output(self.get_id_cmd)
322        for line in result.splitlines():
323            if self.geometry_str in line:
324                return line.split()[0].strip()
325        return None
326