blob: 821765c6c4156d62eb4d33eb322fd802e61b91b8 [file] [log] [blame]
#!/usr/bin/python
#
# Copyright (c) 2012 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 cPickle as pickle
import datetime
import glob
import hashlib
import logging
import os
import shutil
import subprocess
import threading
import time
import factory_common # pylint: disable=W0611
from cros.factory import system
from cros.factory.test import factory
from cros.factory.goofy import connection_manager
from cros.factory.test import state
from cros.factory.test import utils
from cros.factory.utils.process_utils import Spawn
class Environment(object):
'''
Abstract base class for external test operations, e.g., run an autotest,
shutdown, or reboot.
The Environment is assumed not to be thread-safe: callers must grab the lock
before calling any methods. This is primarily necessary because we mock out
this Environment with mox, and unfortunately mox is not thread-safe.
TODO(jsalz): Try to write a thread-safe wrapper for mox.
'''
lock = threading.Lock()
def shutdown(self, operation):
'''
Shuts the machine down (from a ShutdownStep).
Args:
operation: 'reboot' or 'halt'.
Returns:
True if Goofy should gracefully exit, or False if Goofy
should just consider the shutdown to have suceeded (e.g.,
in the chroot).
'''
raise NotImplementedError()
def launch_chrome(self):
'''
Launches Chrome.
Returns:
The Chrome subprocess (or None if none).
'''
raise NotImplementedError()
def spawn_autotest(self, name, args, env_additions, result_file):
'''
Spawns a process to run an autotest.
Args:
name: Name of the autotest to spawn.
args: Command-line arguments.
env_additions: Additions to the environment.
result_file: Expected location of the result file.
'''
raise NotImplementedError()
def create_connection_manager(self, wlans, scan_wifi_period_secs):
'''
Creates a ConnectionManager.
'''
raise NotImplementedError()
class DUTEnvironment(Environment):
'''
A real environment on a device under test.
'''
def shutdown(self, operation):
assert operation in ['reboot', 'halt']
logging.info('Shutting down: %s', operation)
subprocess.check_call('sync')
subprocess.check_call(operation)
time.sleep(30)
assert False, 'Never reached (should %s)' % operation
def spawn_autotest(self, name, args, env_additions, result_file):
return self.goofy.prespawner.spawn(args, env_additions)
def launch_chrome(self):
# The cursor speed needs to be adjusted when running in QEMU
# (but after Chrome starts and has fiddled with the settings
# itself).
if utils.in_qemu():
def FixCursor():
for _ in xrange(6): # Every 500ms for 3 seconds
time.sleep(.5)
subprocess.check_call(['xset','m','200','200'])
thread = threading.Thread(target=FixCursor)
thread.daemon = True
thread.start()
chrome_data_dir = os.path.join(factory.get_state_root(),
'chrome-data-dir')
# Start with a fresh data directory every time.
shutil.rmtree(chrome_data_dir, ignore_errors=True)
# Setup GPU & acceleration flags which differ between x86/ARM SoC
system_info = system.SystemInfo()
if system_info.architecture == "armv7l":
accelerated_flag = "--use-gl=egl"
vda_flag='--use-exynos-vda'
else:
accelerated_flag = "--enable-accelerated-layers"
vda_flag=''
# Auto detect the display modes on DUT
mode_paths = glob.glob('/sys/class/drm/card*/modes')
available_modes = []
for path in mode_paths:
with open(path, 'r') as fd:
available_modes.extend(
line.strip().split('x') for line in open(path).readlines())
if not available_modes:
raise factory.FactoryTestFailure('No display mode was found')
logging.info('Supported display modes: %s', available_modes)
screen_width, screen_height = [int(x) for x in available_modes[0]]
if self.goofy.options.one_pixel_less:
screen_width -= 1
chrome_command = [
'/opt/google/chrome/chrome',
'--ash-host-window-bounds=%dx%d' % (screen_width, screen_height),
'--user-data-dir=%s' % chrome_data_dir,
'--disable-translate',
'--aura-host-window-use-fullscreen',
'--kiosk',
'--kiosk-mode-screensaver-path=/dev/null',
'--use-cras',
'--enable-audio-mixer',
'--enable-renderer-side-mixing',
accelerated_flag,
vda_flag,
('--default-device-scale-factor=%d' %
self.goofy.options.ui_scale_factor),
'--disable-extensions',
# Hard-code localhost IP so Chrome doesn't have to rely on DNS.
'http://127.0.0.1:%d/' % state.DEFAULT_FACTORY_STATE_PORT,
]
if self.goofy.options.automation:
# Automation script will be responsible for opening chrome browser
# argument order:
# chrome_binary_location, option1, option2, ..., factory_url
automation_command = [
'/usr/local/factory/py/automation/factory_automation.py']
automation_command.extend(chrome_command)
automation_log = os.path.join(factory.get_log_root(),
'factory_automation.log')
automation_log_file = open(automation_log, 'a')
# Make sure chromedriver is in the system path
new_env = os.environ.copy()
new_env['PATH'] += ':/usr/local/factory/bin'
logging.info('Launching factory_automation: log in %s', automation_log)
process = Spawn(automation_command,
stdout=automation_log_file,
stderr=subprocess.STDOUT,
# Make other automation logs go to the correct place
cwd=factory.get_log_root(),
env=new_env)
else:
chrome_log = os.path.join(factory.get_log_root(), 'factory.chrome.log')
chrome_log_file = open(chrome_log, 'a', 0)
chrome_log_file.write('#\n# %s: Starting chrome\n#\n' %
datetime.datetime.now().isoformat())
logging.info('Launching Chrome; logs in %s', chrome_log)
process = Spawn(chrome_command,
stdout=chrome_log_file,
stderr=subprocess.STDOUT)
logging.info('Chrome has been launched: PID %d', process.pid)
# Start thread to wait for Chrome to die and log its return
# status
def WaitForChrome():
returncode = process.wait()
logging.info('Chrome exited with return code %d', returncode)
chrome_log_file.write('#\n# %s: Chrome exited with return code %d\n#\n' %
(datetime.datetime.now().isoformat(), returncode))
utils.StartDaemonThread(target=WaitForChrome)
def create_connection_manager(self, wlans, scan_wifi_period_secs):
return connection_manager.ConnectionManager(wlans,
scan_wifi_period_secs)
class FakeChrootEnvironment(Environment):
'''
A chroot environment that doesn't actually shutdown or run autotests.
'''
def shutdown(self, operation):
assert operation in ['reboot', 'halt']
logging.warn('In chroot: skipping %s', operation)
return False
def spawn_autotest(self, name, args, env_additions, result_file):
logging.warn('In chroot: skipping autotest %s', name)
# Mark it as passed with 75% probability, or failed with 25%
# probability (depending on a hash of the autotest name).
pseudo_random = ord(hashlib.sha1(name).digest()[0]) / 256.0
passed = pseudo_random > .25
with open(result_file, 'w') as out:
pickle.dump((passed, '' if passed else 'Simulated failure'), out)
# Start a process that will return with a true exit status in
# 2 seconds (just like a happy autotest).
return subprocess.Popen(['sleep', '2'])
def launch_chrome(self):
logging.warn('In chroot; not launching Chrome. '
'Please open http://localhost:%d/ in Chrome.',
state.DEFAULT_FACTORY_STATE_PORT)
def create_connection_manager(self, wlans, scan_wifi_period_secs):
return connection_manager.DummyConnectionManager()