Home | History | Annotate | Download | only in installer
      1 /*
      2  * Copyright (C) 2015 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 package com.android.timezone.distro.installer;
     17 
     18 import com.android.timezone.distro.DistroVersion;
     19 import com.android.timezone.distro.FileUtils;
     20 import com.android.timezone.distro.StagedDistroOperation;
     21 import com.android.timezone.distro.TimeZoneDistro;
     22 import com.android.timezone.distro.tools.TimeZoneDistroBuilder;
     23 
     24 import junit.framework.TestCase;
     25 
     26 import java.io.ByteArrayOutputStream;
     27 import java.io.File;
     28 import java.io.FileOutputStream;
     29 import java.io.IOException;
     30 import java.nio.file.FileVisitResult;
     31 import java.nio.file.FileVisitor;
     32 import java.nio.file.Files;
     33 import java.nio.file.Path;
     34 import java.nio.file.SimpleFileVisitor;
     35 import java.nio.file.attribute.BasicFileAttributes;
     36 import java.util.ArrayList;
     37 import java.util.List;
     38 import java.util.zip.ZipEntry;
     39 import java.util.zip.ZipOutputStream;
     40 import libcore.io.IoUtils;
     41 import libcore.tzdata.testing.ZoneInfoTestHelper;
     42 
     43 import static org.junit.Assert.assertArrayEquals;
     44 
     45 /**
     46  * Tests for {@link TimeZoneDistroInstaller}.
     47  */
     48 public class TimeZoneDistroInstallerTest extends TestCase {
     49 
     50     // OLDER_RULES_VERSION < SYSTEM_RULES_VERSION < NEW_RULES_VERSION < NEWER_RULES_VERSION
     51     private static final String OLDER_RULES_VERSION = "2030a";
     52     private static final String SYSTEM_RULES_VERSION = "2030b";
     53     private static final String NEW_RULES_VERSION = "2030c";
     54     private static final String NEWER_RULES_VERSION = "2030d";
     55 
     56     private TimeZoneDistroInstaller installer;
     57     private File tempDir;
     58     private File testInstallDir;
     59     private File testSystemTzDataDir;
     60 
     61     @Override
     62     public void setUp() throws Exception {
     63         super.setUp();
     64         tempDir = createUniqueDirectory(null, "tempDir");
     65         testInstallDir = createSubDirectory(tempDir, "testInstall");
     66         testSystemTzDataDir =  createSubDirectory(tempDir, "testSystemTzData");
     67 
     68         // Create a file to represent the tzdata file in the /system partition of the device.
     69         File testSystemTzDataFile = new File(testSystemTzDataDir, "tzdata");
     70         byte[] systemTzDataBytes = createTzData(SYSTEM_RULES_VERSION);
     71         createFile(testSystemTzDataFile, systemTzDataBytes);
     72 
     73         installer = new TimeZoneDistroInstaller(
     74                 "TimeZoneDistroInstallerTest", testSystemTzDataFile, testInstallDir);
     75     }
     76 
     77     /**
     78      * Creates a unique temporary directory. rootDir can be null, in which case the directory will
     79      * be created beneath the directory pointed to by the java.io.tmpdir system property.
     80      */
     81     private static File createUniqueDirectory(File rootDir, String prefix) throws Exception {
     82         File dir = File.createTempFile(prefix, "", rootDir);
     83         assertTrue(dir.delete());
     84         assertTrue(dir.mkdir());
     85         return dir;
     86     }
     87 
     88     private static File createSubDirectory(File parent, String subDirName) {
     89         File dir = new File(parent, subDirName);
     90         assertTrue(dir.mkdir());
     91         return dir;
     92     }
     93 
     94     @Override
     95     public void tearDown() throws Exception {
     96         if (tempDir.exists()) {
     97             FileUtils.deleteRecursive(tempDir);
     98         }
     99         super.tearDown();
    100     }
    101 
    102     /** Tests the an update on a device will fail if the /system tzdata file cannot be found. */
    103     public void testStageInstallWithErrorCode_badSystemFile() throws Exception {
    104         File doesNotExist = new File(testSystemTzDataDir, "doesNotExist");
    105         TimeZoneDistroInstaller brokenSystemInstaller = new TimeZoneDistroInstaller(
    106                 "TimeZoneDistroInstallerTest", doesNotExist, testInstallDir);
    107         byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    108 
    109         try {
    110             brokenSystemInstaller.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes));
    111             fail();
    112         } catch (IOException expected) {}
    113 
    114         assertNoDistroOperationStaged();
    115         assertNoInstalledDistro();
    116     }
    117 
    118     /** Tests the first successful update on a device */
    119     public void testStageInstallWithErrorCode_successfulFirstUpdate() throws Exception {
    120         byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    121 
    122         assertEquals(
    123                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    124                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes)));
    125         assertInstallDistroStaged(distroBytes);
    126         assertNoInstalledDistro();
    127     }
    128 
    129     /**
    130      * Tests we can install an update the same version as is in /system.
    131      */
    132     public void testStageInstallWithErrorCode_successfulFirstUpdate_sameVersionAsSystem()
    133             throws Exception {
    134         byte[] distroBytes = createValidTimeZoneDistroBytes(SYSTEM_RULES_VERSION, 1);
    135         assertEquals(
    136                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    137                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes)));
    138         assertInstallDistroStaged(distroBytes);
    139         assertNoInstalledDistro();
    140     }
    141 
    142     /**
    143      * Tests we cannot install an update older than the version in /system.
    144      */
    145     public void testStageInstallWithErrorCode_unsuccessfulFirstUpdate_olderVersionThanSystem()
    146             throws Exception {
    147         byte[] distroBytes = createValidTimeZoneDistroBytes(OLDER_RULES_VERSION, 1);
    148         assertEquals(
    149                 TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD,
    150                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes)));
    151         assertNoDistroOperationStaged();
    152         assertNoInstalledDistro();
    153     }
    154 
    155     /**
    156      * Tests an update on a device when there is a prior update already staged.
    157      */
    158     public void testStageInstallWithErrorCode_successfulFollowOnUpdate_newerVersion()
    159             throws Exception {
    160         byte[] distro1Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    161         assertEquals(
    162                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    163                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distro1Bytes)));
    164         assertInstallDistroStaged(distro1Bytes);
    165 
    166         byte[] distro2Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 2);
    167         assertEquals(
    168                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    169                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distro2Bytes)));
    170         assertInstallDistroStaged(distro2Bytes);
    171 
    172         byte[] distro3Bytes = createValidTimeZoneDistroBytes(NEWER_RULES_VERSION, 1);
    173         assertEquals(
    174                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    175                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distro3Bytes)));
    176         assertInstallDistroStaged(distro3Bytes);
    177         assertNoInstalledDistro();
    178     }
    179 
    180     /**
    181      * Tests an update on a device when there is a prior update already applied, but the follow
    182      * on update is older than in /system.
    183      */
    184     public void testStageInstallWithErrorCode_unsuccessfulFollowOnUpdate_olderVersion()
    185             throws Exception {
    186         byte[] distro1Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 2);
    187         assertEquals(
    188                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    189                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distro1Bytes)));
    190         assertInstallDistroStaged(distro1Bytes);
    191 
    192         byte[] distro2Bytes = createValidTimeZoneDistroBytes(OLDER_RULES_VERSION, 1);
    193         assertEquals(
    194                 TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD,
    195                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distro2Bytes)));
    196         assertInstallDistroStaged(distro1Bytes);
    197         assertNoInstalledDistro();
    198     }
    199 
    200     /**
    201      * Tests staging an update when there's already an uninstall staged still results in a staged
    202      * install.
    203      */
    204     public void testStageInstallWithErrorCode_existingStagedUninstall()
    205             throws Exception {
    206         byte[] distro1Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    207         simulateInstalledDistro(distro1Bytes);
    208         assertInstalledDistro(distro1Bytes);
    209 
    210         assertEquals(TimeZoneDistroInstaller.UNINSTALL_SUCCESS, installer.stageUninstall());
    211         assertDistroUninstallStaged();
    212         assertInstalledDistro(distro1Bytes);
    213 
    214         byte[] distro2Bytes = createValidTimeZoneDistroBytes(NEWER_RULES_VERSION, 1);
    215         assertEquals(
    216                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    217                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distro2Bytes)));
    218         assertInstalledDistro(distro1Bytes);
    219         assertInstallDistroStaged(distro2Bytes);
    220     }
    221 
    222     /** Tests that a distro with a missing tzdata file will not update the content. */
    223     public void testStageInstallWithErrorCode_missingTzDataFile() throws Exception {
    224         byte[] stagedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    225         assertEquals(
    226                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    227                 installer.stageInstallWithErrorCode(new TimeZoneDistro(stagedDistroBytes)));
    228         assertInstallDistroStaged(stagedDistroBytes);
    229 
    230         byte[] incompleteDistroBytes =
    231                 createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
    232                         .clearTzDataForTests()
    233                         .buildUnvalidatedBytes();
    234         assertEquals(
    235                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
    236                 installer.stageInstallWithErrorCode(new TimeZoneDistro(incompleteDistroBytes)));
    237         assertInstallDistroStaged(stagedDistroBytes);
    238         assertNoInstalledDistro();
    239     }
    240 
    241     /** Tests that a distro with a missing ICU file will not update the content. */
    242     public void testStageInstallWithErrorCode_missingIcuFile() throws Exception {
    243         byte[] stagedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    244         assertEquals(
    245                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    246                 installer.stageInstallWithErrorCode(new TimeZoneDistro(stagedDistroBytes)));
    247         assertInstallDistroStaged(stagedDistroBytes);
    248 
    249         byte[] incompleteDistroBytes =
    250                 createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
    251                         .clearIcuDataForTests()
    252                         .buildUnvalidatedBytes();
    253         assertEquals(
    254                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
    255                 installer.stageInstallWithErrorCode(new TimeZoneDistro(incompleteDistroBytes)));
    256         assertInstallDistroStaged(stagedDistroBytes);
    257         assertNoInstalledDistro();
    258     }
    259 
    260     /** Tests that a distro with a missing tzlookup file will not update the content. */
    261     public void testStageInstallWithErrorCode_missingTzLookupFile() throws Exception {
    262         byte[] stagedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    263         assertEquals(
    264                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    265                 installer.stageInstallWithErrorCode(new TimeZoneDistro(stagedDistroBytes)));
    266         assertInstallDistroStaged(stagedDistroBytes);
    267 
    268         byte[] incompleteDistroBytes =
    269                 createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
    270                         .setTzLookupXml(null)
    271                         .buildUnvalidatedBytes();
    272         assertEquals(
    273                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
    274                 installer.stageInstallWithErrorCode(new TimeZoneDistro(incompleteDistroBytes)));
    275         assertInstallDistroStaged(stagedDistroBytes);
    276         assertNoInstalledDistro();
    277     }
    278 
    279     /** Tests that a distro with a bad tzlookup file will not update the content. */
    280     public void testStageInstallWithErrorCode_badTzLookupFile() throws Exception {
    281         byte[] stagedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    282         assertEquals(
    283                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    284                 installer.stageInstallWithErrorCode(new TimeZoneDistro(stagedDistroBytes)));
    285         assertInstallDistroStaged(stagedDistroBytes);
    286 
    287         byte[] incompleteDistroBytes =
    288                 createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
    289                         .setTzLookupXml("<foo />")
    290                         .buildUnvalidatedBytes();
    291         assertEquals(
    292                 TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR,
    293                 installer.stageInstallWithErrorCode(new TimeZoneDistro(incompleteDistroBytes)));
    294         assertInstallDistroStaged(stagedDistroBytes);
    295         assertNoInstalledDistro();
    296     }
    297 
    298     /**
    299      * Tests that an update will be unpacked even if there is a partial update from a previous run.
    300      */
    301     public void testStageInstallWithErrorCode_withWorkingDir() throws Exception {
    302         File workingDir = installer.getWorkingDir();
    303         assertTrue(workingDir.mkdir());
    304         createFile(new File(workingDir, "myFile"), new byte[] { 'a' });
    305 
    306         byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    307         assertEquals(
    308                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    309                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes)));
    310         assertInstallDistroStaged(distroBytes);
    311         assertNoInstalledDistro();
    312     }
    313 
    314     /**
    315      * Tests that a distro without a distro version file will be rejected.
    316      */
    317     public void testStageInstallWithErrorCode_withMissingDistroVersionFile() throws Exception {
    318         // Create a distro without a version file.
    319         byte[] distroBytes = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1)
    320                 .clearVersionForTests()
    321                 .buildUnvalidatedBytes();
    322         assertEquals(
    323                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
    324                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes)));
    325         assertNoDistroOperationStaged();
    326         assertNoInstalledDistro();
    327     }
    328 
    329     /**
    330      * Tests that a distro with an newer distro version will be rejected.
    331      */
    332     public void testStageInstallWithErrorCode_withNewerDistroVersion() throws Exception {
    333         // Create a distro that will appear to be newer than the one currently supported.
    334         byte[] distroBytes = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1)
    335                 .replaceFormatVersionForTests(
    336                         DistroVersion.CURRENT_FORMAT_MAJOR_VERSION + 1,
    337                         DistroVersion.CURRENT_FORMAT_MINOR_VERSION)
    338                 .buildUnvalidatedBytes();
    339         assertEquals(
    340                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION,
    341                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes)));
    342         assertNoDistroOperationStaged();
    343         assertNoInstalledDistro();
    344     }
    345 
    346     /**
    347      * Tests that a distro with a badly formed distro version will be rejected.
    348      */
    349     public void testStageInstallWithErrorCode_withBadlyFormedDistroVersion() throws Exception {
    350         // Create a distro that has an invalid major distro version. It should be 3 numeric
    351         // characters, "." and 3 more numeric characters.
    352         byte[] invalidFormatVersionBytes =
    353                 createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1).buildUnvalidatedBytes();
    354         invalidFormatVersionBytes[0] = 'A';
    355 
    356         TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidFormatVersionBytes);
    357         assertEquals(
    358                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
    359                 installer.stageInstallWithErrorCode(distro));
    360         assertNoDistroOperationStaged();
    361         assertNoInstalledDistro();
    362     }
    363 
    364     /**
    365      * Tests that a distro with a badly formed revision will be rejected.
    366      */
    367     public void testStageInstallWithErrorCode_withBadlyFormedRevision() throws Exception {
    368         // Create a distro that has an invalid revision. It should be 3 numeric characters.
    369         byte[] invalidRevisionBytes =
    370                 createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1).buildUnvalidatedBytes();
    371         invalidRevisionBytes[invalidRevisionBytes.length - 3] = 'A';
    372 
    373         TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRevisionBytes);
    374         assertEquals(
    375                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
    376                 installer.stageInstallWithErrorCode(distro));
    377         assertNoDistroOperationStaged();
    378         assertNoInstalledDistro();
    379     }
    380 
    381     /**
    382      * Tests that a distro with a badly formed rules version will be rejected.
    383      */
    384     public void testStageInstallWithErrorCode_withBadlyFormedRulesVersion() throws Exception {
    385         // Create a distro that has an invalid rules version. It should be in the form "2016c".
    386         byte[] invalidRulesVersionBytes =
    387                 createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1).buildUnvalidatedBytes();
    388         invalidRulesVersionBytes[invalidRulesVersionBytes.length - 6] = 'B';
    389 
    390         TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRulesVersionBytes);
    391         assertEquals(
    392                 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
    393                 installer.stageInstallWithErrorCode(distro));
    394         assertNoDistroOperationStaged();
    395         assertNoInstalledDistro();
    396     }
    397 
    398     /** Tests what happens if a stageUninstall() is attempted when there's nothing installed. */
    399     public void testStageUninstall_noExistingDistro() throws Exception {
    400         // To stage an uninstall, there would need to be installed rules.
    401         assertEquals(
    402                 TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED,
    403                 installer.stageUninstall());
    404 
    405         assertNoDistroOperationStaged();
    406         assertNoInstalledDistro();
    407     }
    408 
    409     /** Tests what happens if a stageUninstall() is attempted when there's something installed. */
    410     public void testStageUninstall_existingInstalledDataDistro() throws Exception {
    411         // To stage an uninstall, we need to have some installed rules.
    412         byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    413         simulateInstalledDistro(installedDistroBytes);
    414 
    415         File stagedDataDir = installer.getStagedTzDataDir();
    416         assertTrue(stagedDataDir.mkdir());
    417 
    418         assertEquals(
    419                 TimeZoneDistroInstaller.UNINSTALL_SUCCESS,
    420                 installer.stageUninstall());
    421         assertDistroUninstallStaged();
    422         assertInstalledDistro(installedDistroBytes);
    423     }
    424 
    425     /**
    426      * Tests what happens if a stageUninstall() is attempted when there's something installed
    427      * and there's a staged install.
    428      */
    429     public void testStageUninstall_existingStagedInstall() throws Exception {
    430         File stagedDataDir = installer.getStagedTzDataDir();
    431         assertTrue(stagedDataDir.mkdir());
    432 
    433         // Stage an install.
    434         byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    435         assertEquals(TimeZoneDistroInstaller.INSTALL_SUCCESS,
    436                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes)));
    437 
    438         // Now uninstall. It should just remove the staged install.
    439         assertEquals(
    440                 TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED,
    441                 installer.stageUninstall());
    442         assertNoDistroOperationStaged();
    443     }
    444 
    445     /**
    446      * Tests what happens if a stageUninstall() is attempted when there's something installed
    447      * and there's a staged uninstall.
    448      */
    449     public void testStageUninstall_existingStagedUninstall() throws Exception {
    450         // To stage an uninstall, we need to have some installed rules.
    451         byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    452         simulateInstalledDistro(installedDistroBytes);
    453 
    454         File stagedDataDir = installer.getStagedTzDataDir();
    455         assertTrue(stagedDataDir.mkdir());
    456 
    457         assertEquals(
    458                 TimeZoneDistroInstaller.UNINSTALL_SUCCESS,
    459                 installer.stageUninstall());
    460         assertDistroUninstallStaged();
    461         assertInstalledDistro(installedDistroBytes);
    462 
    463         // Now stage a second uninstall.
    464         assertEquals(
    465                 TimeZoneDistroInstaller.UNINSTALL_SUCCESS,
    466                 installer.stageUninstall());
    467         assertDistroUninstallStaged();
    468         assertInstalledDistro(installedDistroBytes);
    469     }
    470 
    471     /**
    472      * Tests what happens if a stageUninstall() is attempted when there are unexpected working
    473      * directories present.
    474      */
    475     public void testStageUninstall_oldDirsAlreadyExists() throws Exception {
    476         // To stage an uninstall, we need to have some installed rules.
    477         byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    478         simulateInstalledDistro(installedDistroBytes);
    479 
    480         File oldStagedDataDir = installer.getOldStagedDataDir();
    481         assertTrue(oldStagedDataDir.mkdir());
    482 
    483         File workingDir = installer.getWorkingDir();
    484         assertTrue(workingDir.mkdir());
    485 
    486         assertEquals(
    487                 TimeZoneDistroInstaller.UNINSTALL_SUCCESS,
    488                 installer.stageUninstall());
    489 
    490         assertDistroUninstallStaged();
    491         assertFalse(workingDir.exists());
    492         assertFalse(oldStagedDataDir.exists());
    493         assertInstalledDistro(installedDistroBytes);
    494     }
    495 
    496     public void testGetSystemRulesVersion() throws Exception {
    497         assertEquals(SYSTEM_RULES_VERSION, installer.getSystemRulesVersion());
    498     }
    499 
    500     public void testGetInstalledDistroVersion() throws Exception {
    501         // Check result when nothing installed.
    502         assertNull(installer.getInstalledDistroVersion());
    503         assertNoDistroOperationStaged();
    504         assertNoInstalledDistro();
    505 
    506         // Now simulate there being an existing install active.
    507         byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    508         simulateInstalledDistro(distroBytes);
    509         assertInstalledDistro(distroBytes);
    510 
    511         // Check result when something installed.
    512         assertEquals(new TimeZoneDistro(distroBytes).getDistroVersion(),
    513                 installer.getInstalledDistroVersion());
    514         assertNoDistroOperationStaged();
    515         assertInstalledDistro(distroBytes);
    516     }
    517 
    518     public void testGetStagedDistroOperation() throws Exception {
    519         byte[] distro1Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    520         byte[] distro2Bytes = createValidTimeZoneDistroBytes(NEWER_RULES_VERSION, 1);
    521 
    522         // Check result when nothing staged.
    523         assertNull(installer.getStagedDistroOperation());
    524         assertNoDistroOperationStaged();
    525         assertNoInstalledDistro();
    526 
    527         // Check result after unsuccessfully staging an uninstall.
    528         // Can't stage an uninstall without an installed distro.
    529         assertEquals(
    530                 TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED,
    531                 installer.stageUninstall());
    532         assertNull(installer.getStagedDistroOperation());
    533         assertNoDistroOperationStaged();
    534         assertNoInstalledDistro();
    535 
    536         // Check result after staging an install.
    537         assertEquals(
    538                 TimeZoneDistroInstaller.INSTALL_SUCCESS,
    539                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distro1Bytes)));
    540         StagedDistroOperation expectedStagedInstall =
    541                 StagedDistroOperation.install(new TimeZoneDistro(distro1Bytes).getDistroVersion());
    542         assertEquals(expectedStagedInstall, installer.getStagedDistroOperation());
    543         assertInstallDistroStaged(distro1Bytes);
    544         assertNoInstalledDistro();
    545 
    546         // Check result after unsuccessfully staging an uninstall (but after removing a staged
    547         // install). Can't stage an uninstall without an installed distro.
    548         assertEquals(
    549                 TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED,
    550                 installer.stageUninstall());
    551         assertNull(installer.getStagedDistroOperation());
    552         assertNoDistroOperationStaged();
    553         assertNoInstalledDistro();
    554 
    555         // Now simulate there being an existing install active.
    556         simulateInstalledDistro(distro1Bytes);
    557         assertInstalledDistro(distro1Bytes);
    558 
    559         // Check state after successfully staging an uninstall.
    560         assertEquals(
    561                 TimeZoneDistroInstaller.UNINSTALL_SUCCESS,
    562                 installer.stageUninstall());
    563         StagedDistroOperation expectedStagedUninstall = StagedDistroOperation.uninstall();
    564         assertEquals(expectedStagedUninstall, installer.getStagedDistroOperation());
    565         assertDistroUninstallStaged();
    566         assertInstalledDistro(distro1Bytes);
    567 
    568         // Check state after successfully staging an install.
    569         assertEquals(TimeZoneDistroInstaller.INSTALL_SUCCESS,
    570                 installer.stageInstallWithErrorCode(new TimeZoneDistro(distro2Bytes)));
    571         StagedDistroOperation expectedStagedInstall2 =
    572                 StagedDistroOperation.install(new TimeZoneDistro(distro2Bytes).getDistroVersion());
    573         assertEquals(expectedStagedInstall2, installer.getStagedDistroOperation());
    574         assertInstallDistroStaged(distro2Bytes);
    575         assertInstalledDistro(distro1Bytes);
    576     }
    577 
    578     private static byte[] createValidTimeZoneDistroBytes(
    579             String rulesVersion, int revision) throws Exception {
    580         return createValidTimeZoneDistroBuilder(rulesVersion, revision).buildBytes();
    581     }
    582 
    583     private static TimeZoneDistroBuilder createValidTimeZoneDistroBuilder(
    584             String rulesVersion, int revision) throws Exception {
    585 
    586         byte[] tzData = createTzData(rulesVersion);
    587         byte[] icuData = new byte[] { 'a' };
    588         String tzlookupXml = "<timezones ianaversion=\"" + rulesVersion + "\">\n"
    589                 + "  <countryzones>\n"
    590                 + "    <country code=\"us\" default=\"America/New_York\" everutc=\"n\">\n"
    591                 + "      <id>America/New_York</id>\n"
    592                 + "      <id>America/Los_Angeles</id>\n"
    593                 + "    </country>\n"
    594                 + "    <country code=\"gb\" default=\"Europe/London\" everutc=\"y\">\n"
    595                 + "      <id>Europe/London</id>\n"
    596                 + "    </country>\n"
    597                 + "  </countryzones>\n"
    598                 + "</timezones>\n";
    599         DistroVersion distroVersion = new DistroVersion(
    600                 DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
    601                 DistroVersion.CURRENT_FORMAT_MINOR_VERSION,
    602                 rulesVersion,
    603                 revision);
    604         return new TimeZoneDistroBuilder()
    605                 .setDistroVersion(distroVersion)
    606                 .setTzDataFile(tzData)
    607                 .setIcuDataFile(icuData)
    608                 .setTzLookupXml(tzlookupXml);
    609     }
    610 
    611     private void assertInstallDistroStaged(byte[] expectedDistroBytes) throws Exception {
    612         assertTrue(testInstallDir.exists());
    613 
    614         File stagedTzDataDir = installer.getStagedTzDataDir();
    615         assertTrue(stagedTzDataDir.exists());
    616 
    617         File distroVersionFile =
    618                 new File(stagedTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
    619         assertTrue(distroVersionFile.exists());
    620 
    621         File tzdataFile = new File(stagedTzDataDir, TimeZoneDistro.TZDATA_FILE_NAME);
    622         assertTrue(tzdataFile.exists());
    623 
    624         File icuFile = new File(stagedTzDataDir, TimeZoneDistro.ICU_DATA_FILE_NAME);
    625         assertTrue(icuFile.exists());
    626 
    627         File tzLookupFile = new File(stagedTzDataDir, TimeZoneDistro.TZLOOKUP_FILE_NAME);
    628         assertTrue(tzLookupFile.exists());
    629 
    630         // Assert getStagedDistroState() is reporting correctly.
    631         StagedDistroOperation stagedDistroOperation = installer.getStagedDistroOperation();
    632         assertNotNull(stagedDistroOperation);
    633         assertFalse(stagedDistroOperation.isUninstall);
    634         assertEquals(new TimeZoneDistro(expectedDistroBytes).getDistroVersion(),
    635                 stagedDistroOperation.distroVersion);
    636 
    637         File expectedZipContentDir = createUniqueDirectory(tempDir, "expectedZipContent");
    638         new TimeZoneDistro(expectedDistroBytes).extractTo(expectedZipContentDir);
    639 
    640         assertContentsMatches(
    641                 new File(expectedZipContentDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME),
    642                 distroVersionFile);
    643         assertContentsMatches(
    644                 new File(expectedZipContentDir, TimeZoneDistro.ICU_DATA_FILE_NAME),
    645                 icuFile);
    646         assertContentsMatches(
    647                 new File(expectedZipContentDir, TimeZoneDistro.TZDATA_FILE_NAME),
    648                 tzdataFile);
    649         assertContentsMatches(
    650                 new File(expectedZipContentDir, TimeZoneDistro.TZLOOKUP_FILE_NAME),
    651                 tzLookupFile);
    652         assertFileCount(4, expectedZipContentDir);
    653 
    654         // Also check no working directory is left lying around.
    655         File workingDir = installer.getWorkingDir();
    656         assertFalse(workingDir.exists());
    657     }
    658 
    659     private static void assertFileCount(int expectedFiles, File rootDir) throws Exception {
    660         final List<Path> paths = new ArrayList<>();
    661         FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
    662             @Override
    663             public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs)
    664                         throws IOException {
    665                 paths.add(filePath);
    666                 return FileVisitResult.CONTINUE;
    667             }
    668         };
    669         Files.walkFileTree(rootDir.toPath(), visitor);
    670         assertEquals("Found: " + paths, expectedFiles, paths.size());
    671     }
    672 
    673     private void assertContentsMatches(File expected, File actual) throws IOException {
    674         byte[] actualBytes = IoUtils.readFileAsByteArray(actual.getPath());
    675         byte[] expectedBytes = IoUtils.readFileAsByteArray(expected.getPath());
    676         assertArrayEquals(expectedBytes, actualBytes);
    677     }
    678 
    679     private void assertNoDistroOperationStaged() throws Exception {
    680         assertNull(installer.getStagedDistroOperation());
    681 
    682         File stagedTzDataDir = installer.getStagedTzDataDir();
    683         assertFalse(stagedTzDataDir.exists());
    684 
    685         // Also check no working directories are left lying around.
    686         File workingDir = installer.getWorkingDir();
    687         assertFalse(workingDir.exists());
    688 
    689         File oldDataDir = installer.getOldStagedDataDir();
    690         assertFalse(oldDataDir.exists());
    691     }
    692 
    693     private void assertDistroUninstallStaged() throws Exception {
    694         assertEquals(StagedDistroOperation.uninstall(), installer.getStagedDistroOperation());
    695 
    696         File stagedTzDataDir = installer.getStagedTzDataDir();
    697         assertTrue(stagedTzDataDir.exists());
    698         assertTrue(stagedTzDataDir.isDirectory());
    699 
    700         File uninstallTombstone =
    701                 new File(stagedTzDataDir, TimeZoneDistroInstaller.UNINSTALL_TOMBSTONE_FILE_NAME);
    702         assertTrue(uninstallTombstone.exists());
    703         assertTrue(uninstallTombstone.isFile());
    704 
    705         // Also check no working directories are left lying around.
    706         File workingDir = installer.getWorkingDir();
    707         assertFalse(workingDir.exists());
    708 
    709         File oldDataDir = installer.getOldStagedDataDir();
    710         assertFalse(oldDataDir.exists());
    711     }
    712 
    713     private void simulateInstalledDistro(byte[] distroBytes) throws Exception {
    714         File currentTzDataDir = installer.getCurrentTzDataDir();
    715         assertFalse(currentTzDataDir.exists());
    716         assertTrue(currentTzDataDir.mkdir());
    717         new TimeZoneDistro(distroBytes).extractTo(currentTzDataDir);
    718     }
    719 
    720     private void assertNoInstalledDistro() {
    721         assertFalse(installer.getCurrentTzDataDir().exists());
    722     }
    723 
    724     private void assertInstalledDistro(byte[] distroBytes) throws Exception {
    725         File currentTzDataDir = installer.getCurrentTzDataDir();
    726         assertTrue(currentTzDataDir.exists());
    727         File versionFile = new File(currentTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
    728         assertTrue(versionFile.exists());
    729         byte[] expectedVersionBytes = new TimeZoneDistro(distroBytes).getDistroVersion().toBytes();
    730         byte[] actualVersionBytes = FileUtils.readBytes(versionFile, expectedVersionBytes.length);
    731         assertArrayEquals(expectedVersionBytes, actualVersionBytes);
    732     }
    733 
    734     private static byte[] createTzData(String rulesVersion) {
    735         return new ZoneInfoTestHelper.TzDataBuilder()
    736                 .initializeToValid()
    737                 .setHeaderMagic("tzdata" + rulesVersion)
    738                 .build();
    739     }
    740 
    741     private static void createFile(File file, byte[] bytes) {
    742         try (FileOutputStream fos = new FileOutputStream(file)) {
    743             fos.write(bytes);
    744         } catch (IOException e) {
    745             fail(e.getMessage());
    746         }
    747     }
    748 
    749     /**
    750      * Creates a TimeZoneDistro containing arbitrary bytes in the version file. Used for testing
    751      * distros with badly formed version info.
    752      */
    753     private TimeZoneDistro createTimeZoneDistroWithVersionBytes(byte[] versionBytes)
    754             throws Exception {
    755 
    756         // Extract a valid distro to a working dir.
    757         byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1);
    758         File workingDir = createUniqueDirectory(tempDir, "versionBytes");
    759         new TimeZoneDistro(distroBytes).extractTo(workingDir);
    760 
    761         // Modify the version file.
    762         File versionFile = new File(workingDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
    763         assertTrue(versionFile.exists());
    764         try (FileOutputStream fos = new FileOutputStream(versionFile, false /* append */)) {
    765             fos.write(versionBytes);
    766         }
    767 
    768         // Zip the distro back up again.
    769         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    770         try (ZipOutputStream zos = new ZipOutputStream(baos)) {
    771             Path workingDirPath = workingDir.toPath();
    772             Files.walkFileTree(workingDirPath, new SimpleFileVisitor<Path>() {
    773                 @Override
    774                 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
    775                         throws IOException {
    776                     byte[] bytes = IoUtils.readFileAsByteArray(file.toString());
    777                     String relativeFileName = workingDirPath.relativize(file).toString();
    778                     addZipEntry(zos, relativeFileName, bytes);
    779                     return FileVisitResult.CONTINUE;
    780                 }
    781             });
    782         }
    783 
    784         return new TimeZoneDistro(baos.toByteArray());
    785     }
    786 
    787     private static void addZipEntry(ZipOutputStream zos, String name, byte[] content)
    788             throws IOException {
    789         ZipEntry zipEntry = new ZipEntry(name);
    790         zipEntry.setSize(content.length);
    791         zos.putNextEntry(zipEntry);
    792         zos.write(content);
    793         zos.closeEntry();
    794     }
    795 }
    796