• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.tests.odsign;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertTrue;
23 
24 import com.android.tradefed.invoker.TestInformation;
25 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
26 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
27 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
28 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
29 
30 import org.junit.Before;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 
34 import java.util.Arrays;
35 import java.util.HashSet;
36 import java.util.Set;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39 
40 /**
41  * Test to check end-to-end odrefresh invocations, but without odsign, fs-verity, and ART runtime
42  * involved.
43  */
44 @RunWith(DeviceJUnit4ClassRunner.class)
45 public class OdrefreshHostTest extends BaseHostJUnit4Test {
46     private static final String CACHE_INFO_FILE =
47             OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME + "/cache-info.xml";
48     private static final String ODREFRESH_BIN = "odrefresh";
49     private static final String ODREFRESH_COMMAND =
50             ODREFRESH_BIN + " --partial-compilation --no-refresh --compile";
51     private static final String ODREFRESH_MINIMAL_COMMAND =
52             ODREFRESH_BIN + " --partial-compilation --no-refresh --minimal --compile";
53 
54     private static final String TAG = "OdrefreshHostTest";
55     private static final String ZYGOTE_ARTIFACTS_KEY = TAG + ":ZYGOTE_ARTIFACTS";
56     private static final String SYSTEM_SERVER_ARTIFACTS_KEY = TAG + ":SYSTEM_SERVER_ARTIFACTS";
57 
58     private OdsignTestUtils mTestUtils;
59 
60     @BeforeClassWithInfo
beforeClassWithDevice(TestInformation testInfo)61     public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
62         OdsignTestUtils testUtils = new OdsignTestUtils(testInfo);
63         testUtils.installTestApex();
64         testUtils.reboot();
65 
66         HashSet<String> zygoteArtifacts = new HashSet<>();
67         for (String zygoteName : testUtils.ZYGOTE_NAMES) {
68             zygoteArtifacts.addAll(
69                     testUtils.getZygoteLoadedArtifacts(zygoteName).orElse(new HashSet<>()));
70         }
71         Set<String> systemServerArtifacts = testUtils.getSystemServerLoadedArtifacts();
72 
73         testInfo.properties().put(ZYGOTE_ARTIFACTS_KEY, String.join(":", zygoteArtifacts));
74         testInfo.properties()
75                 .put(SYSTEM_SERVER_ARTIFACTS_KEY, String.join(":", systemServerArtifacts));
76     }
77 
78     @AfterClassWithInfo
afterClassWithDevice(TestInformation testInfo)79     public static void afterClassWithDevice(TestInformation testInfo) throws Exception {
80         OdsignTestUtils testUtils = new OdsignTestUtils(testInfo);
81         testUtils.uninstallTestApex();
82         testUtils.reboot();
83     }
84 
85     @Before
setUp()86     public void setUp() throws Exception {
87         mTestUtils = new OdsignTestUtils(getTestInformation());
88     }
89 
90     @Test
verifyArtSamegradeUpdateTriggersCompilation()91     public void verifyArtSamegradeUpdateTriggersCompilation() throws Exception {
92         simulateArtApexUpgrade();
93         long timeMs = mTestUtils.getCurrentTimeMs();
94         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
95 
96         assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
97         assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
98     }
99 
100     @Test
verifyOtherApexSamegradeUpdateTriggersCompilation()101     public void verifyOtherApexSamegradeUpdateTriggersCompilation() throws Exception {
102         simulateApexUpgrade();
103         long timeMs = mTestUtils.getCurrentTimeMs();
104         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
105 
106         assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
107         assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
108     }
109 
110     @Test
verifyBootClasspathOtaTriggersCompilation()111     public void verifyBootClasspathOtaTriggersCompilation() throws Exception {
112         simulateBootClasspathOta();
113         long timeMs = mTestUtils.getCurrentTimeMs();
114         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
115 
116         assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
117         assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
118     }
119 
120     @Test
verifySystemServerOtaTriggersCompilation()121     public void verifySystemServerOtaTriggersCompilation() throws Exception {
122         simulateSystemServerOta();
123         long timeMs = mTestUtils.getCurrentTimeMs();
124         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
125 
126         assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
127         assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
128     }
129 
130     @Test
verifyMissingArtifactTriggersCompilation()131     public void verifyMissingArtifactTriggersCompilation() throws Exception {
132         Set<String> missingArtifacts = simulateMissingArtifacts();
133         Set<String> remainingArtifacts = new HashSet<>();
134         remainingArtifacts.addAll(getZygoteArtifacts());
135         remainingArtifacts.addAll(getSystemServerArtifacts());
136         remainingArtifacts.removeAll(missingArtifacts);
137 
138         mTestUtils.removeCompilationLogToAvoidBackoff();
139         long timeMs = mTestUtils.getCurrentTimeMs();
140         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
141 
142         assertArtifactsNotModifiedAfter(remainingArtifacts, timeMs);
143         assertArtifactsModifiedAfter(missingArtifacts, timeMs);
144     }
145 
146     @Test
verifyEnableUffdGcChangeTriggersCompilation()147     public void verifyEnableUffdGcChangeTriggersCompilation() throws Exception {
148         try {
149             // Disable phenotype flag syncing. Potentially, we can set
150             // `set_sync_disabled_for_tests` to `until_reboot`, but setting it to
151             // `persistent` prevents unrelated system crashes/restarts from affecting the
152             // test. `set_sync_disabled_for_tests` is reset in the `finally` block anyway.
153             getDevice().executeShellV2Command(
154                     "device_config set_sync_disabled_for_tests persistent");
155 
156             // Simulate that the phenotype flag is set to the default value.
157             getDevice().executeShellV2Command(
158                     "device_config put runtime_native_boot enable_uffd_gc false");
159 
160             long timeMs = mTestUtils.getCurrentTimeMs();
161             getDevice().executeShellV2Command(ODREFRESH_COMMAND);
162 
163             // Artifacts should not be re-compiled.
164             assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
165             assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
166 
167             // Simulate that the phenotype flag is set to true.
168             getDevice().executeShellV2Command(
169                     "device_config put runtime_native_boot enable_uffd_gc true");
170 
171             timeMs = mTestUtils.getCurrentTimeMs();
172             getDevice().executeShellV2Command(ODREFRESH_COMMAND);
173 
174             // Artifacts should be re-compiled.
175             assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
176             assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
177 
178             // Run odrefresh again with the flag unchanged.
179             timeMs = mTestUtils.getCurrentTimeMs();
180             getDevice().executeShellV2Command(ODREFRESH_COMMAND);
181 
182             // Artifacts should not be re-compiled.
183             assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
184             assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
185 
186             // Simulate that the phenotype flag is set to false.
187             getDevice().executeShellV2Command(
188                     "device_config put runtime_native_boot enable_uffd_gc false");
189 
190             timeMs = mTestUtils.getCurrentTimeMs();
191             getDevice().executeShellV2Command(ODREFRESH_COMMAND);
192 
193             // Artifacts should be re-compiled.
194             assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
195             assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
196         } finally {
197             getDevice().executeShellV2Command("device_config set_sync_disabled_for_tests none");
198             getDevice().executeShellV2Command(
199                     "device_config delete runtime_native_boot enable_uffd_gc");
200         }
201     }
202 
203     @Test
verifySystemPropertyMismatchTriggersCompilation()204     public void verifySystemPropertyMismatchTriggersCompilation() throws Exception {
205         // Change a system property from empty to a value.
206         getDevice().setProperty("dalvik.vm.foo", "1");
207         long timeMs = mTestUtils.getCurrentTimeMs();
208         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
209 
210         // Artifacts should be re-compiled.
211         assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
212         assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
213 
214         // Run again with the same value.
215         timeMs = mTestUtils.getCurrentTimeMs();
216         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
217 
218         // Artifacts should not be re-compiled.
219         assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
220         assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
221 
222         // Change the system property to another value.
223         getDevice().setProperty("dalvik.vm.foo", "2");
224         timeMs = mTestUtils.getCurrentTimeMs();
225         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
226 
227         // Artifacts should be re-compiled.
228         assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
229         assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
230 
231         // Run again with the same value.
232         timeMs = mTestUtils.getCurrentTimeMs();
233         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
234 
235         // Artifacts should not be re-compiled.
236         assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
237         assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
238 
239         // Change the system property to empty.
240         getDevice().setProperty("dalvik.vm.foo", "");
241         timeMs = mTestUtils.getCurrentTimeMs();
242         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
243 
244         // Artifacts should be re-compiled.
245         assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
246         assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
247 
248         // Run again with the same value.
249         timeMs = mTestUtils.getCurrentTimeMs();
250         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
251 
252         // Artifacts should not be re-compiled.
253         assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
254         assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
255     }
256 
257     @Test
verifyNoCompilationWhenCacheIsGood()258     public void verifyNoCompilationWhenCacheIsGood() throws Exception {
259         mTestUtils.removeCompilationLogToAvoidBackoff();
260         long timeMs = mTestUtils.getCurrentTimeMs();
261         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
262 
263         assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
264         assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
265     }
266 
267     @Test
verifyUnexpectedFilesAreCleanedUp()268     public void verifyUnexpectedFilesAreCleanedUp() throws Exception {
269         String unexpected = OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME + "/unexpected";
270         getDevice().pushString(/*contents=*/"", unexpected);
271         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
272 
273         assertFalse(getDevice().doesFileExist(unexpected));
274     }
275 
276     @Test
verifyCacheInfoOmitsIrrelevantApexes()277     public void verifyCacheInfoOmitsIrrelevantApexes() throws Exception {
278         String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
279 
280         // cacheInfo should list all APEXes that have compilable JARs and
281         // none that do not.
282 
283         // This should always contain classpath JARs, that's the reason it exists.
284         assertThat(cacheInfo).contains("name=\"com.android.sdkext\"");
285 
286         // This should never contain classpath JARs, it's the native runtime.
287         assertThat(cacheInfo).doesNotContain("name=\"com.android.runtime\"");
288     }
289 
290     @Test
verifyCompilationOsMode()291     public void verifyCompilationOsMode() throws Exception {
292         mTestUtils.removeCompilationLogToAvoidBackoff();
293         simulateApexUpgrade();
294         long timeMs = mTestUtils.getCurrentTimeMs();
295         getDevice().executeShellV2Command(
296                 ODREFRESH_BIN + " --no-refresh --partial-compilation"
297                         + " --compilation-os-mode --compile");
298 
299         assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
300         assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
301 
302         String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
303         assertThat(cacheInfo).contains("compilationOsMode=\"true\"");
304 
305         // Compilation OS does not write the compilation log to the host.
306         mTestUtils.removeCompilationLogToAvoidBackoff();
307 
308         // Simulate the odrefresh invocation on the next boot.
309         timeMs = mTestUtils.getCurrentTimeMs();
310         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
311 
312         // odrefresh should not re-compile anything.
313         assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
314         assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
315     }
316 
317     @Test
verifyMinimalCompilation()318     public void verifyMinimalCompilation() throws Exception {
319         mTestUtils.removeCompilationLogToAvoidBackoff();
320         getDevice().executeShellV2Command(
321             "rm -rf " + OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME);
322         getDevice().executeShellV2Command(ODREFRESH_MINIMAL_COMMAND);
323 
324         mTestUtils.restartZygote();
325 
326         // The minimal boot image should be loaded.
327         Set<String> minimalZygoteArtifacts =
328                 mTestUtils.verifyZygotesLoadedArtifacts("boot_minimal");
329 
330         // Running the command again should not overwrite the minimal boot image.
331         mTestUtils.removeCompilationLogToAvoidBackoff();
332         long timeMs = mTestUtils.getCurrentTimeMs();
333         getDevice().executeShellV2Command(ODREFRESH_MINIMAL_COMMAND);
334 
335         assertArtifactsNotModifiedAfter(minimalZygoteArtifacts, timeMs);
336 
337         // `odrefresh --check` should keep the minimal boot image.
338         mTestUtils.removeCompilationLogToAvoidBackoff();
339         timeMs = mTestUtils.getCurrentTimeMs();
340         getDevice().executeShellV2Command(ODREFRESH_BIN + " --check");
341 
342         assertArtifactsNotModifiedAfter(minimalZygoteArtifacts, timeMs);
343 
344         // A normal odrefresh invocation should replace the minimal boot image with a full one.
345         mTestUtils.removeCompilationLogToAvoidBackoff();
346         timeMs = mTestUtils.getCurrentTimeMs();
347         getDevice().executeShellV2Command(ODREFRESH_COMMAND);
348 
349         for (String artifact : minimalZygoteArtifacts) {
350             assertFalse(
351                     String.format(
352                             "Artifact %s should be cleaned up while it still exists", artifact),
353                     getDevice().doesFileExist(artifact));
354         }
355 
356         assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
357     }
358 
359     /**
360      * Checks the input line by line and replaces all lines that match the regex with the given
361      * replacement.
362      */
replaceLine(String input, String regex, String replacement)363     private String replaceLine(String input, String regex, String replacement) {
364         StringBuffer output = new StringBuffer();
365         Pattern p = Pattern.compile(regex);
366         for (String line : input.split("\n")) {
367             Matcher m = p.matcher(line);
368             if (m.matches()) {
369                 m.appendReplacement(output, replacement);
370                 output.append("\n");
371             } else {
372                 output.append(line + "\n");
373             }
374         }
375         return output.toString();
376     }
377 
378     /**
379      * Simulates that there is an OTA that updates a boot classpath jar.
380      */
simulateBootClasspathOta()381     private void simulateBootClasspathOta() throws Exception {
382         String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
383         // Replace the cached checksum of /system/framework/framework.jar with "aaaaaaaa".
384         cacheInfo = replaceLine(
385                 cacheInfo,
386                 "(.*/system/framework/framework\\.jar.*checksums=\").*?(\".*)",
387                 "$1aaaaaaaa$2");
388         getDevice().pushString(cacheInfo, CACHE_INFO_FILE);
389     }
390 
391     /**
392      * Simulates that there is an OTA that updates a system server jar.
393      */
simulateSystemServerOta()394     private void simulateSystemServerOta() throws Exception {
395         String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
396         // Replace the cached checksum of /system/framework/services.jar with "aaaaaaaa".
397         cacheInfo = replaceLine(
398                 cacheInfo,
399                 "(.*/system/framework/services\\.jar.*checksums=\").*?(\".*)",
400                 "$1aaaaaaaa$2");
401         getDevice().pushString(cacheInfo, CACHE_INFO_FILE);
402     }
403 
404     /**
405      * Simulates that an ART APEX has been upgraded.
406      */
simulateArtApexUpgrade()407     private void simulateArtApexUpgrade() throws Exception {
408         String apexInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
409         // Replace the lastUpdateMillis of com.android.art with "1".
410         apexInfo = replaceLine(
411                 apexInfo,
412                 "(.*com\\.android\\.art.*lastUpdateMillis=\").*?(\".*)",
413                 "$11$2");
414         getDevice().pushString(apexInfo, CACHE_INFO_FILE);
415     }
416 
417     /**
418      * Simulates that an APEX has been upgraded. We could install a real APEX, but that would
419      * introduce an extra dependency to this test, which we want to avoid.
420      */
simulateApexUpgrade()421     private void simulateApexUpgrade() throws Exception {
422         String apexInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
423         // Replace the lastUpdateMillis of com.android.wifi with "1".
424         apexInfo = replaceLine(
425                 apexInfo,
426                 "(.*com\\.android\\.wifi.*lastUpdateMillis=\").*?(\".*)",
427                 "$11$2");
428         getDevice().pushString(apexInfo, CACHE_INFO_FILE);
429     }
430 
simulateMissingArtifacts()431     private Set<String> simulateMissingArtifacts() throws Exception {
432         Set<String> missingArtifacts = new HashSet<>();
433         String sample = getSystemServerArtifacts().iterator().next();
434         for (String extension : OdsignTestUtils.APP_ARTIFACT_EXTENSIONS) {
435             String artifact = replaceExtension(sample, extension);
436             getDevice().deleteFile(artifact);
437             missingArtifacts.add(artifact);
438         }
439         return missingArtifacts;
440     }
441 
assertArtifactsModifiedAfter(Set<String> artifacts, long timeMs)442     private void assertArtifactsModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
443         for (String artifact : artifacts) {
444             long modifiedTime = mTestUtils.getModifiedTimeMs(artifact);
445             assertTrue(
446                     String.format(
447                             "Artifact %s is not re-compiled. Modified time: %d, Reference time: %d",
448                             artifact,
449                             modifiedTime,
450                             timeMs),
451                     modifiedTime > timeMs);
452         }
453     }
454 
assertArtifactsNotModifiedAfter(Set<String> artifacts, long timeMs)455     private void assertArtifactsNotModifiedAfter(Set<String> artifacts, long timeMs)
456             throws Exception {
457         for (String artifact : artifacts) {
458             long modifiedTime = mTestUtils.getModifiedTimeMs(artifact);
459             assertTrue(
460                     String.format(
461                             "Artifact %s is unexpectedly re-compiled. " +
462                                     "Modified time: %d, Reference time: %d",
463                             artifact,
464                             modifiedTime,
465                             timeMs),
466                     modifiedTime < timeMs);
467         }
468     }
469 
470     private String replaceExtension(String filename, String extension) throws Exception {
471         int index = filename.lastIndexOf(".");
472         assertTrue("Extension not found in filename: " + filename, index != -1);
473         return filename.substring(0, index) + extension;
474     }
475 
476     private Set<String> getColonSeparatedSet(String key) {
477         String value = getTestInformation().properties().get(key);
478         if (value == null || value.isEmpty()) {
479             return new HashSet<>();
480         }
481         return new HashSet<>(Arrays.asList(value.split(":")));
482     }
483 
484     private Set<String> getZygoteArtifacts() {
485         return getColonSeparatedSet(ZYGOTE_ARTIFACTS_KEY);
486     }
487 
488     private Set<String> getSystemServerArtifacts() {
489         return getColonSeparatedSet(SYSTEM_SERVER_ARTIFACTS_KEY);
490     }
491 }
492