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 */ 16package com.android.timezone.distro.installer; 17 18import com.android.timezone.distro.DistroVersion; 19import com.android.timezone.distro.FileUtils; 20import com.android.timezone.distro.StagedDistroOperation; 21import com.android.timezone.distro.TimeZoneDistro; 22import com.android.timezone.distro.tools.TimeZoneDistroBuilder; 23 24import junit.framework.TestCase; 25 26import java.io.ByteArrayOutputStream; 27import java.io.File; 28import java.io.FileOutputStream; 29import java.io.IOException; 30import java.nio.file.FileVisitResult; 31import java.nio.file.FileVisitor; 32import java.nio.file.Files; 33import java.nio.file.Path; 34import java.nio.file.SimpleFileVisitor; 35import java.nio.file.attribute.BasicFileAttributes; 36import java.util.ArrayList; 37import java.util.List; 38import java.util.zip.ZipEntry; 39import java.util.zip.ZipOutputStream; 40import libcore.io.IoUtils; 41import libcore.tzdata.testing.ZoneInfoTestHelper; 42 43import static org.junit.Assert.assertArrayEquals; 44 45/** 46 * Tests for {@link TimeZoneDistroInstaller}. 47 */ 48public 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(2, 1) 336 .buildUnvalidatedBytes(); 337 assertEquals( 338 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION, 339 installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes))); 340 assertNoDistroOperationStaged(); 341 assertNoInstalledDistro(); 342 } 343 344 /** 345 * Tests that a distro with a badly formed distro version will be rejected. 346 */ 347 public void testStageInstallWithErrorCode_withBadlyFormedDistroVersion() throws Exception { 348 // Create a distro that has an invalid major distro version. It should be 3 numeric 349 // characters, "." and 3 more numeric characters. 350 DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1); 351 byte[] invalidFormatVersionBytes = validDistroVersion.toBytes(); 352 invalidFormatVersionBytes[0] = 'A'; 353 354 TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidFormatVersionBytes); 355 assertEquals( 356 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, 357 installer.stageInstallWithErrorCode(distro)); 358 assertNoDistroOperationStaged(); 359 assertNoInstalledDistro(); 360 } 361 362 /** 363 * Tests that a distro with a badly formed revision will be rejected. 364 */ 365 public void testStageInstallWithErrorCode_withBadlyFormedRevision() throws Exception { 366 // Create a distro that has an invalid revision. It should be 3 numeric characters. 367 DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1); 368 byte[] invalidRevisionBytes = validDistroVersion.toBytes(); 369 invalidRevisionBytes[invalidRevisionBytes.length - 3] = 'A'; 370 371 TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRevisionBytes); 372 assertEquals( 373 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, 374 installer.stageInstallWithErrorCode(distro)); 375 assertNoDistroOperationStaged(); 376 assertNoInstalledDistro(); 377 } 378 379 /** 380 * Tests that a distro with a badly formed rules version will be rejected. 381 */ 382 public void testStageInstallWithErrorCode_withBadlyFormedRulesVersion() throws Exception { 383 // Create a distro that has an invalid rules version. It should be in the form "2016c". 384 DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1); 385 byte[] invalidRulesVersionBytes = validDistroVersion.toBytes(); 386 invalidRulesVersionBytes[invalidRulesVersionBytes.length - 6] = 'B'; 387 388 TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRulesVersionBytes); 389 assertEquals( 390 TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE, 391 installer.stageInstallWithErrorCode(distro)); 392 assertNoDistroOperationStaged(); 393 assertNoInstalledDistro(); 394 } 395 396 /** Tests what happens if a stageUninstall() is attempted when there's nothing installed. */ 397 public void testStageUninstall_noExistingDistro() throws Exception { 398 // To stage an uninstall, there would need to be installed rules. 399 assertEquals( 400 TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED, 401 installer.stageUninstall()); 402 403 assertNoDistroOperationStaged(); 404 assertNoInstalledDistro(); 405 } 406 407 /** Tests what happens if a stageUninstall() is attempted when there's something installed. */ 408 public void testStageUninstall_existingInstalledDataDistro() throws Exception { 409 // To stage an uninstall, we need to have some installed rules. 410 byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); 411 simulateInstalledDistro(installedDistroBytes); 412 413 File stagedDataDir = installer.getStagedTzDataDir(); 414 assertTrue(stagedDataDir.mkdir()); 415 416 assertEquals( 417 TimeZoneDistroInstaller.UNINSTALL_SUCCESS, 418 installer.stageUninstall()); 419 assertDistroUninstallStaged(); 420 assertInstalledDistro(installedDistroBytes); 421 } 422 423 /** 424 * Tests what happens if a stageUninstall() is attempted when there's something installed 425 * and there's a staged install. 426 */ 427 public void testStageUninstall_existingStagedInstall() throws Exception { 428 File stagedDataDir = installer.getStagedTzDataDir(); 429 assertTrue(stagedDataDir.mkdir()); 430 431 // Stage an install. 432 byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); 433 assertEquals(TimeZoneDistroInstaller.INSTALL_SUCCESS, 434 installer.stageInstallWithErrorCode(new TimeZoneDistro(distroBytes))); 435 436 // Now uninstall. It should just remove the staged install. 437 assertEquals( 438 TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED, 439 installer.stageUninstall()); 440 assertNoDistroOperationStaged(); 441 } 442 443 /** 444 * Tests what happens if a stageUninstall() is attempted when there's something installed 445 * and there's a staged uninstall. 446 */ 447 public void testStageUninstall_existingStagedUninstall() throws Exception { 448 // To stage an uninstall, we need to have some installed rules. 449 byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); 450 simulateInstalledDistro(installedDistroBytes); 451 452 File stagedDataDir = installer.getStagedTzDataDir(); 453 assertTrue(stagedDataDir.mkdir()); 454 455 assertEquals( 456 TimeZoneDistroInstaller.UNINSTALL_SUCCESS, 457 installer.stageUninstall()); 458 assertDistroUninstallStaged(); 459 assertInstalledDistro(installedDistroBytes); 460 461 // Now stage a second uninstall. 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 are unexpected working 471 * directories present. 472 */ 473 public void testStageUninstall_oldDirsAlreadyExists() throws Exception { 474 // To stage an uninstall, we need to have some installed rules. 475 byte[] installedDistroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); 476 simulateInstalledDistro(installedDistroBytes); 477 478 File oldStagedDataDir = installer.getOldStagedDataDir(); 479 assertTrue(oldStagedDataDir.mkdir()); 480 481 File workingDir = installer.getWorkingDir(); 482 assertTrue(workingDir.mkdir()); 483 484 assertEquals( 485 TimeZoneDistroInstaller.UNINSTALL_SUCCESS, 486 installer.stageUninstall()); 487 488 assertDistroUninstallStaged(); 489 assertFalse(workingDir.exists()); 490 assertFalse(oldStagedDataDir.exists()); 491 assertInstalledDistro(installedDistroBytes); 492 } 493 494 public void testGetSystemRulesVersion() throws Exception { 495 assertEquals(SYSTEM_RULES_VERSION, installer.getSystemRulesVersion()); 496 } 497 498 public void testGetInstalledDistroVersion() throws Exception { 499 // Check result when nothing installed. 500 assertNull(installer.getInstalledDistroVersion()); 501 assertNoDistroOperationStaged(); 502 assertNoInstalledDistro(); 503 504 // Now simulate there being an existing install active. 505 byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); 506 simulateInstalledDistro(distroBytes); 507 assertInstalledDistro(distroBytes); 508 509 // Check result when something installed. 510 assertEquals(new TimeZoneDistro(distroBytes).getDistroVersion(), 511 installer.getInstalledDistroVersion()); 512 assertNoDistroOperationStaged(); 513 assertInstalledDistro(distroBytes); 514 } 515 516 public void testGetStagedDistroOperation() throws Exception { 517 byte[] distro1Bytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); 518 byte[] distro2Bytes = createValidTimeZoneDistroBytes(NEWER_RULES_VERSION, 1); 519 520 // Check result when nothing staged. 521 assertNull(installer.getStagedDistroOperation()); 522 assertNoDistroOperationStaged(); 523 assertNoInstalledDistro(); 524 525 // Check result after unsuccessfully staging an uninstall. 526 // Can't stage an uninstall without an installed distro. 527 assertEquals( 528 TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED, 529 installer.stageUninstall()); 530 assertNull(installer.getStagedDistroOperation()); 531 assertNoDistroOperationStaged(); 532 assertNoInstalledDistro(); 533 534 // Check result after staging an install. 535 assertEquals( 536 TimeZoneDistroInstaller.INSTALL_SUCCESS, 537 installer.stageInstallWithErrorCode(new TimeZoneDistro(distro1Bytes))); 538 StagedDistroOperation expectedStagedInstall = 539 StagedDistroOperation.install(new TimeZoneDistro(distro1Bytes).getDistroVersion()); 540 assertEquals(expectedStagedInstall, installer.getStagedDistroOperation()); 541 assertInstallDistroStaged(distro1Bytes); 542 assertNoInstalledDistro(); 543 544 // Check result after unsuccessfully staging an uninstall (but after removing a staged 545 // install). Can't stage an uninstall without an installed distro. 546 assertEquals( 547 TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED, 548 installer.stageUninstall()); 549 assertNull(installer.getStagedDistroOperation()); 550 assertNoDistroOperationStaged(); 551 assertNoInstalledDistro(); 552 553 // Now simulate there being an existing install active. 554 simulateInstalledDistro(distro1Bytes); 555 assertInstalledDistro(distro1Bytes); 556 557 // Check state after successfully staging an uninstall. 558 assertEquals( 559 TimeZoneDistroInstaller.UNINSTALL_SUCCESS, 560 installer.stageUninstall()); 561 StagedDistroOperation expectedStagedUninstall = StagedDistroOperation.uninstall(); 562 assertEquals(expectedStagedUninstall, installer.getStagedDistroOperation()); 563 assertDistroUninstallStaged(); 564 assertInstalledDistro(distro1Bytes); 565 566 // Check state after successfully staging an install. 567 assertEquals(TimeZoneDistroInstaller.INSTALL_SUCCESS, 568 installer.stageInstallWithErrorCode(new TimeZoneDistro(distro2Bytes))); 569 StagedDistroOperation expectedStagedInstall2 = 570 StagedDistroOperation.install(new TimeZoneDistro(distro2Bytes).getDistroVersion()); 571 assertEquals(expectedStagedInstall2, installer.getStagedDistroOperation()); 572 assertInstallDistroStaged(distro2Bytes); 573 assertInstalledDistro(distro1Bytes); 574 } 575 576 private static byte[] createValidTimeZoneDistroBytes( 577 String rulesVersion, int revision) throws Exception { 578 return createValidTimeZoneDistroBuilder(rulesVersion, revision).buildBytes(); 579 } 580 581 private static TimeZoneDistroBuilder createValidTimeZoneDistroBuilder( 582 String rulesVersion, int revision) throws Exception { 583 584 byte[] tzData = createTzData(rulesVersion); 585 byte[] icuData = new byte[] { 'a' }; 586 String tzlookupXml = "<timezones>\n" 587 + " <countryzones>\n" 588 + " <country code=\"us\">\n" 589 + " <id>America/New_York\"</id>\n" 590 + " <id>America/Los_Angeles</id>\n" 591 + " </country>\n" 592 + " <country code=\"gb\">\n" 593 + " <id>Europe/London</id>\n" 594 + " </country>\n" 595 + " </countryzones>\n" 596 + "</timezones>\n"; 597 DistroVersion distroVersion = new DistroVersion( 598 DistroVersion.CURRENT_FORMAT_MAJOR_VERSION, 599 DistroVersion.CURRENT_FORMAT_MINOR_VERSION, 600 rulesVersion, 601 revision); 602 return new TimeZoneDistroBuilder() 603 .setDistroVersion(distroVersion) 604 .setTzDataFile(tzData) 605 .setIcuDataFile(icuData) 606 .setTzLookupXml(tzlookupXml); 607 } 608 609 private void assertInstallDistroStaged(byte[] expectedDistroBytes) throws Exception { 610 assertTrue(testInstallDir.exists()); 611 612 File stagedTzDataDir = installer.getStagedTzDataDir(); 613 assertTrue(stagedTzDataDir.exists()); 614 615 File distroVersionFile = 616 new File(stagedTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME); 617 assertTrue(distroVersionFile.exists()); 618 619 File tzdataFile = new File(stagedTzDataDir, TimeZoneDistro.TZDATA_FILE_NAME); 620 assertTrue(tzdataFile.exists()); 621 622 File icuFile = new File(stagedTzDataDir, TimeZoneDistro.ICU_DATA_FILE_NAME); 623 assertTrue(icuFile.exists()); 624 625 File tzLookupFile = new File(stagedTzDataDir, TimeZoneDistro.TZLOOKUP_FILE_NAME); 626 assertTrue(tzLookupFile.exists()); 627 628 // Assert getStagedDistroState() is reporting correctly. 629 StagedDistroOperation stagedDistroOperation = installer.getStagedDistroOperation(); 630 assertNotNull(stagedDistroOperation); 631 assertFalse(stagedDistroOperation.isUninstall); 632 assertEquals(new TimeZoneDistro(expectedDistroBytes).getDistroVersion(), 633 stagedDistroOperation.distroVersion); 634 635 File expectedZipContentDir = createUniqueDirectory(tempDir, "expectedZipContent"); 636 new TimeZoneDistro(expectedDistroBytes).extractTo(expectedZipContentDir); 637 638 assertContentsMatches( 639 new File(expectedZipContentDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME), 640 distroVersionFile); 641 assertContentsMatches( 642 new File(expectedZipContentDir, TimeZoneDistro.ICU_DATA_FILE_NAME), 643 icuFile); 644 assertContentsMatches( 645 new File(expectedZipContentDir, TimeZoneDistro.TZDATA_FILE_NAME), 646 tzdataFile); 647 assertContentsMatches( 648 new File(expectedZipContentDir, TimeZoneDistro.TZLOOKUP_FILE_NAME), 649 tzLookupFile); 650 assertFileCount(4, expectedZipContentDir); 651 652 // Also check no working directory is left lying around. 653 File workingDir = installer.getWorkingDir(); 654 assertFalse(workingDir.exists()); 655 } 656 657 private static void assertFileCount(int expectedFiles, File rootDir) throws Exception { 658 final List<Path> paths = new ArrayList<>(); 659 FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() { 660 @Override 661 public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) 662 throws IOException { 663 paths.add(filePath); 664 return FileVisitResult.CONTINUE; 665 } 666 }; 667 Files.walkFileTree(rootDir.toPath(), visitor); 668 assertEquals("Found: " + paths, expectedFiles, paths.size()); 669 } 670 671 private void assertContentsMatches(File expected, File actual) throws IOException { 672 byte[] actualBytes = IoUtils.readFileAsByteArray(actual.getPath()); 673 byte[] expectedBytes = IoUtils.readFileAsByteArray(expected.getPath()); 674 assertArrayEquals(expectedBytes, actualBytes); 675 } 676 677 private void assertNoDistroOperationStaged() throws Exception { 678 assertNull(installer.getStagedDistroOperation()); 679 680 File stagedTzDataDir = installer.getStagedTzDataDir(); 681 assertFalse(stagedTzDataDir.exists()); 682 683 // Also check no working directories are left lying around. 684 File workingDir = installer.getWorkingDir(); 685 assertFalse(workingDir.exists()); 686 687 File oldDataDir = installer.getOldStagedDataDir(); 688 assertFalse(oldDataDir.exists()); 689 } 690 691 private void assertDistroUninstallStaged() throws Exception { 692 assertEquals(StagedDistroOperation.uninstall(), installer.getStagedDistroOperation()); 693 694 File stagedTzDataDir = installer.getStagedTzDataDir(); 695 assertTrue(stagedTzDataDir.exists()); 696 assertTrue(stagedTzDataDir.isDirectory()); 697 698 File uninstallTombstone = 699 new File(stagedTzDataDir, TimeZoneDistroInstaller.UNINSTALL_TOMBSTONE_FILE_NAME); 700 assertTrue(uninstallTombstone.exists()); 701 assertTrue(uninstallTombstone.isFile()); 702 703 // Also check no working directories are left lying around. 704 File workingDir = installer.getWorkingDir(); 705 assertFalse(workingDir.exists()); 706 707 File oldDataDir = installer.getOldStagedDataDir(); 708 assertFalse(oldDataDir.exists()); 709 } 710 711 private void simulateInstalledDistro(byte[] distroBytes) throws Exception { 712 File currentTzDataDir = installer.getCurrentTzDataDir(); 713 assertFalse(currentTzDataDir.exists()); 714 assertTrue(currentTzDataDir.mkdir()); 715 new TimeZoneDistro(distroBytes).extractTo(currentTzDataDir); 716 } 717 718 private void assertNoInstalledDistro() { 719 assertFalse(installer.getCurrentTzDataDir().exists()); 720 } 721 722 private void assertInstalledDistro(byte[] distroBytes) throws Exception { 723 File currentTzDataDir = installer.getCurrentTzDataDir(); 724 assertTrue(currentTzDataDir.exists()); 725 File versionFile = new File(currentTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME); 726 assertTrue(versionFile.exists()); 727 byte[] expectedVersionBytes = new TimeZoneDistro(distroBytes).getDistroVersion().toBytes(); 728 byte[] actualVersionBytes = FileUtils.readBytes(versionFile, expectedVersionBytes.length); 729 assertArrayEquals(expectedVersionBytes, actualVersionBytes); 730 } 731 732 private static byte[] createTzData(String rulesVersion) { 733 return new ZoneInfoTestHelper.TzDataBuilder() 734 .initializeToValid() 735 .setHeaderMagic("tzdata" + rulesVersion) 736 .build(); 737 } 738 739 private static void createFile(File file, byte[] bytes) { 740 try (FileOutputStream fos = new FileOutputStream(file)) { 741 fos.write(bytes); 742 } catch (IOException e) { 743 fail(e.getMessage()); 744 } 745 } 746 747 /** 748 * Creates a TimeZoneDistro containing arbitrary bytes in the version file. Used for testing 749 * distros with badly formed version info. 750 */ 751 private TimeZoneDistro createTimeZoneDistroWithVersionBytes(byte[] versionBytes) 752 throws Exception { 753 754 // Extract a valid distro to a working dir. 755 byte[] distroBytes = createValidTimeZoneDistroBytes(NEW_RULES_VERSION, 1); 756 File workingDir = createUniqueDirectory(tempDir, "versionBytes"); 757 new TimeZoneDistro(distroBytes).extractTo(workingDir); 758 759 // Modify the version file. 760 File versionFile = new File(workingDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME); 761 assertTrue(versionFile.exists()); 762 try (FileOutputStream fos = new FileOutputStream(versionFile, false /* append */)) { 763 fos.write(versionBytes); 764 } 765 766 // Zip the distro back up again. 767 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 768 try (ZipOutputStream zos = new ZipOutputStream(baos)) { 769 Path workingDirPath = workingDir.toPath(); 770 Files.walkFileTree(workingDirPath, new SimpleFileVisitor<Path>() { 771 @Override 772 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 773 throws IOException { 774 byte[] bytes = IoUtils.readFileAsByteArray(file.toString()); 775 String relativeFileName = workingDirPath.relativize(file).toString(); 776 addZipEntry(zos, relativeFileName, bytes); 777 return FileVisitResult.CONTINUE; 778 } 779 }); 780 } 781 782 return new TimeZoneDistro(baos.toByteArray()); 783 } 784 785 private static void addZipEntry(ZipOutputStream zos, String name, byte[] content) 786 throws IOException { 787 ZipEntry zipEntry = new ZipEntry(name); 788 zipEntry.setSize(content.length); 789 zos.putNextEntry(zipEntry); 790 zos.write(content); 791 zos.closeEntry(); 792 } 793} 794