Home | History | Annotate | Download | only in hash
      1 // Copyright 2011 Google Inc. All Rights Reserved.
      2 
      3 package com.google.common.hash;
      4 
      5 import com.google.common.collect.Iterables;
      6 import com.google.common.collect.Lists;
      7 import com.google.common.hash.AbstractStreamingHashFunction.AbstractStreamingHasher;
      8 import com.google.common.hash.HashTestUtils.RandomHasherAction;
      9 
     10 import junit.framework.TestCase;
     11 
     12 import java.io.ByteArrayOutputStream;
     13 import java.nio.ByteBuffer;
     14 import java.nio.ByteOrder;
     15 import java.nio.charset.Charset;
     16 import java.util.Arrays;
     17 import java.util.Collections;
     18 import java.util.List;
     19 import java.util.Random;
     20 
     21 /**
     22  * Tests for AbstractHashSink.
     23  *
     24  * @author andreou (at) google.com (Dimitris Andreou)
     25  */
     26 public class AbstractStreamingHasherTest extends TestCase {
     27   /** Test we get the HashCode that is created by the sink. Later we ignore the result */
     28   public void testSanity() {
     29     Sink sink = new Sink(4);
     30     assertEquals(0xDeadBeef, sink.makeHash().asInt());
     31   }
     32 
     33   public void testBytes() {
     34     Sink sink = new Sink(4); // byte order insignificant here
     35     byte[] expected = { 1, 2, 3, 4, 5, 6, 7, 8 };
     36     sink.putByte((byte) 1);
     37     sink.putBytes(new byte[] { 2, 3, 4, 5, 6 });
     38     sink.putByte((byte) 7);
     39     sink.putBytes(new byte[] { });
     40     sink.putBytes(new byte[] { 8 });
     41     sink.hash();
     42     sink.assertInvariants(8);
     43     sink.assertBytes(expected);
     44   }
     45 
     46   public void testShort() {
     47     Sink sink = new Sink(4);
     48     sink.putShort((short) 0x0201);
     49     sink.hash();
     50     sink.assertInvariants(2);
     51     sink.assertBytes(new byte[] { 1, 2, 0, 0 }); // padded with zeros
     52   }
     53 
     54   public void testInt() {
     55     Sink sink = new Sink(4);
     56     sink.putInt(0x04030201);
     57     sink.hash();
     58     sink.assertInvariants(4);
     59     sink.assertBytes(new byte[] { 1, 2, 3, 4 });
     60   }
     61 
     62   public void testLong() {
     63     Sink sink = new Sink(8);
     64     sink.putLong(0x0807060504030201L);
     65     sink.hash();
     66     sink.assertInvariants(8);
     67     sink.assertBytes(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
     68   }
     69 
     70   public void testChar() {
     71     Sink sink = new Sink(4);
     72     sink.putChar((char) 0x0201);
     73     sink.hash();
     74     sink.assertInvariants(2);
     75     sink.assertBytes(new byte[] { 1, 2, 0, 0  }); // padded with zeros
     76   }
     77 
     78   public void testFloat() {
     79     Sink sink = new Sink(4);
     80     sink.putFloat(Float.intBitsToFloat(0x04030201));
     81     sink.hash();
     82     sink.assertInvariants(4);
     83     sink.assertBytes(new byte[] { 1, 2, 3, 4 });
     84   }
     85 
     86   public void testDouble() {
     87     Sink sink = new Sink(8);
     88     sink.putDouble(Double.longBitsToDouble(0x0807060504030201L));
     89     sink.hash();
     90     sink.assertInvariants(8);
     91     sink.assertBytes(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
     92   }
     93 
     94   public void testCorrectExceptions() {
     95     Sink sink = new Sink(4);
     96     try {
     97       sink.putBytes(new byte[8], -1, 4);
     98       fail();
     99     } catch (IndexOutOfBoundsException ok) {}
    100     try {
    101       sink.putBytes(new byte[8], 0, 16);
    102       fail();
    103     } catch (IndexOutOfBoundsException ok) {}
    104     try {
    105       sink.putBytes(new byte[8], 0, -1);
    106       fail();
    107     } catch (IndexOutOfBoundsException ok) {}
    108   }
    109 
    110   /**
    111    * This test creates a long random sequence of inputs, then a lot of differently configured
    112    * sinks process it; all should produce the same answer, the only difference should be the
    113    * number of process()/processRemaining() invocations, due to alignment.
    114    */
    115   public void testExhaustive() throws Exception {
    116     Random random = new Random(0); // will iteratively make more debuggable, each time it breaks
    117     for (int totalInsertions = 0; totalInsertions < 200; totalInsertions++) {
    118 
    119       List<Sink> sinks = Lists.newArrayList();
    120       for (int chunkSize = 4; chunkSize <= 32; chunkSize++) {
    121         for (int bufferSize = chunkSize; bufferSize <= chunkSize * 4; bufferSize += chunkSize) {
    122           // yes, that's a lot of sinks!
    123           sinks.add(new Sink(chunkSize, bufferSize));
    124           // For convenience, testing only with big endianness, to match DataOutputStream.
    125           // I regard highly unlikely that both the little endianness tests above and this one
    126           // passes, and there is still a little endianness bug lurking around.
    127         }
    128       }
    129 
    130       Control control = new Control();
    131       Hasher controlSink = control.newHasher(1024);
    132 
    133       Iterable<Hasher> sinksAndControl = Iterables.concat(
    134           sinks, Collections.singleton(controlSink));
    135       for (int insertion = 0; insertion < totalInsertions; insertion++) {
    136         RandomHasherAction.pickAtRandom(random).performAction(random, sinksAndControl);
    137       }
    138       for (Sink sink : sinks) {
    139         sink.hash();
    140       }
    141 
    142       byte[] expected = controlSink.hash().asBytes();
    143       for (Sink sink : sinks) {
    144         sink.assertInvariants(expected.length);
    145         sink.assertBytes(expected);
    146       }
    147     }
    148   }
    149 
    150   private static class Sink extends AbstractStreamingHasher {
    151     final int chunkSize;
    152     final int bufferSize;
    153     final ByteArrayOutputStream out = new ByteArrayOutputStream();
    154 
    155     int processCalled = 0;
    156     boolean remainingCalled = false;
    157 
    158     Sink(int chunkSize, int bufferSize) {
    159       super(chunkSize, bufferSize);
    160       this.chunkSize = chunkSize;
    161       this.bufferSize = bufferSize;
    162     }
    163 
    164     Sink(int chunkSize) {
    165       super(chunkSize);
    166       this.chunkSize = chunkSize;
    167       this.bufferSize = chunkSize;
    168     }
    169 
    170     @Override HashCode makeHash() {
    171       return HashCodes.fromInt(0xDeadBeef);
    172     }
    173 
    174     @Override protected void process(ByteBuffer bb) {
    175       processCalled++;
    176       assertEquals(ByteOrder.LITTLE_ENDIAN, bb.order());
    177       assertTrue(bb.remaining() >= chunkSize);
    178       for (int i = 0; i < chunkSize; i++) {
    179         out.write(bb.get());
    180       }
    181     }
    182 
    183     @Override protected void processRemaining(ByteBuffer bb) {
    184       assertFalse(remainingCalled);
    185       remainingCalled = true;
    186       assertEquals(ByteOrder.LITTLE_ENDIAN, bb.order());
    187       assertTrue(bb.remaining() > 0);
    188       assertTrue(bb.remaining() < bufferSize);
    189       int before = processCalled;
    190       super.processRemaining(bb);
    191       int after = processCalled;
    192       assertEquals(before + 1, after); // default implementation pads and calls process()
    193       processCalled--; // don't count the tail invocation (makes tests a bit more understandable)
    194     }
    195 
    196     // ensures that the number of invocations looks sane
    197     void assertInvariants(int expectedBytes) {
    198       // we should have seen as many bytes as the next multiple of chunk after expectedBytes - 1
    199       assertEquals(out.toByteArray().length, ceilToMultiple(expectedBytes, chunkSize));
    200       assertEquals(expectedBytes / chunkSize, processCalled);
    201       assertEquals(expectedBytes % chunkSize != 0, remainingCalled);
    202     }
    203 
    204     // returns the minimum x such as x >= a && (x % b) == 0
    205     private static int ceilToMultiple(int a, int b) {
    206       int remainder = a % b;
    207       return remainder == 0 ? a : a + b - remainder;
    208     }
    209 
    210     void assertBytes(byte[] expected) {
    211       byte[] got = out.toByteArray();
    212       for (int i = 0; i < expected.length; i++) {
    213         assertEquals(expected[i], got[i]);
    214       }
    215     }
    216   }
    217 
    218   private static class Control extends AbstractNonStreamingHashFunction {
    219     @Override
    220     public HashCode hashBytes(byte[] input) {
    221       return HashCodes.fromBytes(input);
    222     }
    223 
    224     @Override
    225     public HashCode hashBytes(byte[] input, int off, int len) {
    226       return hashBytes(Arrays.copyOfRange(input, off, off + len));
    227     }
    228 
    229     @Override
    230     public int bits() {
    231       throw new UnsupportedOperationException();
    232     }
    233 
    234     @Override
    235     public HashCode hashString(CharSequence input) {
    236       throw new UnsupportedOperationException();
    237     }
    238 
    239     @Override
    240     public HashCode hashString(CharSequence input, Charset charset) {
    241       throw new UnsupportedOperationException();
    242     }
    243 
    244     @Override
    245     public HashCode hashLong(long input) {
    246       throw new UnsupportedOperationException();
    247     }
    248   }
    249 }
    250