1 /*
2  * Copyright (C) 2021 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.server.pm.dex;
18 
19 import static org.mockito.Mockito.inOrder;
20 
21 import android.platform.test.annotations.Presubmit;
22 
23 import com.android.internal.art.ArtStatsLog;
24 import com.android.server.pm.dex.ArtStatsLogUtils.ArtStatsLogger;
25 
26 import org.junit.AfterClass;
27 import org.junit.Assert;
28 import org.junit.Before;
29 import org.junit.BeforeClass;
30 import org.junit.Test;
31 import org.junit.runner.RunWith;
32 import org.junit.runners.JUnit4;
33 import org.mockito.InOrder;
34 import org.mockito.Mock;
35 import org.mockito.MockitoAnnotations;
36 
37 import java.io.IOException;
38 import java.io.OutputStream;
39 import java.nio.file.Files;
40 import java.nio.file.Path;
41 import java.util.zip.ZipEntry;
42 import java.util.zip.ZipOutputStream;
43 
44 /**
45  * Unit tests for {@link com.android.server.pm.dex.ArtStatsLogUtils}.
46  *
47  * Run with "atest ArtStatsLogUtilsTest".
48  */
49 @Presubmit
50 @RunWith(JUnit4.class)
51 public final class ArtStatsLogUtilsTest {
52     private static final String TAG = ArtStatsLogUtilsTest.class.getSimpleName();
53     private static final String COMPILER_FILTER = "space-profile";
54     private static final String PROFILE_DEX_METADATA = "primary.prof";
55     private static final String VDEX_DEX_METADATA = "primary.vdex";
56     private static final String INSTRUCTION_SET = "arm64";
57     private static final String BASE_APK_PATH = "/tmp/base.apk";
58     private static final String[] SPLIT_APK_PATHS =
59             new String[]{"/tmp/split1.apk", "/tmp/split2.apk"};
60     private static final byte[] DEX_CONTENT = "dexData".getBytes();
61     private static final int COMPILATION_REASON = 1;
62     private static final int RESULT_CODE = 222;
63     private static final int UID = 111;
64     private static final long COMPILE_TIME = 333L;
65     private static final long SESSION_ID = 444L;
66 
67     @Mock
68     ArtStatsLogger mockLogger;
69 
70     private static Path TEST_DIR;
71     private static Path DEX;
72     private static Path NON_DEX;
73 
74     @BeforeClass
setUpAll()75     public static void setUpAll() throws IOException {
76         TEST_DIR = Files.createTempDirectory(null);
77         DEX = Files.createFile(TEST_DIR.resolve("classes.dex"));
78         NON_DEX = Files.createFile(TEST_DIR.resolve("test.dex"));
79         Files.write(DEX, DEX_CONTENT);
80         Files.write(NON_DEX, "empty".getBytes());
81     }
82 
83     @AfterClass
tearnDownAll()84     public static void tearnDownAll() {
85         deleteSliently(DEX);
86         deleteSliently(NON_DEX);
87         deleteSliently(TEST_DIR);
88     }
89 
90     @Before
setUp()91     public void setUp() {
92         MockitoAnnotations.initMocks(this);
93     }
94 
95     @Test
testProfileAndVdexDexMetadata()96     public void testProfileAndVdexDexMetadata() throws IOException {
97         // Setup
98         Path dexMetadataPath = null;
99         Path apk = null;
100         try {
101             dexMetadataPath = createDexMetadata(PROFILE_DEX_METADATA, VDEX_DEX_METADATA);
102             apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
103 
104             // Act
105             ArtStatsLogUtils.writeStatsLog(
106                     mockLogger,
107                     SESSION_ID,
108                     COMPILER_FILTER,
109                     UID,
110                     COMPILE_TIME,
111                     dexMetadataPath.toString(),
112                     COMPILATION_REASON,
113                     RESULT_CODE,
114                     ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
115                     INSTRUCTION_SET,
116                     apk.toString());
117 
118             // Assert
119             verifyWrites(ArtStatsLog.
120                     ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE_AND_VDEX);
121         } finally {
122             deleteSliently(dexMetadataPath);
123             deleteSliently(apk);
124         }
125     }
126 
127     @Test
testProfileOnlyDexMetadata()128     public void testProfileOnlyDexMetadata() throws IOException {
129         // Setup
130         Path dexMetadataPath = null;
131         Path apk = null;
132         try {
133             dexMetadataPath = createDexMetadata(PROFILE_DEX_METADATA);
134             apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
135 
136             // Act
137             ArtStatsLogUtils.writeStatsLog(
138                     mockLogger,
139                     SESSION_ID,
140                     COMPILER_FILTER,
141                     UID,
142                     COMPILE_TIME,
143                     dexMetadataPath.toString(),
144                     COMPILATION_REASON,
145                     RESULT_CODE,
146                     ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
147                     INSTRUCTION_SET,
148                     apk.toString());
149 
150             // Assert
151             verifyWrites(ArtStatsLog.
152                     ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE);
153         } finally {
154             deleteSliently(dexMetadataPath);
155             deleteSliently(apk);
156         }
157     }
158 
159     @Test
testVdexOnlyDexMetadata()160     public void testVdexOnlyDexMetadata() throws IOException {
161         // Setup
162         Path dexMetadataPath = null;
163         Path apk = null;
164         try {
165             dexMetadataPath = createDexMetadata(VDEX_DEX_METADATA);
166             apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
167 
168             // Act
169             ArtStatsLogUtils.writeStatsLog(
170                     mockLogger,
171                     SESSION_ID,
172                     COMPILER_FILTER,
173                     UID,
174                     COMPILE_TIME,
175                     dexMetadataPath.toString(),
176                     COMPILATION_REASON,
177                     RESULT_CODE,
178                     ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
179                     INSTRUCTION_SET,
180                     apk.toString());
181 
182             // Assert
183             verifyWrites(ArtStatsLog.
184                     ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_VDEX);
185         } finally {
186             deleteSliently(dexMetadataPath);
187             deleteSliently(apk);
188         }
189     }
190 
191     @Test
testNoneDexMetadata()192     public void testNoneDexMetadata() throws IOException {
193         // Setup
194         Path apk = null;
195         try {
196             apk = zipFiles(".apk", DEX, NON_DEX);
197 
198             // Act
199             ArtStatsLogUtils.writeStatsLog(
200                     mockLogger,
201                     SESSION_ID,
202                     COMPILER_FILTER,
203                     UID,
204                     COMPILE_TIME,
205                     /*dexMetadataPath=*/ null,
206                     COMPILATION_REASON,
207                     RESULT_CODE,
208                     ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
209                     INSTRUCTION_SET,
210                     apk.toString());
211 
212             // Assert
213             verifyWrites(ArtStatsLog.
214                     ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_NONE);
215         } finally {
216             deleteSliently(apk);
217         }
218     }
219 
220     @Test
testUnKnownDexMetadata()221     public void testUnKnownDexMetadata() throws IOException {
222         // Setup
223         Path dexMetadataPath = null;
224         Path apk = null;
225         try {
226             dexMetadataPath = createDexMetadata("unknown");
227             apk = zipFiles(".apk", DEX, NON_DEX, dexMetadataPath);
228 
229             // Act
230             ArtStatsLogUtils.writeStatsLog(
231                     mockLogger,
232                     SESSION_ID,
233                     COMPILER_FILTER,
234                     UID,
235                     COMPILE_TIME,
236                     dexMetadataPath.toString(),
237                     COMPILATION_REASON,
238                     RESULT_CODE,
239                     ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
240                     INSTRUCTION_SET,
241                     apk.toString());
242 
243             // Assert
244             verifyWrites(ArtStatsLog.
245                     ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN);
246         } finally {
247             deleteSliently(dexMetadataPath);
248             deleteSliently(apk);
249         }
250     }
251 
252     @Test
testGetApkType()253     public void testGetApkType() {
254         // Act
255         int result1 = ArtStatsLogUtils.getApkType("/tmp/base.apk", BASE_APK_PATH, SPLIT_APK_PATHS);
256         int result2 = ArtStatsLogUtils.getApkType("/tmp/split1.apk", BASE_APK_PATH,
257                 SPLIT_APK_PATHS);
258         int result3 = ArtStatsLogUtils.getApkType("/tmp/none.apk", BASE_APK_PATH, SPLIT_APK_PATHS);
259 
260         // Assert
261         Assert.assertEquals(result1, ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE);
262         Assert.assertEquals(result2, ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_SPLIT);
263         Assert.assertEquals(result3,
264                 ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_UNKNOWN);
265     }
266 
verifyWrites(int dexMetadataType)267     private void verifyWrites(int dexMetadataType) {
268         InOrder inorder = inOrder(mockLogger);
269         inorder.verify(mockLogger).write(
270                 SESSION_ID, UID,
271                 COMPILATION_REASON,
272                 COMPILER_FILTER,
273                 ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_RESULT_CODE,
274                 RESULT_CODE,
275                 dexMetadataType,
276                 ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
277                 INSTRUCTION_SET);
278         inorder.verify(mockLogger).write(
279                 SESSION_ID,
280                 UID,
281                 COMPILATION_REASON,
282                 COMPILER_FILTER,
283                 ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_DEX_CODE_COUNTER_BYTES,
284                 DEX_CONTENT.length,
285                 dexMetadataType,
286                 ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
287                 INSTRUCTION_SET);
288         inorder.verify(mockLogger).write(
289                 SESSION_ID,
290                 UID,
291                 COMPILATION_REASON,
292                 COMPILER_FILTER,
293                 ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_TOTAL_TIME_COUNTER_MILLIS,
294                 COMPILE_TIME,
295                 dexMetadataType,
296                 ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
297                 INSTRUCTION_SET);
298     }
299 
zipFiles(String suffix, Path... files)300     private Path zipFiles(String suffix, Path... files) throws IOException {
301         Path zipFile = Files.createTempFile(null, suffix);
302         try (final OutputStream os = Files.newOutputStream(zipFile)) {
303             try (final ZipOutputStream zos = new ZipOutputStream(os)) {
304                 for (Path file : files) {
305                     ZipEntry zipEntry = new ZipEntry(file.getFileName().toString());
306                     zos.putNextEntry(zipEntry);
307                     zos.write(Files.readAllBytes(file));
308                     zos.closeEntry();
309                 }
310             }
311         }
312         return zipFile;
313     }
314 
createDexMetadata(String... entryNames)315     private Path createDexMetadata(String... entryNames) throws IOException {
316         Path zipFile = Files.createTempFile(null, ".dm");
317         try (final OutputStream os = Files.newOutputStream(zipFile)) {
318             try (final ZipOutputStream zos = new ZipOutputStream(os)) {
319                 for (String entryName : entryNames) {
320                     ZipEntry zipEntry = new ZipEntry(entryName);
321                     zos.putNextEntry(zipEntry);
322                     zos.write(entryName.getBytes());
323                     zos.closeEntry();
324                 }
325             }
326         }
327         return zipFile;
328     }
329 
deleteSliently(Path file)330     private static void deleteSliently(Path file) {
331         if (file != null) {
332             try {
333                 Files.deleteIfExists(file);
334             } catch (IOException e) {
335                 // ignore
336             }
337         }
338     }
339 }
340