1 // Copyright 2016 Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.archivepatcher.applier.bsdiff; 16 17 import com.google.archivepatcher.applier.PatchFormatException; 18 19 import org.junit.After; 20 import org.junit.Assert; 21 import org.junit.Before; 22 import org.junit.Test; 23 import org.junit.runner.RunWith; 24 import org.junit.runners.JUnit4; 25 26 import java.io.ByteArrayInputStream; 27 import java.io.ByteArrayOutputStream; 28 import java.io.File; 29 import java.io.FileOutputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.OutputStream; 33 import java.io.RandomAccessFile; 34 35 /** 36 * Tests for {@link BsPatch}. 37 */ 38 @RunWith(JUnit4.class) 39 public class BsPatchTest { 40 41 private static final String SIGNATURE = "ENDSLEY/BSDIFF43"; 42 private byte[] buffer1; 43 private byte[] buffer2; 44 45 /** 46 * The tests need access to an actual File object for the "old file", so that it can be used as 47 * the argument to a RandomAccessFile constructor... but the old file is a resource loaded at test 48 * run-time, potentially from a JAR, and therefore a copy must be made in the filesystem to access 49 * via RandomAccessFile. This is not true for the new file or the patch file, both of which are 50 * streamable. 51 */ 52 private File oldFile; 53 54 @Before setUp()55 public void setUp() throws IOException { 56 buffer1 = new byte[6]; 57 buffer2 = new byte[6]; 58 try { 59 oldFile = File.createTempFile("archive_patcher", "old"); 60 oldFile.deleteOnExit(); 61 } catch (IOException e) { 62 if (oldFile != null) { 63 oldFile.delete(); 64 } 65 throw e; 66 } 67 } 68 69 @After tearDown()70 public void tearDown() { 71 if (oldFile != null) { 72 oldFile.delete(); 73 } 74 oldFile = null; 75 } 76 77 @Test testTransformBytes()78 public void testTransformBytes() throws IOException { 79 // In this case the "patch stream" is just a stream of addends that transformBytes(...) will 80 // apply to the old data file. 81 final byte[] patchInput = "this is a sample string to read".getBytes("US-ASCII"); 82 final ByteArrayInputStream patchInputStream = new ByteArrayInputStream(patchInput); 83 copyToOldFile("bsdifftest_partial_a.txt"); 84 RandomAccessFile oldData = new RandomAccessFile(oldFile, "r"); 85 final byte[] expectedNewData = readTestData("bsdifftest_partial_b.bin"); 86 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 87 BsPatch.transformBytes(patchInput.length, patchInputStream, oldData, newData, buffer1, buffer2); 88 byte[] actual = newData.toByteArray(); 89 Assert.assertArrayEquals(expectedNewData, actual); 90 } 91 92 @Test testTransformBytes_Error_NotEnoughBytes()93 public void testTransformBytes_Error_NotEnoughBytes() throws IOException { 94 // This test sets up a trivial 1-byte "patch" (addends) stream but then asks 95 // transformBytes(...) to apply *2* bytes, which should fail when it hits EOF. 96 final InputStream patchIn = new ByteArrayInputStream(new byte[] {(byte) 0x00}); 97 copyToOldFile("bsdifftest_partial_a.txt"); // Any file would work here 98 RandomAccessFile oldData = new RandomAccessFile(oldFile, "r"); 99 try { 100 BsPatch.transformBytes(2, patchIn, oldData, new ByteArrayOutputStream(), buffer1, buffer2); 101 Assert.fail("Read past EOF"); 102 } catch (IOException expected) { 103 // Pass 104 } 105 } 106 107 @Test testTransformBytes_Error_JunkPatch()108 public void testTransformBytes_Error_JunkPatch() throws IOException { 109 final byte[] patchInput = "this is a second sample string to read".getBytes("US-ASCII"); 110 final ByteArrayInputStream patchInputStream = new ByteArrayInputStream(patchInput); 111 copyToOldFile("bsdifftest_partial_a.txt"); // Any file would work here 112 RandomAccessFile oldData = new RandomAccessFile(oldFile, "r"); 113 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 114 try { 115 BsPatch.transformBytes( 116 patchInput.length, patchInputStream, oldData, newData, buffer1, buffer2); 117 Assert.fail("Should have thrown an IOException"); 118 } catch (IOException expected) { 119 // Pass 120 } 121 } 122 123 @Test testTransformBytes_Error_JunkPatch_Underflow()124 public void testTransformBytes_Error_JunkPatch_Underflow() throws IOException { 125 final byte[] patchInput = "this is a sample string".getBytes("US-ASCII"); 126 final ByteArrayInputStream patchInputStream = new ByteArrayInputStream(patchInput); 127 copyToOldFile("bsdifftest_partial_a.txt"); 128 RandomAccessFile oldData = new RandomAccessFile(oldFile, "r"); 129 final byte[] buffer1 = new byte[6]; 130 final byte[] buffer2 = new byte[6]; 131 132 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 133 try { 134 BsPatch.transformBytes( 135 patchInput.length + 1, patchInputStream, oldData, newData, buffer1, buffer2); 136 Assert.fail("Should have thrown an IOException"); 137 } catch (IOException expected) { 138 // Pass 139 } 140 } 141 142 @Test testApplyPatch_ContrivedData()143 public void testApplyPatch_ContrivedData() throws Exception { 144 invokeApplyPatch( 145 "bsdifftest_internal_blob_a.bin", 146 "bsdifftest_internal_patch_a_to_b.bin", 147 "bsdifftest_internal_blob_b.bin"); 148 } 149 150 @Test testApplyPatch_BetterData()151 public void testApplyPatch_BetterData() throws Exception { 152 invokeApplyPatch( 153 "bsdifftest_minimal_blob_a.bin", 154 "bsdifftest_minimal_patch_a_to_b.bin", 155 "bsdifftest_minimal_blob_b.bin"); 156 } 157 158 @Test testApplyPatch_BadSignature()159 public void testApplyPatch_BadSignature() throws Exception { 160 createEmptyOldFile(10); 161 String junkSignature = "WOOOOOO/BSDIFF43"; // Correct length, wrong content 162 InputStream patchIn = 163 makePatch( 164 junkSignature, 165 10, // newLength 166 10, // diffSegmentLength 167 0, // copySegmentLength 168 0, // offsetToNextInput 169 new byte[10] // addends 170 ); 171 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 172 try { 173 BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); 174 Assert.fail("Read patch with bad signature"); 175 } catch (PatchFormatException expected) { 176 // No way to mock the internal logic, so resort to testing exception string for coverage 177 String actual = expected.getMessage(); 178 Assert.assertEquals("bad signature", actual); 179 } 180 } 181 182 @Test testApplyPatch_NewLengthNegative()183 public void testApplyPatch_NewLengthNegative() throws Exception { 184 createEmptyOldFile(10); 185 InputStream patchIn = 186 makePatch( 187 SIGNATURE, 188 -10, // newLength (illegal) 189 10, // diffSegmentLength 190 0, // copySegmentLength 191 0, // offsetToNextInput 192 new byte[10] // addends 193 ); 194 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 195 try { 196 BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); 197 Assert.fail("Read patch with negative newLength"); 198 } catch (PatchFormatException expected) { 199 // No way to mock the internal logic, so resort to testing exception string for coverage 200 String actual = expected.getMessage(); 201 Assert.assertEquals("bad newSize", actual); 202 } 203 } 204 205 @Test testApplyPatch_NewLengthTooLarge()206 public void testApplyPatch_NewLengthTooLarge() throws Exception { 207 createEmptyOldFile(10); 208 InputStream patchIn = 209 makePatch( 210 SIGNATURE, 211 Integer.MAX_VALUE + 1, // newLength (max supported is Integer.MAX_VALUE) 212 10, // diffSegmentLength 213 0, // copySegmentLength 214 0, // offsetToNextInput 215 new byte[10] // addends 216 ); 217 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 218 try { 219 BsPatch.applyPatch( 220 new RandomAccessFile(oldFile, "r"), newData, patchIn); 221 Assert.fail("Read patch with excessive newLength"); 222 } catch (PatchFormatException expected) { 223 // No way to mock the internal logic, so resort to testing exception string for coverage 224 String actual = expected.getMessage(); 225 Assert.assertEquals("bad newSize", actual); 226 } 227 } 228 229 @Test testApplyPatch_DiffSegmentLengthNegative()230 public void testApplyPatch_DiffSegmentLengthNegative() throws Exception { 231 createEmptyOldFile(10); 232 InputStream patchIn = 233 makePatch( 234 SIGNATURE, 235 10, // newLength 236 -10, // diffSegmentLength (negative) 237 0, // copySegmentLength 238 0, // offsetToNextInput 239 new byte[10] // addends 240 ); 241 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 242 try { 243 BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); 244 Assert.fail("Read patch with negative diffSegmentLength"); 245 } catch (PatchFormatException expected) { 246 // No way to mock the internal logic, so resort to testing exception string for coverage 247 String actual = expected.getMessage(); 248 Assert.assertEquals("bad diffSegmentLength", actual); 249 } 250 } 251 252 @Test testApplyPatch_DiffSegmentLengthTooLarge()253 public void testApplyPatch_DiffSegmentLengthTooLarge() throws Exception { 254 createEmptyOldFile(10); 255 InputStream patchIn = 256 makePatch( 257 SIGNATURE, 258 10, // newLength 259 Integer.MAX_VALUE + 1, // diffSegmentLength (too big) 260 0, // copySegmentLength 261 0, // offsetToNextInput 262 new byte[10] // addends 263 ); 264 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 265 try { 266 BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); 267 Assert.fail("Read patch with excessive diffSegmentLength"); 268 } catch (PatchFormatException expected) { 269 // No way to mock the internal logic, so resort to testing exception string for coverage 270 String actual = expected.getMessage(); 271 Assert.assertEquals("bad diffSegmentLength", actual); 272 } 273 } 274 275 @Test testApplyPatch_CopySegmentLengthNegative()276 public void testApplyPatch_CopySegmentLengthNegative() throws Exception { 277 createEmptyOldFile(10); 278 InputStream patchIn = 279 makePatch( 280 SIGNATURE, 281 10, // newLength 282 10, // diffSegmentLength 283 -10, // copySegmentLength (negative) 284 0, // offsetToNextInput 285 new byte[10] // addends 286 ); 287 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 288 try { 289 BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); 290 Assert.fail("Read patch with negative copySegmentLength"); 291 } catch (PatchFormatException expected) { 292 // No way to mock the internal logic, so resort to testing exception string for coverage 293 String actual = expected.getMessage(); 294 Assert.assertEquals("bad copySegmentLength", actual); 295 } 296 } 297 298 @Test testApplyPatch_CopySegmentLengthTooLarge()299 public void testApplyPatch_CopySegmentLengthTooLarge() throws Exception { 300 createEmptyOldFile(10); 301 InputStream patchIn = 302 makePatch( 303 SIGNATURE, 304 10, // newLength 305 0, // diffSegmentLength 306 Integer.MAX_VALUE + 1, // copySegmentLength (too big) 307 0, // offsetToNextInput 308 new byte[10] // addends 309 ); 310 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 311 try { 312 BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); 313 Assert.fail("Read patch with excessive copySegmentLength"); 314 } catch (PatchFormatException expected) { 315 // No way to mock the internal logic, so resort to testing exception string for coverage 316 String actual = expected.getMessage(); 317 Assert.assertEquals("bad copySegmentLength", actual); 318 } 319 } 320 321 // ExpectedFinalNewDataBytesWritten_Negative case is impossible in code, so no need to test 322 // that; just the TooLarge condition. 323 @Test testApplyPatch_ExpectedFinalNewDataBytesWritten_PastEOF()324 public void testApplyPatch_ExpectedFinalNewDataBytesWritten_PastEOF() throws Exception { 325 createEmptyOldFile(10); 326 // Make diffSegmentLength + copySegmentLength > newLength 327 InputStream patchIn = 328 makePatch( 329 SIGNATURE, 330 10, // newLength 331 10, // diffSegmentLength 332 1, // copySegmentLength 333 0, // offsetToNextInput 334 new byte[10] // addends 335 ); 336 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 337 try { 338 BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); 339 Assert.fail("Read patch that moves past EOF in new file"); 340 } catch (PatchFormatException expected) { 341 // No way to mock the internal logic, so resort to testing exception string for coverage 342 String actual = expected.getMessage(); 343 Assert.assertEquals("expectedFinalNewDataBytesWritten too large", actual); 344 } 345 } 346 347 @Test testApplyPatch_ExpectedFinalOldDataOffset_Negative()348 public void testApplyPatch_ExpectedFinalOldDataOffset_Negative() throws Exception { 349 createEmptyOldFile(10); 350 // Make diffSegmentLength + offsetToNextInput < 0 351 InputStream patchIn = 352 makePatch( 353 SIGNATURE, 354 10, // newLength 355 10, // diffSegmentLength 356 0, // copySegmentLength 357 -11, // offsetToNextInput 358 new byte[10] // addends 359 ); 360 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 361 try { 362 BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); 363 Assert.fail("Read patch with that moves to a negative offset in old file"); 364 } catch (PatchFormatException expected) { 365 // No way to mock the internal logic, so resort to testing exception string for coverage 366 String actual = expected.getMessage(); 367 Assert.assertEquals("expectedFinalOldDataOffset is negative", actual); 368 } 369 } 370 371 @Test testApplyPatch_ExpectedFinalOldDataOffset_PastEOF()372 public void testApplyPatch_ExpectedFinalOldDataOffset_PastEOF() throws Exception { 373 createEmptyOldFile(10); 374 // Make diffSegmentLength + offsetToNextInput > oldLength 375 InputStream patchIn = 376 makePatch( 377 SIGNATURE, 378 10, // newLength 379 10, // diffSegmentLength 380 0, // copySegmentLength 381 1, // offsetToNextInput 382 new byte[10] // addends 383 ); 384 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 385 try { 386 BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); 387 Assert.fail("Read patch with that moves past EOF in old file"); 388 } catch (PatchFormatException expected) { 389 // No way to mock the internal logic, so resort to testing exception string for coverage 390 String actual = expected.getMessage(); 391 Assert.assertEquals("expectedFinalOldDataOffset too large", actual); 392 } 393 } 394 395 @Test testApplyPatch_TruncatedSignature()396 public void testApplyPatch_TruncatedSignature() throws Exception { 397 createEmptyOldFile(10); 398 InputStream patchIn = new ByteArrayInputStream("X".getBytes("US-ASCII")); 399 ByteArrayOutputStream newData = new ByteArrayOutputStream(); 400 try { 401 BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); 402 Assert.fail("Read patch with truncated signature"); 403 } catch (PatchFormatException expected) { 404 // No way to mock the internal logic, so resort to testing exception string for coverage 405 String actual = expected.getMessage(); 406 Assert.assertEquals("truncated signature", actual); 407 } 408 } 409 410 @Test testReadBsdiffLong()411 public void testReadBsdiffLong() throws Exception { 412 byte[] data = { 413 (byte) 0x78, 414 (byte) 0x56, 415 (byte) 0x34, 416 (byte) 0x12, 417 (byte) 0, 418 (byte) 0, 419 (byte) 0, 420 (byte) 0, 421 (byte) 0xef, 422 (byte) 0xbe, 423 (byte) 0xad, 424 (byte) 0x0e, 425 (byte) 0, 426 (byte) 0, 427 (byte) 0, 428 (byte) 0 429 }; 430 ByteArrayInputStream inputStream = new ByteArrayInputStream(data); 431 long actual = BsPatch.readBsdiffLong(inputStream); 432 Assert.assertEquals(0x12345678, actual); 433 actual = BsPatch.readBsdiffLong(inputStream); 434 Assert.assertEquals(0x0eadbeef, actual); 435 } 436 437 @Test testReadBsdiffLong_Zero()438 public void testReadBsdiffLong_Zero() throws Exception { 439 long expected = 0x00000000L; 440 long actual = 441 BsPatch.readBsdiffLong( 442 new ByteArrayInputStream( 443 new byte[] { 444 (byte) 0x00, 445 (byte) 0x00, 446 (byte) 0x00, 447 (byte) 0x00, 448 (byte) 0x00, 449 (byte) 0x00, 450 (byte) 0x00, 451 (byte) 0x00 452 })); 453 Assert.assertEquals(expected, actual); 454 } 455 456 @Test testReadBsdiffLong_IntegerMaxValue()457 public void testReadBsdiffLong_IntegerMaxValue() throws Exception { 458 long expected = 0x7fffffffL; 459 long actual = 460 BsPatch.readBsdiffLong( 461 new ByteArrayInputStream( 462 new byte[] { 463 (byte) 0xff, 464 (byte) 0xff, 465 (byte) 0xff, 466 (byte) 0x7f, 467 (byte) 0x00, 468 (byte) 0x00, 469 (byte) 0x00, 470 (byte) 0x00 471 })); 472 Assert.assertEquals(expected, actual); 473 } 474 475 @Test testReadBsdiffLong_IntegerMinValue()476 public void testReadBsdiffLong_IntegerMinValue() throws Exception { 477 long expected = -0x80000000L; 478 long actual = 479 BsPatch.readBsdiffLong( 480 new ByteArrayInputStream( 481 new byte[] { 482 (byte) 0x00, 483 (byte) 0x00, 484 (byte) 0x00, 485 (byte) 0x80, 486 (byte) 0x00, 487 (byte) 0x00, 488 (byte) 0x00, 489 (byte) 0x80 490 })); 491 Assert.assertEquals(expected, actual); 492 } 493 494 @Test testReadBsdiffLong_LongMaxValue()495 public void testReadBsdiffLong_LongMaxValue() throws Exception { 496 long expected = 0x7fffffffffffffffL; 497 long actual = 498 BsPatch.readBsdiffLong( 499 new ByteArrayInputStream( 500 new byte[] { 501 (byte) 0xff, 502 (byte) 0xff, 503 (byte) 0xff, 504 (byte) 0xff, 505 (byte) 0xff, 506 (byte) 0xff, 507 (byte) 0xff, 508 (byte) 0x7f 509 })); 510 Assert.assertEquals(expected, actual); 511 } 512 513 // Can't read Long.MIN_VALUE because the signed-magnitude representation stops at 514 // Long.MIN_VALUE+1. 515 @Test testReadBsdiffLong_LongMinValueIsh()516 public void testReadBsdiffLong_LongMinValueIsh() throws Exception { 517 long expected = -0x7fffffffffffffffL; 518 long actual = 519 BsPatch.readBsdiffLong( 520 new ByteArrayInputStream( 521 new byte[] { 522 (byte) 0xff, 523 (byte) 0xff, 524 (byte) 0xff, 525 (byte) 0xff, 526 (byte) 0xff, 527 (byte) 0xff, 528 (byte) 0xff, 529 (byte) 0xff 530 })); 531 Assert.assertEquals(expected, actual); 532 } 533 534 // This is also Java's Long.MAX_VALUE. 535 @Test testReadBsdiffLong_NegativeZero()536 public void testReadBsdiffLong_NegativeZero() throws Exception { 537 try { 538 BsPatch.readBsdiffLong( 539 new ByteArrayInputStream( 540 new byte[] { 541 (byte) 0x00, 542 (byte) 0x00, 543 (byte) 0x00, 544 (byte) 0x00, 545 (byte) 0x00, 546 (byte) 0x00, 547 (byte) 0x00, 548 (byte) 0x80 549 })); 550 Assert.fail("Tolerated negative zero"); 551 } catch (PatchFormatException expected) { 552 // Pass 553 } 554 } 555 556 @Test testReadFully()557 public void testReadFully() throws IOException { 558 final byte[] input = "this is a sample string to read".getBytes(); 559 final ByteArrayInputStream inputStream = new ByteArrayInputStream(input); 560 final byte[] dst = new byte[50]; 561 562 try { 563 BsPatch.readFully(inputStream, dst, 0, 50); 564 Assert.fail("Should've thrown an IOException"); 565 } catch (IOException expected) { 566 // Pass 567 } 568 569 inputStream.reset(); 570 BsPatch.readFully(inputStream, dst, 0, input.length); 571 Assert.assertTrue(regionEquals(dst, 0, input, 0, input.length)); 572 573 inputStream.reset(); 574 BsPatch.readFully(inputStream, dst, 40, 10); 575 Assert.assertTrue(regionEquals(dst, 40, input, 0, 10)); 576 577 inputStream.reset(); 578 try { 579 BsPatch.readFully(inputStream, dst, 45, 11); 580 Assert.fail("Should've thrown an IndexOutOfBoundsException"); 581 } catch (IndexOutOfBoundsException expected) { 582 // Pass 583 } 584 } 585 586 @Test testPipe()587 public void testPipe() throws IOException { 588 final String inputString = "this is a sample string to read"; 589 final byte[] input = inputString.getBytes("US-ASCII"); 590 final ByteArrayInputStream inputStream = new ByteArrayInputStream(input); 591 final byte[] buffer = new byte[5]; 592 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 593 594 BsPatch.pipe(inputStream, outputStream, buffer, 0); 595 int actualLength = outputStream.toByteArray().length; 596 Assert.assertEquals(0, actualLength); 597 598 inputStream.reset(); 599 BsPatch.pipe(inputStream, outputStream, buffer, 1); 600 actualLength = outputStream.toByteArray().length; 601 Assert.assertEquals(1, actualLength); 602 byte actualByte = outputStream.toByteArray()[0]; 603 Assert.assertEquals((byte) 't', actualByte); 604 605 outputStream = new ByteArrayOutputStream(); 606 inputStream.reset(); 607 BsPatch.pipe(inputStream, outputStream, buffer, 5); 608 actualLength = outputStream.toByteArray().length; 609 Assert.assertEquals(5, actualLength); 610 String actualOutput = outputStream.toString(); 611 String expectedOutput = inputString.substring(0, 5); 612 Assert.assertEquals(expectedOutput, actualOutput); 613 614 outputStream = new ByteArrayOutputStream(); 615 inputStream.reset(); 616 BsPatch.pipe(inputStream, outputStream, buffer, input.length); 617 actualLength = outputStream.toByteArray().length; 618 Assert.assertEquals(input.length, actualLength); 619 expectedOutput = outputStream.toString(); 620 Assert.assertEquals(inputString, expectedOutput); 621 } 622 623 @Test testPipe_Underrun()624 public void testPipe_Underrun() { 625 int dataLength = 10; 626 ByteArrayInputStream in = new ByteArrayInputStream(new byte[dataLength]); 627 try { 628 // Tell pipe to copy 1 more byte than is actually available 629 BsPatch.pipe(in, new ByteArrayOutputStream(), new byte[dataLength], dataLength + 1); 630 Assert.fail("Should've thrown an IOException"); 631 } catch (IOException expected) { 632 // Pass 633 } 634 } 635 636 @Test testPipe_CopyZeroBytes()637 public void testPipe_CopyZeroBytes() throws IOException { 638 int dataLength = 0; 639 ByteArrayInputStream in = new ByteArrayInputStream(new byte[dataLength]); 640 ByteArrayOutputStream out = new ByteArrayOutputStream(); 641 BsPatch.pipe(in, out, new byte[100], dataLength); 642 int actualLength = out.toByteArray().length; 643 Assert.assertEquals(0, actualLength); 644 } 645 646 /** 647 * Invoke applyPatch(...) and verify that the results are as expected. 648 * @param oldPath the path to the old asset in /assets 649 * @param patchPatch the path to the patch asset in /assets 650 * @param newPath the path to the new asset in /assets 651 * @throws IOException if unable to read/write 652 * @throws PatchFormatException if the patch is invalid 653 */ invokeApplyPatch(String oldPath, String patchPatch, String newPath)654 private void invokeApplyPatch(String oldPath, String patchPatch, String newPath) 655 throws IOException, PatchFormatException { 656 copyToOldFile(oldPath); 657 RandomAccessFile oldData = new RandomAccessFile(oldFile, "r"); 658 InputStream patchInputStream = new ByteArrayInputStream(readTestData(patchPatch)); 659 byte[] expectedNewDataBytes = readTestData(newPath); 660 ByteArrayOutputStream actualNewData = new ByteArrayOutputStream(); 661 BsPatch.applyPatch(oldData, actualNewData, patchInputStream); 662 byte[] actualNewDataBytes = actualNewData.toByteArray(); 663 Assert.assertArrayEquals(expectedNewDataBytes, actualNewDataBytes); 664 } 665 666 /** 667 * Checks two byte ranges for equivalence. 668 * 669 * @param data1 first array 670 * @param data2 second array 671 * @param start1 first byte to compare in |data1| 672 * @param start2 first byte to compare in |data2| 673 * @param length the number of bytes to compare 674 */ regionEquals( final byte[] data1, final int start1, final byte[] data2, final int start2, final int length)675 private static boolean regionEquals( 676 final byte[] data1, 677 final int start1, 678 final byte[] data2, 679 final int start2, 680 final int length) { 681 for (int x = 0; x < length; x++) { 682 if (data1[x + start1] != data2[x + start2]) { 683 return false; 684 } 685 } 686 return true; 687 } 688 689 // (Copied from BsDiffTest) 690 // Some systems force all text files to end in a newline, which screws up this test. stripNewlineIfNecessary(byte[] b)691 private static byte[] stripNewlineIfNecessary(byte[] b) { 692 if (b[b.length - 1] != (byte) '\n') { 693 return b; 694 } 695 696 byte[] ret = new byte[b.length - 1]; 697 System.arraycopy(b, 0, ret, 0, ret.length); 698 return ret; 699 } 700 701 // (Copied from BsDiffTest) readTestData(String testDataFileName)702 private byte[] readTestData(String testDataFileName) throws IOException { 703 InputStream in = getClass().getResourceAsStream("testdata/" + testDataFileName); 704 Assert.assertNotNull("test data file doesn't exist: " + testDataFileName, in); 705 ByteArrayOutputStream result = new ByteArrayOutputStream(); 706 byte[] buffer = new byte[32768]; 707 int numRead = 0; 708 while ((numRead = in.read(buffer)) >= 0) { 709 result.write(buffer, 0, numRead); 710 } 711 return stripNewlineIfNecessary(result.toByteArray()); 712 } 713 714 /** 715 * Copy the contents of the specified testdata asset into {@link #oldFile}. 716 * @param testDataFileName the name of the testdata asset to read 717 * @throws IOException if unable to complete the copy 718 */ copyToOldFile(String testDataFileName)719 private void copyToOldFile(String testDataFileName) throws IOException { 720 oldFile = File.createTempFile("archive_patcher", "temp"); 721 Assert.assertNotNull("cant create file!", oldFile); 722 byte[] buffer = readTestData(testDataFileName); 723 FileOutputStream out = new FileOutputStream(oldFile); 724 out.write(buffer); 725 out.flush(); 726 out.close(); 727 } 728 729 /** 730 * Make {@link #oldFile} an empty file (full of binary zeroes) of the specified length. 731 * @param desiredLength the desired length in bytes 732 * @throws IOException if unable to write the file 733 */ createEmptyOldFile(int desiredLength)734 private void createEmptyOldFile(int desiredLength) throws IOException { 735 OutputStream out = new FileOutputStream(oldFile); 736 for (int x = 0; x < desiredLength; x++) { 737 out.write(0); 738 } 739 out.close(); 740 } 741 742 /** 743 * Create an arbitrary patch that consists of a signature, a length, and a directive sequence. 744 * Used to manufacture junk for failure and edge cases. 745 * @param signature the signature to use 746 * @param newLength the expected length of the "new" file produced by applying the patch 747 * @param diffSegmentLength the value to supply as diffSegmentLength 748 * @param copySegmentLength the value to supply as copySegmentLength 749 * @param offsetToNextInput the value to supply as offsetToNextInput 750 * @param addends a byte array of addends; all are written, ignoring |diffSegmentLength|. 751 * @return the bytes constituting the patch 752 * @throws IOException 753 */ makePatch( String signature, long newLength, long diffSegmentLength, long copySegmentLength, long offsetToNextInput, byte[] addends)754 private static InputStream makePatch( 755 String signature, 756 long newLength, 757 long diffSegmentLength, 758 long copySegmentLength, 759 long offsetToNextInput, 760 byte[] addends) 761 throws IOException { 762 ByteArrayOutputStream out = new ByteArrayOutputStream(); 763 out.write(signature.getBytes("US-ASCII")); 764 writeBsdiffLong(newLength, out); 765 writeBsdiffLong(diffSegmentLength, out); 766 writeBsdiffLong(copySegmentLength, out); 767 writeBsdiffLong(offsetToNextInput, out); 768 out.write(addends); 769 return new ByteArrayInputStream(out.toByteArray()); 770 } 771 772 // Copied from com.google.archivepatcher.generator.bsdiff.BsUtil for convenience. writeBsdiffLong(final long value, OutputStream out)773 private static void writeBsdiffLong(final long value, OutputStream out) throws IOException { 774 long y = value; 775 if (y < 0) { 776 y = (-y) | (1L << 63); 777 } 778 for (int i = 0; i < 8; ++i) { 779 out.write((byte) (y & 0xff)); 780 y >>>= 8; 781 } 782 } 783 } 784