2016-06-22 00:07:06 +02:00
# include <gtest/gtest.h>
# include "blockstore/implementations/versioncounting/VersionCountingBlockStore.h"
2016-06-26 01:07:52 +02:00
# include "blockstore/implementations/versioncounting/VersionCountingBlock.h"
2016-06-22 00:07:06 +02:00
# include "blockstore/implementations/testfake/FakeBlockStore.h"
# include "blockstore/utils/BlockStoreUtils.h"
# include <cpp-utils/data/DataFixture.h>
2016-06-22 00:56:29 +02:00
# include <cpp-utils/tempfile/TempFile.h>
2016-06-22 00:07:06 +02:00
using : : testing : : Test ;
using cpputils : : DataFixture ;
using cpputils : : Data ;
using cpputils : : unique_ref ;
using cpputils : : make_unique_ref ;
2016-06-22 00:56:29 +02:00
using cpputils : : TempFile ;
2016-06-23 22:13:00 +02:00
using boost : : none ;
2016-06-26 00:28:16 +02:00
using std : : make_unique ;
using std : : unique_ptr ;
2016-06-22 00:07:06 +02:00
using blockstore : : testfake : : FakeBlockStore ;
using namespace blockstore : : versioncounting ;
class VersionCountingBlockStoreTest : public Test {
public :
static constexpr unsigned int BLOCKSIZE = 1024 ;
VersionCountingBlockStoreTest ( ) :
2016-06-22 00:56:29 +02:00
stateFile ( false ) ,
2016-06-22 00:07:06 +02:00
baseBlockStore ( new FakeBlockStore ) ,
2016-06-26 23:35:52 +02:00
blockStore ( make_unique_ref < VersionCountingBlockStore > ( std : : move ( cpputils : : nullcheck ( std : : unique_ptr < FakeBlockStore > ( baseBlockStore ) ) . value ( ) ) , stateFile . path ( ) , myClientId , false ) ) ,
2016-06-22 00:07:06 +02:00
data ( DataFixture : : generate ( BLOCKSIZE ) ) {
}
2016-06-26 23:35:52 +02:00
static constexpr uint32_t myClientId = 0x12345678 ;
2016-06-22 00:56:29 +02:00
TempFile stateFile ;
2016-06-22 00:07:06 +02:00
FakeBlockStore * baseBlockStore ;
unique_ref < VersionCountingBlockStore > blockStore ;
Data data ;
2016-06-26 00:28:16 +02:00
std : : pair < FakeBlockStore * , unique_ptr < VersionCountingBlockStore > > makeBlockStoreWithDeletionPrevention ( ) {
FakeBlockStore * baseBlockStore = new FakeBlockStore ;
2016-06-26 23:35:52 +02:00
auto blockStore = make_unique < VersionCountingBlockStore > ( std : : move ( cpputils : : nullcheck ( std : : unique_ptr < FakeBlockStore > ( baseBlockStore ) ) . value ( ) ) , stateFile . path ( ) , myClientId , true ) ;
2016-06-26 00:28:16 +02:00
return std : : make_pair ( baseBlockStore , std : : move ( blockStore ) ) ;
}
std : : pair < FakeBlockStore * , unique_ptr < VersionCountingBlockStore > > makeBlockStoreWithoutDeletionPrevention ( ) {
FakeBlockStore * baseBlockStore = new FakeBlockStore ;
2016-06-26 23:35:52 +02:00
auto blockStore = make_unique < VersionCountingBlockStore > ( std : : move ( cpputils : : nullcheck ( std : : unique_ptr < FakeBlockStore > ( baseBlockStore ) ) . value ( ) ) , stateFile . path ( ) , myClientId , false ) ;
2016-06-26 00:28:16 +02:00
return std : : make_pair ( baseBlockStore , std : : move ( blockStore ) ) ;
}
2016-06-22 00:07:06 +02:00
blockstore : : Key CreateBlockReturnKey ( ) {
return CreateBlockReturnKey ( data ) ;
}
blockstore : : Key CreateBlockReturnKey ( const Data & initData ) {
return blockStore - > create ( initData ) - > key ( ) ;
}
Data loadBaseBlock ( const blockstore : : Key & key ) {
auto block = baseBlockStore - > load ( key ) . value ( ) ;
Data result ( block - > size ( ) ) ;
std : : memcpy ( result . data ( ) , block - > data ( ) , data . size ( ) ) ;
return result ;
}
2016-06-23 01:27:35 +02:00
Data loadBlock ( const blockstore : : Key & key ) {
auto block = blockStore - > load ( key ) . value ( ) ;
Data result ( block - > size ( ) ) ;
std : : memcpy ( result . data ( ) , block - > data ( ) , data . size ( ) ) ;
return result ;
}
2016-06-22 00:07:06 +02:00
void modifyBlock ( const blockstore : : Key & key ) {
auto block = blockStore - > load ( key ) . value ( ) ;
uint64_t data = 5 ;
block - > write ( & data , 0 , sizeof ( data ) ) ;
}
void rollbackBaseBlock ( const blockstore : : Key & key , const Data & data ) {
auto block = baseBlockStore - > load ( key ) . value ( ) ;
block - > resize ( data . size ( ) ) ;
block - > write ( data . data ( ) , 0 , data . size ( ) ) ;
}
2016-06-23 01:27:35 +02:00
void decreaseVersionNumber ( const blockstore : : Key & key ) {
auto baseBlock = baseBlockStore - > load ( key ) . value ( ) ;
uint64_t version = * ( uint64_t * ) ( ( uint8_t * ) baseBlock - > data ( ) + VersionCountingBlock : : VERSION_HEADER_OFFSET ) ;
ASSERT ( version > 1 , " Can't decrease the lowest allowed version number " ) ;
version - = 1 ;
baseBlock - > write ( ( char * ) & version , VersionCountingBlock : : VERSION_HEADER_OFFSET , sizeof ( version ) ) ;
}
2016-06-25 23:53:29 +02:00
void increaseVersionNumber ( const blockstore : : Key & key ) {
auto baseBlock = baseBlockStore - > load ( key ) . value ( ) ;
uint64_t version = * ( uint64_t * ) ( ( uint8_t * ) baseBlock - > data ( ) + VersionCountingBlock : : VERSION_HEADER_OFFSET ) ;
version + = 1 ;
baseBlock - > write ( ( char * ) & version , VersionCountingBlock : : VERSION_HEADER_OFFSET , sizeof ( version ) ) ;
}
2016-06-23 01:27:35 +02:00
void changeClientId ( const blockstore : : Key & key ) {
auto baseBlock = baseBlockStore - > load ( key ) . value ( ) ;
uint32_t clientId = * ( uint32_t * ) ( ( uint8_t * ) baseBlock - > data ( ) + VersionCountingBlock : : CLIENTID_HEADER_OFFSET ) ;
clientId + = 1 ;
baseBlock - > write ( ( char * ) & clientId , VersionCountingBlock : : CLIENTID_HEADER_OFFSET , sizeof ( clientId ) ) ;
}
2016-06-23 22:13:00 +02:00
void deleteBlock ( const blockstore : : Key & key ) {
2016-07-13 11:06:37 +02:00
blockStore - > remove ( key ) ;
2016-06-23 22:13:00 +02:00
}
void insertBaseBlock ( const blockstore : : Key & key , Data data ) {
EXPECT_NE ( none , baseBlockStore - > tryCreate ( key , std : : move ( data ) ) ) ;
}
2016-06-22 00:07:06 +02:00
private :
DISALLOW_COPY_AND_ASSIGN ( VersionCountingBlockStoreTest ) ;
} ;
2016-06-26 23:35:52 +02:00
constexpr uint32_t VersionCountingBlockStoreTest : : myClientId ;
2016-06-23 01:27:35 +02:00
// Test that a decreasing version number is not allowed
TEST_F ( VersionCountingBlockStoreTest , RollbackPrevention_DoesntAllowDecreasingVersionNumberForSameClient_1 ) {
2016-06-22 00:07:06 +02:00
auto key = CreateBlockReturnKey ( ) ;
Data oldBaseBlock = loadBaseBlock ( key ) ;
modifyBlock ( key ) ;
rollbackBaseBlock ( key , oldBaseBlock ) ;
2016-06-26 00:15:56 +02:00
EXPECT_THROW (
blockStore - > load ( key ) ,
IntegrityViolationError
) ;
2016-06-22 00:07:06 +02:00
}
2016-06-23 01:27:35 +02:00
TEST_F ( VersionCountingBlockStoreTest , RollbackPrevention_DoesntAllowDecreasingVersionNumberForSameClient_2 ) {
auto key = CreateBlockReturnKey ( ) ;
// Increase the version number
modifyBlock ( key ) ;
// Decrease the version number again
decreaseVersionNumber ( key ) ;
2016-06-26 00:15:56 +02:00
EXPECT_THROW (
blockStore - > load ( key ) ,
IntegrityViolationError
) ;
2016-06-23 01:27:35 +02:00
}
// Test that a different client doesn't need to have a higher version number (i.e. version numbers are per client).
TEST_F ( VersionCountingBlockStoreTest , RollbackPrevention_DoesAllowDecreasingVersionNumberForDifferentClient ) {
auto key = CreateBlockReturnKey ( ) ;
// Increase the version number
modifyBlock ( key ) ;
// Fake a modification by a different client with lower version numbers
changeClientId ( key ) ;
decreaseVersionNumber ( key ) ;
EXPECT_NE ( boost : : none , blockStore - > load ( key ) ) ;
}
// Test that it doesn't allow a rollback to the "newest" block of a client, when this block was superseded by a version of a different client
TEST_F ( VersionCountingBlockStoreTest , RollbackPrevention_DoesntAllowSameVersionNumberForOldClient ) {
auto key = CreateBlockReturnKey ( ) ;
// Increase the version number
modifyBlock ( key ) ;
Data oldBaseBlock = loadBaseBlock ( key ) ;
// Fake a modification by a different client with lower version numbers
changeClientId ( key ) ;
loadBlock ( key ) ; // make the block store know about this other client's modification
// Rollback to old client
rollbackBaseBlock ( key , oldBaseBlock ) ;
2016-06-26 00:15:56 +02:00
EXPECT_THROW (
blockStore - > load ( key ) ,
IntegrityViolationError
) ;
2016-06-23 01:27:35 +02:00
}
2016-06-23 22:13:00 +02:00
// Test that deleted blocks cannot be re-introduced
TEST_F ( VersionCountingBlockStoreTest , RollbackPrevention_DoesntAllowReintroducingDeletedBlocks ) {
auto key = CreateBlockReturnKey ( ) ;
Data oldBaseBlock = loadBaseBlock ( key ) ;
deleteBlock ( key ) ;
insertBaseBlock ( key , std : : move ( oldBaseBlock ) ) ;
2016-06-26 00:15:56 +02:00
EXPECT_THROW (
blockStore - > load ( key ) ,
IntegrityViolationError
) ;
2016-06-23 22:13:00 +02:00
}
2016-06-25 23:53:29 +02:00
// This can happen if a client synchronization is delayed. Another client might have won the conflict and pushed a new version for the deleted block.
TEST_F ( VersionCountingBlockStoreTest , RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber ) {
auto key = CreateBlockReturnKey ( ) ;
Data oldBaseBlock = loadBaseBlock ( key ) ;
deleteBlock ( key ) ;
insertBaseBlock ( key , std : : move ( oldBaseBlock ) ) ;
increaseVersionNumber ( key ) ;
EXPECT_NE ( boost : : none , blockStore - > load ( key ) ) ;
}
2016-06-26 00:28:16 +02:00
// Check that in a multi-client scenario, missing blocks are not integrity errors, because another client might have deleted them.
TEST_F ( VersionCountingBlockStoreTest , DeletionPrevention_AllowsDeletingBlocksWhenDeactivated ) {
FakeBlockStore * baseBlockStore ;
unique_ptr < VersionCountingBlockStore > blockStore ;
std : : tie ( baseBlockStore , blockStore ) = makeBlockStoreWithoutDeletionPrevention ( ) ;
auto key = blockStore - > create ( Data ( 0 ) ) - > key ( ) ;
2016-07-13 11:06:37 +02:00
baseBlockStore - > remove ( key ) ;
2016-06-26 00:28:16 +02:00
EXPECT_EQ ( boost : : none , blockStore - > load ( key ) ) ;
}
// Check that in a single-client scenario, missing blocks are integrity errors.
TEST_F ( VersionCountingBlockStoreTest , DeletionPrevention_DoesntAllowDeletingBlocksWhenActivated ) {
FakeBlockStore * baseBlockStore ;
unique_ptr < VersionCountingBlockStore > blockStore ;
std : : tie ( baseBlockStore , blockStore ) = makeBlockStoreWithDeletionPrevention ( ) ;
auto key = blockStore - > create ( Data ( 0 ) ) - > key ( ) ;
2016-07-13 11:06:37 +02:00
baseBlockStore - > remove ( key ) ;
2016-06-26 00:28:16 +02:00
EXPECT_THROW (
blockStore - > load ( key ) ,
IntegrityViolationError
) ;
}
2016-06-26 01:36:41 +02:00
// Check that in a multi-client scenario, missing blocks are not integrity errors, because another client might have deleted them.
TEST_F ( VersionCountingBlockStoreTest , DeletionPrevention_InForEachBlock_AllowsDeletingBlocksWhenDeactivated ) {
FakeBlockStore * baseBlockStore ;
unique_ptr < VersionCountingBlockStore > blockStore ;
std : : tie ( baseBlockStore , blockStore ) = makeBlockStoreWithoutDeletionPrevention ( ) ;
auto key = blockStore - > create ( Data ( 0 ) ) - > key ( ) ;
2016-07-13 11:06:37 +02:00
baseBlockStore - > remove ( key ) ;
2016-06-26 01:36:41 +02:00
int count = 0 ;
blockStore - > forEachBlock ( [ & count ] ( const blockstore : : Key & ) {
+ + count ;
} ) ;
EXPECT_EQ ( 0 , count ) ;
}
// Check that in a single-client scenario, missing blocks are integrity errors.
TEST_F ( VersionCountingBlockStoreTest , DeletionPrevention_InForEachBlock_DoesntAllowDeletingBlocksWhenActivated ) {
FakeBlockStore * baseBlockStore ;
unique_ptr < VersionCountingBlockStore > blockStore ;
std : : tie ( baseBlockStore , blockStore ) = makeBlockStoreWithDeletionPrevention ( ) ;
auto key = blockStore - > create ( Data ( 0 ) ) - > key ( ) ;
2016-07-13 11:06:37 +02:00
baseBlockStore - > remove ( key ) ;
2016-06-26 01:36:41 +02:00
EXPECT_THROW (
blockStore - > forEachBlock ( [ ] ( const blockstore : : Key & ) { } ) ,
IntegrityViolationError
) ;
}
2016-06-25 23:53:29 +02:00
// TODO Test more integrity cases:
// - RollbackPrevention_DoesntAllowReintroducingDeletedBlocks with different client id (i.e. trying to re-introduce the newest block of a different client)
// - RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber with different client id
2016-06-26 00:28:16 +02:00
// - Think about more...
2016-06-25 23:53:29 +02:00
2016-06-22 00:07:06 +02:00
TEST_F ( VersionCountingBlockStoreTest , PhysicalBlockSize_zerophysical ) {
EXPECT_EQ ( 0u , blockStore - > blockSizeFromPhysicalBlockSize ( 0 ) ) ;
}
TEST_F ( VersionCountingBlockStoreTest , PhysicalBlockSize_zerovirtual ) {
auto key = CreateBlockReturnKey ( Data ( 0 ) ) ;
auto base = baseBlockStore - > load ( key ) . value ( ) ;
EXPECT_EQ ( 0u , blockStore - > blockSizeFromPhysicalBlockSize ( base - > size ( ) ) ) ;
}
TEST_F ( VersionCountingBlockStoreTest , PhysicalBlockSize_negativeboundaries ) {
// This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the
// correct boundary set. We test the highest value that is negative and the smallest value that is positive.
auto physicalSizeForVirtualSizeZero = baseBlockStore - > load ( CreateBlockReturnKey ( Data ( 0 ) ) ) . value ( ) - > size ( ) ;
if ( physicalSizeForVirtualSizeZero > 0 ) {
EXPECT_EQ ( 0u , blockStore - > blockSizeFromPhysicalBlockSize ( physicalSizeForVirtualSizeZero - 1 ) ) ;
}
EXPECT_EQ ( 0u , blockStore - > blockSizeFromPhysicalBlockSize ( physicalSizeForVirtualSizeZero ) ) ;
EXPECT_EQ ( 1u , blockStore - > blockSizeFromPhysicalBlockSize ( physicalSizeForVirtualSizeZero + 1 ) ) ;
}
TEST_F ( VersionCountingBlockStoreTest , PhysicalBlockSize_positive ) {
auto key = CreateBlockReturnKey ( Data ( 10 * 1024 ) ) ;
auto base = baseBlockStore - > load ( key ) . value ( ) ;
EXPECT_EQ ( 10 * 1024u , blockStore - > blockSizeFromPhysicalBlockSize ( base - > size ( ) ) ) ;
}