1 /*===========================================================================
2 *
3 *                            PUBLIC DOMAIN NOTICE
4 *               National Center for Biotechnology Information
5 *
6 *  This software/database is a "United States Government Work" under the
7 *  terms of the United States Copyright Act.  It was written as part of
8 *  the author's official duties as a United States Government employee and
9 *  thus cannot be copyrighted.  This software/database is freely available
10 *  to the public for use. The National Library of Medicine and the U.S.
11 *  Government have not placed any restriction on its use or reproduction.
12 *
13 *  Although all reasonable efforts have been taken to ensure the accuracy
14 *  and reliability of the software and data, the NLM and the U.S.
15 *  Government do not and cannot warrant the performance or results that
16 *  may be obtained by using this software or data. The NLM and the U.S.
17 *  Government disclaim all warranties, express or implied, including
18 *  warranties of performance, merchantability or fitness for any particular
19 *  purpose.
20 *
21 *  Please cite the author in any work or product based on this material.
22 *
23 * ===========================================================================
24 *
25 */
26 
27 /**
28 * Unit tests for lru_cache in libs/kfs/poolpages.c
29 */
30 
31 #include <ktst/unit_test.hpp>
32 
33 #include <klib/out.h>
34 #include <klib/rc.h>
35 #include <klib/time.h>
36 
37 #include <kfs/directory.h>
38 #include <kfs/file.h>
39 #include <kfs/rrcachedfile.h>
40 
rand_32(size_t min,size_t max)41 static size_t rand_32( size_t min, size_t max )
42 {
43        double scaled = ( ( double )rand() / RAND_MAX );
44        return ( ( max - min + 1 ) * scaled ) + min;
45 }
46 
fill_file_with_random_data(KFile * file,size_t file_size)47 static rc_t fill_file_with_random_data( KFile * file, size_t file_size )
48 {
49     rc_t rc = KFileSetSize( file, file_size );
50     if ( rc == 0 )
51     {
52         uint64_t pos = 0;
53         size_t total = 0;
54         while ( rc == 0 && total < file_size )
55         {
56             uint32_t data[ 512 ];
57             uint32_t i;
58             size_t to_write, num_writ;
59 
60             for ( i = 0; i < 512; ++i ) data[ i ] = rand_32( 0, 0xFFFFFFFF - 1 );
61             to_write = ( file_size - total );
62             if ( to_write > sizeof data ) to_write = sizeof data;
63             rc = KFileWriteAll ( file, pos, data, to_write, &num_writ );
64             if ( rc == 0 )
65             {
66                 pos += num_writ;
67                 total += num_writ;
68             }
69         }
70     }
71     return rc;
72 }
73 
report_diff(uint8_t * b1,uint8_t * b2,size_t n,uint32_t max_diffs)74 static void report_diff( uint8_t * b1, uint8_t * b2, size_t n, uint32_t max_diffs )
75 {
76     size_t i, d;
77     for ( i = 0, d = 0; i < n && d < max_diffs; ++i )
78     {
79         if ( b1[ i ] != b2[ i ] )
80         {
81             KOutMsg( "[ %.08d ] %.02X %.02X\n", i, b1[ i ], b2[ i ] );
82             d++;
83         }
84     }
85 }
86 
compare_file_content(const KFile * file1,const KFile * file2,uint64_t pos,size_t bytes)87 static rc_t compare_file_content( const KFile * file1, const KFile * file2, uint64_t pos, size_t bytes )
88 {
89     rc_t rc = 0;
90     uint8_t * buffer1 = ( uint8_t * )malloc( bytes );
91     if ( buffer1 == NULL )
92         rc = RC ( rcRuntime, rcBuffer, rcConstructing, rcMemory, rcExhausted );
93     else
94     {
95         uint8_t * buffer2 = ( uint8_t * )malloc( bytes );
96         if ( buffer2 == NULL )
97             rc = RC ( rcRuntime, rcBuffer, rcConstructing, rcMemory, rcExhausted );
98         else
99         {
100             size_t num_read1;
101             rc = KFileReadAll ( file1, pos, buffer1, bytes, &num_read1 );
102             if ( rc == 0 )
103             {
104                 size_t num_read2;
105                 rc = KFileReadAll ( file2, pos, buffer2, bytes, &num_read2 );
106                 if ( rc == 0 )
107                 {
108                     if ( num_read1 != num_read2 )
109                     {
110                         rc = RC ( rcRuntime, rcBuffer, rcReading, rcMemory, rcInvalid );
111                         KOutMsg( "compare_file_content() requested:%lu, read 1:%lu, 2:%lu", bytes, num_read1, num_read2 );
112                     }
113                     else
114                     {
115                         int diff = memcmp( buffer1, buffer2, num_read1 );
116                         if ( diff != 0 )
117                         {
118                             report_diff( buffer1, buffer2, num_read1, 20 );
119                             rc = RC ( rcRuntime, rcBuffer, rcReading, rcMemory, rcCorrupt );
120                         }
121                     }
122                 }
123             }
124             free( buffer2 );
125         }
126         free( buffer1 );
127     }
128     return rc;
129 }
130 
seed_random_number_generator(void)131 void seed_random_number_generator( void )
132 {
133     KTime_t t = KTimeStamp ();  /* klib/time.h */
134     srand( t );
135 }
136 
137 TEST_SUITE( LRU_Cache_Test );
138 
139 #define PAGE_SIZE 128 * 1024
140 #define PAGE_COUNT 1024
141 
TEST_CASE(LRU_Cache_Test_Basic)142 TEST_CASE( LRU_Cache_Test_Basic )
143 {
144     KOutMsg( "Test: LRU-Cache-Test-Basic\n" );
145 
146     REQUIRE_RC_FAIL( MakeRRCached ( NULL, NULL, 0, 0 ) );
147     REQUIRE_RC_FAIL( MakeRRCached ( NULL, NULL, PAGE_SIZE, PAGE_COUNT ) );
148     REQUIRE_RC_FAIL( MakeRRCached ( NULL, NULL, 0, PAGE_COUNT ) );
149     REQUIRE_RC_FAIL( MakeRRCached ( NULL, NULL, PAGE_SIZE, 0 ) );
150 
151     KDirectory * dir;
152     REQUIRE_RC( KDirectoryNativeDir( &dir ) );
153 
154     const char * filename = "./LRU_Cache_Test_Basic.txt";
155     KFile * file;
156 
157     REQUIRE_RC( KDirectoryCreateFile ( dir, &file, false, 0664, kcmInit, filename ) );
158     REQUIRE_RC( fill_file_with_random_data( file, PAGE_SIZE * 2 ) );
159     KFileRelease( file );
160 
161     const KFile * org;
162     REQUIRE_RC( KDirectoryOpenFileRead( dir, &org, "%s", filename ) );
163 
164     REQUIRE_RC_FAIL( MakeRRCached ( NULL, org, 0, 0L ) );
165     REQUIRE_RC_FAIL( MakeRRCached ( NULL, org, PAGE_SIZE, PAGE_COUNT ) );
166     REQUIRE_RC_FAIL( MakeRRCached ( NULL, org, 0, PAGE_COUNT ) );
167     REQUIRE_RC_FAIL( MakeRRCached ( NULL, org, PAGE_SIZE, 0 ) );
168 
169     const KFile * cache;
170     REQUIRE_RC_FAIL( MakeRRCached ( &cache, org, 0, 0 ) );
171     REQUIRE_RC_FAIL( MakeRRCached ( &cache, org, 0, PAGE_COUNT ) );
172     REQUIRE_RC_FAIL( MakeRRCached ( &cache, org, PAGE_SIZE, 0 ) );
173 
174     REQUIRE_RC( MakeRRCached ( &cache, org, PAGE_SIZE, PAGE_COUNT ) );
175 
176     KFileRelease( org );
177     KFileRelease( cache );
178     KDirectoryRemove ( dir, true, "%s", filename );
179 
180     REQUIRE_RC( KDirectoryRelease( dir ) );
181 }
182 
183 typedef struct events
184 {
185     uint32_t requests, found, enter, discard, failed;
186 } events;
187 
on_event(void * data,enum cache_event event,uint64_t pos,size_t len,uint32_t block_nr)188 void on_event( void * data, enum cache_event event, uint64_t pos, size_t len, uint32_t block_nr )
189 {
190     events * ev = ( events * )data;
191     if ( ev != NULL )
192     {
193         switch ( event )
194         {
195             case CE_REQUEST : ev -> requests++;
196                               //KOutMsg( "R %lx.%lx (%u)\n", pos, len, block_nr );
197                               break;
198 
199             case CE_FOUND   : ev -> found++;
200                               //KOutMsg( "F %lx.%lx (%u)\n", pos, len, block_nr );
201                               break;
202 
203             case CE_ENTER   : ev -> enter++;
204                               //KOutMsg( "E %lx.%lx (%u)\n", pos, len, block_nr );
205                               break;
206 
207             case CE_DISCARD : ev -> discard++;
208                               //KOutMsg( "D %lx.%lx (%u)\n", pos, len, block_nr );
209                               break;
210 
211             case CE_FAILED  : ev -> failed++; break;
212         }
213     }
214 }
215 
print_events(events * events)216 void print_events( events * events )
217 {
218     KOutMsg( "req     = %u\n", events -> requests );
219     KOutMsg( "found   = %u\n", events -> found );
220     KOutMsg( "enter   = %u\n", events -> enter );
221     KOutMsg( "discard = %u\n", events -> discard );
222     KOutMsg( "failed  = %u\n", events -> failed );
223 }
224 
TEST_CASE(LRU_Cache_Test_Linear_Reading)225 TEST_CASE( LRU_Cache_Test_Linear_Reading )
226 {
227     KOutMsg( "Test: LRU-Cache-Test-Linear-Reading\n" );
228 
229 	KDirectory * dir;
230 	REQUIRE_RC( KDirectoryNativeDir( &dir ) );
231 
232     const char * filename = "./LRU_Cache_Test_Linear_Reading.txt";
233     KFile * file;
234 
235     REQUIRE_RC( KDirectoryCreateFile ( dir, &file, false, 0664, kcmInit, filename ) );
236     REQUIRE_RC( fill_file_with_random_data( file, PAGE_SIZE * 10 ) );
237     REQUIRE_RC( KFileRelease( file ) );
238 
239     const KFile * org;
240     REQUIRE_RC( KDirectoryOpenFileRead( dir, &org, "%s", filename ) );
241 
242     const KFile * cache;
243     REQUIRE_RC( MakeRRCached ( &cache, org, 64 * 1024, 22 ) );
244 
245     uint64_t file_size;
246     REQUIRE_RC( KFileSize ( cache, &file_size ) );
247     REQUIRE( ( file_size == ( PAGE_SIZE * 10 ) ) );
248 
249     events events;
250     memset( &events, 0, sizeof events );
251     REQUIRE_RC( SetRRCachedEventHandler( cache, &events, on_event ) );
252 
253     REQUIRE_RC( compare_file_content( org, cache, 0, file_size ) );
254     REQUIRE_RC( compare_file_content( org, cache, 0, file_size ) );
255 
256     REQUIRE_RC( KFileRelease( cache ) );
257     REQUIRE_RC( KFileRelease( org ) );
258 
259 #if _DEBUGGING
260     // print_events( &events );
261     REQUIRE( events . requests == 21 );
262     REQUIRE( events . found == 20 );
263     REQUIRE( events . enter == 20 );
264     REQUIRE( events . discard == 0 );
265     REQUIRE( events . failed == 0 );
266 #endif
267 
268     REQUIRE_RC( KDirectoryOpenFileRead( dir, &org, "%s", filename ) );
269     REQUIRE_RC( MakeRRCached ( &cache, org, 64 * 1024, 20 ) );
270     memset( &events, 0, sizeof events );
271     REQUIRE_RC( SetRRCachedEventHandler( cache, &events, on_event ) );
272 
273     REQUIRE_RC( compare_file_content( org, cache, 0, file_size ) );
274     REQUIRE_RC( compare_file_content( org, cache, 0, file_size ) );
275 
276 #if _DEBUGGING
277     //print_events( &events );
278     REQUIRE( events . requests == 21 );
279     REQUIRE( events . found == 20 );
280     REQUIRE( events . enter == 20 );
281     REQUIRE( events . discard == 0 );
282     REQUIRE( events . failed == 0 );
283 #endif
284 
285     REQUIRE_RC( KFileRelease( cache ) );
286     REQUIRE_RC( KFileRelease( org ) );
287     REQUIRE_RC( KDirectoryRemove ( dir, true, "%s", filename ) );
288 	REQUIRE_RC( KDirectoryRelease( dir ) );
289 }
290 
TEST_CASE(LRU_Cache_Test_Random_Reading)291 TEST_CASE( LRU_Cache_Test_Random_Reading )
292 {
293     KOutMsg( "Test: LRU-Cache-Test-Random-Reading\n" );
294 
295 	KDirectory * dir;
296 	REQUIRE_RC( KDirectoryNativeDir( &dir ) );
297 
298     const char * filename = "./LRU_Cache_Test_Random_Reading.txt";
299     KFile * file;
300 
301     size_t file_size = PAGE_SIZE * 10;
302     REQUIRE_RC( KDirectoryCreateFile ( dir, &file, false, 0664, kcmInit, filename ) );
303     REQUIRE_RC( fill_file_with_random_data( file, file_size ) );
304     REQUIRE_RC( KFileRelease( file ) );
305 
306     const KFile * org;
307     REQUIRE_RC( KDirectoryOpenFileRead( dir, &org, "%s", filename ) );
308 
309     uint32_t page_count = 20;
310     const KFile * cache;
311     REQUIRE_RC( MakeRRCached ( &cache, org, 64 * 1024, page_count ) );
312 
313     events events;
314     memset( &events, 0, sizeof events );
315     REQUIRE_RC( SetRRCachedEventHandler( cache, &events, on_event ) );
316 
317     uint32_t loops = 100000;
318     KOutMsg( "---testing %u loops\n", loops );
319     for ( uint32_t i = 0; i < loops; ++i )
320     {
321         size_t bsize = rand_32( 10, 50000 );
322         uint64_t pos = rand_32( 0, file_size - ( bsize + 1 ) );
323         rc_t rc = compare_file_content( org, cache, pos, bsize );
324         if ( rc != 0 )
325             KOutMsg( "Test: LRU-Cache-Test-Random-Reading in loop #%u : %lu.%lu = %R\n", i, pos, bsize, rc );
326         REQUIRE_RC( rc );
327     }
328 
329 #if _DEBUGGING
330     //print_events( &events );
331     REQUIRE( events . requests >= loops );
332     REQUIRE( events . found >= loops );
333     REQUIRE( events . enter = page_count );
334     REQUIRE( events . failed == 0 );
335 #endif
336 
337     REQUIRE_RC( KFileRelease( cache ) );
338     REQUIRE_RC( KFileRelease( org ) );
339     REQUIRE_RC( KDirectoryRemove ( dir, true, "%s", filename ) );
340 	REQUIRE_RC( KDirectoryRelease( dir ) );
341 }
342 
343 //////////////////////////////////////////// Main
344 extern "C"
345 {
346 
347 #include <kapp/args.h>
348 #include <kfg/config.h>
349 
KAppVersion(void)350 ver_t CC KAppVersion ( void )
351 {
352     return 0x1000000;
353 }
354 
UsageSummary(const char * progname)355 rc_t CC UsageSummary ( const char * progname )
356 {
357     return 0;
358 }
359 
Usage(const Args * args)360 rc_t CC Usage ( const Args * args )
361 {
362     return 0;
363 }
364 
365 const char UsageDefaultName[] = "lru-cache-test";
366 
KMain(int argc,char * argv[])367 rc_t CC KMain ( int argc, char *argv [] )
368 {
369     rc_t rc;
370     seed_random_number_generator();
371     rc = LRU_Cache_Test( argc, argv );
372     KOutMsg( "lru-cache-test : %R\n", rc );
373     return rc;
374 }
375 
376 }
377