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