blob: c8cb20c05baaaa8ed2206c246b76ab6255f58927 [file] [log] [blame]
#
# Copyright (C) 2020 The Android Open Source Project
#
# 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.
#
# pylint: disable=invalid-name
"""Script to submit builds/tests in Forrest."""
from typing import Dict, List, NamedTuple
import re
import yaml
import test_paths
import utils
USER = utils.check_output(['whoami']).strip()
changeSpecsPB = """
change_specs {{
gerrit_change {{
hostname: "android"
change_number: {cl_number}
}}
}}"""
buildRequestPB = """
build_request {{
pending_build {{
branch: "{branch}"
target: "{target}"
{change_specs}
force_cherry_pick: true
exclude_submitted_together_changes: true
}}
}}
"""
testRequestPB = """
test_request {{
atp_test {{
test_name: "{test_name}"
}}
runner: TRADEFED
device_selection {{
test_bench {{
cluster: "{cluster}"
run_target: "{run_target}"
}}
}}
run_count: 1
shard_count: 1
location: REMOTE
branch_gcl: ""
test_bench_gcl: "{gcl_path}/{test_bench_gcl}"
test_run_context: ""
import_extra_args: true
}}
"""
suffixFields = """
user: "{user}@google.com"
tag: "{tag}"
"""
class ClusterRecord(NamedTuple):
"""Device info needed to submit a test on Forrest."""
cluster: str
run_target: str
test_bench_gcl: str
def _read_cluster_info() -> Dict[str, ClusterRecord]:
with open(test_paths.CLUSTER_INFO_YAML) as infile:
info = yaml.safe_load(infile)
return {
device: ClusterRecord(**record) for device, record in info.items()
}
ClusterInfo = _read_cluster_info()
def _trimprefix(string: str, prefix: str) -> str:
if string.startswith(prefix):
return string[len(prefix):]
return string
def _trimsuffix(string: str, suffix: str) -> str:
if string.endswith(suffix):
return string[:-len(suffix)]
return string
def _get_device_info(target: str) -> ClusterRecord:
# normalize target name
orig = target
target = _trimprefix(target, 'aosp_')
target = _trimsuffix(target, '_coverage')
target = _trimsuffix(target, '-userdebug')
target = _trimsuffix(target, '-eng')
target = _trimprefix(target, 'pgo-')
target = _trimsuffix(target, '-config1')
target = _trimsuffix(target, '_profcollect')
target = _trimsuffix(target, '_hwasan')
if target in ClusterInfo:
return ClusterInfo[target]
else:
raise RuntimeError(
f'Cannot find cluster info for {orig} (normalized to {target})')
def invokeForrestRun(branch: str, target: str, cl_numbers: List[str],
tests: List[str], tag: str) -> str:
"""Submit a build/test to forrest."""
gcl_path = test_paths.gcl_path()
if tests:
cluster_info = _get_device_info(target)._asdict()
# Create Forrest protobuf and write to file.
pb = ''
changeSpecs = '\n'.join(
changeSpecsPB.format(cl_number=cl) for cl in cl_numbers)
pb += buildRequestPB.format(
branch=branch, target=target, change_specs=changeSpecs)
for test in tests:
pb += testRequestPB.format(
test_name=test, gcl_path=gcl_path, **cluster_info)
pb += suffixFields.format(user=USER, tag=tag)
cl_str = '_'.join(cl_numbers)
pbFile = f'/tmp/forrest_{tag}_{branch}_{target}_{cl_str}.pb'
# TODO(pirama): Cleanup files in /tmp. They are currently retained to help
# debugging.
with open(pbFile, 'w') as fileobj:
fileobj.write(pb)
# Submit to forrest and return invocation id.
output = utils.check_output([test_paths.FORREST, 'run', pbFile])
match = re.search(r'http://go/forrest-run/(L[0-9]*)\x1b', output)
if not match:
raise RuntimeError('Forrest invocation id not found in output,' +
f'which is\n{output}\n<END_OF_FORREST_OUTPUT>')
return match.group(1)