Home | History | Annotate | Download | only in tzdata
      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