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