Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2013 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 libcore.libcore.util;
     18 
     19 import java.io.File;
     20 import java.io.FileOutputStream;
     21 import java.io.IOException;
     22 import java.io.RandomAccessFile;
     23 
     24 import libcore.tzdata.testing.ZoneInfoTestHelper;
     25 import libcore.util.TimeZoneDataFiles;
     26 import libcore.util.ZoneInfo;
     27 import libcore.util.ZoneInfoDB;
     28 
     29 import static libcore.util.ZoneInfoDB.TzData.SIZEOF_INDEX_ENTRY;
     30 
     31 public class ZoneInfoDBTest extends junit.framework.TestCase {
     32 
     33   // The base tzdata file, always present on a device.
     34   private static final String SYSTEM_TZDATA_FILE =
     35       TimeZoneDataFiles.getSystemTimeZoneFile(ZoneInfoDB.TZDATA_FILE);
     36 
     37   // An empty override file should fall back to the default file.
     38   public void testLoadTzDataWithFallback_emptyOverrideFile() throws Exception {
     39     String emptyFilePath = makeEmptyFile().getPath();
     40     try (ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
     41          ZoneInfoDB.TzData dataWithEmptyOverride =
     42                  ZoneInfoDB.TzData.loadTzDataWithFallback(emptyFilePath, SYSTEM_TZDATA_FILE)) {
     43       assertEquals(data.getVersion(), dataWithEmptyOverride.getVersion());
     44       assertEquals(data.getAvailableIDs().length, dataWithEmptyOverride.getAvailableIDs().length);
     45     }
     46   }
     47 
     48   // A corrupt override file should fall back to the default file.
     49   public void testLoadTzDataWithFallback_corruptOverrideFile() throws Exception {
     50     String corruptFilePath = makeCorruptFile().getPath();
     51     try (ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
     52          ZoneInfoDB.TzData dataWithCorruptOverride =
     53                  ZoneInfoDB.TzData.loadTzDataWithFallback(corruptFilePath, SYSTEM_TZDATA_FILE)) {
     54       assertEquals(data.getVersion(), dataWithCorruptOverride.getVersion());
     55       assertEquals(data.getAvailableIDs().length, dataWithCorruptOverride.getAvailableIDs().length);
     56     }
     57   }
     58 
     59   // Given no tzdata files we can use, we should fall back to built-in "GMT".
     60   public void testLoadTzDataWithFallback_noGoodFile() throws Exception {
     61     String emptyFilePath = makeEmptyFile().getPath();
     62     try (ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzDataWithFallback(emptyFilePath)) {
     63       assertEquals("missing", data.getVersion());
     64       assertEquals(1, data.getAvailableIDs().length);
     65       assertEquals("GMT", data.getAvailableIDs()[0]);
     66     }
     67   }
     68 
     69   // Given a valid override file, we should find ourselves using that.
     70   public void testLoadTzDataWithFallback_goodOverrideFile() throws Exception {
     71     RandomAccessFile in = new RandomAccessFile(SYSTEM_TZDATA_FILE, "r");
     72     byte[] content = new byte[(int) in.length()];
     73     in.readFully(content);
     74     in.close();
     75 
     76     // Bump the version number to one long past where humans will be extinct.
     77     content[6] = '9';
     78     content[7] = '9';
     79     content[8] = '9';
     80     content[9] = '9';
     81     content[10] = 'z';
     82 
     83     File goodFile = makeTemporaryFile(content);
     84     try (ZoneInfoDB.TzData dataWithOverride =
     85               ZoneInfoDB.TzData.loadTzDataWithFallback(goodFile.getPath(), SYSTEM_TZDATA_FILE);
     86          ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE)) {
     87 
     88       assertEquals("9999z", dataWithOverride.getVersion());
     89       assertEquals(data.getAvailableIDs().length, dataWithOverride.getAvailableIDs().length);
     90     } finally {
     91       goodFile.delete();
     92     }
     93   }
     94 
     95   public void testLoadTzData_badHeader() throws Exception {
     96     RandomAccessFile in = new RandomAccessFile(SYSTEM_TZDATA_FILE, "r");
     97     byte[] content = new byte[(int) in.length()];
     98     in.readFully(content);
     99     in.close();
    100 
    101     // Break the header.
    102     content[0] = 'a';
    103     checkInvalidDataDetected(content);
    104   }
    105 
    106   public void testLoadTzData_validTestData() throws Exception {
    107     byte[] data = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid().build();
    108     File testFile = makeTemporaryFile(data);
    109     try {
    110       assertNotNull(ZoneInfoDB.TzData.loadTzData(testFile.getPath()));
    111     } finally {
    112       testFile.delete();
    113     }
    114   }
    115 
    116   public void testLoadTzData_invalidOffsets() throws Exception {
    117     ZoneInfoTestHelper.TzDataBuilder builder =
    118             new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
    119 
    120     // Sections must be in the correct order: section sizing is calculated using them.
    121     builder.setIndexOffsetOverride(10);
    122     builder.setDataOffsetOverride(30);
    123 
    124     byte[] data = builder.build();
    125     // The offsets must all be under the total size of the file for this test to be valid.
    126     assertTrue(30 < data.length);
    127     checkInvalidDataDetected(data);
    128   }
    129 
    130   public void testLoadTzData_zoneTabOutsideFile() throws Exception {
    131     ZoneInfoTestHelper.TzDataBuilder builder =
    132             new ZoneInfoTestHelper.TzDataBuilder()
    133                     .initializeToValid();
    134 
    135     // Sections must be in the correct order: section sizing is calculated using them.
    136     builder.setIndexOffsetOverride(10);
    137     builder.setDataOffsetOverride(10 + SIZEOF_INDEX_ENTRY);
    138     builder.setZoneTabOffsetOverride(3000); // This is invalid if it is outside of the file.
    139 
    140     byte[] data = builder.build();
    141     // The zoneTab offset must be outside of the file for this test to be valid.
    142     assertTrue(3000 > data.length);
    143     checkInvalidDataDetected(data);
    144   }
    145 
    146   public void testLoadTzData_nonDivisibleIndex() throws Exception {
    147     ZoneInfoTestHelper.TzDataBuilder builder =
    148             new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
    149 
    150     // Sections must be in the correct order: section sizing is calculated using them.
    151     int indexOffset = 10;
    152     builder.setIndexOffsetOverride(indexOffset);
    153     int dataOffset = indexOffset + ZoneInfoDB.TzData.SIZEOF_INDEX_ENTRY - 1;
    154     builder.setDataOffsetOverride(dataOffset);
    155     builder.setZoneTabOffsetOverride(dataOffset + 40);
    156 
    157     byte[] data = builder.build();
    158     // The zoneTab offset must be outside of the file for this test to be valid.
    159     assertTrue(3000 > data.length);
    160     checkInvalidDataDetected(data);
    161   }
    162 
    163   public void testLoadTzData_badId() throws Exception {
    164     ZoneInfoTestHelper.TzDataBuilder builder =
    165             new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
    166     builder.clearZicData();
    167     byte[] validZicData =
    168             new ZoneInfoTestHelper.ZicDataBuilder().initializeToValid().build();
    169     builder.addZicData("", validZicData); // "" is an invalid ID
    170 
    171     checkInvalidDataDetected(builder.build());
    172   }
    173 
    174   public void testLoadTzData_badIdOrder() throws Exception {
    175     ZoneInfoTestHelper.TzDataBuilder builder =
    176             new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
    177     builder.clearZicData();
    178     byte[] validZicData =
    179             new ZoneInfoTestHelper.ZicDataBuilder().initializeToValid().build();
    180     builder.addZicData("Europe/Zurich", validZicData);
    181     builder.addZicData("Europe/London", validZicData);
    182 
    183     checkInvalidDataDetected(builder.build());
    184   }
    185 
    186   public void testLoadTzData_duplicateId() throws Exception {
    187     ZoneInfoTestHelper.TzDataBuilder builder =
    188             new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
    189     builder.clearZicData();
    190     byte[] validZicData =
    191             new ZoneInfoTestHelper.ZicDataBuilder().initializeToValid().build();
    192     builder.addZicData("Europe/London", validZicData);
    193     builder.addZicData("Europe/London", validZicData);
    194 
    195     checkInvalidDataDetected(builder.build());
    196   }
    197 
    198   public void testLoadTzData_badZicLength() throws Exception {
    199     ZoneInfoTestHelper.TzDataBuilder builder =
    200             new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
    201     builder.clearZicData();
    202     byte[] invalidZicData = "This is too short".getBytes();
    203     builder.addZicData("Europe/London", invalidZicData);
    204 
    205     checkInvalidDataDetected(builder.build());
    206   }
    207 
    208   private static void checkInvalidDataDetected(byte[] data) throws Exception {
    209     File testFile = makeTemporaryFile(data);
    210     try {
    211       assertNull(ZoneInfoDB.TzData.loadTzData(testFile.getPath()));
    212     } finally {
    213       testFile.delete();
    214     }
    215   }
    216 
    217   // Confirms any caching that exists correctly handles TimeZone mutability.
    218   public void testMakeTimeZone_timeZoneMutability() throws Exception {
    219     try (ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE)) {
    220       String tzId = "Europe/London";
    221       ZoneInfo first = data.makeTimeZone(tzId);
    222       ZoneInfo second = data.makeTimeZone(tzId);
    223       assertNotSame(first, second);
    224 
    225       assertTrue(first.hasSameRules(second));
    226 
    227       first.setID("Not Europe/London");
    228 
    229       assertFalse(first.getID().equals(second.getID()));
    230 
    231       first.setRawOffset(3600);
    232       assertFalse(first.getRawOffset() == second.getRawOffset());
    233     }
    234   }
    235 
    236   public void testMakeTimeZone_notFound() throws Exception {
    237     try (ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE)) {
    238       assertNull(data.makeTimeZone("THIS_TZ_DOES_NOT_EXIST"));
    239       assertFalse(data.hasTimeZone("THIS_TZ_DOES_NOT_EXIST"));
    240     }
    241   }
    242 
    243   public void testMakeTimeZone_found() throws Exception {
    244     try (ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE)) {
    245       assertNotNull(data.makeTimeZone("Europe/London"));
    246       assertTrue(data.hasTimeZone("Europe/London"));
    247     }
    248   }
    249 
    250   public void testGetRulesVersion() throws Exception {
    251     try (ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE)) {
    252       String rulesVersion = ZoneInfoDB.TzData.getRulesVersion(new File(SYSTEM_TZDATA_FILE));
    253       assertEquals(data.getVersion(), rulesVersion);
    254     }
    255   }
    256 
    257   public void testGetRulesVersion_corruptFile() throws Exception {
    258     File corruptFilePath = makeCorruptFile();
    259     try {
    260       ZoneInfoDB.TzData.getRulesVersion(corruptFilePath);
    261       fail();
    262     } catch (IOException expected) {
    263     }
    264   }
    265 
    266   public void testGetRulesVersion_emptyFile() throws Exception {
    267     File emptyFilePath = makeEmptyFile();
    268     try {
    269       ZoneInfoDB.TzData.getRulesVersion(emptyFilePath);
    270       fail();
    271     } catch (IOException expected) {
    272     }
    273   }
    274 
    275   public void testGetRulesVersion_missingFile() throws Exception {
    276     File missingFile = makeMissingFile();
    277     try {
    278       ZoneInfoDB.TzData.getRulesVersion(missingFile);
    279       fail();
    280     } catch (IOException expected) {
    281     }
    282   }
    283 
    284   private static File makeMissingFile() throws Exception {
    285     File file = File.createTempFile("temp-", ".txt");
    286     assertTrue(file.delete());
    287     assertFalse(file.exists());
    288     return file;
    289   }
    290 
    291   private static File makeCorruptFile() throws Exception {
    292     return makeTemporaryFile("invalid content".getBytes());
    293   }
    294 
    295   private static File makeEmptyFile() throws Exception {
    296     return makeTemporaryFile(new byte[0]);
    297   }
    298 
    299   private static File makeTemporaryFile(byte[] content) throws Exception {
    300     File f = File.createTempFile("temp-", ".txt");
    301     FileOutputStream fos = new FileOutputStream(f);
    302     fos.write(content);
    303     fos.close();
    304     return f;
    305   }
    306 }
    307