1# Copyright 2018, The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" 16SUITE Tradefed test runner class. 17""" 18 19import copy 20import logging 21import os 22 23from typing import List 24 25from atest import atest_utils 26from atest import constants 27 28from atest.atest_enum import ExitCode 29from atest.logstorage import atest_gcp_utils 30from atest.logstorage import logstorage_utils 31from atest.metrics import metrics 32from atest.test_finders import test_info 33from atest.test_runners import atest_tf_test_runner 34 35class SuitePlanTestRunner(atest_tf_test_runner.AtestTradefedTestRunner): 36 """Suite Plan Test Runner class.""" 37 NAME = 'SuitePlanTestRunner' 38 EXECUTABLE = '%s-tradefed' 39 _RUN_CMD = ('{exe} run commandAndExit {test} {args}') 40 41 def __init__(self, results_dir, **kwargs): 42 """Init stuff for suite tradefed runner class.""" 43 super().__init__(results_dir, **kwargs) 44 self.run_cmd_dict = {'exe': '', 45 'test': '', 46 'args': ''} 47 48 def get_test_runner_build_reqs(self, test_infos: List[test_info.TestInfo]): 49 """Return the build requirements. 50 51 Args: 52 test_infos: List of TestInfo. 53 54 Returns: 55 Set of build targets. 56 """ 57 build_req = set() 58 build_req |= super().get_test_runner_build_reqs(test_infos) 59 return build_req 60 61 def run_tests(self, test_infos, extra_args, reporter): 62 """Run the list of test_infos. 63 Args: 64 test_infos: List of TestInfo. 65 extra_args: Dict of extra args to add to test run. 66 reporter: An instance of result_report.ResultReporter. 67 68 Returns: 69 Return code of the process for running tests. 70 """ 71 reporter.register_unsupported_runner(self.NAME) 72 creds, inv = atest_gcp_utils.do_upload_flow(extra_args) 73 74 run_cmds = self.generate_run_commands(test_infos, extra_args) 75 ret_code = ExitCode.SUCCESS 76 for run_cmd in run_cmds: 77 try: 78 proc = super().run(run_cmd, output_to_stdout=True, 79 env_vars=self.generate_env_vars(extra_args)) 80 ret_code |= self.wait_for_subprocess(proc) 81 finally: 82 if inv: 83 try: 84 logging.disable(logging.INFO) 85 # Always set invocation status to completed due to 86 # the ATest handle whole process by its own. 87 inv['schedulerState'] = 'completed' 88 logstorage_utils.BuildClient(creds).update_invocation( 89 inv) 90 reporter.test_result_link = (constants.RESULT_LINK 91 % inv['invocationId']) 92 finally: 93 logging.disable(logging.NOTSET) 94 return ret_code 95 96 # pylint: disable=arguments-differ 97 def _parse_extra_args(self, extra_args): 98 """Convert the extra args into something *ts-tf can understand. 99 100 We want to transform the top-level args from atest into specific args 101 that *ts-tradefed supports. The only arg we take as is 102 EXTRA_ARG since that is what the user intentionally wants to pass to 103 the test runner. 104 105 Args: 106 extra_args: Dict of args 107 108 Returns: 109 List of args to append. 110 """ 111 args_to_append = [] 112 args_not_supported = [] 113 for arg in extra_args: 114 if constants.SERIAL == arg: 115 args_to_append.append('--serial') 116 args_to_append.append(extra_args[arg]) 117 continue 118 if constants.CUSTOM_ARGS == arg: 119 args_to_append.extend(extra_args[arg]) 120 continue 121 if constants.INVOCATION_ID == arg: 122 args_to_append.append('--invocation-data invocation_id=%s' 123 % extra_args[arg]) 124 if constants.WORKUNIT_ID == arg: 125 args_to_append.append('--invocation-data work_unit_id=%s' 126 % extra_args[arg]) 127 if arg in (constants.DRY_RUN, 128 constants.REQUEST_UPLOAD_RESULT): 129 continue 130 if constants.TF_DEBUG == arg: 131 debug_port = extra_args.get(constants.TF_DEBUG, '') 132 port = (debug_port if debug_port else 133 constants.DEFAULT_DEBUG_PORT) 134 print('Please attach process to your IDE...(%s)' % port) 135 continue 136 args_not_supported.append(arg) 137 if args_not_supported: 138 logging.info('%s does not support the following args: %s', 139 self.EXECUTABLE, args_not_supported) 140 return args_to_append 141 142 # pylint: disable=arguments-differ 143 def generate_run_commands(self, test_infos, extra_args): 144 """Generate a list of run commands from TestInfos. 145 146 Args: 147 test_infos: List of TestInfo tests to run. 148 extra_args: Dict of extra args to add to test run. 149 150 Returns: 151 A List of strings that contains the run command 152 which *ts-tradefed supports. 153 """ 154 cmds = [] 155 args = [] 156 args.extend(self._parse_extra_args(extra_args)) 157 args.extend(atest_utils.get_result_server_args()) 158 for test_info in test_infos: 159 cmd_dict = copy.deepcopy(self.run_cmd_dict) 160 cmd_dict['test'] = test_info.test_name 161 cmd_dict['args'] = ' '.join(args) 162 cmd_dict['exe'] = self.EXECUTABLE % test_info.suite 163 cmds.append(self._RUN_CMD.format(**cmd_dict)) 164 if constants.DETECT_TYPE_XTS_SUITE: 165 xts_detect_type = constants.DETECT_TYPE_XTS_SUITE.get( 166 test_info.suite, '') 167 if xts_detect_type: 168 metrics.LocalDetectEvent( 169 detect_type=xts_detect_type, 170 result=1) 171 return cmds 172 173 def generate_env_vars(self, extra_args): 174 """Convert extra args into env vars.""" 175 env_vars = os.environ.copy() 176 debug_port = extra_args.get(constants.TF_DEBUG, '') 177 if debug_port: 178 env_vars['TF_DEBUG'] = 'true' 179 env_vars['TF_DEBUG_PORT'] = str(debug_port) 180 if constants.TF_GLOBAL_CONFIG: 181 env_vars["TF_GLOBAL_CONFIG"] = constants.TF_GLOBAL_CONFIG 182 return env_vars 183