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 */
16package libcore.tzdata.update;
17
18import junit.framework.TestCase;
19
20import java.io.File;
21import java.io.FileOutputStream;
22import java.io.IOException;
23import libcore.tzdata.update.tools.TzDataBundleBuilder;
24
25/**
26 * Tests for {@link libcore.tzdata.update.TzDataBundleInstaller}.
27 */
28public class TzDataBundleInstallerTest extends TestCase {
29
30    private static final File SYSTEM_ZONE_INFO_FILE = new File("/system/usr/share/zoneinfo/tzdata");
31
32    private TzDataBundleInstaller installer;
33    private File tempDir;
34    private File testInstallDir;
35
36    @Override
37    public void setUp() throws Exception {
38        super.setUp();
39        tempDir = createDirectory("tempDir");
40        testInstallDir =  createDirectory("testInstall");
41        installer = new TzDataBundleInstaller("TzDataBundleInstallerTest", testInstallDir);
42    }
43
44    private static File createDirectory(String prefix) throws IOException {
45        File dir = File.createTempFile(prefix, "");
46        assertTrue(dir.delete());
47        assertTrue(dir.mkdir());
48        return dir;
49    }
50
51    @Override
52    public void tearDown() throws Exception {
53        if (testInstallDir.exists()) {
54            FileUtils.deleteRecursive(testInstallDir);
55        }
56        if (tempDir.exists()) {
57            FileUtils.deleteRecursive(tempDir);
58        }
59        super.tearDown();
60    }
61
62    /** Tests the first update on a device */
63    public void testSuccessfulFirstUpdate() throws Exception {
64        ConfigBundle tzData = createValidTzDataBundle("2030a");
65
66        assertTrue(install(tzData));
67        assertTzDataInstalled(tzData);
68    }
69
70    /**
71     * Tests an update on a device when there is a prior update already applied.
72     */
73    public void testSuccessfulFollowOnUpdate() throws Exception {
74        ConfigBundle tzData1 = createValidTzDataBundle("2030a");
75        assertTrue(install(tzData1));
76        assertTzDataInstalled(tzData1);
77
78        ConfigBundle tzData2 = createValidTzDataBundle("2030b");
79        assertTrue(install(tzData2));
80        assertTzDataInstalled(tzData2);
81    }
82
83
84    /** Tests that a bundle with a missing file will not update the content. */
85    public void testMissingRequiredBundleFile() throws Exception {
86        ConfigBundle installedConfigBundle = createValidTzDataBundle("2030a");
87        assertTrue(install(installedConfigBundle));
88        assertTzDataInstalled(installedConfigBundle);
89
90        ConfigBundle incompleteUpdate =
91                createValidTzDataBundleBuilder("2030b").clearBionicTzData().buildUnvalidated();
92        assertFalse(install(incompleteUpdate));
93        assertTzDataInstalled(installedConfigBundle);
94    }
95
96    /**
97     * Tests that an update will be unpacked even if there is a partial update from a previous run.
98     */
99    public void testInstallWithWorkingDir() throws Exception {
100        File workingDir = new File(testInstallDir, TzDataBundleInstaller.WORKING_DIR_NAME);
101        assertTrue(workingDir.mkdir());
102        createFile(new File(workingDir, "myFile"));
103
104        ConfigBundle tzData = createValidTzDataBundle("2030a");
105        assertTrue(install(tzData));
106        assertTzDataInstalled(tzData);
107    }
108
109    /**
110     * Tests that a bundle with a checksum entry that references a missing file will not update the
111     * content.
112     */
113    public void testChecksumBundleEntry_fileMissing() throws Exception {
114        ConfigBundle badUpdate =
115                createValidTzDataBundleBuilder("2030b")
116                        .addChecksum("/fileDoesNotExist", 1234)
117                        .build();
118        assertFalse(install(badUpdate));
119        assertNoContentInstalled();
120    }
121
122    /**
123     * Tests that a bundle with a checksum entry with a bad checksum will not update the
124     * content.
125     */
126    public void testChecksumBundleEntry_incorrectChecksum() throws Exception {
127        File fileToChecksum = SYSTEM_ZONE_INFO_FILE;
128        long badChecksum = FileUtils.calculateChecksum(fileToChecksum) + 1;
129        ConfigBundle badUpdate =
130                createValidTzDataBundleBuilder("2030b")
131                        .clearChecksumEntries()
132                        .addChecksum(fileToChecksum.getPath(), badChecksum)
133                        .build();
134        assertFalse(install(badUpdate));
135        assertNoContentInstalled();
136    }
137
138    private boolean install(ConfigBundle configBundle) throws Exception {
139        return installer.install(configBundle.getBundleBytes());
140    }
141
142    private ConfigBundle createValidTzDataBundle(String tzDataVersion)
143            throws IOException {
144        return createValidTzDataBundleBuilder(tzDataVersion).build();
145    }
146
147    private TzDataBundleBuilder createValidTzDataBundleBuilder(String tzDataVersion)
148            throws IOException {
149
150        // The file to include in the installation-time checksum check.
151        File fileToChecksum = SYSTEM_ZONE_INFO_FILE;
152        long checksum = FileUtils.calculateChecksum(fileToChecksum);
153
154        File bionicTzData = new File(tempDir, "zoneinfo");
155        createFile(bionicTzData);
156
157        File icuData = new File(tempDir, "icudata");
158        createFile(icuData);
159
160        return new TzDataBundleBuilder()
161                .addChecksum(fileToChecksum.getPath(), checksum)
162                .setTzDataVersion(tzDataVersion)
163                .addBionicTzData(bionicTzData)
164                .addIcuTzData(icuData);
165    }
166
167    private void assertTzDataInstalled(ConfigBundle expectedTzData) throws Exception {
168        assertTrue(testInstallDir.exists());
169
170        File currentTzDataDir = new File(testInstallDir, TzDataBundleInstaller.CURRENT_TZ_DATA_DIR_NAME);
171        assertTrue(currentTzDataDir.exists());
172
173        File checksumFile = new File(currentTzDataDir, ConfigBundle.CHECKSUMS_FILE_NAME);
174        assertTrue(checksumFile.exists());
175
176        File versionFile = new File(currentTzDataDir,
177                ConfigBundle.TZ_DATA_VERSION_FILE_NAME);
178        assertTrue(versionFile.exists());
179
180        File bionicFile = new File(currentTzDataDir, ConfigBundle.ZONEINFO_FILE_NAME);
181        assertTrue(bionicFile.exists());
182
183        File icuFile = new File(currentTzDataDir, ConfigBundle.ICU_DATA_FILE_NAME);
184        assertTrue(icuFile.exists());
185
186        // Also check no working directory is left lying around.
187        File workingDir = new File(testInstallDir, TzDataBundleInstaller.WORKING_DIR_NAME);
188        assertFalse(workingDir.exists());
189    }
190
191    private void assertNoContentInstalled() {
192        File currentTzDataDir = new File(testInstallDir, TzDataBundleInstaller.CURRENT_TZ_DATA_DIR_NAME);
193        assertFalse(currentTzDataDir.exists());
194
195        // Also check no working directories are left lying around.
196        File workingDir = new File(testInstallDir, TzDataBundleInstaller.WORKING_DIR_NAME);
197        assertFalse(workingDir.exists());
198
199        File oldDataDir = new File(testInstallDir, TzDataBundleInstaller.OLD_TZ_DATA_DIR_NAME);
200        assertFalse(oldDataDir.exists());
201    }
202
203    private static void createFile(File file) {
204        try (FileOutputStream fos = new FileOutputStream(file)) {
205            fos.write('a');
206        } catch (IOException e) {
207            fail(e.getMessage());
208        }
209    }
210}
211