package org.smoothbuild.db.hashed; import static com.google.common.io.ByteStreams.toByteArray; import static org.hamcrest.Matchers.not; import static org.smoothbuild.testing.common.ExceptionMatcher.exception; import static org.testory.Testory.given; import static org.testory.Testory.thenReturned; import static org.testory.Testory.thenThrown; import static org.testory.Testory.when; import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.smoothbuild.io.fs.base.Path; import org.smoothbuild.io.fs.mem.MemoryFileSystem; import org.smoothbuild.io.util.TempManager; import com.google.common.hash.HashCode; public class HashedDbTest { private final byte[] bytes1 = new byte[] { 1 }; private final byte[] bytes2 = new byte[] { 1, 2 }; private HashCode hash; private HashedDb hashedDb; private Marshaller marshaller; private HashCode hashId; private Unmarshaller unmarshaller; private MemoryFileSystem fileSystem; @Before public void before() { given(fileSystem = new MemoryFileSystem()); given(hashedDb = new HashedDb(fileSystem, Path.root(), new TempManager(fileSystem))); } @Test public void db_doesnt_contain_not_stored_data() throws Exception { when(hashedDb.contains(HashCode.fromInt(33))); thenReturned(false); } @Test public void db_contains_added_data() throws Exception { given(marshaller = hashedDb.newMarshaller()); given(marshaller).close(); when(hashedDb.contains(marshaller.hash())); thenReturned(true); } @Test public void written_single_byte_can_be_read_back() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(17); given(marshaller).close(); when(hashedDb.newUnmarshaller(marshaller.hash()).read()); thenReturned(17); } @Test public void written_byte_array_can_be_read_back() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(bytes1); given(marshaller).close(); when(toByteArray(hashedDb.newUnmarshaller(marshaller.hash()))); thenReturned(bytes1); } @Test public void written_byte_array_with_range_can_be_read_back() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(new byte[] { 1, 2, 3, 4, 5 }, 1, 3); given(marshaller).close(); when(toByteArray(hashedDb.newUnmarshaller(marshaller.hash()))); thenReturned(new byte[] { 2, 3, 4 }); } @Test public void written_empty_byte_array_can_be_read_back() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).close(); when(toByteArray(hashedDb.newUnmarshaller(marshaller.hash()))); thenReturned(new byte[] {}); } @Test public void written_hash_can_be_read_back() { given(hash = Hash.integer(17)); given(marshaller = hashedDb.newMarshaller()); given(marshaller).writeHash(hash); given(marshaller).close(); when(hashedDb.newUnmarshaller(marshaller.hash()).readHash()); thenReturned(hash); } @Test public void written_int_can_be_read_back() throws Exception { given(marshaller = hashedDb.newMarshaller()); given(marshaller).writeInt(0x12345678); given(marshaller).close(); when(hashedDb.newUnmarshaller(marshaller.hash()).readInt()); thenReturned(0x12345678); } @Test public void reading_int_when_db_has_too_few_bytes_causes_exception() throws Exception { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(new byte[1]); given(marshaller).close(); given(hashId = marshaller.hash()); given(unmarshaller = hashedDb.newUnmarshaller(hashId)); when(unmarshaller).readInt(); thenThrown(exception(new HashedDbException(corruptedMessage("int", hashId, 4, 1)))); } @Test public void written_byte_array_at_given_hash_can_be_read_back() throws IOException { given(hashId = Hash.integer(33)); given(marshaller = hashedDb.newMarshaller(hashId)); given(marshaller).write(bytes1); given(marshaller).close(); when(toByteArray(hashedDb.newUnmarshaller(marshaller.hash()))); thenReturned(bytes1); } @Test public void bytes_written_twice_can_be_read_back() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(bytes1); given(marshaller).close(); given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(bytes1); given(marshaller).close(); when(toByteArray(hashedDb.newUnmarshaller(marshaller.hash()))); thenReturned(bytes1); } @Test public void storing_bytes_at_already_used_hash_is_ignored() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(bytes1); given(marshaller).close(); given(marshaller = hashedDb.newMarshaller(marshaller.hash())); given(marshaller).write(bytes2); given(marshaller).close(); when(toByteArray(hashedDb.newUnmarshaller(marshaller.hash()))); thenReturned(bytes1); } @Test public void hases_for_different_data_are_different() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(bytes1); given(marshaller).close(); given(hashId = marshaller.hash()); given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(bytes2); given(marshaller).close(); when(marshaller.hash()); thenReturned(not(hashId)); } @Test public void reading_not_stored_value_fails() throws Exception { given(hashId = HashCode.fromInt(33)); when(hashedDb).newUnmarshaller(hashId); thenThrown(exception(new HashedDbException("Could not find " + hashId + " object."))); } @Test public void written_data_is_not_visible_until_close_is_invoked() throws Exception { given(hashId = Hash.integer(17)); given(marshaller = hashedDb.newMarshaller(hashId)); given(marshaller).write(new byte[1024 * 1024]); when(hashedDb).newUnmarshaller(hashId); thenThrown(exception(new HashedDbException("Could not find " + hashId + " object."))); } @Test public void reading_hash_when_db_has_too_few_bytes_causes_exception() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(new byte[1]); given(marshaller).close(); given(hashId = marshaller.hash()); when(hashedDb.newUnmarshaller(hashId)).readHash(); thenThrown(exception(new HashedDbException(corruptedMessage("hash", hashId, 20, 1)))); } @Test public void reading_hash_when_db_has_zero_bytes_causes_exception() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(new byte[0]); given(marshaller).close(); given(hashId = marshaller.hash()); when(hashedDb.newUnmarshaller(hashId)).readHash(); thenThrown(exception(new HashedDbException(corruptedMessage("hash", hashId, 20, 0)))); } @Test public void trying_to_read_hash_when_db_has_zero_bytes_returns_null() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(new byte[0]); given(marshaller).close(); when(hashedDb.newUnmarshaller(marshaller.hash())).tryReadHash(); thenReturned(null); } @Test public void trying_to_read_hash_when_db_has_too_few_bytes_causes_exception() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).write(new byte[1]); given(marshaller).close(); given(hashId = marshaller.hash()); when(hashedDb.newUnmarshaller(hashId)).tryReadHash(); thenThrown(exception(new HashedDbException(corruptedMessage("hash", hashId, 20, 1)))); } @Test public void trying_to_read_hash_twice_when_only_one_is_stored_returns_null_second_time() throws IOException { given(marshaller = hashedDb.newMarshaller()); given(marshaller).writeHash(Hash.integer(17)); given(marshaller).close(); given(unmarshaller = hashedDb.newUnmarshaller(marshaller.hash())); given(unmarshaller).tryReadHash(); when(unmarshaller).tryReadHash(); thenReturned(null); } private static String corruptedMessage(String valueName, HashCode hash, int expected, int available) { return "Corrupted " + hash + " object. Value " + valueName + " has expected size = " + expected + " but only " + available + " is available."; } }