| /* |
| * 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 libcore.tzdata.update2; |
| |
| import junit.framework.AssertionFailedError; |
| import junit.framework.TestCase; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.Arrays; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipInputStream; |
| import java.util.zip.ZipOutputStream; |
| import libcore.io.IoUtils; |
| import libcore.io.Streams; |
| import libcore.tzdata.shared2.DistroVersion; |
| import libcore.tzdata.shared2.FileUtils; |
| import libcore.tzdata.shared2.TimeZoneDistro; |
| import libcore.tzdata.testing.ZoneInfoTestHelper; |
| import libcore.tzdata.update2.tools.TimeZoneDistroBuilder; |
| |
| /** |
| * 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 = createDirectory("tempDir"); |
| testInstallDir = createDirectory("testInstall"); |
| testSystemTzDataDir = createDirectory("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); |
| } |
| |
| private static File createDirectory(String prefix) throws Exception { |
| File dir = File.createTempFile(prefix, ""); |
| assertTrue(dir.delete()); |
| assertTrue(dir.mkdir()); |
| return dir; |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| if (testSystemTzDataDir.exists()) { |
| FileUtils.deleteRecursive(testInstallDir); |
| } |
| if (testInstallDir.exists()) { |
| FileUtils.deleteRecursive(testInstallDir); |
| } |
| 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 testInstall_badSystemFile() throws Exception { |
| File doesNotExist = new File(testSystemTzDataDir, "doesNotExist"); |
| TimeZoneDistroInstaller brokenSystemInstaller = new TimeZoneDistroInstaller( |
| "TimeZoneDistroInstallerTest", doesNotExist, testInstallDir); |
| TimeZoneDistro tzData = createValidTimeZoneDistro(NEW_RULES_VERSION, 1); |
| |
| try { |
| brokenSystemInstaller.installWithErrorCode(tzData.getBytes()); |
| fail(); |
| } catch (IOException expected) {} |
| |
| assertNoContentInstalled(); |
| } |
| |
| /** Tests the first successful update on a device */ |
| public void testInstall_successfulFirstUpdate() throws Exception { |
| TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1); |
| |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.installWithErrorCode(distro.getBytes())); |
| assertDistroInstalled(distro); |
| } |
| |
| /** |
| * Tests we can install an update the same version as is in /system. |
| */ |
| public void testInstall_successfulFirstUpdate_sameVersionAsSystem() throws Exception { |
| TimeZoneDistro distro = createValidTimeZoneDistro(SYSTEM_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.installWithErrorCode(distro.getBytes())); |
| assertDistroInstalled(distro); |
| } |
| |
| /** |
| * Tests we cannot install an update older than the version in /system. |
| */ |
| public void testInstall_unsuccessfulFirstUpdate_olderVersionThanSystem() throws Exception { |
| TimeZoneDistro distro = createValidTimeZoneDistro(OLDER_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD, |
| installer.installWithErrorCode(distro.getBytes())); |
| assertNoContentInstalled(); |
| } |
| |
| /** |
| * Tests an update on a device when there is a prior update already applied. |
| */ |
| public void testInstall_successfulFollowOnUpdate_newerVersion() throws Exception { |
| TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.installWithErrorCode(distro1.getBytes())); |
| assertDistroInstalled(distro1); |
| |
| TimeZoneDistro distro2 = createValidTimeZoneDistro(NEW_RULES_VERSION, 2); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.installWithErrorCode(distro2.getBytes())); |
| assertDistroInstalled(distro2); |
| |
| TimeZoneDistro distro3 = createValidTimeZoneDistro(NEWER_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.installWithErrorCode(distro3.getBytes())); |
| assertDistroInstalled(distro3); |
| } |
| |
| /** |
| * 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 testInstall_unsuccessfulFollowOnUpdate_olderVersion() throws Exception { |
| TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 2); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.installWithErrorCode(distro1.getBytes())); |
| assertDistroInstalled(distro1); |
| |
| TimeZoneDistro distro2 = createValidTimeZoneDistro(OLDER_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD, |
| installer.installWithErrorCode(distro2.getBytes())); |
| assertDistroInstalled(distro1); |
| } |
| |
| /** Tests that a distro with a missing file will not update the content. */ |
| public void testInstall_missingTzDataFile() throws Exception { |
| TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.installWithErrorCode(installedDistro.getBytes())); |
| assertDistroInstalled(installedDistro); |
| |
| TimeZoneDistro incompleteDistro = |
| createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1) |
| .clearTzDataForTests() |
| .buildUnvalidated(); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.installWithErrorCode(incompleteDistro.getBytes())); |
| assertDistroInstalled(installedDistro); |
| } |
| |
| /** Tests that a distro with a missing file will not update the content. */ |
| public void testInstall_missingIcuFile() throws Exception { |
| TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.installWithErrorCode(installedDistro.getBytes())); |
| assertDistroInstalled(installedDistro); |
| |
| TimeZoneDistro incompleteDistro = |
| createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1) |
| .clearIcuDataForTests() |
| .buildUnvalidated(); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.installWithErrorCode(incompleteDistro.getBytes())); |
| assertDistroInstalled(installedDistro); |
| } |
| |
| /** |
| * Tests that an update will be unpacked even if there is a partial update from a previous run. |
| */ |
| public void testInstall_withWorkingDir() throws Exception { |
| File workingDir = installer.getWorkingDir(); |
| assertTrue(workingDir.mkdir()); |
| createFile(new File(workingDir, "myFile"), new byte[] { 'a' }); |
| |
| TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_SUCCESS, |
| installer.installWithErrorCode(distro.getBytes())); |
| assertDistroInstalled(distro); |
| } |
| |
| /** |
| * Tests that a distro without a distro version file will be rejected. |
| */ |
| public void testInstall_withMissingDistroVersionFile() throws Exception { |
| // Create a distro without a version file. |
| TimeZoneDistro distro = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1) |
| .clearVersionForTests() |
| .buildUnvalidated(); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.installWithErrorCode(distro.getBytes())); |
| assertNoContentInstalled(); |
| } |
| |
| /** |
| * Tests that a distro with an newer distro version will be rejected. |
| */ |
| public void testInstall_withNewerDistroVersion() throws Exception { |
| // Create a distro that will appear to be newer than the one currently supported. |
| TimeZoneDistro distro = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1) |
| .replaceFormatVersionForTests(2, 1) |
| .buildUnvalidated(); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION, |
| installer.installWithErrorCode(distro.getBytes())); |
| assertNoContentInstalled(); |
| } |
| |
| /** |
| * Tests that a distro with a badly formed distro version will be rejected. |
| */ |
| public void testInstall_withBadlyFormedDistroVersion() throws Exception { |
| // Create a distro that has an invalid major distro version. It should be 3 numeric |
| // characters, "." and 3 more numeric characters. |
| DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1); |
| byte[] invalidFormatVersionBytes = validDistroVersion.toBytes(); |
| invalidFormatVersionBytes[0] = 'A'; |
| |
| TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidFormatVersionBytes); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.installWithErrorCode(distro.getBytes())); |
| assertNoContentInstalled(); |
| } |
| |
| /** |
| * Tests that a distro with a badly formed revision will be rejected. |
| */ |
| public void testInstall_withBadlyFormedRevision() throws Exception { |
| // Create a distro that has an invalid revision. It should be 3 numeric characters. |
| DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1); |
| byte[] invalidRevisionBytes = validDistroVersion.toBytes(); |
| invalidRevisionBytes[invalidRevisionBytes.length - 3] = 'A'; |
| |
| TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRevisionBytes); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.installWithErrorCode(distro.getBytes())); |
| assertNoContentInstalled(); |
| } |
| |
| /** |
| * Tests that a distro with a badly formed rules version will be rejected. |
| */ |
| public void testInstall_withBadlyFormedRulesVersion() throws Exception { |
| // Create a distro that has an invalid rules version. It should be in the form "2016c". |
| DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1); |
| byte[] invalidRulesVersionBytes = validDistroVersion.toBytes(); |
| invalidRulesVersionBytes[invalidRulesVersionBytes.length - 6] = 'B'; |
| |
| TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRulesVersionBytes); |
| assertEquals( |
| TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, |
| installer.installWithErrorCode(distro.getBytes())); |
| assertNoContentInstalled(); |
| } |
| |
| public void testUninstall_noExistingDataDistro() throws Exception { |
| assertFalse(installer.uninstall()); |
| assertNoContentInstalled(); |
| } |
| |
| public void testUninstall_existingDataDistro() throws Exception { |
| File currentDataDir = installer.getCurrentTzDataDir(); |
| assertTrue(currentDataDir.mkdir()); |
| |
| assertTrue(installer.uninstall()); |
| assertNoContentInstalled(); |
| } |
| |
| public void testUninstall_oldDirsAlreadyExists() throws Exception { |
| File oldTzDataDir = installer.getOldTzDataDir(); |
| assertTrue(oldTzDataDir.mkdir()); |
| |
| File currentDataDir = installer.getCurrentTzDataDir(); |
| assertTrue(currentDataDir.mkdir()); |
| |
| assertTrue(installer.uninstall()); |
| assertNoContentInstalled(); |
| } |
| |
| public void testGetSystemRulesVersion() throws Exception { |
| assertEquals(SYSTEM_RULES_VERSION, installer.getSystemRulesVersion()); |
| } |
| |
| private static TimeZoneDistro createValidTimeZoneDistro( |
| String rulesVersion, int revision) throws Exception { |
| return createValidTimeZoneDistroBuilder(rulesVersion, revision).build(); |
| } |
| |
| private static TimeZoneDistroBuilder createValidTimeZoneDistroBuilder( |
| String rulesVersion, int revision) throws Exception { |
| |
| byte[] bionicTzData = createTzData(rulesVersion); |
| byte[] icuData = new byte[] { 'a' }; |
| DistroVersion distroVersion = new DistroVersion( |
| DistroVersion.CURRENT_FORMAT_MAJOR_VERSION, |
| DistroVersion.CURRENT_FORMAT_MINOR_VERSION, |
| rulesVersion, |
| revision); |
| return new TimeZoneDistroBuilder() |
| .setDistroVersion(distroVersion) |
| .setTzData(bionicTzData) |
| .setIcuData(icuData); |
| } |
| |
| private void assertDistroInstalled(TimeZoneDistro expectedDistro) throws Exception { |
| assertTrue(testInstallDir.exists()); |
| |
| File currentTzDataDir = installer.getCurrentTzDataDir(); |
| assertTrue(currentTzDataDir.exists()); |
| |
| File distroVersionFile = |
| new File(currentTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME); |
| assertTrue(distroVersionFile.exists()); |
| |
| File bionicFile = new File(currentTzDataDir, TimeZoneDistro.TZDATA_FILE_NAME); |
| assertTrue(bionicFile.exists()); |
| |
| File icuFile = new File(currentTzDataDir, TimeZoneDistro.ICU_DATA_FILE_NAME); |
| assertTrue(icuFile.exists()); |
| |
| // Assert getInstalledDistroVersion() is reporting correctly. |
| assertEquals(expectedDistro.getDistroVersion(), installer.getInstalledDistroVersion()); |
| |
| try (ZipInputStream zis = new ZipInputStream( |
| new ByteArrayInputStream(expectedDistro.getBytes()))) { |
| ZipEntry entry; |
| while ((entry = zis.getNextEntry()) != null) { |
| String entryName = entry.getName(); |
| File actualFile; |
| if (entryName.endsWith(TimeZoneDistro.DISTRO_VERSION_FILE_NAME)) { |
| actualFile = distroVersionFile; |
| } else if (entryName.endsWith(TimeZoneDistro.ICU_DATA_FILE_NAME)) { |
| actualFile = icuFile; |
| } else if (entryName.endsWith(TimeZoneDistro.TZDATA_FILE_NAME)) { |
| actualFile = bionicFile; |
| } else { |
| throw new AssertionFailedError("Unknown file found"); |
| } |
| assertContentsMatches(zis, actualFile); |
| } |
| } |
| |
| // Also check no working directory is left lying around. |
| File workingDir = installer.getWorkingDir(); |
| assertFalse(workingDir.exists()); |
| } |
| |
| private void assertContentsMatches(InputStream expected, File actual) |
| throws Exception { |
| byte[] actualBytes = IoUtils.readFileAsByteArray(actual.getPath()); |
| byte[] expectedBytes = Streams.readFullyNoClose(expected); |
| assertTrue(Arrays.equals(expectedBytes, actualBytes)); |
| } |
| |
| private void assertNoContentInstalled() throws Exception { |
| assertNull(installer.getInstalledDistroVersion()); |
| |
| File currentTzDataDir = installer.getCurrentTzDataDir(); |
| assertFalse(currentTzDataDir.exists()); |
| |
| // Also check no working directories are left lying around. |
| File workingDir = installer.getWorkingDir(); |
| assertFalse(workingDir.exists()); |
| |
| File oldDataDir = installer.getOldTzDataDir(); |
| assertFalse(oldDataDir.exists()); |
| } |
| |
| 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 static TimeZoneDistro createTimeZoneDistroWithVersionBytes(byte[] versionBytes) |
| throws Exception { |
| |
| // Create a valid distro, then manipulate the version file. |
| TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1); |
| byte[] distroBytes = distro.getBytes(); |
| |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(distroBytes.length); |
| try (ZipInputStream zipInputStream = |
| new ZipInputStream(new ByteArrayInputStream(distroBytes)); |
| ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) { |
| |
| ZipEntry entry; |
| while ((entry = zipInputStream.getNextEntry()) != null) { |
| zipOutputStream.putNextEntry(entry); |
| if (entry.getName().equals(TimeZoneDistro.DISTRO_VERSION_FILE_NAME)) { |
| // Replace the content. |
| zipOutputStream.write(versionBytes); |
| } else { |
| // Just copy the content. |
| Streams.copy(zipInputStream, zipOutputStream); |
| } |
| zipOutputStream.closeEntry(); |
| zipInputStream.closeEntry(); |
| } |
| } |
| return new TimeZoneDistro(baos.toByteArray()); |
| } |
| } |