Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 1 | # Copyright 2016 The Chromium OS Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 5 | import json |
| 6 | import logging |
| 7 | import os |
| 8 | |
| 9 | from autotest_lib.client.common_lib import error |
| 10 | from autotest_lib.client.common_lib import global_config |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 11 | from autotest_lib.server import adb_utils |
| 12 | from autotest_lib.server import constants |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 13 | from autotest_lib.server.hosts import adb_host |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 14 | |
| 15 | DEFAULT_ACTS_INTERNAL_DIRECTORY = 'tools/test/connectivity/acts' |
| 16 | |
| 17 | CONFIG_FOLDER_LOCATION = global_config.global_config.get_config_value( |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 18 | 'ACTS', 'acts_config_folder', default='') |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 19 | |
| 20 | TEST_DIR_NAME = 'tests' |
| 21 | FRAMEWORK_DIR_NAME = 'framework' |
| 22 | SETUP_FILE_NAME = 'setup.py' |
| 23 | CONFIG_DIR_NAME = 'autotest_config' |
| 24 | CAMPAIGN_DIR_NAME = 'autotest_campaign' |
| 25 | LOG_DIR_NAME = 'logs' |
| 26 | ACTS_EXECUTABLE_IN_FRAMEWORK = 'acts/bin/act.py' |
| 27 | |
| 28 | ACTS_TESTPATHS_ENV_KEY = 'ACTS_TESTPATHS' |
| 29 | ACTS_LOGPATH_ENV_KEY = 'ACTS_LOGPATH' |
| 30 | ACTS_PYTHONPATH_ENV_KEY = 'PYTHONPATH' |
| 31 | |
| 32 | |
| 33 | def create_acts_package_from_current_artifact(test_station, job_repo_url, |
| 34 | target_zip_file): |
| 35 | """Creates an acts package from the build branch being used. |
| 36 | |
| 37 | Creates an acts artifact from the build branch being used. This is |
| 38 | determined by the job_repo_url passed in. |
| 39 | |
| 40 | @param test_station: The teststation that should be creating the package. |
| 41 | @param job_repo_url: The job_repo_url to get the build info from. |
| 42 | @param target_zip_file: The zip file to create form the artifact on the |
| 43 | test_station. |
| 44 | |
| 45 | @returns An ActsPackage containing all the information about the zipped |
| 46 | artifact. |
| 47 | """ |
| 48 | build_info = adb_host.ADBHost.get_build_info_from_build_url(job_repo_url) |
| 49 | |
| 50 | return create_acts_package_from_artifact( |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 51 | test_station, build_info['branch'], build_info['target'], |
| 52 | build_info['build_id'], job_repo_url, target_zip_file) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 53 | |
| 54 | |
| 55 | def create_acts_package_from_artifact(test_station, branch, target, build_id, |
Benny Peake | 285d394 | 2017-06-26 14:32:53 -0700 | [diff] [blame] | 56 | devserver, target_zip_file): |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 57 | """Creates an acts package from a specified branch. |
| 58 | |
| 59 | Grabs the packaged acts artifact from the branch and places it on the |
| 60 | test_station. |
| 61 | |
| 62 | @param test_station: The teststation that should be creating the package. |
| 63 | @param branch: The name of the branch where the artifact is to be pulled. |
| 64 | @param target: The name of the target where the artifact is to be pulled. |
| 65 | @param build_id: The build id to pull the artifact from. |
Benny Peake | 285d394 | 2017-06-26 14:32:53 -0700 | [diff] [blame] | 66 | @param devserver: The devserver to use. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 67 | @param target_zip_file: The zip file to create on the teststation. |
| 68 | |
| 69 | @returns An ActsPackage containing all the information about the zipped |
| 70 | artifact. |
| 71 | """ |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 72 | devserver.trigger_download( |
| 73 | target, build_id, branch, files='acts.zip', synchronous=True) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 74 | |
Benny Peake | d6ac897 | 2017-02-22 23:09:01 -0800 | [diff] [blame] | 75 | pull_base_url = devserver.get_pull_url(target, build_id, branch) |
| 76 | download_ulr = os.path.join(pull_base_url, 'acts.zip') |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 77 | |
| 78 | test_station.download_file(download_ulr, target_zip_file) |
| 79 | |
| 80 | return ActsPackage(test_station, target_zip_file) |
| 81 | |
| 82 | |
| 83 | def create_acts_package_from_zip(test_station, zip_location, target_zip_file): |
| 84 | """Creates an acts package from an existing zip. |
| 85 | |
| 86 | Creates an acts package from a zip file that already sits on the drone. |
| 87 | |
| 88 | @param test_station: The teststation to create the package on. |
| 89 | @param zip_location: The location of the zip on the drone. |
| 90 | @param target_zip_file: The zip file to create on the teststaiton. |
| 91 | |
| 92 | @returns An ActsPackage containing all the information about the zipped |
| 93 | artifact. |
| 94 | """ |
| 95 | if not os.path.isabs(zip_location): |
| 96 | zip_location = os.path.join(CONFIG_FOLDER_LOCATION, 'acts_artifacts', |
| 97 | zip_location) |
| 98 | |
Benny Peake | 33fb5ff | 2017-03-22 18:12:11 -0700 | [diff] [blame] | 99 | test_station.send_file(zip_location, target_zip_file) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 100 | |
Benny Peake | 33fb5ff | 2017-03-22 18:12:11 -0700 | [diff] [blame] | 101 | return ActsPackage(test_station, target_zip_file) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 102 | |
| 103 | |
| 104 | class ActsPackage(object): |
| 105 | """A packaged version of acts on a teststation.""" |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 106 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 107 | def __init__(self, test_station, zip_file_path): |
| 108 | """ |
| 109 | @param test_station: The teststation this package is on. |
| 110 | @param zip_file_path: The path to the zip file on the test station that |
| 111 | holds the package on the teststation. |
| 112 | """ |
| 113 | self.test_station = test_station |
| 114 | self.zip_file = zip_file_path |
| 115 | |
| 116 | def create_container(self, |
| 117 | container_directory, |
| 118 | internal_acts_directory=None): |
| 119 | """Unpacks this package into a container. |
| 120 | |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 121 | Unpacks this acts package into a container to interact with acts. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 122 | |
| 123 | @param container_directory: The directory on the teststation to hold |
| 124 | the container. |
| 125 | @param internal_acts_directory: The directory inside of the package |
| 126 | that holds acts. |
| 127 | |
| 128 | @returns: An ActsContainer with info on the unpacked acts container. |
| 129 | """ |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 130 | self.test_station.run('unzip "%s" -x -d "%s"' % |
| 131 | (self.zip_file, container_directory)) |
| 132 | |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 133 | return ActsContainer( |
| 134 | self.test_station, |
| 135 | container_directory, |
| 136 | acts_directory=internal_acts_directory) |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 137 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 138 | def create_environment(self, |
| 139 | container_directory, |
| 140 | devices, |
| 141 | testbed_name, |
| 142 | internal_acts_directory=None): |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 143 | """Unpacks this package into an acts testing enviroment. |
| 144 | |
| 145 | Unpacks this acts package into a test enviroment to test with acts. |
| 146 | |
| 147 | @param container_directory: The directory on the teststation to hold |
| 148 | the test enviroment. |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 149 | @param devices: The list of devices in the environment. |
| 150 | @param testbed_name: The name of the testbed. |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 151 | @param internal_acts_directory: The directory inside of the package |
| 152 | that holds acts. |
| 153 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 154 | @returns: An ActsTestingEnvironment with info on the unpacked |
| 155 | acts testing environment. |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 156 | """ |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 157 | container = self.create_container(container_directory, |
| 158 | internal_acts_directory) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 159 | |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 160 | return ActsTestingEnviroment( |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 161 | devices=devices, |
| 162 | container=container, |
| 163 | testbed_name=testbed_name) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 164 | |
| 165 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 166 | class AndroidTestingEnvironment(object): |
Benny Peake | 591cff4 | 2016-11-21 16:17:29 -0800 | [diff] [blame] | 167 | """A container for testing android devices on a test station.""" |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 168 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 169 | def __init__(self, devices, testbed_name): |
| 170 | """Creates a new android testing environment. |
Benny Peake | 591cff4 | 2016-11-21 16:17:29 -0800 | [diff] [blame] | 171 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 172 | @param devices: The devices on the testbed to use. |
| 173 | @param testbed_name: The name for the testbed. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 174 | """ |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 175 | self.devices = devices |
Benny Peake | 591cff4 | 2016-11-21 16:17:29 -0800 | [diff] [blame] | 176 | self.testbed_name = testbed_name |
| 177 | |
Benny Peake | ccac2d1 | 2017-03-23 13:20:05 -0700 | [diff] [blame] | 178 | def install_sl4a_apk(self, force_reinstall=True): |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 179 | """Install sl4a to all provided devices.. |
Benny Peake | ccac2d1 | 2017-03-23 13:20:05 -0700 | [diff] [blame] | 180 | |
| 181 | @param force_reinstall: If true the apk will be force to reinstall. |
| 182 | """ |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 183 | for device in self.devices: |
Benny Peake | 591cff4 | 2016-11-21 16:17:29 -0800 | [diff] [blame] | 184 | adb_utils.install_apk_from_build( |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 185 | device, |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 186 | constants.SL4A_APK, |
| 187 | constants.SL4A_ARTIFACT, |
Benny Peake | ccac2d1 | 2017-03-23 13:20:05 -0700 | [diff] [blame] | 188 | package_name=constants.SL4A_PACKAGE, |
| 189 | force_reinstall=force_reinstall) |
Benny Peake | 591cff4 | 2016-11-21 16:17:29 -0800 | [diff] [blame] | 190 | |
Benny Peake | ccac2d1 | 2017-03-23 13:20:05 -0700 | [diff] [blame] | 191 | def install_apk(self, apk_info, force_reinstall=True): |
Benny Peake | 591cff4 | 2016-11-21 16:17:29 -0800 | [diff] [blame] | 192 | """Installs an additional apk on all adb devices. |
Benny Peake | 2573fae | 2016-11-28 15:15:52 -0800 | [diff] [blame] | 193 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 194 | @param apk_info: A dictionary containing the apk info. This dictionary |
Benny Peake | 2573fae | 2016-11-28 15:15:52 -0800 | [diff] [blame] | 195 | should contain the keys: |
| 196 | apk="Name of the apk", |
| 197 | package="Name of the package". |
| 198 | artifact="Name of the artifact", if missing |
| 199 | the package name is used." |
Benny Peake | ccac2d1 | 2017-03-23 13:20:05 -0700 | [diff] [blame] | 200 | @param force_reinstall: If true the apk will be forced to reinstall. |
Benny Peake | 591cff4 | 2016-11-21 16:17:29 -0800 | [diff] [blame] | 201 | """ |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 202 | for device in self.devices: |
Benny Peake | 591cff4 | 2016-11-21 16:17:29 -0800 | [diff] [blame] | 203 | adb_utils.install_apk_from_build( |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 204 | device, |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 205 | apk_info['apk'], |
| 206 | apk_info.get('artifact') or constants.SL4A_ARTIFACT, |
Benny Peake | ccac2d1 | 2017-03-23 13:20:05 -0700 | [diff] [blame] | 207 | package_name=apk_info['package'], |
| 208 | force_reinstall=force_reinstall) |
Benny Peake | 591cff4 | 2016-11-21 16:17:29 -0800 | [diff] [blame] | 209 | |
| 210 | |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 211 | class ActsContainer(object): |
| 212 | """A container for working with acts.""" |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 213 | |
| 214 | def __init__(self, test_station, container_directory, acts_directory=None): |
Benny Peake | 591cff4 | 2016-11-21 16:17:29 -0800 | [diff] [blame] | 215 | """ |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 216 | @param test_station: The test station that the container is on. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 217 | @param container_directory: The directory on the teststation this |
| 218 | container operates out of. |
| 219 | @param acts_directory: The directory within the container that holds |
| 220 | acts. If none then it defaults to |
| 221 | DEFAULT_ACTS_INTERNAL_DIRECTORY. |
| 222 | """ |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 223 | self.test_station = test_station |
| 224 | self.container_directory = container_directory |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 225 | |
| 226 | if not acts_directory: |
| 227 | acts_directory = DEFAULT_ACTS_INTERNAL_DIRECTORY |
| 228 | |
| 229 | if not os.path.isabs(acts_directory): |
| 230 | self.acts_directory = os.path.join(container_directory, |
| 231 | acts_directory) |
| 232 | else: |
| 233 | self.acts_directory = acts_directory |
| 234 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 235 | self.tests_directory = os.path.join(self.acts_directory, TEST_DIR_NAME) |
| 236 | self.framework_directory = os.path.join(self.acts_directory, |
| 237 | FRAMEWORK_DIR_NAME) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 238 | |
| 239 | self.acts_file = os.path.join(self.framework_directory, |
| 240 | ACTS_EXECUTABLE_IN_FRAMEWORK) |
| 241 | |
| 242 | self.setup_file = os.path.join(self.framework_directory, |
| 243 | SETUP_FILE_NAME) |
| 244 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 245 | self.log_directory = os.path.join(container_directory, |
| 246 | LOG_DIR_NAME) |
| 247 | |
| 248 | self.config_location = os.path.join(container_directory, |
| 249 | CONFIG_DIR_NAME) |
| 250 | |
| 251 | self.acts_file = os.path.join(self.framework_directory, |
| 252 | ACTS_EXECUTABLE_IN_FRAMEWORK) |
| 253 | |
| 254 | self.working_directory = os.path.join(container_directory, |
| 255 | CONFIG_DIR_NAME) |
| 256 | test_station.run('mkdir %s' % self.working_directory, |
| 257 | ignore_status=True) |
| 258 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 259 | def get_test_paths(self): |
| 260 | """Get all test paths within this container. |
| 261 | |
| 262 | Gets all paths that hold tests within the container. |
| 263 | |
| 264 | @returns: A list of paths on the teststation that hold tests. |
| 265 | """ |
| 266 | get_test_paths_result = self.test_station.run('find %s -type d' % |
| 267 | self.tests_directory) |
| 268 | test_search_dirs = get_test_paths_result.stdout.splitlines() |
| 269 | return test_search_dirs |
| 270 | |
| 271 | def get_python_path(self): |
| 272 | """Get the python path being used. |
| 273 | |
| 274 | Gets the python path that will be set in the enviroment for this |
| 275 | container. |
| 276 | |
| 277 | @returns: A string of the PYTHONPATH enviroment variable to be used. |
| 278 | """ |
| 279 | return '%s:$PYTHONPATH' % self.framework_directory |
| 280 | |
| 281 | def get_enviroment(self): |
| 282 | """Gets the enviroment variables to be used for this container. |
| 283 | |
| 284 | @returns: A dictionary of enviroment variables to be used by this |
| 285 | container. |
| 286 | """ |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 287 | env = { |
| 288 | ACTS_TESTPATHS_ENV_KEY: ':'.join(self.get_test_paths()), |
| 289 | ACTS_LOGPATH_ENV_KEY: self.log_directory, |
| 290 | ACTS_PYTHONPATH_ENV_KEY: self.get_python_path() |
| 291 | } |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 292 | |
| 293 | return env |
| 294 | |
| 295 | def upload_file(self, src, dst): |
| 296 | """Uploads a file to be used by the container. |
| 297 | |
| 298 | Uploads a file from the drone to the test staiton to be used by the |
| 299 | test container. |
| 300 | |
| 301 | @param src: The source file on the drone. If a relative path is given |
| 302 | it is assumed to exist in CONFIG_FOLDER_LOCATION. |
| 303 | @param dst: The destination on the teststation. If a relative path is |
| 304 | given it is assumed that it is within the container. |
| 305 | |
| 306 | @returns: The full path on the teststation. |
| 307 | """ |
| 308 | if not os.path.isabs(src): |
| 309 | src = os.path.join(CONFIG_FOLDER_LOCATION, src) |
| 310 | |
| 311 | if not os.path.isabs(dst): |
| 312 | dst = os.path.join(self.container_directory, dst) |
| 313 | |
Benny Peake | f1b9f39 | 2016-11-30 18:25:38 -0800 | [diff] [blame] | 314 | path = os.path.dirname(dst) |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 315 | self.test_station.run('mkdir "%s"' % path, ignore_status=True) |
Benny Peake | f1b9f39 | 2016-11-30 18:25:38 -0800 | [diff] [blame] | 316 | |
| 317 | original_dst = dst |
| 318 | if os.path.basename(src) == os.path.basename(dst): |
| 319 | dst = os.path.dirname(dst) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 320 | |
| 321 | self.test_station.send_file(src, dst) |
| 322 | |
Benny Peake | f1b9f39 | 2016-11-30 18:25:38 -0800 | [diff] [blame] | 323 | return original_dst |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 324 | |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 325 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 326 | class ActsTestingEnviroment(AndroidTestingEnvironment): |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 327 | """A container for running acts tests with a contained version of acts.""" |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 328 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 329 | def __init__(self, container, devices, testbed_name): |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 330 | """ |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 331 | @param container: The acts container to use. |
| 332 | @param devices: The list of devices to use. |
| 333 | @testbed_name: The name of the testbed being used. |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 334 | """ |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 335 | super(ActsTestingEnviroment, self).__init__(devices=devices, |
| 336 | testbed_name=testbed_name) |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 337 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 338 | self.container = container |
Benny Peake | 9519ea8 | 2016-12-02 17:23:38 -0800 | [diff] [blame] | 339 | |
| 340 | self.configs = {} |
| 341 | self.campaigns = {} |
| 342 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 343 | def upload_config(self, config_file): |
| 344 | """Uploads a config file to the container. |
| 345 | |
| 346 | Uploads a config file to the config folder in the container. |
| 347 | |
| 348 | @param config_file: The config file to upload. This must be a file |
| 349 | within the autotest_config directory under the |
| 350 | CONFIG_FOLDER_LOCATION. |
| 351 | |
| 352 | @returns: The full path of the config on the test staiton. |
| 353 | """ |
| 354 | full_name = os.path.join(CONFIG_DIR_NAME, config_file) |
| 355 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 356 | full_path = self.container.upload_file(full_name, full_name) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 357 | self.configs[config_file] = full_path |
| 358 | |
| 359 | return full_path |
| 360 | |
| 361 | def upload_campaign(self, campaign_file): |
| 362 | """Uploads a campaign file to the container. |
| 363 | |
| 364 | Uploads a campaign file to the campaign folder in the container. |
| 365 | |
| 366 | @param campaign_file: The campaign file to upload. This must be a file |
| 367 | within the autotest_campaign directory under the |
| 368 | CONFIG_FOLDER_LOCATION. |
| 369 | |
| 370 | @returns: The full path of the campaign on the test staiton. |
| 371 | """ |
| 372 | full_name = os.path.join(CAMPAIGN_DIR_NAME, campaign_file) |
| 373 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 374 | full_path = self.container.upload_file(full_name, full_name) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 375 | self.campaigns[campaign_file] = full_path |
| 376 | |
| 377 | return full_path |
| 378 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 379 | def setup_enviroment(self, python_bin='python'): |
| 380 | """Sets up the teststation system enviroment so the container can run. |
| 381 | |
| 382 | Prepares the remote system so that the container can run. This involves |
| 383 | uninstalling all versions of acts for the version of python being |
| 384 | used and installing all needed dependencies. |
| 385 | |
| 386 | @param python_bin: The python binary to use. |
| 387 | """ |
| 388 | uninstall_command = '%s %s uninstall' % ( |
| 389 | python_bin, self.container.setup_file) |
| 390 | install_deps_command = '%s %s install_deps' % ( |
| 391 | python_bin, self.container.setup_file) |
| 392 | |
| 393 | self.container.test_station.run(uninstall_command) |
| 394 | self.container.test_station.run(install_deps_command) |
| 395 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 396 | def run_test(self, |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 397 | config, |
| 398 | campaign=None, |
| 399 | test_case=None, |
| 400 | extra_env={}, |
| 401 | python_bin='python', |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 402 | timeout=7200, |
| 403 | additional_cmd_line_params=None): |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 404 | """Runs a test within the container. |
| 405 | |
| 406 | Runs a test within a container using the given settings. |
| 407 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 408 | @param config: The name of the config file to use as the main config. |
| 409 | This should have already been uploaded with |
| 410 | upload_config. The string passed into upload_config |
| 411 | should be used here. |
| 412 | @param campaign: The campaign file to use for this test. If none then |
| 413 | test_case is assumed. This file should have already |
| 414 | been uploaded with upload_campaign. The string passed |
| 415 | into upload_campaign should be used here. |
| 416 | @param test_case: The test case to run the test with. If none then the |
Benny Peake | 913791f | 2016-12-08 11:00:56 -0800 | [diff] [blame] | 417 | campaign will be used. If multiple are given, |
| 418 | multiple will be run. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 419 | @param extra_env: Extra enviroment variables to run the test with. |
| 420 | @param python_bin: The python binary to execute the test with. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 421 | @param timeout: How many seconds to wait before timing out. |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 422 | @param additional_cmd_line_params: Adds the ability to add any string |
| 423 | to the end of the acts.py command |
| 424 | line string. This is intended to |
| 425 | add acts command line flags however |
| 426 | this is unbounded so it could cause |
| 427 | errors if incorrectly set. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 428 | |
| 429 | @returns: The results of the test run. |
| 430 | """ |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 431 | if not config in self.configs: |
| 432 | # Check if the config has been uploaded and upload if it hasn't |
| 433 | self.upload_config(config) |
| 434 | |
| 435 | full_config = self.configs[config] |
| 436 | |
| 437 | if campaign: |
| 438 | # When given a campaign check if it's upload. |
| 439 | if not campaign in self.campaigns: |
| 440 | self.upload_campaign(campaign) |
| 441 | |
| 442 | full_campaign = self.campaigns[campaign] |
| 443 | else: |
| 444 | full_campaign = None |
| 445 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 446 | full_env = self.container.get_enviroment() |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 447 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 448 | # Setup environment variables. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 449 | if extra_env: |
| 450 | for k, v in extra_env.items(): |
| 451 | full_env[k] = extra_env |
| 452 | |
| 453 | logging.info('Using env: %s', full_env) |
| 454 | exports = ('export %s=%s' % (k, v) for k, v in full_env.items()) |
| 455 | env_command = ';'.join(exports) |
| 456 | |
| 457 | # Make sure to execute in the working directory. |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 458 | command_setup = 'cd %s' % self.container.working_directory |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 459 | |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 460 | if additional_cmd_line_params: |
| 461 | act_base_cmd = '%s %s -c %s -tb %s %s ' % ( |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 462 | python_bin, self.container.acts_file, full_config, |
| 463 | self.testbed_name, additional_cmd_line_params) |
Joe Brennan | a0db27c | 2017-02-27 15:40:00 -0800 | [diff] [blame] | 464 | else: |
| 465 | act_base_cmd = '%s %s -c %s -tb %s ' % ( |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 466 | python_bin, self.container.acts_file, full_config, |
| 467 | self.testbed_name) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 468 | |
| 469 | # Format the acts command based on what type of test is being run. |
| 470 | if test_case and campaign: |
| 471 | raise error.TestError( |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 472 | 'campaign and test_file cannot both have a value.') |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 473 | elif test_case: |
Benny Peake | 913791f | 2016-12-08 11:00:56 -0800 | [diff] [blame] | 474 | if isinstance(test_case, str): |
| 475 | test_case = [test_case] |
| 476 | if len(test_case) < 1: |
| 477 | raise error.TestError('At least one test case must be given.') |
| 478 | |
| 479 | tc_str = '' |
| 480 | for tc in test_case: |
| 481 | tc_str = '%s %s' % (tc_str, tc) |
| 482 | tc_str = tc_str.strip() |
| 483 | |
| 484 | act_cmd = '%s -tc %s' % (act_base_cmd, tc_str) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 485 | elif campaign: |
| 486 | act_cmd = '%s -tf %s' % (act_base_cmd, full_campaign) |
| 487 | else: |
| 488 | raise error.TestFail('No tests was specified!') |
| 489 | |
| 490 | # Format all commands into a single command. |
| 491 | command_list = [command_setup, env_command, act_cmd] |
| 492 | full_command = '; '.join(command_list) |
| 493 | |
| 494 | try: |
| 495 | # Run acts on the remote machine. |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 496 | act_result = self.container.test_station.run(full_command, |
| 497 | timeout=timeout) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 498 | excep = None |
| 499 | except Exception as e: |
| 500 | # Catch any error to store in the results. |
| 501 | act_result = None |
| 502 | excep = e |
| 503 | |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 504 | return ActsTestResults(str(test_case) or campaign, |
| 505 | container=self.container, |
| 506 | devices=self.devices, |
| 507 | testbed_name=self.testbed_name, |
| 508 | run_result=act_result, |
| 509 | exception=excep) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 510 | |
| 511 | |
| 512 | class ActsTestResults(object): |
| 513 | """The packaged results of a test run.""" |
| 514 | acts_result_to_autotest = { |
| 515 | 'PASS': 'GOOD', |
| 516 | 'FAIL': 'FAIL', |
| 517 | 'UNKNOWN': 'WARN', |
| 518 | 'SKIP': 'ABORT' |
| 519 | } |
| 520 | |
| 521 | def __init__(self, |
| 522 | name, |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 523 | container, |
| 524 | devices, |
| 525 | testbed_name, |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 526 | run_result=None, |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 527 | exception=None): |
| 528 | """ |
| 529 | @param name: A name to identify the test run. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 530 | @param testbed_name: The name the testbed was run with, if none the |
| 531 | default name of the testbed is used. |
| 532 | @param run_result: The raw i/o result of the test run. |
Benny Peake | 51c675b | 2016-11-15 15:55:39 -0800 | [diff] [blame] | 533 | @param log_directory: The directory that acts logged to. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 534 | @param exception: An exception that was thrown while running the test. |
| 535 | """ |
| 536 | self.name = name |
| 537 | self.run_result = run_result |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 538 | self.exception = exception |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 539 | self.log_directory = container.log_directory |
| 540 | self.test_station = container.test_station |
| 541 | self.testbed_name = testbed_name |
| 542 | self.devices = devices |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 543 | |
| 544 | self.reported_to = set() |
| 545 | |
Benny Peake | 51c675b | 2016-11-15 15:55:39 -0800 | [diff] [blame] | 546 | self.json_results = {} |
| 547 | self.results_dir = None |
| 548 | if self.log_directory: |
| 549 | self.results_dir = os.path.join(self.log_directory, |
| 550 | self.testbed_name, 'latest') |
| 551 | results_file = os.path.join(self.results_dir, |
| 552 | 'test_run_summary.json') |
| 553 | cat_log_result = self.test_station.run('cat %s' % results_file, |
| 554 | ignore_status=True) |
| 555 | if not cat_log_result.exit_status: |
| 556 | self.json_results = json.loads(cat_log_result.stdout) |
| 557 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 558 | def log_output(self): |
| 559 | """Logs the output of the test.""" |
| 560 | if self.run_result: |
| 561 | logging.debug('ACTS Output:\n%s', self.run_result.stdout) |
| 562 | |
Benny Peake | 83904b1 | 2017-03-09 14:08:07 -0800 | [diff] [blame] | 563 | def save_test_info(self, test): |
| 564 | """Save info about the test. |
| 565 | |
| 566 | @param test: The test to save. |
| 567 | """ |
Benny Peake | fda1d8c | 2017-06-02 16:38:31 -0700 | [diff] [blame] | 568 | for device in self.devices: |
| 569 | device.save_info(test.resultsdir) |
Benny Peake | 83904b1 | 2017-03-09 14:08:07 -0800 | [diff] [blame] | 570 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 571 | def rethrow_exception(self): |
| 572 | """Re-throws the exception thrown during the test.""" |
| 573 | if self.exception: |
| 574 | raise self.exception |
| 575 | |
Benny Peake | 51c675b | 2016-11-15 15:55:39 -0800 | [diff] [blame] | 576 | def upload_to_local(self, local_dir): |
| 577 | """Saves all acts results to a local directory. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 578 | |
Benny Peake | 51c675b | 2016-11-15 15:55:39 -0800 | [diff] [blame] | 579 | @param local_dir: The directory on the local machine to save all results |
| 580 | to. |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 581 | """ |
Benny Peake | 51c675b | 2016-11-15 15:55:39 -0800 | [diff] [blame] | 582 | if self.results_dir: |
| 583 | self.test_station.get_file(self.results_dir, local_dir) |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 584 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 585 | def report_to_autotest(self, test): |
| 586 | """Reports the results to an autotest test object. |
| 587 | |
Benny Peake | 51c675b | 2016-11-15 15:55:39 -0800 | [diff] [blame] | 588 | Reports the results to the test and saves all acts results under the |
| 589 | tests results directory. |
| 590 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 591 | @param test: The autotest test object to report to. If this test object |
| 592 | has already recived our report then this call will be |
| 593 | ignored. |
| 594 | """ |
| 595 | if test in self.reported_to: |
| 596 | return |
| 597 | |
Benny Peake | 51c675b | 2016-11-15 15:55:39 -0800 | [diff] [blame] | 598 | if self.results_dir: |
| 599 | self.upload_to_local(test.resultsdir) |
| 600 | |
Benny Peake | 9190a13 | 2016-11-02 12:57:01 -0700 | [diff] [blame] | 601 | if not 'Results' in self.json_results: |
| 602 | return |
| 603 | |
| 604 | results = self.json_results['Results'] |
| 605 | for result in results: |
| 606 | verdict = self.acts_result_to_autotest[result['Result']] |
| 607 | details = result['Details'] |
| 608 | test.job.record(verdict, None, self.name, status=(details or '')) |
| 609 | |
| 610 | self.reported_to.add(test) |