blob: 65e2e47e4ca145cba64cad2c086491185c0d9cd8 [file] [log] [blame]
#!/usr/bin/python -u
# -*- coding: utf-8 -*-
#
# 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.
'''rsyncs goofy and runs on a remote device.'''
import argparse
import glob
import logging
import os
import pipes
import re
import sys
import tempfile
import factory_common # pylint: disable=W0611
from cros.factory.test import factory
from cros.factory.test.test_lists import test_lists
from cros.factory.test.utils import Retry, in_chroot
from cros.factory.utils.process_utils import Spawn
SRCROOT = os.environ.get('CROS_WORKON_SRCROOT')
ssh_command = None # set in main
rsync_command = None
def GetBoard(host):
logging.info('Checking release board on %s...', host)
release = Spawn(ssh_command + [host, 'cat /etc/lsb-release'],
check_output=True, log=True).stdout_data
match = re.search(r'^CHROMEOS_RELEASE_BOARD=(.+)', release, re.MULTILINE)
if not match:
logging.warn('Unable to determine release board')
return None
return match.group(1)
def SyncTestList(host, board, test_list,
clear_factory_environment, clear_password,
shopfloor_host, shopfloor_port):
# Uses dash in board name for overlay directory name
board_dash = board.replace('_', '-')
if test_list is None:
test_list_globs = []
for x in [['chromeos-factory-board', 'files', 'test_lists',
'test_list'],
['autotest-private-board', 'files', 'test_list']]:
test_list_globs.append(
os.path.join(
SRCROOT, 'src',
'*-overlays', 'overlay-%s-*' % board_dash,
'chromeos-base', *x))
test_list_globs.append(
os.path.join(
SRCROOT, 'src',
'*-overlays', 'overlay-variant-%s-*' % board_dash,
'chromeos-base', *x))
test_list_names = sum([glob.glob(x) for x in test_list_globs], [])
if not test_list_names:
logging.warn('Unable to find test list %s', test_list_globs)
return
test_list = test_list_names[0]
logging.info('Using test list %s', test_list)
old_test_list_data = test_list_data = open(test_list).read()
if clear_factory_environment:
test_list_data = test_list_data.replace(
'_FACTORY_ENVIRONMENT = True',
'_FACTORY_ENVIRONMENT = False')
if clear_password:
test_list_data += '\noptions.engineering_password_sha1 = None\n'
if shopfloor_host:
test_list_data += '\noptions.shopfloor_server_url = "http://%s:%d/"\n' % (
shopfloor_host, shopfloor_port)
if old_test_list_data != test_list_data:
tmp_test_list = tempfile.NamedTemporaryFile(prefix='test_list.', bufsize=0)
tmp_test_list.write(test_list_data)
test_list = tmp_test_list.name
test_list_dir = (
'custom' if 'autotest-private-board' in test_list
else 'test_lists')
Spawn(rsync_command +
[test_list, host + ':/usr/local/factory/%s/' % test_list_dir],
check_call=True, log=True)
Spawn(ssh_command +
[host, 'ln', '-sf', os.path.basename(test_list),
'/usr/local/factory/%s/active' % test_list_dir],
check_call=True, log=True)
return board
def TweakTestLists(args):
"""Tweaks new-style test lists as required by the arguments.
Note that this runs *on the target DUT*, not locally.
Args:
args: The arguments from argparse.
"""
for path in glob.glob(os.path.join(test_lists.TEST_LISTS_PATH, '*.py')):
with open(path) as f:
data = f.read()
def SubLine(variable_re, new_value, string):
"""Replaces certain assignments in the test list with a new value.
We'll replace any line that begins with the given variable name
and an equal sign.
Args:
variable_re: A regular expression matching the names of variables whose
value is to be replaced.
new_value: The new value of the variable. We wrap this in repr() before
substituting.
string: The original contents of the test list.
"""
return re.sub(
r'^(\s*' + # Beginning of line
variable_re + # The name of the variable we're looking for
r'\s*=\s*)' +
r'.+', # The old value
# Keep everything but the value in the original string;
# replace that with new_value.
lambda match_obj: match_obj.group(1) + repr(new_value),
string,
flags=re.MULTILINE)
new_data = data
if args.clear_factory_environment:
new_data = SubLine('factory_environment', False, new_data)
if args.clear_password:
new_data = SubLine('engineering_password_sha1', None, new_data)
if args.shopfloor_host:
new_data = SubLine('shop_floor_host', args.shopfloor_host, new_data)
if args.shopfloor_port:
new_data = SubLine('shop_floor_port', args.shopfloor_port, new_data)
# Write out the file if anything has changed.
if new_data != data:
with open(path, 'w') as f:
logging.info('Modified %s', path)
f.write(new_data)
def main():
parser = argparse.ArgumentParser(
description='Rsync and run Goofy on a remote device.')
parser.add_argument('host', metavar='HOST',
help='host to run on')
parser.add_argument('-a', dest='clear_state', action='store_true',
help='clear Goofy state and logs on device')
parser.add_argument('-e', dest='clear_factory_environment',
action='store_true',
help='set _FACTORY_ENVIRONMENT = False in test_list')
parser.add_argument('-p', dest='clear_password',
action='store_true',
help='remove password from test_list')
parser.add_argument('-s', dest='shopfloor_host',
help='set shopfloor host')
parser.add_argument('--shopfloor_port', dest='shopfloor_port', type=int,
default=8082, help='set shopfloor port')
parser.add_argument('--board', '-b', dest='board',
help='board to use (default: auto-detect')
parser.add_argument('--autotest', dest='autotest', action='store_true',
help='also rsync autotest directory')
parser.add_argument('--norestart', dest='restart', action='store_false',
help="don't restart Goofy")
parser.add_argument('--hwid', action='store_true',
help="update HWID bundle")
parser.add_argument('--run', '-r', dest='run_test',
help="the test to run on device")
parser.add_argument('--test_list',
help=("test list to use (defaults to the one in "
"the board's overlay"))
parser.add_argument('--local', action='store_true',
help=('Rather than syncing the source tree, only '
'perform test list modifications locally. '
'This must be run only on the target device.'))
args = parser.parse_args()
logging.basicConfig(level=logging.INFO)
if args.local:
if in_chroot():
sys.exit('--local must be used only on the target device')
TweakTestLists(args)
return
if not SRCROOT:
sys.exit('goofy_remote must be run from within the chroot')
# Copy testing_rsa into a private file since otherwise ssh will ignore it
testing_rsa = tempfile.NamedTemporaryFile(prefix='testing_rsa.')
testing_rsa.write(open(os.path.join(
SRCROOT, 'src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa')).read())
testing_rsa.flush()
os.fchmod(testing_rsa.fileno(), 0400)
global ssh_command, rsync_command # pylint: disable=W0603
ssh_command = ['ssh',
'-o', 'IdentityFile=%s' % testing_rsa.name,
'-o', 'UserKnownHostsFile=/dev/null',
'-o', 'User=root',
'-o', 'StrictHostKeyChecking=no']
rsync_command = ['rsync', '-e', ' '.join(ssh_command)]
Spawn(['make', '--quiet'], cwd=factory.FACTORY_PATH,
check_call=True, log=True)
board = args.board or GetBoard(args.host)
if args.autotest:
Spawn(rsync_command +
['-aC', '--exclude', 'tests'] +
[os.path.join(SRCROOT, 'src/third_party/autotest/files/client/'),
'%s:/usr/local/autotest/' % args.host],
check_call=True, log=True)
board_dash = board.replace('_', '-')
private_paths = [os.path.join(SRCROOT, 'src', 'private-overlays',
'overlay-%s-private' % board_dash,
'chromeos-base', 'chromeos-factory-board',
'files'),
os.path.join(SRCROOT, 'src', 'private-overlays',
'overlay-variant-%s-private' % board_dash,
'chromeos-base', 'chromeos-factory-board',
'files')]
for private_path in private_paths:
if os.path.isdir(private_path):
Spawn(rsync_command +
['-aC', '--exclude', 'bundle'] +
[private_path + '/', '%s:/usr/local/factory/' % args.host],
check_call=True, log=True)
Spawn(rsync_command +
['-aC', '--exclude', '*.pyc'] +
[os.path.join(factory.FACTORY_PATH, x)
for x in ('bin', 'py', 'py_pkg', 'sh', 'test_lists', 'third_party')] +
['%s:/usr/local/factory' % args.host],
check_call=True, log=True)
SyncTestList(args.host, board, args.test_list,
args.clear_factory_environment, args.clear_password,
args.shopfloor_host, args.shopfloor_port)
# Call goofy_remote on the remote host, allowing it to tweak test lists.
Spawn(ssh_command +
[args.host, 'goofy_remote', '--local'] +
[pipes.quote(x) for x in sys.argv[1:]],
check_call=True, log=True)
if args.hwid:
if not board:
sys.exit('Cannot update hwid without board')
hwid_board = board.split('_')[-1]
chromeos_hwid_path = os.path.join(
os.path.dirname(factory.FACTORY_PATH), 'chromeos-hwid')
Spawn(['./create_bundle', '--version', '3', hwid_board.upper()],
cwd=chromeos_hwid_path, check_call=True, log=True)
Spawn(ssh_command + [args.host, 'bash'],
stdin=open(os.path.join(chromeos_hwid_path,
'hwid_v3_bundle_%s.sh' % hwid_board.upper())),
check_call=True, log=True)
if args.restart:
Spawn(ssh_command +
[args.host, '/usr/local/factory/bin/factory_restart'] +
(['-a'] if args.clear_state else []),
check_call=True, log=True)
if args.run_test:
def GoofyRpcRunTest():
return Spawn(ssh_command +
[args.host, 'goofy_rpc', r'RunTest\(\"%s\"\)' % args.run_test],
check_call=True, log=True)
Retry(max_retry_times=10, interval=5, callback=None, target=GoofyRpcRunTest)
if __name__ == '__main__':
main()