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 17 package com.android.cts.tzdata; 18 19 import static org.junit.Assert.assertArrayEquals; 20 21 import libcore.timezone.TzDataSetVersion; 22 import libcore.timezone.testing.ZoneInfoTestHelper; 23 24 import com.android.timezone.distro.DistroVersion; 25 import com.android.timezone.distro.TimeZoneDistro; 26 import com.android.timezone.distro.builder.TimeZoneDistroBuilder; 27 import com.android.tradefed.device.DeviceNotAvailableException; 28 import com.android.tradefed.testtype.DeviceTestCase; 29 30 import java.io.File; 31 import java.io.FileOutputStream; 32 import java.io.IOException; 33 import java.nio.charset.StandardCharsets; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import java.util.Comparator; 37 import java.util.StringJoiner; 38 import java.util.function.Consumer; 39 40 /** 41 * Tests for the tzdatacheck binary. 42 * 43 * <p>The tzdatacheck binary operates over two directories: the "system directory" containing the 44 * time zone rules in the system image, and a "data directory" in the data partition which can 45 * optionally contain time zone rules data files for bionic/libcore and ICU. 46 * 47 * <p>This test executes the tzdatacheck binary to confirm it operates correctly in a number of 48 * simulated situations; simulated system and data directories in various states are created in a 49 * location the shell user has permission to access and the tzdatacheck binary is then executed. 50 * The status code and directory state after execution is then used to determine if the tzdatacheck 51 * binary operated correctly. 52 * 53 * <p>Most of the tests below prepare simulated directory structure for the system and data dirs 54 * on the host before pushing them to the device. Device state is then checked rather than syncing 55 * the files back. 56 */ 57 public class TzDataCheckTest extends DeviceTestCase { 58 59 /** 60 * The name of the directory containing the current time zone rules data beneath 61 * {@link #mDataDir}. Also known to {@link 62 * com.android.timezone.distro.installer.TimeZoneDistroInstaller} and tzdatacheck.cpp. 63 */ 64 private static final String CURRENT_DIR_NAME = "current"; 65 66 /** 67 * The name of the directory containing the staged time zone rules data beneath 68 * {@link #mDataDir}. Also known to {@link 69 * com.android.timezone.distro.installer.TimeZoneDistroInstaller} and tzdatacheck.cpp. 70 */ 71 private static final String STAGED_DIR_NAME = "staged"; 72 73 /** 74 * The name of the file inside the staged directory that indicates the staged operation is an 75 * uninstall. Also known to {@link com.android.timezone.distro.installer.TimeZoneDistroInstaller} and 76 * tzdatacheck.cpp. 77 */ 78 private static final String UNINSTALL_TOMBSTONE_FILE_NAME = "STAGED_UNINSTALL_TOMBSTONE"; 79 80 /** 81 * The name of the /system time zone data file. Also known to tzdatacheck.cpp. 82 */ 83 private static final String SYSTEM_TZ_VERSION_FILE_NAME = "tz_version"; 84 85 /** A valid time zone rules version guaranteed to be older than {@link #RULES_VERSION_TWO} */ 86 private static final String RULES_VERSION_ONE = "2016g"; 87 /** A valid time zone rules version guaranteed to be newer than {@link #RULES_VERSION_ONE} */ 88 private static final String RULES_VERSION_TWO = "2016h"; 89 /** 90 * An arbitrary, valid time zone rules version used when it doesn't matter what the rules 91 * version is. 92 */ 93 private static final String VALID_RULES_VERSION = RULES_VERSION_ONE; 94 95 /** An arbitrary valid revision number. */ 96 private static final int VALID_REVISION = 1; 97 98 private String mDeviceAndroidRootDir; 99 private PathPair mTestRootDir; 100 private PathPair mSystemDir; 101 private PathPair mDataDir; 102 setUp()103 public void setUp() throws Exception { 104 super.setUp(); 105 106 // It's not clear how we would get this without invoking "/system/bin/sh", but we need the 107 // value first to do so. It has been hardcoded instead. 108 mDeviceAndroidRootDir = "/system"; 109 110 // Create a test root directory on host and device. 111 Path hostTestRootDir = Files.createTempDirectory("tzdatacheck_test"); 112 mTestRootDir = new PathPair( 113 hostTestRootDir, 114 "/data/local/tmp/tzdatacheck_test"); 115 createDeviceDirectory(mTestRootDir); 116 117 // tzdatacheck requires two directories: a "system" path and a "data" path. 118 mSystemDir = mTestRootDir.createSubPath("system_dir"); 119 mDataDir = mTestRootDir.createSubPath("data_dir"); 120 121 // Create the host-side directory structure (for preparing files before pushing them to 122 // device and looking at files retrieved from device). 123 createHostDirectory(mSystemDir); 124 createHostDirectory(mDataDir); 125 126 // Create the equivalent device-side directory structure for receiving files. 127 createDeviceDirectory(mSystemDir); 128 createDeviceDirectory(mDataDir); 129 } 130 131 @Override tearDown()132 public void tearDown() throws Exception { 133 // Remove the test root directories that have been created by this test. 134 deleteHostDirectory(mTestRootDir, true /* failOnError */); 135 deleteDeviceDirectory(mTestRootDir, true /* failOnError */); 136 super.tearDown(); 137 } 138 139 /** 140 * Test the real base files exist in the expected locations - tzcdatacheck relies on some of 141 * them via a command line argument hardcoded in system/core/rootdir/init.rc. 142 */ testExpectedBaseFilesExist()143 public void testExpectedBaseFilesExist() throws Exception { 144 String baseTzFilesDir = "/apex/com.android.runtime/etc/tz/"; 145 assertDeviceFileExists(baseTzFilesDir + "tz_version"); 146 assertDeviceFileExists(baseTzFilesDir + "tzdata"); 147 assertDeviceFileExists(baseTzFilesDir + "tzlookup.xml"); 148 } 149 testTooFewArgs()150 public void testTooFewArgs() throws Exception { 151 // No need to set up or push files to the device for this test. 152 assertEquals(1, runTzDataCheckWithArgs(new String[0])); 153 assertEquals(1, runTzDataCheckWithArgs(new String[] { "oneArg" })); 154 } 155 156 // {dataDir}/staged exists but it is a file. testStaging_stagingDirIsFile()157 public void testStaging_stagingDirIsFile() throws Exception { 158 // Set up the /system directory structure on host. 159 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 160 161 // Set up the /data directory structure on host. 162 PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME); 163 // Create a file with the same name as the directory that tzdatacheck expects. 164 Files.write(dataStagedDir.hostPath, new byte[] { 'a' }); 165 166 // Push the host test directory and contents to the device. 167 pushHostTestDirToDevice(); 168 169 // Execute tzdatacheck and check the status code. Failures due to staging issues are 170 // generally ignored providing the device is left in a reasonable state. 171 assertEquals(0, runTzDataCheckOnDevice()); 172 173 // Assert the file was just ignored. This is a fairly arbitrary choice to leave it rather 174 // than delete. 175 assertDevicePathExists(dataStagedDir); 176 assertDevicePathIsFile(dataStagedDir); 177 } 178 179 // {dataDir}/staged exists but /current dir is a file. testStaging_uninstall_currentDirIsFile()180 public void testStaging_uninstall_currentDirIsFile() throws Exception { 181 // Set up the /system directory structure on host. 182 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 183 184 // Set up the /data directory structure on host. 185 186 // Create a staged uninstall. 187 PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME); 188 createStagedUninstallOnHost(dataStagedDir); 189 190 // Create a file with the same name as the directory that tzdatacheck expects. 191 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 192 Files.write(dataCurrentDir.hostPath, new byte[] { 'a' }); 193 194 // Push the host test directory and contents to the device. 195 pushHostTestDirToDevice(); 196 197 // Execute tzdatacheck and check the status code. 198 assertEquals(0, runTzDataCheckOnDevice()); 199 200 // Assert the device was left in a valid "uninstalled" state. 201 assertDevicePathDoesNotExist(dataStagedDir); 202 assertDevicePathDoesNotExist(dataCurrentDir); 203 } 204 205 // {dataDir}/staged contains an uninstall, but there is nothing to uninstall. testStaging_uninstall_noCurrent()206 public void testStaging_uninstall_noCurrent() throws Exception { 207 // Set up the /system directory structure on host. 208 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 209 210 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 211 212 // Set up the /data directory structure on host. 213 214 // Create a staged uninstall. 215 PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME); 216 createStagedUninstallOnHost(dataStagedDir); 217 218 // Push the host test directory and contents to the device. 219 pushHostTestDirToDevice(); 220 221 // Execute tzdatacheck and check the status code. Failures due to staging issues are 222 // generally ignored providing the device is left in a reasonable state. 223 assertEquals(0, runTzDataCheckOnDevice()); 224 225 // Assert the device was left in a valid "uninstalled" state. 226 assertDevicePathDoesNotExist(dataStagedDir); 227 assertDevicePathDoesNotExist(dataCurrentDir); 228 } 229 230 // {dataDir}/staged contains an uninstall, and there is something to uninstall. testStaging_uninstall_withCurrent()231 public void testStaging_uninstall_withCurrent() throws Exception { 232 // Set up the /system directory structure on host. 233 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 234 235 // Set up the /data directory structure on host. 236 237 // Create a staged uninstall. 238 PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME); 239 createStagedUninstallOnHost(dataStagedDir); 240 241 // Create a current installed distro. 242 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 243 byte[] distroBytes = createValidDistroBuilder().buildBytes(); 244 unpackOnHost(dataCurrentDir, distroBytes); 245 246 // Push the host test directory and contents to the device. 247 pushHostTestDirToDevice(); 248 249 // Execute tzdatacheck and check the status code. Failures due to staging issues are 250 // generally ignored providing the device is left in a reasonable state. 251 assertEquals(0, runTzDataCheckOnDevice()); 252 253 // Assert the device was left in a valid "uninstalled" state. 254 assertDevicePathDoesNotExist(dataStagedDir); 255 assertDevicePathDoesNotExist(dataCurrentDir); 256 } 257 258 // {dataDir}/staged exists but /current dir is a file. testStaging_install_currentDirIsFile()259 public void testStaging_install_currentDirIsFile() throws Exception { 260 // Set up the /system directory structure on host. 261 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 262 263 // Set up the /data directory structure on host. 264 265 // Create a staged install. 266 PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME); 267 byte[] distroBytes = createValidDistroBuilder().buildBytes(); 268 unpackOnHost(dataStagedDir, distroBytes); 269 270 // Create a file with the same name as the directory that tzdatacheck expects. 271 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 272 Files.write(dataCurrentDir.hostPath, new byte[] { 'a' }); 273 274 // Push the host test directory and contents to the device. 275 pushHostTestDirToDevice(); 276 277 // Execute tzdatacheck and check the status code. Failures due to staging issues are 278 // generally ignored providing the device is left in a reasonable state. 279 assertEquals(0, runTzDataCheckOnDevice()); 280 281 // Assert the device was left in a valid "installed" state. 282 assertDevicePathDoesNotExist(dataStagedDir); 283 assertDeviceDirContainsDistro(dataCurrentDir, distroBytes); 284 } 285 286 // {dataDir}/staged contains an install, but there is nothing to replace. testStaging_install_noCurrent()287 public void testStaging_install_noCurrent() throws Exception { 288 // Set up the /system directory structure on host. 289 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 290 291 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 292 293 // Set up the /data directory structure on host. 294 295 // Create a staged install. 296 PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME); 297 byte[] stagedDistroBytes = createValidDistroBuilder().buildBytes(); 298 unpackOnHost(dataStagedDir, stagedDistroBytes); 299 300 // Push the host test directory and contents to the device. 301 pushHostTestDirToDevice(); 302 303 // Execute tzdatacheck and check the status code. Failures due to staging issues are 304 // generally ignored providing the device is left in a reasonable state. 305 assertEquals(0, runTzDataCheckOnDevice()); 306 307 // Assert the device was left in a valid "installed" state. 308 assertDevicePathDoesNotExist(dataStagedDir); 309 assertDeviceDirContainsDistro(dataCurrentDir, stagedDistroBytes); 310 } 311 312 // {dataDir}/staged contains an install, and there is something to replace. testStaging_install_withCurrent()313 public void testStaging_install_withCurrent() throws Exception { 314 // Set up the /system directory structure on host. 315 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 316 317 DistroVersion currentDistroVersion = new DistroVersion( 318 TzDataSetVersion.currentFormatMajorVersion(), 1, VALID_RULES_VERSION, 1); 319 DistroVersion stagedDistroVersion = new DistroVersion( 320 TzDataSetVersion.currentFormatMajorVersion(), 1, VALID_RULES_VERSION, 2); 321 322 // Set up the /data directory structure on host. 323 324 // Create a staged uninstall. 325 PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME); 326 byte[] stagedDistroBytes = createValidDistroBuilder() 327 .setDistroVersion(stagedDistroVersion) 328 .buildBytes(); 329 unpackOnHost(dataStagedDir, stagedDistroBytes); 330 331 // Create a current installed distro. 332 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 333 byte[] currentDistroBytes = createValidDistroBuilder() 334 .setDistroVersion(currentDistroVersion) 335 .buildBytes(); 336 unpackOnHost(dataCurrentDir, currentDistroBytes); 337 338 // Push the host test directory and contents to the device. 339 pushHostTestDirToDevice(); 340 341 // Execute tzdatacheck and check the status code. Failures due to staging issues are 342 // generally ignored providing the device is left in a reasonable state. 343 assertEquals(0, runTzDataCheckOnDevice()); 344 345 // Assert the device was left in a valid "installed" state. 346 // The stagedDistro should now be the one in the current dir. 347 assertDevicePathDoesNotExist(dataStagedDir); 348 assertDeviceDirContainsDistro(dataCurrentDir, stagedDistroBytes); 349 } 350 351 // {dataDir}/staged contains an invalid install, and there is something to replace. 352 // Most of the invalid cases are tested without staging; this is just to prove that staging 353 // an invalid distro is handled the same. testStaging_install_withCurrent_invalidStaged()354 public void testStaging_install_withCurrent_invalidStaged() throws Exception { 355 // Set up the /system directory structure on host. 356 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 357 358 // Set up the /data directory structure on host. 359 360 // Create a staged uninstall which contains invalid files (missing distro version). 361 PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME); 362 byte[] stagedDistroBytes = createValidDistroBuilder() 363 .clearVersionForTests() 364 .buildUnvalidatedBytes(); 365 unpackOnHost(dataStagedDir, stagedDistroBytes); 366 367 // Create a current installed distro. 368 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 369 byte[] currentDistroBytes = createValidDistroBuilder().buildBytes(); 370 unpackOnHost(dataCurrentDir, currentDistroBytes); 371 372 // Push the host test directory and contents to the device. 373 pushHostTestDirToDevice(); 374 375 // Execute tzdatacheck and check the status code. The staged directory will have become the 376 // current one, but then it will be discovered to be invalid and will be removed. 377 assertEquals(3, runTzDataCheckOnDevice()); 378 379 // Assert the device was left in a valid "uninstalled" state. 380 assertDevicePathDoesNotExist(dataStagedDir); 381 assertDevicePathDoesNotExist(dataCurrentDir); 382 } 383 384 // No {dataDir}/current exists. testNoCurrentDataDir()385 public void testNoCurrentDataDir() throws Exception { 386 // Set up the /system directory structure on host. 387 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 388 389 // Deliberately not creating anything on host in the data dir here, leaving the empty 390 // structure. 391 392 // Push the host test directory and contents to the device. 393 pushHostTestDirToDevice(); 394 395 // Execute tzdatacheck and check the status code. 396 assertEquals(0, runTzDataCheckOnDevice()); 397 } 398 399 // {dataDir}/current exists but it is a file. testCurrentDataDirIsFile()400 public void testCurrentDataDirIsFile() throws Exception { 401 // Set up the /system directory structure on host. 402 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 403 404 // Set up the /data directory structure on host. 405 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 406 // Create a file with the same name as the directory that tzdatacheck expects. 407 Files.write(dataCurrentDir.hostPath, new byte[] { 'a' }); 408 409 // Push the host test directory and contents to the device. 410 pushHostTestDirToDevice(); 411 412 // Execute tzdatacheck and check the status code. 413 assertEquals(2, runTzDataCheckOnDevice()); 414 415 // Assert the file was just ignored. This is a fairly arbitrary choice to leave it rather 416 // than delete. 417 assertDevicePathExists(dataCurrentDir); 418 assertDevicePathIsFile(dataCurrentDir); 419 } 420 421 // {dataDir}/current exists but is missing the distro version file. testMissingDataDirDistroVersionFile()422 public void testMissingDataDirDistroVersionFile() throws Exception { 423 // Set up the /system directory structure on host. 424 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 425 426 // Set up the /data directory structure on host. 427 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 428 byte[] distroWithoutAVersionFileBytes = createValidDistroBuilder() 429 .clearVersionForTests() 430 .buildUnvalidatedBytes(); 431 unpackOnHost(dataCurrentDir, distroWithoutAVersionFileBytes); 432 433 // Push the host test directory and contents to the device. 434 pushHostTestDirToDevice(); 435 436 // Execute tzdatacheck and check the status code. 437 assertEquals(3, runTzDataCheckOnDevice()); 438 439 // Assert the current data directory was deleted. 440 assertDevicePathDoesNotExist(dataCurrentDir); 441 } 442 443 // {dataDir}/current exists but the distro version file is short. testShortDataDirDistroVersionFile()444 public void testShortDataDirDistroVersionFile() throws Exception { 445 // Set up the /system directory structure on host. 446 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 447 448 // Set up the /data directory structure on host. 449 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 450 unpackOnHost(dataCurrentDir, createValidDistroBuilder().buildBytes()); 451 // Replace the distro version file with a short file. 452 Path distroVersionFile = 453 dataCurrentDir.hostPath.resolve(TimeZoneDistro.DISTRO_VERSION_FILE_NAME); 454 assertHostFileExists(distroVersionFile); 455 Files.write(distroVersionFile, new byte[3]); 456 457 // Push the host test directory and contents to the device. 458 pushHostTestDirToDevice(); 459 460 // Execute tzdatacheck and check the status code. 461 assertEquals(3, runTzDataCheckOnDevice()); 462 463 // Assert the current data directory was deleted. 464 assertDevicePathDoesNotExist(dataCurrentDir); 465 } 466 467 // {dataDir}/current exists and the distro version file is long enough, but contains junk. testCorruptDistroVersionFile()468 public void testCorruptDistroVersionFile() throws Exception { 469 // Set up the /system directory structure on host. 470 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 471 472 // Set up the /data directory structure on host. 473 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 474 unpackOnHost(dataCurrentDir, createValidDistroBuilder().buildBytes()); 475 476 // Replace the distro version file with junk. 477 Path distroVersionFile = 478 dataCurrentDir.hostPath.resolve(TimeZoneDistro.DISTRO_VERSION_FILE_NAME); 479 assertHostFileExists(distroVersionFile); 480 481 int fileLength = (int) Files.size(distroVersionFile); 482 byte[] junkArray = new byte[fileLength]; // all zeros 483 Files.write(distroVersionFile, junkArray); 484 485 // Push the host test directory and contents to the device. 486 pushHostTestDirToDevice(); 487 488 // Execute tzdatacheck and check the status code. 489 assertEquals(4, runTzDataCheckOnDevice()); 490 491 // Assert the current data directory was deleted. 492 assertDevicePathDoesNotExist(dataCurrentDir); 493 } 494 495 // {dataDir}/current exists but the distro version is incorrect. testInvalidMajorDistroVersion_older()496 public void testInvalidMajorDistroVersion_older() throws Exception { 497 // Set up the /system directory structure on host. 498 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 499 500 // Set up the /data directory structure on host. 501 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 502 DistroVersion oldMajorDistroVersion = new DistroVersion( 503 TzDataSetVersion.currentFormatMajorVersion() - 1, 1, VALID_RULES_VERSION, 1); 504 byte[] distroBytes = createValidDistroBuilder() 505 .setDistroVersion(oldMajorDistroVersion) 506 .buildBytes(); 507 unpackOnHost(dataCurrentDir, distroBytes); 508 509 // Push the host test directory and contents to the device. 510 pushHostTestDirToDevice(); 511 512 // Execute tzdatacheck and check the status code. 513 assertEquals(5, runTzDataCheckOnDevice()); 514 515 // Assert the current data directory was deleted. 516 assertDevicePathDoesNotExist(dataCurrentDir); 517 } 518 519 // {dataDir}/current exists but the distro version is incorrect. testInvalidMajorDistroVersion_newer()520 public void testInvalidMajorDistroVersion_newer() throws Exception { 521 // Set up the /system directory structure on host. 522 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 523 524 // Set up the /data directory structure on host. 525 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 526 DistroVersion newMajorDistroVersion = new DistroVersion( 527 TzDataSetVersion.currentFormatMajorVersion() + 1, 528 TzDataSetVersion.currentFormatMinorVersion(), 529 VALID_RULES_VERSION, VALID_REVISION); 530 byte[] distroBytes = createValidDistroBuilder() 531 .setDistroVersion(newMajorDistroVersion) 532 .buildBytes(); 533 unpackOnHost(dataCurrentDir, distroBytes); 534 535 // Push the host test directory and contents to the device. 536 pushHostTestDirToDevice(); 537 538 // Execute tzdatacheck and check the status code. 539 assertEquals(5, runTzDataCheckOnDevice()); 540 541 // Assert the current data directory was deleted. 542 assertDevicePathDoesNotExist(dataCurrentDir); 543 } 544 545 // {dataDir}/current exists but the distro version is incorrect. testInvalidMinorDistroVersion_older()546 public void testInvalidMinorDistroVersion_older() throws Exception { 547 // Set up the /system directory structure on host. 548 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 549 550 // Set up the /data directory structure on host. 551 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 552 DistroVersion oldMinorDistroVersion = new DistroVersion( 553 TzDataSetVersion.currentFormatMajorVersion(), 554 TzDataSetVersion.currentFormatMinorVersion() - 1, 555 VALID_RULES_VERSION, 1); 556 byte[] distroBytes = createValidDistroBuilder() 557 .setDistroVersion(oldMinorDistroVersion) 558 .buildBytes(); 559 unpackOnHost(dataCurrentDir, distroBytes); 560 561 // Push the host test directory and contents to the device. 562 pushHostTestDirToDevice(); 563 564 // Execute tzdatacheck and check the status code. 565 assertEquals(5, runTzDataCheckOnDevice()); 566 567 // Assert the current data directory was deleted. 568 assertDevicePathDoesNotExist(dataCurrentDir); 569 } 570 571 // {dataDir}/current exists but the distro version is newer (which is accepted because it should 572 // be backwards compatible). testValidMinorDistroVersion_newer()573 public void testValidMinorDistroVersion_newer() throws Exception { 574 // Set up the /system directory structure on host. 575 createSystemTzVersionFileOnHost(VALID_RULES_VERSION); 576 577 // Set up the /data directory structure on host. 578 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 579 DistroVersion newMajorDistroVersion = new DistroVersion( 580 TzDataSetVersion.currentFormatMajorVersion(), 581 TzDataSetVersion.currentFormatMinorVersion() + 1, 582 VALID_RULES_VERSION, VALID_REVISION); 583 byte[] distroBytes = createValidDistroBuilder() 584 .setDistroVersion(newMajorDistroVersion) 585 .buildBytes(); 586 unpackOnHost(dataCurrentDir, distroBytes); 587 588 // Push the host test directory and contents to the device. 589 pushHostTestDirToDevice(); 590 591 // Execute tzdatacheck and check the status code. 592 assertEquals(0, runTzDataCheckOnDevice()); 593 594 // Assert the current data directory was not touched. 595 assertDeviceDirContainsDistro(dataCurrentDir, distroBytes); 596 } 597 598 // {dataDir}/current is valid but the tz_version file in /system is missing. testSystemTzVersionFileMissing()599 public void testSystemTzVersionFileMissing() throws Exception { 600 // Deliberately not writing anything in /system here. 601 602 // Set up the /data directory structure on host. 603 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 604 byte[] validDistroBytes = createValidDistroBuilder().buildBytes(); 605 unpackOnHost(dataCurrentDir, validDistroBytes); 606 607 // Push the host test directory and contents to the device. 608 pushHostTestDirToDevice(); 609 610 // Execute tzdatacheck and check the status code. 611 assertEquals(6, runTzDataCheckOnDevice()); 612 613 // Assert the current data directory was not touched. 614 assertDeviceDirContainsDistro(dataCurrentDir, validDistroBytes); 615 } 616 617 // {dataDir}/current is valid but the tz_version file in /system is junk. testSystemTzVersionFileCorrupt()618 public void testSystemTzVersionFileCorrupt() throws Exception { 619 // Set up the /system directory structure on host. 620 byte[] invalidTzDataBytes = new byte[20]; 621 Files.write(mSystemDir.hostPath.resolve(SYSTEM_TZ_VERSION_FILE_NAME), invalidTzDataBytes); 622 623 // Set up the /data directory structure on host. 624 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 625 byte[] validDistroBytes = createValidDistroBuilder().buildBytes(); 626 unpackOnHost(dataCurrentDir, validDistroBytes); 627 628 // Push the host test directory and contents to the device. 629 pushHostTestDirToDevice(); 630 631 // Execute tzdatacheck and check the status code. 632 assertEquals(7, runTzDataCheckOnDevice()); 633 634 // Assert the current data directory was not touched. 635 assertDeviceDirContainsDistro(dataCurrentDir, validDistroBytes); 636 } 637 638 // {dataDir}/current is valid and the tz_version file in /system is for older data. testSystemTzRulesOlder()639 public void testSystemTzRulesOlder() throws Exception { 640 // Set up the /system directory structure on host. 641 createSystemTzVersionFileOnHost(RULES_VERSION_ONE); 642 643 // Set up the /data directory structure on host. 644 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 645 // Newer than RULES_VERSION_ONE in /system 646 final String distroRulesVersion = RULES_VERSION_TWO; 647 DistroVersion distroVersion = new DistroVersion( 648 TzDataSetVersion.currentFormatMajorVersion(), 649 TzDataSetVersion.currentFormatMinorVersion(), distroRulesVersion, VALID_REVISION); 650 byte[] distroBytes = createValidDistroBuilder() 651 .setDistroVersion(distroVersion) 652 .setTzDataFile(createValidTzDataBytes(distroRulesVersion)) 653 .buildBytes(); 654 unpackOnHost(dataCurrentDir, distroBytes); 655 656 // Push the host test directory and contents to the device. 657 pushHostTestDirToDevice(); 658 659 // Execute tzdatacheck and check the status code. 660 assertEquals(0, runTzDataCheckOnDevice()); 661 662 // Assert the current data directory was not touched. 663 assertDeviceDirContainsDistro(dataCurrentDir, distroBytes); 664 } 665 666 // {dataDir}/current is valid and the tz_version file in /system is the same. Data dir should be 667 // kept. testSystemTzVersionSame()668 public void testSystemTzVersionSame() throws Exception { 669 // Set up the /system directory structure on host. 670 final String systemRulesVersion = VALID_RULES_VERSION; 671 createSystemTzVersionFileOnHost(systemRulesVersion); 672 673 // Set up the /data directory structure on host. 674 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 675 DistroVersion distroVersion = new DistroVersion( 676 TzDataSetVersion.currentFormatMajorVersion(), 677 TzDataSetVersion.currentFormatMinorVersion(), 678 systemRulesVersion, 679 VALID_REVISION); 680 byte[] distroBytes = createValidDistroBuilder() 681 .setDistroVersion(distroVersion) 682 .setTzDataFile(createValidTzDataBytes(systemRulesVersion)) 683 .buildBytes(); 684 unpackOnHost(dataCurrentDir, distroBytes); 685 686 // Push the host test directory and contents to the device. 687 pushHostTestDirToDevice(); 688 689 // Execute tzdatacheck and check the status code. 690 assertEquals(0, runTzDataCheckOnDevice()); 691 692 // Assert the current data directory was not touched. 693 assertDeviceDirContainsDistro(dataCurrentDir, distroBytes); 694 } 695 696 // {dataDir}/current is valid and the tzdata file in /system is the newer. testSystemTzVersionNewer()697 public void testSystemTzVersionNewer() throws Exception { 698 // Set up the /system directory structure on host. 699 String systemRulesVersion = RULES_VERSION_TWO; 700 createSystemTzVersionFileOnHost(systemRulesVersion); 701 702 // Set up the /data directory structure on host. 703 PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME); 704 String distroRulesVersion = RULES_VERSION_ONE; // Older than the system version. 705 DistroVersion distroVersion = new DistroVersion( 706 TzDataSetVersion.currentFormatMajorVersion(), 707 TzDataSetVersion.currentFormatMinorVersion(), 708 distroRulesVersion, 709 VALID_REVISION); 710 byte[] distroBytes = createValidDistroBuilder() 711 .setDistroVersion(distroVersion) 712 .setTzDataFile(createValidTzDataBytes(distroRulesVersion)) 713 .buildBytes(); 714 unpackOnHost(dataCurrentDir, distroBytes); 715 716 // Push the host test directory and contents to the device. 717 pushHostTestDirToDevice(); 718 719 // Execute tzdatacheck and check the status code. 720 assertEquals(0, runTzDataCheckOnDevice()); 721 722 // It is important the dataCurrentDir is deleted in this case - this test case is the main 723 // reason tzdatacheck exists. 724 assertDevicePathDoesNotExist(dataCurrentDir); 725 } 726 createSystemTzVersionFileOnHost(String systemRulesVersion)727 private void createSystemTzVersionFileOnHost(String systemRulesVersion) throws Exception { 728 byte[] systemTzData = createValidTzVersionBytes(systemRulesVersion); 729 Files.write(mSystemDir.hostPath.resolve(SYSTEM_TZ_VERSION_FILE_NAME), systemTzData); 730 } 731 createStagedUninstallOnHost(PathPair stagedDir)732 private static void createStagedUninstallOnHost(PathPair stagedDir) throws Exception { 733 createHostDirectory(stagedDir); 734 735 PathPair uninstallTombstoneFile = stagedDir.createSubPath(UNINSTALL_TOMBSTONE_FILE_NAME); 736 // Create an empty file. 737 new FileOutputStream(uninstallTombstoneFile.hostFile()).close(); 738 } 739 unpackOnHost(PathPair path, byte[] distroBytes)740 private static void unpackOnHost(PathPair path, byte[] distroBytes) throws Exception { 741 createHostDirectory(path); 742 new TimeZoneDistro(distroBytes).extractTo(path.hostFile()); 743 } 744 createValidDistroBuilder()745 private static TimeZoneDistroBuilder createValidDistroBuilder() throws Exception { 746 String distroRulesVersion = VALID_RULES_VERSION; 747 DistroVersion validDistroVersion = 748 new DistroVersion( 749 TzDataSetVersion.currentFormatMajorVersion(), 750 TzDataSetVersion.currentFormatMinorVersion(), 751 distroRulesVersion, VALID_REVISION); 752 return new TimeZoneDistroBuilder() 753 .setDistroVersion(validDistroVersion) 754 .setTzDataFile(createValidTzDataBytes(distroRulesVersion)) 755 .setIcuDataFile(new byte[10]); 756 } 757 createValidTzDataBytes(String rulesVersion)758 private static byte[] createValidTzDataBytes(String rulesVersion) { 759 return new ZoneInfoTestHelper.TzDataBuilder() 760 .initializeToValid() 761 .setHeaderMagic("tzdata" + rulesVersion) 762 .build(); 763 } 764 createValidTzVersionBytes(String rulesVersion)765 private static byte[] createValidTzVersionBytes(String rulesVersion) throws Exception { 766 return new TzDataSetVersion( 767 TzDataSetVersion.currentFormatMajorVersion(), 768 TzDataSetVersion.currentFormatMinorVersion(), 769 rulesVersion, 770 VALID_REVISION) 771 .toBytes(); 772 } 773 runTzDataCheckOnDevice()774 private int runTzDataCheckOnDevice() throws Exception { 775 return runTzDataCheckWithArgs(new String[] { mSystemDir.devicePath, mDataDir.devicePath }); 776 } 777 runTzDataCheckWithArgs(String[] args)778 private int runTzDataCheckWithArgs(String[] args) throws Exception { 779 String command = createTzDataCheckCommand(mDeviceAndroidRootDir, args); 780 return executeCommandOnDeviceWithResultCode(command).statusCode; 781 } 782 createTzDataCheckCommand(String rootDir, String[] args)783 private static String createTzDataCheckCommand(String rootDir, String[] args) { 784 StringJoiner joiner = new StringJoiner(" "); 785 String tzDataCheckCommand = rootDir + "/bin/tzdatacheck"; 786 joiner.add(tzDataCheckCommand); 787 for (String arg : args) { 788 joiner.add(arg); 789 } 790 return joiner.toString(); 791 } 792 assertHostFileExists(Path path)793 private static void assertHostFileExists(Path path) { 794 assertTrue(Files.exists(path)); 795 } 796 executeCommandOnDeviceRaw(String command)797 private String executeCommandOnDeviceRaw(String command) throws DeviceNotAvailableException { 798 return getDevice().executeShellCommand(command); 799 } 800 createDeviceDirectory(PathPair dir)801 private void createDeviceDirectory(PathPair dir) throws DeviceNotAvailableException { 802 executeCommandOnDeviceRaw("mkdir -p " + dir.devicePath); 803 } 804 createHostDirectory(PathPair dir)805 private static void createHostDirectory(PathPair dir) throws Exception { 806 Files.createDirectory(dir.hostPath); 807 } 808 809 private static class ShellResult { 810 final String output; 811 final int statusCode; 812 ShellResult(String output, int statusCode)813 private ShellResult(String output, int statusCode) { 814 this.output = output; 815 this.statusCode = statusCode; 816 } 817 } 818 executeCommandOnDeviceWithResultCode(String command)819 private ShellResult executeCommandOnDeviceWithResultCode(String command) throws Exception { 820 // A file to hold the script we're going to create. 821 PathPair scriptFile = mTestRootDir.createSubPath("script.sh"); 822 // A file to hold the output of the script. 823 PathPair scriptOut = mTestRootDir.createSubPath("script.out"); 824 825 // The content of the script. Runs the command, capturing stdout and stderr to scriptOut 826 // and printing the result code. 827 String hostScriptContent = command + " > " + scriptOut.devicePath + " 2>&1 ; echo -n $?"; 828 829 // Parse and return the result. 830 try { 831 Files.write(scriptFile.hostPath, hostScriptContent.getBytes(StandardCharsets.US_ASCII)); 832 833 // Push the script to the device. 834 pushFile(scriptFile); 835 836 // Execute the script using "sh". 837 String execCommandUnderShell = 838 mDeviceAndroidRootDir + "/bin/sh " + scriptFile.devicePath; 839 String resultCodeString = executeCommandOnDeviceRaw(execCommandUnderShell); 840 841 // Pull back scriptOut to the host and read the content. 842 pullFile(scriptOut); 843 byte[] outputBytes = Files.readAllBytes(scriptOut.hostPath); 844 String output = new String(outputBytes, StandardCharsets.US_ASCII); 845 846 int resultCode; 847 try { 848 resultCode = Integer.parseInt(resultCodeString); 849 } catch (NumberFormatException e) { 850 fail("Command: " + command 851 + " returned a non-integer: \"" + resultCodeString + "\"" 852 + ", output=\"" + output + "\""); 853 return null; 854 } 855 return new ShellResult(output, resultCode); 856 } finally { 857 deleteDeviceFile(scriptFile, false /* failOnError */); 858 deleteDeviceFile(scriptOut, false /* failOnError */); 859 deleteHostFile(scriptFile, false /* failOnError */); 860 deleteHostFile(scriptOut, false /* failOnError */); 861 } 862 } 863 pushHostTestDirToDevice()864 private void pushHostTestDirToDevice() throws Exception { 865 assertTrue(getDevice().pushDir(mTestRootDir.hostFile(), mTestRootDir.devicePath)); 866 } 867 pullFile(PathPair file)868 private void pullFile(PathPair file) throws DeviceNotAvailableException { 869 assertTrue("Could not pull file " + file.devicePath + " to " + file.hostFile(), 870 getDevice().pullFile(file.devicePath, file.hostFile())); 871 } 872 pushFile(PathPair file)873 private void pushFile(PathPair file) throws DeviceNotAvailableException { 874 assertTrue("Could not push file " + file.hostFile() + " to " + file.devicePath, 875 getDevice().pushFile(file.hostFile(), file.devicePath)); 876 } 877 deleteHostFile(PathPair file, boolean failOnError)878 private void deleteHostFile(PathPair file, boolean failOnError) { 879 try { 880 Files.deleteIfExists(file.hostPath); 881 } catch (IOException e) { 882 if (failOnError) { 883 fail(e); 884 } 885 } 886 } 887 deleteDeviceDirectory(PathPair dir, boolean failOnError)888 private void deleteDeviceDirectory(PathPair dir, boolean failOnError) 889 throws DeviceNotAvailableException { 890 String deviceDir = dir.devicePath; 891 try { 892 executeCommandOnDeviceRaw("rm -r " + deviceDir); 893 } catch (Exception e) { 894 if (failOnError) { 895 throw deviceFail(e); 896 } 897 } 898 } 899 deleteDeviceFile(PathPair file, boolean failOnError)900 private void deleteDeviceFile(PathPair file, boolean failOnError) 901 throws DeviceNotAvailableException { 902 try { 903 assertDevicePathIsFile(file); 904 executeCommandOnDeviceRaw("rm " + file.devicePath); 905 } catch (Exception e) { 906 if (failOnError) { 907 throw deviceFail(e); 908 } 909 } 910 } 911 deleteHostDirectory(PathPair dir, final boolean failOnError)912 private static void deleteHostDirectory(PathPair dir, final boolean failOnError) { 913 Path hostPath = dir.hostPath; 914 if (Files.exists(hostPath)) { 915 Consumer<Path> pathConsumer = file -> { 916 try { 917 Files.delete(file); 918 } catch (Exception e) { 919 if (failOnError) { 920 fail(e); 921 } 922 } 923 }; 924 925 try { 926 Files.walk(hostPath).sorted(Comparator.reverseOrder()).forEach(pathConsumer); 927 } catch (IOException e) { 928 fail(e); 929 } 930 } 931 } 932 assertDeviceFileExists(String s)933 private void assertDeviceFileExists(String s) throws DeviceNotAvailableException { 934 assertTrue(getDevice().doesFileExist(s)); 935 } 936 assertDevicePathExists(PathPair path)937 private void assertDevicePathExists(PathPair path) throws DeviceNotAvailableException { 938 assertDeviceFileExists(path.devicePath); 939 } 940 assertDeviceDirContainsDistro(PathPair distroPath, byte[] expectedDistroBytes)941 private void assertDeviceDirContainsDistro(PathPair distroPath, byte[] expectedDistroBytes) 942 throws Exception { 943 // Pull back just the version file and compare it. 944 File localFile = mTestRootDir.createSubPath("temp.file").hostFile(); 945 try { 946 String remoteVersionFile = distroPath.devicePath + "/" 947 + TimeZoneDistro.DISTRO_VERSION_FILE_NAME; 948 assertTrue("Could not pull file " + remoteVersionFile + " to " + localFile, 949 getDevice().pullFile(remoteVersionFile, localFile)); 950 951 byte[] bytes = Files.readAllBytes(localFile.toPath()); 952 assertArrayEquals(bytes, 953 new TimeZoneDistro(expectedDistroBytes).getDistroVersion().toBytes()); 954 } finally { 955 localFile.delete(); 956 } 957 } 958 assertDevicePathDoesNotExist(PathPair path)959 private void assertDevicePathDoesNotExist(PathPair path) throws DeviceNotAvailableException { 960 assertFalse(getDevice().doesFileExist(path.devicePath)); 961 } 962 assertDevicePathIsFile(PathPair path)963 private void assertDevicePathIsFile(PathPair path) throws DeviceNotAvailableException { 964 // This check cannot rely on getDevice().getFile(devicePath).isDirectory() here because that 965 // requires that the user has rights to list all files beneath each and every directory in 966 // the path. That is not the case for the shell user and the /data and /data/local 967 // directories. http://b/35753041. 968 String output = executeCommandOnDeviceRaw("stat -c %F " + path.devicePath); 969 assertTrue(path.devicePath + " not a file. Received: " + output, 970 output.startsWith("regular") && output.endsWith("file\n")); 971 } 972 deviceFail(Exception e)973 private static DeviceNotAvailableException deviceFail(Exception e) 974 throws DeviceNotAvailableException { 975 if (e instanceof DeviceNotAvailableException) { 976 throw (DeviceNotAvailableException) e; 977 } 978 fail(e); 979 return null; 980 } 981 fail(Exception e)982 private static void fail(Exception e) { 983 e.printStackTrace(); 984 fail(e.getMessage()); 985 } 986 987 /** A path that has equivalents on both host and device. */ 988 private static class PathPair { 989 private final Path hostPath; 990 private final String devicePath; 991 PathPair(Path hostPath, String devicePath)992 PathPair(Path hostPath, String devicePath) { 993 this.hostPath = hostPath; 994 this.devicePath = devicePath; 995 } 996 hostFile()997 File hostFile() { 998 return hostPath.toFile(); 999 } 1000 createSubPath(String s)1001 PathPair createSubPath(String s) { 1002 return new PathPair(hostPath.resolve(s), devicePath + "/" + s); 1003 } 1004 } 1005 } 1006