All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsimpledb.kv.test.AtomicKVStoreTest Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version

/*
 * 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