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