| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.timezone.distro.installer; |
| |
| import com.android.timezone.distro.DistroVersion; |
| import com.android.timezone.distro.FileUtils; |
| import com.android.timezone.distro.StagedDistroOperation; |
| import com.android.timezone.distro.TimeZoneDistro; |
| import com.android.timezone.distro.tools.TimeZoneDistroBuilder; |
| |
| import junit.framework.TestCase; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.FileVisitor; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.SimpleFileVisitor; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipOutputStream; |
| import libcore.io.IoUtils; |
| import libcore.tzdata.testing.ZoneInfoTestHelper; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| |
| /** |
| * Tests for {@link TimeZoneDistroInstaller}. |
| */ |
| public class TimeZoneDistroInstallerTest extends TestCase { |
| |
| // OLDER_RULES_VERSION < SYSTEM_RULES_VERSION < NEW_RULES_VERSION < NEWER_RULES_VERSION |
| private static final String OLDER_RULES_VERSION = "2030a"; |
| private static final String SYSTEM_RULES_VERSION = "2030b"; |
| private static final String NEW_RULES_VERSION = "2030c"; |
| private static final String NEWER_RULES_VERSION = "2030d"; |
| |
| private TimeZoneDistroInstaller installer; |
| private File tempDir; |
| private File testInstallDir; |
| private File testSystemTzDataDir; |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| tempDir = createUniqueDirectory(null, "tempDir"); |
| testInstallDir = createSubDirectory(tempDir, "testInstall"); |
| testSystemTzDataDir = createSubDirectory(tempDir, "testSystemTzData"); |
| |
| // Create a file to represent the tzdata file in the /system partition of the device. |
| File testSystemTzDataFile = new File(testSystemTzDataDir, "tzdata"); |
| byte[] systemTzDataBytes = createTzData(SYSTEM_RULES_VERSION); |
| createFile(testSystemTzDataFile, systemTzDataBytes); |
| |
| installer = new TimeZoneDistroInstaller( |
| "TimeZoneDistroInstallerTest", testSystemTzDataFile, testInstallDir); |
| } |
| |
| /** |
| * Creates a unique temporary directory. rootDir can be null, in which case the directory will |
| * be created beneath the directory pointed to by the java.io.tmpdir system property. |
| */ |
| private static File createUniqueDirectory(File rootDir, String prefix) throws Exception { |
| File dir = File.createTempFile(prefix, "", rootDir); |
| assertTrue(dir.delete()); |
| assertTrue(dir.mkdir()); |
| return dir; |
| } |
| |
| private static File createSubDirectory(File parent, String subDirName) { |
| File dir = new File(parent, subDirName); |
| assertTrue(dir.mkdir()); |
| return dir; |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| if (tempDir.exists()) { |
| FileUtils.deleteRecursive(tempDir); |
| } |
| super.tearDown(); |
| } |
| |
| /** Tests the an update on a device will fail if the /system tzdata file cannot be found. */ |
| public void testStageInstallWithErrorCode_badSystemFile() throws Exception { |
| File doesNotExist = new File(testSystemTzDataDir, "doesNotExist"); |
| TimeZoneDistroInstaller brokenSystemInstaller = new TimeZoneDistroInstaller( |
| "TimeZoneDistroInstallerTest", doesNotExist, testInstallDir); |
| byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| |
| try { |
| brokenSystemInstaller.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes)); |
| fail(); |
| } catch (IOException expected) {} |
| |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| } |
| |
| /** Tests the first successful update on a device */ |
| public void testStageInstallWithErrorCode_successfulFirstUpdate() throws Exception { |
| byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes))); |
| assertInstallDistroStaged(distroBytes); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests we can install an update the same version as is in /system. |
| */ |
| public void testStageInstallWithErrorCode_successfulFirstUpdate_sameVersionAsSystem() |
| throws Exception { |
| byte[] distroBytes = createValidTimeZoneDistroBytes(SYSTEM_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes))); |
| assertInstallDistroStaged(distroBytes); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests we cannot install an update older than the version in /system. |
| */ |
| public void testStageInstallWithErrorCode_unsuccessfulFirstUpdate_olderVersionThanSystem() |
| throws Exception { |
| byte[] distroBytes = createValidTimeZoneDistroBytes(OLDER_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes))); |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests an update on a device when there is a prior update already staged. |
| */ |
| public void testStageInstallWithErrorCode_successfulFollowOnUpdate_newerVersion() |
| throws Exception { |
| byte[] distro1Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distro1Bytes))); |
| assertInstallDistroStaged(distro1Bytes); |
| |
| byte[] distro2Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 2); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distro2Bytes))); |
| assertInstallDistroStaged(distro2Bytes); |
| |
| byte[] distro3Bytes = createValidTimeZoneDistroBytes(NEWER_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distro3Bytes))); |
| assertInstallDistroStaged(distro3Bytes); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests an update on a device when there is a prior update already applied, but the follow |
| * on update is older than in /system. |
| */ |
| public void testStageInstallWithErrorCode_unsuccessfulFollowOnUpdate_olderVersion() |
| throws Exception { |
| byte[] distro1Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 2); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distro1Bytes))); |
| assertInstallDistroStaged(distro1Bytes); |
| |
| byte[] distro2Bytes = createValidTimeZoneDistroBytes(OLDER_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distro2Bytes))); |
| assertInstallDistroStaged(distro1Bytes); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests staging an update when there's already an uninstall staged still results in a staged |
| * install. |
| */ |
| public void testStageInstallWithErrorCode_existingStagedUninstall() |
| throws Exception { |
| byte[] distro1Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| simulateInstalledDistro(distro1Bytes); |
| assertInstalledDistro(distro1Bytes); |
| |
| assertEquals(TimeZoneDistroInstaller.UNINSTALL_SUCCESS, installer.stageUninstall()); |
| assertDistroUninstallStaged(); |
| assertInstalledDistro(distro1Bytes); |
| |
| byte[] distro2Bytes = createValidTimeZoneDistroBytes(NEWER_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distro2Bytes))); |
| assertInstalledDistro(distro1Bytes); |
| assertInstallDistroStaged(distro2Bytes); |
| } |
| |
| /** Tests that a distro with a missing tzdata file will not update the content. */ |
| public void testStageInstallWithErrorCode_missingTzDataFile() throws Exception { |
| byte[] stagedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(stagedDistroBytes))); |
| assertInstallDistroStaged(stagedDistroBytes); |
| |
| byte[] incompleteDistroBytes = |
| createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1) |
| .clearTzDataForTests() |
| .buildUnvalidatedBytes(); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(incompleteDistroBytes))); |
| assertInstallDistroStaged(stagedDistroBytes); |
| assertNoInstalledDistro(); |
| } |
| |
| /** Tests that a distro with a missing ICU file will not update the content. */ |
| public void testStageInstallWithErrorCode_missingIcuFile() throws Exception { |
| byte[] stagedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(stagedDistroBytes))); |
| assertInstallDistroStaged(stagedDistroBytes); |
| |
| byte[] incompleteDistroBytes = |
| createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1) |
| .clearIcuDataForTests() |
| .buildUnvalidatedBytes(); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(incompleteDistroBytes))); |
| assertInstallDistroStaged(stagedDistroBytes); |
| assertNoInstalledDistro(); |
| } |
| |
| /** Tests that a distro with a missing tzlookup file will not update the content. */ |
| public void testStageInstallWithErrorCode_missingTzLookupFile() throws Exception { |
| byte[] stagedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(stagedDistroBytes))); |
| assertInstallDistroStaged(stagedDistroBytes); |
| |
| byte[] incompleteDistroBytes = |
| createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1) |
| .setTzLookupXml(null) |
| .buildUnvalidatedBytes(); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(incompleteDistroBytes))); |
| assertInstallDistroStaged(stagedDistroBytes); |
| assertNoInstalledDistro(); |
| } |
| |
| /** Tests that a distro with a bad tzlookup file will not update the content. */ |
| public void testStageInstallWithErrorCode_badTzLookupFile() throws Exception { |
| byte[] stagedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(stagedDistroBytes))); |
| assertInstallDistroStaged(stagedDistroBytes); |
| |
| byte[] incompleteDistroBytes = |
| createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1) |
| .setTzLookupXml("<foo />") |
| .buildUnvalidatedBytes(); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(incompleteDistroBytes))); |
| assertInstallDistroStaged(stagedDistroBytes); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests that an update will be unpacked even if there is a partial update from a previous run. |
| */ |
| public void testStageInstallWithErrorCode_withWorkingDir() throws Exception { |
| File workingDir = installer.getWorkingDir(); |
| assertTrue(workingDir.mkdir()); |
| createFile(new File(workingDir, "myFile"), new byte[] { 'a' }); |
| |
| byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes))); |
| assertInstallDistroStaged(distroBytes); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests that a distro without a distro version file will be rejected. |
| */ |
| public void testStageInstallWithErrorCode_withMissingDistroVersionFile() throws Exception { |
| // Create a distro without a version file. |
| byte[] distroBytes = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1) |
| .clearVersionForTests() |
| .buildUnvalidatedBytes(); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes))); |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests that a distro with an newer distro version will be rejected. |
| */ |
| public void testStageInstallWithErrorCode_withNewerDistroVersion() throws Exception { |
| // Create a distro that will appear to be newer than the one currently supported. |
| byte[] distroBytes = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1) |
| .replaceFormatVersionForTests( |
| DistroVersion.CURRENT_FORMAT_MAJOR_VERSION + 1, |
| DistroVersion.CURRENT_FORMAT_MINOR_VERSION) |
| .buildUnvalidatedBytes(); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes))); |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests that a distro with a badly formed distro version will be rejected. |
| */ |
| public void testStageInstallWithErrorCode_withBadlyFormedDistroVersion() throws Exception { |
| // Create a distro that has an invalid major distro version. It should be 3 numeric |
| // characters, "." and 3 more numeric characters. |
| byte[] invalidFormatVersionBytes = |
| createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1).buildUnvalidatedBytes(); |
| invalidFormatVersionBytes[0] = 'A'; |
| |
| TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidFormatVersionBytes); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.stageInstallWithErrorCode(distro)); |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests that a distro with a badly formed revision will be rejected. |
| */ |
| public void testStageInstallWithErrorCode_withBadlyFormedRevision() throws Exception { |
| // Create a distro that has an invalid revision. It should be 3 numeric characters. |
| byte[] invalidRevisionBytes = |
| createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1).buildUnvalidatedBytes(); |
| invalidRevisionBytes[invalidRevisionBytes.length - 3] = 'A'; |
| |
| TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRevisionBytes); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.stageInstallWithErrorCode(distro)); |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| } |
| |
| /** |
| * Tests that a distro with a badly formed rules version will be rejected. |
| */ |
| public void testStageInstallWithErrorCode_withBadlyFormedRulesVersion() throws Exception { |
| // Create a distro that has an invalid rules version. It should be in the form "2016c". |
| byte[] invalidRulesVersionBytes = |
| createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1).buildUnvalidatedBytes(); |
| invalidRulesVersionBytes[invalidRulesVersionBytes.length - 6] = 'B'; |
| |
| TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRulesVersionBytes); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.stageInstallWithErrorCode(distro)); |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| } |
| |
| /** Tests what happens if a stageUninstall() is attempted when there's nothing installed. */ |
| public void testStageUninstall_noExistingDistro() throws Exception { |
| // To stage an uninstall, there would need to be installed rules. |
| assertEquals( |
| TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED, |
| installer.stageUninstall()); |
| |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| } |
| |
| /** Tests what happens if a stageUninstall() is attempted when there's something installed. */ |
| public void testStageUninstall_existingInstalledDataDistro() throws Exception { |
| // To stage an uninstall, we need to have some installed rules. |
| byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| simulateInstalledDistro(installedDistroBytes); |
| |
| File stagedDataDir = installer.getStagedTzDataDir(); |
| assertTrue(stagedDataDir.mkdir()); |
| |
| assertEquals( |
| TimeZoneDistroInstaller.UNINSTALL_SUCCESS, |
| installer.stageUninstall()); |
| assertDistroUninstallStaged(); |
| assertInstalledDistro(installedDistroBytes); |
| } |
| |
| /** |
| * Tests what happens if a stageUninstall() is attempted when there's something installed |
| * and there's a staged install. |
| */ |
| public void testStageUninstall_existingStagedInstall() throws Exception { |
| File stagedDataDir = installer.getStagedTzDataDir(); |
| assertTrue(stagedDataDir.mkdir()); |
| |
| // Stage an install. |
| byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| assertEquals(TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes))); |
| |
| // Now uninstall. It should just remove the staged install. |
| assertEquals( |
| TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED, |
| installer.stageUninstall()); |
| assertNoDistroOperationStaged(); |
| } |
| |
| /** |
| * Tests what happens if a stageUninstall() is attempted when there's something installed |
| * and there's a staged uninstall. |
| */ |
| public void testStageUninstall_existingStagedUninstall() throws Exception { |
| // To stage an uninstall, we need to have some installed rules. |
| byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| simulateInstalledDistro(installedDistroBytes); |
| |
| File stagedDataDir = installer.getStagedTzDataDir(); |
| assertTrue(stagedDataDir.mkdir()); |
| |
| assertEquals( |
| TimeZoneDistroInstaller.UNINSTALL_SUCCESS, |
| installer.stageUninstall()); |
| assertDistroUninstallStaged(); |
| assertInstalledDistro(installedDistroBytes); |
| |
| // Now stage a second uninstall. |
| assertEquals( |
| TimeZoneDistroInstaller.UNINSTALL_SUCCESS, |
| installer.stageUninstall()); |
| assertDistroUninstallStaged(); |
| assertInstalledDistro(installedDistroBytes); |
| } |
| |
| /** |
| * Tests what happens if a stageUninstall() is attempted when there are unexpected working |
| * directories present. |
| */ |
| public void testStageUninstall_oldDirsAlreadyExists() throws Exception { |
| // To stage an uninstall, we need to have some installed rules. |
| byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| simulateInstalledDistro(installedDistroBytes); |
| |
| File oldStagedDataDir = installer.getOldStagedDataDir(); |
| assertTrue(oldStagedDataDir.mkdir()); |
| |
| File workingDir = installer.getWorkingDir(); |
| assertTrue(workingDir.mkdir()); |
| |
| assertEquals( |
| TimeZoneDistroInstaller.UNINSTALL_SUCCESS, |
| installer.stageUninstall()); |
| |
| assertDistroUninstallStaged(); |
| assertFalse(workingDir.exists()); |
| assertFalse(oldStagedDataDir.exists()); |
| assertInstalledDistro(installedDistroBytes); |
| } |
| |
| public void testGetSystemRulesVersion() throws Exception { |
| assertEquals(SYSTEM_RULES_VERSION, installer.getSystemRulesVersion()); |
| } |
| |
| public void testGetInstalledDistroVersion() throws Exception { |
| // Check result when nothing installed. |
| assertNull(installer.getInstalledDistroVersion()); |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| |
| // Now simulate there being an existing install active. |
| byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| simulateInstalledDistro(distroBytes); |
| assertInstalledDistro(distroBytes); |
| |
| // Check result when something installed. |
| assertEquals(new TimeZoneDistro(distroBytes).getDistroVersion(), |
| installer.getInstalledDistroVersion()); |
| assertNoDistroOperationStaged(); |
| assertInstalledDistro(distroBytes); |
| } |
| |
| public void testGetStagedDistroOperation() throws Exception { |
| byte[] distro1Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| byte[] distro2Bytes = createValidTimeZoneDistroBytes(NEWER_RULES_VERSION, 1); |
| |
| // Check result when nothing staged. |
| assertNull(installer.getStagedDistroOperation()); |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| |
| // Check result after unsuccessfully staging an uninstall. |
| // Can't stage an uninstall without an installed distro. |
| assertEquals( |
| TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED, |
| installer.stageUninstall()); |
| assertNull(installer.getStagedDistroOperation()); |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| |
| // Check result after staging an install. |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distro1Bytes))); |
| StagedDistroOperation expectedStagedInstall = |
| StagedDistroOperation.install(new TimeZoneDistro(distro1Bytes).getDistroVersion()); |
| assertEquals(expectedStagedInstall, installer.getStagedDistroOperation()); |
| assertInstallDistroStaged(distro1Bytes); |
| assertNoInstalledDistro(); |
| |
| // Check result after unsuccessfully staging an uninstall (but after removing a staged |
| // install). Can't stage an uninstall without an installed distro. |
| assertEquals( |
| TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED, |
| installer.stageUninstall()); |
| assertNull(installer.getStagedDistroOperation()); |
| assertNoDistroOperationStaged(); |
| assertNoInstalledDistro(); |
| |
| // Now simulate there being an existing install active. |
| simulateInstalledDistro(distro1Bytes); |
| assertInstalledDistro(distro1Bytes); |
| |
| // Check state after successfully staging an uninstall. |
| assertEquals( |
| TimeZoneDistroInstaller.UNINSTALL_SUCCESS, |
| installer.stageUninstall()); |
| StagedDistroOperation expectedStagedUninstall = StagedDistroOperation.uninstall(); |
| assertEquals(expectedStagedUninstall, installer.getStagedDistroOperation()); |
| assertDistroUninstallStaged(); |
| assertInstalledDistro(distro1Bytes); |
| |
| // Check state after successfully staging an install. |
| assertEquals(TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.stageInstallWithErrorCode(new TimeZoneDistro(distro2Bytes))); |
| StagedDistroOperation expectedStagedInstall2 = |
| StagedDistroOperation.install(new TimeZoneDistro(distro2Bytes).getDistroVersion()); |
| assertEquals(expectedStagedInstall2, installer.getStagedDistroOperation()); |
| assertInstallDistroStaged(distro2Bytes); |
| assertInstalledDistro(distro1Bytes); |
| } |
| |
| private static byte[] createValidTimeZoneDistroBytes( |
| String rulesVersion, int revision) throws Exception { |
| return createValidTimeZoneDistroBuilder(rulesVersion, revision).buildBytes(); |
| } |
| |
| private static TimeZoneDistroBuilder createValidTimeZoneDistroBuilder( |
| String rulesVersion, int revision) throws Exception { |
| |
| byte[] tzData = createTzData(rulesVersion); |
| byte[] icuData = new byte[] { 'a' }; |
| String tzlookupXml = "<timezones>\n" |
| + " <countryzones>\n" |
| + " <country code=\"us\">\n" |
| + " <id>America/New_York\"</id>\n" |
| + " <id>America/Los_Angeles</id>\n" |
| + " </country>\n" |
| + " <country code=\"gb\">\n" |
| + " <id>Europe/London</id>\n" |
| + " </country>\n" |
| + " </countryzones>\n" |
| + "</timezones>\n"; |
| DistroVersion distroVersion = new DistroVersion( |
| DistroVersion.CURRENT_FORMAT_MAJOR_VERSION, |
| DistroVersion.CURRENT_FORMAT_MINOR_VERSION, |
| rulesVersion, |
| revision); |
| return new TimeZoneDistroBuilder() |
| .setDistroVersion(distroVersion) |
| .setTzDataFile(tzData) |
| .setIcuDataFile(icuData) |
| .setTzLookupXml(tzlookupXml); |
| } |
| |
| private void assertInstallDistroStaged(byte[] expectedDistroBytes) throws Exception { |
| assertTrue(testInstallDir.exists()); |
| |
| File stagedTzDataDir = installer.getStagedTzDataDir(); |
| assertTrue(stagedTzDataDir.exists()); |
| |
| File distroVersionFile = |
| new File(stagedTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME); |
| assertTrue(distroVersionFile.exists()); |
| |
| File tzdataFile = new File(stagedTzDataDir, TimeZoneDistro.TZDATA_FILE_NAME); |
| assertTrue(tzdataFile.exists()); |
| |
| File icuFile = new File(stagedTzDataDir, TimeZoneDistro.ICU_DATA_FILE_NAME); |
| assertTrue(icuFile.exists()); |
| |
| File tzLookupFile = new File(stagedTzDataDir, TimeZoneDistro.TZLOOKUP_FILE_NAME); |
| assertTrue(tzLookupFile.exists()); |
| |
| // Assert getStagedDistroState() is reporting correctly. |
| StagedDistroOperation stagedDistroOperation = installer.getStagedDistroOperation(); |
| assertNotNull(stagedDistroOperation); |
| assertFalse(stagedDistroOperation.isUninstall); |
| assertEquals(new TimeZoneDistro(expectedDistroBytes).getDistroVersion(), |
| stagedDistroOperation.distroVersion); |
| |
| File expectedZipContentDir = createUniqueDirectory(tempDir, "expectedZipContent"); |
| new TimeZoneDistro(expectedDistroBytes).extractTo(expectedZipContentDir); |
| |
| assertContentsMatches( |
| new File(expectedZipContentDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME), |
| distroVersionFile); |
| assertContentsMatches( |
| new File(expectedZipContentDir, TimeZoneDistro.ICU_DATA_FILE_NAME), |
| icuFile); |
| assertContentsMatches( |
| new File(expectedZipContentDir, TimeZoneDistro.TZDATA_FILE_NAME), |
| tzdataFile); |
| assertContentsMatches( |
| new File(expectedZipContentDir, TimeZoneDistro.TZLOOKUP_FILE_NAME), |
| tzLookupFile); |
| assertFileCount(4, expectedZipContentDir); |
| |
| // Also check no working directory is left lying around. |
| File workingDir = installer.getWorkingDir(); |
| assertFalse(workingDir.exists()); |
| } |
| |
| private static void assertFileCount(int expectedFiles, File rootDir) throws Exception { |
| final List<Path> paths = new ArrayList<>(); |
| FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() { |
| @Override |
| public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) |
| throws IOException { |
| paths.add(filePath); |
| return FileVisitResult.CONTINUE; |
| } |
| }; |
| Files.walkFileTree(rootDir.toPath(), visitor); |
| assertEquals("Found: " + paths, expectedFiles, paths.size()); |
| } |
| |
| private void assertContentsMatches(File expected, File actual) throws IOException { |
| byte[] actualBytes = IoUtils.readFileAsByteArray(actual.getPath()); |
| byte[] expectedBytes = IoUtils.readFileAsByteArray(expected.getPath()); |
| assertArrayEquals(expectedBytes, actualBytes); |
| } |
| |
| private void assertNoDistroOperationStaged() throws Exception { |
| assertNull(installer.getStagedDistroOperation()); |
| |
| File stagedTzDataDir = installer.getStagedTzDataDir(); |
| assertFalse(stagedTzDataDir.exists()); |
| |
| // Also check no working directories are left lying around. |
| File workingDir = installer.getWorkingDir(); |
| assertFalse(workingDir.exists()); |
| |
| File oldDataDir = installer.getOldStagedDataDir(); |
| assertFalse(oldDataDir.exists()); |
| } |
| |
| private void assertDistroUninstallStaged() throws Exception { |
| assertEquals(StagedDistroOperation.uninstall(), installer.getStagedDistroOperation()); |
| |
| File stagedTzDataDir = installer.getStagedTzDataDir(); |
| assertTrue(stagedTzDataDir.exists()); |
| assertTrue(stagedTzDataDir.isDirectory()); |
| |
| File uninstallTombstone = |
| new File(stagedTzDataDir, TimeZoneDistroInstaller.UNINSTALL_TOMBSTONE_FILE_NAME); |
| assertTrue(uninstallTombstone.exists()); |
| assertTrue(uninstallTombstone.isFile()); |
| |
| // Also check no working directories are left lying around. |
| File workingDir = installer.getWorkingDir(); |
| assertFalse(workingDir.exists()); |
| |
| File oldDataDir = installer.getOldStagedDataDir(); |
| assertFalse(oldDataDir.exists()); |
| } |
| |
| private void simulateInstalledDistro(byte[] distroBytes) throws Exception { |
| File currentTzDataDir = installer.getCurrentTzDataDir(); |
| assertFalse(currentTzDataDir.exists()); |
| assertTrue(currentTzDataDir.mkdir()); |
| new TimeZoneDistro(distroBytes).extractTo(currentTzDataDir); |
| } |
| |
| private void assertNoInstalledDistro() { |
| assertFalse(installer.getCurrentTzDataDir().exists()); |
| } |
| |
| private void assertInstalledDistro(byte[] distroBytes) throws Exception { |
| File currentTzDataDir = installer.getCurrentTzDataDir(); |
| assertTrue(currentTzDataDir.exists()); |
| File versionFile = new File(currentTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME); |
| assertTrue(versionFile.exists()); |
| byte[] expectedVersionBytes = new TimeZoneDistro(distroBytes).getDistroVersion().toBytes(); |
| byte[] actualVersionBytes = FileUtils.readBytes(versionFile, expectedVersionBytes.length); |
| assertArrayEquals(expectedVersionBytes, actualVersionBytes); |
| } |
| |
| private static byte[] createTzData(String rulesVersion) { |
| return new ZoneInfoTestHelper.TzDataBuilder() |
| .initializeToValid() |
| .setHeaderMagic("tzdata" + rulesVersion) |
| .build(); |
| } |
| |
| private static void createFile(File file, byte[] bytes) { |
| try (FileOutputStream fos = new FileOutputStream(file)) { |
| fos.write(bytes); |
| } catch (IOException e) { |
| fail(e.getMessage()); |
| } |
| } |
| |
| /** |
| * Creates a TimeZoneDistro containing arbitrary bytes in the version file. Used for testing |
| * distros with badly formed version info. |
| */ |
| private TimeZoneDistro createTimeZoneDistroWithVersionBytes(byte[] versionBytes) |
| throws Exception { |
| |
| // Extract a valid distro to a working dir. |
| byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); |
| File workingDir = createUniqueDirectory(tempDir, "versionBytes"); |
| new TimeZoneDistro(distroBytes).extractTo(workingDir); |
| |
| // Modify the version file. |
| File versionFile = new File(workingDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME); |
| assertTrue(versionFile.exists()); |
| try (FileOutputStream fos = new FileOutputStream(versionFile, false /* append */)) { |
| fos.write(versionBytes); |
| } |
| |
| // Zip the distro back up again. |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| try (ZipOutputStream zos = new ZipOutputStream(baos)) { |
| Path workingDirPath = workingDir.toPath(); |
| Files.walkFileTree(workingDirPath, new SimpleFileVisitor<Path>() { |
| @Override |
| public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) |
| throws IOException { |
| byte[] bytes = IoUtils.readFileAsByteArray(file.toString()); |
| String relativeFileName = workingDirPath.relativize(file).toString(); |
| addZipEntry(zos, relativeFileName, bytes); |
| return FileVisitResult.CONTINUE; |
| } |
| }); |
| } |
| |
| return new TimeZoneDistro(baos.toByteArray()); |
| } |
| |
| private static void addZipEntry(ZipOutputStream zos, String name, byte[] content) |
| throws IOException { |
| ZipEntry zipEntry = new ZipEntry(name); |
| zipEntry.setSize(content.length); |
| zos.putNextEntry(zipEntry); |
| zos.write(content); |
| zos.closeEntry(); |
| } |
| } |