• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 
17 package com.android.modules.targetprep;
18 
19 import static android.compat.testing.Classpaths.ClasspathType.BOOTCLASSPATH;
20 import static android.compat.testing.Classpaths.ClasspathType.SYSTEMSERVERCLASSPATH;
21 
22 import android.compat.testing.Classpaths;
23 import android.compat.testing.Classpaths.ClasspathType;
24 
25 import com.android.modules.proto.ClasspathClasses.Classpath;
26 import com.android.modules.proto.ClasspathClasses.ClasspathClassesDump;
27 import com.android.modules.proto.ClasspathClasses.ClasspathEntry;
28 import com.android.modules.proto.ClasspathClasses.Jar;
29 
30 import com.android.tradefed.config.Option;
31 import com.android.tradefed.device.DeviceNotAvailableException;
32 import com.android.tradefed.device.INativeDevice;
33 import com.android.tradefed.device.ITestDevice;
34 import com.android.tradefed.invoker.TestInformation;
35 import com.android.tradefed.log.LogUtil.CLog;
36 import com.android.tradefed.targetprep.BaseTargetPreparer;
37 import com.android.tradefed.targetprep.TargetSetupError;
38 import com.android.tradefed.util.RunUtil;
39 import com.google.common.collect.ImmutableList;
40 import com.google.common.collect.ImmutableSet;
41 
42 import java.io.File;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.nio.file.Files;
46 import java.nio.file.Path;
47 import java.util.Objects;
48 
49 import com.android.tools.smali.dexlib2.iface.ClassDef;
50 
51 /*
52  * Target preparer that fetches classpath relevant artifacts for a test in a 'reentrant' manner.
53  *
54  * <p>The preparer will fetch all <b>BOOTCLASSPATH</b>, <b>SYSTEMSERVERCLASSPATH</b> and shared
55  * libraries from the device, parse their contents and dump the classnames into a temporary
56  * directory.</p>
57  *
58  * <p>Additionally, the preparer <i>can</i> fetch, parse and dump data for module specific artifacts
59  * (i.e. apk-in-apex) if specified.</p>
60  *
61  * <p>Nested runs of the artifact fetcher (i.e. in the case of a top-level test config xml and a
62  * child test xml) will only fetch non-common elements, and remove temporary class dumps fetched
63  * during that particular preparer run.</p>
64  *
65  * <p>Each module's conformance framework test config xml must run the preparer (with module
66  * specific parameters) before the entrypoint test jar, and the top-level conformance framework
67  * config xml must also run it before any other module xml.</p>
68  *
69  * <p>The upshot is that when running all conformance framework tests for all modules, the shared
70  * artifacts are fetched and processed only once.</p>
71  */
72 public class ClasspathFetcher extends BaseTargetPreparer {
73 
74     public static final String DEVICE_JAR_ARTIFACTS_TAG = "device-jar-artifacts";
75     public static final String BCP_CLASSES_FILE = "bcp.pb";
76     public static final String SSCP_CLASSES_FILE = "sscp.pb";
77 
78     // TODO(andreionea): also fetch classes for standalone system server jars, apk-in-apex and
79     // shared libraries. They require more mocking on the test side.
80 
81     public static final String APEX_PKG_TAG = "apex-package";
82     // Special case for fetching only non-updatable platform.
83     public static final String PLATFORM_PACKAGE = "platform";
84 
85     @Option(name = "apex-package",
86             description = "The package name of the apex under test.")
87     private String mApexPackage;
88 
89     private boolean mFetchedArtifacts = false;
90 
91     @Override
setUp(TestInformation testInfo)92     public void setUp(TestInformation testInfo)
93             throws TargetSetupError, DeviceNotAvailableException {
94         Objects.requireNonNull(testInfo.getDevice());
95         if (mApexPackage != null) {
96             testInfo.properties().put(APEX_PKG_TAG, mApexPackage);
97         }
98         // The artifacts have been fetched already, no need to do anything else.
99         if (testInfo.properties().containsKey(DEVICE_JAR_ARTIFACTS_TAG)) {
100             return;
101         }
102         try {
103             final Path tmpDir = Files.createTempDirectory("device_artifacts");
104             testInfo.properties().put(DEVICE_JAR_ARTIFACTS_TAG,
105                 tmpDir.toAbsolutePath().toString());
106 
107             getClassesInClasspath(testInfo.getDevice(), BOOTCLASSPATH)
108                 .writeTo(new FileOutputStream(new File(tmpDir.toFile(), BCP_CLASSES_FILE)));
109             getClassesInClasspath(testInfo.getDevice(), SYSTEMSERVERCLASSPATH)
110                 .writeTo(new FileOutputStream(new File(tmpDir.toFile(), SSCP_CLASSES_FILE)));
111 
112             mFetchedArtifacts = true;
113         } catch(IOException e) {
114             throw new RuntimeException("Could not create temp artifacts dir!", e);
115         }
116     }
117 
118     @Override
tearDown(TestInformation testInfo, Throwable e)119     public void tearDown(TestInformation testInfo, Throwable e) {
120         if (mFetchedArtifacts) {
121             try {
122                 final String path = testInfo.properties().get(DEVICE_JAR_ARTIFACTS_TAG);
123                 if (path == null) {
124                     throw new IllegalStateException("Target preparer has previously fetched"
125                             + " artifacts, but the DEVICE_JAR_ARTIFACTS_TAG property was removed");
126                 }
127                 final File jarArtifactsDir = new File(path);
128                 deleteDirectory(jarArtifactsDir);
129             } finally {
130                 testInfo.properties().remove(DEVICE_JAR_ARTIFACTS_TAG);
131             }
132         }
133     }
134 
classpathTypeToClasspathEnum(ClasspathType t)135     private Classpath classpathTypeToClasspathEnum(ClasspathType t) {
136         switch(t) {
137             case BOOTCLASSPATH:
138                 return Classpath.valueOf(Classpath.BOOTCLASSPATH_VALUE);
139             case SYSTEMSERVERCLASSPATH:
140                 return Classpath.valueOf(Classpath.SYSTEMSERVERCLASSPATH_VALUE);
141             default:
142                 throw new RuntimeException("Unknown classpath type " + t);
143         }
144     }
145 
getClassesInFile(INativeDevice device, String file)146     private ImmutableSet<String> getClassesInFile(INativeDevice device, String file)
147             throws DeviceNotAvailableException, IOException {
148         final File jar = device.pullFile(file);
149         if (jar == null) {
150             throw new IllegalStateException("could not pull remote file " + file);
151         }
152         return Classpaths.getClassDefsFromJar(jar)
153                 .stream()
154                 .map(ClassDef::getType)
155                 .collect(ImmutableSet.toImmutableSet());
156     }
157 
getClassesInClasspath(INativeDevice device, ClasspathType type)158     private ClasspathClassesDump getClassesInClasspath(INativeDevice device, ClasspathType type)
159             throws DeviceNotAvailableException, IOException {
160         ClasspathClassesDump.Builder builder = ClasspathClassesDump.newBuilder();
161         final ImmutableList<String> jars = Classpaths.getJarsOnClasspath(device, type);
162         for (String jar : jars) {
163             ClasspathEntry.Builder entryBuilder = ClasspathEntry.newBuilder();
164             Jar.Builder jarBuilder = Jar.newBuilder();
165             jarBuilder.setClasspath(classpathTypeToClasspathEnum(type));
166             jarBuilder.setPath(jar);
167             entryBuilder.setJar(jarBuilder.build());
168             entryBuilder.addAllClasses(getClassesInFile(device, jar));
169             builder.addEntries(entryBuilder.build());
170         }
171         return builder.build();
172     }
173 
174 
175     /**
176      * Deletes a directory and its contents recursively
177      *
178      * @param directory to delete
179      */
deleteDirectory(File directory)180     private static void deleteDirectory(File directory) {
181         File[] files = directory.listFiles();
182         if (files != null) {
183             for (File file : files) {
184                 if (!file.isDirectory()) {
185                     file.delete();
186                 } else {
187                     deleteDirectory(file);
188                 }
189             }
190         }
191         directory.delete();
192     }
193 
194 }