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