Unified interface to save key-value pairs in a persistent store. 
#define TEST_RUNTIME 240 //seconds
#include <test.h>
#include <lunchbox/clock.h>
#include <lunchbox/os.h>
#include <lunchbox/persistentMap.h>
#include <lunchbox/rng.h>
#ifdef LUNCHBOX_USE_LEVELDB
#  include <leveldb/db.h>
#endif
#ifdef LUNCHBOX_USE_SKV
#  include <FxLogger/FxLogger.hpp>
#endif
#include <boost/format.hpp>
#include <stdexcept>
const int ints[] = { 17, 53, 42, 65535, 32768 };
const size_t numInts = sizeof( ints ) / sizeof( int );
const int64_t loopTime = 1000;
bool perfTest = false;
{
    std::vector< T > vector;
    for( size_t i = 0; i < numInts; ++i )
        vector.push_back( T( ints[ i ] ));
    TEST( map.insert( typeid( vector ).name(), vector ));
}
{
    const std::vector< T >& vector =
        map.getVector< T >( typeid( vector ).name( ));
    TESTINFO( vector.size() ==  numInts, vector.size() << " != " << numInts );
    for( size_t i = 0; i < numInts; ++i )
        TEST( vector[ i ] == T( ints[i] ));
}
template< 
class T > 
void insertVector( 
PersistentMap& map, 
const size_t elems )
 
{
    std::vector< T > vector;
    for( size_t i = 0; i < elems; ++i )
        vector.push_back( i );
    TEST( map.insert( std::string( "bulk" ) + typeid( vector ).name(), vector ));
}
template< 
class T > 
void readVector( 
const PersistentMap& map, 
const size_t elems )
 
{
    const std::vector< T >& vector =
        map.getVector< T >( std::string( "bulk" ) + typeid( vector ).name());
    TESTINFO( vector.size() ==  elems, vector.size() << " != " << elems );
    for( size_t i = 0; i < numInts; ++i )
        TESTINFO( vector[ i ] == T( i ), vector[ i ] << " != " << i );
}
{
    const std::set< uint32_t >& bigSet =
        map.getSet< uint32_t >( "std::set< uint32_t >" );
    TEST( bigSet.size() == 1000 );
    for( uint32_t i = 1; i <= 1000; ++i )
        TEST( bigSet.find( i ) != bigSet.end( ));
    TEST( map[ "foo" ] == "bar" );
    TEST( map[ "bar" ].empty( ));
    TEST( map.get< bool >( "bValue" ) == true );
    TEST( map.get< int >( "iValue" ) == 42 );
    readVector< int >( map );
    readVector< uint16_t >( map );
    const std::set< int >& set = map.getSet< int >( "std::set< int >" );
    TESTINFO( set.size() ==  numInts, set.size() << " != " << numInts );
    for( size_t i = 0; i < numInts; ++i )
        TESTINFO( set.find( ints[i] ) != set.end(),
                  ints[i] << " not found in set" );
}
void read( const std::string& uri )
{
    read( map );
}
void setup( const std::string& uri )
{
    TEST( map.insert( "foo", "bar" ));
    TEST( map.contains( "foo" ));
    TESTINFO( map[ "foo" ] == "bar",
              map[ "foo" ] << " length " << map[ "foo" ].length( ));
    TEST( map[ "bar" ].empty( ));
    TEST( map.insert( "the quick brown fox", "jumped over something" ));
    TESTINFO( map[ "the quick brown fox" ] == "jumped over something",
              map[ "the quick brown fox" ] );
    TEST( map.insert( "hans", std::string( "dampf" )));
    TESTINFO( map[ "hans" ] == "dampf", map[ "hans" ] );
    const bool bValue = true;
    TEST( map.insert( "bValue", bValue ));
    TEST( map.get< bool >( "bValue" ) == bValue );
    const int iValue = 42;
    TEST( map.insert( "iValue", iValue ));
    TEST( map.get< int >( "iValue" ) == iValue );
    TEST( map.insert( "coffee", 0xC0FFEE ));
    map.setByteswap( true );
    TEST( map.get< unsigned >( "coffee" ) == 0xEEFFC000u );
    map.setByteswap( false );
    TEST( map.get< int >( "coffee" ) == 0xC0FFEE );
    insertVector< int >( map );
    insertVector< uint16_t >( map );
    readVector< int >( map );
    readVector< uint16_t >( map );
    insertVector< int >( map, LB_128KB );
    insertVector< uint16_t >( map, LB_128KB );
    map.fetch( std::string( "bulk" ) + typeid( std::vector< int > ).name( ));
    map.fetch( std::string( "bulk" ) + typeid( std::vector<uint16_t> ).name( ));
    readVector< int >( map, LB_128KB );
    readVector< uint16_t >( map, LB_128KB );
    std::set< int > set( ints, ints + numInts );
    TEST( map.insert( "std::set< int >", set ));
    std::set< uint32_t > bigSet;
    for( uint32_t i = 1; i <= 1000; ++i )
        bigSet.insert( i );
    TEST( map.insert( "std::set< uint32_t >", bigSet ));
    read( map );
}
void benchmark( const std::string& uri, const uint64_t queueDepth,
                const size_t valueSize )
{
    static std::string lastURI;
    if( uri != lastURI )
    {
        std::cout << uri << std::endl;
        lastURI = uri;
    }
    map.setQueueDepth( queueDepth );
    
    keys.resize( queueDepth + 1 );
    for( uint64_t i = 0; i <= queueDepth; ++i )
        keys[i].assign( reinterpret_cast< char* >( &i ), 8 );
    std::string value( valueSize, '*' );
    for( size_t i = 0; i < valueSize; ++i )
        value[i] = rng.
get<
char>();
 
    
    uint64_t i = 0;
    {
        map.insert( keys[ i % (queueDepth+1) ], value );
        ++i;
    }
    map.flush();
    const float writeTime = clock.
getTimef() / 1000.f;
 
    const uint64_t wOps = i;
    TEST( i > queueDepth );
    
    if( queueDepth == 0 ) 
    {
        for( i = 0; i < wOps && clock.
getTime64() < loopTime; ++i ) 
 
            map[ keys[ i % (queueDepth+1) ]];
    }
    else 
    {
        for( i = 0; i < queueDepth; ++i ) 
            TEST( map.fetch( keys[ i % (queueDepth+1) ], valueSize ) );
        for( ; i < wOps && clock.
getTime64() < loopTime; ++i ) 
 
        {
            map[ keys[ (i - queueDepth) % (queueDepth+1) ] ];
            TEST( map.fetch( keys[ i % (queueDepth+1) ], valueSize ));
        }
        for( uint64_t j = i - queueDepth; j <= i; ++j ) 
            map[ keys[ j % (queueDepth+1) ]];
    }
    const float readTime = clock.
getTimef() / 1000.f;
 
    const size_t rOps = i;
    std::cout << boost::format( "%6i, %6i, %9.2f, %9.2f, %9.2f, %9.2f")
        
        % queueDepth % valueSize % (rOps/readTime) % (wOps/writeTime)
        % (rOps/1024.f/1024.f*valueSize/readTime)
        % (wOps/1024.f/1024.f*valueSize/writeTime) << std::endl;
    if( !perfTest )
    {
        
        for( uint64_t j = 0; j < wOps && clock.
getTime64() < loopTime; ++j )
 
        {
            const std::string& val = map[ keys[ j % (queueDepth+1) ]];
            TESTINFO( val.size() == valueSize,
                      val.size() << " != " << valueSize );
            TEST( val == value );
        }
    }
    
    map.flush();
}
void testGenericFailures()
{
    try
    {
        setup( "foobar://" );
    }
    catch( const std::runtime_error& )
    {
        return;
    }
    TESTINFO( false, "Missing exception" );
}
void testLevelDBFailures()
{
#ifdef LUNCHBOX_USE_LEVELDB
    try
    {
        setup( "leveldb:///doesnotexist/deadbeef/coffee" );
    }
    catch( const std::runtime_error& )
    {
        return;
    }
    TESTINFO( false, "Missing exception" );
#endif
}
int main( int, char* argv[] )
{
    perfTest = std::string( argv[0] ).find( "perf-" ) != std::string::npos;
    if( perfTest )
        std::cout
            << " async,  value,   reads/s,  writes/s, read MB/s, write MB/s"
            << std::endl;
    try
    {
#ifdef LUNCHBOX_USE_LEVELDB
        setup( "" );
        setup( "leveldb://" );
        setup( "leveldb://persistentMap2.leveldb" );
        read( "" );
        read( "leveldb://" );
        read( "leveldb://persistentMap2.leveldb" );
        if( perfTest )
            for( size_t i=1; i <= 65536; i = i<<2 )
                benchmark( "leveldb://", 0, i );
#endif
#ifdef LUNCHBOX_USE_SKV
        FxLogger_Init( argv[0] );
        setup( "skv://" );
        read( "skv://" );
        if( perfTest )
        {
            benchmark( "skv://", 0, 64 );
            for( size_t i=1; i <= 65536; i = i<<1 )
                benchmark( "skv://", i, 64 );
            for( size_t i=1; i <= 65536; i = i<<2 )
                benchmark( "skv://", 65536, i );
        }
#endif
    }
#ifdef LUNCHBOX_USE_LEVELDB
    catch( const leveldb::Status& status )
    {
        TESTINFO( !"exception", status.ToString( ));
    }
#endif
    catch( const std::runtime_error& error )
    {
#ifdef LUNCHBOX_USE_SKV
        if( error.what() !=
            std::string( "skv init failed: SKV_ERRNO_CONN_FAILED" ))
#endif
        {
            TESTINFO( !"exception", error.what( ));
        }
    }
    testGenericFailures();
    testLevelDBFailures();
    return EXIT_SUCCESS;
}