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