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