blob: cae01ff6050c9c50e89a02d72c8b5bf44dcaaf08 [file] [log] [blame]
# Copyright 2017 The Cobalt Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Contains utilities to create black box tests and run them."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import importlib
import logging
import random
import socket
import sys
import unittest
import _env # pylint: disable=unused-import
from cobalt.black_box_tests import black_box_cobalt_runner
from proxy_server import ProxyServer
from starboard.tools import abstract_launcher
from starboard.tools import build
from starboard.tools import command_line
from starboard.tools import log_level
_DISABLED_BLACKBOXTEST_CONFIGS = [
'android-arm/devel',
'android-arm64/devel',
'android-x86/devel',
'raspi-0/devel',
]
_PORT_SELECTION_RETRY_LIMIT = 10
_PORT_SELECTION_RANGE = [5000, 7000]
# List of blocked ports.
_RESTRICTED_PORTS = [6000, 6665, 6666, 6667, 6668, 6669, 6697]
_SERVER_EXIT_TIMEOUT_SECONDS = 30
_SOCKET_SUCCESS = 0
# These tests can only be run on platforms whose app launcher can send suspend/
# resume signals.
_TESTS_NEEDING_SYSTEM_SIGNAL = [
'cancel_sync_loads_when_suspended',
'pointer_test',
'preload_font',
'preload_visibility',
'preload_launch_parameter',
'signal_handler_doesnt_crash',
'suspend_visibility',
'timer_hit_after_preload',
'timer_hit_in_preload',
]
# These tests only need app launchers with webdriver.
_TESTS_NO_SIGNAL = [
'allow_eval',
'compression_test',
'disable_eval_with_csp',
'persistent_cookie',
'web_debugger',
'web_platform_tests',
]
# These tests can only be run on platforms whose app launcher can send deep
# links.
_TESTS_NEEDING_DEEP_LINK = [
'deep_links',
]
# Location of test files.
_TEST_DIR_PATH = 'cobalt.black_box_tests.tests.'
# Platform configuration and device information parameters.
_launcher_params = None
# Binding address used to create the test server.
_binding_address = None
# Port used to create the web platform test http server.
_wpt_http_port = None
class BlackBoxTestCase(unittest.TestCase):
"""Base class for Cobalt black box test cases."""
def __init__(self, *args, **kwargs):
super(BlackBoxTestCase, self).__init__(*args, **kwargs)
self.launcher_params = _launcher_params
self.platform_config = build.GetPlatformConfig(_launcher_params.platform)
self.cobalt_config = self.platform_config.GetApplicationConfiguration(
'cobalt')
@classmethod
def setUpClass(cls):
super(BlackBoxTestCase, cls).setUpClass()
logging.info('Running %s', cls.__name__)
@classmethod
def tearDownClass(cls):
super(BlackBoxTestCase, cls).tearDownClass()
logging.info('Done %s', cls.__name__)
def CreateCobaltRunner(self, url, target_params=None):
all_target_params = list(target_params) if target_params else []
if _launcher_params.target_params is not None:
all_target_params += _launcher_params.target_params
new_runner = black_box_cobalt_runner.BlackBoxCobaltRunner(
launcher_params=_launcher_params,
url=url,
target_params=all_target_params)
return new_runner
def GetBindingAddress(self):
return _binding_address
def GetWptHttpPort(self):
return _wpt_http_port
def LoadTests(launcher_params):
launcher = abstract_launcher.LauncherFactory(
launcher_params.platform,
'cobalt',
launcher_params.config,
device_id=launcher_params.device_id,
target_params=None,
output_file=None,
out_directory=launcher_params.out_directory,
loader_platform=launcher_params.loader_platform,
loader_config=launcher_params.loader_config,
loader_out_directory=launcher_params.loader_out_directory)
test_targets = _TESTS_NO_SIGNAL
if launcher.SupportsSuspendResume():
test_targets += _TESTS_NEEDING_SYSTEM_SIGNAL
if launcher.SupportsDeepLink():
test_targets += _TESTS_NEEDING_DEEP_LINK
test_suite = unittest.TestSuite()
for test in test_targets:
test_suite.addTest(unittest.TestLoader().loadTestsFromModule(
importlib.import_module(_TEST_DIR_PATH + test)))
return test_suite
class BlackBoxTests(object):
"""Helper class to run all black box tests and return results."""
def __init__(self,
server_binding_address,
proxy_address=None,
proxy_port=None,
test_name=None,
wpt_http_port=None,
device_ips=None,
device_id=None):
# Setup global variables used by test cases.
global _launcher_params
_launcher_params = command_line.CreateLauncherParams()
# Keep other modules from seeing these args.
sys.argv = sys.argv[:1]
global _binding_address
_binding_address = server_binding_address
# Port used to create the web platform test http server. If not specified,
# a random free port is used.
if wpt_http_port is None:
wpt_http_port = str(self.GetUnusedPort([server_binding_address]))
global _wpt_http_port
_wpt_http_port = wpt_http_port
# TODO: Remove generation of --dev_servers_listen_ip once executable will
# be able to bind correctly with incomplete support of IPv6
if device_id and IsValidIpAddress(device_id):
_launcher_params.target_params.append(
'--dev_servers_listen_ip={}'.format(device_id))
elif IsValidIpAddress(server_binding_address):
_launcher_params.target_params.append(
'--dev_servers_listen_ip={}'.format(server_binding_address))
_launcher_params.target_params.append(
'--web-platform-test-server=http://web-platform.test:{}'.format(
wpt_http_port))
# Port used to create the proxy server. If not specified, a random free
# port is used.
if proxy_port is None:
proxy_port = str(self.GetUnusedPort([server_binding_address]))
if proxy_address is None:
proxy_address = server_binding_address
_launcher_params.target_params.append('--proxy=%s:%s' %
(proxy_address, proxy_port))
self.proxy_port = proxy_port
self.test_name = test_name
self.device_ips = device_ips
# Test domains used in web platform tests to be resolved to the server
# binding address.
hosts = [
'web-platform.test', 'www.web-platform.test', 'www1.web-platform.test',
'www2.web-platform.test', 'xn--n8j6ds53lwwkrqhv28a.web-platform.test',
'xn--lve-6lad.web-platform.test'
]
self.host_resolve_map = {host: server_binding_address for host in hosts}
def Run(self):
if self.proxy_port == '-1':
return 1
# Temporary means to determine if we are running on CI
# TODO: Update to IS_CI environment variable or similar
out_dir = _launcher_params.out_directory
is_ci = out_dir and 'mh_lab' in out_dir # pylint: disable=unsupported-membership-test
target = (_launcher_params.platform, _launcher_params.config)
if is_ci and '{}/{}'.format(*target) in _DISABLED_BLACKBOXTEST_CONFIGS:
logging.warning('Blackbox tests disabled for platform:%s config:%s',
*target)
return 0
logging.info('Using proxy port: %s', self.proxy_port)
with ProxyServer(
port=self.proxy_port,
host_resolve_map=self.host_resolve_map,
client_ips=self.device_ips):
if self.test_name:
suite = unittest.TestLoader().loadTestsFromModule(
importlib.import_module(_TEST_DIR_PATH + self.test_name))
else:
suite = LoadTests(_launcher_params)
return_code = not unittest.TextTestRunner(
verbosity=0, stream=sys.stdout).run(suite).wasSuccessful()
return return_code
def GetUnusedPort(self, addresses):
"""Find a free port on the list of addresses by pinging with sockets."""
if not addresses:
logging.error('Can not find unused port on invalid addresses.')
return -1
socks = []
for address in addresses:
socks.append((address, socket.socket(socket.AF_INET, socket.SOCK_STREAM)))
try:
for _ in range(_PORT_SELECTION_RETRY_LIMIT):
port = random.choice([
i for i in range(_PORT_SELECTION_RANGE[0], _PORT_SELECTION_RANGE[1])
if i not in _RESTRICTED_PORTS
])
unused = True
for sock in socks:
result = sock[1].connect_ex((sock[0], port))
if result == _SOCKET_SUCCESS:
unused = False
break
if unused:
return port
logging.error('Can not find unused port on addresses within %s attempts.',
_PORT_SELECTION_RETRY_LIMIT)
return -1
finally:
for sock in socks:
sock[1].close()
def IsValidIpAddress(address):
"""Checks if address is valid IP address."""
return IsValidIpv4Address(address) or IsValidIpv6Address(address)
def IsValidIpv4Address(address):
"""Checks if address is valid IPv4 address."""
try:
socket.inet_pton(socket.AF_INET, address)
return True
except socket.error: # not a valid address
return False
def IsValidIpv6Address(address):
"""Checks if address is valid IPv6 address."""
try:
socket.inet_pton(socket.AF_INET6, address)
return True
except socket.error: # not a valid address
return False
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose', required=False, action='store_true')
parser.add_argument(
'--server_binding_address',
default='127.0.0.1',
help='Binding address used to create the test server.')
parser.add_argument(
'--proxy_address',
default=None,
help=('Address to the proxy server that all black box'
'tests are run through. If not specified, the'
'server binding address is used.'))
parser.add_argument(
'--proxy_port',
default=None,
help=('Port used to create the proxy server that all'
'black box tests are run through. If not'
'specified, a random free port is used.'))
parser.add_argument(
'--test_name',
default=None,
help=('Name of test to be run. If not specified, all '
'tests are run.'))
parser.add_argument(
'--wpt_http_port',
default=None,
help=('Port used to create the web platform test http'
'server. If not specified, a random free port is'
'used.'))
parser.add_argument(
'--device_id',
default=None,
help=('ID of test device to connect. If specified, it will be passed '
'as --dev_servers_listen_ip param on the test device.'))
parser.add_argument(
'--device_ips',
default=None,
nargs='*',
help=('IPs of test devices that will be allowed to connect. If not '
'specified, all IPs will be allowed to connect.'))
args, _ = parser.parse_known_args()
log_level.InitializeLogging(args)
test_object = BlackBoxTests(args.server_binding_address, args.proxy_address,
args.proxy_port, args.test_name,
args.wpt_http_port, args.device_ips,
args.device_id)
sys.exit(test_object.Run())
if __name__ == '__main__':
# Running this script on the command line and importing this file are
# different and create two modules.
# Import this module to ensure we are using the same module as the tests to
# make module-owned variables like launcher_param accessible to the tests.
main_module = importlib.import_module(
'cobalt.black_box_tests.black_box_tests')
sys.exit(main_module.main())