1dc2c4915Sdrh# 2009 January 30 20410302eSdanielk1977# 30410302eSdanielk1977# The author disclaims copyright to this source code. In place of 40410302eSdanielk1977# a legal notice, here is a blessing: 50410302eSdanielk1977# 60410302eSdanielk1977# May you do good and not evil. 70410302eSdanielk1977# May you find forgiveness for yourself and forgive others. 80410302eSdanielk1977# May you share freely, never taking more than you give. 90410302eSdanielk1977# 100410302eSdanielk1977#*********************************************************************** 110410302eSdanielk1977# This file implements regression tests for SQLite library. The 120410302eSdanielk1977# focus of this file is testing the handling of IO errors by the 130410302eSdanielk1977# sqlite3_backup_XXX APIs. 140410302eSdanielk1977# 1507a10893Sdanielk1977# $Id: backup_ioerr.test,v 1.3 2009/04/10 18:41:01 danielk1977 Exp $ 160410302eSdanielk1977 170410302eSdanielk1977set testdir [file dirname $argv0] 180410302eSdanielk1977source $testdir/tester.tcl 190410302eSdanielk1977 200410302eSdanielk1977proc data_checksum {db file} { 210410302eSdanielk1977 $db one "SELECT md5sum(a, b) FROM ${file}.t1" 220410302eSdanielk1977} 230410302eSdanielk1977proc test_contents {name db1 file1 db2 file2} { 240410302eSdanielk1977 $db2 eval {select * from sqlite_master} 250410302eSdanielk1977 $db1 eval {select * from sqlite_master} 260410302eSdanielk1977 set checksum [data_checksum $db2 $file2] 270410302eSdanielk1977 uplevel [list do_test $name [list data_checksum $db1 $file1] $checksum] 280410302eSdanielk1977} 290410302eSdanielk1977 300410302eSdanielk1977#-------------------------------------------------------------------- 310410302eSdanielk1977# This proc creates a database of approximately 290 pages. Depending 320410302eSdanielk1977# on whether or not auto-vacuum is configured. Test cases backup_ioerr-1.* 330410302eSdanielk1977# verify nothing more than this assumption. 340410302eSdanielk1977# 350410302eSdanielk1977proc populate_database {db {xtra_large 0}} { 360410302eSdanielk1977 execsql { 370410302eSdanielk1977 BEGIN; 380410302eSdanielk1977 CREATE TABLE t1(a, b); 390410302eSdanielk1977 INSERT INTO t1 VALUES(1, randstr(1000,1000)); 400410302eSdanielk1977 INSERT INTO t1 SELECT a+ 1, randstr(1000,1000) FROM t1; 410410302eSdanielk1977 INSERT INTO t1 SELECT a+ 2, randstr(1000,1000) FROM t1; 420410302eSdanielk1977 INSERT INTO t1 SELECT a+ 4, randstr(1000,1000) FROM t1; 430410302eSdanielk1977 INSERT INTO t1 SELECT a+ 8, randstr(1000,1000) FROM t1; 440410302eSdanielk1977 INSERT INTO t1 SELECT a+16, randstr(1000,1000) FROM t1; 450410302eSdanielk1977 INSERT INTO t1 SELECT a+32, randstr(1000,1000) FROM t1; 460410302eSdanielk1977 CREATE INDEX i1 ON t1(b); 470410302eSdanielk1977 COMMIT; 480410302eSdanielk1977 } $db 490410302eSdanielk1977 if {$xtra_large} { 500410302eSdanielk1977 execsql { INSERT INTO t1 SELECT a+64, randstr(1000,1000) FROM t1 } $db 510410302eSdanielk1977 } 520410302eSdanielk1977} 530410302eSdanielk1977do_test backup_ioerr-1.1 { 540410302eSdanielk1977 populate_database db 550410302eSdanielk1977 set nPage [expr {[file size test.db] / 1024}] 5607a10893Sdanielk1977 expr {$nPage>130 && $nPage<160} 570410302eSdanielk1977} {1} 580410302eSdanielk1977do_test backup_ioerr-1.2 { 590410302eSdanielk1977 expr {[file size test.db] > $sqlite_pending_byte} 600410302eSdanielk1977} {1} 610410302eSdanielk1977do_test backup_ioerr-1.3 { 620410302eSdanielk1977 db close 63fda06befSmistachkin forcedelete test.db 640410302eSdanielk1977} {} 650410302eSdanielk1977 660410302eSdanielk1977# Turn off IO error simulation. 670410302eSdanielk1977# 680410302eSdanielk1977proc clear_ioerr_simulation {} { 690410302eSdanielk1977 set ::sqlite_io_error_hit 0 700410302eSdanielk1977 set ::sqlite_io_error_hardhit 0 710410302eSdanielk1977 set ::sqlite_io_error_pending 0 720410302eSdanielk1977 set ::sqlite_io_error_persist 0 730410302eSdanielk1977} 740410302eSdanielk1977 750410302eSdanielk1977#-------------------------------------------------------------------- 760410302eSdanielk1977# The following procedure runs with SQLite's IO error simulation 770410302eSdanielk1977# enabled. 780410302eSdanielk1977# 790410302eSdanielk1977# 1) Start with a reasonably sized database. One that includes the 800410302eSdanielk1977# pending-byte (locking) page. 810410302eSdanielk1977# 820410302eSdanielk1977# 2) Open a backup process. Set the cache-size for the destination 830410302eSdanielk1977# database to 10 pages only. 840410302eSdanielk1977# 850410302eSdanielk1977# 3) Step the backup process N times to partially backup the database 860410302eSdanielk1977# file. If an IO error is reported, then the backup process is 870410302eSdanielk1977# concluded with a call to backup_finish(). 880410302eSdanielk1977# 890410302eSdanielk1977# If an IO error occurs, verify that: 900410302eSdanielk1977# 910410302eSdanielk1977# * the call to backup_step() returns an SQLITE_IOERR_XXX error code. 920410302eSdanielk1977# 930410302eSdanielk1977# * after the failed call to backup_step() but before the call to 940410302eSdanielk1977# backup_finish() the destination database handle error code and 950410302eSdanielk1977# error message remain unchanged. 960410302eSdanielk1977# 970410302eSdanielk1977# * the call to backup_finish() returns an SQLITE_IOERR_XXX error code. 980410302eSdanielk1977# 990410302eSdanielk1977# * following the call to backup_finish(), the destination database 1000410302eSdanielk1977# handle has been populated with an error code and error message. 1010410302eSdanielk1977# 1020410302eSdanielk1977# 4) Write to the database via the source database connection. Check 1030410302eSdanielk1977# that: 1040410302eSdanielk1977# 1050410302eSdanielk1977# * If an IO error occurs while writing the source database, the 1060410302eSdanielk1977# write operation should report an IO error. The backup should 1070410302eSdanielk1977# proceed as normal. 1080410302eSdanielk1977# 1090410302eSdanielk1977# * If an IO error occurs while updating the backup, the write 1100410302eSdanielk1977# operation should proceed normally. The error should be reported 1110410302eSdanielk1977# from the next call to backup_step() (in step 5 of this test 1120410302eSdanielk1977# procedure). 1130410302eSdanielk1977# 1140410302eSdanielk1977# 5) Step the backup process to finish the backup. If an IO error is 1150410302eSdanielk1977# reported, then the backup process is concluded with a call to 1160410302eSdanielk1977# backup_finish(). 1170410302eSdanielk1977# 118*48864df9Smistachkin# Test that if an IO error occurs, or if one occurred while updating 1190410302eSdanielk1977# the backup database during step 4, then the conditions listed 1200410302eSdanielk1977# under step 3 are all true. 1210410302eSdanielk1977# 1220410302eSdanielk1977# 6) Finish the backup process. 1230410302eSdanielk1977# 1240410302eSdanielk1977# * If the backup succeeds (backup_finish() returns SQLITE_OK), then 1250410302eSdanielk1977# the contents of the backup database should match that of the 1260410302eSdanielk1977# source database. 1270410302eSdanielk1977# 1280410302eSdanielk1977# * If the backup fails (backup_finish() returns other than SQLITE_OK), 1290410302eSdanielk1977# then the contents of the backup database should be as they were 1300410302eSdanielk1977# before the operation was started. 1310410302eSdanielk1977# 1320410302eSdanielk1977# The following factors are varied: 1330410302eSdanielk1977# 1340410302eSdanielk1977# * Destination database is initially larger than the source database, OR 1350410302eSdanielk1977# * Destination database is initially smaller than the source database. 1360410302eSdanielk1977# 1370410302eSdanielk1977# * IO errors are transient, OR 1380410302eSdanielk1977# * IO errors are persistent. 1390410302eSdanielk1977# 1400410302eSdanielk1977# * Destination page-size is smaller than the source. 1410410302eSdanielk1977# * Destination page-size is the same as the source. 1420410302eSdanielk1977# * Destination page-size is larger than the source. 1430410302eSdanielk1977# 1440410302eSdanielk1977 1450410302eSdanielk1977set iTest 1 1460410302eSdanielk1977foreach bPersist {0 1} { 1470410302eSdanielk1977foreach iDestPagesize {512 1024 4096} { 1480410302eSdanielk1977foreach zSetupBak [list "" {populate_database ddb 1}] { 1490410302eSdanielk1977 1500410302eSdanielk1977 incr iTest 1510410302eSdanielk1977 set bStop 0 1520410302eSdanielk1977for {set iError 1} {$bStop == 0} {incr iError} { 1530410302eSdanielk1977 # Disable IO error simulation. 1540410302eSdanielk1977 clear_ioerr_simulation 1550410302eSdanielk1977 1560410302eSdanielk1977 catch { ddb close } 1570410302eSdanielk1977 catch { sdb close } 158fda06befSmistachkin catch { forcedelete test.db } 159fda06befSmistachkin catch { forcedelete bak.db } 1600410302eSdanielk1977 1610410302eSdanielk1977 # Open the source and destination databases. 1620410302eSdanielk1977 sqlite3 sdb test.db 1630410302eSdanielk1977 sqlite3 ddb bak.db 1640410302eSdanielk1977 1650410302eSdanielk1977 # Step 1: Populate the source and destination databases. 1660410302eSdanielk1977 populate_database sdb 1670410302eSdanielk1977 ddb eval "PRAGMA page_size = $iDestPagesize" 1680410302eSdanielk1977 ddb eval "PRAGMA cache_size = 10" 1690410302eSdanielk1977 eval $zSetupBak 1700410302eSdanielk1977 1710410302eSdanielk1977 # Step 2: Open the backup process. 1720410302eSdanielk1977 sqlite3_backup B ddb main sdb main 1730410302eSdanielk1977 1740410302eSdanielk1977 # Enable IO error simulation. 1750410302eSdanielk1977 set ::sqlite_io_error_pending $iError 1760410302eSdanielk1977 set ::sqlite_io_error_persist $bPersist 1770410302eSdanielk1977 1780410302eSdanielk1977 # Step 3: Partially backup the database. If an IO error occurs, check 1790410302eSdanielk1977 # a few things then skip to the next iteration of the loop. 1800410302eSdanielk1977 # 1810410302eSdanielk1977 set rc [B step 100] 1820410302eSdanielk1977 if {$::sqlite_io_error_hardhit} { 1830410302eSdanielk1977 1840410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.1 { 1850410302eSdanielk1977 string match SQLITE_IOERR* $rc 1860410302eSdanielk1977 } {1} 1870410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.2 { 1880410302eSdanielk1977 list [sqlite3_errcode ddb] [sqlite3_errmsg ddb] 1890410302eSdanielk1977 } {SQLITE_OK {not an error}} 1900410302eSdanielk1977 1910410302eSdanielk1977 set rc [B finish] 1920410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.3 { 1930410302eSdanielk1977 string match SQLITE_IOERR* $rc 1940410302eSdanielk1977 } {1} 1950410302eSdanielk1977 1960410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.4 { 1970410302eSdanielk1977 sqlite3_errmsg ddb 1980410302eSdanielk1977 } {disk I/O error} 1990410302eSdanielk1977 2000410302eSdanielk1977 clear_ioerr_simulation 2010410302eSdanielk1977 sqlite3 ddb bak.db 2020410302eSdanielk1977 integrity_check backup_ioerr-$iTest.$iError.5 ddb 2030410302eSdanielk1977 2040410302eSdanielk1977 continue 2050410302eSdanielk1977 } 2060410302eSdanielk1977 2070410302eSdanielk1977 # No IO error was encountered during step 3. Check that backup_step() 2080410302eSdanielk1977 # returned SQLITE_OK before proceding. 2090410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.6 { 2100410302eSdanielk1977 expr {$rc eq "SQLITE_OK"} 2110410302eSdanielk1977 } {1} 2120410302eSdanielk1977 2130410302eSdanielk1977 # Step 4: Write to the source database. 2140410302eSdanielk1977 set rc [catchsql { UPDATE t1 SET b = randstr(1000,1000) WHERE a < 50 } sdb] 2150410302eSdanielk1977 2160410302eSdanielk1977 if {[lindex $rc 0] && $::sqlite_io_error_persist==0} { 217*48864df9Smistachkin # The IO error occurred while updating the source database. In this 2180410302eSdanielk1977 # case the backup should be able to continue. 2190410302eSdanielk1977 set rc [B step 5000] 2200410302eSdanielk1977 if { $rc != "SQLITE_IOERR_UNLOCK" } { 2210410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.7 { 2220410302eSdanielk1977 list [B step 5000] [B finish] 2230410302eSdanielk1977 } {SQLITE_DONE SQLITE_OK} 2240410302eSdanielk1977 2250410302eSdanielk1977 clear_ioerr_simulation 2260410302eSdanielk1977 test_contents backup_ioerr-$iTest.$iError.8 ddb main sdb main 2270410302eSdanielk1977 integrity_check backup_ioerr-$iTest.$iError.9 ddb 2280410302eSdanielk1977 } else { 2290410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.10 { 2300410302eSdanielk1977 B finish 2310410302eSdanielk1977 } {SQLITE_IOERR_UNLOCK} 2320410302eSdanielk1977 } 2330410302eSdanielk1977 2340410302eSdanielk1977 clear_ioerr_simulation 2350410302eSdanielk1977 sqlite3 ddb bak.db 2360410302eSdanielk1977 integrity_check backup_ioerr-$iTest.$iError.11 ddb 2370410302eSdanielk1977 2380410302eSdanielk1977 continue 2390410302eSdanielk1977 } 2400410302eSdanielk1977 2410410302eSdanielk1977 # Step 5: Finish the backup operation. If an IO error occurs, check that 2420410302eSdanielk1977 # it is reported correctly and skip to the next iteration of the loop. 2430410302eSdanielk1977 # 2440410302eSdanielk1977 set rc [B step 5000] 2450410302eSdanielk1977 if {$rc != "SQLITE_DONE"} { 2460410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.12 { 2470410302eSdanielk1977 string match SQLITE_IOERR* $rc 2480410302eSdanielk1977 } {1} 2490410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.13 { 2500410302eSdanielk1977 list [sqlite3_errcode ddb] [sqlite3_errmsg ddb] 2510410302eSdanielk1977 } {SQLITE_OK {not an error}} 2520410302eSdanielk1977 2530410302eSdanielk1977 set rc [B finish] 2540410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.14 { 2550410302eSdanielk1977 string match SQLITE_IOERR* $rc 2560410302eSdanielk1977 } {1} 2570410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.15 { 2580410302eSdanielk1977 sqlite3_errmsg ddb 2590410302eSdanielk1977 } {disk I/O error} 2600410302eSdanielk1977 2610410302eSdanielk1977 clear_ioerr_simulation 2620410302eSdanielk1977 sqlite3 ddb bak.db 2630410302eSdanielk1977 integrity_check backup_ioerr-$iTest.$iError.16 ddb 2640410302eSdanielk1977 2650410302eSdanielk1977 continue 2660410302eSdanielk1977 } 2670410302eSdanielk1977 2680410302eSdanielk1977 # The backup was successfully completed. 2690410302eSdanielk1977 # 2700410302eSdanielk1977 do_test backup_ioerr-$iTest.$iError.17 { 2710410302eSdanielk1977 list [set rc] [B finish] 2720410302eSdanielk1977 } {SQLITE_DONE SQLITE_OK} 2730410302eSdanielk1977 2740410302eSdanielk1977 clear_ioerr_simulation 2750410302eSdanielk1977 sqlite3 sdb test.db 2760410302eSdanielk1977 sqlite3 ddb bak.db 2770410302eSdanielk1977 2780410302eSdanielk1977 test_contents backup_ioerr-$iTest.$iError.18 ddb main sdb main 2790410302eSdanielk1977 integrity_check backup_ioerr-$iTest.$iError.19 ddb 2800410302eSdanielk1977 2810410302eSdanielk1977 set bStop [expr $::sqlite_io_error_pending<=0] 2820410302eSdanielk1977}}}} 2830410302eSdanielk1977 2840410302eSdanielk1977catch { sdb close } 2850410302eSdanielk1977catch { ddb close } 2860410302eSdanielk1977finish_test 287