| // Copyright 2016 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.archivepatcher.applier.bsdiff; |
| |
| import com.google.archivepatcher.applier.PatchFormatException; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.RandomAccessFile; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * Tests for {@link BsPatch}. |
| */ |
| @RunWith(JUnit4.class) |
| public class BsPatchTest { |
| |
| private static final String SIGNATURE = "ENDSLEY/BSDIFF43"; |
| private byte[] buffer1; |
| private byte[] buffer2; |
| |
| /** |
| * The tests need access to an actual File object for the "old file", so that it can be used as |
| * the argument to a RandomAccessFile constructor... but the old file is a resource loaded at test |
| * run-time, potentially from a JAR, and therefore a copy must be made in the filesystem to access |
| * via RandomAccessFile. This is not true for the new file or the patch file, both of which are |
| * streamable. |
| */ |
| private File oldFile; |
| |
| @Before |
| public void setUp() throws IOException { |
| buffer1 = new byte[6]; |
| buffer2 = new byte[6]; |
| try { |
| oldFile = File.createTempFile("archive_patcher", "old"); |
| oldFile.deleteOnExit(); |
| } catch (IOException e) { |
| if (oldFile != null) { |
| oldFile.delete(); |
| } |
| throw e; |
| } |
| } |
| |
| @After |
| public void tearDown() { |
| if (oldFile != null) { |
| oldFile.delete(); |
| } |
| oldFile = null; |
| } |
| |
| @Test |
| public void testTransformBytes() throws IOException { |
| // In this case the "patch stream" is just a stream of addends that transformBytes(...) will |
| // apply to the old data file. |
| final byte[] patchInput = "this is a sample string to read".getBytes("US-ASCII"); |
| final ByteArrayInputStream patchInputStream = new ByteArrayInputStream(patchInput); |
| copyToOldFile("bsdifftest_partial_a.txt"); |
| RandomAccessFile oldData = new RandomAccessFile(oldFile, "r"); |
| final byte[] expectedNewData = readTestData("bsdifftest_partial_b.bin"); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| BsPatch.transformBytes(patchInput.length, patchInputStream, oldData, newData, buffer1, buffer2); |
| byte[] actual = newData.toByteArray(); |
| Assert.assertArrayEquals(expectedNewData, actual); |
| } |
| |
| @Test |
| public void testTransformBytes_Error_NotEnoughBytes() throws IOException { |
| // This test sets up a trivial 1-byte "patch" (addends) stream but then asks |
| // transformBytes(...) to apply *2* bytes, which should fail when it hits EOF. |
| final InputStream patchIn = new ByteArrayInputStream(new byte[] {(byte) 0x00}); |
| copyToOldFile("bsdifftest_partial_a.txt"); // Any file would work here |
| RandomAccessFile oldData = new RandomAccessFile(oldFile, "r"); |
| try { |
| BsPatch.transformBytes(2, patchIn, oldData, new ByteArrayOutputStream(), buffer1, buffer2); |
| Assert.fail("Read past EOF"); |
| } catch (IOException expected) { |
| // Pass |
| } |
| } |
| |
| @Test |
| public void testTransformBytes_Error_JunkPatch() throws IOException { |
| final byte[] patchInput = "this is a second sample string to read".getBytes("US-ASCII"); |
| final ByteArrayInputStream patchInputStream = new ByteArrayInputStream(patchInput); |
| copyToOldFile("bsdifftest_partial_a.txt"); // Any file would work here |
| RandomAccessFile oldData = new RandomAccessFile(oldFile, "r"); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.transformBytes( |
| patchInput.length, patchInputStream, oldData, newData, buffer1, buffer2); |
| Assert.fail("Should have thrown an IOException"); |
| } catch (IOException expected) { |
| // Pass |
| } |
| } |
| |
| @Test |
| public void testTransformBytes_Error_JunkPatch_Underflow() throws IOException { |
| final byte[] patchInput = "this is a sample string".getBytes("US-ASCII"); |
| final ByteArrayInputStream patchInputStream = new ByteArrayInputStream(patchInput); |
| copyToOldFile("bsdifftest_partial_a.txt"); |
| RandomAccessFile oldData = new RandomAccessFile(oldFile, "r"); |
| final byte[] buffer1 = new byte[6]; |
| final byte[] buffer2 = new byte[6]; |
| |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.transformBytes( |
| patchInput.length + 1, patchInputStream, oldData, newData, buffer1, buffer2); |
| Assert.fail("Should have thrown an IOException"); |
| } catch (IOException expected) { |
| // Pass |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_ContrivedData() throws Exception { |
| invokeApplyPatch( |
| "bsdifftest_internal_blob_a.bin", |
| "bsdifftest_internal_patch_a_to_b.bin", |
| "bsdifftest_internal_blob_b.bin"); |
| } |
| |
| @Test |
| public void testApplyPatch_BetterData() throws Exception { |
| invokeApplyPatch( |
| "bsdifftest_minimal_blob_a.bin", |
| "bsdifftest_minimal_patch_a_to_b.bin", |
| "bsdifftest_minimal_blob_b.bin"); |
| } |
| |
| @Test |
| public void testApplyPatch_BadSignature() throws Exception { |
| createEmptyOldFile(10); |
| String junkSignature = "WOOOOOO/BSDIFF43"; // Correct length, wrong content |
| InputStream patchIn = |
| makePatch( |
| junkSignature, |
| 10, // newLength |
| 10, // diffSegmentLength |
| 0, // copySegmentLength |
| 0, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch with bad signature"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("bad signature", actual); |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_NewLengthMismatch() throws Exception { |
| createEmptyOldFile(10); |
| InputStream patchIn = |
| makePatch( |
| SIGNATURE, |
| 10, // newLength (illegal) |
| 10, // diffSegmentLength |
| 0, // copySegmentLength |
| 0, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn, (long) 10 + 1); |
| Assert.fail("Read patch with mismatched newLength"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("expectedNewSize != newSize", actual); |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_NewLengthNegative() throws Exception { |
| createEmptyOldFile(10); |
| InputStream patchIn = |
| makePatch( |
| SIGNATURE, |
| -10, // newLength (illegal) |
| 10, // diffSegmentLength |
| 0, // copySegmentLength |
| 0, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch with negative newLength"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("bad newSize", actual); |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_NewLengthTooLarge() throws Exception { |
| createEmptyOldFile(10); |
| InputStream patchIn = |
| makePatch( |
| SIGNATURE, |
| Integer.MAX_VALUE + 1, // newLength (max supported is Integer.MAX_VALUE) |
| 10, // diffSegmentLength |
| 0, // copySegmentLength |
| 0, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch( |
| new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch with excessive newLength"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("bad newSize", actual); |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_DiffSegmentLengthNegative() throws Exception { |
| createEmptyOldFile(10); |
| InputStream patchIn = |
| makePatch( |
| SIGNATURE, |
| 10, // newLength |
| -10, // diffSegmentLength (negative) |
| 0, // copySegmentLength |
| 0, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch with negative diffSegmentLength"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("bad diffSegmentLength", actual); |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_DiffSegmentLengthTooLarge() throws Exception { |
| createEmptyOldFile(10); |
| InputStream patchIn = |
| makePatch( |
| SIGNATURE, |
| 10, // newLength |
| Integer.MAX_VALUE + 1, // diffSegmentLength (too big) |
| 0, // copySegmentLength |
| 0, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch with excessive diffSegmentLength"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("bad diffSegmentLength", actual); |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_CopySegmentLengthNegative() throws Exception { |
| createEmptyOldFile(10); |
| InputStream patchIn = |
| makePatch( |
| SIGNATURE, |
| 10, // newLength |
| 10, // diffSegmentLength |
| -10, // copySegmentLength (negative) |
| 0, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch with negative copySegmentLength"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("bad copySegmentLength", actual); |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_CopySegmentLengthTooLarge() throws Exception { |
| createEmptyOldFile(10); |
| InputStream patchIn = |
| makePatch( |
| SIGNATURE, |
| 10, // newLength |
| 0, // diffSegmentLength |
| Integer.MAX_VALUE + 1, // copySegmentLength (too big) |
| 0, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch with excessive copySegmentLength"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("bad copySegmentLength", actual); |
| } |
| } |
| |
| // ExpectedFinalNewDataBytesWritten_Negative case is impossible in code, so no need to test |
| // that; just the TooLarge condition. |
| @Test |
| public void testApplyPatch_ExpectedFinalNewDataBytesWritten_PastEOF() throws Exception { |
| createEmptyOldFile(10); |
| // Make diffSegmentLength + copySegmentLength > newLength |
| InputStream patchIn = |
| makePatch( |
| SIGNATURE, |
| 10, // newLength |
| 10, // diffSegmentLength |
| 1, // copySegmentLength |
| 0, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch that moves past EOF in new file"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("expectedFinalNewDataBytesWritten too large", actual); |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_ExpectedFinalOldDataOffset_Negative() throws Exception { |
| createEmptyOldFile(10); |
| // Make diffSegmentLength + offsetToNextInput < 0 |
| InputStream patchIn = |
| makePatch( |
| SIGNATURE, |
| 10, // newLength |
| 10, // diffSegmentLength |
| 0, // copySegmentLength |
| -11, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch with that moves to a negative offset in old file"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("expectedFinalOldDataOffset is negative", actual); |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_ExpectedFinalOldDataOffset_PastEOF() throws Exception { |
| createEmptyOldFile(10); |
| // Make diffSegmentLength + offsetToNextInput > oldLength |
| InputStream patchIn = |
| makePatch( |
| SIGNATURE, |
| 10, // newLength |
| 10, // diffSegmentLength |
| 0, // copySegmentLength |
| 1, // offsetToNextInput |
| new byte[10] // addends |
| ); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch with that moves past EOF in old file"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("expectedFinalOldDataOffset too large", actual); |
| } |
| } |
| |
| @Test |
| public void testApplyPatch_TruncatedSignature() throws Exception { |
| createEmptyOldFile(10); |
| InputStream patchIn = new ByteArrayInputStream("X".getBytes("US-ASCII")); |
| ByteArrayOutputStream newData = new ByteArrayOutputStream(); |
| try { |
| BsPatch.applyPatch(new RandomAccessFile(oldFile, "r"), newData, patchIn); |
| Assert.fail("Read patch with truncated signature"); |
| } catch (PatchFormatException expected) { |
| // No way to mock the internal logic, so resort to testing exception string for coverage |
| String actual = expected.getMessage(); |
| Assert.assertEquals("truncated signature", actual); |
| } |
| } |
| |
| @Test |
| public void testReadBsdiffLong() throws Exception { |
| byte[] data = { |
| (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0, (byte) 0, (byte) 0, (byte) 0, |
| (byte) 0xef, (byte) 0xbe, (byte) 0xad, (byte) 0x0e, (byte) 0, (byte) 0, (byte) 0, (byte) 0 |
| }; |
| ByteArrayInputStream inputStream = new ByteArrayInputStream(data); |
| long actual = BsPatch.readBsdiffLong(inputStream); |
| Assert.assertEquals(0x12345678, actual); |
| actual = BsPatch.readBsdiffLong(inputStream); |
| Assert.assertEquals(0x0eadbeef, actual); |
| } |
| |
| @Test |
| public void testReadBsdiffLong_Zero() throws Exception { |
| long expected = 0x00000000L; |
| long actual = |
| BsPatch.readBsdiffLong( |
| new ByteArrayInputStream( |
| new byte[] { |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 |
| })); |
| Assert.assertEquals(expected, actual); |
| } |
| |
| @Test |
| public void testReadBsdiffLong_IntegerMaxValue() throws Exception { |
| long expected = 0x7fffffffL; |
| long actual = |
| BsPatch.readBsdiffLong( |
| new ByteArrayInputStream( |
| new byte[] { |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 |
| })); |
| Assert.assertEquals(expected, actual); |
| } |
| |
| @Test |
| public void testReadBsdiffLong_IntegerMinValue() throws Exception { |
| long expected = -0x80000000L; |
| long actual = |
| BsPatch.readBsdiffLong( |
| new ByteArrayInputStream( |
| new byte[] { |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80 |
| })); |
| Assert.assertEquals(expected, actual); |
| } |
| |
| @Test |
| public void testReadBsdiffLong_LongMaxValue() throws Exception { |
| long expected = 0x7fffffffffffffffL; |
| long actual = |
| BsPatch.readBsdiffLong( |
| new ByteArrayInputStream( |
| new byte[] { |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f |
| })); |
| Assert.assertEquals(expected, actual); |
| } |
| |
| // Can't read Long.MIN_VALUE because the signed-magnitude representation stops at |
| // Long.MIN_VALUE+1. |
| @Test |
| public void testReadBsdiffLong_LongMinValueIsh() throws Exception { |
| long expected = -0x7fffffffffffffffL; |
| long actual = |
| BsPatch.readBsdiffLong( |
| new ByteArrayInputStream( |
| new byte[] { |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, |
| (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff |
| })); |
| Assert.assertEquals(expected, actual); |
| } |
| |
| // This is also Java's Long.MAX_VALUE. |
| @Test |
| public void testReadBsdiffLong_NegativeZero() throws Exception { |
| try { |
| BsPatch.readBsdiffLong( |
| new ByteArrayInputStream( |
| new byte[] { |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80 |
| })); |
| Assert.fail("Tolerated negative zero"); |
| } catch (PatchFormatException expected) { |
| // Pass |
| } |
| } |
| |
| @Test |
| public void testReadFully() throws IOException { |
| final byte[] input = "this is a sample string to read".getBytes("UTF-8"); |
| final ByteArrayInputStream inputStream = new ByteArrayInputStream(input); |
| final byte[] dst = new byte[50]; |
| |
| try { |
| BsPatch.readFully(inputStream, dst, 0, 50); |
| Assert.fail("Should've thrown an IOException"); |
| } catch (IOException expected) { |
| // Pass |
| } |
| |
| inputStream.reset(); |
| BsPatch.readFully(inputStream, dst, 0, input.length); |
| Assert.assertTrue(regionEquals(dst, 0, input, 0, input.length)); |
| |
| inputStream.reset(); |
| BsPatch.readFully(inputStream, dst, 40, 10); |
| Assert.assertTrue(regionEquals(dst, 40, input, 0, 10)); |
| |
| inputStream.reset(); |
| try { |
| BsPatch.readFully(inputStream, dst, 45, 11); |
| Assert.fail("Should've thrown an IndexOutOfBoundsException"); |
| } catch (IndexOutOfBoundsException expected) { |
| // Pass |
| } |
| } |
| |
| @Test |
| public void testPipe() throws IOException { |
| final String inputString = "this is a sample string to read"; |
| final byte[] input = inputString.getBytes("US-ASCII"); |
| final ByteArrayInputStream inputStream = new ByteArrayInputStream(input); |
| final byte[] buffer = new byte[5]; |
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| |
| BsPatch.pipe(inputStream, outputStream, buffer, 0); |
| int actualLength = outputStream.toByteArray().length; |
| Assert.assertEquals(0, actualLength); |
| |
| inputStream.reset(); |
| BsPatch.pipe(inputStream, outputStream, buffer, 1); |
| actualLength = outputStream.toByteArray().length; |
| Assert.assertEquals(1, actualLength); |
| byte actualByte = outputStream.toByteArray()[0]; |
| Assert.assertEquals((byte) 't', actualByte); |
| |
| outputStream = new ByteArrayOutputStream(); |
| inputStream.reset(); |
| BsPatch.pipe(inputStream, outputStream, buffer, 5); |
| actualLength = outputStream.toByteArray().length; |
| Assert.assertEquals(5, actualLength); |
| String actualOutput = outputStream.toString(); |
| String expectedOutput = inputString.substring(0, 5); |
| Assert.assertEquals(expectedOutput, actualOutput); |
| |
| outputStream = new ByteArrayOutputStream(); |
| inputStream.reset(); |
| BsPatch.pipe(inputStream, outputStream, buffer, input.length); |
| actualLength = outputStream.toByteArray().length; |
| Assert.assertEquals(input.length, actualLength); |
| expectedOutput = outputStream.toString(); |
| Assert.assertEquals(inputString, expectedOutput); |
| } |
| |
| @Test |
| public void testPipe_Underrun() { |
| int dataLength = 10; |
| ByteArrayInputStream in = new ByteArrayInputStream(new byte[dataLength]); |
| try { |
| // Tell pipe to copy 1 more byte than is actually available |
| BsPatch.pipe(in, new ByteArrayOutputStream(), new byte[dataLength], dataLength + 1); |
| Assert.fail("Should've thrown an IOException"); |
| } catch (IOException expected) { |
| // Pass |
| } |
| } |
| |
| @Test |
| public void testPipe_CopyZeroBytes() throws IOException { |
| int dataLength = 0; |
| ByteArrayInputStream in = new ByteArrayInputStream(new byte[dataLength]); |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| BsPatch.pipe(in, out, new byte[100], dataLength); |
| int actualLength = out.toByteArray().length; |
| Assert.assertEquals(0, actualLength); |
| } |
| |
| /** |
| * Invoke applyPatch(...) and verify that the results are as expected. |
| * @param oldPath the path to the old asset in /assets |
| * @param patchPatch the path to the patch asset in /assets |
| * @param newPath the path to the new asset in /assets |
| * @throws IOException if unable to read/write |
| * @throws PatchFormatException if the patch is invalid |
| */ |
| private void invokeApplyPatch(String oldPath, String patchPatch, String newPath) |
| throws IOException, PatchFormatException { |
| copyToOldFile(oldPath); |
| RandomAccessFile oldData = new RandomAccessFile(oldFile, "r"); |
| InputStream patchInputStream = new ByteArrayInputStream(readTestData(patchPatch)); |
| byte[] expectedNewDataBytes = readTestData(newPath); |
| ByteArrayOutputStream actualNewData = new ByteArrayOutputStream(); |
| BsPatch.applyPatch(oldData, actualNewData, patchInputStream); |
| byte[] actualNewDataBytes = actualNewData.toByteArray(); |
| Assert.assertArrayEquals(expectedNewDataBytes, actualNewDataBytes); |
| } |
| |
| /** |
| * Checks two byte ranges for equivalence. |
| * |
| * @param data1 first array |
| * @param data2 second array |
| * @param start1 first byte to compare in |data1| |
| * @param start2 first byte to compare in |data2| |
| * @param length the number of bytes to compare |
| */ |
| private static boolean regionEquals( |
| final byte[] data1, |
| final int start1, |
| final byte[] data2, |
| final int start2, |
| final int length) { |
| for (int x = 0; x < length; x++) { |
| if (data1[x + start1] != data2[x + start2]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // (Copied from BsDiffTest) |
| // Some systems force all text files to end in a newline, which screws up this test. |
| private static byte[] stripNewlineIfNecessary(byte[] b) { |
| if (b[b.length - 1] != (byte) '\n') { |
| return b; |
| } |
| |
| byte[] ret = new byte[b.length - 1]; |
| System.arraycopy(b, 0, ret, 0, ret.length); |
| return ret; |
| } |
| |
| // (Copied from BsDiffTest) |
| private byte[] readTestData(String testDataFileName) throws IOException { |
| InputStream in = getClass().getResourceAsStream("testdata/" + testDataFileName); |
| Assert.assertNotNull("test data file doesn't exist: " + testDataFileName, in); |
| ByteArrayOutputStream result = new ByteArrayOutputStream(); |
| byte[] buffer = new byte[32768]; |
| int numRead = 0; |
| while ((numRead = in.read(buffer)) >= 0) { |
| result.write(buffer, 0, numRead); |
| } |
| return stripNewlineIfNecessary(result.toByteArray()); |
| } |
| |
| /** |
| * Copy the contents of the specified testdata asset into {@link #oldFile}. |
| * @param testDataFileName the name of the testdata asset to read |
| * @throws IOException if unable to complete the copy |
| */ |
| private void copyToOldFile(String testDataFileName) throws IOException { |
| oldFile = File.createTempFile("archive_patcher", "temp"); |
| Assert.assertNotNull("cant create file!", oldFile); |
| byte[] buffer = readTestData(testDataFileName); |
| FileOutputStream out = new FileOutputStream(oldFile); |
| out.write(buffer); |
| out.flush(); |
| out.close(); |
| } |
| |
| /** |
| * Make {@link #oldFile} an empty file (full of binary zeroes) of the specified length. |
| * @param desiredLength the desired length in bytes |
| * @throws IOException if unable to write the file |
| */ |
| private void createEmptyOldFile(int desiredLength) throws IOException { |
| OutputStream out = new FileOutputStream(oldFile); |
| for (int x = 0; x < desiredLength; x++) { |
| out.write(0); |
| } |
| out.close(); |
| } |
| |
| /** |
| * Create an arbitrary patch that consists of a signature, a length, and a directive sequence. |
| * Used to manufacture junk for failure and edge cases. |
| * @param signature the signature to use |
| * @param newLength the expected length of the "new" file produced by applying the patch |
| * @param diffSegmentLength the value to supply as diffSegmentLength |
| * @param copySegmentLength the value to supply as copySegmentLength |
| * @param offsetToNextInput the value to supply as offsetToNextInput |
| * @param addends a byte array of addends; all are written, ignoring |diffSegmentLength|. |
| * @return the bytes constituting the patch |
| * @throws IOException |
| */ |
| private static InputStream makePatch( |
| String signature, |
| long newLength, |
| long diffSegmentLength, |
| long copySegmentLength, |
| long offsetToNextInput, |
| byte[] addends) |
| throws IOException { |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| out.write(signature.getBytes("US-ASCII")); |
| writeBsdiffLong(newLength, out); |
| writeBsdiffLong(diffSegmentLength, out); |
| writeBsdiffLong(copySegmentLength, out); |
| writeBsdiffLong(offsetToNextInput, out); |
| out.write(addends); |
| return new ByteArrayInputStream(out.toByteArray()); |
| } |
| |
| // Copied from com.google.archivepatcher.generator.bsdiff.BsUtil for convenience. |
| private static void writeBsdiffLong(final long value, OutputStream out) throws IOException { |
| long y = value; |
| if (y < 0) { |
| y = (-y) | (1L << 63); |
| } |
| for (int i = 0; i < 8; ++i) { |
| out.write((byte) (y & 0xff)); |
| y >>>= 8; |
| } |
| } |
| } |