blob: 4e3d8a262091683f5e4c7e1343590c16357036c1 [file] [log] [blame]
# Copyright (c) 2010 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 errno, logging, os, re, signal, subprocess, time
import common
import constants, cros_logging, cros_ui, cryptohome
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
_DEFAULT_TIMEOUT = 30
_DEFAULT_OWNERSHIP_TIMEOUT = 60
# Log messages used to signal when we're in a logout situation. Used to detect
# crashes by cros_ui_test.UITest.
LOGOUT_ATTEMPT_MSG = 'cros/login.py: Attempting logout...'
LOGOUT_COMPLETE_MSG = 'cros/login.py: Logout complete.'
class TimeoutError(error.TestError):
"""Error raised when we time out while waiting on a condition."""
pass
class CrashError(error.TestError):
"""Error raised when a pertinent process crashes while waiting on
a condition.
"""
pass
class UnexpectedCondition(error.TestError):
"""Error raised when an expected precondition is not met."""
pass
def __get_session_manager_pid():
"""Determine the pid of the session manager.
Returns:
An integer indicating the current session manager pid, or None if
it is not running.
"""
p = subprocess.Popen(["pgrep", "^%s$" % constants.SESSION_MANAGER],
stdout=subprocess.PIPE)
ary = p.communicate()[0].split()
return int(ary[0]) if ary else None
def __session_manager_restarted(oldpid):
"""Detect if the session manager has restarted.
Args:
oldpid: Integer indicating the last known pid of the session_manager.
Returns:
True if the session manager is running under a pid other than
'oldpid', X is running, and there is a window displayed.
"""
import autox
newpid = __get_session_manager_pid()
if newpid and newpid != oldpid:
try:
ax = cros_ui.get_autox()
except autox.Xlib.error.DisplayConnectionError:
return False
# When the session manager starts up there is a moment where we can
# make a connection with autox, but there is no window displayed. If
# we start sending keystrokes at this point they get lost. If we wait
# for this window to show up, things go much smoother.
wid = ax.get_top_window_id_at_point(0, 0)
if not wid:
return False
# The login manager displays its widgetry in a second window centered
# on the screen. Waiting for this window to show up is also helpful.
# TODO: perhaps the login manager should emit some more trustworthy
# signal when it's ready to accept credentials.
x, y = ax.get_screen_size()
wid2 = ax.get_top_window_id_at_point(x / 2, y / 2)
if wid == wid2:
return False
return True
return False
def logged_in():
# this file is created when the session_manager emits start-user-session
# and removed when the session_manager emits stop-user-session
return os.path.exists(constants.LOGGED_IN_MAGIC_FILE)
def process_crashed(process, log_reader):
"""Checks the log watched by |log_reader| to see if a crash was reported
for |process|.
Returns True if so, False if not.
"""
return log_reader.can_find('Received crash notification for %s' % process)
def wait_for_condition(condition, timeout_msg, timeout, process, log_reader,
crash_msg):
try:
utils.poll_for_condition(
condition,
TimeoutError(timeout_msg),
timeout=timeout)
except TimeoutError, e:
# We could fail faster if necessary, but it'd be more complicated.
if process_crashed(process, log_reader):
raise CrashError(crash_msg)
else:
raise e
def attempt_login(username, password, timeout=_DEFAULT_TIMEOUT):
"""Attempt to log in.
Args:
username: str username for login
password: str password for login
timeout: float number of seconds to wait
Raises:
TimeoutError: login didn't complete before timeout
UnexpectedCondition: login manager is not running, or user is already
logged in.
"""
logging.info("Attempting to login using autox.py and (%s, %s)" %
(username, password))
if not __get_session_manager_pid():
raise UnexpectedCondition("Session manager is not running")
if logged_in():
raise UnexpectedCondition("Already logged in")
# Mark /var/log/messages now; we'll run through all subsequent log messages
# if we couldn't log in to see if the browser crashed.
log_reader = cros_logging.LogReader()
log_reader.set_start_by_current()
# Up our priority so we don't get descheduled in the middle of sending key
# press and key release events.
ax = cros_ui.get_autox()
# navigate to login screen
ax.send_hotkey("Ctrl+Alt+L")
# escape out of any login screen menus (e.g., the network select menu)
ax.send_hotkey("Escape")
time.sleep(0.5)
if (username):
# focus username
ax.send_hotkey("Alt+U")
ax.send_text(username)
# focus password
ax.send_hotkey("Alt+P")
ax.send_text(password)
ax.send_hotkey("Return")
else:
ax.send_hotkey("Alt+B") # Browse without signing-in
wait_for_condition(condition=logged_in,
timeout_msg='Timed out waiting for login',
timeout=timeout,
process='chrome',
log_reader=log_reader,
crash_msg='Chrome crashed during login')
if (username):
# We don't take ownership on Guest login; Otherwise we SIGABRT keygen
wait_for_ownership()
def attempt_logout(timeout=_DEFAULT_TIMEOUT):
"""Attempt to log out by killing Chrome.
Args:
timeout: float number of seconds to wait
Raises:
TimeoutError: logout didn't complete before timeout
UnexpectedCondition: user is not logged in
"""
if not logged_in():
raise UnexpectedCondition('Already logged out')
# Log what we're about to do to /var/log/messages. Used to log crashes later
# in cleanup by cros_ui_test.UITest.
utils.system('logger "%s"' % LOGOUT_ATTEMPT_MSG)
try:
oldpid = __get_session_manager_pid()
# Mark /var/log/messages now; we'll run through all subsequent log
# messages if we couldn't TERM and restart the session manager.
log_reader = cros_logging.LogReader()
log_reader.set_start_by_current()
# Gracefully exiting the session manager causes the user's session to end.
utils.system('pkill -TERM -o ^%s$' % constants.SESSION_MANAGER)
wait_for_condition(
condition=lambda: __session_manager_restarted(oldpid),
timeout_msg='Timed out waiting for logout',
timeout=timeout,
process='session_manager',
log_reader=log_reader,
crash_msg='session_manager crashed while shutting down.')
finally:
utils.system('logger "%s"' % LOGOUT_COMPLETE_MSG)
def wait_for_browser(timeout=_DEFAULT_TIMEOUT):
"""Wait until a Chrome process is running.
Args:
timeout: float number of seconds to wait
Raises:
TimeoutError: Chrome didn't start before timeout
"""
# Mark /var/log/messages now; we'll run through all subsequent log messages
# if we couldn't start chrome to see if the browser crashed.
log_reader = cros_logging.LogReader()
log_reader.set_start_by_current()
wait_for_condition(
lambda: os.system('pgrep ^%s$' % constants.BROWSER) == 0,
timeout_msg='Timed out waiting for Chrome to start',
timeout=timeout,
process='chrome',
log_reader=log_reader,
crash_msg='Chrome crashed while starting up.')
def wait_for_cryptohome(timeout=_DEFAULT_TIMEOUT):
"""Wait until cryptohome is mounted.
Args:
timeout: float number of seconds to wait
Raises:
TimeoutError: cryptohome wasn't mounted before timeout
"""
# Mark /var/log/messages now; we'll run through all subsequent log messages
# if we couldn't get the browser up to see if the browser crashed.
log_reader = cros_logging.LogReader()
log_reader.set_start_by_current()
wait_for_condition(
condition=lambda: cryptohome.is_mounted(),
timeout_msg='Timed out waiting for cryptohome to be mounted',
timeout=timeout,
process='cryptohomed',
log_reader=log_reader,
crash_msg='cryptohomed crashed during mount attempt')
def wait_for_login_prompt(timeout=_DEFAULT_TIMEOUT):
"""Wait the login prompt is on screen and ready
Args:
timeout: float number of seconds to wait
Raises:
TimeoutError: Login prompt didn't get up before timeout
"""
# Mark /var/log/messages now; we'll run through all subsequent log messages
# if we couldn't get the browser up to see if the browser crashed.
log_reader = cros_logging.LogReader()
log_reader.set_start_by_current()
wait_for_condition(
condition=lambda: os.access(
constants.LOGIN_PROMPT_READY_MAGIC_FILE, os.F_OK),
timeout_msg='Timed out waiting for login prompt',
timeout=timeout,
process='chrome',
log_reader=log_reader,
crash_msg='Chrome crashed before the login prompt.')
def wait_for_window_manager(timeout=_DEFAULT_TIMEOUT):
"""Wait until the window manager is running.
Args:
timeout: float number of seconds to wait
Raises:
TimeoutError: window manager didn't start before timeout
"""
utils.poll_for_condition(
lambda: not os.system('pgrep ^%s$' % constants.WINDOW_MANAGER),
TimeoutError('Timed out waiting for window manager to start'),
timeout=timeout)
def wait_for_initial_chrome_window(timeout=_DEFAULT_TIMEOUT):
"""Wait until the initial Chrome window is mapped.
Args:
timeout: float number of seconds to wait
Raises:
TimeoutError: Chrome window wasn't mapped before timeout
"""
# Mark /var/log/messages now; we'll run through all subsequent log messages
# if we couldn't get the browser up to see if the browser crashed.
log_reader = cros_logging.LogReader()
log_reader.set_start_by_current()
wait_for_condition(
lambda: os.access(
constants.CHROME_WINDOW_MAPPED_MAGIC_FILE, os.F_OK),
'Timed out waiting for initial Chrome window',
timeout=timeout,
process='chrome',
log_reader=log_reader,
crash_msg='Chrome crashed before first tab rendered.')
def wait_for_ownership(timeout=_DEFAULT_OWNERSHIP_TIMEOUT):
log_reader = cros_logging.LogReader()
log_reader.set_start_by_current()
wait_for_condition(
condition=lambda: os.access(constants.OWNER_KEY_FILE, os.F_OK),
timeout_msg='Timed out waiting for ownership',
timeout=timeout,
process=constants.BROWSER,
log_reader=log_reader,
crash_msg='Chrome crashed before ownership could be taken.')
def nuke_login_manager():
nuke_process_by_name('session_manager')
wait_for_browser()
def nuke_process_by_name(name, with_prejudice=False):
pid = int(utils.system_output('pgrep -o ^%s$' % name).split()[0])
if with_prejudice:
utils.nuke_pid(pid, [signal.SIGKILL])
else:
utils.nuke_pid(pid)
def refresh_window_manager(timeout=_DEFAULT_TIMEOUT):
"""Clear state that tracks what WM has done, kill it, and wait until
the window manager is running.
Args:
timeout: float number of seconds to wait
Raises:
TimeoutError: window manager didn't start before timeout
"""
os.unlink(constants.CHROME_WINDOW_MAPPED_MAGIC_FILE)
nuke_process_by_name(constants.WINDOW_MANAGER)
wait_for_window_manager()
def refresh_login_screen(timeout=_DEFAULT_TIMEOUT):
"""Clear any runtime state that chrome has built up at the login screen.
Args:
timeout: float number of seconds to wait
Raises:
UnexpectedCondition: called while already logged in
TimeoutError: chrome didn't start before timeout
"""
if logged_in():
raise UnexpectedCondition('Already logged in')
wait_for_browser()
wait_for_login_prompt()
oldpid = __get_session_manager_pid()
# Clear breadcrumb that shows we've emitted login-prompt-ready.
try:
os.unlink(constants.LOGIN_PROMPT_READY_MAGIC_FILE)
except OSError, e:
if e.errno != errno.ENOENT:
raise e
# Clear old log files.
logpath = constants.CHROME_LOG_DIR
try:
for file in os.listdir(logpath):
fullpath = os.path.join(logpath, file)
if os.path.isfile(fullpath):
os.unlink(fullpath)
except (IOError, OSError) as error:
logging.error(error)
# Restart the UI.
nuke_login_manager()
utils.poll_for_condition(
lambda: __session_manager_restarted(oldpid),
TimeoutError('Timed out waiting for logout'),
timeout)
wait_for_login_prompt()