1 /*
2  * Copyright (C) 2017 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.compatibility.common.tradefed.presubmit;
17 
18 import static org.junit.Assert.assertNotNull;
19 import static org.junit.Assert.assertTrue;
20 import static org.junit.Assert.fail;
21 
22 import com.android.compatibility.common.tradefed.targetprep.FilePusher;
23 import com.android.tradefed.config.ConfigurationException;
24 import com.android.tradefed.config.ConfigurationFactory;
25 import com.android.tradefed.config.IConfiguration;
26 import com.android.tradefed.config.IDeviceConfiguration;
27 import com.android.tradefed.targetprep.ITargetPreparer;
28 import com.android.tradefed.targetprep.PushFilePreparer;
29 import com.android.tradefed.targetprep.TestAppInstallSetup;
30 import com.android.tradefed.testtype.IRemoteTest;
31 import com.android.tradefed.testtype.InstrumentationTest;
32 import com.android.tradefed.util.AaptParser;
33 import com.android.tradefed.util.FileUtil;
34 
35 import com.google.common.base.Joiner;
36 
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 import org.junit.runners.JUnit4;
40 
41 import java.io.File;
42 import java.util.ArrayList;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48 
49 /**
50  * Class to validate tests Apks in testcases/
51  */
52 @RunWith(JUnit4.class)
53 public class ApkPackageNameCheck {
54 
55     private static final Set<String> EXCEPTION_LIST = new HashSet<>();
56     static {
57         // TODO: Remove exception when their package have been fixed.
58         EXCEPTION_LIST.add("android.app.cts");
59         EXCEPTION_LIST.add("android.content.cts");
60         // TODO(b/290634145): Refactor WM CTS into fully isolated modules
61         EXCEPTION_LIST.add("android.server.wm.cts");
62         EXCEPTION_LIST.add("android.systemui.cts");
63         // TODO(b/331566031): Remove exception when the test owner
64         // split the CTS and MCTS into different modules.
65         EXCEPTION_LIST.add("android.mediav2.cts");
66         EXCEPTION_LIST.add("android.media.bettertogether.cts");
67         EXCEPTION_LIST.add("android.media.codec.cts");
68         EXCEPTION_LIST.add("android.media.drmframework.cts");
69         EXCEPTION_LIST.add("android.media.encoder.cts");
70         EXCEPTION_LIST.add("android.media.extractor.cts");
71         EXCEPTION_LIST.add("android.media.misc.cts");
72         EXCEPTION_LIST.add("android.media.muxer.cts");
73         EXCEPTION_LIST.add("android.media.player.cts");
74         EXCEPTION_LIST.add("android.media.recorder.cts");
75         EXCEPTION_LIST.add("android.media.mediaparser.cts");
76         EXCEPTION_LIST.add("android.mediastress.cts");
77         EXCEPTION_LIST.add("android.media.mediatranscoding.cts");
78         EXCEPTION_LIST.add("android.view.textclassifier.cts");
79         EXCEPTION_LIST.add("android.appsecurity.cts");
80     }
81 
82     /**
83      * We ensure that no apk with same package names may be installed. Otherwise it may results in
84      * conflicts.
85      */
86     @Test
testApkPackageNames()87     public void testApkPackageNames() throws Exception {
88         String ctsRoot = System.getProperty("CTS_ROOT");
89         File testcases = new File(ctsRoot, "/android-cts/testcases/");
90         if (!testcases.exists()) {
91             fail(String.format("%s does not exists", testcases));
92             return;
93         }
94         Set<File> listConfigs = FileUtil.findFilesObject(testcases, ".*\\.config");
95         assertTrue(listConfigs.size() > 0);
96         // We check all apk installed by all modules
97         Map<String, String> packageNames = new HashMap<>();
98 
99         List<String> errors = new ArrayList<>();
100 
101         for (File config : listConfigs) {
102             IConfiguration c = ConfigurationFactory.getInstance()
103                     .createConfigurationFromArgs(new String[] {config.getAbsolutePath()});
104             // For each config, we check all the apk it's going to install
105             List<File> apkNames = new ArrayList<>();
106             List<String> packageListNames = new ArrayList<>();
107             for (IDeviceConfiguration dConfig : c.getDeviceConfig()) {
108                 for (ITargetPreparer prep : dConfig.getTargetPreparers()) {
109                     if (prep instanceof TestAppInstallSetup) {
110                         apkNames.addAll(((TestAppInstallSetup) prep).getTestsFileName());
111                     }
112                     // Ensure the files requested to be pushed exist.
113                     if (prep instanceof FilePusher && ((FilePusher) prep).shouldAppendBitness()) {
114                         if (!((FilePusher) prep).shouldAbortOnFailure()) {
115                             errors.add(String.format("Config %s should not disable abort-on-push-failure", config.getName()));
116                         }
117                         for (File f : ((PushFilePreparer) prep).getPushSpecs(null).values()) {
118                             String path = f.getPath();
119                             File file32 = FileUtil.findFile(config.getParentFile(), path + "32");
120                             File file64 = FileUtil.findFile(config.getParentFile(), path + "64");
121                             if (file32 == null || file64 == null) {
122                                 errors.add(
123                                         String.format(
124                                                 "File %s[32/64] wasn't found in module "
125                                                         + "dependencies while it's expected to "
126                                                         + "be pushed as part of %s. Make sure "
127                                                         + "that it's added in the Android.bp "
128                                                         + "file of the module under "
129                                                         + "'data_device_bins_both' field.",
130                                                         path, config.getName()));
131                                 continue;
132                             }
133                         }
134                     } else if (prep instanceof PushFilePreparer) {
135                         if (!((PushFilePreparer) prep).shouldAbortOnFailure()) {
136                             errors.add(String.format("Config %s should not disable abort-on-push-failure", config.getName()));
137                         }
138                         for (File f : ((PushFilePreparer) prep).getPushSpecs(null).values()) {
139                             String path = f.getPath();
140                             // Use findFiles to also match top-level dir, which is a valid push spec
141                             Set<String> toBePushed = FileUtil.findFiles(config.getParentFile(),
142                                                                         path);
143                             if (toBePushed.isEmpty()) {
144                                 errors.add(
145                                         String.format(
146                                                 "File %s wasn't found in module dependencies "
147                                                         + "while it's expected to be pushed "
148                                                         + "as part of %s. Make sure that it's added in the Android.bp file of the module under 'data' field.",
149                                                         path, config.getName()));
150                                 continue;
151                             }
152                         }
153                     }
154                 }
155 
156                 // All apks need to be in the config dir or sub-dir
157                 for (File apk : apkNames) {
158                     String apkName = apk.getName();
159                     File apkFile = FileUtil.findFile(config.getParentFile(), apkName);
160                     if (apkFile == null || !apkFile.exists()) {
161                         errors.add(
162                                 String.format("Module %s is trying to install %s which does not "
163                                                 + "exists in testcases/. Make sure that it's added "
164                                                 + "in the Android.bp file of the module under "
165                                                 + "'data' field.", config.getName(), apkName));
166                         continue;
167                     }
168                     AaptParser res = AaptParser.parse(apkFile);
169                     assertNotNull(res);
170                     String packageName = res.getPackageName();
171                     String put = packageNames.put(packageName, apkName);
172                     packageListNames.add(packageName);
173                     // The package already exists and it's a different apk
174                     if (put != null && !apkName.equals(put) && !EXCEPTION_LIST.contains(packageName)) {
175                         errors.add(
176                                 String.format("Module %s: Package name '%s' from apk '%s' was "
177                                                 + "already added by previous apk '%s'.",
178                                         config.getName(), packageName, apkName, put));
179                         continue;
180                     }
181                 }
182             }
183             // Catch a test trying to run something it doesn't install.
184             List<IRemoteTest> tests = c.getTests();
185             for (IRemoteTest test : tests) {
186                 if (test instanceof InstrumentationTest) {
187                     InstrumentationTest instrumentationTest = (InstrumentationTest) test;
188                     if (instrumentationTest.getPackageName() != null) {
189                         if (!packageListNames.contains(instrumentationTest.getPackageName())) {
190                             errors.add(
191                                     String.format("Module %s requests to run '%s' but it's not "
192                                         + "part of any apks.",
193                                         config.getName(), instrumentationTest.getPackageName()));
194                             continue;
195                         }
196                     }
197                 }
198             }
199         }
200         if (!errors.isEmpty()) {
201             throw new ConfigurationException(Joiner.on("\n").join(errors));
202         }
203     }
204 }
205