Annotation of embedaddon/sqlite3/test/walcksum.test, revision 1.1.1.1

1.1       misho       1: # 2010 May 24
                      2: #
                      3: # The author disclaims copyright to this source code.  In place of
                      4: # a legal notice, here is a blessing:
                      5: #
                      6: #    May you do good and not evil.
                      7: #    May you find forgiveness for yourself and forgive others.
                      8: #    May you share freely, never taking more than you give.
                      9: #
                     10: #***********************************************************************
                     11: #
                     12: 
                     13: set testdir [file dirname $argv0]
                     14: source $testdir/tester.tcl
                     15: source $testdir/lock_common.tcl
                     16: source $testdir/wal_common.tcl
                     17: 
                     18: ifcapable !wal {finish_test ; return }
                     19: 
                     20: # Read and return the contents of file $filename. Treat the content as
                     21: # binary data.
                     22: #
                     23: proc readfile {filename} {
                     24:   set fd [open $filename]
                     25:   fconfigure $fd -encoding binary
                     26:   fconfigure $fd -translation binary
                     27:   set data [read $fd]
                     28:   close $fd
                     29:   return $data
                     30: }
                     31: 
                     32: #
                     33: # File $filename must be a WAL file on disk. Check that the checksum of frame
                     34: # $iFrame in the file is correct when interpreting data as $endian-endian
                     35: # integers ($endian must be either "big" or "little"). If the checksum looks
                     36: # correct, return 1. Otherwise 0.
                     37: #
                     38: proc log_checksum_verify {filename iFrame endian} {
                     39:   set data [readfile $filename]
                     40: 
                     41:   foreach {offset c1 c2} [log_checksum_calc $data $iFrame $endian] {}
                     42: 
                     43:   binary scan [string range $data $offset [expr $offset+7]] II expect1 expect2
                     44:   set expect1 [expr $expect1&0xFFFFFFFF]
                     45:   set expect2 [expr $expect2&0xFFFFFFFF]
                     46: 
                     47:   expr {$c1==$expect1 && $c2==$expect2}
                     48: }
                     49: 
                     50: # File $filename must be a WAL file on disk. Compute the checksum for frame
                     51: # $iFrame in the file by interpreting data as $endian-endian integers 
                     52: # ($endian must be either "big" or "little"). Then write the computed 
                     53: # checksum into the file.
                     54: #
                     55: proc log_checksum_write {filename iFrame endian} {
                     56:   set data [readfile $filename]
                     57: 
                     58:   foreach {offset c1 c2} [log_checksum_calc $data $iFrame $endian] {}
                     59: 
                     60:   set bin [binary format II $c1 $c2]
                     61:   set fd [open $filename r+]
                     62:   fconfigure $fd -encoding binary
                     63:   fconfigure $fd -translation binary
                     64:   seek $fd $offset
                     65:   puts -nonewline $fd $bin
                     66:   close $fd
                     67: }
                     68: 
                     69: # Calculate and return the checksum for a particular frame in a WAL.
                     70: #
                     71: # Arguments are:
                     72: #
                     73: #   $data         Blob containing the entire contents of a WAL.
                     74: #
                     75: #   $iFrame       Frame number within the $data WAL. Frames are numbered 
                     76: #                 starting at 1.
                     77: #
                     78: #   $endian       One of "big" or "little".
                     79: #
                     80: # Returns a list of three elements, as follows:
                     81: #
                     82: #   * The byte offset of the checksum belonging to frame $iFrame in the WAL.
                     83: #   * The first integer in the calculated version of the checksum.
                     84: #   * The second integer in the calculated version of the checksum.
                     85: #
                     86: proc log_checksum_calc {data iFrame endian} {
                     87:   
                     88:   binary scan [string range $data 8 11] I pgsz
                     89:   if {$iFrame > 1} {
                     90:     set n [wal_file_size [expr $iFrame-2] $pgsz]
                     91:     binary scan [string range $data [expr $n+16] [expr $n+23]] II c1 c2
                     92:   } else {
                     93:     set c1 0
                     94:     set c2 0
                     95:     wal_cksum $endian c1 c2 [string range $data 0 23]
                     96:   }
                     97: 
                     98:   set n [wal_file_size [expr $iFrame-1] $pgsz]
                     99:   wal_cksum $endian c1 c2 [string range $data $n [expr $n+7]]
                    100:   wal_cksum $endian c1 c2 [string range $data [expr $n+24] [expr $n+24+$pgsz-1]]
                    101: 
                    102:   list [expr $n+16] $c1 $c2
                    103: }
                    104: 
                    105: #
                    106: # File $filename must be a WAL file on disk. Set the 'magic' field of the
                    107: # WAL header to indicate that checksums are $endian-endian ($endian must be
                    108: # either "big" or "little").
                    109: #
                    110: # Also update the wal header checksum (since the wal header contents may
                    111: # have changed).
                    112: #
                    113: proc log_checksum_writemagic {filename endian} {
                    114:   set val [expr {0x377f0682 | ($endian == "big" ? 1 : 0)}]
                    115:   set bin [binary format I $val]
                    116:   set fd [open $filename r+]
                    117:   fconfigure $fd -encoding binary
                    118:   fconfigure $fd -translation binary
                    119:   puts -nonewline $fd $bin
                    120: 
                    121:   seek $fd 0
                    122:   set blob [read $fd 24]
                    123:   set c1 0
                    124:   set c2 0
                    125:   wal_cksum $endian c1 c2 $blob 
                    126:   seek $fd 24
                    127:   puts -nonewline $fd [binary format II $c1 $c2]
                    128: 
                    129:   close $fd
                    130: }
                    131: 
                    132: #-------------------------------------------------------------------------
                    133: # Test cases walcksum-1.* attempt to verify the following:
                    134: #
                    135: #   * That both native and non-native order checksum log files can 
                    136: #      be recovered.
                    137: #
                    138: #   * That when appending to native or non-native checksum log files 
                    139: #     SQLite continues to use the right kind of checksums.
                    140: #
                    141: #   * Test point 2 when the appending process is not one that recovered
                    142: #     the log file.
                    143: #
                    144: #   * Test that both native and non-native checksum log files can be
                    145: #     checkpointed. And that after doing so the next write to the log
                    146: #     file occurs using native byte-order checksums. 
                    147: #
                    148: set native "big"
                    149: if {$::tcl_platform(byteOrder) == "littleEndian"} { set native "little" }
                    150: foreach endian {big little} {
                    151: 
                    152:   # Create a database. Leave some data in the log file.
                    153:   #
                    154:   do_test walcksum-1.$endian.1 {
                    155:     catch { db close }
                    156:     forcedelete test.db test.db-wal test.db-journal
                    157:     sqlite3 db test.db
                    158:     execsql {
                    159:       PRAGMA page_size = 1024;
                    160:       PRAGMA auto_vacuum = 0;
                    161:       PRAGMA synchronous = NORMAL;
                    162: 
                    163:       CREATE TABLE t1(a PRIMARY KEY, b);
                    164:       INSERT INTO t1 VALUES(1,  'one');
                    165:       INSERT INTO t1 VALUES(2,  'two');
                    166:       INSERT INTO t1 VALUES(3,  'three');
                    167:       INSERT INTO t1 VALUES(5,  'five');
                    168: 
                    169:       PRAGMA journal_mode = WAL;
                    170:       INSERT INTO t1 VALUES(8,  'eight');
                    171:       INSERT INTO t1 VALUES(13, 'thirteen');
                    172:       INSERT INTO t1 VALUES(21, 'twentyone');
                    173:     }
                    174: 
                    175:     forcecopy test.db test2.db
                    176:     forcecopy test.db-wal test2.db-wal
                    177:     db close
                    178: 
                    179:     list [file size test2.db] [file size test2.db-wal]
                    180:   } [list [expr 1024*3] [wal_file_size 6 1024]]
                    181: 
                    182:   # Verify that the checksums are valid for all frames and that they
                    183:   # are calculated by interpreting data in native byte-order.
                    184:   #
                    185:   for {set f 1} {$f <= 6} {incr f} {
                    186:     do_test walcksum-1.$endian.2.$f {
                    187:       log_checksum_verify test2.db-wal $f $native
                    188:     } 1
                    189:   }
                    190: 
                    191:   # Replace all checksums in the current WAL file with $endian versions.
                    192:   # Then check that it is still possible to recover and read the database.
                    193:   #
                    194:   log_checksum_writemagic test2.db-wal $endian
                    195:   for {set f 1} {$f <= 6} {incr f} {
                    196:     do_test walcksum-1.$endian.3.$f {
                    197:       log_checksum_write test2.db-wal $f $endian
                    198:       log_checksum_verify test2.db-wal $f $endian
                    199:     } {1}
                    200:   }
                    201:   do_test walcksum-1.$endian.4.1 {
                    202:     forcecopy test2.db test.db
                    203:     forcecopy test2.db-wal test.db-wal
                    204:     sqlite3 db test.db
                    205:     execsql { SELECT a FROM t1 }
                    206:   } {1 2 3 5 8 13 21}
                    207: 
                    208:   # Following recovery, any frames written to the log should use the same 
                    209:   # endianness as the existing frames. Check that this is the case.
                    210:   #
                    211:   do_test walcksum-1.$endian.5.0 {
                    212:     execsql { 
                    213:       PRAGMA synchronous = NORMAL;
                    214:       INSERT INTO t1 VALUES(34, 'thirtyfour');
                    215:     }
                    216:     list [file size test.db] [file size test.db-wal]
                    217:   } [list [expr 1024*3] [wal_file_size 8 1024]]
                    218:   for {set f 1} {$f <= 8} {incr f} {
                    219:     do_test walcksum-1.$endian.5.$f {
                    220:       log_checksum_verify test.db-wal $f $endian
                    221:     } {1}
                    222:   }
                    223: 
                    224:   # Now connect a second connection to the database. Check that this one
                    225:   # (not the one that did recovery) also appends frames to the log using
                    226:   # the same endianness for checksums as the existing frames.
                    227:   #
                    228:   do_test walcksum-1.$endian.6 {
                    229:     sqlite3 db2 test.db
                    230:     execsql { 
                    231:       PRAGMA integrity_check;
                    232:       SELECT a FROM t1;
                    233:     } db2
                    234:   } {ok 1 2 3 5 8 13 21 34}
                    235:   do_test walcksum-1.$endian.7.0 {
                    236:     execsql { 
                    237:       PRAGMA synchronous = NORMAL;
                    238:       INSERT INTO t1 VALUES(55, 'fiftyfive');
                    239:     } db2
                    240:     list [file size test.db] [file size test.db-wal]
                    241:   } [list [expr 1024*3] [wal_file_size 10 1024]]
                    242:   for {set f 1} {$f <= 10} {incr f} {
                    243:     do_test walcksum-1.$endian.7.$f {
                    244:       log_checksum_verify test.db-wal $f $endian
                    245:     } {1}
                    246:   }
                    247: 
                    248:   # Now that both the recoverer and non-recoverer have added frames to the
                    249:   # log file, check that it can still be recovered.
                    250:   #
                    251:   forcecopy test.db test2.db
                    252:   forcecopy test.db-wal test2.db-wal
                    253:   do_test walcksum-1.$endian.7.11 {
                    254:     sqlite3 db3 test2.db
                    255:     execsql { 
                    256:       PRAGMA integrity_check;
                    257:       SELECT a FROM t1;
                    258:     } db3
                    259:   } {ok 1 2 3 5 8 13 21 34 55}
                    260:   db3 close
                    261: 
                    262:   # Run a checkpoint on the database file. Then, check that any frames written
                    263:   # to the start of the log use native byte-order checksums.
                    264:   #
                    265:   do_test walcksum-1.$endian.8.1 {
                    266:     execsql {
                    267:       PRAGMA wal_checkpoint;
                    268:       INSERT INTO t1 VALUES(89, 'eightynine');
                    269:     }
                    270:     log_checksum_verify test.db-wal 1 $native
                    271:   } {1}
                    272:   do_test walcksum-1.$endian.8.2 {
                    273:     log_checksum_verify test.db-wal 2 $native
                    274:   } {1}
                    275:   do_test walcksum-1.$endian.8.3 {
                    276:     log_checksum_verify test.db-wal 3 $native
                    277:   } {0}
                    278: 
                    279:   do_test walcksum-1.$endian.9 {
                    280:     execsql { 
                    281:       PRAGMA integrity_check;
                    282:       SELECT a FROM t1;
                    283:     } db2
                    284:   } {ok 1 2 3 5 8 13 21 34 55 89}
                    285: 
                    286:   catch { db close }
                    287:   catch { db2 close }
                    288: }
                    289: 
                    290: #-------------------------------------------------------------------------
                    291: # Test case walcksum-2.* tests that if a statement transaction is rolled
                    292: # back after frames are written to the WAL, and then (after writing some
                    293: # more) the outer transaction is committed, the WAL file is still correctly
                    294: # formatted (and can be recovered by a second process if required).
                    295: #
                    296: do_test walcksum-2.1 {
                    297:   forcedelete test.db test.db-wal test.db-journal
                    298:   sqlite3 db test.db
                    299:   execsql {
                    300:     PRAGMA synchronous = NORMAL;
                    301:     PRAGMA page_size = 1024;
                    302:     PRAGMA journal_mode = WAL;
                    303:     PRAGMA cache_size = 10;
                    304:     CREATE TABLE t1(x PRIMARY KEY);
                    305:     PRAGMA wal_checkpoint;
                    306:     INSERT INTO t1 VALUES(randomblob(800));
                    307:     BEGIN;
                    308:       INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*   2 */
                    309:       INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*   4 */
                    310:       INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*   8 */
                    311:       INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*  16 */
                    312:       SAVEPOINT one;
                    313:         INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*  32 */
                    314:         INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*  64 */
                    315:         INSERT INTO t1 SELECT randomblob(800) FROM t1;   /* 128 */
                    316:         INSERT INTO t1 SELECT randomblob(800) FROM t1;   /* 256 */
                    317:       ROLLBACK TO one;
                    318:       INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*  32 */
                    319:       INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*  64 */
                    320:       INSERT INTO t1 SELECT randomblob(800) FROM t1;   /* 128 */
                    321:       INSERT INTO t1 SELECT randomblob(800) FROM t1;   /* 256 */
                    322:     COMMIT;
                    323:   }
                    324: 
                    325:   forcecopy test.db test2.db
                    326:   forcecopy test.db-wal test2.db-wal
                    327: 
                    328:   sqlite3 db2 test2.db
                    329:   execsql {
                    330:     PRAGMA integrity_check;
                    331:     SELECT count(*) FROM t1;
                    332:   } db2
                    333: } {ok 256}
                    334: catch { db close }
                    335: catch { db2 close }
                    336: 
                    337: #-------------------------------------------------------------------------
                    338: # Test case walcksum-3.* tests that the checksum calculation detects single 
                    339: # byte changes to frame or frame-header data and considers the frame
                    340: # invalid as a result.
                    341: #
                    342: do_test walcksum-3.1 {
                    343:   forcedelete test.db test.db-wal test.db-journal
                    344:   sqlite3 db test.db
                    345: 
                    346:   execsql {
                    347:     PRAGMA synchronous = NORMAL;
                    348:     PRAGMA page_size = 1024;
                    349:     CREATE TABLE t1(a, b);
                    350:     INSERT INTO t1 VALUES(1, randomblob(300));
                    351:     INSERT INTO t1 VALUES(2, randomblob(300));
                    352:     PRAGMA journal_mode = WAL;
                    353:     INSERT INTO t1 VALUES(3, randomblob(300));
                    354:   }
                    355: 
                    356:   file size test.db-wal
                    357: } [wal_file_size 1 1024]
                    358: do_test walcksum-3.2 {
                    359:   forcecopy test.db-wal test2.db-wal
                    360:   forcecopy test.db test2.db
                    361:   sqlite3 db2 test2.db
                    362:   execsql { SELECT a FROM t1 } db2
                    363: } {1 2 3}
                    364: db2 close
                    365: forcecopy test.db test2.db
                    366: 
                    367: 
                    368: foreach incr {1 2 3 20 40 60 80 100 120 140 160 180 200 220 240 253 254 255} {
                    369:   do_test walcksum-3.3.$incr {
                    370:     set FAIL 0
                    371:     for {set iOff 0} {$iOff < [wal_file_size 1 1024]} {incr iOff} {
                    372: 
                    373:       forcecopy test.db-wal test2.db-wal
                    374:       set fd [open test2.db-wal r+]
                    375:       fconfigure $fd -encoding binary
                    376:       fconfigure $fd -translation binary
                    377:   
                    378:       seek $fd $iOff
                    379:       binary scan [read $fd 1] c x
                    380:       seek $fd $iOff
                    381:       puts -nonewline $fd [binary format c [expr {($x+$incr)&0xFF}]]
                    382:       close $fd
                    383:     
                    384:       sqlite3 db2 test2.db
                    385:       if { [execsql { SELECT a FROM t1 } db2] != "1 2" } {set FAIL 1}
                    386:       db2 close
                    387:     }
                    388:     set FAIL
                    389:   } {0}
                    390: }
                    391:   
                    392: finish_test
                    393: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>