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