blob: 9631bfa1e1029a71740f77f5b871a0c61e33f5a3 [file] [log] [blame]
# Copyright 2015 The Chromium 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 functools
import json
import os
import re
import sys
from recipe_engine.types import freeze
from recipe_engine import recipe_api
THIS_DIR = os.path.dirname(__file__)
sys.path.append(os.path.join(os.path.dirname(THIS_DIR)))
# adb path relative to out dir (e.g. out/Release)
ADB_PATH = '../../third_party/android_sdk/public/platform-tools/adb'
ANDROID_CIPD_PACKAGES = [
("bin",
"infra/tools/luci/logdog/butler/${platform}",
"git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c",
)
]
def generate_tests(phase, bot, platform_name, build_out_dir, checkout_path,
is_tryserver):
"""Generate a list of tests to run on a bot.
Args:
phase: string to distinguish the phase of a builder (used only for
more_configs builders).
bot: string with the name of the bot (e.g. 'linux_compile_rel').
platform: String representing the platform on which tests
will run. Possible values are: "linux", "mac", "win".
build_out_dir: the out/ dir of the builder.
checkout_path: the path to the checkout on the builder.
is_tryserver: True if the tests needs to be generated for a tryserver.
Returns:
A list of steps.WebRtcIsolatedGtest to compile and run on a bot.
"""
assert platform_name in ('linux', 'mac', 'win')
tests = []
test_suite = bot.test_suite
if test_suite in ('webrtc', 'webrtc_and_baremetal'):
tests += [
SwarmingDesktopTest('audio_decoder_unittests'),
SwarmingDesktopTest('common_audio_unittests'),
SwarmingDesktopTest('common_video_unittests'),
SwarmingDesktopTest('low_bandwidth_audio_test'),
SwarmingDesktopTest('modules_tests', shards=2),
SwarmingDesktopTest('modules_unittests', shards=6),
SwarmingDesktopTest('peerconnection_unittests', shards=4),
SwarmingDesktopTest('rtc_media_unittests'),
SwarmingDesktopTest('rtc_pc_unittests'),
SwarmingDesktopTest('rtc_stats_unittests'),
SwarmingDesktopTest('rtc_unittests', shards=6),
SwarmingDesktopTest('slow_tests'),
SwarmingDesktopTest('system_wrappers_unittests'),
SwarmingDesktopTest('test_support_unittests'),
SwarmingDesktopTest('tools_unittests'),
SwarmingDesktopTest('video_engine_tests', shards=4),
SwarmingDesktopTest('voip_unittests'),
SwarmingDesktopTest('webrtc_nonparallel_tests'),
]
if test_suite == 'webrtc_and_baremetal':
baremetal_test = functools.partial(
SwarmingDesktopTest,
dimensions=bot.config['baremetal_swarming_dimensions'])
tests.append(baremetal_test('video_capture_tests'))
# Cover tests only running on perf tests on our trybots:
if is_tryserver:
if platform_name == 'linux':
tests.append(baremetal_test('isac_fix_test'))
is_win_clang = (
platform_name == 'win' and
'clang' in bot.recipe_config['chromium_config'])
# TODO(kjellander): Enable on Mac when bugs.webrtc.org/7322 is fixed.
# TODO(oprypin): Enable on MSVC when bugs.webrtc.org/9290 is fixed.
if platform_name == 'linux' or is_win_clang:
tests.append(baremetal_test('webrtc_perf_tests', args=[
'--force_fieldtrials=WebRTC-QuickPerfTest/Enabled/',
'--nologs',
]))
if test_suite == 'desktop_perf_swarming':
tests += [
SwarmingPerfTest('isac_fix_test'),
SwarmingPerfTest('low_bandwidth_audio_perf_test'),
SwarmingPerfTest(
'webrtc_perf_tests',
args=[
'--test_artifacts_dir=${ISOLATED_OUTDIR}',
'--save_worst_frame',
'--nologs',
]),
]
if test_suite == 'android_perf_swarming':
tests.append(
SwarmingAndroidPerfTest(
'low_bandwidth_audio_perf_test',
args=[
'--android',
'--adb-path',
ADB_PATH,
]))
tests.append(
SwarmingAndroidPerfTest(
'webrtc_perf_tests',
args=[
'--save_worst_frame',
'--nologs',
]))
if test_suite == 'android':
tests += [
SwarmingAndroidTest('AppRTCMobile_test_apk'),
SwarmingAndroidTest('android_instrumentation_test_apk'),
SwarmingAndroidTest('audio_decoder_unittests'),
SwarmingAndroidTest('common_audio_unittests'),
SwarmingAndroidTest('common_video_unittests'),
SwarmingAndroidTest('modules_tests', shards=2),
SwarmingAndroidTest('modules_unittests', shards=6),
SwarmingAndroidTest('peerconnection_unittests', shards=4),
SwarmingAndroidTest('rtc_stats_unittests'),
SwarmingAndroidTest('rtc_unittests', shards=6),
SwarmingAndroidTest('system_wrappers_unittests'),
SwarmingAndroidTest('test_support_unittests'),
SwarmingAndroidTest('tools_unittests'),
SwarmingAndroidTest('video_engine_tests', shards=4),
SwarmingAndroidTest('voip_unittests'),
SwarmingAndroidTest('webrtc_nonparallel_tests'),
AndroidJunitTest('android_examples_junit_tests'),
AndroidJunitTest('android_sdk_junit_tests'),
]
if bot.should_test_android_studio_project_generation:
tests.append(
PythonTest(
test='gradle_project_test',
script=checkout_path.join('examples', 'androidtests',
'gradle_project_test.py'),
args=[build_out_dir],
env={'GOMA_DISABLED': True}))
if is_tryserver:
tests.append(
SwarmingAndroidTest(
'webrtc_perf_tests',
args=[
'--force_fieldtrials=WebRTC-QuickPerfTest/Enabled/',
'--nologs',
]))
if test_suite == 'ios':
tests += [
IosTest('apprtcmobile_tests', xctest=True),
IosTest('sdk_unittests', xctest=True),
IosTest('sdk_framework_unittests', xctest=True),
IosTest('audio_decoder_unittests'),
IosTest('common_audio_unittests'),
IosTest('common_video_unittests'),
IosTest('modules_tests'),
IosTest('modules_unittests'),
IosTest('rtc_media_unittests'),
IosTest('rtc_pc_unittests'),
IosTest('rtc_stats_unittests'),
IosTest('rtc_unittests'),
IosTest('system_wrappers_unittests'),
IosTest('test_support_unittests'),
IosTest('tools_unittests'),
IosTest('video_capture_tests'),
IosTest('video_engine_tests'),
IosTest('voip_unittests'),
IosTest('webrtc_nonparallel_tests'),
]
if test_suite == 'ios_device':
tests += [
IosTest('common_audio_unittests'),
IosTest('common_video_unittests'),
IosTest('modules_tests'),
IosTest('modules_unittests'),
IosTest('rtc_pc_unittests'),
IosTest('rtc_stats_unittests'),
IosTest('system_wrappers_unittests'),
IosTest('test_support_unittests'),
IosTest('tools_unittests'),
IosTest('video_capture_tests'),
IosTest('video_engine_tests'),
]
if test_suite == 'ios_perf':
tests += [
IosTest(
'webrtc_perf_tests',
args=[
'--write_perf_output_on_ios',
'--nologs',
]),
]
if test_suite == 'more_configs':
if 'no_sctp' in phase:
tests.append(SwarmingDesktopTest('peerconnection_unittests'))
return tests
class WebRtcIsolatedGtest(object):
"""Triggers an isolated task to run a GTest binary, and collects the results.
This class is based off Chromium's SwarmingIsolatedScriptTest, but strips out
the parts we don't need.
"""
def __init__(self,
name,
dimensions=None,
args=None,
shards=1,
cipd_packages=None,
idempotent=None,
result_handlers=None):
"""Constructs an instance of WebRtcIsolatedGtest.
Args:
name: Displayed name of the test.
dimensions (dict of str: str): Requested dimensions of the test.
args: List of arguments to pass as "Extra Args" to the swarming task.
These are passed to whatever runs first in the swarming job, like
build/android/test_runner.py on Android or gtest-parallel on Desktop.
(these often pass through args to the test binary).
shards: Number of shards to trigger.
cipd_packages: [(str, str, str)] list of 3-tuples containing cipd
package root, package name, and package version.
idempotent: Whether to mark the task as idempotent. A value of None will
cause chromium_swarming/api.py to apply its default_idempotent val.
result_handlers: a list of callbacks that take (api, step_result,
has_valid_results) and take some action to it (typically writing
something into the step result).
"""
self._name = name
self._dimensions = dimensions or {}
self._args = args or []
self._shards = shards
self._cipd_packages = cipd_packages
self._idempotent = idempotent
self._result_handlers = result_handlers or []
self._raw_cmd = []
self._relative_cwd = None
self._task = None
self._has_collected = False
@property
def isolate_target(self):
return self._name
@property
def name(self):
return self._name
@property
def step_name(self):
return self._name
@property
def raw_cmd(self): # pragma: no cover
return self._raw_cmd
@raw_cmd.setter
def raw_cmd(self, value):
self._raw_cmd = value
@property
def relative_cwd(self): # pragma: no cover
return self._relative_cwd
@relative_cwd.setter
def relative_cwd(self, value):
self._relative_cwd = value
@property
def runs_on_swarming(self):
return True
def pre_run(self, api):
"""Launches the test on Swarming."""
assert self._task is None, (
'Test %s was already triggered' % self.step_name) # pragma no cover
# *.isolated may be missing if *_run target is misconfigured.
isolated = api.isolate.isolated_tests.get(self.isolate_target)
if not isolated: # pragma no cover
return api.python.failing_step(
'[error] %s' % self.step_name,
'*.isolated file for target %s is missing' % self.isolate_target)
self._task = self.create_task(api, isolated)
return api.chromium_swarming.trigger_task(self._task)
@recipe_api.composite_step
def run(self, api):
"""Waits for launched test to finish and collects the results."""
assert not self._has_collected, ( # pragma no cover
'Results of %s were already collected' % self.step_name)
self._has_collected = True
step_result, has_valid_results = api.chromium_swarming.collect_task(
self._task, allow_missing_json=True)
for handler in self._result_handlers:
handler(api, step_result, has_valid_results)
return step_result
def create_task(self, api, isolated):
task = api.chromium_swarming.task(
name=self.step_name,
raw_cmd=self._raw_cmd,
relative_cwd=self._relative_cwd)
task_slice = task.request[0]
task.request = task.request.with_slice(0, task_slice)
self._apply_swarming_task_config(task, api, isolated)
return task
def _apply_swarming_task_config(self, task, api, isolated):
"""Applies shared configuration for swarming tasks.
"""
task.shards = self._shards
task.shard_indices = range(task.shards)
task.build_properties = api.chromium.build_properties
task_slice = task.request[0]
if self._idempotent is not None:
task_slice = task_slice.with_idempotent(self._idempotent)
ensure_file = task_slice.cipd_ensure_file
if self._cipd_packages:
for package in self._cipd_packages:
ensure_file.add_package(package[1], package[2], package[0])
task_slice = (
task_slice.with_cipd_ensure_file(ensure_file).with_isolated(isolated))
task_dimensions = task_slice.dimensions
for k, v in self._dimensions.iteritems():
task_dimensions[k] = v
# Set default value for os.
if 'os' not in task_dimensions:
task_dimensions['os'] = api.chromium_swarming.prefered_os_dimension(
api.platform.name) # pragma no cover
task_slice = task_slice.with_dimensions(**task_dimensions)
task.extra_args.extend(self._args)
task.request = (
task.request.with_slice(0, task_slice).with_name(self.step_name))
return task
def InvalidResultsHandler(api, step_result, has_valid_results):
if (api.step.active_result.retcode == 0 and not has_valid_results):
# This failure won't be caught automatically. Need to manually
# raise it as a step failure.
raise api.step.StepFailure(
api.test_utils.INVALID_RESULTS_MAGIC) # pragma no cover
def LogcatHandler(api, step_result, has_valid_results):
del has_valid_results
task_output_dir = api.step.active_result.raw_io.output_dir
result = ""
for file_name, contents in task_output_dir.iteritems():
if file_name.endswith('logcats'): # pragma: no cover
result += contents
step_result.presentation.logs['logcats'] = result.splitlines()
def SwarmingDesktopTest(name, **kwargs):
return WebRtcIsolatedGtest(
name, result_handlers=[InvalidResultsHandler], **kwargs)
def SwarmingPerfTest(name, args=None, **kwargs):
def UploadToPerfDashboardHandler(api, step_result, has_valid_results):
del has_valid_results
api.webrtc.upload_to_perf_dashboard(name, step_result)
handlers = [InvalidResultsHandler, UploadToPerfDashboardHandler]
args = list(args or [])
# This flag is translated to --isolated_script_test_perf_output in
# gtest-parallel_wrapper.py and flags_compatibility.py. Why not pass the right
# flag right away? Unfortunately Chromium's android/test_runner.py does
# magical treatment of the dashed version of the flag, and we need that to
# get a writable out dir on Android, so we must have this translation step.
args.extend([
('--isolated-script-test-perf-output='
'${ISOLATED_OUTDIR}/perftest-output.pb'),
])
# Perf tests are marked as not idempotent, which means they're re-run if they
# did not change this build. This will give the dashboard some more variance
# data to work with."""
return WebRtcIsolatedGtest(
name,
args=args,
cipd_packages=ANDROID_CIPD_PACKAGES,
shards=1,
idempotent=False,
result_handlers=handlers,
**kwargs)
def SwarmingAndroidTest(name, **kwargs):
return WebRtcIsolatedGtest(
name,
cipd_packages=ANDROID_CIPD_PACKAGES,
result_handlers=[InvalidResultsHandler, LogcatHandler],
**kwargs)
def SwarmingAndroidPerfTest(name, args=None, **kwargs):
def UploadToPerfDashboardHandler(api, step_result, has_valid_results):
del has_valid_results
api.webrtc.upload_to_perf_dashboard(name, step_result)
handlers = [
InvalidResultsHandler, LogcatHandler, UploadToPerfDashboardHandler
]
args = list(args or [])
# See SwarmingDesktopPerfTest for more details why we pass this rather than
# --isolated_script_test_perf_output.
args.extend([
('--isolated-script-test-perf-output='
'${ISOLATED_OUTDIR}/perftest-output.pb'),
])
return WebRtcIsolatedGtest(
name,
args=args,
shards=1,
idempotent=False,
result_handlers=handlers,
**kwargs)
class Test(object):
def __init__(self, test, name=None):
self._test = test
self._name = name or test
def pre_run(self, api):
del api
return []
def run(self, api): # pragma: no cover
del api
return []
@property
def runs_on_swarming(self): # pragma: no cover
return False
class PythonTest(Test):
def __init__(self, test, script, args, env):
super(PythonTest, self).__init__(test)
self._script = script
self._args = args
self._env = env or {}
self._name = test
@property
def name(self):
return self._name
def run(self, api):
with api.depot_tools.on_path():
with api.context(env=self._env):
return api.python(self._test, self._script, self._args)
@property
def runs_on_swarming(self):
return False
class AndroidJunitTest(Test):
"""Runs an Android Junit test."""
@property
def name(self):
return self._name
def run(self, api):
return api.chromium_android.run_java_unit_test_suite(self._name)
@property
def runs_on_swarming(self):
return False
class IosTest(object):
"""A fake shell of an iOS test. It is only read by apply_ios_config."""
def __init__(self, name, args=None, xctest=False):
self._name = name
self.config = {'app': name}
if args:
self.config['test args'] = args
if xctest:
self.config['xctest'] = True
# TODO(crbug.com/1006881): "xctest" indicates how to run the targets but
# not how to parse test outputs since recent iOS test runner changes.
# This arg is needed for outputs to be parsed correctly.
self.config['xcode parallelization'] = True
@property
def name(self):
return self._name
@property
def runs_on_swarming(self): # pragma: no cover
# Even if this sounds unrelated from Swarming, this is just a shell for an
# iOS test, the real test that will run is an instance of Chromium's
# SwarmingIosTest.
return True