2
# -*- coding: utf-8 -*-
6
# This file is part of Checkbox.
8
# Copyright 2012 Canonical Ltd.
10
# Authors: Alberto Milone <alberto.milone@canonical.com>
12
# Checkbox is free software: you can redistribute it and/or modify
13
# it under the terms of the GNU General Public License as published by
14
# the Free Software Foundation, either version 3 of the License, or
15
# (at your option) any later version.
17
# Checkbox is distributed in the hope that it will be useful,
18
# but WITHOUT ANY WARRANTY; without even the implied warranty of
19
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
# GNU General Public License for more details.
22
# You should have received a copy of the GNU General Public License
23
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
33
from argparse import ArgumentParser
34
from subprocess import call, Popen, PIPE
35
from checkbox.contrib import xrandr
38
class VtWrapper(object):
39
"""docstring for VtWrapper"""
41
self.x_vt = self._get_x_vt()
44
'''Get the vt where X lives'''
46
proc = Popen(['ps', 'aux'], stdout=PIPE, universal_newlines=True)
47
proc_output = proc.communicate()[0].split('\n')
48
proc_line = re.compile('.*tty(\d+).+/usr/bin/X.*')
49
for line in proc_output:
50
match = proc_line.match(line)
52
vt = match.group(1).strip().lower()
56
retcode = call(['chvt', '%d' % vt])
59
class SuspendWrapper(object):
63
def can_we_sleep(self, mode):
65
Test to see if S3 state is available to us. /proc/acpi/* is old
66
and will be deprecated, using /sys/power to maintine usefulness for
70
states_fh = open('/sys/power/state', 'r')
72
states = states_fh.read().split()
81
def get_current_time(self):
83
time_fh = open('/sys/class/rtc/rtc0/since_epoch', 'r')
85
cur_time = int(time_fh.read())
90
def set_wake_time(self, time):
92
Get the current epoch time from /sys/class/rtc/rtc0/since_epoch
93
then add time and write our new wake_alarm time to
94
/sys/class/rtc/rtc0/wakealarm.
96
The math could probably be done better but this method avoids having to
97
worry about whether or not we're using UTC or local time for both the
98
hardware and system clocks.
101
cur_time = self.get_current_time()
102
logging.debug('Current epoch time: %s' % cur_time)
104
wakealarm_fh = open('/sys/class/rtc/rtc0/wakealarm', 'w')
107
wakealarm_fh.write('0\n')
110
wakealarm_fh.write('+%s\n' % time)
115
logging.debug('Wake alarm in %s seconds' % time)
117
def do_suspend(self, mode):
119
Suspend the system and hope it wakes up.
120
Previously tried writing new state to /sys/power/state but that
121
seems to put the system into an uncrecoverable S3 state. So far,
122
pm-suspend seems to be the most reliable way to go.
126
status = call('/usr/sbin/pm-suspend')
128
status = call('/usr/sbin/pm-hibernate')
130
logging.debug('Unknown sleep state passed')
135
class RotationWrapper(object):
138
self._rotations = {'normal': xrandr.RR_ROTATE_0,
139
'right': xrandr.RR_ROTATE_90,
140
'inverted': xrandr.RR_ROTATE_180,
141
'left': xrandr.RR_ROTATE_270}
143
def _rotate_screen(self, rotation):
144
# Refresh the screen. Required by NVIDIA
145
screen = xrandr.get_current_screen()
146
screen.set_rotation(rotation)
147
return screen.apply_config()
149
def do_rotation_cycle(self):
150
'''Cycle through all possible rotations'''
153
for rot in self._rotations:
155
status = self._rotate_screen(self._rotations[rot])
156
except(xrandr.RRError, xrandr.UnsupportedRRError) as err:
161
# Collect the status and the error message
162
rots_statuses[rot] = (status, error)
165
# Try to set the screen back to normal
167
self._rotate_screen(xrandr.RR_ROTATE_0)
168
except(xrandr.RRError, xrandr.UnsupportedRRError) as error:
172
for elem in rots_statuses:
173
status = rots_statuses.get(elem)[0]
174
error = rots_statuses.get(elem)[1]
176
logging.error('Error: rotation "%s" failed with status %d: %s.'
177
% (elem, status, error))
181
class RenderCheckWrapper(object):
182
"""A simple class to run the rendercheck suites"""
184
def __init__(self, temp_dir=None):
185
self._temp_dir = temp_dir
187
def _print_test_info(self, suites='all', iteration=1, show_errors=False):
188
'''Print the output of the test suite'''
190
main_command = 'rendercheck'
195
# Use the specified path
196
temp_file = tempfile.NamedTemporaryFile(dir=self._temp_dir,
200
temp_file = tempfile.NamedTemporaryFile(delete=False)
203
full_command = [main_command, '-f', 'a8r8g8b8']
205
full_command = [main_command, '-t', suites, '-f', 'a8r8g8b8']
208
# Let's dump the output into file as it can be very large
209
# and we don't want to store it in memory
210
process = Popen(full_command, stdout=temp_file,
211
universal_newlines=True)
212
except OSError as exc:
213
if exc.errno == errno.ENOENT:
214
logging.error('Error: please make sure that rendercheck '
220
exit_code = process.wait()
224
# Read values from the file
225
errors = re.compile('.*test error.*')
226
results = re.compile('(.+) tests passed of (.+) total.*')
229
with open(temp_file.name) as temp_handle:
230
for line in temp_handle:
231
match_output = results.match(line)
232
match_errors = errors.match(line)
234
passed = int(match_output.group(1).strip())
235
total = int(match_output.group(2).strip())
236
logging.info('Results:')
237
logging.info(' %d tests passed out of %d.'
239
if show_errors and match_errors:
240
error = match_errors.group(0).strip()
242
logging.debug('Rendercheck %s suite errors '
244
% (suites, iteration))
246
logging.debug(' %s' % error)
249
os.unlink(temp_file.name)
251
return (exit_code, passed, total)
253
def run_test(self, suites=[], iterations=1, show_errors=False):
256
for it in range(iterations):
257
logging.info('Iteration %d of Rendercheck %s suite...'
259
(status, passed, total) = \
260
self._print_test_info(suites=suite,
262
show_errors=show_errors)
264
# Make sure to catch a non-zero exit status
265
logging.info('Iteration %d of Rendercheck %s suite '
266
'exited with status %d.'
267
% (it + 1, suite, status))
271
# exit with 1 if passed < total
277
def get_suites_list(self):
278
'''Return a list of the available test suites'''
280
process = Popen(['rendercheck', '--help'], stdout=PIPE,
281
stderr=PIPE, universal_newlines=True)
282
except OSError as exc:
283
if exc.errno == errno.ENOENT:
284
logging.error('Error: please make sure that rendercheck '
290
proc = process.communicate()[1].split('\n')
292
tests_pattern = re.compile('.*Available tests: *(.+).*')
298
match = tests_pattern.match(line)
300
first_line = match.group(1).strip().lower()
302
temp_line += first_line
303
for elem in temp_line.split(','):
311
# Make sure that we have root privileges
312
if os.geteuid() != 0:
313
print('Error: please run this program as root',
317
usage = 'Usage: %prog [OPTIONS]'
318
parser = ArgumentParser(usage)
319
parser.add_argument('-i', '--iterations',
322
help='The number of times to run the test. \
324
parser.add_argument('-d', '--debug',
326
help='Choose this to add verbose output \
328
parser.add_argument('-b', '--blacklist',
330
help='Name(s) of rendercheck test(s) to blacklist.')
331
parser.add_argument('-o', '--output',
333
help='The path to the log which will be dumped. \
335
parser.add_argument('-tp', '--temp',
337
help='The path where to store temporary files. \
339
args = parser.parse_args()
341
# Set up logging to console
342
format = '%(message)s'
344
console_handler = logging.StreamHandler()
345
console_handler.setFormatter(logging.Formatter(format))
347
# Set up the overall logger
348
logger = logging.getLogger()
349
# This is necessary to ensure debug messages are passed through the logger
351
logger.setLevel(logging.DEBUG)
353
# This is what happens when -d and/or -o are passed:
354
# -o -> stdout (info) - log (info)
355
# -d -> only stdout (info and debug) - no log
356
# -d -o -> stdout (info) - log (info and debug)
360
# Write INFO to stdout
361
console_handler.setLevel(logging.INFO)
362
logger.addHandler(console_handler)
364
logfile = args.output
365
logfile_handler = logging.FileHandler(logfile)
367
# Write INFO and DEBUG to a log
368
logfile_handler.setLevel(logging.DEBUG)
370
# Write INFO to a log
371
logfile_handler.setLevel(logging.INFO)
373
logfile_handler.setFormatter(logging.Formatter(format))
374
logger.addHandler(logfile_handler)
375
log_path = os.path.abspath(logfile)
377
# Write only to stdout
380
# Write INFO and DEBUG to stdout
381
console_handler.setLevel(logging.DEBUG)
382
logger.addHandler(console_handler)
384
# Write INFO to stdout
385
console_handler.setLevel(logging.INFO)
386
logger.addHandler(console_handler)
390
rendercheck = RenderCheckWrapper(args.temp)
391
tests = rendercheck.get_suites_list()
392
for test in args.blacklist:
396
# Switch between the tty where X lives and tty10
397
vt_wrap = VtWrapper()
399
if vt_wrap.x_vt != target_vt:
400
logging.info('== Vt switch test ==')
401
for it in range(args.iterations):
402
logging.info('Iteration %d...', it)
403
retcode = vt_wrap.set_vt(target_vt)
405
logging.error('Error: switching to tty%d failed with code %d '
406
'on iteration %d' % (target_vt, retcode, it))
409
logging.info('Switching to tty%d: passed' % (target_vt))
411
retcode = vt_wrap.set_vt(vt_wrap.x_vt)
413
logging.error('Error: switching to tty%d failed with code %d '
414
'on iteration %d' % (vt_wrap.x_vt, retcode, it))
416
logging.info('Switching to tty%d: passed' % (vt_wrap.x_vt))
419
logging.error('Error: please run X on a tty other than 10')
422
logging.info('== Sleep test ==')
423
sleep_test = SuspendWrapper()
425
# See if we can sleep
426
if sleep_test.can_we_sleep(sleep_mode):
427
for it in range(args.iterations):
429
logging.info('Iteration %d...', it + 1)
431
sleep_test.set_wake_time(20)
433
if sleep_test.do_suspend(sleep_mode) == 0:
434
logging.info('Passed')
436
logging.error('Failed')
440
logging.info('Skipped (the system does not seem to support S3')
442
# Rotate the screen x times
443
# The app already rotates the screen 5 times
444
logging.info('== Rotation test ==')
445
rotation_test = RotationWrapper()
447
for it in range(args.iterations):
448
logging.info('Iteration %d...', it + 1)
449
if rotation_test.do_rotation_cycle() == 0:
450
logging.info('Passed')
452
logging.error('Failed')
455
# Call rendercheck x times
456
logging.info('== Rendercheck test ==')
457
if rendercheck.run_test(tests, args.iterations,
459
logging.info('Passed')
461
logging.error('Failed')
466
if __name__ == '__main__':