2018-02-01 12:54:56 +03:00
/*
* Copyright ( C ) 2018 Red Hat , Inc . All rights reserved .
*
* This file is part of LVM2 .
*
* This copyrighted material is made available to anyone wishing to use ,
* modify , copy , or redistribute it subject to the terms and conditions
* of the GNU General Public License v .2 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software Foundation ,
* Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
2018-04-26 13:59:39 +03:00
# include <errno.h>
2018-02-05 19:04:23 +03:00
# include <stdio.h>
# include <stdlib.h>
2018-02-01 17:52:43 +03:00
# include <unistd.h>
2018-02-01 12:54:56 +03:00
# include "bcache.h"
2018-04-26 13:59:39 +03:00
# include "framework.h"
# include "units.h"
2018-02-01 12:54:56 +03:00
2018-02-05 19:04:23 +03:00
# define SHOW_MOCK_CALLS 0
/*----------------------------------------------------------------
* Mock engine
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
struct mock_engine {
struct io_engine e ;
struct dm_list expected_calls ;
struct dm_list issued_io ;
unsigned max_io ;
2018-02-06 18:10:44 +03:00
sector_t block_size ;
2018-02-05 19:04:23 +03:00
} ;
enum method {
E_DESTROY ,
E_ISSUE ,
E_WAIT ,
E_MAX_IO
} ;
struct mock_call {
struct dm_list list ;
enum method m ;
2018-02-06 18:10:44 +03:00
bool match_args ;
enum dir d ;
int fd ;
block_address b ;
2018-04-26 13:59:39 +03:00
bool issue_r ;
bool wait_r ;
2018-02-05 19:04:23 +03:00
} ;
struct mock_io {
struct dm_list list ;
int fd ;
sector_t sb ;
sector_t se ;
void * data ;
void * context ;
2018-04-26 13:59:39 +03:00
bool r ;
2018-02-05 19:04:23 +03:00
} ;
static const char * _show_method ( enum method m )
2018-02-01 12:54:56 +03:00
{
2018-02-05 19:04:23 +03:00
switch ( m ) {
case E_DESTROY :
return " destroy() " ;
case E_ISSUE :
return " issue() " ;
case E_WAIT :
return " wait() " ;
case E_MAX_IO :
return " max_io() " ;
}
return " <unknown> " ;
}
static void _expect ( struct mock_engine * e , enum method m )
{
struct mock_call * mc = malloc ( sizeof ( * mc ) ) ;
mc - > m = m ;
2018-02-06 18:10:44 +03:00
mc - > match_args = false ;
2018-02-05 19:04:23 +03:00
dm_list_add ( & e - > expected_calls , & mc - > list ) ;
}
2018-02-06 18:10:44 +03:00
static void _expect_read ( struct mock_engine * e , int fd , block_address b )
2018-02-05 19:04:23 +03:00
{
2018-02-06 18:10:44 +03:00
struct mock_call * mc = malloc ( sizeof ( * mc ) ) ;
mc - > m = E_ISSUE ;
mc - > match_args = true ;
mc - > d = DIR_READ ;
mc - > fd = fd ;
mc - > b = b ;
2018-04-26 13:59:39 +03:00
mc - > issue_r = true ;
mc - > wait_r = true ;
2018-02-06 18:10:44 +03:00
dm_list_add ( & e - > expected_calls , & mc - > list ) ;
2018-02-01 12:54:56 +03:00
}
2018-05-01 11:17:55 +03:00
static void _expect_read_any ( struct mock_engine * e )
{
struct mock_call * mc = malloc ( sizeof ( * mc ) ) ;
mc - > m = E_ISSUE ;
mc - > match_args = false ;
mc - > issue_r = true ;
mc - > wait_r = true ;
dm_list_add ( & e - > expected_calls , & mc - > list ) ;
}
2018-02-06 18:10:44 +03:00
static void _expect_write ( struct mock_engine * e , int fd , block_address b )
2018-02-01 17:52:43 +03:00
{
2018-02-06 18:10:44 +03:00
struct mock_call * mc = malloc ( sizeof ( * mc ) ) ;
mc - > m = E_ISSUE ;
mc - > match_args = true ;
mc - > d = DIR_WRITE ;
mc - > fd = fd ;
mc - > b = b ;
2018-04-26 13:59:39 +03:00
mc - > issue_r = true ;
mc - > wait_r = true ;
dm_list_add ( & e - > expected_calls , & mc - > list ) ;
}
static void _expect_read_bad_issue ( struct mock_engine * e , int fd , block_address b )
{
struct mock_call * mc = malloc ( sizeof ( * mc ) ) ;
mc - > m = E_ISSUE ;
mc - > match_args = true ;
mc - > d = DIR_READ ;
mc - > fd = fd ;
mc - > b = b ;
mc - > issue_r = false ;
mc - > wait_r = true ;
dm_list_add ( & e - > expected_calls , & mc - > list ) ;
}
static void _expect_write_bad_issue ( struct mock_engine * e , int fd , block_address b )
{
struct mock_call * mc = malloc ( sizeof ( * mc ) ) ;
mc - > m = E_ISSUE ;
mc - > match_args = true ;
mc - > d = DIR_WRITE ;
mc - > fd = fd ;
mc - > b = b ;
mc - > issue_r = false ;
mc - > wait_r = true ;
dm_list_add ( & e - > expected_calls , & mc - > list ) ;
}
static void _expect_read_bad_wait ( struct mock_engine * e , int fd , block_address b )
{
struct mock_call * mc = malloc ( sizeof ( * mc ) ) ;
mc - > m = E_ISSUE ;
mc - > match_args = true ;
mc - > d = DIR_READ ;
mc - > fd = fd ;
mc - > b = b ;
mc - > issue_r = true ;
mc - > wait_r = false ;
dm_list_add ( & e - > expected_calls , & mc - > list ) ;
}
static void _expect_write_bad_wait ( struct mock_engine * e , int fd , block_address b )
{
struct mock_call * mc = malloc ( sizeof ( * mc ) ) ;
mc - > m = E_ISSUE ;
mc - > match_args = true ;
mc - > d = DIR_WRITE ;
mc - > fd = fd ;
mc - > b = b ;
mc - > issue_r = true ;
mc - > wait_r = false ;
2018-02-06 18:10:44 +03:00
dm_list_add ( & e - > expected_calls , & mc - > list ) ;
2018-02-01 17:52:43 +03:00
}
2018-02-06 18:10:44 +03:00
static struct mock_call * _match_pop ( struct mock_engine * e , enum method m )
2018-02-01 17:52:43 +03:00
{
2018-02-06 18:10:44 +03:00
2018-02-05 19:04:23 +03:00
struct mock_call * mc ;
if ( dm_list_empty ( & e - > expected_calls ) )
2018-04-26 13:59:39 +03:00
test_fail ( " unexpected call to method %s \n " , _show_method ( m ) ) ;
2018-02-01 17:52:43 +03:00
2018-02-05 19:04:23 +03:00
mc = dm_list_item ( e - > expected_calls . n , struct mock_call ) ;
dm_list_del ( & mc - > list ) ;
2018-02-01 17:52:43 +03:00
2018-02-05 19:04:23 +03:00
if ( mc - > m ! = m )
2018-04-26 13:59:39 +03:00
test_fail ( " expected %s, but got %s \n " , _show_method ( mc - > m ) , _show_method ( m ) ) ;
2018-02-05 19:04:23 +03:00
# if SHOW_MOCK_CALLS
else
fprintf ( stderr , " %s called (expected) \n " , _show_method ( m ) ) ;
# endif
2018-02-06 18:10:44 +03:00
return mc ;
}
static void _match ( struct mock_engine * e , enum method m )
{
free ( _match_pop ( e , m ) ) ;
2018-02-05 19:04:23 +03:00
}
static void _no_outstanding_expectations ( struct mock_engine * e )
{
struct mock_call * mc ;
if ( ! dm_list_empty ( & e - > expected_calls ) ) {
fprintf ( stderr , " unsatisfied expectations: \n " ) ;
dm_list_iterate_items ( mc , & e - > expected_calls )
fprintf ( stderr , " %s \n " , _show_method ( mc - > m ) ) ;
2018-02-01 17:52:43 +03:00
}
2018-02-05 19:04:23 +03:00
T_ASSERT ( dm_list_empty ( & e - > expected_calls ) ) ;
}
2018-02-01 17:52:43 +03:00
2018-02-05 19:04:23 +03:00
static struct mock_engine * _to_mock ( struct io_engine * e )
{
return container_of ( e , struct mock_engine , e ) ;
2018-02-01 17:52:43 +03:00
}
2018-02-05 19:04:23 +03:00
static void _mock_destroy ( struct io_engine * e )
{
struct mock_engine * me = _to_mock ( e ) ;
_match ( me , E_DESTROY ) ;
T_ASSERT ( dm_list_empty ( & me - > issued_io ) ) ;
T_ASSERT ( dm_list_empty ( & me - > expected_calls ) ) ;
free ( _to_mock ( e ) ) ;
}
2018-02-01 17:52:43 +03:00
2018-02-05 19:04:23 +03:00
static bool _mock_issue ( struct io_engine * e , enum dir d , int fd ,
sector_t sb , sector_t se , void * data , void * context )
2018-02-01 17:52:43 +03:00
{
2018-04-26 13:59:39 +03:00
bool r , wait_r ;
2018-02-05 19:04:23 +03:00
struct mock_io * io ;
2018-02-06 18:10:44 +03:00
struct mock_call * mc ;
2018-02-05 19:04:23 +03:00
struct mock_engine * me = _to_mock ( e ) ;
2018-02-06 18:10:44 +03:00
mc = _match_pop ( me , E_ISSUE ) ;
if ( mc - > match_args ) {
T_ASSERT ( d = = mc - > d ) ;
T_ASSERT ( fd = = mc - > fd ) ;
T_ASSERT ( sb = = mc - > b * me - > block_size ) ;
T_ASSERT ( se = = ( mc - > b + 1 ) * me - > block_size ) ;
}
2018-04-26 13:59:39 +03:00
r = mc - > issue_r ;
wait_r = mc - > wait_r ;
2018-02-06 18:10:44 +03:00
free ( mc ) ;
2018-04-26 13:59:39 +03:00
if ( r ) {
io = malloc ( sizeof ( * io ) ) ;
if ( ! io )
abort ( ) ;
2018-02-05 19:04:23 +03:00
2018-04-26 13:59:39 +03:00
io - > fd = fd ;
io - > sb = sb ;
io - > se = se ;
io - > data = data ;
io - > context = context ;
io - > r = wait_r ;
2018-02-05 19:04:23 +03:00
2018-04-26 13:59:39 +03:00
dm_list_add ( & me - > issued_io , & io - > list ) ;
}
return r ;
2018-02-01 17:52:43 +03:00
}
2018-02-05 19:04:23 +03:00
static bool _mock_wait ( struct io_engine * e , io_complete_fn fn )
2018-02-01 17:52:43 +03:00
{
2018-02-05 19:04:23 +03:00
struct mock_io * io ;
struct mock_engine * me = _to_mock ( e ) ;
_match ( me , E_WAIT ) ;
// FIXME: provide a way to control how many are completed and whether
// they error.
T_ASSERT ( ! dm_list_empty ( & me - > issued_io ) ) ;
io = dm_list_item ( me - > issued_io . n , struct mock_io ) ;
dm_list_del ( & io - > list ) ;
2018-04-26 13:59:39 +03:00
fn ( io - > context , io - > r ? 0 : - EIO ) ;
free ( io ) ;
2018-02-05 19:04:23 +03:00
return true ;
2018-02-01 17:52:43 +03:00
}
2018-02-05 19:04:23 +03:00
static unsigned _mock_max_io ( struct io_engine * e )
{
struct mock_engine * me = _to_mock ( e ) ;
_match ( me , E_MAX_IO ) ;
return me - > max_io ;
}
2018-02-06 18:10:44 +03:00
static struct mock_engine * _mock_create ( unsigned max_io , sector_t block_size )
2018-02-05 19:04:23 +03:00
{
struct mock_engine * m = malloc ( sizeof ( * m ) ) ;
m - > e . destroy = _mock_destroy ;
m - > e . issue = _mock_issue ;
m - > e . wait = _mock_wait ;
m - > e . max_io = _mock_max_io ;
m - > max_io = max_io ;
2018-02-06 18:10:44 +03:00
m - > block_size = block_size ;
2018-02-05 19:04:23 +03:00
dm_list_init ( & m - > expected_calls ) ;
dm_list_init ( & m - > issued_io ) ;
return m ;
}
2018-02-06 16:06:15 +03:00
/*----------------------------------------------------------------
* Fixtures
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
struct fixture {
struct mock_engine * me ;
struct bcache * cache ;
} ;
2018-02-06 18:10:44 +03:00
static struct fixture * _fixture_init ( sector_t block_size , unsigned nr_cache_blocks )
2018-02-06 16:06:15 +03:00
{
struct fixture * f = malloc ( sizeof ( * f ) ) ;
2018-02-06 18:10:44 +03:00
f - > me = _mock_create ( 16 , block_size ) ;
2018-02-06 16:06:15 +03:00
T_ASSERT ( f - > me ) ;
_expect ( f - > me , E_MAX_IO ) ;
2018-02-06 18:10:44 +03:00
f - > cache = bcache_create ( block_size , nr_cache_blocks , & f - > me - > e ) ;
2018-02-06 16:06:15 +03:00
T_ASSERT ( f - > cache ) ;
return f ;
}
static void _fixture_exit ( struct fixture * f )
{
_expect ( f - > me , E_DESTROY ) ;
bcache_destroy ( f - > cache ) ;
free ( f ) ;
}
static void * _small_fixture_init ( void )
{
2018-02-06 18:10:44 +03:00
return _fixture_init ( 128 , 16 ) ;
2018-02-06 16:06:15 +03:00
}
static void _small_fixture_exit ( void * context )
{
_fixture_exit ( context ) ;
}
static void * _large_fixture_init ( void )
{
2018-02-06 18:10:44 +03:00
return _fixture_init ( 128 , 1024 ) ;
2018-02-06 16:06:15 +03:00
}
static void _large_fixture_exit ( void * context )
{
_fixture_exit ( context ) ;
}
2018-02-05 19:04:23 +03:00
/*----------------------------------------------------------------
* Tests
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
# define MEG 2048
# define SECTOR_SHIFT 9
static void good_create ( sector_t block_size , unsigned nr_cache_blocks )
2018-02-01 12:54:56 +03:00
{
2018-02-05 19:04:23 +03:00
struct bcache * cache ;
2018-02-06 18:10:44 +03:00
struct mock_engine * me = _mock_create ( 16 , 128 ) ;
2018-02-05 19:04:23 +03:00
_expect ( me , E_MAX_IO ) ;
cache = bcache_create ( block_size , nr_cache_blocks , & me - > e ) ;
T_ASSERT ( cache ) ;
_expect ( me , E_DESTROY ) ;
2018-02-01 12:54:56 +03:00
bcache_destroy ( cache ) ;
}
2018-02-05 19:04:23 +03:00
static void bad_create ( sector_t block_size , unsigned nr_cache_blocks )
{
struct bcache * cache ;
2018-02-06 18:10:44 +03:00
struct mock_engine * me = _mock_create ( 16 , 128 ) ;
2018-02-05 19:04:23 +03:00
_expect ( me , E_MAX_IO ) ;
cache = bcache_create ( block_size , nr_cache_blocks , & me - > e ) ;
T_ASSERT ( ! cache ) ;
_expect ( me , E_DESTROY ) ;
me - > e . destroy ( & me - > e ) ;
}
2018-02-06 16:06:15 +03:00
static void test_create ( void * fixture )
2018-02-05 19:04:23 +03:00
{
good_create ( 8 , 16 ) ;
}
2018-02-06 16:06:15 +03:00
static void test_nr_cache_blocks_must_be_positive ( void * fixture )
2018-02-01 17:52:43 +03:00
{
2018-02-05 19:04:23 +03:00
bad_create ( 8 , 0 ) ;
2018-02-01 17:52:43 +03:00
}
2018-02-06 16:06:15 +03:00
static void test_block_size_must_be_positive ( void * fixture )
2018-02-01 17:52:43 +03:00
{
2018-02-05 19:04:23 +03:00
bad_create ( 0 , 16 ) ;
2018-02-01 17:52:43 +03:00
}
2018-02-06 16:06:15 +03:00
static void test_block_size_must_be_multiple_of_page_size ( void * fixture )
2018-02-01 17:52:43 +03:00
{
2018-02-05 19:04:23 +03:00
static unsigned _bad_examples [ ] = { 3 , 9 , 13 , 1025 } ;
2018-02-01 17:52:43 +03:00
unsigned i ;
2018-02-05 19:04:23 +03:00
for ( i = 0 ; i < DM_ARRAY_SIZE ( _bad_examples ) ; i + + )
bad_create ( _bad_examples [ i ] , 16 ) ;
2018-05-01 16:17:12 +03:00
for ( i = 1 ; i < 100 ; i + + )
2018-02-05 19:04:23 +03:00
good_create ( i * 8 , 16 ) ;
}
2018-02-06 16:06:15 +03:00
static void test_get_triggers_read ( void * context )
2018-02-05 19:04:23 +03:00
{
2018-02-06 16:06:15 +03:00
struct fixture * f = context ;
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
int fd = 17 ; // arbitrary key
struct block * b ;
2018-02-01 17:52:43 +03:00
2018-02-06 18:10:44 +03:00
_expect_read ( f - > me , fd , 0 ) ;
2018-02-06 16:06:15 +03:00
_expect ( f - > me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( f - > cache , fd , 0 , 0 , & b ) ) ;
2018-02-06 16:06:15 +03:00
bcache_put ( b ) ;
2018-05-01 14:07:33 +03:00
_expect_read ( f - > me , fd , 1 ) ;
_expect ( f - > me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( f - > cache , fd , 1 , GF_DIRTY , & b ) ) ;
2018-05-01 14:07:33 +03:00
_expect_write ( f - > me , fd , 1 ) ;
_expect ( f - > me , E_WAIT ) ;
bcache_put ( b ) ;
2018-02-05 19:04:23 +03:00
}
2018-02-06 16:06:15 +03:00
static void test_repeated_reads_are_cached ( void * context )
2018-02-05 19:04:23 +03:00
{
2018-02-06 16:06:15 +03:00
struct fixture * f = context ;
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
int fd = 17 ; // arbitrary key
unsigned i ;
struct block * b ;
2018-02-05 19:04:23 +03:00
2018-02-06 18:10:44 +03:00
_expect_read ( f - > me , fd , 0 ) ;
2018-02-06 16:06:15 +03:00
_expect ( f - > me , E_WAIT ) ;
for ( i = 0 ; i < 100 ; i + + ) {
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( f - > cache , fd , 0 , 0 , & b ) ) ;
2018-02-06 16:06:15 +03:00
bcache_put ( b ) ;
2018-02-01 17:52:43 +03:00
}
}
2018-02-06 16:06:15 +03:00
static void test_block_gets_evicted_with_many_reads ( void * context )
2018-02-01 17:52:43 +03:00
{
2018-02-06 16:06:15 +03:00
struct fixture * f = context ;
2018-02-01 17:52:43 +03:00
2018-02-06 16:06:15 +03:00
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
const unsigned nr_cache_blocks = 16 ;
2018-02-01 17:52:43 +03:00
2018-02-06 16:06:15 +03:00
int fd = 17 ; // arbitrary key
unsigned i ;
struct block * b ;
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
for ( i = 0 ; i < nr_cache_blocks ; i + + ) {
2018-02-06 18:10:44 +03:00
_expect_read ( me , fd , i ) ;
2018-02-05 19:04:23 +03:00
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , i , 0 , & b ) ) ;
2018-02-01 17:52:43 +03:00
bcache_put ( b ) ;
}
2018-02-06 16:06:15 +03:00
// Not enough cache blocks to hold this one
2018-02-06 18:10:44 +03:00
_expect_read ( me , fd , nr_cache_blocks ) ;
2018-02-06 16:06:15 +03:00
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , nr_cache_blocks , 0 , & b ) ) ;
2018-02-06 16:06:15 +03:00
bcache_put ( b ) ;
// Now if we run through we should find one block has been
// evicted. We go backwards because the oldest is normally
// evicted first.
2018-05-01 11:17:55 +03:00
_expect_read_any ( me ) ;
2018-02-06 16:06:15 +03:00
_expect ( me , E_WAIT ) ;
for ( i = nr_cache_blocks ; i ; i - - ) {
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , i - 1 , 0 , & b ) ) ;
2018-02-06 16:06:15 +03:00
bcache_put ( b ) ;
2018-02-05 19:04:23 +03:00
}
}
2018-02-06 16:06:15 +03:00
static void test_prefetch_issues_a_read ( void * context )
2018-02-05 19:04:23 +03:00
{
2018-02-06 16:06:15 +03:00
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
2018-02-05 19:04:23 +03:00
const unsigned nr_cache_blocks = 16 ;
2018-02-06 16:06:15 +03:00
int fd = 17 ; // arbitrary key
unsigned i ;
struct block * b ;
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
for ( i = 0 ; i < nr_cache_blocks ; i + + ) {
// prefetch should not wait
2018-02-06 18:10:44 +03:00
_expect_read ( me , fd , i ) ;
2018-02-06 16:06:15 +03:00
bcache_prefetch ( cache , fd , i ) ;
2018-02-01 17:52:43 +03:00
}
2018-05-16 12:09:17 +03:00
_no_outstanding_expectations ( me ) ;
2018-02-01 17:52:43 +03:00
2018-02-06 16:06:15 +03:00
for ( i = 0 ; i < nr_cache_blocks ; i + + ) {
2018-02-05 19:04:23 +03:00
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , i , 0 , & b ) ) ;
2018-02-05 19:04:23 +03:00
bcache_put ( b ) ;
}
}
2018-02-06 16:06:15 +03:00
static void test_too_many_prefetches_does_not_trigger_a_wait ( void * context )
2018-02-05 19:04:23 +03:00
{
2018-02-06 16:06:15 +03:00
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
const unsigned nr_cache_blocks = 16 ;
int fd = 17 ; // arbitrary key
unsigned i ;
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
for ( i = 0 ; i < 10 * nr_cache_blocks ; i + + ) {
// prefetch should not wait
if ( i < nr_cache_blocks )
2018-02-06 18:10:44 +03:00
_expect_read ( me , fd , i ) ;
2018-02-06 16:06:15 +03:00
bcache_prefetch ( cache , fd , i ) ;
2018-02-05 19:04:23 +03:00
}
2018-02-06 16:06:15 +03:00
// Destroy will wait for any in flight IO triggered by prefetches.
for ( i = 0 ; i < nr_cache_blocks ; i + + )
_expect ( me , E_WAIT ) ;
2018-02-05 19:04:23 +03:00
}
2018-02-06 16:06:15 +03:00
static void test_dirty_data_gets_written_back ( void * context )
2018-02-05 19:04:23 +03:00
{
2018-02-06 16:06:15 +03:00
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
int fd = 17 ; // arbitrary key
struct block * b ;
// Expect the read
2018-02-06 18:10:44 +03:00
_expect_read ( me , fd , 0 ) ;
2018-02-06 16:06:15 +03:00
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , 0 , GF_DIRTY , & b ) ) ;
2018-02-06 16:06:15 +03:00
bcache_put ( b ) ;
// Expect the write
2018-02-06 18:10:44 +03:00
_expect_write ( me , fd , 0 ) ;
2018-02-06 16:06:15 +03:00
_expect ( me , E_WAIT ) ;
}
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
static void test_zeroed_data_counts_as_dirty ( void * context )
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
int fd = 17 ; // arbitrary key
struct block * b ;
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
// No read
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , 0 , GF_ZERO , & b ) ) ;
2018-02-06 16:06:15 +03:00
bcache_put ( b ) ;
2018-02-05 19:04:23 +03:00
2018-02-06 16:06:15 +03:00
// Expect the write
2018-02-06 18:10:44 +03:00
_expect_write ( me , fd , 0 ) ;
2018-02-06 16:06:15 +03:00
_expect ( me , E_WAIT ) ;
2018-02-01 17:52:43 +03:00
}
2018-02-06 16:06:15 +03:00
static void test_flush_waits_for_all_dirty ( void * context )
2018-02-02 17:35:11 +03:00
{
2018-02-06 16:06:15 +03:00
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
2018-02-02 17:35:11 +03:00
2018-02-06 16:06:15 +03:00
const unsigned count = 16 ;
int fd = 17 ; // arbitrary key
unsigned i ;
struct block * b ;
2018-02-02 17:35:11 +03:00
2018-02-06 16:06:15 +03:00
for ( i = 0 ; i < count ; i + + ) {
if ( i % 2 ) {
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , i , GF_ZERO , & b ) ) ;
2018-02-06 16:06:15 +03:00
} else {
2018-02-06 18:10:44 +03:00
_expect_read ( me , fd , i ) ;
2018-02-06 16:06:15 +03:00
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , i , 0 , & b ) ) ;
2018-02-02 17:35:11 +03:00
}
2018-02-06 16:06:15 +03:00
bcache_put ( b ) ;
}
2018-02-02 17:35:11 +03:00
2018-02-06 16:06:15 +03:00
for ( i = 0 ; i < count ; i + + ) {
if ( i % 2 )
2018-02-06 18:10:44 +03:00
_expect_write ( me , fd , i ) ;
2018-02-02 17:35:11 +03:00
}
2018-02-06 16:06:15 +03:00
for ( i = 0 ; i < count ; i + + ) {
if ( i % 2 )
_expect ( me , E_WAIT ) ;
}
2018-02-02 17:35:11 +03:00
2018-02-06 16:06:15 +03:00
bcache_flush ( cache ) ;
_no_outstanding_expectations ( me ) ;
2018-02-02 17:35:11 +03:00
}
2018-02-06 16:06:15 +03:00
2018-04-26 13:59:39 +03:00
static void test_multiple_files ( void * context )
2018-02-06 18:10:44 +03:00
{
static int _fds [ ] = { 1 , 128 , 345 , 678 , 890 } ;
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
unsigned i ;
for ( i = 0 ; i < DM_ARRAY_SIZE ( _fds ) ; i + + ) {
_expect_read ( me , _fds [ i ] , 0 ) ;
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , _fds [ i ] , 0 , 0 , & b ) ) ;
2018-02-06 18:10:44 +03:00
bcache_put ( b ) ;
}
}
2018-04-26 13:59:39 +03:00
static void test_read_bad_issue ( void * context )
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
_expect_read_bad_issue ( me , 17 , 0 ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( ! bcache_get ( cache , 17 , 0 , 0 , & b ) ) ;
2018-04-26 13:59:39 +03:00
}
static void test_read_bad_issue_intermittent ( void * context )
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
int fd = 17 ;
_expect_read_bad_issue ( me , fd , 0 ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( ! bcache_get ( cache , fd , 0 , 0 , & b ) ) ;
2018-04-26 13:59:39 +03:00
_expect_read ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , 0 , 0 , & b ) ) ;
2018-04-26 13:59:39 +03:00
bcache_put ( b ) ;
}
static void test_read_bad_wait ( void * context )
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
int fd = 17 ;
_expect_read_bad_wait ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( ! bcache_get ( cache , fd , 0 , 0 , & b ) ) ;
2018-04-26 13:59:39 +03:00
}
static void test_read_bad_wait_intermittent ( void * context )
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
int fd = 17 ;
_expect_read_bad_wait ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( ! bcache_get ( cache , fd , 0 , 0 , & b ) ) ;
2018-04-26 13:59:39 +03:00
_expect_read ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , 0 , 0 , & b ) ) ;
2018-04-26 13:59:39 +03:00
bcache_put ( b ) ;
}
static void test_write_bad_issue_stops_flush ( void * context )
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
int fd = 17 ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , 0 , GF_ZERO , & b ) ) ;
2018-04-26 13:59:39 +03:00
_expect_write_bad_issue ( me , fd , 0 ) ;
bcache_put ( b ) ;
T_ASSERT ( ! bcache_flush ( cache ) ) ;
// we'll let it succeed the second time
_expect_write ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
T_ASSERT ( bcache_flush ( cache ) ) ;
}
static void test_write_bad_io_stops_flush ( void * context )
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
int fd = 17 ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , 0 , GF_ZERO , & b ) ) ;
2018-04-26 13:59:39 +03:00
_expect_write_bad_wait ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
bcache_put ( b ) ;
T_ASSERT ( ! bcache_flush ( cache ) ) ;
// we'll let it succeed the second time
_expect_write ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
T_ASSERT ( bcache_flush ( cache ) ) ;
}
2018-04-27 11:12:57 +03:00
static void test_invalidate_not_present ( void * context )
{
struct fixture * f = context ;
struct bcache * cache = f - > cache ;
int fd = 17 ;
2018-04-27 12:56:13 +03:00
T_ASSERT ( bcache_invalidate ( cache , fd , 0 ) ) ;
2018-04-27 11:12:57 +03:00
}
static void test_invalidate_present ( void * context )
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
int fd = 17 ;
_expect_read ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , 0 , 0 , & b ) ) ;
2018-04-27 11:12:57 +03:00
bcache_put ( b ) ;
2018-04-27 12:56:13 +03:00
T_ASSERT ( bcache_invalidate ( cache , fd , 0 ) ) ;
2018-04-27 11:12:57 +03:00
}
2018-04-27 12:56:13 +03:00
static void test_invalidate_after_read_error ( void * context )
2018-04-27 11:12:57 +03:00
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
int fd = 17 ;
_expect_read_bad_issue ( me , fd , 0 ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( ! bcache_get ( cache , fd , 0 , 0 , & b ) ) ;
2018-04-27 12:56:13 +03:00
T_ASSERT ( bcache_invalidate ( cache , fd , 0 ) ) ;
2018-04-27 11:12:57 +03:00
}
2018-04-27 12:56:13 +03:00
static void test_invalidate_after_write_error ( void * context )
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
int fd = 17 ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , 0 , GF_ZERO , & b ) ) ;
2018-04-27 12:56:13 +03:00
bcache_put ( b ) ;
// invalidate should fail if the write fails
_expect_write_bad_wait ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
T_ASSERT ( ! bcache_invalidate ( cache , fd , 0 ) ) ;
// and should succeed if the write does
_expect_write ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
T_ASSERT ( bcache_invalidate ( cache , fd , 0 ) ) ;
// a read is not required to get the block
_expect_read ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , 0 , 0 , & b ) ) ;
2018-04-27 12:56:13 +03:00
bcache_put ( b ) ;
}
static void test_invalidate_held_block ( void * context )
{
struct fixture * f = context ;
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
struct block * b ;
int fd = 17 ;
2018-05-10 15:26:08 +03:00
T_ASSERT ( bcache_get ( cache , fd , 0 , GF_ZERO , & b ) ) ;
2018-04-27 12:56:13 +03:00
T_ASSERT ( ! bcache_invalidate ( cache , fd , 0 ) ) ;
_expect_write ( me , fd , 0 ) ;
_expect ( me , E_WAIT ) ;
bcache_put ( b ) ;
}
2018-02-05 19:04:23 +03:00
2018-05-16 12:09:17 +03:00
//----------------------------------------------------------------
// Chasing a bug reported by dct
static void _cycle ( struct fixture * f , unsigned nr_cache_blocks )
{
struct mock_engine * me = f - > me ;
struct bcache * cache = f - > cache ;
unsigned i ;
struct block * b ;
for ( i = 0 ; i < nr_cache_blocks ; i + + ) {
// prefetch should not wait
_expect_read ( me , i , 0 ) ;
bcache_prefetch ( cache , i , 0 ) ;
}
// This double checks the reads occur in response to the prefetch
_no_outstanding_expectations ( me ) ;
for ( i = 0 ; i < nr_cache_blocks ; i + + ) {
_expect ( me , E_WAIT ) ;
T_ASSERT ( bcache_get ( cache , i , 0 , 0 , & b ) ) ;
bcache_put ( b ) ;
}
_no_outstanding_expectations ( me ) ;
}
static void test_concurrent_reads_after_invalidate ( void * context )
{
struct fixture * f = context ;
unsigned i , nr_cache_blocks = 16 ;
_cycle ( f , nr_cache_blocks ) ;
for ( i = 0 ; i < nr_cache_blocks ; i + + )
bcache_invalidate_fd ( f - > cache , i ) ;
_cycle ( f , nr_cache_blocks ) ;
}
2018-05-01 12:47:40 +03:00
2018-04-26 13:59:39 +03:00
/*----------------------------------------------------------------
* Top level
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
# define T(path, desc, fn) register_test(ts, " / base / device / bcache / " path, desc, fn)
2018-05-01 14:54:57 +03:00
static struct test_suite * _tiny_tests ( void )
2018-04-26 13:59:39 +03:00
{
2018-05-01 14:54:57 +03:00
struct test_suite * ts = test_suite_create ( NULL , NULL ) ;
2018-04-26 13:59:39 +03:00
if ( ! ts ) {
fprintf ( stderr , " out of memory \n " ) ;
exit ( 1 ) ;
}
T ( " create-destroy " , " simple create/destroy " , test_create ) ;
T ( " cache-blocks-positive " , " nr cache blocks must be positive " , test_nr_cache_blocks_must_be_positive ) ;
T ( " block-size-positive " , " block size must be positive " , test_block_size_must_be_positive ) ;
T ( " block-size-multiple-page " , " block size must be a multiple of page size " , test_block_size_must_be_multiple_of_page_size ) ;
2018-05-01 14:54:57 +03:00
return ts ;
}
static struct test_suite * _small_tests ( void )
{
struct test_suite * ts = test_suite_create ( _small_fixture_init , _small_fixture_exit ) ;
if ( ! ts ) {
fprintf ( stderr , " out of memory \n " ) ;
exit ( 1 ) ;
}
2018-04-26 13:59:39 +03:00
T ( " get-reads " , " bcache_get() triggers read " , test_get_triggers_read ) ;
T ( " reads-cached " , " repeated reads are cached " , test_repeated_reads_are_cached ) ;
T ( " blocks-get-evicted " , " block get evicted with many reads " , test_block_gets_evicted_with_many_reads ) ;
T ( " prefetch-reads " , " prefetch issues a read " , test_prefetch_issues_a_read ) ;
T ( " prefetch-never-waits " , " too many prefetches does not trigger a wait " , test_too_many_prefetches_does_not_trigger_a_wait ) ;
T ( " writeback-occurs " , " dirty data gets written back " , test_dirty_data_gets_written_back ) ;
T ( " zero-flag-dirties " , " zeroed data counts as dirty " , test_zeroed_data_counts_as_dirty ) ;
T ( " read-multiple-files " , " read from multiple files " , test_multiple_files ) ;
T ( " read-bad-issue " , " read fails if io engine unable to issue " , test_read_bad_issue ) ;
T ( " read-bad-issue-intermittent " , " failed issue, followed by succes " , test_read_bad_issue_intermittent ) ;
T ( " read-bad-io " , " read issued ok, but io fails " , test_read_bad_wait ) ;
T ( " read-bad-io-intermittent " , " failed io, followed by success " , test_read_bad_wait_intermittent ) ;
T ( " write-bad-issue-stops-flush " , " flush fails temporarily if any block fails to write " , test_write_bad_issue_stops_flush ) ;
T ( " write-bad-io-stops-flush " , " flush fails temporarily if any block fails to write " , test_write_bad_io_stops_flush ) ;
2018-04-27 11:12:57 +03:00
T ( " invalidate-not-present " , " invalidate a block that isn't in the cache " , test_invalidate_not_present ) ;
T ( " invalidate-present " , " invalidate a block that is in the cache " , test_invalidate_present ) ;
2018-04-27 12:56:13 +03:00
T ( " invalidate-read-error " , " invalidate a block that errored " , test_invalidate_after_read_error ) ;
T ( " invalidate-write-error " , " invalidate a block that errored " , test_invalidate_after_write_error ) ;
T ( " invalidate-fails-in-held " , " invalidating a held block fails " , test_invalidate_held_block ) ;
2018-05-16 12:09:17 +03:00
T ( " concurrent-reads-after-invalidate " , " prefetch should still issue concurrent reads after invalidate " ,
test_concurrent_reads_after_invalidate ) ;
2018-04-26 13:59:39 +03:00
return ts ;
}
static struct test_suite * _large_tests ( void )
{
struct test_suite * ts = test_suite_create ( _large_fixture_init , _large_fixture_exit ) ;
if ( ! ts ) {
fprintf ( stderr , " out of memory \n " ) ;
exit ( 1 ) ;
2018-02-05 19:04:23 +03:00
}
2018-04-26 13:59:39 +03:00
T ( " flush-waits " , " flush waits for all dirty " , test_flush_waits_for_all_dirty ) ;
return ts ;
}
2018-02-05 19:04:23 +03:00
2018-04-26 13:59:39 +03:00
void bcache_tests ( struct dm_list * all_tests )
{
2018-05-01 14:54:57 +03:00
dm_list_add ( all_tests , & _tiny_tests ( ) - > list ) ;
2018-04-26 13:59:39 +03:00
dm_list_add ( all_tests , & _small_tests ( ) - > list ) ;
dm_list_add ( all_tests , & _large_tests ( ) - > list ) ;
2018-02-05 19:04:23 +03:00
}