blob: a862f1c943e8bff04a5009c6e28b6c78263764f9 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2017 The Fuchsia 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 argparse
import json
import os
import socket
import subprocess
import sys
import time
import uuid
sys.path.append(os.path.join(os.path.dirname(__file__), 'driver'))
from driver import *
def parse_args(argv):
default_port = 8342
parser = argparse.ArgumentParser()
parser.add_argument(
'--server',
help='Server to connect to. This can be an IPv4 or IPv6 hostname or '
'address (IPv4 will be attempted first), a Zircon device name, or '
'a single colon meaning to use the default device given by netls.',
type=str,
default=':')
parser.add_argument(
'--port',
help=('Port where the test_runner daemon is running. Default: %s' %
default_port),
type=int,
default=default_port)
parser.add_argument(
'--test_file',
help='Path to a json file containing a series of tests to run.',
type=str,
default=None)
parser.add_argument(
'--test_name',
help='Use with --test_file to select a particular test.',
type=str,
default=None)
parser.add_argument(
'--sync',
help='Use with --test_file to copy the dependent files to the device.',
dest='sync',
action='store_true')
parser.add_argument(
'--no-sync',
help=
'Use with --test_file to disable copying the dependent files to the device.',
dest='sync',
action='store_false')
parser.set_defaults(sync=False, ip_version=socket.AF_INET6)
parser.add_argument('command', nargs='*')
args = parser.parse_args()
if args.test_name and not args.test_file:
parser.error('--test_file is required in order to use --test_name')
if args.test_file is None and len(args.command) == 0:
parser.error(
'Either a single test command, or --test_file must be specified')
return args
def get_ip_info(server, port):
# Host names will be resolved with IPv4 first. So for example, "localhost"
# will return "127.0.0.1", not "::1".
for version in [socket.AF_INET, socket.AF_INET6]:
try:
return (version, socket.getaddrinfo(
server, port, version, socket.SOCK_STREAM)[0][-1])
except socket.gaierror:
pass
return socket.AF_INET6, None
def main(argv):
args = parse_args(argv)
if len(args.command) > 0:
tests = [{'name': args.command[0], 'exec': ' '.join(args.command)}]
else:
config = json.load(open(args.test_file))
tests = get_tests(config, args.test_name)
fuchsia_tools = FuchsiaTools(os.environ)
ip_version, address = get_ip_info(args.server, args.port)
if not address:
ip_version = socket.AF_INET6
if args.server == ':':
netls = fuchsia_tools.netls()
device = get_default_device(subprocess.check_output(netls))
else:
device = args.server
netaddr = fuchsia_tools.netaddr(device)
host = subprocess.check_output(netaddr).strip()
address = socket.getaddrinfo(
host, args.port, socket.AF_INET6, socket.SOCK_STREAM)[0][-1]
sock = socket.socket(ip_version)
# Allow for retries when connecting, since QEMU might not be listening yet.
Log.line('connecting', repr(address))
failed = 0
while True:
try:
sock.connect(address)
break
except socket.error:
failed += 1
if failed == 10:
print 'Failed to connect. Is test_runner running on Fuchsia?'
return 1
Log.line('connecting', "Couldn't connect, retrying in 3 seconds")
time.sleep(3)
driver = Driver()
# Sync all files at once because sftp has a 1.3 second startup overhead
if args.sync:
file_pairs = []
for test in tests:
for filename, remote_dir in test['copy']:
file_pairs.append((filename, remote_dir + '/' + filename))
command_tuple = fuchsia_tools.sftp( '[' + host + ']', file_pairs)
with open(os.devnull, 'w') as devnull:
status = subprocess.call(command_tuple[0], stdout=devnull)
command_tuple[1].close()
if status != 0:
print >> sys.stderr, 'error: sftp failed'
return status
for test in tests:
id_ = str(uuid.uuid4())
try:
sock.send(driver.start_test(id_, test) + '\n')
driver.wait_for_teardown(sock.makefile())
except (socket.error, MissingTeardown):
# MissingTeardown is likely caused by a closed connection.
print ('Lost connection to Fuchsia. '
'Check the log from the device or emulator.')
return 1
driver.print_summary()
if driver.failed:
# Unique exit code for when tests failed but there was no internal error.
# Already taken: 1 for Python exception, 2 for argument error.
return 3
else:
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))