blob: fb6147c9c1cda794c8361727ee52e863fbd9c7a5 [file] [log] [blame]
# Copyright 2020 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.
from recipe_engine.recipe_api import Property
DEPS = [
'flutter/bucket_util',
'flutter/devicelab_osx_sdk',
'flutter/flutter_deps',
'flutter/logs_util',
'flutter/repo_util',
'flutter/os_utils',
'flutter/osx_sdk',
'flutter/retry',
'flutter/test_utils',
'recipe_engine/buildbucket',
'recipe_engine/cas',
'recipe_engine/context',
'recipe_engine/file',
'recipe_engine/path',
'recipe_engine/platform',
'recipe_engine/properties',
'recipe_engine/raw_io',
'recipe_engine/runtime',
'recipe_engine/service_account',
'recipe_engine/step',
'recipe_engine/swarming',
]
# Fifteen minutes
MAX_TIMEOUT_SECS = 30 * 60
def RunSteps(api):
task_name = api.properties.get("task_name")
if not task_name:
raise ValueError('A task_name property is required')
api.os_utils.print_pub_certs()
flutter_path = api.path.mkdtemp().join('flutter sdk')
api.repo_util.checkout(
'flutter',
flutter_path,
api.properties.get('git_url'),
api.properties.get('git_ref'),
)
env, env_prefixes = api.repo_util.flutter_environment(flutter_path)
api.logs_util.initialize_logs_collection(env)
with api.step.nest('Dependencies'):
api.flutter_deps.flutter_engine(env, env_prefixes)
deps = api.properties.get('dependencies', [])
# TODO: If deps contains dart_sdk and we are running a local engine,
# we don't want to fetch it with cipd, so don't fetch it with required_deps
api.flutter_deps.required_deps(env, env_prefixes, deps)
api.flutter_deps.vpython(env, env_prefixes, 'latest')
devicelab_path = flutter_path.join('dev', 'devicelab')
git_branch = api.buildbucket.gitiles_commit.ref.replace('refs/heads/', '')
# Create tmp file to store results in
results_path = api.path.mkdtemp(prefix='results').join('results')
# Run test
runner_params = [
'-t', task_name, '--results-file', results_path, '--luci-builder',
api.properties.get('buildername')
]
if 'LOCAL_ENGINE' in env:
runner_params.extend(['--local-engine', env['LOCAL_ENGINE']])
# LUCI git checkouts end up in a detached HEAD state, so branch must
# be passed from gitiles -> test runner -> Cocoon.
if git_branch:
# git_branch is set only when the build was triggered by buildbucket.
runner_params.extend(['--git-branch', git_branch])
test_status = ''
with api.context(env=env, env_prefixes=env_prefixes, cwd=devicelab_path):
api.retry.step(
'flutter doctor',
['flutter', 'doctor'],
max_attempts=3,
timeout=300,
)
api.step('pub get', ['pub', 'get'], infra_step=True)
dep_list = {d['dependency']: d.get('version') for d in deps}
if dep_list.has_key('xcode'):
api.os_utils.clean_derived_data()
if str(api.swarming.bot_id).startswith('flutter-devicelab'):
with api.devicelab_osx_sdk('ios'):
test_status = mac_test(
api, env, env_prefixes, flutter_path, task_name, runner_params
)
else:
with api.osx_sdk('ios'):
test_status = mac_test(
api, env, env_prefixes, flutter_path, task_name, runner_params
)
else:
with api.context(env=env, env_prefixes=env_prefixes):
api.retry.step(
'flutter doctor',
['flutter', 'doctor', '--verbose'],
max_attempts=3,
timeout=300,
)
test_runner_command = ['dart', 'bin/test_runner.dart', 'test']
test_runner_command.extend(runner_params)
try:
test_status = api.test_utils.run_test(
'run %s' % task_name,
test_runner_command,
timeout_secs=MAX_TIMEOUT_SECS
)
finally:
api.logs_util.upload_logs(task_name)
# This is to clean up leaked processes.
api.os_utils.kill_processes()
if test_status == 'flaky':
check_flaky(api)
with api.context(env=env, env_prefixes=env_prefixes, cwd=devicelab_path):
uploadResults(
api, results_path, test_status == 'flaky', git_branch,
api.properties.get('buildername')
)
uploadMetricsToCas(api, results_path)
def mac_test(api, env, env_prefixes, flutter_path, task_name, runner_params):
"""Runs a devicelab mac test."""
api.flutter_deps.gems(
env, env_prefixes, flutter_path.join('dev', 'ci', 'mac')
)
api.retry.step(
'flutter doctor', ['flutter', 'doctor', '--verbose'],
max_attempts=3,
timeout=300
)
api.os_utils.dismiss_dialogs()
api.os_utils.shutdown_simulators()
with api.context(env=env, env_prefixes=env_prefixes):
resource_name = api.resource('runner.sh')
api.step('Set execute permission', ['chmod', '755', resource_name])
test_runner_command = [resource_name]
test_runner_command.extend(runner_params)
try:
test_status = api.test_utils.run_test(
'run %s' % task_name,
test_runner_command,
timeout_secs=MAX_TIMEOUT_SECS
)
finally:
api.logs_util.upload_logs(task_name)
# This is to clean up leaked processes.
api.os_utils.kill_processes()
if test_status == 'flaky':
check_flaky(api)
return test_status
def check_flaky(api):
if api.platform.is_win:
api.step(
'check flaky',
['powershell.exe', 'echo "test run is flaky"'],
infra_step=True,
)
else:
api.step(
'check flaky',
['echo', 'test run is flaky'],
infra_step=True,
)
def shouldNotUpdate(api, builder_name, git_branch):
"""Check if a post submit builder should update results to cocoon.
Test results will be sent to cocoon only when test is post-submit, when test
runs in prod pool, and when test is from master branch.
"""
supported_branches = ['master']
if api.runtime.is_experimental or api.properties.get(
'git_url') or 'staging' in builder_name or git_branch not in supported_branches:
return True
else:
return False
def uploadResults(
api,
results_path,
is_test_flaky,
git_branch,
builder_name,
test_status='Succeeded'
):
"""Upload DeviceLab test results to Cocoon.
luci-auth only gurantees a service account token life of 3 minutes. To work
around this limitation, results uploading is separate from the the test run.
Only post-submit tests upload results to Cocoon. If `upload_metrics: true`, generated
test metrics will be uploaded to Cocoon. Otherwise, only test flaky status will be
updated to Cocoon.
"""
if shouldNotUpdate(api, builder_name, git_branch):
return
runner_params = ['--test-flaky', is_test_flaky]
if not api.properties.get('upload_metrics'):
runner_params.extend([
'--git-branch', git_branch, '--luci-builder', builder_name,
'--test-status', test_status
])
else:
runner_params.extend(['--results-file', results_path])
with api.step.nest('Upload metrics'):
service_account = api.service_account.default()
access_token = service_account.get_access_token()
access_token_path = api.path.mkstemp()
api.file.write_text(
"write token", access_token_path, access_token, include_log=False
)
runner_params.extend(['--service-account-token-file', access_token_path])
upload_command = ['dart', 'bin/test_runner.dart', 'upload-metrics']
upload_command.extend(runner_params)
api.step('upload results', upload_command, infra_step=True)
def uploadMetricsToCas(api, results_path):
"""Upload DeviceLab test performance metrics to CAS.
The hash of the CAS (content-addressed storage) upload is added as an
output property to the build.
"""
if not api.properties.get('upload_metrics_to_cas'):
return
cas_hash = api.cas.archive(
'Upload metrics to CAS', api.path.dirname(results_path), results_path
)
api.step.active_result.presentation.properties['results_cas_hash'] = cas_hash
def GenTests(api):
checkout_path = api.path['cleanup'].join('tmp_tmp_1', 'flutter sdk')
yield api.test(
"no-task-name",
api.expect_exception('ValueError'),
)
yield api.test(
"basic",
api.properties(buildername='Linux abc', task_name='abc'),
api.repo_util.flutter_environment_data(checkout_dir=checkout_path),
api.step_data(
'run abc',
stdout=api.raw_io.output_text('#flaky\nthis is a flaky\nflaky: true'),
retcode=0
),
api.buildbucket.ci_build(
project='test',
git_repo='git.example.com/test/repo',
),
api.platform.name('linux'),
api.runtime(is_experimental=True),
)
yield api.test(
"xcode-devicelab",
api.properties(
buildername='Mac abc',
task_name='abc',
dependencies=[{'dependency': 'xcode'}]
), api.repo_util.flutter_environment_data(checkout_dir=checkout_path),
api.buildbucket.ci_build(
git_ref='refs/heads/master',
),
api.step_data(
'run abc',
stdout=api.raw_io.output_text('#flaky\nthis is a flaky\nflaky: true'),
retcode=0
), api.swarming.properties(bot_id='flutter-devicelab-mac-1')
)
yield api.test(
"xcode-chromium-mac",
api.properties(
buildername='Mac abc',
task_name='abc',
dependencies=[{'dependency': 'xcode'}]
),
api.buildbucket.ci_build(
git_ref='refs/heads/master',
),
api.repo_util.flutter_environment_data(checkout_dir=checkout_path),
)
yield api.test(
"post-submit",
api.properties(
buildername='Windows abc', task_name='abc', upload_metrics=True
),
api.repo_util.flutter_environment_data(checkout_dir=checkout_path),
api.step_data(
'run abc',
stdout=api.raw_io.output_text('#flaky\nthis is a flaky\nflaky: true'),
retcode=0
),
api.buildbucket.ci_build(
git_ref='refs/heads/master',
),
api.platform.name('win'),
)
yield api.test(
"upload-metrics-mac",
api.properties(
buildername='Mac abc',
dependencies=[{'dependency': 'xcode'}],
task_name='abc',
upload_metrics=True,
upload_metrics_to_cas=True,
), api.repo_util.flutter_environment_data(checkout_dir=checkout_path),
api.buildbucket.ci_build(
git_ref='refs/heads/master',
)
)
yield api.test(
"local-engine",
api.properties(
buildername='Linux abc',
task_name='abc',
local_engine_cas_hash='isolatehashlocalengine/22',
local_engine='host-release'
), api.repo_util.flutter_environment_data(checkout_dir=checkout_path),
api.buildbucket.ci_build(
project='test',
git_repo='git.example.com/test/repo',
git_ref='refs/heads/master',
)
)