
org.jsimpledb.kv.test.AtomicKVStoreTest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jsimpledb-kv-test Show documentation
Show all versions of jsimpledb-kv-test Show documentation
JSimpleDB unit test classes for key/value store implementations.
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package org.jsimpledb.kv.test;
import com.google.common.base.Converter;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;
import org.jsimpledb.kv.CloseableKVStore;
import org.jsimpledb.kv.KVPair;
import org.jsimpledb.kv.KVStore;
import org.jsimpledb.kv.KeyRange;
import org.jsimpledb.kv.mvcc.AtomicKVStore;
import org.jsimpledb.kv.mvcc.Writes;
import org.jsimpledb.util.ByteUtil;
import org.jsimpledb.util.ConvertedNavigableMap;
import org.jsimpledb.util.LongEncoder;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public abstract class AtomicKVStoreTest extends KVTestSupport {
private File dir;
@AfterClass
public void cleanup() throws IOException {
Files.walkFileTree(this.dir.toPath(), new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
@DataProvider(name = "kvstores")
public AtomicKVStore[][] genAtomicKVStores() throws Exception {
// Create directory
this.dir = File.createTempFile(this.getClass().getSimpleName(), null);
if (!this.dir.delete() || !this.dir.mkdirs())
throw new IOException("can't create " + dir);
final ArrayList list = new ArrayList<>();
// Get AtomicKVStore(s)
this.addAtomicKVStores(dir, list);
// Build array
final AtomicKVStore[][] array = new AtomicKVStore[list.size()][];
for (int i = 0; i < array.length; i++)
array[i] = new AtomicKVStore[] { list.get(i) };
return array;
}
protected void addAtomicKVStores(File dir, List list) throws Exception {
list.add(this.createAtomicKVStore(dir));
}
protected abstract AtomicKVStore createAtomicKVStore(File dir) throws Exception;
@Test(dataProvider = "kvstores")
public void testAtomicKVStore(AtomicKVStore kv) throws Exception {
// Start kvstore
kv.start();
// Test
final TreeMap map = new TreeMap(ByteUtil.COMPARATOR);
for (int count = 0; count < 200; count++) {
this.log.trace("[" + count + "] next iteration");
Writes writes;
// Do puts atomically
writes = this.getPuts(count, map);
kv.mutate(writes, true);
this.compare(this.read(count, kv), map);
Thread.sleep(5);
// Do removes non-atomically
writes = this.getRemoves(count, map);
writes.applyTo(kv);
this.compare(this.read(count, kv), map);
Thread.sleep(5);
// Do puts non-atomically
writes = this.getPuts(count, map);
writes.applyTo(kv);
this.compare(this.read(count, kv), map);
Thread.sleep(5);
// Do removes atomically
writes = this.getRemoves(count, map);
kv.mutate(writes, true);
this.compare(this.read(count, kv), map);
Thread.sleep(5);
}
// Stop kvstore
kv.stop();
}
private Writes getPuts(int count, TreeMap map) {
final Writes writes = new Writes();
for (int i = 0; i < 16; i++) {
final byte[] key = new byte[] { (byte)this.random.nextInt(0xff) };
final byte[] key2 = new byte[] { (byte)this.random.nextInt(0xff), (byte)this.random.nextInt(0xff) };
final byte[] value = LongEncoder.encode((1 << i) + i);
this.log.trace("[" + count + "]: PUT: " + ByteUtil.toString(key) + " -> " + ByteUtil.toString(value));
writes.getPuts().put(key, value);
this.log.trace("[" + count + "]: PUT: " + ByteUtil.toString(key2) + " -> " + ByteUtil.toString(value));
writes.getPuts().put(key2, value);
map.put(key, value);
map.put(key2, value);
}
return writes;
}
private Writes getRemoves(int count, TreeMap map) {
final Writes writes = new Writes();
for (int i = 0; i < 9; i++) {
if (this.random.nextInt(5) > 0) {
final byte[] key = new byte[] { (byte)this.random.nextInt(0xff) };
this.log.trace("[" + count + "]: REMOVE: " + ByteUtil.toString(key));
writes.setRemoves(writes.getRemoves().add(new KeyRange(key)));
map.remove(key);
} else {
final byte[] x = this.random.nextInt(10) == 0 ? new byte[0] : new byte[] { (byte)this.random.nextInt(0xff) };
final byte[] y = this.random.nextInt(10) == 0 ? null : new byte[] { (byte)this.random.nextInt(0xff) };
final byte[] minKey = y == null || ByteUtil.compare(x, y) < 0 ? x : y;
final byte[] maxKey = y == null || ByteUtil.compare(x, y) < 0 ? y : x;
this.log.trace("[" + count + "]: REMOVE: [" + ByteUtil.toString(minKey) + ", " + ByteUtil.toString(maxKey) + ")");
writes.setRemoves(writes.getRemoves().add(new KeyRange(minKey, maxKey)));
if (maxKey == null)
map.tailMap(minKey, true).clear();
else
map.subMap(minKey, true, maxKey, false).clear();
}
}
return writes;
}
private TreeMap read(int count, AtomicKVStore kv) {
return this.read(count, kv, ByteUtil.EMPTY, null);
}
private TreeMap read(int count, AtomicKVStore lkv, byte[] minKey, byte[] maxKey) {
final TreeMap map = new TreeMap(ByteUtil.COMPARATOR);
this.log.trace("[" + count + "]: reading kv store");
final KVStore kv;
final CloseableKVStore snapshot;
if (this.random.nextBoolean()) {
snapshot = lkv.snapshot();
kv = snapshot;
} else {
snapshot = null;
kv = lkv;
}
try {
for (Iterator i = kv.getRange(minKey, maxKey, false); i.hasNext(); ) {
final KVPair pair = i.next();
map.put(pair.getKey(), pair.getValue());
}
} finally {
if (snapshot != null)
snapshot.close();
}
return map;
}
private void compare(TreeMap map1, TreeMap map2) {
final NavigableMap smap1 = this.stringView(map1);
final NavigableMap smap2 = this.stringView(map2);
Assert.assertEquals(smap1, smap2, "\n*** ACTUAL:\n" + smap1 + "\n*** EXPECTED:\n" + smap2 + "\n");
}
private NavigableMap stringView(NavigableMap byteMap) {
if (byteMap == null)
return null;
final Converter converter = ByteUtil.STRING_CONVERTER.reverse();
return new ConvertedNavigableMap(byteMap, converter, converter);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy