From 0703a53daf1571ec28b8e331c97fc5a24ecf549c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Demian=20V=C3=B6hringer?= <demian.voehringer@fau.de> Date: Fri, 17 Jan 2020 18:40:56 +0100 Subject: [PATCH] HashTest angepasst (Meilenstein 4) --- tests/HashTest.java | 524 +++++++++++++++++++++++++------------------- 1 file changed, 295 insertions(+), 229 deletions(-) diff --git a/tests/HashTest.java b/tests/HashTest.java index 8adea33..35d64e5 100644 --- a/tests/HashTest.java +++ b/tests/HashTest.java @@ -26,6 +26,11 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import org.junit.jupiter.api.*; +@FunctionalInterface +interface TSupplier<T> { + T get() throws Exception; +} + public class HashTest { private Function<Integer, ? extends BlockFile> bfgenerator; private Function<Integer, ? extends DBBuffer> bufferGenerator; @@ -35,14 +40,15 @@ public class HashTest { private CountingDBBuffer buf; @BeforeEach - public void init() throws IOException{ + public void init() throws IOException { bfgenerator = x -> Util.generateBlockFile(x); bufferGenerator = Util::generateSimpleBuffer; testFile = File.createTempFile("foobar", null); testFileO = File.createTempFile("foobar-overflow", null); } - private <D extends DataObject, K extends Key> KeyRecordFile<D, K> generate(int pageSize, double thresh, int initCapacity) throws IOException { + private <D extends DataObject, K extends Key> KeyRecordFile<D, K> generate(int pageSize, double thresh, + int initCapacity) throws IOException { bf = new GarbageBlockFile(cbf = new CountingBlockFile(bfgenerator.apply(pageSize)), pageSize); bf.open(testFile.getCanonicalPath(), "rw"); bfO = new GarbageBlockFile(cbfO = new CountingBlockFile(bfgenerator.apply(pageSize)), pageSize); @@ -50,46 +56,68 @@ public class HashTest { buf = new CountingDBBuffer(bufferGenerator.apply(pageSize)); return Util.<K, D>generateHash(buf, bf, bfO, thresh, initCapacity); } + @AfterEach public void cleanup() throws Exception { - if (buf != null) buf.close(); + if (buf != null) + buf.close(); buf = null; - if (bf != null) bf.close(); + if (bf != null) + bf.close(); bf = null; - if (bfO != null) bfO.close(); + if (bfO != null) + bfO.close(); bfO = null; testFile.delete(); testFileO.delete(); } + + private <T> T assertBlockRead(CountingBlockFile bfCur, int pageNr, TSupplier<T> delayed) throws Exception { + bfCur.setExpRead(pageNr); + ByteBuffer bb = buf.fix(bfCur, pageNr); // fixing the page manually does allow us to make sure that multiple fixes in delayed do not count. + bfCur.reset(); + T res = delayed.get(); + assertEquals(0, bfCur.getReads()); // Make sure that no other pages are needed + buf.unfix(bfCur, pageNr); + buf.flush(); + assertEquals(0, bfCur.getWrites()); // Make sure no setDirty was called + return res; + } + @Test public void readWriteNoOverflow() throws Exception { KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.5, 7); cbfO.reset(); - for(int i=0; i < 10; ++i){ - krf.insert(new IntegerKey(i), new VariableRecord(10*i+10)); // this is less than 200 bytes, so it should not trigger an overflow or split + for (int i = 0; i < 10; ++i) { + krf.insert(new IntegerKey(i), new VariableRecord(10 * i + 10)); // this is less than 200 bytes, so it should + // not trigger an overflow or split } buf.flush(); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - for(int i=0; i < 10; ++i){ + for (int i = 0; i < 10; ++i) { assertEquals(1, krf.size(new IntegerKey(i), new VariableRecord(0))); } - for (int i=0; i < 10; ++i){ + for (int i = 0; i < 10; ++i) { VariableRecord vr = new VariableRecord(1); krf.read(new IntegerKey(i), vr, 0); final int j = i; - assertThrows(ArrayIndexOutOfBoundsException.class, () -> krf.read(new IntegerKey(j), new VariableRecord(1), 1)); - assertEquals((i+1)*10*Integer.SIZE / Byte.SIZE, vr.size()); + assertThrows(ArrayIndexOutOfBoundsException.class, + () -> krf.read(new IntegerKey(j), new VariableRecord(1), 1)); + assertEquals((i + 1) * 10 * Integer.SIZE / Byte.SIZE, vr.size(), "In run" + i); } buf.flush(); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); } + @Test public void readWriteForeign() throws Exception { KeyRecordFile<VariableRecord, IntegerKey> krfI = generate(4096, 0.5, 7); - for(int i=0; i < 10; ++i){ - krfI.insert(new IntegerKey(i), new VariableRecord(10*i+10)); // this is less than 200 bytes, so it should not trigger an overflow or split + for (int i = 0; i < 10; ++i) { + krfI.insert(new IntegerKey(i), new VariableRecord(10 * i + 10)); // this is less than 200 bytes, so it + // should not trigger an overflow or + // split } krfI.close(); buf.flush(); @@ -101,61 +129,69 @@ public class HashTest { assertEquals(1, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); cbfO.reset(); - for(int i=0; i < 10; ++i){ + for (int i = 0; i < 10; ++i) { assertEquals(1, krf.size(new IntegerKey(i), new VariableRecord(0))); } - for (int i=0; i < 10; ++i){ + for (int i = 0; i < 10; ++i) { VariableRecord vr = new VariableRecord(1); krf.read(new IntegerKey(i), vr, 0); final int j = i; - assertThrows(ArrayIndexOutOfBoundsException.class, () -> krf.read(new IntegerKey(j), new VariableRecord(1), 1)); - assertEquals((i+1)*10*Integer.SIZE / Byte.SIZE, vr.size()); + assertThrows(ArrayIndexOutOfBoundsException.class, + () -> krf.read(new IntegerKey(j), new VariableRecord(1), 1)); + assertEquals((i + 1) * 10 * Integer.SIZE / Byte.SIZE, vr.size()); } buf.flush(); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - for(int i=0; i < 5; ++i){ - krfI.insert(new IntegerKey(10+i), new VariableRecord(10*i+10)); // this is less than additional 100 bytes, so it should not trigger an overflow or split + for (int i = 0; i < 5; ++i) { + krfI.insert(new IntegerKey(10 + i), new VariableRecord(10 * i + 10)); // this is less than additional 100 + // bytes, so it should not trigger + // an overflow or split } buf.flush(); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - for (int i=0; i < 15; ++i){ + for (int i = 0; i < 15; ++i) { VariableRecord vr = new VariableRecord(1); krf.read(new IntegerKey(i), vr, 0); final int j = i; - assertThrows(ArrayIndexOutOfBoundsException.class, () -> krf.read(new IntegerKey(j), new VariableRecord(1), 1)); + assertThrows(ArrayIndexOutOfBoundsException.class, + () -> krf.read(new IntegerKey(j), new VariableRecord(1), 1)); int k = i; - if (i >= 10) k = i - 10; - assertEquals((k+1)*10*Integer.SIZE / Byte.SIZE, vr.size()); + if (i >= 10) + k = i - 10; + assertEquals((k + 1) * 10 * Integer.SIZE / Byte.SIZE, vr.size()); } } - private ByteBuffer randomBB(int size, long seed){ + + private ByteBuffer randomBB(int size, long seed) { byte[] arr = new byte[size]; new Random(seed).nextBytes(arr); return ByteBuffer.wrap(arr); } + // @param max: exclusive upper bound // @min: 1 private ArrayList<VariableRecord> randomVR(int size, int max) { ArrayList<VariableRecord> ret = new ArrayList<>(); Random r = new Random(1337); - for (int i=0; i < size; ++i){ - ret.add(new VariableRecord(r.nextInt(max-1)+1)); + for (int i = 0; i < size; ++i) { + ret.add(new VariableRecord(r.nextInt(max - 1) + 1)); } return ret; } + @RepeatedTest(10) public void garbage(RepetitionInfo repetitionInfo) throws Exception { ArrayList<VariableRecord> data = randomVR(1000, 100); // 100 ints is less than 4000 (bytes) KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.5, 7); bf.setGarbage(randomBB(4096, repetitionInfo.getCurrentRepetition())); - bfO.setGarbage(randomBB(4096, 50*repetitionInfo.getCurrentRepetition())); + bfO.setGarbage(randomBB(4096, 50 * repetitionInfo.getCurrentRepetition())); for (int i = 0; i < data.size(); ++i) { krf.insert(new IntegerKey(i), data.get(i)); } assertEquals(buf.getFix(), buf.getUnfix()); - for (int i = 0 ; i < data.size(); ++i){ + for (int i = 0; i < data.size(); ++i) { VariableRecord ret = new VariableRecord(0); assertEquals(1, krf.size(new IntegerKey(i), ret)); krf.read(new IntegerKey(i), ret, 0); @@ -163,23 +199,26 @@ public class HashTest { } data = randomVR(2000, 100); // 100 ints is less than 4000 (bytes) for (int i = 1000; i < data.size(); ++i) { - krf.insert(new IntegerKey(i-1000), data.get(i)); + krf.insert(new IntegerKey(i - 1000), data.get(i)); } - for (int i = 0 ; i < 1000; ++i){ + for (int i = 0; i < 1000; ++i) { VariableRecord ret = new VariableRecord(0); - assertEquals(2, krf.size(new IntegerKey(i), ret), "Int run: "+ i); + assertEquals(2, krf.size(new IntegerKey(i), ret), "Int run: " + i); krf.read(new IntegerKey(i), ret, 0); assertEquals(data.get(i).size(), ret.size()); krf.read(new IntegerKey(i), ret, 1); - assertEquals(data.get(i+1000).size(), ret.size()); - krf.delete(new IntegerKey(i), 1, ret); // This is allowed as the order must be preserved when removing the last element, as stated in @insert. - assertEquals(1, krf.size(new IntegerKey(i), ret), "Int run: "+ i); + assertEquals(data.get(i + 1000).size(), ret.size()); + krf.delete(new IntegerKey(i), 1, ret); // This is allowed as the order must be preserved when removing the + // last element, as stated in @insert. + assertEquals(1, krf.size(new IntegerKey(i), ret), "Int run: " + i); krf.read(new IntegerKey(i), ret, 0); assertEquals(data.get(i).size(), ret.size()); final int j = i; - assertThrows(ArrayIndexOutOfBoundsException.class, () -> krf.read(new IntegerKey(j), new VariableRecord(1), 1)); + assertThrows(ArrayIndexOutOfBoundsException.class, + () -> krf.read(new IntegerKey(j), new VariableRecord(1), 1)); } } + @Test public void readWriteMixedOverflow() throws Exception { KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.5, 7); @@ -188,62 +227,75 @@ public class HashTest { buf.flush(); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - krf.insert(new IntegerKey(7), new VariableRecord(1000)); // this is too large the for bucket, so it has to go in overflow + krf.insert(new IntegerKey(7), new VariableRecord(1000)); // this is too large the for bucket, so it has to go in + // overflow buf.flush(); - assertThat(cbfO.getReads(), either(equalTo(1)).or(equalTo(2))); // We need to read the meta-block at the beginning of the overflow segment to know where to get a free block. - //TODO: this is if free list is optional - assertEquals(2, cbfO.getWrites()); // As appending is mapped to writes in GarbageBlockFile, we want 2 (one for appending and one for inserting the data) + assertThat(cbfO.getReads(), either(equalTo(1)).or(equalTo(2))); // We need to read the meta-block at the + // beginning of the overflow segment to know + // where to get a free block. + // TODO: this is if free list is optional + assertEquals(2, cbfO.getWrites()); // As appending is mapped to writes in GarbageBlockFile, we want 2 (one for + // appending and one for inserting the data) cbfO.reset(); - krf.insert(new IntegerKey(14), new VariableRecord(10)); // while this does fit into bucket1, one has to make sure that no 14 is stored in the overflow. + krf.insert(new IntegerKey(14), new VariableRecord(10)); // while this does fit into bucket1, one has to make + // sure that no 14 is stored in the overflow. buf.flush(); // Therefore we don't check Writes, but only reads - assertEquals(1, cbfO.getReads()); + assertNotEquals(0, cbfO.getReads()); cbf.reset(); // All should be in the same bucket, bucket 0 - cbf.setExpRead(0); + assertEquals(1, assertBlockRead(cbf, 0, () -> krf.size(new IntegerKey(0), new VariableRecord(0)))); + // Rerun without assertBlockRead to make sure use-after-free could be detectable assertEquals(1, krf.size(new IntegerKey(0), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - cbf.reset(); + VariableRecord vr = new VariableRecord(1); - cbf.setExpRead(0); krf.read(new IntegerKey(0), vr, 0); - assertEquals(1, cbf.getReads()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); cbf.reset(); - assertEquals(100*Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 0, () -> {krf.read(new IntegerKey(0), vr, 0); return null;}); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); + // Repeat for 7 and 14 - cbf.setExpRead(0); + assertEquals(1, assertBlockRead(cbf, 0, () -> krf.size(new IntegerKey(7), new VariableRecord(0)))); assertEquals(1, krf.size(new IntegerKey(7), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - cbf.reset(); - cbf.setExpRead(0); + krf.read(new IntegerKey(7), vr, 0); - assertEquals(1, cbf.getReads()); + assertEquals(1000 * Integer.SIZE / Byte.SIZE, vr.size()); cbf.reset(); - assertEquals(1000*Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 0, () -> {krf.read(new IntegerKey(7), vr, 0); return null;}); + assertEquals(1000 * Integer.SIZE / Byte.SIZE, vr.size()); + // Repeat for 14 - cbf.setExpRead(0); assertEquals(1, krf.size(new IntegerKey(14), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); cbf.reset(); - cbf.setExpRead(0); + assertEquals(1, assertBlockRead(cbf, 0, () -> krf.size(new IntegerKey(14), new VariableRecord(0)))); + krf.read(new IntegerKey(14), vr, 0); - assertEquals(1, cbf.getReads()); + assertEquals(10 * Integer.SIZE / Byte.SIZE, vr.size()); cbf.reset(); - assertEquals(10*Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 0, () -> {krf.read(new IntegerKey(14), vr, 0); return null;}); + assertEquals(10 * Integer.SIZE / Byte.SIZE, vr.size()); } + @Test public void doubleSplit() throws Exception { - KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.2, 7); // setting the threshhold to 0.2 should trigger a split after 1024*7/5 = 1433 Ints, after that at 1024 * 8/5 = 1638 + KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.2, 7); // setting the threshhold to 0.2 should + // trigger a split after 1024*7/5 = 1433 + // Ints, after that at 1024 * 8/5 = 1638 cbfO.reset(); krf.insert(new IntegerKey(0), new VariableRecord(1000)); buf.flush(); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - krf.insert(new IntegerKey(7), new VariableRecord(400)); // this is too large the for bucket, so it has to go in overflow + krf.insert(new IntegerKey(7), new VariableRecord(400)); // this is too large the for bucket, so it has to go in + // overflow buf.flush(); - assertThat(cbfO.getReads(), either(equalTo(1)).or(equalTo(2))); // We need to read the meta-block at the beginning of the overflow segment to know where to get a free block. - //TODO: this is if free list is optional - assertEquals(2, cbfO.getWrites()); // As appending is mapped to writes in GarbageBlockFile, we want 2 (one for appending and one for inserting the data) + assertThat(cbfO.getReads(), either(equalTo(1)).or(equalTo(2))); // We need to read the meta-block at the + // beginning of the overflow segment to know + // where to get a free block. + // TODO: this is if free list is optional + assertEquals(2, cbfO.getWrites()); // As appending is mapped to writes in GarbageBlockFile, we want 2 (one for + // appending and one for inserting the data) krf.insert(new IntegerKey(21), new VariableRecord(100)); buf.flush(); cbfO.reset(); @@ -254,226 +306,215 @@ public class HashTest { assertEquals(0, cbfO.getWrites()); cbfO.reset(); cbf.reset(); + // 0 should be in the bucket 0 - cbf.setExpRead(0); assertEquals(1, krf.size(new IntegerKey(0), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 0, () -> krf.size(new IntegerKey(0), new VariableRecord(0)))); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); + VariableRecord vr = new VariableRecord(1); - cbf.setExpRead(0); krf.read(new IntegerKey(0), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1000 * Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 0, () -> {krf.read(new IntegerKey(0), vr, 0); return null;}); + assertEquals(1000 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(1000*Integer.SIZE / Byte.SIZE, vr.size()); + // Repeat for 7 and 21 - cbf.setExpRead(7); assertEquals(1, krf.size(new IntegerKey(7), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 7, () -> krf.size(new IntegerKey(7), new VariableRecord(0)))); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + + assertBlockRead(cbf, 7, () -> {krf.read(new IntegerKey(7), vr, 0); return null;}); + assertEquals(400 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(7); krf.read(new IntegerKey(7), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(400 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(400*Integer.SIZE / Byte.SIZE, vr.size()); + // Repeat for 21 - cbf.setExpRead(7); assertEquals(1, krf.size(new IntegerKey(21), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 7, () -> krf.size(new IntegerKey(21), new VariableRecord(0)))); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(7); + krf.read(new IntegerKey(21), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(100*Integer.SIZE / Byte.SIZE, vr.size()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 7, () -> {krf.read(new IntegerKey(21), vr, 0); return null;}); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + // Repeat for 8 - cbf.setExpRead(1); assertEquals(1, krf.size(new IntegerKey(8), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 1, () -> krf.size(new IntegerKey(8), new VariableRecord(0)))); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(1); + krf.read(new IntegerKey(8), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(100*Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 1, () -> {krf.read(new IntegerKey(8), vr, 0); return null;}); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); + krf.insert(new IntegerKey(9), new VariableRecord(100)); buf.flush(); cbfO.reset(); cbf.reset(); + // 0 should be in the bucket 0 - cbf.setExpRead(0); assertEquals(1, krf.size(new IntegerKey(0), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 0, () -> krf.size(new IntegerKey(0), new VariableRecord(0)))); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(0); + krf.read(new IntegerKey(0), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1000 * Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 0, () -> {krf.read(new IntegerKey(0), vr, 0); return null;}); + assertEquals(1000 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(1000*Integer.SIZE / Byte.SIZE, vr.size()); + // Repeat for 7 and 21 - cbf.setExpRead(7); assertEquals(1, krf.size(new IntegerKey(7), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 7, () -> krf.size(new IntegerKey(7), new VariableRecord(0)))); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + + assertBlockRead(cbf, 7, () -> {krf.read(new IntegerKey(7), vr, 0); return null;}); + assertEquals(400 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(7); krf.read(new IntegerKey(7), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(400 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(400*Integer.SIZE / Byte.SIZE, vr.size()); + // Repeat for 21 - cbf.setExpRead(7); assertEquals(1, krf.size(new IntegerKey(21), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 7, () -> krf.size(new IntegerKey(21), new VariableRecord(0)))); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(7); + krf.read(new IntegerKey(21), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(100*Integer.SIZE / Byte.SIZE, vr.size()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 7, () -> {krf.read(new IntegerKey(21), vr, 0); return null;}); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + // Repeat for 8 - cbf.setExpRead(8); assertEquals(1, krf.size(new IntegerKey(8), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 8, () -> krf.size(new IntegerKey(8), new VariableRecord(0)))); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(8); + krf.read(new IntegerKey(8), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(100*Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 8, () -> {krf.read(new IntegerKey(8), vr, 0); return null;}); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); + // Repeat for 9 - cbf.setExpRead(2); assertEquals(1, krf.size(new IntegerKey(9), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 2, () -> krf.size(new IntegerKey(9), new VariableRecord(0)))); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(2); + krf.read(new IntegerKey(9), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(100*Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 2, () -> {krf.read(new IntegerKey(9), vr, 0); return null;}); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); } + @Test public void readWriteMixedSplit() throws Exception { - KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.2, 7); // setting the threshhold to 0.2 should trigger a split after 1024*7/5 = 1433 Ints + KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.2, 7); // setting the threshhold to 0.2 should + // trigger a split after 1024*7/5 = 1433 + // Ints cbfO.reset(); krf.insert(new IntegerKey(0), new VariableRecord(1000)); buf.flush(); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - krf.insert(new IntegerKey(7), new VariableRecord(400)); // this is too large the for bucket, so it has to go in overflow + krf.insert(new IntegerKey(7), new VariableRecord(400)); // this is too large the for bucket, so it has to go in + // overflow buf.flush(); - assertThat(cbfO.getReads(), either(equalTo(1)).or(equalTo(2))); // We need to read the meta-block at the beginning of the overflow segment to know where to get a free block. - //TODO: this is if free list is optional - assertEquals(2, cbfO.getWrites()); // As appending is mapped to writes in GarbageBlockFile, we want 2 (one for appending and one for inserting the data) + assertThat(cbfO.getReads(), either(equalTo(1)).or(equalTo(2))); // We need to read the meta-block at the + // beginning of the overflow segment to know + // where to get a free block. + // TODO: this is if free list is optional + assertEquals(2, cbfO.getWrites()); // As appending is mapped to writes in GarbageBlockFile, we want 2 (one for + // appending and one for inserting the data) krf.insert(new IntegerKey(21), new VariableRecord(100)); buf.flush(); cbfO.reset(); cbf.reset(); - // 0 should be in the bucket 0 - cbf.setExpRead(0); + + VariableRecord vr = new VariableRecord(1); + // 0 should be in bucket 0 assertEquals(1, krf.size(new IntegerKey(0), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 0, () -> krf.size(new IntegerKey(0), new VariableRecord(0)))); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - VariableRecord vr = new VariableRecord(1); - cbf.setExpRead(0); + krf.read(new IntegerKey(0), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1000 * Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 0, () -> {krf.read(new IntegerKey(0), vr, 0); return null;}); + assertEquals(1000 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(1000*Integer.SIZE / Byte.SIZE, vr.size()); - // Repeat for 7 and 21 - cbf.setExpRead(7); + + // Repeat for 7 assertEquals(1, krf.size(new IntegerKey(7), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 7, () -> krf.size(new IntegerKey(7), new VariableRecord(0)))); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(7); + krf.read(new IntegerKey(7), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(400 * Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 7, () -> {krf.read(new IntegerKey(7), vr, 0); return null;}); + assertEquals(400 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(400*Integer.SIZE / Byte.SIZE, vr.size()); + // Repeat for 21 - cbf.setExpRead(7); assertEquals(1, krf.size(new IntegerKey(21), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(1, assertBlockRead(cbf, 7, () -> krf.size(new IntegerKey(21), new VariableRecord(0)))); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(7); + krf.read(new IntegerKey(21), vr, 0); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 7, () -> {krf.read(new IntegerKey(21), vr, 0); return null;}); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(100*Integer.SIZE / Byte.SIZE, vr.size()); } + @Test public void reclaimSpace() throws Exception { - KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.2, 7); // setting the threshhold to 0.2 should trigger a split after 1024*7/5 = 1433 Ints + KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.2, 7); // setting the threshhold to 0.2 should + // trigger a split after 1024*7/5 = 1433 + // Ints cbfO.reset(); krf.insert(new IntegerKey(0), new VariableRecord(1000)); buf.flush(); @@ -494,39 +535,45 @@ public class HashTest { cbfO.reset(); cbf.reset(); VariableRecord vr = new VariableRecord(0); - // Read 7 and 21 - cbf.setExpRead(2); + // Read 2 assertEquals(1, krf.size(new IntegerKey(2), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); - assertEquals(0, cbf.getWrites()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(2); + assertEquals(1, assertBlockRead(cbf, 2, () -> krf.size(new IntegerKey(2), new VariableRecord(0)))); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + krf.read(new IntegerKey(2), vr, 0); - assertEquals(1, cbf.getReads()); assertEquals(0, cbf.getWrites()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(400*Integer.SIZE / Byte.SIZE, vr.size()); - // Repeat for 21 - cbf.setExpRead(0); - assertEquals(1, krf.size(new IntegerKey(21), new VariableRecord(0))); - assertEquals(1, cbf.getReads()); + assertEquals(400 * Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 2, () -> {krf.read(new IntegerKey(2), vr, 0); return null;}); assertEquals(0, cbf.getWrites()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - cbf.setExpRead(0); + assertEquals(400 * Integer.SIZE / Byte.SIZE, vr.size()); + + // Read 21 + assertEquals(1, krf.size(new IntegerKey(21), new VariableRecord(0))); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + assertEquals(1, assertBlockRead(cbf, 0, () -> krf.size(new IntegerKey(21), new VariableRecord(0)))); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + krf.read(new IntegerKey(21), vr, 0); - assertEquals(1, cbf.getReads()); assertEquals(0, cbf.getWrites()); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - cbf.reset(); - assertEquals(100*Integer.SIZE / Byte.SIZE, vr.size()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); + assertBlockRead(cbf, 0, () -> {krf.read(new IntegerKey(21), vr, 0); return null;}); + assertEquals(0, cbf.getWrites()); + assertEquals(0, cbfO.getReads()); + assertEquals(0, cbfO.getWrites()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); } + @Test public void readWriteSameOverflow() throws Exception { KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.5, 7); @@ -535,27 +582,33 @@ public class HashTest { buf.flush(); assertEquals(0, cbfO.getReads()); assertEquals(0, cbfO.getWrites()); - krf.insert(new IntegerKey(0), new VariableRecord(1000)); // this is too large the for bucket, so it has to go in overflow + krf.insert(new IntegerKey(0), new VariableRecord(1000)); // this is too large the for bucket, so it has to go in + // overflow buf.flush(); - assertThat(cbfO.getReads(), either(equalTo(1)).or(equalTo(2))); // We need to read the meta-block at the beginning of the overflow segment to know where to get a free block. - //TODO: this is if free list is optional - assertEquals(2, cbfO.getWrites()); // As appending is mapped to writes in GarbageBlockFile, we want 2 (one for appending and one for inserting the data) + assertThat(cbfO.getReads(), either(equalTo(1)).or(equalTo(2))); // We need to read the meta-block at the + // beginning of the overflow segment to know + // where to get a free block. + // TODO: this is if free list is optional + assertEquals(2, cbfO.getWrites()); // As appending is mapped to writes in GarbageBlockFile, we want 2 (one for + // appending and one for inserting the data) cbfO.reset(); - krf.insert(new IntegerKey(0), new VariableRecord(10)); // while this does fit into bucket1, one has to make sure that the index is correct + krf.insert(new IntegerKey(0), new VariableRecord(10)); // while this does fit into bucket1, one has to make sure + // that the index is correct assertEquals(3, krf.size(new IntegerKey(0), new VariableRecord(0))); VariableRecord vr = new VariableRecord(1); krf.read(new IntegerKey(0), vr, 0); - assertEquals(100*Integer.SIZE / Byte.SIZE, vr.size()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); krf.read(new IntegerKey(0), vr, 1); - assertEquals(1000*Integer.SIZE / Byte.SIZE, vr.size()); + assertEquals(1000 * Integer.SIZE / Byte.SIZE, vr.size()); krf.read(new IntegerKey(0), vr, 2); - assertEquals(10*Integer.SIZE / Byte.SIZE, vr.size()); + assertEquals(10 * Integer.SIZE / Byte.SIZE, vr.size()); krf.delete(new IntegerKey(0), 2, vr); krf.read(new IntegerKey(0), vr, 0); - assertEquals(100*Integer.SIZE / Byte.SIZE, vr.size()); + assertEquals(100 * Integer.SIZE / Byte.SIZE, vr.size()); krf.read(new IntegerKey(0), vr, 1); - assertEquals(1000*Integer.SIZE / Byte.SIZE, vr.size()); + assertEquals(1000 * Integer.SIZE / Byte.SIZE, vr.size()); } + @Test public void dbStrings() throws Exception { KeyRecordFile<DBString, DBString> krf = generate(4096, 0.5, 7); @@ -578,15 +631,16 @@ public class HashTest { krf.read(new DBString("Umlauts"), out, 0); assertEquals("äöüß", out.content()); } + @Test public void loadTest() throws Exception { KeyRecordFile<VariableRecord, IntegerKey> krf = generate(4096, 0.5, 7); HashMap<Integer, ArrayList<Integer>> inMemory = new HashMap<>(); // key->(index->Value) Random r = new Random(13763); - for (int i=0; i < 100; ++i){ + for (int i = 0; i < 100; ++i) { insert(krf, inMemory, r); } - for (int i=0; i < 2_000; ++i){ + for (int i = 0; i < 2_000; ++i) { if (r.nextInt(2) == 1) { insert(krf, inMemory, r); } @@ -605,55 +659,63 @@ public class HashTest { } } - private void refresh(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory) throws IOException, BufferFullException{ + private void refresh(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory) + throws IOException, BufferFullException { HashMap<Integer, ArrayList<Integer>> out = new HashMap<>(); - for(Integer k : inMemory.keySet()){ + for (Integer k : inMemory.keySet()) { int sz = krf.size(new IntegerKey(k), new VariableRecord(1)); ArrayList<Integer> curList = inMemory.get(k); assertEquals(curList.size(), sz, "For Key: " + k); ArrayList<Integer> actual = new ArrayList<>(sz); - for(int i=0; i < sz; ++i){ + for (int i = 0; i < sz; ++i) { VariableRecord vr = new VariableRecord(1); krf.read(new IntegerKey(k), vr, i); actual.add(vr.size()); } - assertTrue(actual.stream().allMatch(v -> curList.contains(v)), "Actual and previous do not contain the same content"); + assertTrue(actual.stream().allMatch(v -> curList.contains(v)), + "Actual and previous do not contain the same content"); out.put(k, actual); } inMemory.clear(); inMemory.putAll(out); } - private void insert(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory, Random r) throws IOException, BufferFullException{ + private void insert(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory, + Random r) throws IOException, BufferFullException { int key = r.nextInt(500); - int amm = r.nextInt(500)+1; + int amm = r.nextInt(500) + 1; VariableRecord vr = new VariableRecord(amm); inMemory.computeIfAbsent(key, ArrayList::new).add(vr.size()); krf.insert(new IntegerKey(key), vr); check(krf, inMemory); } - private void check(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory) throws IOException, BufferFullException{ - for(Integer k : inMemory.keySet()){ + private void check(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory) + throws IOException, BufferFullException { + for (Integer k : inMemory.keySet()) { int sz = krf.size(new IntegerKey(k), new VariableRecord(1)); ArrayList<Integer> curList = inMemory.get(k); assertEquals(curList.size(), sz); - for(int i=0; i < sz; ++i){ + for (int i = 0; i < sz; ++i) { VariableRecord vr = new VariableRecord(1); krf.read(new IntegerKey(k), vr, i); assertEquals(curList.get(i), vr.size()); } final int j = k; - assertThrows(ArrayIndexOutOfBoundsException.class, ()->krf.read(new IntegerKey(j), new VariableRecord(1), sz)); + assertThrows(ArrayIndexOutOfBoundsException.class, + () -> krf.read(new IntegerKey(j), new VariableRecord(1), sz)); } } - private void delete(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory, Random r) throws IOException, BufferFullException{ + + private void delete(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory, + Random r) throws IOException, BufferFullException { int key = choice(inMemory.keySet(), r); ArrayList<Integer> cur = inMemory.get(key); - if (cur.size() == 0) return; + if (cur.size() == 0) + return; if (r.nextInt(5) < 2) { // remove last element - cur.remove(cur.size()-1); + cur.remove(cur.size() - 1); krf.delete(new IntegerKey(key), cur.size(), new VariableRecord(1)); check(krf, inMemory); return; @@ -664,7 +726,8 @@ public class HashTest { refresh(krf, inMemory); } - private void modifiy(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory, Random r) throws IOException, BufferFullException{ + private void modifiy(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory, + Random r) throws IOException, BufferFullException { int key = choice(inMemory.keySet(), r); ArrayList<Integer> cur = inMemory.get(key); if (cur.size() == 0) { @@ -672,18 +735,20 @@ public class HashTest { return; } int idx = r.nextInt(cur.size()); - int amm = r.nextInt(500)+1; + int amm = r.nextInt(500) + 1; VariableRecord vr = new VariableRecord(amm); cur.set(idx, vr.size()); krf.modify(new IntegerKey(key), idx, vr); refresh(krf, inMemory); } - private void modifiyKey(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory, Random r) throws IOException, BufferFullException{ + private void modifiyKey(KeyRecordFile<VariableRecord, IntegerKey> krf, + HashMap<Integer, ArrayList<Integer>> inMemory, Random r) throws IOException, BufferFullException { int key = choice(inMemory.keySet(), r); ArrayList<Integer> cur = inMemory.get(key); int key2 = choice(inMemory.keySet(), r); - if (key == key2) return; + if (key == key2) + return; ArrayList<Integer> cur2 = inMemory.get(key2); cur2.addAll(cur); cur.clear(); @@ -691,7 +756,8 @@ public class HashTest { refresh(krf, inMemory); } - private void deleteAll(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory, Random r) throws IOException, BufferFullException{ + private void deleteAll(KeyRecordFile<VariableRecord, IntegerKey> krf, HashMap<Integer, ArrayList<Integer>> inMemory, + Random r) throws IOException, BufferFullException { int key = choice(inMemory.keySet(), r); ArrayList<Integer> cur = inMemory.get(key); cur.clear(); @@ -699,7 +765,7 @@ public class HashTest { refresh(krf, inMemory); } - //from: https://stackoverflow.com/a/25410520 + // from: https://stackoverflow.com/a/25410520 public static <E> E choice(Collection<? extends E> coll, Random rand) { if (coll.size() == 0) { return null; // or throw IAE, if you prefer -- GitLab