Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2017 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 
     17 package com.android.apksig.util;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.fail;
     21 
     22 import java.io.Closeable;
     23 import java.io.IOException;
     24 import java.nio.BufferOverflowException;
     25 import java.nio.ByteBuffer;
     26 import java.nio.charset.StandardCharsets;
     27 import org.junit.Test;
     28 
     29 /**
     30  * Base class for testing implementations of {@link DataSource}. This class tests the contract of
     31  * {@code DataSource}.
     32  *
     33  * <p>To subclass, provide an implementation of {@link #createDataSource(byte[])} which returns
     34  * the implementation of {@code DataSource} you want to test.
     35  */
     36 public abstract class DataSourceTestBase {
     37 
     38     /**
     39      * Returns a new {@link DataSource} containing the provided contents.
     40      */
     41     protected abstract CloseableWithDataSource createDataSource(byte[] contents) throws IOException;
     42 
     43     protected CloseableWithDataSource createDataSource(String contents) throws IOException {
     44         return createDataSource(contents.getBytes(StandardCharsets.UTF_8));
     45     }
     46 
     47     @Test
     48     public void testSize() throws Exception {
     49         try (CloseableWithDataSource c = createDataSource("Hello12345")) {
     50             DataSource ds = c.getDataSource();
     51             assertEquals(10, ds.size());
     52         }
     53     }
     54 
     55     @Test
     56     public void testSlice() throws Exception {
     57         try (CloseableWithDataSource c = createDataSource("Hello12345")) {
     58             DataSource ds = c.getDataSource();
     59             assertSliceEquals("123", ds, 5, 3);
     60             DataSource slice = ds.slice(3, 5);
     61             assertGetByteBufferEquals("lo123", slice, 0, 5);
     62 
     63             // Zero-length slices
     64             assertSliceEquals("", ds, 0, 0);
     65             assertSliceEquals("", ds, 1, 0);
     66             assertSliceEquals("", ds, ds.size() - 2, 0);
     67             assertSliceEquals("", ds, ds.size() - 1, 0);
     68             assertSliceEquals("", ds, ds.size(), 0);
     69             assertSliceEquals("", slice, 0, 0);
     70             assertSliceEquals("", slice, 1, 0);
     71             assertSliceEquals("", slice, slice.size() - 2, 0);
     72             assertSliceEquals("", slice, slice.size() - 1, 0);
     73             assertSliceEquals("", slice, slice.size(), 0);
     74 
     75             // Invalid slices
     76             assertSliceThrowsIOOB(ds, -1, 0);
     77             assertSliceThrowsIOOB(slice, -1, 0);
     78             assertSliceThrowsIOOB(ds, -1, 2);
     79             assertSliceThrowsIOOB(slice, -1, 2);
     80             assertSliceThrowsIOOB(ds, -1, 20);
     81             assertSliceThrowsIOOB(slice, -1, 20);
     82             assertSliceThrowsIOOB(ds, 1, 20);
     83             assertSliceThrowsIOOB(slice, 1, 20);
     84             assertSliceThrowsIOOB(ds, ds.size() + 1, 0);
     85             assertSliceThrowsIOOB(slice, slice.size() + 1, 0);
     86             assertSliceThrowsIOOB(ds, ds.size(), 1);
     87             assertSliceThrowsIOOB(slice, slice.size(), 1);
     88             assertSliceThrowsIOOB(ds, ds.size() - 1, -1);
     89             assertSliceThrowsIOOB(ds, slice.size() - 1, -1);
     90         }
     91     }
     92 
     93     @Test
     94     public void testGetByteBuffer() throws Exception {
     95         try (CloseableWithDataSource c = createDataSource("test1234")) {
     96             DataSource ds = c.getDataSource();
     97             assertGetByteBufferEquals("s", ds, 2, 1);
     98             DataSource slice = ds.slice(3, 4); // "t123"
     99             assertGetByteBufferEquals("2", slice, 2, 1);
    100 
    101             // Zero-length chunks
    102             assertEquals(0, ds.getByteBuffer(0, 0).capacity());
    103             assertEquals(0, ds.getByteBuffer(ds.size(), 0).capacity());
    104             assertEquals(0, ds.getByteBuffer(ds.size() - 1, 0).capacity());
    105             assertEquals(0, ds.getByteBuffer(ds.size() - 2, 0).capacity());
    106             assertEquals(0, slice.getByteBuffer(0, 0).capacity());
    107             assertEquals(0, slice.getByteBuffer(slice.size(), 0).capacity());
    108             assertEquals(0, slice.getByteBuffer(slice.size() - 1, 0).capacity());
    109             assertEquals(0, slice.getByteBuffer(slice.size() - 2, 0).capacity());
    110 
    111             // Invalid chunks
    112             assertGetByteBufferThrowsIOOB(ds, -1, 0);
    113             assertGetByteBufferThrowsIOOB(slice, -1, 0);
    114             assertGetByteBufferThrowsIOOB(ds, -1, 2);
    115             assertGetByteBufferThrowsIOOB(slice, -1, 2);
    116             assertGetByteBufferThrowsIOOB(ds, -1, 20);
    117             assertGetByteBufferThrowsIOOB(slice, -1, 20);
    118             assertGetByteBufferThrowsIOOB(ds, 1, 20);
    119             assertGetByteBufferThrowsIOOB(slice, 1, 20);
    120             assertGetByteBufferThrowsIOOB(ds, ds.size() + 1, 0);
    121             assertGetByteBufferThrowsIOOB(slice, slice.size() + 1, 0);
    122             assertGetByteBufferThrowsIOOB(ds, ds.size(), 1);
    123             assertGetByteBufferThrowsIOOB(slice, slice.size(), 1);
    124             assertGetByteBufferThrowsIOOB(ds, ds.size() - 1, -1);
    125             assertGetByteBufferThrowsIOOB(ds, slice.size() - 1, -1);
    126         }
    127     }
    128 
    129     @Test
    130     public void testFeed() throws Exception {
    131         try (CloseableWithDataSource c = createDataSource("test1234")) {
    132             DataSource ds = c.getDataSource();
    133             assertFeedEquals("23", ds, 5, 2);
    134             DataSource slice = ds.slice(1, 5); // "est12"
    135             assertFeedEquals("t", slice, 2, 1);
    136 
    137             // Zero-length chunks
    138             assertFeedEquals("", ds, 0, 0);
    139             assertFeedEquals("", ds, 1, 0);
    140             assertFeedEquals("", ds, ds.size() - 2, 0);
    141             assertFeedEquals("", ds, ds.size() - 1, 0);
    142             assertFeedEquals("", ds, ds.size(), 0);
    143             assertFeedEquals("", slice, 0, 0);
    144             assertFeedEquals("", slice, 2, 0);
    145             assertFeedEquals("", slice, slice.size() - 2, 0);
    146             assertFeedEquals("", slice, slice.size() - 1, 0);
    147             assertFeedEquals("", slice, slice.size(), 0);
    148 
    149             // Invalid chunks
    150             assertFeedThrowsIOOB(ds, -1, 0);
    151             assertFeedThrowsIOOB(slice, -1, 0);
    152             assertFeedThrowsIOOB(ds, -1, 2);
    153             assertFeedThrowsIOOB(slice, -1, 2);
    154             assertFeedThrowsIOOB(ds, -1, 10);
    155             assertFeedThrowsIOOB(slice, -1, 10);
    156             assertFeedThrowsIOOB(ds, 1, 10);
    157             assertFeedThrowsIOOB(slice, 1, 10);
    158             assertFeedThrowsIOOB(ds, ds.size() + 1, 0);
    159             assertFeedThrowsIOOB(slice, slice.size() + 1, 0);
    160             assertFeedThrowsIOOB(ds, ds.size(), 1);
    161             assertFeedThrowsIOOB(slice, slice.size(), 1);
    162             assertFeedThrowsIOOB(ds, ds.size() - 1, -1);
    163             assertFeedThrowsIOOB(ds, slice.size() - 1, -1);
    164         }
    165     }
    166 
    167     @Test
    168     public void testCopyTo() throws Exception {
    169         try (CloseableWithDataSource c = createDataSource("abcdefghijklmnop")) {
    170             DataSource ds = c.getDataSource();
    171             assertCopyToEquals("fgh", ds, 5, 3);
    172             DataSource slice = ds.slice(2, 7); // "cdefghi"
    173             assertCopyToEquals("efgh", slice, 2, 4);
    174 
    175             // Zero-length chunks
    176             assertCopyToEquals("", ds, 0, 0);
    177             assertCopyToEquals("", ds, 1, 0);
    178             assertCopyToEquals("", ds, ds.size() - 2, 0);
    179             assertCopyToEquals("", ds, ds.size() - 1, 0);
    180             assertCopyToEquals("", ds, ds.size(), 0);
    181             assertCopyToEquals("", slice, 0, 0);
    182             assertCopyToEquals("", slice, 2, 0);
    183             assertCopyToEquals("", slice, slice.size() - 2, 0);
    184             assertCopyToEquals("", slice, slice.size() - 1, 0);
    185             assertCopyToEquals("", slice, slice.size(), 0);
    186 
    187             // Invalid chunks
    188             assertCopyToThrowsIOOB(ds, -1, 0);
    189             assertCopyToThrowsIOOB(slice, -1, 0);
    190             assertCopyToThrowsIOOB(ds, -1, 2);
    191             assertCopyToThrowsIOOB(slice, -1, 2);
    192             assertCopyToThrowsIOOB(ds, -1, 20);
    193             assertCopyToThrowsIOOB(slice, -1, 20);
    194             assertCopyToThrowsIOOB(ds, 1, 20);
    195             assertCopyToThrowsIOOB(slice, 1, 20);
    196             assertCopyToThrowsIOOB(ds, ds.size() + 1, 0);
    197             assertCopyToThrowsIOOB(slice, slice.size() + 1, 0);
    198             assertCopyToThrowsIOOB(ds, ds.size(), 1);
    199             assertCopyToThrowsIOOB(slice, slice.size(), 1);
    200             assertCopyToThrowsIOOB(ds, ds.size() - 1, -1);
    201             assertCopyToThrowsIOOB(ds, slice.size() - 1, -1);
    202 
    203             // Destination buffer too small
    204             ByteBuffer buf = ByteBuffer.allocate(5);
    205             buf.position(2);
    206             buf.limit(3);
    207             assertCopyToThrowsBufferOverflow(ds, 0, 2, buf);
    208             buf.position(2);
    209             buf.limit(3);
    210             assertCopyToThrowsBufferOverflow(slice, 1, 2, buf);
    211 
    212             // Destination buffer larger than chunk copied using copyTo
    213             buf = ByteBuffer.allocate(10);
    214             buf.position(2);
    215             assertCopyToEquals("bcd", ds, 1, 3, buf);
    216             buf = ByteBuffer.allocate(10);
    217             buf.position(2);
    218             assertCopyToEquals("fg", slice, 3, 2, buf);
    219         }
    220     }
    221 
    222     protected static void assertSliceEquals(
    223             String expectedContents, DataSource ds, long offset, int size) throws IOException {
    224         DataSource slice = ds.slice(offset, size);
    225         assertEquals(size, slice.size());
    226         assertGetByteBufferEquals(expectedContents, slice, 0, size);
    227     }
    228 
    229     protected static void assertSliceThrowsIOOB(DataSource ds, long offset, int size) {
    230         try {
    231             ds.slice(offset, size);
    232             fail();
    233         } catch (IndexOutOfBoundsException expected) {}
    234     }
    235 
    236     protected static void assertGetByteBufferEquals(
    237             String expectedContents, DataSource ds, long offset, int size) throws IOException {
    238         ByteBuffer buf = ds.getByteBuffer(offset, size);
    239         assertEquals(0, buf.position());
    240         assertEquals(size, buf.limit());
    241         assertEquals(size, buf.capacity());
    242         assertEquals(expectedContents, toString(buf));
    243     }
    244 
    245     protected static void assertGetByteBufferThrowsIOOB(DataSource ds, long offset, int size)
    246             throws IOException {
    247         try {
    248             ds.getByteBuffer(offset, size);
    249             fail();
    250         } catch (IndexOutOfBoundsException expected) {}
    251     }
    252 
    253     protected static void assertFeedEquals(
    254             String expectedFedContents, DataSource ds, long offset, int size) throws IOException {
    255         ReadableDataSink out = DataSinks.newInMemoryDataSink(size);
    256         ds.feed(offset, size, out);
    257         assertEquals(size, out.size());
    258         assertEquals(expectedFedContents, toString(out.getByteBuffer(0, size)));
    259     }
    260 
    261     protected static void assertFeedThrowsIOOB(DataSource ds, long offset, long size)
    262             throws IOException {
    263         try {
    264             ds.feed(offset, size, NullDataSink.INSTANCE);
    265             fail();
    266         } catch (IndexOutOfBoundsException expected) {}
    267     }
    268 
    269     protected static void assertCopyToEquals(
    270             String expectedContents, DataSource ds, long offset, int size) throws IOException {
    271         // Create a ByteBuffer backed by a section of a byte array. The ByteBuffer is on purpose not
    272         // starting at offset 0 to catch issues to do with not checking ByteBuffer.arrayOffset().
    273         byte[] arr = new byte[size + 10];
    274         ByteBuffer buf = ByteBuffer.wrap(arr, 1, size + 5);
    275         // Use non-zero position to catch issues with not checking buf.position()
    276         buf.position(2);
    277         // Buffer contains sufficient space for the requested copyTo operation
    278         assertEquals(size + 4, buf.remaining());
    279         assertCopyToEquals(expectedContents, ds, offset, size, buf);
    280     }
    281 
    282     private static void assertCopyToEquals(
    283             String expectedContents, DataSource ds, long offset, int size, ByteBuffer buf)
    284                     throws IOException {
    285         int oldPosition = buf.position();
    286         int oldLimit = buf.limit();
    287         ds.copyTo(offset, size, buf);
    288         // Position should've advanced by size whereas limit should've remained unchanged
    289         assertEquals(oldPosition + size, buf.position());
    290         assertEquals(oldLimit, buf.limit());
    291 
    292         buf.limit(buf.position());
    293         buf.position(oldPosition);
    294         assertEquals(expectedContents, toString(buf));
    295     }
    296 
    297     protected static void assertCopyToThrowsIOOB(DataSource ds, long offset, int size)
    298             throws IOException {
    299         ByteBuffer buf = ByteBuffer.allocate((size < 0) ? 0 : size);
    300         try {
    301             ds.copyTo(offset, size, buf);
    302             fail();
    303         } catch (IndexOutOfBoundsException expected) {}
    304     }
    305 
    306     private static void assertCopyToThrowsBufferOverflow(
    307             DataSource ds, long offset, int size, ByteBuffer buf) throws IOException {
    308         try {
    309             ds.copyTo(offset, size, buf);
    310             fail();
    311         } catch (BufferOverflowException expected) {}
    312     }
    313 
    314     /**
    315      * Returns the contents of the provided buffer as a string. The buffer's position and limit
    316      * remain unchanged.
    317      */
    318     static String toString(ByteBuffer buf) {
    319         byte[] arr;
    320         int offset;
    321         int size = buf.remaining();
    322         if (buf.hasArray()) {
    323             arr = buf.array();
    324             offset = buf.arrayOffset() + buf.position();
    325         } else {
    326             arr = new byte[buf.remaining()];
    327             offset = 0;
    328             int oldPos = buf.position();
    329             buf.get(arr);
    330             buf.position(oldPos);
    331         }
    332         return new String(arr, offset, size, StandardCharsets.UTF_8);
    333     }
    334 
    335     public static class CloseableWithDataSource implements Closeable {
    336         private final DataSource mDataSource;
    337         private final Closeable mCloseable;
    338 
    339         private CloseableWithDataSource(DataSource dataSource, Closeable closeable) {
    340             mDataSource = dataSource;
    341             mCloseable = closeable;
    342         }
    343 
    344         public static CloseableWithDataSource of(DataSource dataSource) {
    345             return new CloseableWithDataSource(dataSource, null);
    346         }
    347 
    348         public static CloseableWithDataSource of(DataSource dataSource, Closeable closeable) {
    349             return new CloseableWithDataSource(dataSource, closeable);
    350         }
    351 
    352         public DataSource getDataSource() {
    353             return mDataSource;
    354         }
    355 
    356         public Closeable getCloseable() {
    357             return mCloseable;
    358         }
    359 
    360         @Override
    361         public void close() throws IOException {
    362             if (mCloseable != null) {
    363                 mCloseable.close();
    364             }
    365         }
    366     }
    367 
    368     private static final class NullDataSink implements DataSink {
    369         private static final NullDataSink INSTANCE = new NullDataSink();
    370 
    371         @Override
    372         public void consume(byte[] buf, int offset, int length) {}
    373 
    374         @Override
    375         public void consume(ByteBuffer buf) {}
    376     }
    377 }
    378