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