• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 
17 package com.android.cts.tzdata;
18 
19 import static org.junit.Assert.assertArrayEquals;
20 
21 import libcore.timezone.TzDataSetVersion;
22 import libcore.timezone.testing.ZoneInfoTestHelper;
23 
24 import com.android.timezone.distro.DistroVersion;
25 import com.android.timezone.distro.TimeZoneDistro;
26 import com.android.timezone.distro.builder.TimeZoneDistroBuilder;
27 import com.android.tradefed.device.DeviceNotAvailableException;
28 import com.android.tradefed.testtype.DeviceTestCase;
29 
30 import java.io.File;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.nio.charset.StandardCharsets;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.util.Comparator;
37 import java.util.StringJoiner;
38 import java.util.function.Consumer;
39 
40 /**
41  * Tests for the tzdatacheck binary.
42  *
43  * <p>The tzdatacheck binary operates over two directories: the "system directory" containing the
44  * time zone rules in the system image, and a "data directory" in the data partition which can
45  * optionally contain time zone rules data files for bionic/libcore and ICU.
46  *
47  * <p>This test executes the tzdatacheck binary to confirm it operates correctly in a number of
48  * simulated situations; simulated system and data directories in various states are created in a
49  * location the shell user has permission to access and the tzdatacheck binary is then executed.
50  * The status code and directory state after execution is then used to determine if the tzdatacheck
51  * binary operated correctly.
52  *
53  * <p>Most of the tests below prepare simulated directory structure for the system and data dirs
54  * on the host before pushing them to the device. Device state is then checked rather than syncing
55  * the files back.
56  */
57 public class TzDataCheckTest extends DeviceTestCase {
58 
59     /**
60      * The name of the directory containing the current time zone rules data beneath
61      * {@link #mDataDir}.  Also known to {@link
62      * com.android.timezone.distro.installer.TimeZoneDistroInstaller} and tzdatacheck.cpp.
63      */
64     private static final String CURRENT_DIR_NAME = "current";
65 
66     /**
67      * The name of the directory containing the staged time zone rules data beneath
68      * {@link #mDataDir}.  Also known to {@link
69      * com.android.timezone.distro.installer.TimeZoneDistroInstaller} and tzdatacheck.cpp.
70      */
71     private static final String STAGED_DIR_NAME = "staged";
72 
73     /**
74      * The name of the file inside the staged directory that indicates the staged operation is an
75      * uninstall. Also known to {@link com.android.timezone.distro.installer.TimeZoneDistroInstaller} and
76      * tzdatacheck.cpp.
77      */
78     private static final String UNINSTALL_TOMBSTONE_FILE_NAME = "STAGED_UNINSTALL_TOMBSTONE";
79 
80     /**
81      * The name of the /system time zone data file. Also known to tzdatacheck.cpp.
82      */
83     private static final String SYSTEM_TZ_VERSION_FILE_NAME = "tz_version";
84 
85     /** A valid time zone rules version guaranteed to be older than {@link #RULES_VERSION_TWO} */
86     private static final String RULES_VERSION_ONE = "2016g";
87     /** A valid time zone rules version guaranteed to be newer than {@link #RULES_VERSION_ONE} */
88     private static final String RULES_VERSION_TWO = "2016h";
89     /**
90      * An arbitrary, valid time zone rules version used when it doesn't matter what the rules
91      * version is.
92      */
93     private static final String VALID_RULES_VERSION = RULES_VERSION_ONE;
94 
95     /** An arbitrary valid revision number. */
96     private static final int VALID_REVISION = 1;
97 
98     private String mDeviceAndroidRootDir;
99     private PathPair mTestRootDir;
100     private PathPair mSystemDir;
101     private PathPair mDataDir;
102 
setUp()103     public void setUp() throws Exception {
104         super.setUp();
105 
106         // It's not clear how we would get this without invoking "/system/bin/sh", but we need the
107         // value first to do so. It has been hardcoded instead.
108         mDeviceAndroidRootDir = "/system";
109 
110         // Create a test root directory on host and device.
111         Path hostTestRootDir = Files.createTempDirectory("tzdatacheck_test");
112         mTestRootDir = new PathPair(
113                 hostTestRootDir,
114                 "/data/local/tmp/tzdatacheck_test");
115         createDeviceDirectory(mTestRootDir);
116 
117         // tzdatacheck requires two directories: a "system" path and a "data" path.
118         mSystemDir = mTestRootDir.createSubPath("system_dir");
119         mDataDir = mTestRootDir.createSubPath("data_dir");
120 
121         // Create the host-side directory structure (for preparing files before pushing them to
122         // device and looking at files retrieved from device).
123         createHostDirectory(mSystemDir);
124         createHostDirectory(mDataDir);
125 
126         // Create the equivalent device-side directory structure for receiving files.
127         createDeviceDirectory(mSystemDir);
128         createDeviceDirectory(mDataDir);
129     }
130 
131     @Override
tearDown()132     public void tearDown() throws Exception {
133         // Remove the test root directories that have been created by this test.
134         deleteHostDirectory(mTestRootDir, true /* failOnError */);
135         deleteDeviceDirectory(mTestRootDir, true /* failOnError */);
136         super.tearDown();
137     }
138 
139     /**
140      * Test the real base files exist in the expected locations - tzcdatacheck relies on some of
141      * them via a command line argument hardcoded in system/core/rootdir/init.rc.
142      */
testExpectedBaseFilesExist()143     public void testExpectedBaseFilesExist() throws Exception {
144         String baseTzFilesDir = "/apex/com.android.runtime/etc/tz/";
145         assertDeviceFileExists(baseTzFilesDir + "tz_version");
146         assertDeviceFileExists(baseTzFilesDir + "tzdata");
147         assertDeviceFileExists(baseTzFilesDir + "tzlookup.xml");
148     }
149 
testTooFewArgs()150     public void testTooFewArgs() throws Exception {
151         // No need to set up or push files to the device for this test.
152         assertEquals(1, runTzDataCheckWithArgs(new String[0]));
153         assertEquals(1, runTzDataCheckWithArgs(new String[] { "oneArg" }));
154     }
155 
156     // {dataDir}/staged exists but it is a file.
testStaging_stagingDirIsFile()157     public void testStaging_stagingDirIsFile() throws Exception {
158         // Set up the /system directory structure on host.
159         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
160 
161         // Set up the /data directory structure on host.
162         PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
163         // Create a file with the same name as the directory that tzdatacheck expects.
164         Files.write(dataStagedDir.hostPath, new byte[] { 'a' });
165 
166         // Push the host test directory and contents to the device.
167         pushHostTestDirToDevice();
168 
169         // Execute tzdatacheck and check the status code. Failures due to staging issues are
170         // generally ignored providing the device is left in a reasonable state.
171         assertEquals(0, runTzDataCheckOnDevice());
172 
173         // Assert the file was just ignored. This is a fairly arbitrary choice to leave it rather
174         // than delete.
175         assertDevicePathExists(dataStagedDir);
176         assertDevicePathIsFile(dataStagedDir);
177     }
178 
179     // {dataDir}/staged exists but /current dir is a file.
testStaging_uninstall_currentDirIsFile()180     public void testStaging_uninstall_currentDirIsFile() throws Exception {
181         // Set up the /system directory structure on host.
182         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
183 
184         // Set up the /data directory structure on host.
185 
186         // Create a staged uninstall.
187         PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
188         createStagedUninstallOnHost(dataStagedDir);
189 
190         // Create a file with the same name as the directory that tzdatacheck expects.
191         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
192         Files.write(dataCurrentDir.hostPath, new byte[] { 'a' });
193 
194         // Push the host test directory and contents to the device.
195         pushHostTestDirToDevice();
196 
197         // Execute tzdatacheck and check the status code.
198         assertEquals(0, runTzDataCheckOnDevice());
199 
200         // Assert the device was left in a valid "uninstalled" state.
201         assertDevicePathDoesNotExist(dataStagedDir);
202         assertDevicePathDoesNotExist(dataCurrentDir);
203     }
204 
205     // {dataDir}/staged contains an uninstall, but there is nothing to uninstall.
testStaging_uninstall_noCurrent()206     public void testStaging_uninstall_noCurrent() throws Exception {
207         // Set up the /system directory structure on host.
208         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
209 
210         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
211 
212         // Set up the /data directory structure on host.
213 
214         // Create a staged uninstall.
215         PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
216         createStagedUninstallOnHost(dataStagedDir);
217 
218         // Push the host test directory and contents to the device.
219         pushHostTestDirToDevice();
220 
221         // Execute tzdatacheck and check the status code. Failures due to staging issues are
222         // generally ignored providing the device is left in a reasonable state.
223         assertEquals(0, runTzDataCheckOnDevice());
224 
225         // Assert the device was left in a valid "uninstalled" state.
226         assertDevicePathDoesNotExist(dataStagedDir);
227         assertDevicePathDoesNotExist(dataCurrentDir);
228     }
229 
230     // {dataDir}/staged contains an uninstall, and there is something to uninstall.
testStaging_uninstall_withCurrent()231     public void testStaging_uninstall_withCurrent() throws Exception {
232         // Set up the /system directory structure on host.
233         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
234 
235         // Set up the /data directory structure on host.
236 
237         // Create a staged uninstall.
238         PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
239         createStagedUninstallOnHost(dataStagedDir);
240 
241         // Create a current installed distro.
242         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
243         byte[] distroBytes = createValidDistroBuilder().buildBytes();
244         unpackOnHost(dataCurrentDir, distroBytes);
245 
246         // Push the host test directory and contents to the device.
247         pushHostTestDirToDevice();
248 
249         // Execute tzdatacheck and check the status code. Failures due to staging issues are
250         // generally ignored providing the device is left in a reasonable state.
251         assertEquals(0, runTzDataCheckOnDevice());
252 
253         // Assert the device was left in a valid "uninstalled" state.
254         assertDevicePathDoesNotExist(dataStagedDir);
255         assertDevicePathDoesNotExist(dataCurrentDir);
256     }
257 
258     // {dataDir}/staged exists but /current dir is a file.
testStaging_install_currentDirIsFile()259     public void testStaging_install_currentDirIsFile() throws Exception {
260         // Set up the /system directory structure on host.
261         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
262 
263         // Set up the /data directory structure on host.
264 
265         // Create a staged install.
266         PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
267         byte[] distroBytes = createValidDistroBuilder().buildBytes();
268         unpackOnHost(dataStagedDir, distroBytes);
269 
270         // Create a file with the same name as the directory that tzdatacheck expects.
271         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
272         Files.write(dataCurrentDir.hostPath, new byte[] { 'a' });
273 
274         // Push the host test directory and contents to the device.
275         pushHostTestDirToDevice();
276 
277         // Execute tzdatacheck and check the status code. Failures due to staging issues are
278         // generally ignored providing the device is left in a reasonable state.
279         assertEquals(0, runTzDataCheckOnDevice());
280 
281         // Assert the device was left in a valid "installed" state.
282         assertDevicePathDoesNotExist(dataStagedDir);
283         assertDeviceDirContainsDistro(dataCurrentDir, distroBytes);
284     }
285 
286     // {dataDir}/staged contains an install, but there is nothing to replace.
testStaging_install_noCurrent()287     public void testStaging_install_noCurrent() throws Exception {
288         // Set up the /system directory structure on host.
289         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
290 
291         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
292 
293         // Set up the /data directory structure on host.
294 
295         // Create a staged install.
296         PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
297         byte[] stagedDistroBytes = createValidDistroBuilder().buildBytes();
298         unpackOnHost(dataStagedDir, stagedDistroBytes);
299 
300         // Push the host test directory and contents to the device.
301         pushHostTestDirToDevice();
302 
303         // Execute tzdatacheck and check the status code. Failures due to staging issues are
304         // generally ignored providing the device is left in a reasonable state.
305         assertEquals(0, runTzDataCheckOnDevice());
306 
307         // Assert the device was left in a valid "installed" state.
308         assertDevicePathDoesNotExist(dataStagedDir);
309         assertDeviceDirContainsDistro(dataCurrentDir, stagedDistroBytes);
310     }
311 
312     // {dataDir}/staged contains an install, and there is something to replace.
testStaging_install_withCurrent()313     public void testStaging_install_withCurrent() throws Exception {
314         // Set up the /system directory structure on host.
315         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
316 
317         DistroVersion currentDistroVersion = new DistroVersion(
318                 TzDataSetVersion.currentFormatMajorVersion(), 1, VALID_RULES_VERSION, 1);
319         DistroVersion stagedDistroVersion = new DistroVersion(
320                 TzDataSetVersion.currentFormatMajorVersion(), 1, VALID_RULES_VERSION, 2);
321 
322         // Set up the /data directory structure on host.
323 
324         // Create a staged uninstall.
325         PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
326         byte[] stagedDistroBytes = createValidDistroBuilder()
327                 .setDistroVersion(stagedDistroVersion)
328                 .buildBytes();
329         unpackOnHost(dataStagedDir, stagedDistroBytes);
330 
331         // Create a current installed distro.
332         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
333         byte[] currentDistroBytes = createValidDistroBuilder()
334                 .setDistroVersion(currentDistroVersion)
335                 .buildBytes();
336         unpackOnHost(dataCurrentDir, currentDistroBytes);
337 
338         // Push the host test directory and contents to the device.
339         pushHostTestDirToDevice();
340 
341         // Execute tzdatacheck and check the status code. Failures due to staging issues are
342         // generally ignored providing the device is left in a reasonable state.
343         assertEquals(0, runTzDataCheckOnDevice());
344 
345         // Assert the device was left in a valid "installed" state.
346         // The stagedDistro should now be the one in the current dir.
347         assertDevicePathDoesNotExist(dataStagedDir);
348         assertDeviceDirContainsDistro(dataCurrentDir, stagedDistroBytes);
349     }
350 
351     // {dataDir}/staged contains an invalid install, and there is something to replace.
352     // Most of the invalid cases are tested without staging; this is just to prove that staging
353     // an invalid distro is handled the same.
testStaging_install_withCurrent_invalidStaged()354     public void testStaging_install_withCurrent_invalidStaged() throws Exception {
355         // Set up the /system directory structure on host.
356         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
357 
358         // Set up the /data directory structure on host.
359 
360         // Create a staged uninstall which contains invalid files (missing distro version).
361         PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
362         byte[] stagedDistroBytes = createValidDistroBuilder()
363                 .clearVersionForTests()
364                 .buildUnvalidatedBytes();
365         unpackOnHost(dataStagedDir, stagedDistroBytes);
366 
367         // Create a current installed distro.
368         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
369         byte[] currentDistroBytes = createValidDistroBuilder().buildBytes();
370         unpackOnHost(dataCurrentDir, currentDistroBytes);
371 
372         // Push the host test directory and contents to the device.
373         pushHostTestDirToDevice();
374 
375         // Execute tzdatacheck and check the status code. The staged directory will have become the
376         // current one, but then it will be discovered to be invalid and will be removed.
377         assertEquals(3, runTzDataCheckOnDevice());
378 
379         // Assert the device was left in a valid "uninstalled" state.
380         assertDevicePathDoesNotExist(dataStagedDir);
381         assertDevicePathDoesNotExist(dataCurrentDir);
382     }
383 
384     // No {dataDir}/current exists.
testNoCurrentDataDir()385     public void testNoCurrentDataDir() throws Exception {
386         // Set up the /system directory structure on host.
387         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
388 
389         // Deliberately not creating anything on host in the data dir here, leaving the empty
390         // structure.
391 
392         // Push the host test directory and contents to the device.
393         pushHostTestDirToDevice();
394 
395         // Execute tzdatacheck and check the status code.
396         assertEquals(0, runTzDataCheckOnDevice());
397     }
398 
399     // {dataDir}/current exists but it is a file.
testCurrentDataDirIsFile()400     public void testCurrentDataDirIsFile() throws Exception {
401         // Set up the /system directory structure on host.
402         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
403 
404         // Set up the /data directory structure on host.
405         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
406         // Create a file with the same name as the directory that tzdatacheck expects.
407         Files.write(dataCurrentDir.hostPath, new byte[] { 'a' });
408 
409         // Push the host test directory and contents to the device.
410         pushHostTestDirToDevice();
411 
412         // Execute tzdatacheck and check the status code.
413         assertEquals(2, runTzDataCheckOnDevice());
414 
415         // Assert the file was just ignored. This is a fairly arbitrary choice to leave it rather
416         // than delete.
417         assertDevicePathExists(dataCurrentDir);
418         assertDevicePathIsFile(dataCurrentDir);
419     }
420 
421     // {dataDir}/current exists but is missing the distro version file.
testMissingDataDirDistroVersionFile()422     public void testMissingDataDirDistroVersionFile() throws Exception {
423         // Set up the /system directory structure on host.
424         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
425 
426         // Set up the /data directory structure on host.
427         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
428         byte[] distroWithoutAVersionFileBytes = createValidDistroBuilder()
429                 .clearVersionForTests()
430                 .buildUnvalidatedBytes();
431         unpackOnHost(dataCurrentDir, distroWithoutAVersionFileBytes);
432 
433         // Push the host test directory and contents to the device.
434         pushHostTestDirToDevice();
435 
436         // Execute tzdatacheck and check the status code.
437         assertEquals(3, runTzDataCheckOnDevice());
438 
439         // Assert the current data directory was deleted.
440         assertDevicePathDoesNotExist(dataCurrentDir);
441     }
442 
443     // {dataDir}/current exists but the distro version file is short.
testShortDataDirDistroVersionFile()444     public void testShortDataDirDistroVersionFile() throws Exception {
445         // Set up the /system directory structure on host.
446         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
447 
448         // Set up the /data directory structure on host.
449         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
450         unpackOnHost(dataCurrentDir, createValidDistroBuilder().buildBytes());
451         // Replace the distro version file with a short file.
452         Path distroVersionFile =
453                 dataCurrentDir.hostPath.resolve(TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
454         assertHostFileExists(distroVersionFile);
455         Files.write(distroVersionFile, new byte[3]);
456 
457         // Push the host test directory and contents to the device.
458         pushHostTestDirToDevice();
459 
460         // Execute tzdatacheck and check the status code.
461         assertEquals(3, runTzDataCheckOnDevice());
462 
463         // Assert the current data directory was deleted.
464         assertDevicePathDoesNotExist(dataCurrentDir);
465     }
466 
467     // {dataDir}/current exists and the distro version file is long enough, but contains junk.
testCorruptDistroVersionFile()468     public void testCorruptDistroVersionFile() throws Exception {
469         // Set up the /system directory structure on host.
470         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
471 
472         // Set up the /data directory structure on host.
473         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
474         unpackOnHost(dataCurrentDir, createValidDistroBuilder().buildBytes());
475 
476         // Replace the distro version file with junk.
477         Path distroVersionFile =
478                 dataCurrentDir.hostPath.resolve(TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
479         assertHostFileExists(distroVersionFile);
480 
481         int fileLength = (int) Files.size(distroVersionFile);
482         byte[] junkArray = new byte[fileLength]; // all zeros
483         Files.write(distroVersionFile, junkArray);
484 
485         // Push the host test directory and contents to the device.
486         pushHostTestDirToDevice();
487 
488         // Execute tzdatacheck and check the status code.
489         assertEquals(4, runTzDataCheckOnDevice());
490 
491         // Assert the current data directory was deleted.
492         assertDevicePathDoesNotExist(dataCurrentDir);
493     }
494 
495     // {dataDir}/current exists but the distro version is incorrect.
testInvalidMajorDistroVersion_older()496     public void testInvalidMajorDistroVersion_older() throws Exception {
497         // Set up the /system directory structure on host.
498         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
499 
500         // Set up the /data directory structure on host.
501         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
502         DistroVersion oldMajorDistroVersion = new DistroVersion(
503                 TzDataSetVersion.currentFormatMajorVersion() - 1, 1, VALID_RULES_VERSION, 1);
504         byte[] distroBytes = createValidDistroBuilder()
505                 .setDistroVersion(oldMajorDistroVersion)
506                 .buildBytes();
507         unpackOnHost(dataCurrentDir, distroBytes);
508 
509         // Push the host test directory and contents to the device.
510         pushHostTestDirToDevice();
511 
512         // Execute tzdatacheck and check the status code.
513         assertEquals(5, runTzDataCheckOnDevice());
514 
515         // Assert the current data directory was deleted.
516         assertDevicePathDoesNotExist(dataCurrentDir);
517     }
518 
519     // {dataDir}/current exists but the distro version is incorrect.
testInvalidMajorDistroVersion_newer()520     public void testInvalidMajorDistroVersion_newer() throws Exception {
521         // Set up the /system directory structure on host.
522         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
523 
524         // Set up the /data directory structure on host.
525         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
526         DistroVersion newMajorDistroVersion = new DistroVersion(
527                 TzDataSetVersion.currentFormatMajorVersion() + 1,
528                 TzDataSetVersion.currentFormatMinorVersion(),
529                 VALID_RULES_VERSION, VALID_REVISION);
530         byte[] distroBytes = createValidDistroBuilder()
531                 .setDistroVersion(newMajorDistroVersion)
532                 .buildBytes();
533         unpackOnHost(dataCurrentDir, distroBytes);
534 
535         // Push the host test directory and contents to the device.
536         pushHostTestDirToDevice();
537 
538         // Execute tzdatacheck and check the status code.
539         assertEquals(5, runTzDataCheckOnDevice());
540 
541         // Assert the current data directory was deleted.
542         assertDevicePathDoesNotExist(dataCurrentDir);
543     }
544 
545     // {dataDir}/current exists but the distro version is incorrect.
testInvalidMinorDistroVersion_older()546     public void testInvalidMinorDistroVersion_older() throws Exception {
547         // Set up the /system directory structure on host.
548         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
549 
550         // Set up the /data directory structure on host.
551         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
552         DistroVersion oldMinorDistroVersion = new DistroVersion(
553                 TzDataSetVersion.currentFormatMajorVersion(),
554                 TzDataSetVersion.currentFormatMinorVersion() - 1,
555                 VALID_RULES_VERSION, 1);
556         byte[] distroBytes = createValidDistroBuilder()
557                 .setDistroVersion(oldMinorDistroVersion)
558                 .buildBytes();
559         unpackOnHost(dataCurrentDir, distroBytes);
560 
561         // Push the host test directory and contents to the device.
562         pushHostTestDirToDevice();
563 
564         // Execute tzdatacheck and check the status code.
565         assertEquals(5, runTzDataCheckOnDevice());
566 
567         // Assert the current data directory was deleted.
568         assertDevicePathDoesNotExist(dataCurrentDir);
569     }
570 
571     // {dataDir}/current exists but the distro version is newer (which is accepted because it should
572     // be backwards compatible).
testValidMinorDistroVersion_newer()573     public void testValidMinorDistroVersion_newer() throws Exception {
574         // Set up the /system directory structure on host.
575         createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
576 
577         // Set up the /data directory structure on host.
578         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
579         DistroVersion newMajorDistroVersion = new DistroVersion(
580                 TzDataSetVersion.currentFormatMajorVersion(),
581                 TzDataSetVersion.currentFormatMinorVersion() + 1,
582                 VALID_RULES_VERSION, VALID_REVISION);
583         byte[] distroBytes = createValidDistroBuilder()
584                 .setDistroVersion(newMajorDistroVersion)
585                 .buildBytes();
586         unpackOnHost(dataCurrentDir, distroBytes);
587 
588         // Push the host test directory and contents to the device.
589         pushHostTestDirToDevice();
590 
591         // Execute tzdatacheck and check the status code.
592         assertEquals(0, runTzDataCheckOnDevice());
593 
594         // Assert the current data directory was not touched.
595         assertDeviceDirContainsDistro(dataCurrentDir, distroBytes);
596     }
597 
598     // {dataDir}/current is valid but the tz_version file in /system is missing.
testSystemTzVersionFileMissing()599     public void testSystemTzVersionFileMissing() throws Exception {
600         // Deliberately not writing anything in /system here.
601 
602         // Set up the /data directory structure on host.
603         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
604         byte[] validDistroBytes = createValidDistroBuilder().buildBytes();
605         unpackOnHost(dataCurrentDir, validDistroBytes);
606 
607         // Push the host test directory and contents to the device.
608         pushHostTestDirToDevice();
609 
610         // Execute tzdatacheck and check the status code.
611         assertEquals(6, runTzDataCheckOnDevice());
612 
613         // Assert the current data directory was not touched.
614         assertDeviceDirContainsDistro(dataCurrentDir, validDistroBytes);
615     }
616 
617     // {dataDir}/current is valid but the tz_version file in /system is junk.
testSystemTzVersionFileCorrupt()618     public void testSystemTzVersionFileCorrupt() throws Exception {
619         // Set up the /system directory structure on host.
620         byte[] invalidTzDataBytes = new byte[20];
621         Files.write(mSystemDir.hostPath.resolve(SYSTEM_TZ_VERSION_FILE_NAME), invalidTzDataBytes);
622 
623         // Set up the /data directory structure on host.
624         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
625         byte[] validDistroBytes = createValidDistroBuilder().buildBytes();
626         unpackOnHost(dataCurrentDir, validDistroBytes);
627 
628         // Push the host test directory and contents to the device.
629         pushHostTestDirToDevice();
630 
631         // Execute tzdatacheck and check the status code.
632         assertEquals(7, runTzDataCheckOnDevice());
633 
634         // Assert the current data directory was not touched.
635         assertDeviceDirContainsDistro(dataCurrentDir, validDistroBytes);
636     }
637 
638     // {dataDir}/current is valid and the tz_version file in /system is for older data.
testSystemTzRulesOlder()639     public void testSystemTzRulesOlder() throws Exception {
640         // Set up the /system directory structure on host.
641         createSystemTzVersionFileOnHost(RULES_VERSION_ONE);
642 
643         // Set up the /data directory structure on host.
644         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
645         // Newer than RULES_VERSION_ONE in /system
646         final String distroRulesVersion = RULES_VERSION_TWO;
647         DistroVersion distroVersion = new DistroVersion(
648                 TzDataSetVersion.currentFormatMajorVersion(),
649                 TzDataSetVersion.currentFormatMinorVersion(), distroRulesVersion, VALID_REVISION);
650         byte[] distroBytes = createValidDistroBuilder()
651                 .setDistroVersion(distroVersion)
652                 .setTzDataFile(createValidTzDataBytes(distroRulesVersion))
653                 .buildBytes();
654         unpackOnHost(dataCurrentDir, distroBytes);
655 
656         // Push the host test directory and contents to the device.
657         pushHostTestDirToDevice();
658 
659         // Execute tzdatacheck and check the status code.
660         assertEquals(0, runTzDataCheckOnDevice());
661 
662         // Assert the current data directory was not touched.
663         assertDeviceDirContainsDistro(dataCurrentDir, distroBytes);
664     }
665 
666     // {dataDir}/current is valid and the tz_version file in /system is the same. Data dir should be
667     // kept.
testSystemTzVersionSame()668     public void testSystemTzVersionSame() throws Exception {
669         // Set up the /system directory structure on host.
670         final String systemRulesVersion = VALID_RULES_VERSION;
671         createSystemTzVersionFileOnHost(systemRulesVersion);
672 
673         // Set up the /data directory structure on host.
674         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
675         DistroVersion distroVersion = new DistroVersion(
676                 TzDataSetVersion.currentFormatMajorVersion(),
677                 TzDataSetVersion.currentFormatMinorVersion(),
678                 systemRulesVersion,
679                 VALID_REVISION);
680         byte[] distroBytes = createValidDistroBuilder()
681                 .setDistroVersion(distroVersion)
682                 .setTzDataFile(createValidTzDataBytes(systemRulesVersion))
683                 .buildBytes();
684         unpackOnHost(dataCurrentDir, distroBytes);
685 
686         // Push the host test directory and contents to the device.
687         pushHostTestDirToDevice();
688 
689         // Execute tzdatacheck and check the status code.
690         assertEquals(0, runTzDataCheckOnDevice());
691 
692         // Assert the current data directory was not touched.
693         assertDeviceDirContainsDistro(dataCurrentDir, distroBytes);
694     }
695 
696     // {dataDir}/current is valid and the tzdata file in /system is the newer.
testSystemTzVersionNewer()697     public void testSystemTzVersionNewer() throws Exception {
698         // Set up the /system directory structure on host.
699         String systemRulesVersion = RULES_VERSION_TWO;
700         createSystemTzVersionFileOnHost(systemRulesVersion);
701 
702         // Set up the /data directory structure on host.
703         PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
704         String distroRulesVersion = RULES_VERSION_ONE; // Older than the system version.
705         DistroVersion distroVersion = new DistroVersion(
706                 TzDataSetVersion.currentFormatMajorVersion(),
707                 TzDataSetVersion.currentFormatMinorVersion(),
708                 distroRulesVersion,
709                 VALID_REVISION);
710         byte[] distroBytes = createValidDistroBuilder()
711                 .setDistroVersion(distroVersion)
712                 .setTzDataFile(createValidTzDataBytes(distroRulesVersion))
713                 .buildBytes();
714         unpackOnHost(dataCurrentDir, distroBytes);
715 
716         // Push the host test directory and contents to the device.
717         pushHostTestDirToDevice();
718 
719         // Execute tzdatacheck and check the status code.
720         assertEquals(0, runTzDataCheckOnDevice());
721 
722         // It is important the dataCurrentDir is deleted in this case - this test case is the main
723         // reason tzdatacheck exists.
724         assertDevicePathDoesNotExist(dataCurrentDir);
725     }
726 
createSystemTzVersionFileOnHost(String systemRulesVersion)727     private void createSystemTzVersionFileOnHost(String systemRulesVersion) throws Exception {
728         byte[] systemTzData = createValidTzVersionBytes(systemRulesVersion);
729         Files.write(mSystemDir.hostPath.resolve(SYSTEM_TZ_VERSION_FILE_NAME), systemTzData);
730     }
731 
createStagedUninstallOnHost(PathPair stagedDir)732     private static void createStagedUninstallOnHost(PathPair stagedDir) throws Exception {
733         createHostDirectory(stagedDir);
734 
735         PathPair uninstallTombstoneFile = stagedDir.createSubPath(UNINSTALL_TOMBSTONE_FILE_NAME);
736         // Create an empty file.
737         new FileOutputStream(uninstallTombstoneFile.hostFile()).close();
738     }
739 
unpackOnHost(PathPair path, byte[] distroBytes)740     private static void unpackOnHost(PathPair path, byte[] distroBytes) throws Exception {
741         createHostDirectory(path);
742         new TimeZoneDistro(distroBytes).extractTo(path.hostFile());
743     }
744 
createValidDistroBuilder()745     private static TimeZoneDistroBuilder createValidDistroBuilder() throws Exception {
746         String distroRulesVersion = VALID_RULES_VERSION;
747         DistroVersion validDistroVersion =
748                 new DistroVersion(
749                         TzDataSetVersion.currentFormatMajorVersion(),
750                         TzDataSetVersion.currentFormatMinorVersion(),
751                         distroRulesVersion, VALID_REVISION);
752         return new TimeZoneDistroBuilder()
753                 .setDistroVersion(validDistroVersion)
754                 .setTzDataFile(createValidTzDataBytes(distroRulesVersion))
755                 .setIcuDataFile(new byte[10]);
756     }
757 
createValidTzDataBytes(String rulesVersion)758     private static byte[] createValidTzDataBytes(String rulesVersion) {
759         return new ZoneInfoTestHelper.TzDataBuilder()
760                 .initializeToValid()
761                 .setHeaderMagic("tzdata" + rulesVersion)
762                 .build();
763     }
764 
createValidTzVersionBytes(String rulesVersion)765     private static byte[] createValidTzVersionBytes(String rulesVersion) throws Exception {
766         return new TzDataSetVersion(
767                 TzDataSetVersion.currentFormatMajorVersion(),
768                 TzDataSetVersion.currentFormatMinorVersion(),
769                 rulesVersion,
770                 VALID_REVISION)
771                 .toBytes();
772     }
773 
runTzDataCheckOnDevice()774     private int runTzDataCheckOnDevice() throws Exception {
775         return runTzDataCheckWithArgs(new String[] { mSystemDir.devicePath, mDataDir.devicePath });
776     }
777 
runTzDataCheckWithArgs(String[] args)778     private int runTzDataCheckWithArgs(String[] args) throws Exception {
779         String command = createTzDataCheckCommand(mDeviceAndroidRootDir, args);
780         return executeCommandOnDeviceWithResultCode(command).statusCode;
781     }
782 
createTzDataCheckCommand(String rootDir, String[] args)783     private static String createTzDataCheckCommand(String rootDir, String[] args) {
784         StringJoiner joiner = new StringJoiner(" ");
785         String tzDataCheckCommand = rootDir + "/bin/tzdatacheck";
786         joiner.add(tzDataCheckCommand);
787         for (String arg : args) {
788             joiner.add(arg);
789         }
790         return joiner.toString();
791     }
792 
assertHostFileExists(Path path)793     private static void assertHostFileExists(Path path) {
794         assertTrue(Files.exists(path));
795     }
796 
executeCommandOnDeviceRaw(String command)797     private String executeCommandOnDeviceRaw(String command) throws DeviceNotAvailableException {
798         return getDevice().executeShellCommand(command);
799     }
800 
createDeviceDirectory(PathPair dir)801     private void createDeviceDirectory(PathPair dir) throws DeviceNotAvailableException {
802         executeCommandOnDeviceRaw("mkdir -p " + dir.devicePath);
803     }
804 
createHostDirectory(PathPair dir)805     private static void createHostDirectory(PathPair dir) throws Exception {
806         Files.createDirectory(dir.hostPath);
807     }
808 
809     private static class ShellResult {
810         final String output;
811         final int statusCode;
812 
ShellResult(String output, int statusCode)813         private ShellResult(String output, int statusCode) {
814             this.output = output;
815             this.statusCode = statusCode;
816         }
817     }
818 
executeCommandOnDeviceWithResultCode(String command)819     private ShellResult executeCommandOnDeviceWithResultCode(String command) throws Exception {
820         // A file to hold the script we're going to create.
821         PathPair scriptFile = mTestRootDir.createSubPath("script.sh");
822         // A file to hold the output of the script.
823         PathPair scriptOut = mTestRootDir.createSubPath("script.out");
824 
825         // The content of the script. Runs the command, capturing stdout and stderr to scriptOut
826         // and printing the result code.
827         String hostScriptContent = command + " > " + scriptOut.devicePath + " 2>&1 ; echo -n $?";
828 
829         // Parse and return the result.
830         try {
831             Files.write(scriptFile.hostPath, hostScriptContent.getBytes(StandardCharsets.US_ASCII));
832 
833             // Push the script to the device.
834             pushFile(scriptFile);
835 
836             // Execute the script using "sh".
837             String execCommandUnderShell =
838                     mDeviceAndroidRootDir + "/bin/sh " + scriptFile.devicePath;
839             String resultCodeString = executeCommandOnDeviceRaw(execCommandUnderShell);
840 
841             // Pull back scriptOut to the host and read the content.
842             pullFile(scriptOut);
843             byte[] outputBytes = Files.readAllBytes(scriptOut.hostPath);
844             String output = new String(outputBytes, StandardCharsets.US_ASCII);
845 
846             int resultCode;
847             try {
848                 resultCode = Integer.parseInt(resultCodeString);
849             } catch (NumberFormatException e) {
850                 fail("Command: " + command
851                         + " returned a non-integer: \"" + resultCodeString + "\""
852                         + ", output=\"" + output + "\"");
853                 return null;
854             }
855             return new ShellResult(output, resultCode);
856         } finally {
857             deleteDeviceFile(scriptFile, false /* failOnError */);
858             deleteDeviceFile(scriptOut, false /* failOnError */);
859             deleteHostFile(scriptFile, false /* failOnError */);
860             deleteHostFile(scriptOut, false /* failOnError */);
861         }
862     }
863 
pushHostTestDirToDevice()864     private void pushHostTestDirToDevice() throws Exception {
865         assertTrue(getDevice().pushDir(mTestRootDir.hostFile(), mTestRootDir.devicePath));
866     }
867 
pullFile(PathPair file)868     private void pullFile(PathPair file) throws DeviceNotAvailableException {
869         assertTrue("Could not pull file " + file.devicePath + " to " + file.hostFile(),
870                 getDevice().pullFile(file.devicePath, file.hostFile()));
871     }
872 
pushFile(PathPair file)873     private void pushFile(PathPair file) throws DeviceNotAvailableException {
874         assertTrue("Could not push file " + file.hostFile() + " to " + file.devicePath,
875                 getDevice().pushFile(file.hostFile(), file.devicePath));
876     }
877 
deleteHostFile(PathPair file, boolean failOnError)878     private void deleteHostFile(PathPair file, boolean failOnError) {
879         try {
880             Files.deleteIfExists(file.hostPath);
881         } catch (IOException e) {
882             if (failOnError) {
883                 fail(e);
884             }
885         }
886     }
887 
deleteDeviceDirectory(PathPair dir, boolean failOnError)888     private void deleteDeviceDirectory(PathPair dir, boolean failOnError)
889             throws DeviceNotAvailableException {
890         String deviceDir = dir.devicePath;
891         try {
892             executeCommandOnDeviceRaw("rm -r " + deviceDir);
893         } catch (Exception e) {
894             if (failOnError) {
895                 throw deviceFail(e);
896             }
897         }
898     }
899 
deleteDeviceFile(PathPair file, boolean failOnError)900     private void deleteDeviceFile(PathPair file, boolean failOnError)
901             throws DeviceNotAvailableException {
902         try {
903             assertDevicePathIsFile(file);
904             executeCommandOnDeviceRaw("rm " + file.devicePath);
905         } catch (Exception e) {
906             if (failOnError) {
907                 throw deviceFail(e);
908             }
909         }
910     }
911 
deleteHostDirectory(PathPair dir, final boolean failOnError)912     private static void deleteHostDirectory(PathPair dir, final boolean failOnError) {
913         Path hostPath = dir.hostPath;
914         if (Files.exists(hostPath)) {
915             Consumer<Path> pathConsumer = file -> {
916                 try {
917                     Files.delete(file);
918                 } catch (Exception e) {
919                     if (failOnError) {
920                         fail(e);
921                     }
922                 }
923             };
924 
925             try {
926                 Files.walk(hostPath).sorted(Comparator.reverseOrder()).forEach(pathConsumer);
927             } catch (IOException e) {
928                 fail(e);
929             }
930         }
931     }
932 
assertDeviceFileExists(String s)933     private void assertDeviceFileExists(String s) throws DeviceNotAvailableException {
934         assertTrue(getDevice().doesFileExist(s));
935     }
936 
assertDevicePathExists(PathPair path)937     private void assertDevicePathExists(PathPair path) throws DeviceNotAvailableException {
938         assertDeviceFileExists(path.devicePath);
939     }
940 
assertDeviceDirContainsDistro(PathPair distroPath, byte[] expectedDistroBytes)941     private void assertDeviceDirContainsDistro(PathPair distroPath, byte[] expectedDistroBytes)
942             throws Exception {
943         // Pull back just the version file and compare it.
944         File localFile = mTestRootDir.createSubPath("temp.file").hostFile();
945         try {
946             String remoteVersionFile = distroPath.devicePath + "/"
947                     + TimeZoneDistro.DISTRO_VERSION_FILE_NAME;
948             assertTrue("Could not pull file " + remoteVersionFile + " to " + localFile,
949                     getDevice().pullFile(remoteVersionFile, localFile));
950 
951             byte[] bytes = Files.readAllBytes(localFile.toPath());
952             assertArrayEquals(bytes,
953                     new TimeZoneDistro(expectedDistroBytes).getDistroVersion().toBytes());
954         } finally {
955             localFile.delete();
956         }
957     }
958 
assertDevicePathDoesNotExist(PathPair path)959     private void assertDevicePathDoesNotExist(PathPair path) throws DeviceNotAvailableException {
960         assertFalse(getDevice().doesFileExist(path.devicePath));
961     }
962 
assertDevicePathIsFile(PathPair path)963     private void assertDevicePathIsFile(PathPair path) throws DeviceNotAvailableException {
964         // This check cannot rely on getDevice().getFile(devicePath).isDirectory() here because that
965         // requires that the user has rights to list all files beneath each and every directory in
966         // the path. That is not the case for the shell user and the /data and /data/local
967         // directories. http://b/35753041.
968         String output = executeCommandOnDeviceRaw("stat -c %F " + path.devicePath);
969         assertTrue(path.devicePath + " not a file. Received: " + output,
970                 output.startsWith("regular") && output.endsWith("file\n"));
971     }
972 
deviceFail(Exception e)973     private static DeviceNotAvailableException deviceFail(Exception e)
974             throws DeviceNotAvailableException {
975         if (e instanceof DeviceNotAvailableException) {
976             throw (DeviceNotAvailableException) e;
977         }
978         fail(e);
979         return null;
980     }
981 
fail(Exception e)982     private static void fail(Exception e) {
983         e.printStackTrace();
984         fail(e.getMessage());
985     }
986 
987     /** A path that has equivalents on both host and device. */
988     private static class PathPair {
989         private final Path hostPath;
990         private final String devicePath;
991 
PathPair(Path hostPath, String devicePath)992         PathPair(Path hostPath, String devicePath) {
993             this.hostPath = hostPath;
994             this.devicePath = devicePath;
995         }
996 
hostFile()997         File hostFile() {
998             return hostPath.toFile();
999         }
1000 
createSubPath(String s)1001         PathPair createSubPath(String s) {
1002             return new PathPair(hostPath.resolve(s), devicePath + "/" + s);
1003         }
1004     }
1005 }
1006