1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.invoker; 17 18 import com.android.tradefed.build.IBuildInfo; 19 import com.android.tradefed.device.ITestDevice; 20 import com.android.tradefed.invoker.ExecutionFiles.FilesKey; 21 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 22 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 23 import com.android.tradefed.log.LogUtil.CLog; 24 import com.android.tradefed.util.FileUtil; 25 import com.android.tradefed.util.SearchArtifactUtil; 26 27 import java.io.File; 28 import java.io.FileNotFoundException; 29 import java.util.List; 30 31 /** 32 * Holder object that contains all the information and dependencies a test runner or test might need 33 * to execute properly. 34 */ 35 public class TestInformation { 36 /** The context of the invocation or module in progress */ 37 private final IInvocationContext mContext; 38 /** Properties generated during execution. */ 39 private final ExecutionProperties mProperties; 40 /** 41 * Files generated during execution that needs to be carried, they will be deleted at the end of 42 * the invocation. 43 */ 44 private final ExecutionFiles mExecutionFiles; 45 46 /** Main folder for all dependencies of tests */ 47 private final File mDependenciesFolder; 48 49 private int mPrimaryDeviceIndex = 0; 50 51 // Flag to indicate if the test was informed of timeout 52 private boolean mTestTimedOut = false; 53 TestInformation(Builder builder)54 private TestInformation(Builder builder) { 55 mContext = builder.mContext; 56 mProperties = builder.mProperties; 57 mDependenciesFolder = builder.mDependenciesFolder; 58 mExecutionFiles = builder.mExecutionFiles; 59 } 60 TestInformation( TestInformation invocationInfo, IInvocationContext moduleContext, boolean copyExecFile)61 private TestInformation( 62 TestInformation invocationInfo, 63 IInvocationContext moduleContext, 64 boolean copyExecFile) { 65 mContext = moduleContext; 66 // Copy properties so each shard has its own 67 mProperties = new ExecutionProperties(); 68 mProperties.putAll(invocationInfo.mProperties.getAll()); 69 mDependenciesFolder = invocationInfo.mDependenciesFolder; 70 if (copyExecFile) { 71 mExecutionFiles = new ExecutionFiles(); 72 mExecutionFiles.putAll(invocationInfo.executionFiles()); 73 } else { 74 mExecutionFiles = invocationInfo.mExecutionFiles; 75 } 76 } 77 78 /** Create a builder for creating {@link TestInformation} instances. */ newBuilder()79 public static Builder newBuilder() { 80 return new Builder(); 81 } 82 83 /** Create an {@link TestInformation} representing a module rather than an invocation. */ createModuleTestInfo( TestInformation invocationInfo, IInvocationContext moduleContext)84 public static TestInformation createModuleTestInfo( 85 TestInformation invocationInfo, IInvocationContext moduleContext) { 86 return new TestInformation(invocationInfo, moduleContext, false); 87 } 88 89 /** Create an {@link TestInformation} with a copied {@link ExecutionFiles}. */ createCopyTestInfo( TestInformation invocationInfo, IInvocationContext context)90 public static TestInformation createCopyTestInfo( 91 TestInformation invocationInfo, IInvocationContext context) { 92 return new TestInformation(invocationInfo, context, true); 93 } 94 95 /** Returns the current invocation context, or the module context if this is a module. */ getContext()96 public IInvocationContext getContext() { 97 return mContext; 98 } 99 100 /** Returns the primary device under tests. */ getDevice()101 public ITestDevice getDevice() { 102 return mContext.getDevices().get(mPrimaryDeviceIndex); 103 } 104 105 /** Returns the list of devices part of the invocation. */ getDevices()106 public List<ITestDevice> getDevices() { 107 return mContext.getDevices(); 108 } 109 110 /** Returns the primary device build information. */ getBuildInfo()111 public IBuildInfo getBuildInfo() { 112 return mContext.getBuildInfos().get(mPrimaryDeviceIndex); 113 } 114 115 /** 116 * Test Harness internal method to switch which device is returned by default with {@link 117 * #getDevice()}. Always reset to 0. 118 */ setActiveDeviceIndex(int index)119 public final void setActiveDeviceIndex(int index) { 120 mPrimaryDeviceIndex = index; 121 } 122 123 /** 124 * Returns the properties generated during the invocation execution. Passing values and 125 * information through the {@link ExecutionProperties} is the recommended way to exchange 126 * information between target_preparers and tests. 127 */ properties()128 public ExecutionProperties properties() { 129 return mProperties; 130 } 131 132 /** 133 * Returns the files generated during the invocation execution. Passing files through the {@link 134 * ExecutionFiles} is the recommended way to make a file available between target_preparers and 135 * tests. 136 */ executionFiles()137 public ExecutionFiles executionFiles() { 138 return mExecutionFiles; 139 } 140 141 /** Returns the folder where all the dependencies are stored for an invocation. */ dependenciesFolder()142 public File dependenciesFolder() { 143 return mDependenciesFolder; 144 } 145 146 /** Returns whether the test was informed of timeout or not. */ isTestTimedOut()147 public boolean isTestTimedOut() { 148 return mTestTimedOut; 149 } 150 151 /** Notifies that test phase timeout has been triggered for this test. */ notifyTimeout()152 public void notifyTimeout() { 153 mTestTimedOut = true; 154 } 155 156 /** Builder to create a {@link TestInformation} instance. */ 157 public static class Builder { 158 private IInvocationContext mContext; 159 private ExecutionProperties mProperties; 160 private File mDependenciesFolder; 161 private ExecutionFiles mExecutionFiles; 162 Builder()163 private Builder() { 164 mProperties = new ExecutionProperties(); 165 mExecutionFiles = new ExecutionFiles(); 166 } 167 build()168 public TestInformation build() { 169 return new TestInformation(this); 170 } 171 setInvocationContext(IInvocationContext context)172 public Builder setInvocationContext(IInvocationContext context) { 173 this.mContext = context; 174 return this; 175 } 176 setDependenciesFolder(File dependenciesFolder)177 public Builder setDependenciesFolder(File dependenciesFolder) { 178 this.mDependenciesFolder = dependenciesFolder; 179 return this; 180 } 181 } 182 183 /** 184 * Search for a dependency/artifact file based on its name, and whether or not it's a target or 185 * host file (for quicker search). 186 * 187 * @param fileName The name of the file we are looking for. 188 * @param targetFirst whether or not we are favoring target-side files vs. host-side files for 189 * the search. 190 * @return The found artifact file. 191 * @throws FileNotFoundException If the file is not found. 192 */ getDependencyFile(String fileName, boolean targetFirst)193 public File getDependencyFile(String fileName, boolean targetFirst) 194 throws FileNotFoundException { 195 File dependency = null; 196 try { 197 dependency = SearchArtifactUtil.searchFile(fileName, targetFirst, this); 198 } catch (Exception e) { 199 // TODO: handle error when migration is complete. 200 CLog.e(e); 201 } 202 if (dependency != null && dependency.isFile()) { 203 return dependency; 204 } else { 205 // Silently report not found and fall back to old logic. 206 InvocationMetricLogger.addInvocationMetrics( 207 InvocationMetricKey.SEARCH_ARTIFACT_FAILURE_COUNT, 1); 208 } 209 dependency = getFromEnv(fileName, targetFirst); 210 if (dependency != null && dependency.isFile()) { 211 return dependency; 212 } 213 dependency = getFromTestsDir(fileName); 214 if (dependency != null && dependency.isFile()) { 215 return dependency; 216 } 217 dependency = getFile(fileName); 218 if (dependency != null && dependency.isFile()) { 219 return dependency; 220 } 221 dependency = getFromDependencyFolder(fileName); 222 if (dependency != null && dependency.isFile()) { 223 return dependency; 224 } 225 // if old logic fails too, do not report search artifact failure 226 InvocationMetricLogger.addInvocationMetrics( 227 InvocationMetricKey.SEARCH_ARTIFACT_FAILURE_COUNT, -1); 228 throw new FileNotFoundException( 229 String.format("Could not find an artifact file associated with %s", fileName)); 230 } 231 getFromEnv(String fileName, boolean targetFirst)232 private File getFromEnv(String fileName, boolean targetFirst) { 233 FilesKey hostOrTarget = FilesKey.HOST_TESTS_DIRECTORY; 234 if (targetFirst) { 235 hostOrTarget = FilesKey.TARGET_TESTS_DIRECTORY; 236 } 237 File testsDir = mExecutionFiles.get(hostOrTarget); 238 if (testsDir != null && testsDir.exists()) { 239 File file = FileUtil.findFile(testsDir, fileName); 240 if (file != null) { 241 return file; 242 } 243 } 244 return null; 245 } 246 getFromTestsDir(String fileName)247 private File getFromTestsDir(String fileName) { 248 File testsDir = mExecutionFiles.get(FilesKey.TESTS_DIRECTORY); 249 if (testsDir != null && testsDir.exists()) { 250 File file = FileUtil.findFile(testsDir, fileName); 251 if (file == null) { 252 // TODO(b/138416078): Once build dependency can be fixed and test required 253 // APKs are all under the test module directory, we can remove this fallback 254 // approach to do individual download from remote artifact. 255 // Try to stage the files from remote zip files. 256 file = getBuildInfo().stageRemoteFile(fileName, testsDir); 257 if (file != null) { 258 InvocationMetricLogger.addInvocationMetrics( 259 InvocationMetricKey.STAGE_UNDEFINED_DEPENDENCY, fileName); 260 } 261 } else if (file.isDirectory()) { 262 CLog.d("Found %s as a directory, searching further.", fileName); 263 file = FileUtil.findFile(file, fileName); 264 } 265 return file; 266 } 267 return null; 268 } 269 getFile(String fileName)270 private File getFile(String fileName) { 271 return mExecutionFiles.get(fileName); 272 } 273 getFromDependencyFolder(String fileName)274 private File getFromDependencyFolder(String fileName) { 275 File testsDir = mDependenciesFolder; 276 if (testsDir != null && testsDir.exists()) { 277 File file = FileUtil.findFile(testsDir, fileName); 278 if (file != null) { 279 return file; 280 } 281 } 282 return null; 283 } 284 } 285