SQLite4
Check-in [cd8da865a428db092e6514c84a4f2cdf494b665c]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
SHA1 Hash:cd8da865a428db092e6514c84a4f2cdf494b665c
Date: 2014-02-18 20:01:27
User: dan
Comment:Add crash simulation and recovery test infrastructure. And one test case.
Tags And Properties
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/kvbt.c

259
260
261
262
263
264
265
266

267
268
269
270
271
272
273
274
275
276
277
278
279
280
...
285
286
287
288
289
290
291













292
293
294
295
296
297
298
299
300
301
302
303
304
...
306
307
308
309
310
311
312

313
314
315
316
317
318
319
  sqlite4_kvstore *pKVStore;
  int ePragma;
};

/*
** Candidate values for BtPragmaCtx.ePragma
*/
#define BTPRAGMA_PAGESZ 1


static void btPragmaDestroy(void *pArg){
  BtPragmaCtx *p = (BtPragmaCtx*)pArg;
  sqlite4_free(p->pKVStore->pEnv, p);
}

static int btPragma(
  sqlite4_context *pCtx, 
  int nVal,
  sqlite4_value **apVal
){
  int rc = SQLITE4_OK;            /* Return code */
  BtPragmaCtx *p = (BtPragmaCtx*)sqlite4_context_appdata(pCtx);
  bt_db *db = ((KVBt*)(p->pKVStore))->pDb;
................................................................................
      if( nVal>0 ){
        pgsz = sqlite4_value_int(apVal[0]);
      }
      sqlite4BtControl(db, BT_CONTROL_PAGESZ, (void*)&pgsz);
      sqlite4_result_int(pCtx, pgsz);
      break;
    }














    default:
      assert( 0 );
  }

  return rc;
}


static int btGetMethod(
  sqlite4_kvstore *pKVStore, 
  const char *zMethod, 
  void **ppArg,
................................................................................
  void (**pxDestroy)(void*)
){
  struct PragmaMethod {
    const char *zPragma;
    int ePragma;
  } aPragma[] = {
    { "page_size", BTPRAGMA_PAGESZ },

  };
  int i;
  for(i=0; i<ArraySize(aPragma); i++){
    if( sqlite4_stricmp(aPragma[i].zPragma, zMethod)==0 ){
      BtPragmaCtx *pCtx = sqlite4_malloc(pKVStore->pEnv, sizeof(BtPragmaCtx));
      if( pCtx==0 ) return SQLITE4_NOMEM;
      pCtx->ePragma = aPragma[i].ePragma;







|
>






|







 







>
>
>
>
>
>
>
>
>
>
>
>
>




<
<







 







>







259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
...
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309


310
311
312
313
314
315
316
...
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
  sqlite4_kvstore *pKVStore;
  int ePragma;
};

/*
** Candidate values for BtPragmaCtx.ePragma
*/
#define BTPRAGMA_PAGESZ     1
#define BTPRAGMA_CHECKPOINT 2

static void btPragmaDestroy(void *pArg){
  BtPragmaCtx *p = (BtPragmaCtx*)pArg;
  sqlite4_free(p->pKVStore->pEnv, p);
}

static void btPragma(
  sqlite4_context *pCtx, 
  int nVal,
  sqlite4_value **apVal
){
  int rc = SQLITE4_OK;            /* Return code */
  BtPragmaCtx *p = (BtPragmaCtx*)sqlite4_context_appdata(pCtx);
  bt_db *db = ((KVBt*)(p->pKVStore))->pDb;
................................................................................
      if( nVal>0 ){
        pgsz = sqlite4_value_int(apVal[0]);
      }
      sqlite4BtControl(db, BT_CONTROL_PAGESZ, (void*)&pgsz);
      sqlite4_result_int(pCtx, pgsz);
      break;
    }

    case BTPRAGMA_CHECKPOINT: {
      bt_checkpoint ckpt;
      ckpt.nFrameBuffer = 0;
      ckpt.nCkpt = 0;
      rc = sqlite4BtControl(db, BT_CONTROL_CHECKPOINT, (void*)&ckpt);
      if( rc!=SQLITE4_OK ){
        sqlite4_result_error_code(pCtx, rc);
      }else{
        sqlite4_result_int(pCtx, ckpt.nCkpt);
      }
      break;
    }

    default:
      assert( 0 );
  }


}


static int btGetMethod(
  sqlite4_kvstore *pKVStore, 
  const char *zMethod, 
  void **ppArg,
................................................................................
  void (**pxDestroy)(void*)
){
  struct PragmaMethod {
    const char *zPragma;
    int ePragma;
  } aPragma[] = {
    { "page_size", BTPRAGMA_PAGESZ },
    { "checkpoint", BTPRAGMA_CHECKPOINT },
  };
  int i;
  for(i=0; i<ArraySize(aPragma); i++){
    if( sqlite4_stricmp(aPragma[i].zPragma, zMethod)==0 ){
      BtPragmaCtx *pCtx = sqlite4_malloc(pKVStore->pEnv, sizeof(BtPragmaCtx));
      if( pCtx==0 ) return SQLITE4_NOMEM;
      pCtx->ePragma = aPragma[i].ePragma;

Changes to test/fault1.test

17
18
19
20
21
22
23

24
25
26
27
28
29
30
31
32
33
34
..
35
36
37
38
39
40
41
42
43
44
45
46
47

db close

proc open_test_db {} { 
  sqlite4 db test.db
  btenv testenv
  testenv attach db

}

proc close_test_db {} { 
  db close
  testenv delete
}

do_faultsim_test 2.0 -prep {
  forcedelete test.db test.db-wal
  open_test_db
} -body {
................................................................................
  execsql {
    CREATE TABLE t1(x,y);
    INSERT INTO t1 VALUES('abc', 'def');
    SELECT * FROM t1;
  }
} -test {
  faultsim_test_result {0 {abc def}}
  close_test_db
}

finish_test









>


<
|







 







|




<
17
18
19
20
21
22
23
24
25
26

27
28
29
30
31
32
33
34
..
35
36
37
38
39
40
41
42
43
44
45
46


db close

proc open_test_db {} { 
  sqlite4 db test.db
  btenv testenv
  testenv attach db
  trace add command db delete del_testenv
}


proc del_testenv {args} { 
  testenv delete
}

do_faultsim_test 2.0 -prep {
  forcedelete test.db test.db-wal
  open_test_db
} -body {
................................................................................
  execsql {
    CREATE TABLE t1(x,y);
    INSERT INTO t1 VALUES('abc', 'def');
    SELECT * FROM t1;
  }
} -test {
  faultsim_test_result {0 {abc def}}
  db close
}

finish_test


Added test/recover1.test































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# 2014 February 19
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/malloc_common.tcl
set ::testprefix recover1


#-------------------------------------------------------------------------
#   crashsql2 ?-wal? N SQL
#
# Open the database and execute the SQL script, with crash simulation on
# the N'th sync of either the log or database file, depending on whether
# or not the "-wal" switch is present.
#
proc crashsql2 {args} {
  set bWal 0
  set nSync 0

  if {([llength $args]!=2)
   && ([llength $args]!=3 || 0==[string match [lindex $args 0]* "-wal"])
  } {
    error "should be: crashsql2 ?-wal? N SQL"
  }

  set bWal [expr {[llength $args]==3}]
  set nSync [lindex $args [expr [llength $args] - 2]]
  set sql [lindex $args [expr [llength $args] - 1]]

  sqlite4 dbc test.db
  btenv crashenv 
  crashenv attach dbc
  crashenv crash $nSync $bWal

  catch { dbc eval $sql } msg
  dbc close
  set ret [crashenv crash 0 0]
  crashenv delete
  return $ret
}

proc checkdb {} {
  db one { SELECT (SELECT x FROM sum)==(SELECT md5sum(a, b) FROM t1); }
}

do_execsql_test 1.0 {
  CREATE TABLE t1(a, b);
  CREATE TABLE sum(x);

  INSERT INTO t1 VALUES(randomblob(200), randomblob(200));
  INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1;
  INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1;
  INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1;
  INSERT INTO sum SELECT md5sum(a, b) FROM t1;
}
do_test 1.1 { checkdb } 1

for {set i 1} {$i<25} {incr i} {
  set nRow [db one {SELECT count(*) FROM t1}]
  db close

  do_test 1.2.$i.1 {
    crashsql2 1 {
      BEGIN;
      INSERT INTO t1 SELECT randomblob(200), randomblob(200) FROM t1;
      UPDATE sum SET x = (SELECT md5sum(a, b) FROM t1);
      COMMIT;
      PRAGMA main.checkpoint = 1;
    }
  } {1}

  do_test 1.2.$i.2 { 
    sqlite4 db test.db
    checkdb 
  } 1

  do_execsql_test 1.2.$i.3 { SELECT count(*) FROM t1 } [expr $nRow*2]
  do_execsql_test 1.2.$i.4 { DELETE FROM t1 WHERE rowid%2 }
}

finish_test




Changes to test/test_bt.c

14
15
16
17
18
19
20
21

22
23


24







25
26
27
28
29
30
31
32
33







34
35

36





37
38


39



40
41
42





























































































43




44
45

46
47
48
49
50
51
52
..
68
69
70
71
72
73
74

75
76
77
78



79
80
81
82
83
84
85
..
97
98
99
100
101
102
103
104
105
106
107

















































108
109
110
111
112
113
114
115
116
117
118
...
138
139
140
141
142
143
144





145
146

















147
148
149
150
151
152
153



154
155
156
157
158
159
160
...
255
256
257
258
259
260
261
262

263
264



265
266
267
268
269
270
271
272
...
286
287
288
289
290
291
292

293
294
295
296
297
298
299
300
301
302

303
304
305
306
307
308
309
...
351
352
353
354
355
356
357
358












359
360
361
362
363
364
365
#include <tcl.h>
#include "bt.h"
#include "sqlite4.h"
#include "testInt.h"
#include <assert.h>
#include <string.h>

typedef sqlite4_int64 i64;





typedef struct BtTestEnv BtTestEnv;







struct BtTestEnv {
  bt_env base;                    /* The wrapper environment */
  sqlite4_env *pSqlEnv;           /* SQLite environment */
  bt_env *pEnv;                   /* Underlying environment object */

  /* IO error injection parameters */
  int nIoerrCnt;                  /* Error countdown */
  int bIoerrPersist;              /* True for persistent errors */
  int nIoerrInjected;             /* Number of errors injected */







};


typedef struct BtTestFile BtTestFile;





struct BtTestFile {
  BtTestEnv *pTestEnv;            /* Test environment */


  bt_file *pFile;                 /* Underlying file object */



};

/*





























































































** Return true if an IO error should be injected. False otherwise.




*/
static int testInjectIoerr(BtTestEnv *p){

  if( (p->nIoerrCnt==0 && p->bIoerrPersist)
   || (p->nIoerrCnt>0 && (--p->nIoerrCnt)==0)
  ){
    p->nIoerrInjected++;
    return 1;
  }
  return 0;
................................................................................

  if( testInjectIoerr(p) ){
    rc = SQLITE4_CANTOPEN;
  }else{
    pNew = ckalloc(sizeof(BtTestFile));
    memset(pNew, 0, sizeof(BtTestFile));
    pNew->pTestEnv = p;

    rc = p->pEnv->xOpen(pSqlEnv, p->pEnv, zFile, flags, &pNew->pFile);
    if( rc!=SQLITE4_OK ){
      ckfree(pNew);
      pNew = 0;



    }
  }
  *ppFile = (bt_file*)pNew;
  return rc;
}

static int testBtOsSize(bt_file *pFile, i64 *pnByte){
................................................................................

static int testBtOsWrite(
  bt_file *pFile,                 /* File to write to */
  i64 iOff,                       /* Offset to write to */
  void *pData,                    /* Write data from this buffer */
  int nData                       /* Bytes of data to write */
){
  BtTestFile *pTestFile = (BtTestFile*)pFile;
  bt_env *pEnv = pTestFile->pTestEnv->pEnv;
  int rc;


















































  if( testInjectIoerr(pTestFile->pTestEnv) ){
    rc = SQLITE4_IOERR_WRITE;
  }else{
    rc = pEnv->xWrite(pTestFile->pFile, iOff, pData, nData);
  }
  return rc;
}

static int testBtOsTruncate(
  bt_file *pFile,                 /* File to write to */
  i64 nSize                       /* Size to truncate file to */
................................................................................
  BtTestFile *pTestFile = (BtTestFile*)pFile;
  bt_env *pEnv = pTestFile->pTestEnv->pEnv;
  int rc;

  if( testInjectIoerr(pTestFile->pTestEnv) ){
    rc = SQLITE4_IOERR_READ;
  }else{





    rc = pEnv->xRead(pTestFile->pFile, iOff, pData, nData);
  }

















  return rc;
}

static int testBtOsSync(bt_file *pFile){
  BtTestFile *pTestFile = (BtTestFile*)pFile;
  bt_env *pEnv = pTestFile->pTestEnv->pEnv;
  int rc;




  if( testInjectIoerr(pTestFile->pTestEnv) ){
    rc = SQLITE4_IOERR_FSYNC;
  }else{
    rc = pEnv->xSync(pTestFile->pFile);
  }
  return rc;
................................................................................

  rc = pEnv->xShmUnmap(pTestFile->pFile, bDelete);
  return rc;
}

static int testBtOsClose(bt_file *pFile){
  BtTestFile *pTestFile = (BtTestFile*)pFile;
  bt_env *pEnv = pTestFile->pTestEnv->pEnv;

  int rc;




  rc = pEnv->xClose(pTestFile->pFile);
  ckfree(pTestFile);
  return rc;
}

/*
** Destructor for object created by tcl [btenv] command.
*/
................................................................................
){
  BtTestEnv *p = (BtTestEnv*)clientData;

  enum BtenvCmdSymbol {
    BTC_ATTACH,
    BTC_DELETE,
    BTC_IOERR,

  };
  struct BtenvCmd {
    const char *zOpt;
    int eOpt;
    int nArg;
    const char *zErr;
  } aCmd[] = {
    { "attach", BTC_ATTACH, 3, "DBCMD" },
    { "delete", BTC_DELETE, 2, "" },
    { "ioerr",  BTC_IOERR,  4, "COUNT PERSISTENT" },

    { 0, 0 }
  };
  int rc = TCL_OK;
  int iOpt;

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "sub-command ...");
................................................................................
      if( Tcl_GetIntFromObj(interp, objv[2], &p->nIoerrCnt)
       || Tcl_GetBooleanFromObj(interp, objv[3], &p->bIoerrPersist)
      ){
        rc = TCL_ERROR;
      }else{
        Tcl_SetObjResult(interp, Tcl_NewIntObj(ret));
      }
      












      break;
    }

    default:
      assert( 0 );
      break;
  }







|
>

<
>
>

>
>
>
>
>
>
>









>
>
>
>
>
>
>


>
|
>
>
>
>
>


>
>

>
>
>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
>


>







 







>




>
>
>







 







|
|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|


|







 







>
>
>
>
>

|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







>
>
>







 







|
>


>
>
>
|







 







>










>







 







|
>
>
>
>
>
>
>
>
>
>
>
>







14
15
16
17
18
19
20
21
22
23

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
...
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
...
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
...
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
...
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
...
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
...
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
#include <tcl.h>
#include "bt.h"
#include "sqlite4.h"
#include "testInt.h"
#include <assert.h>
#include <string.h>

#define MIN(x,y) (((x)<(y)) ? (x) : (y))
#define MAX(x,y) (((x)<(y)) ? (y) : (x))


typedef sqlite4_int64 i64;
typedef unsigned char u8;
typedef struct BtTestEnv BtTestEnv;
typedef struct BtTestFile BtTestFile;
typedef struct BtTestSector BtTestSector;

/*
** An instance of the following object is created each invocation of the 
** [btenv] command. 
*/
struct BtTestEnv {
  bt_env base;                    /* The wrapper environment */
  sqlite4_env *pSqlEnv;           /* SQLite environment */
  bt_env *pEnv;                   /* Underlying environment object */

  /* IO error injection parameters */
  int nIoerrCnt;                  /* Error countdown */
  int bIoerrPersist;              /* True for persistent errors */
  int nIoerrInjected;             /* Number of errors injected */

  /* Crash simulation parameters */
  int nCrashCnt;                  /* Crash on nCrashCnt'th xSync() */
  int bWalCrash;                  /* Crash on xSync of WAL file */
  int bCrashed;                   /* True after crash is simulated */
  
  BtTestFile *pTestFile;          /* List of open test files */
};

#define BTTEST_SECTOR_SIZE 512
struct BtTestSector {
  int iFirstUnsynced;             /* Offset of first dirty byte within sector */
  int iLastUnsynced;              /* One more than offset of last dirty byte */
  u8 aBuf[BTTEST_SECTOR_SIZE];    /* Sector contents */
};

struct BtTestFile {
  BtTestEnv *pTestEnv;            /* Test environment */
  BtTestFile *pNext;              /* Next file belonging to pTestEnv */
  int flags;                      /* Copy of flags parameter passed to xOpen */
  bt_file *pFile;                 /* Underlying file object */

  int nSector;                    /* Size of apSector[] array */
  BtTestSector **apSector;        /* Data written but not synced */
};

/*
** If required, simulate the effects of a crash or power failure on the 
** contents of the file-system. Otherwise, flush any buffers held by this
** file object to disk.
*/
static void testFlush(BtTestFile *pTest){
  int i;
  bt_env *pEnv = pTest->pTestEnv->pEnv;

  for(i=0; i<pTest->nSector; i++){
    BtTestSector *pSector = pTest->apSector[i];
    if( pSector ){
      i64 iOff = (i64)i * BTTEST_SECTOR_SIZE + pSector->iFirstUnsynced;
      int nData = pSector->iLastUnsynced - pSector->iFirstUnsynced;
      pEnv->xWrite(
          pTest->pFile, iOff, &pSector->aBuf[pSector->iFirstUnsynced], nData
      );
    }

    pTest->apSector[i] = 0;
    ckfree(pSector);
  }

  ckfree(pTest->apSector);
  pTest->apSector = 0;
  pTest->nSector = 0;
}

static void testCrash(BtTestEnv *pTestEnv){
  BtTestFile *pFile;
  for(pFile=pTestEnv->pTestFile; pFile; pFile=pFile->pNext){
    int i;
    for(i=0; i<pFile->nSector; i++){
      int r;
      BtTestSector *pSector = pFile->apSector[i];
      if( pSector==0 ) continue;

      sqlite4_randomness(pTestEnv->pSqlEnv, sizeof(int), &r);
      switch( r%3 ){
        case 0:
          /* no-op */
          break;

        case 1:
          ckfree(pSector);
          pFile->apSector[i] = 0;
          break;

        case 2: {
          int nByte = (pSector->iLastUnsynced - pSector->iFirstUnsynced);
          sqlite4_randomness(
              pTestEnv->pSqlEnv, nByte, &pSector->aBuf[pSector->iFirstUnsynced]
          );
          break;
        }
      }
    }
  }

  pTestEnv->bCrashed = 1;
}

/*
** If required, simulate the effects of a crash or power failure on the 
** contents of the file-system. Otherwise, flush any buffers held by this
** file object to disk.
*/
static void testCrashOrFlush(BtTestFile *pTest){
  int bAll = 0;
  BtTestEnv *pTestEnv = pTest->pTestEnv;

  if( pTestEnv->nCrashCnt ){
    if( ((pTest->flags & BT_OPEN_LOG) && pTestEnv->bWalCrash)
     || ((pTest->flags & BT_OPEN_DATABASE) && pTestEnv->bWalCrash==0)
    ){
      pTestEnv->nCrashCnt--;
      if( pTestEnv->nCrashCnt==0 ){
        testCrash(pTestEnv);
        bAll = 1;
      }
    }
  }

  if( bAll ){
    BtTestFile *pFile;
    for(pFile=pTestEnv->pTestFile; pFile; pFile=pFile->pNext){
      testFlush(pFile);
    }
  }else{
    testFlush(pTest);
  }
}

/*
** Return true if an IO error should be injected. False otherwise.
**
** Also return true if the BtTestEnv is in "crashed" state. In this state
** the only objective is to prevent any further write operations to the 
** file-system. 
*/
static int testInjectIoerr(BtTestEnv *p){
  if( p->bCrashed ) return 1;
  if( (p->nIoerrCnt==0 && p->bIoerrPersist)
   || (p->nIoerrCnt>0 && (--p->nIoerrCnt)==0)
  ){
    p->nIoerrInjected++;
    return 1;
  }
  return 0;
................................................................................

  if( testInjectIoerr(p) ){
    rc = SQLITE4_CANTOPEN;
  }else{
    pNew = ckalloc(sizeof(BtTestFile));
    memset(pNew, 0, sizeof(BtTestFile));
    pNew->pTestEnv = p;
    pNew->flags = flags;
    rc = p->pEnv->xOpen(pSqlEnv, p->pEnv, zFile, flags, &pNew->pFile);
    if( rc!=SQLITE4_OK ){
      ckfree(pNew);
      pNew = 0;
    }else{
      pNew->pNext = p->pTestFile;
      p->pTestFile = pNew;
    }
  }
  *ppFile = (bt_file*)pNew;
  return rc;
}

static int testBtOsSize(bt_file *pFile, i64 *pnByte){
................................................................................

static int testBtOsWrite(
  bt_file *pFile,                 /* File to write to */
  i64 iOff,                       /* Offset to write to */
  void *pData,                    /* Write data from this buffer */
  int nData                       /* Bytes of data to write */
){
  BtTestFile *pTest = (BtTestFile*)pFile;
  BtTestEnv *pTestEnv = pTest->pTestEnv;
  int rc;

  if( pTestEnv->nCrashCnt ){
    /* A crash simulation is running. Instead of writing to the file on
    ** disk, store the new data in the BtTestfile.apSector[] array.  */
    i64 iWrite = iOff;
    int nRem = nData;
    u8 *aRem = (u8*)pData;
    int nSector;
    int iSector;

    /* Ensure the apSector[] array is large enough */
    nSector = ((iOff+nData) + BTTEST_SECTOR_SIZE - 1) / BTTEST_SECTOR_SIZE;
    if( nSector>pTest->nSector ){
      int nByte = sizeof(BtTestSector*)*nSector;
      int nExtra = nByte - sizeof(BtTestSector*)*pTest->nSector;
      pTest->apSector = (BtTestSector**)ckrealloc(pTest->apSector, nByte);
      memset(&pTest->apSector[pTest->nSector], 0, nExtra);
      pTest->nSector = nSector;
    }

    /* Update or create the required BtTestSector objects */
    for(iSector=(iOff / BTTEST_SECTOR_SIZE); iSector<nSector; iSector++){
      BtTestSector *pSector;
      int nCopy;
      int iSectorOff;

      if( pTest->apSector[iSector]==0 ){
        int nByte = sizeof(BtTestSector);
        pTest->apSector[iSector] = (BtTestSector*)ckalloc(nByte);
        memset(pTest->apSector[iSector], 0, nByte);
      }
      pSector = pTest->apSector[iSector];

      iSectorOff = (iWrite % BTTEST_SECTOR_SIZE);
      nCopy = MIN(nRem, (BTTEST_SECTOR_SIZE - iSectorOff));
      memcpy(&pSector->aBuf[iSectorOff], aRem, nCopy);

      nRem -= nCopy; 
      aRem += nCopy;
      iWrite += nCopy;

      if( pSector->iLastUnsynced==0 ){
        pSector->iFirstUnsynced = iSectorOff;
        pSector->iLastUnsynced = nCopy;
      }else{
        pSector->iFirstUnsynced = MIN(pSector->iFirstUnsynced, iSectorOff);
        pSector->iLastUnsynced = MAX(pSector->iLastUnsynced, iSectorOff+nCopy);
      }
    }
    rc = SQLITE4_OK;
  }else if( testInjectIoerr(pTest->pTestEnv) ){
    rc = SQLITE4_IOERR_WRITE;
  }else{
    rc = pTestEnv->pEnv->xWrite(pTest->pFile, iOff, pData, nData);
  }
  return rc;
}

static int testBtOsTruncate(
  bt_file *pFile,                 /* File to write to */
  i64 nSize                       /* Size to truncate file to */
................................................................................
  BtTestFile *pTestFile = (BtTestFile*)pFile;
  bt_env *pEnv = pTestFile->pTestEnv->pEnv;
  int rc;

  if( testInjectIoerr(pTestFile->pTestEnv) ){
    rc = SQLITE4_IOERR_READ;
  }else{
    int iSector;
    u8 *aOut = (u8*)pData;
    int nRem = nData;
    int iOffset = (iOff % BTTEST_SECTOR_SIZE);

    rc = pEnv->xRead(pTestFile->pFile, iOff, pData, nData);

    /* Read any buffered data */
    for(iSector=(iOff / BTTEST_SECTOR_SIZE); nRem>0; iSector++){
      if( (pTestFile->nSector > iSector) && pTestFile->apSector[iSector] ){
        BtTestSector *pSector = pTestFile->apSector[iSector];
        int iStart = MAX(pSector->iFirstUnsynced, iOffset);
        int nCopy = MIN(BTTEST_SECTOR_SIZE - iStart, nRem + iOffset - iStart);

        if( nCopy>0 ){
          memcpy(&aOut[iOffset-iStart], &pSector->aBuf[iStart], nCopy);
        }
      }
      nRem -= (BTTEST_SECTOR_SIZE - iOffset);
      aOut += (BTTEST_SECTOR_SIZE - iOffset);
      iOffset = 0;
    }
  }

  return rc;
}

static int testBtOsSync(bt_file *pFile){
  BtTestFile *pTestFile = (BtTestFile*)pFile;
  bt_env *pEnv = pTestFile->pTestEnv->pEnv;
  int rc;

  /* If required, simulate the effects of a crash on the file-system */
  testCrashOrFlush(pTestFile);

  if( testInjectIoerr(pTestFile->pTestEnv) ){
    rc = SQLITE4_IOERR_FSYNC;
  }else{
    rc = pEnv->xSync(pTestFile->pFile);
  }
  return rc;
................................................................................

  rc = pEnv->xShmUnmap(pTestFile->pFile, bDelete);
  return rc;
}

static int testBtOsClose(bt_file *pFile){
  BtTestFile *pTestFile = (BtTestFile*)pFile;
  BtTestEnv *pTestEnv = pTestFile->pTestEnv;
  BtTestFile **pp;
  int rc;

  for(pp=&pTestEnv->pTestFile; *pp!=pTestFile; pp = &(*pp)->pNext);
  *pp = pTestFile->pNext;

  rc = pTestEnv->pEnv->xClose(pTestFile->pFile);
  ckfree(pTestFile);
  return rc;
}

/*
** Destructor for object created by tcl [btenv] command.
*/
................................................................................
){
  BtTestEnv *p = (BtTestEnv*)clientData;

  enum BtenvCmdSymbol {
    BTC_ATTACH,
    BTC_DELETE,
    BTC_IOERR,
    BTC_CRASH,
  };
  struct BtenvCmd {
    const char *zOpt;
    int eOpt;
    int nArg;
    const char *zErr;
  } aCmd[] = {
    { "attach", BTC_ATTACH, 3, "DBCMD" },
    { "delete", BTC_DELETE, 2, "" },
    { "ioerr",  BTC_IOERR,  4, "COUNT PERSISTENT" },
    { "crash",  BTC_CRASH,  4, "COUNT WAL" },
    { 0, 0 }
  };
  int rc = TCL_OK;
  int iOpt;

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "sub-command ...");
................................................................................
      if( Tcl_GetIntFromObj(interp, objv[2], &p->nIoerrCnt)
       || Tcl_GetBooleanFromObj(interp, objv[3], &p->bIoerrPersist)
      ){
        rc = TCL_ERROR;
      }else{
        Tcl_SetObjResult(interp, Tcl_NewIntObj(ret));
      }

      break;
    }

    case BTC_CRASH: {
      if( Tcl_GetIntFromObj(interp, objv[2], &p->nCrashCnt)
       || Tcl_GetBooleanFromObj(interp, objv[3], &p->bWalCrash)
      ){
        rc = TCL_ERROR;
      }else{
        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(p->bCrashed));
        p->bCrashed = 0;
      }
      break;
    }

    default:
      assert( 0 );
      break;
  }