1 /*
2  * Copyright (C) 2015 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.util;
17 
18 import com.android.tradefed.build.IBuildInfo;
19 import com.android.tradefed.build.IDeviceBuildInfo;
20 import com.android.tradefed.targetprep.AltDirBehavior;
21 
22 import java.io.File;
23 import java.io.IOException;
24 import java.net.URL;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
28 
29 /**
30  * A helper class for operations related to tests zip generated by Android build system
31  */
32 public class BuildTestsZipUtils {
33 
34     /**
35      * Resolve the actual apk path based on testing artifact information inside build info.
36      *
37      * @param buildInfo build artifact information
38      * @param apkFileName filename of the apk to install
39      * @param altDirs alternative search paths, in addition to path inside {@code buildInfo}
40      * @param altDirBehavior how alternative search paths should be used against path inside
41      *        {@code buildInfo}: as fallback, or as override; if unspecified, fallback will be used
42      * @param lookupInResource if the file should be looked up in test harness resources as a final
43      *        fallback mechanism
44      * @param deviceSigningKey
45      * @return a {@link File} representing the physical apk file on host or {@code null} if the
46      *     file does not exist.
47      */
getApkFile(IBuildInfo buildInfo, String apkFileName, List<File> altDirs, AltDirBehavior altDirBehavior, boolean lookupInResource, String deviceSigningKey)48     public static File getApkFile(IBuildInfo buildInfo, String apkFileName,
49             List<File> altDirs, AltDirBehavior altDirBehavior,
50             boolean lookupInResource, String deviceSigningKey) throws IOException {
51         String apkBase = apkFileName.split("\\.")[0];
52 
53         List<File> dirs = new ArrayList<>();
54         if (altDirs != null) {
55             for (File dir : altDirs) {
56                 dirs.add(dir);
57                 // Files in tests zip file will be in DATA/app/,
58                 // DATA/app/apk_name or DATA/priv-app/apk_name
59                 dirs.add(FileUtil.getFileForPath(dir, "DATA", "app"));
60                 dirs.add(FileUtil.getFileForPath(dir, "DATA", "app", apkBase));
61                 dirs.add(FileUtil.getFileForPath(dir, "DATA", "priv-app", apkBase));
62                 // Files in out dir will be in data/app/apk_name
63                 dirs.add(FileUtil.getFileForPath(dir, "data", "app", apkBase));
64             }
65         }
66         // reverse the order so ones provided via command line last can be searched first
67         Collections.reverse(dirs);
68 
69         List<File> expandedTestDirs = new ArrayList<>();
70         File testsDir = null;
71         if (buildInfo != null && buildInfo instanceof IDeviceBuildInfo) {
72             testsDir = ((IDeviceBuildInfo) buildInfo).getTestsDir();
73             if (testsDir != null && testsDir.exists()) {
74                 expandedTestDirs.add(FileUtil.getFileForPath(testsDir, "DATA", "app"));
75                 expandedTestDirs.add(FileUtil.getFileForPath(testsDir, "DATA", "app", apkBase));
76                 expandedTestDirs.add(
77                         FileUtil.getFileForPath(testsDir, "DATA", "priv-app", apkBase));
78                 expandedTestDirs.add(FileUtil.getFileForPath(testsDir, apkBase));
79 
80                 // Files in testcases directory imported from env. variable can have a folder
81                 // hierarchy, so we search for folder.
82                 File testcasesSubDir = FileUtil.findFile(testsDir, apkBase);
83                 if (testcasesSubDir != null) {
84                     expandedTestDirs.add(testcasesSubDir);
85                 } else {
86                     // If there doesn't exist a directory named after apkBase, it's possible that
87                     // the apk is built output to a different module directory. Therefore, try to
88                     // search entire testsDir to locate the apk.
89                     // TODO(dshi): Find a better way to locate apk. Ideally we should start with the
90                     // test module's directory to avoid false-positive result.
91                     expandedTestDirs.add(testsDir);
92                 }
93             }
94         }
95         if (altDirBehavior == null) {
96             altDirBehavior = AltDirBehavior.FALLBACK;
97         }
98         if (altDirBehavior == AltDirBehavior.FALLBACK) {
99             // alt dirs are appended after build artifact dirs
100             expandedTestDirs.addAll(dirs);
101             dirs = expandedTestDirs;
102         } else if (altDirBehavior == AltDirBehavior.OVERRIDE) {
103             dirs.addAll(expandedTestDirs);
104         } else {
105             throw new IOException("Missing handler for alt-dir-behavior: " + altDirBehavior);
106         }
107         if (dirs.isEmpty() && !lookupInResource) {
108             throw new IOException(
109                     "Provided buildInfo does not contain a valid tests directory and no " +
110                     "fallback options were provided");
111         }
112 
113         for (File dir : dirs) {
114             // Recursively search each folder
115             File testAppFile = FileUtil.findFile(dir, apkFileName);
116             if (testAppFile != null && testAppFile.exists()) {
117                 return testAppFile;
118             }
119         }
120         if (lookupInResource) {
121             List<String> resourceLookup = new ArrayList<>();
122             if (deviceSigningKey != null) {
123                 resourceLookup.add(String.format("/apks/%s-%s.apk", apkBase, deviceSigningKey));
124             }
125             resourceLookup.add(String.format("/apks/%s", apkFileName));
126             File apkTempFile = FileUtil.createTempFile(apkFileName, ".apk");
127             URL apkUrl = null;
128             for (String path :  resourceLookup) {
129                 apkUrl = BuildTestsZipUtils.class.getResource(path);
130                 if (apkUrl != null) {
131                     break;
132                 }
133             }
134             if (apkUrl != null) {
135                 FileUtil.writeToFile(apkUrl.openStream(), apkTempFile);
136                 // since we don't know when the file would be no longer needed, we set the file to
137                 // be deleted on VM termination
138                 apkTempFile.deleteOnExit();
139                 return apkTempFile;
140             }
141             // If we couldn't find a resource, we delete the tmp file
142             FileUtil.deleteFile(apkTempFile);
143         }
144 
145         // Try to stage the files from remote zip files.
146         if (testsDir != null) {
147             File apkFile = buildInfo.stageRemoteFile(apkFileName, testsDir);
148             if (apkFile != null) {
149                 return apkFile;
150             }
151         }
152         return null;
153     }
154 }
155