gitlab.com/CoiaPrant/sqlite3@v1.19.1/testdata/tcl/crash.test (about)

     1  # 2001 September 15
     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  # This file implements regression tests for SQLite library.
    12  #
    13  # The focus of this file is testing the ability of the database to
    14  # uses its rollback journal to recover intact (no database corruption)
    15  # from a power failure during the middle of a COMMIT.  The OS interface
    16  # modules are overloaded using the modified I/O routines found in test6.c.  
    17  # These routines allow us to simulate the kind of file damage that 
    18  # occurs after a power failure.
    19  #
    20  # $Id: crash.test,v 1.27 2008/01/08 15:18:52 drh Exp $
    21  
    22  set testdir [file dirname $argv0]
    23  source $testdir/tester.tcl
    24  
    25  ifcapable !crashtest {
    26    finish_test
    27    return
    28  }
    29  
    30  set repeats 100
    31  #set repeats 10
    32  
    33  # The following procedure computes a "signature" for table "abc".  If
    34  # abc changes in any way, the signature should change.  
    35  proc signature {} {
    36    return [db eval {SELECT count(*), md5sum(a), md5sum(b), md5sum(c) FROM abc}]
    37  }
    38  proc signature2 {} {
    39    return [db eval {SELECT count(*), md5sum(a), md5sum(b), md5sum(c) FROM abc2}]
    40  }
    41  
    42  #--------------------------------------------------------------------------
    43  # Simple crash test:
    44  #
    45  # crash-1.1: Create a database with a table with two rows.
    46  # crash-1.2: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
    47  #            the first journal-sync.
    48  # crash-1.3: Ensure the database is in the same state as after crash-1.1.
    49  # crash-1.4: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
    50  #            the first database-sync.
    51  # crash-1.5: Ensure the database is in the same state as after crash-1.1.
    52  # crash-1.6: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
    53  #            the second journal-sync.
    54  # crash-1.7: Ensure the database is in the same state as after crash-1.1.
    55  #
    56  # Tests 1.8 through 1.11 test for crashes on the third journal sync and
    57  # second database sync.  Neither of these is required in such a small test
    58  # case, so these tests are just to verify that the test infrastructure
    59  # operates as expected.
    60  #
    61  do_test crash-1.1 {
    62    execsql {
    63      CREATE TABLE abc(a, b, c);
    64      INSERT INTO abc VALUES(1, 2, 3);
    65      INSERT INTO abc VALUES(4, 5, 6);
    66    }
    67    set ::sig [signature]
    68    expr 0
    69  } {0}
    70  for {set i 0} {$i<10} {incr i} {
    71    set seed [expr {int(abs(rand()*10000))}]
    72    do_test crash-1.2.$i {
    73      crashsql -delay 1 -file test.db-journal -seed $seed {
    74        DELETE FROM abc WHERE a = 1;
    75      }
    76    } {1 {child process exited abnormally}}
    77    do_test crash-1.3.$i {
    78      signature
    79    } $::sig
    80  }
    81  do_test crash-1.4 {
    82    crashsql -delay 1 -file test.db {
    83      DELETE FROM abc WHERE a = 1;
    84    }
    85  } {1 {child process exited abnormally}}
    86  do_test crash-1.5 {
    87    signature
    88  } $::sig
    89  do_test crash-1.6 {
    90    crashsql -delay 2 -file test.db-journal {
    91      DELETE FROM abc WHERE a = 1;
    92    }
    93  } {1 {child process exited abnormally}}
    94  do_test crash-1.7 {
    95    catchsql {
    96      SELECT * FROM abc;
    97    }
    98  } {0 {1 2 3 4 5 6}}
    99  
   100  do_test crash-1.8 {
   101    crashsql -delay 3 -file test.db-journal {
   102      DELETE FROM abc WHERE a = 1;
   103    }
   104  } {0 {}}
   105  do_test crash-1.9 {
   106    catchsql {
   107      SELECT * FROM abc;
   108    }
   109  } {0 {4 5 6}}
   110  do_test crash-1.10 {
   111    crashsql -delay 2 -file test.db {
   112      DELETE FROM abc WHERE a = 4;
   113    }
   114  } {0 {}}
   115  do_test crash-1.11 {
   116    catchsql {
   117      SELECT * FROM abc;
   118    }
   119  } {0 {}}
   120  
   121  #--------------------------------------------------------------------------
   122  # The following tests test recovery when both the database file and the
   123  # journal file contain corrupt data. This can happen after pages are
   124  # written to the database file before a transaction is committed due to
   125  # cache-pressure.
   126  #
   127  # crash-2.1: Insert 18 pages of data into the database.
   128  # crash-2.2: Check the database file size looks ok.
   129  # crash-2.3: Delete 15 or so pages (with a 10 page page-cache), then crash.
   130  # crash-2.4: Ensure the database is in the same state as after crash-2.1.
   131  #
   132  # Test cases crash-2.5 and crash-2.6 check that the database is OK if the 
   133  # crash occurs during the main database file sync. But this isn't really
   134  # different from the crash-1.* cases.
   135  #
   136  do_test crash-2.1 {
   137    execsql { BEGIN }
   138    for {set n 0} {$n < 1000} {incr n} {
   139      execsql "INSERT INTO abc VALUES($n, [expr 2*$n], [expr 3*$n])"
   140    }
   141    execsql { COMMIT }
   142    set ::sig [signature]
   143    execsql { SELECT sum(a), sum(b), sum(c) from abc }
   144  } {499500 999000 1498500}
   145  do_test crash-2.2 {
   146    expr ([file size test.db] / 1024)>16
   147  } {1}
   148  do_test crash-2.3 {
   149    crashsql -delay 2 -file test.db-journal {
   150      DELETE FROM abc WHERE a < 800;
   151    }
   152  } {1 {child process exited abnormally}}
   153  do_test crash-2.4 {
   154    signature
   155  } $sig
   156  do_test crash-2.5 {
   157    crashsql -delay 1 -file test.db {
   158      DELETE FROM abc WHERE a<800;
   159    }
   160  } {1 {child process exited abnormally}}
   161  do_test crash-2.6 {
   162    signature
   163  } $sig
   164  
   165  #--------------------------------------------------------------------------
   166  # The crash-3.* test cases are essentially the same test as test case
   167  # crash-2.*, but with a more complicated data set. 
   168  #
   169  # The test is repeated a few times with different seeds for the random
   170  # number generator in the crashing executable. Because there is no way to
   171  # seed the random number generator directly, some SQL is added to the test
   172  # case to 'use up' a different quantity random numbers before the test SQL
   173  # is executed.
   174  #
   175  
   176  # Make sure the file is much bigger than the pager-cache (10 pages). This
   177  # ensures that cache-spills happen regularly.
   178  do_test crash-3.0 {
   179    execsql {
   180      INSERT INTO abc SELECT * FROM abc;
   181      INSERT INTO abc SELECT * FROM abc;
   182      INSERT INTO abc SELECT * FROM abc;
   183      INSERT INTO abc SELECT * FROM abc;
   184      INSERT INTO abc SELECT * FROM abc;
   185    }
   186    expr ([file size test.db] / 1024) > 450
   187  } {1}
   188  for {set i 1} {$i < $repeats} {incr i} {
   189    set sig [signature]
   190    do_test crash-3.$i.1 {
   191       set seed [expr {int(abs(rand()*10000))}]
   192       crashsql -delay [expr $i%5 + 1] -file test.db-journal -seed $seed "
   193         BEGIN;
   194         SELECT random() FROM abc LIMIT $i;
   195         INSERT INTO abc VALUES(randstr(10,10), 0, 0);
   196         DELETE FROM abc WHERE random()%10!=0;
   197         COMMIT;
   198       "
   199    } {1 {child process exited abnormally}}
   200    do_test crash-3.$i.2 {
   201      signature
   202    } $sig
   203  } 
   204  
   205  #--------------------------------------------------------------------------
   206  # The following test cases - crash-4.* - test the correct recovery of the
   207  # database when a crash occurs during a multi-file transaction.
   208  #
   209  # crash-4.1.*: Test recovery when crash occurs during sync() of the 
   210  #              main database journal file.
   211  # crash-4.2.*: Test recovery when crash occurs during sync() of an 
   212  #              attached database journal file.
   213  # crash-4.3.*: Test recovery when crash occurs during sync() of the master
   214  #              journal file. 
   215  #
   216  ifcapable attach {
   217    do_test crash-4.0 {
   218      forcedelete test2.db
   219      forcedelete test2.db-journal
   220      execsql {
   221        ATTACH 'test2.db' AS aux;
   222        PRAGMA aux.default_cache_size = 10;
   223        CREATE TABLE aux.abc2 AS SELECT 2*a as a, 2*b as b, 2*c as c FROM abc;
   224      }
   225      expr ([file size test2.db] / 1024) > 450
   226    } {1}
   227    
   228    set fin 0
   229    for {set i 1} {$i<$repeats} {incr i} {
   230      set seed [expr {int(abs(rand()*10000))}]
   231      set sig [signature]
   232      set sig2 [signature2]
   233      do_test crash-4.1.$i.1 {
   234         set c [crashsql -delay $i -file test.db-journal -seed $::seed "
   235           ATTACH 'test2.db' AS aux;
   236           BEGIN;
   237           SELECT randstr($i,$i) FROM abc LIMIT $i;
   238           INSERT INTO abc VALUES(randstr(10,10), 0, 0);
   239           DELETE FROM abc WHERE random()%10!=0;
   240           INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
   241           DELETE FROM abc2 WHERE random()%10!=0;
   242           COMMIT;
   243         "]
   244         if { $c == {0 {}} } {
   245           set ::fin 1
   246           set c {1 {child process exited abnormally}}
   247         }
   248         set c
   249      } {1 {child process exited abnormally}}
   250      if {$::fin} break
   251      do_test crash-4.1.$i.2 {
   252        signature
   253      } $sig
   254      do_test crash-4.1.$i.3 {
   255        signature2
   256      } $sig2
   257    } 
   258    set i 0
   259    set fin 0
   260    while {[incr i]} {
   261      set seed [expr {int(abs(rand()*10000))}]
   262      set sig [signature]
   263      set sig2 [signature2]
   264      set ::fin 0
   265      do_test crash-4.2.$i.1 {
   266         set c [crashsql -delay $i -file test2.db-journal -seed $::seed "
   267           ATTACH 'test2.db' AS aux;
   268           BEGIN;
   269           SELECT randstr($i,$i) FROM abc LIMIT $i;
   270           INSERT INTO abc VALUES(randstr(10,10), 0, 0);
   271           DELETE FROM abc WHERE random()%10!=0;
   272           INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
   273           DELETE FROM abc2 WHERE random()%10!=0;
   274           COMMIT;
   275         "]
   276         if { $c == {0 {}} } {
   277           set ::fin 1
   278           set c {1 {child process exited abnormally}}
   279         }
   280         set c
   281      } {1 {child process exited abnormally}}
   282      if { $::fin } break
   283      do_test crash-4.2.$i.2 {
   284        signature
   285      } $sig
   286      do_test crash-4.2.$i.3 {
   287        signature2
   288      } $sig2
   289    } 
   290    for {set i 1} {$i < 5} {incr i} {
   291      set sig [signature]
   292      set sig2 [signature2]
   293      do_test crash-4.3.$i.1 {
   294         crashsql -delay 1 -file test.db-mj* "
   295           ATTACH 'test2.db' AS aux;
   296           BEGIN;
   297           SELECT random() FROM abc LIMIT $i;
   298           INSERT INTO abc VALUES(randstr(10,10), 0, 0);
   299           DELETE FROM abc WHERE random()%10!=0;
   300           INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
   301           DELETE FROM abc2 WHERE random()%10!=0;
   302           COMMIT;
   303         "
   304      } {1 {child process exited abnormally}}
   305      do_test crash-4.3.$i.2 {
   306        signature
   307      } $sig
   308      do_test crash-4.3.$i.3 {
   309        signature2
   310      } $sig2
   311    }
   312  }
   313  
   314  #--------------------------------------------------------------------------
   315  # The following test cases - crash-5.* - exposes a bug that existed in the
   316  # sqlite3pager_movepage() API used by auto-vacuum databases.
   317  # database when a crash occurs during a multi-file transaction. See comments
   318  # in test crash-5.3 for details.
   319  #
   320  db close
   321  forcedelete test.db
   322  sqlite3 db test.db
   323  do_test crash-5.1 {
   324    execsql {
   325      CREATE TABLE abc(a, b, c);                          -- Root page 3
   326      INSERT INTO abc VALUES(randstr(1500,1500), 0, 0);   -- Overflow page 4
   327      INSERT INTO abc SELECT * FROM abc;
   328      INSERT INTO abc SELECT * FROM abc;
   329      INSERT INTO abc SELECT * FROM abc;
   330    }
   331  } {}
   332  do_test crash-5.2 {
   333    expr [file size test.db] / 1024
   334  } [expr [string match [execsql {pragma auto_vacuum}] 1] ? 11 : 10]
   335  set sig [signature]
   336  do_test crash-5.3 {
   337  # The SQL below is used to expose a bug that existed in
   338  # sqlite3pager_movepage() during development of the auto-vacuum feature. It
   339  # functions as follows:
   340  # 
   341  # 1: Begin a transaction.
   342  # 2: Put page 4 on the free-list (was the overflow page for the row deleted).
   343  # 3: Write data to page 4 (it becomes the overflow page for the row inserted).
   344  #    The old page 4 data has been written to the journal file, but the
   345  #    journal file has not been sync()hronized.
   346  # 4: Create a table, which calls sqlite3pager_movepage() to move page 4
   347  #    to the end of the database (page 12) to make room for the new root-page.
   348  # 5: Put pressure on the pager-cache. This results in page 4 being written
   349  #    to the database file to make space in the cache to load a new page. The
   350  #    bug was that page 4 was written to the database file before the journal
   351  #    is sync()hronized.
   352  # 6: Commit. A crash occurs during the sync of the journal file.
   353  #
   354  # End result: Before the bug was fixed, data has been written to page 4 of the
   355  # database file and the journal file does not contain trustworthy rollback
   356  # data for this page.
   357  #
   358    crashsql -delay 1 -file test.db-journal {
   359      BEGIN;                                             -- 1
   360      DELETE FROM abc WHERE oid = 1;                     -- 2
   361      INSERT INTO abc VALUES(randstr(1500,1500), 0, 0);  -- 3
   362      CREATE TABLE abc2(a, b, c);                        -- 4
   363      SELECT * FROM abc;                                 -- 5
   364      COMMIT;                                            -- 6
   365    }
   366  } {1 {child process exited abnormally}}
   367  integrity_check crash-5.4
   368  do_test crash-5.5 {
   369    signature
   370  } $sig
   371  
   372  #--------------------------------------------------------------------------
   373  # The following test cases - crash-6.* - test that a DROP TABLE operation
   374  # is correctly rolled back in the event of a crash while the database file
   375  # is being written. This is mainly to test that all pages are written to the
   376  # journal file before truncation in an auto-vacuum database.
   377  #
   378  do_test crash-6.1 {
   379    crashsql -delay 1 -file test.db {
   380      DROP TABLE abc;
   381    }
   382  } {1 {child process exited abnormally}}
   383  do_test crash-6.2 {
   384    signature
   385  } $sig
   386  
   387  #--------------------------------------------------------------------------
   388  # These test cases test the case where the master journal file name is 
   389  # corrupted slightly so that the corruption has to be detected by the
   390  # checksum.
   391  do_test crash-7.1 {
   392    crashsql -delay 1 -file test.db {
   393      ATTACH 'test2.db' AS aux;
   394      BEGIN;
   395      INSERT INTO abc VALUES(randstr(1500,1500), 0, 0);
   396      INSERT INTO abc2 VALUES(randstr(1500,1500), 0, 0);
   397      COMMIT;
   398    }
   399  
   400    # Change the checksum value for the master journal name.
   401    set f [open test.db-journal a]
   402    fconfigure $f -encoding binary
   403    seek $f [expr [file size test.db-journal] - 12]
   404    puts -nonewline $f "\00\00\00\00"
   405    close $f
   406  } {}
   407  do_test crash-7.2 {
   408    signature
   409  } $sig
   410  
   411  finish_test