| #!/usr/bin/env python3 |
| # |
| # Copyright 2022 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. |
| """gRPC On-device Tests Gateway client.""" |
| |
| import argparse |
| import logging |
| import sys |
| |
| import grpc |
| |
| import on_device_tests_gateway_pb2 |
| import on_device_tests_gateway_pb2_grpc |
| |
| # All tests On-Device tests support |
| _TEST_TYPES = [ |
| 'black_box_test', |
| 'evergreen_test', |
| 'unit_test', |
| ] |
| |
| # All test configs On-Device tests support |
| _TEST_CONFIGS = [ |
| 'devel', |
| 'staging', |
| 'production', |
| ] |
| |
| _WORK_DIR = '/on_device_tests_gateway' |
| _ON_DEVICE_TESTS_GATEWAY_SERVICE_HOST = ( |
| 'on-device-tests-gateway-service.on-device-tests.svc.cluster.local') |
| _ON_DEVICE_TESTS_GATEWAY_SERVICE_PORT = '50052' |
| |
| |
| class OnDeviceTestsGatewayClient(): |
| """On-device tests Gateway Client class.""" |
| |
| def __init__(self): |
| self.channel = grpc.insecure_channel( |
| target=f'{_ON_DEVICE_TESTS_GATEWAY_SERVICE_HOST}:{_ON_DEVICE_TESTS_GATEWAY_SERVICE_PORT}', # pylint:disable=line-too-long |
| # These options need to match server settings. |
| options=[('grpc.keepalive_time_ms', 10000), |
| ('grpc.keepalive_timeout_ms', 5000), |
| ('grpc.keepalive_permit_without_calls', 1), |
| ('grpc.http2.max_pings_without_data', 0), |
| ('grpc.http2.min_time_between_pings_ms', 10000), |
| ('grpc.http2.min_ping_interval_without_data_ms', 5000)]) |
| self.stub = on_device_tests_gateway_pb2_grpc.on_device_tests_gatewayStub( |
| self.channel) |
| |
| def run_trigger_command(self, workdir: str, args: argparse.Namespace): |
| """Calls On-Device Tests service and passing given parameters to it. |
| |
| Args: |
| workdir (str): Current script workdir. |
| args (Namespace): Arguments passed in command line. |
| """ |
| for response_line in self.stub.exec_command( |
| on_device_tests_gateway_pb2.OnDeviceTestsCommand( |
| workdir=workdir, |
| token=args.token, |
| test_type=args.test_type, |
| platform=args.platform, |
| archive_path=args.archive_path, |
| config=args.config, |
| tag=args.tag, |
| labels=args.label, |
| builder_name=args.builder_name, |
| change_id=args.change_id, |
| build_number=args.build_number, |
| loader_platform=args.loader_platform, |
| loader_config=args.loader_config, |
| version=args.version, |
| dry_run=args.dry_run, |
| dimension=args.dimension or [], |
| unittest_shard_index=args.unittest_shard_index, |
| test_attempts=args.test_attempts, |
| retry_level=args.retry_level, |
| )): |
| |
| print(response_line.response) |
| |
| def run_watch_command(self, workdir: str, args: argparse.Namespace): |
| """Calls On-Device Tests watch service and passing given parameters to it. |
| |
| Args: |
| workdir (str): Current script workdir. |
| args (Namespace): Arguments passed in command line. |
| """ |
| for response_line in self.stub.exec_watch_command( |
| on_device_tests_gateway_pb2.OnDeviceTestsWatchCommand( |
| workdir=workdir, |
| token=args.token, |
| change_id=args.change_id, |
| session_id=args.session_id, |
| )): |
| |
| print(response_line.response) |
| |
| |
| def main(): |
| """Main routine.""" |
| logging.basicConfig( |
| level=logging.INFO, format='[%(filename)s:%(lineno)s] %(message)s') |
| |
| parser = argparse.ArgumentParser( |
| epilog=('Example: ./on_device_tests_gateway_client.py ' |
| '--test_type=unit_test ' |
| '--remote_archive_path=gs://my_bucket/path/to/artifacts.tar ' |
| '--platform=raspi-2 ' |
| '--config=devel'), |
| formatter_class=argparse.RawDescriptionHelpFormatter) |
| parser.add_argument( |
| '-t', |
| '--token', |
| type=str, |
| required=True, |
| help='On Device Tests authentication token') |
| parser.add_argument( |
| '--dry_run', |
| action='store_true', |
| help='Specifies to show what would be done without actually doing it.') |
| parser.add_argument( |
| '-i', |
| '--change_id', |
| type=str, |
| help='ChangeId that triggered this test, if any. ' |
| 'Saved with performance test results.') |
| |
| subparsers = parser.add_subparsers( |
| dest='action', help='On-Device tests commands', required=True) |
| trigger_parser = subparsers.add_parser( |
| 'trigger', help='Trigger On-Device tests') |
| |
| trigger_parser.add_argument( |
| '-e', |
| '--test_type', |
| type=str, |
| choices=_TEST_TYPES, |
| required=True, |
| help='Type of test to run.') |
| trigger_parser.add_argument( |
| '-p', |
| '--platform', |
| type=str, |
| required=True, |
| help='Platform this test was built for.') |
| trigger_parser.add_argument( |
| '-c', |
| '--config', |
| type=str, |
| choices=_TEST_CONFIGS, |
| required=True, |
| help='Cobalt config being tested.') |
| trigger_parser.add_argument( |
| '-a', |
| '--archive_path', |
| type=str, |
| required=True, |
| help='Path to Cobalt archive to be tested. Must be on gcs.') |
| trigger_parser.add_argument( |
| '-g', |
| '--tag', |
| type=str, |
| help='Value saved with performance results. ' |
| 'Indicates why this test was triggered.') |
| trigger_parser.add_argument( |
| '-l', |
| '--label', |
| type=str, |
| default=[], |
| action='append', |
| help='Additional labels to assign to the test.') |
| trigger_parser.add_argument( |
| '-b', |
| '--builder_name', |
| type=str, |
| help='Name of the builder that built the artifacts, ' |
| 'if any. Saved with performance test results') |
| trigger_parser.add_argument( |
| '-n', |
| '--build_number', |
| type=str, |
| help='Build number associated with the build, if any. ' |
| 'Saved with performance test results.') |
| trigger_parser.add_argument( |
| '--loader_platform', |
| type=str, |
| help='Platform of the loader to run the test. Only ' |
| 'applicable in Evergreen mode.') |
| trigger_parser.add_argument( |
| '--loader_config', |
| type=str, |
| help='Cobalt config of the loader to run the test. Only ' |
| 'applicable in Evergreen mode.') |
| trigger_parser.add_argument( |
| '--dimension', |
| type=str, |
| action='append', |
| help='On-Device Tests dimension used to select a device. ' |
| 'Must have the following form: <dimension>=<value>.' |
| ' E.G. "release_version=regex:10.*') |
| trigger_parser.add_argument( |
| '--version', |
| type=str, |
| default='COBALT', |
| help='Cobalt version being tested.') |
| trigger_parser.add_argument( |
| '--unittest_shard_index', |
| type=str, |
| required=False, |
| help='Optional argument to specify which unit testing shard to run in ' |
| 'the On-Device Tests Job. Defaults behavior is to run all tests without ' |
| 'sharding enabled.') |
| trigger_parser.add_argument( |
| '--test_attempts', |
| type=str, |
| default='1', |
| required=False, |
| help='The maximum number of times a test could retry.') |
| trigger_parser.add_argument( |
| '--retry_level', |
| type=str, |
| default='ERROR', |
| required=False, |
| help='The retry level of Mobile harness job. Either ERROR (to retry for ' |
| 'MH errors) or FAIL (to retry for failing tests). Setting retry_level to ' |
| 'FAIL will also retry for MH errors.') |
| |
| watch_parser = subparsers.add_parser('watch', help='Trigger On-Device tests') |
| watch_parser.add_argument( |
| 'session_id', |
| type=str, |
| help='Session id of a previously triggered mobile ' |
| 'harness test. If passed, the test will not be ' |
| 'triggered, but will be watched until the exit ' |
| 'status is reached.') |
| |
| args = parser.parse_args() |
| |
| client = OnDeviceTestsGatewayClient() |
| try: |
| if args.action == 'trigger': |
| client.run_trigger_command(workdir=_WORK_DIR, args=args) |
| else: |
| client.run_watch_command(workdir=_WORK_DIR, args=args) |
| except grpc.RpcError as e: |
| print(e) |
| return e.code().value |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |