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