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