OLD | NEW |
| (Empty) |
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 some common TCL routines used for regression | |
12 # testing the SQLite library | |
13 # | |
14 # $Id: tester.tcl,v 1.143 2009/04/09 01:23:49 drh Exp $ | |
15 | |
16 #------------------------------------------------------------------------- | |
17 # The commands provided by the code in this file to help with creating | |
18 # test cases are as follows: | |
19 # | |
20 # Commands to manipulate the db and the file-system at a high level: | |
21 # | |
22 # is_relative_file | |
23 # test_pwd | |
24 # get_pwd | |
25 # copy_file FROM TO | |
26 # delete_file FILENAME | |
27 # drop_all_tables ?DB? | |
28 # forcecopy FROM TO | |
29 # forcedelete FILENAME | |
30 # | |
31 # Test the capability of the SQLite version built into the interpreter to | |
32 # determine if a specific test can be run: | |
33 # | |
34 # capable EXPR | |
35 # ifcapable EXPR | |
36 # | |
37 # Calulate checksums based on database contents: | |
38 # | |
39 # dbcksum DB DBNAME | |
40 # allcksum ?DB? | |
41 # cksum ?DB? | |
42 # | |
43 # Commands to execute/explain SQL statements: | |
44 # | |
45 # memdbsql SQL | |
46 # stepsql DB SQL | |
47 # execsql2 SQL | |
48 # explain_no_trace SQL | |
49 # explain SQL ?DB? | |
50 # catchsql SQL ?DB? | |
51 # execsql SQL ?DB? | |
52 # | |
53 # Commands to run test cases: | |
54 # | |
55 # do_ioerr_test TESTNAME ARGS... | |
56 # crashsql ARGS... | |
57 # integrity_check TESTNAME ?DB? | |
58 # verify_ex_errcode TESTNAME EXPECTED ?DB? | |
59 # do_test TESTNAME SCRIPT EXPECTED | |
60 # do_execsql_test TESTNAME SQL EXPECTED | |
61 # do_catchsql_test TESTNAME SQL EXPECTED | |
62 # do_timed_execsql_test TESTNAME SQL EXPECTED | |
63 # | |
64 # Commands providing a lower level interface to the global test counters: | |
65 # | |
66 # set_test_counter COUNTER ?VALUE? | |
67 # omit_test TESTNAME REASON ?APPEND? | |
68 # fail_test TESTNAME | |
69 # incr_ntest | |
70 # | |
71 # Command run at the end of each test file: | |
72 # | |
73 # finish_test | |
74 # | |
75 # Commands to help create test files that run with the "WAL" and other | |
76 # permutations (see file permutations.test): | |
77 # | |
78 # wal_is_wal_mode | |
79 # wal_set_journal_mode ?DB? | |
80 # wal_check_journal_mode TESTNAME?DB? | |
81 # permutation | |
82 # presql | |
83 # | |
84 | |
85 # Set the precision of FP arithmatic used by the interpreter. And | |
86 # configure SQLite to take database file locks on the page that begins | |
87 # 64KB into the database file instead of the one 1GB in. This means | |
88 # the code that handles that special case can be tested without creating | |
89 # very large database files. | |
90 # | |
91 set tcl_precision 15 | |
92 sqlite3_test_control_pending_byte 0x0010000 | |
93 | |
94 | |
95 # If the pager codec is available, create a wrapper for the [sqlite3] | |
96 # command that appends "-key {xyzzy}" to the command line. i.e. this: | |
97 # | |
98 # sqlite3 db test.db | |
99 # | |
100 # becomes | |
101 # | |
102 # sqlite3 db test.db -key {xyzzy} | |
103 # | |
104 if {[info command sqlite_orig]==""} { | |
105 rename sqlite3 sqlite_orig | |
106 proc sqlite3 {args} { | |
107 if {[llength $args]>=2 && [string index [lindex $args 0] 0]!="-"} { | |
108 # This command is opening a new database connection. | |
109 # | |
110 if {[info exists ::G(perm:sqlite3_args)]} { | |
111 set args [concat $args $::G(perm:sqlite3_args)] | |
112 } | |
113 if {[sqlite_orig -has-codec] && ![info exists ::do_not_use_codec]} { | |
114 lappend args -key {xyzzy} | |
115 } | |
116 | |
117 set res [uplevel 1 sqlite_orig $args] | |
118 if {[info exists ::G(perm:presql)]} { | |
119 [lindex $args 0] eval $::G(perm:presql) | |
120 } | |
121 if {[info exists ::G(perm:dbconfig)]} { | |
122 set ::dbhandle [lindex $args 0] | |
123 uplevel #0 $::G(perm:dbconfig) | |
124 } | |
125 set res | |
126 } else { | |
127 # This command is not opening a new database connection. Pass the | |
128 # arguments through to the C implementation as the are. | |
129 # | |
130 uplevel 1 sqlite_orig $args | |
131 } | |
132 } | |
133 } | |
134 | |
135 proc getFileRetries {} { | |
136 if {![info exists ::G(file-retries)]} { | |
137 # | |
138 # NOTE: Return the default number of retries for [file] operations. A | |
139 # value of zero or less here means "disabled". | |
140 # | |
141 return [expr {$::tcl_platform(platform) eq "windows" ? 50 : 0}] | |
142 } | |
143 return $::G(file-retries) | |
144 } | |
145 | |
146 proc getFileRetryDelay {} { | |
147 if {![info exists ::G(file-retry-delay)]} { | |
148 # | |
149 # NOTE: Return the default number of milliseconds to wait when retrying | |
150 # failed [file] operations. A value of zero or less means "do not | |
151 # wait". | |
152 # | |
153 return 100; # TODO: Good default? | |
154 } | |
155 return $::G(file-retry-delay) | |
156 } | |
157 | |
158 # Return the string representing the name of the current directory. On | |
159 # Windows, the result is "normalized" to whatever our parent command shell | |
160 # is using to prevent case-mismatch issues. | |
161 # | |
162 proc get_pwd {} { | |
163 if {$::tcl_platform(platform) eq "windows"} { | |
164 # | |
165 # NOTE: Cannot use [file normalize] here because it would alter the | |
166 # case of the result to what Tcl considers canonical, which would | |
167 # defeat the purpose of this procedure. | |
168 # | |
169 return [string map [list \\ /] \ | |
170 [string trim [exec -- $::env(ComSpec) /c echo %CD%]]] | |
171 } else { | |
172 return [pwd] | |
173 } | |
174 } | |
175 | |
176 # Copy file $from into $to. This is used because some versions of | |
177 # TCL for windows (notably the 8.4.1 binary package shipped with the | |
178 # current mingw release) have a broken "file copy" command. | |
179 # | |
180 proc copy_file {from to} { | |
181 do_copy_file false $from $to | |
182 } | |
183 | |
184 proc forcecopy {from to} { | |
185 do_copy_file true $from $to | |
186 } | |
187 | |
188 proc do_copy_file {force from to} { | |
189 set nRetry [getFileRetries] ;# Maximum number of retries. | |
190 set nDelay [getFileRetryDelay] ;# Delay in ms before retrying. | |
191 | |
192 # On windows, sometimes even a [file copy -force] can fail. The cause is | |
193 # usually "tag-alongs" - programs like anti-virus software, automatic backup | |
194 # tools and various explorer extensions that keep a file open a little longer | |
195 # than we expect, causing the delete to fail. | |
196 # | |
197 # The solution is to wait a short amount of time before retrying the copy. | |
198 # | |
199 if {$nRetry > 0} { | |
200 for {set i 0} {$i<$nRetry} {incr i} { | |
201 set rc [catch { | |
202 if {$force} { | |
203 file copy -force $from $to | |
204 } else { | |
205 file copy $from $to | |
206 } | |
207 } msg] | |
208 if {$rc==0} break | |
209 if {$nDelay > 0} { after $nDelay } | |
210 } | |
211 if {$rc} { error $msg } | |
212 } else { | |
213 if {$force} { | |
214 file copy -force $from $to | |
215 } else { | |
216 file copy $from $to | |
217 } | |
218 } | |
219 } | |
220 | |
221 # Check if a file name is relative | |
222 # | |
223 proc is_relative_file { file } { | |
224 return [expr {[file pathtype $file] != "absolute"}] | |
225 } | |
226 | |
227 # If the VFS supports using the current directory, returns [pwd]; | |
228 # otherwise, it returns only the provided suffix string (which is | |
229 # empty by default). | |
230 # | |
231 proc test_pwd { args } { | |
232 if {[llength $args] > 0} { | |
233 set suffix1 [lindex $args 0] | |
234 if {[llength $args] > 1} { | |
235 set suffix2 [lindex $args 1] | |
236 } else { | |
237 set suffix2 $suffix1 | |
238 } | |
239 } else { | |
240 set suffix1 ""; set suffix2 "" | |
241 } | |
242 ifcapable curdir { | |
243 return "[get_pwd]$suffix1" | |
244 } else { | |
245 return $suffix2 | |
246 } | |
247 } | |
248 | |
249 # Delete a file or directory | |
250 # | |
251 proc delete_file {args} { | |
252 do_delete_file false {*}$args | |
253 } | |
254 | |
255 proc forcedelete {args} { | |
256 do_delete_file true {*}$args | |
257 } | |
258 | |
259 proc do_delete_file {force args} { | |
260 set nRetry [getFileRetries] ;# Maximum number of retries. | |
261 set nDelay [getFileRetryDelay] ;# Delay in ms before retrying. | |
262 | |
263 foreach filename $args { | |
264 # On windows, sometimes even a [file delete -force] can fail just after | |
265 # a file is closed. The cause is usually "tag-alongs" - programs like | |
266 # anti-virus software, automatic backup tools and various explorer | |
267 # extensions that keep a file open a little longer than we expect, causing | |
268 # the delete to fail. | |
269 # | |
270 # The solution is to wait a short amount of time before retrying the | |
271 # delete. | |
272 # | |
273 if {$nRetry > 0} { | |
274 for {set i 0} {$i<$nRetry} {incr i} { | |
275 set rc [catch { | |
276 if {$force} { | |
277 file delete -force $filename | |
278 } else { | |
279 file delete $filename | |
280 } | |
281 } msg] | |
282 if {$rc==0} break | |
283 if {$nDelay > 0} { after $nDelay } | |
284 } | |
285 if {$rc} { error $msg } | |
286 } else { | |
287 if {$force} { | |
288 file delete -force $filename | |
289 } else { | |
290 file delete $filename | |
291 } | |
292 } | |
293 } | |
294 } | |
295 | |
296 if {$::tcl_platform(platform) eq "windows"} { | |
297 proc do_remove_win32_dir {args} { | |
298 set nRetry [getFileRetries] ;# Maximum number of retries. | |
299 set nDelay [getFileRetryDelay] ;# Delay in ms before retrying. | |
300 | |
301 foreach dirName $args { | |
302 # On windows, sometimes even a [remove_win32_dir] can fail just after | |
303 # a directory is emptied. The cause is usually "tag-alongs" - programs | |
304 # like anti-virus software, automatic backup tools and various explorer | |
305 # extensions that keep a file open a little longer than we expect, | |
306 # causing the delete to fail. | |
307 # | |
308 # The solution is to wait a short amount of time before retrying the | |
309 # removal. | |
310 # | |
311 if {$nRetry > 0} { | |
312 for {set i 0} {$i < $nRetry} {incr i} { | |
313 set rc [catch { | |
314 remove_win32_dir $dirName | |
315 } msg] | |
316 if {$rc == 0} break | |
317 if {$nDelay > 0} { after $nDelay } | |
318 } | |
319 if {$rc} { error $msg } | |
320 } else { | |
321 remove_win32_dir $dirName | |
322 } | |
323 } | |
324 } | |
325 | |
326 proc do_delete_win32_file {args} { | |
327 set nRetry [getFileRetries] ;# Maximum number of retries. | |
328 set nDelay [getFileRetryDelay] ;# Delay in ms before retrying. | |
329 | |
330 foreach fileName $args { | |
331 # On windows, sometimes even a [delete_win32_file] can fail just after | |
332 # a file is closed. The cause is usually "tag-alongs" - programs like | |
333 # anti-virus software, automatic backup tools and various explorer | |
334 # extensions that keep a file open a little longer than we expect, | |
335 # causing the delete to fail. | |
336 # | |
337 # The solution is to wait a short amount of time before retrying the | |
338 # delete. | |
339 # | |
340 if {$nRetry > 0} { | |
341 for {set i 0} {$i < $nRetry} {incr i} { | |
342 set rc [catch { | |
343 delete_win32_file $fileName | |
344 } msg] | |
345 if {$rc == 0} break | |
346 if {$nDelay > 0} { after $nDelay } | |
347 } | |
348 if {$rc} { error $msg } | |
349 } else { | |
350 delete_win32_file $fileName | |
351 } | |
352 } | |
353 } | |
354 } | |
355 | |
356 proc execpresql {handle args} { | |
357 trace remove execution $handle enter [list execpresql $handle] | |
358 if {[info exists ::G(perm:presql)]} { | |
359 $handle eval $::G(perm:presql) | |
360 } | |
361 } | |
362 | |
363 # This command should be called after loading tester.tcl from within | |
364 # all test scripts that are incompatible with encryption codecs. | |
365 # | |
366 proc do_not_use_codec {} { | |
367 set ::do_not_use_codec 1 | |
368 reset_db | |
369 } | |
370 | |
371 # The following block only runs the first time this file is sourced. It | |
372 # does not run in slave interpreters (since the ::cmdlinearg array is | |
373 # populated before the test script is run in slave interpreters). | |
374 # | |
375 if {[info exists cmdlinearg]==0} { | |
376 | |
377 # Parse any options specified in the $argv array. This script accepts the | |
378 # following options: | |
379 # | |
380 # --pause | |
381 # --soft-heap-limit=NN | |
382 # --maxerror=NN | |
383 # --malloctrace=N | |
384 # --backtrace=N | |
385 # --binarylog=N | |
386 # --soak=N | |
387 # --file-retries=N | |
388 # --file-retry-delay=N | |
389 # --start=[$permutation:]$testfile | |
390 # --match=$pattern | |
391 # | |
392 set cmdlinearg(soft-heap-limit) 0 | |
393 set cmdlinearg(maxerror) 1000 | |
394 set cmdlinearg(malloctrace) 0 | |
395 set cmdlinearg(backtrace) 10 | |
396 set cmdlinearg(binarylog) 0 | |
397 set cmdlinearg(soak) 0 | |
398 set cmdlinearg(file-retries) 0 | |
399 set cmdlinearg(file-retry-delay) 0 | |
400 set cmdlinearg(start) "" | |
401 set cmdlinearg(match) "" | |
402 | |
403 set leftover [list] | |
404 foreach a $argv { | |
405 switch -regexp -- $a { | |
406 {^-+pause$} { | |
407 # Wait for user input before continuing. This is to give the user an | |
408 # opportunity to connect profiling tools to the process. | |
409 puts -nonewline "Press RETURN to begin..." | |
410 flush stdout | |
411 gets stdin | |
412 } | |
413 {^-+soft-heap-limit=.+$} { | |
414 foreach {dummy cmdlinearg(soft-heap-limit)} [split $a =] break | |
415 } | |
416 {^-+maxerror=.+$} { | |
417 foreach {dummy cmdlinearg(maxerror)} [split $a =] break | |
418 } | |
419 {^-+malloctrace=.+$} { | |
420 foreach {dummy cmdlinearg(malloctrace)} [split $a =] break | |
421 if {$cmdlinearg(malloctrace)} { | |
422 sqlite3_memdebug_log start | |
423 } | |
424 } | |
425 {^-+backtrace=.+$} { | |
426 foreach {dummy cmdlinearg(backtrace)} [split $a =] break | |
427 sqlite3_memdebug_backtrace $value | |
428 } | |
429 {^-+binarylog=.+$} { | |
430 foreach {dummy cmdlinearg(binarylog)} [split $a =] break | |
431 } | |
432 {^-+soak=.+$} { | |
433 foreach {dummy cmdlinearg(soak)} [split $a =] break | |
434 set ::G(issoak) $cmdlinearg(soak) | |
435 } | |
436 {^-+file-retries=.+$} { | |
437 foreach {dummy cmdlinearg(file-retries)} [split $a =] break | |
438 set ::G(file-retries) $cmdlinearg(file-retries) | |
439 } | |
440 {^-+file-retry-delay=.+$} { | |
441 foreach {dummy cmdlinearg(file-retry-delay)} [split $a =] break | |
442 set ::G(file-retry-delay) $cmdlinearg(file-retry-delay) | |
443 } | |
444 {^-+start=.+$} { | |
445 foreach {dummy cmdlinearg(start)} [split $a =] break | |
446 | |
447 set ::G(start:file) $cmdlinearg(start) | |
448 if {[regexp {(.*):(.*)} $cmdlinearg(start) -> s.perm s.file]} { | |
449 set ::G(start:permutation) ${s.perm} | |
450 set ::G(start:file) ${s.file} | |
451 } | |
452 if {$::G(start:file) == ""} {unset ::G(start:file)} | |
453 } | |
454 {^-+match=.+$} { | |
455 foreach {dummy cmdlinearg(match)} [split $a =] break | |
456 | |
457 set ::G(match) $cmdlinearg(match) | |
458 if {$::G(match) == ""} {unset ::G(match)} | |
459 } | |
460 default { | |
461 lappend leftover $a | |
462 } | |
463 } | |
464 } | |
465 set argv $leftover | |
466 | |
467 # Install the malloc layer used to inject OOM errors. And the 'automatic' | |
468 # extensions. This only needs to be done once for the process. | |
469 # | |
470 sqlite3_shutdown | |
471 install_malloc_faultsim 1 | |
472 sqlite3_initialize | |
473 autoinstall_test_functions | |
474 | |
475 # If the --binarylog option was specified, create the logging VFS. This | |
476 # call installs the new VFS as the default for all SQLite connections. | |
477 # | |
478 if {$cmdlinearg(binarylog)} { | |
479 vfslog new binarylog {} vfslog.bin | |
480 } | |
481 | |
482 # Set the backtrace depth, if malloc tracing is enabled. | |
483 # | |
484 if {$cmdlinearg(malloctrace)} { | |
485 sqlite3_memdebug_backtrace $cmdlinearg(backtrace) | |
486 } | |
487 } | |
488 | |
489 # Update the soft-heap-limit each time this script is run. In that | |
490 # way if an individual test file changes the soft-heap-limit, it | |
491 # will be reset at the start of the next test file. | |
492 # | |
493 sqlite3_soft_heap_limit $cmdlinearg(soft-heap-limit) | |
494 | |
495 # Create a test database | |
496 # | |
497 proc reset_db {} { | |
498 catch {db close} | |
499 forcedelete test.db | |
500 forcedelete test.db-journal | |
501 forcedelete test.db-wal | |
502 sqlite3 db ./test.db | |
503 set ::DB [sqlite3_connection_pointer db] | |
504 if {[info exists ::SETUP_SQL]} { | |
505 db eval $::SETUP_SQL | |
506 } | |
507 } | |
508 reset_db | |
509 | |
510 # Abort early if this script has been run before. | |
511 # | |
512 if {[info exists TC(count)]} return | |
513 | |
514 # Make sure memory statistics are enabled. | |
515 # | |
516 sqlite3_config_memstatus 1 | |
517 | |
518 # Initialize the test counters and set up commands to access them. | |
519 # Or, if this is a slave interpreter, set up aliases to write the | |
520 # counters in the parent interpreter. | |
521 # | |
522 if {0==[info exists ::SLAVE]} { | |
523 set TC(errors) 0 | |
524 set TC(count) 0 | |
525 set TC(fail_list) [list] | |
526 set TC(omit_list) [list] | |
527 set TC(warn_list) [list] | |
528 | |
529 proc set_test_counter {counter args} { | |
530 if {[llength $args]} { | |
531 set ::TC($counter) [lindex $args 0] | |
532 } | |
533 set ::TC($counter) | |
534 } | |
535 } | |
536 | |
537 # Record the fact that a sequence of tests were omitted. | |
538 # | |
539 proc omit_test {name reason {append 1}} { | |
540 set omitList [set_test_counter omit_list] | |
541 if {$append} { | |
542 lappend omitList [list $name $reason] | |
543 } | |
544 set_test_counter omit_list $omitList | |
545 } | |
546 | |
547 # Record the fact that a test failed. | |
548 # | |
549 proc fail_test {name} { | |
550 set f [set_test_counter fail_list] | |
551 lappend f $name | |
552 set_test_counter fail_list $f | |
553 set_test_counter errors [expr [set_test_counter errors] + 1] | |
554 | |
555 set nFail [set_test_counter errors] | |
556 if {$nFail>=$::cmdlinearg(maxerror)} { | |
557 puts "*** Giving up..." | |
558 finalize_testing | |
559 } | |
560 } | |
561 | |
562 # Remember a warning message to be displayed at the conclusion of all testing | |
563 # | |
564 proc warning {msg {append 1}} { | |
565 puts "Warning: $msg" | |
566 set warnList [set_test_counter warn_list] | |
567 if {$append} { | |
568 lappend warnList $msg | |
569 } | |
570 set_test_counter warn_list $warnList | |
571 } | |
572 | |
573 | |
574 # Increment the number of tests run | |
575 # | |
576 proc incr_ntest {} { | |
577 set_test_counter count [expr [set_test_counter count] + 1] | |
578 } | |
579 | |
580 | |
581 # Invoke the do_test procedure to run a single test | |
582 # | |
583 proc do_test {name cmd expected} { | |
584 global argv cmdlinearg | |
585 | |
586 fix_testname name | |
587 | |
588 sqlite3_memdebug_settitle $name | |
589 | |
590 # if {[llength $argv]==0} { | |
591 # set go 1 | |
592 # } else { | |
593 # set go 0 | |
594 # foreach pattern $argv { | |
595 # if {[string match $pattern $name]} { | |
596 # set go 1 | |
597 # break | |
598 # } | |
599 # } | |
600 # } | |
601 | |
602 if {[info exists ::G(perm:prefix)]} { | |
603 set name "$::G(perm:prefix)$name" | |
604 } | |
605 | |
606 incr_ntest | |
607 puts -nonewline $name... | |
608 flush stdout | |
609 | |
610 if {![info exists ::G(match)] || [string match $::G(match) $name]} { | |
611 if {[catch {uplevel #0 "$cmd;\n"} result]} { | |
612 puts "\nError: $result" | |
613 fail_test $name | |
614 } else { | |
615 if {[regexp {^~?/.*/$} $expected]} { | |
616 # "expected" is of the form "/PATTERN/" then the result if correct if | |
617 # regular expression PATTERN matches the result. "~/PATTERN/" means | |
618 # the regular expression must not match. | |
619 if {[string index $expected 0]=="~"} { | |
620 set re [string range $expected 2 end-1] | |
621 if {[string index $re 0]=="*"} { | |
622 # If the regular expression begins with * then treat it as a glob in
stead | |
623 set ok [string match $re $result] | |
624 } else { | |
625 set re [string map {# {[-0-9.]+}} $re] | |
626 set ok [regexp $re $result] | |
627 } | |
628 set ok [expr {!$ok}] | |
629 } else { | |
630 set re [string range $expected 1 end-1] | |
631 if {[string index $re 0]=="*"} { | |
632 # If the regular expression begins with * then treat it as a glob in
stead | |
633 set ok [string match $re $result] | |
634 } else { | |
635 set re [string map {# {[-0-9.]+}} $re] | |
636 set ok [regexp $re $result] | |
637 } | |
638 } | |
639 } elseif {[regexp {^~?\*.*\*$} $expected]} { | |
640 # "expected" is of the form "*GLOB*" then the result if correct if | |
641 # glob pattern GLOB matches the result. "~/GLOB/" means | |
642 # the glob must not match. | |
643 if {[string index $expected 0]=="~"} { | |
644 set e [string range $expected 1 end] | |
645 set ok [expr {![string match $e $result]}] | |
646 } else { | |
647 set ok [string match $expected $result] | |
648 } | |
649 } else { | |
650 set ok [expr {[string compare $result $expected]==0}] | |
651 } | |
652 if {!$ok} { | |
653 # if {![info exists ::testprefix] || $::testprefix eq ""} { | |
654 # error "no test prefix" | |
655 # } | |
656 puts "\nExpected: \[$expected\]\n Got: \[$result\]" | |
657 fail_test $name | |
658 } else { | |
659 puts " Ok" | |
660 } | |
661 } | |
662 } else { | |
663 puts " Omitted" | |
664 omit_test $name "pattern mismatch" 0 | |
665 } | |
666 flush stdout | |
667 } | |
668 | |
669 proc catchcmd {db {cmd ""}} { | |
670 global CLI | |
671 set out [open cmds.txt w] | |
672 puts $out $cmd | |
673 close $out | |
674 set line "exec $CLI $db < cmds.txt" | |
675 set rc [catch { eval $line } msg] | |
676 list $rc $msg | |
677 } | |
678 | |
679 proc filepath_normalize {p} { | |
680 # test cases should be written to assume "unix"-like file paths | |
681 if {$::tcl_platform(platform)!="unix"} { | |
682 # lreverse*2 as a hack to remove any unneeded {} after the string map | |
683 lreverse [lreverse [string map {\\ /} [regsub -nocase -all {[a-z]:[/\\]+} $p
{/}]]] | |
684 } { | |
685 set p | |
686 } | |
687 } | |
688 proc do_filepath_test {name cmd expected} { | |
689 uplevel [list do_test $name [ | |
690 subst -nocommands { filepath_normalize [ $cmd ] } | |
691 ] [filepath_normalize $expected]] | |
692 } | |
693 | |
694 proc realnum_normalize {r} { | |
695 # different TCL versions display floating point values differently. | |
696 string map {1.#INF inf Inf inf .0e e} [regsub -all {(e[+-])0+} $r {\1}] | |
697 } | |
698 proc do_realnum_test {name cmd expected} { | |
699 uplevel [list do_test $name [ | |
700 subst -nocommands { realnum_normalize [ $cmd ] } | |
701 ] [realnum_normalize $expected]] | |
702 } | |
703 | |
704 proc fix_testname {varname} { | |
705 upvar $varname testname | |
706 if {[info exists ::testprefix] | |
707 && [string is digit [string range $testname 0 0]] | |
708 } { | |
709 set testname "${::testprefix}-$testname" | |
710 } | |
711 } | |
712 | |
713 proc do_execsql_test {testname sql {result {}}} { | |
714 fix_testname testname | |
715 uplevel do_test [list $testname] [list "execsql {$sql}"] [list [list {*}$resul
t]] | |
716 } | |
717 proc do_catchsql_test {testname sql result} { | |
718 fix_testname testname | |
719 uplevel do_test [list $testname] [list "catchsql {$sql}"] [list $result] | |
720 } | |
721 proc do_timed_execsql_test {testname sql {result {}}} { | |
722 fix_testname testname | |
723 uplevel do_test [list $testname] [list "execsql_timed {$sql}"]\ | |
724 [list [list {*}$result]] | |
725 } | |
726 proc do_eqp_test {name sql res} { | |
727 uplevel do_execsql_test $name [list "EXPLAIN QUERY PLAN $sql"] [list $res] | |
728 } | |
729 | |
730 #------------------------------------------------------------------------- | |
731 # Usage: do_select_tests PREFIX ?SWITCHES? TESTLIST | |
732 # | |
733 # Where switches are: | |
734 # | |
735 # -errorformat FMTSTRING | |
736 # -count | |
737 # -query SQL | |
738 # -tclquery TCL | |
739 # -repair TCL | |
740 # | |
741 proc do_select_tests {prefix args} { | |
742 | |
743 set testlist [lindex $args end] | |
744 set switches [lrange $args 0 end-1] | |
745 | |
746 set errfmt "" | |
747 set countonly 0 | |
748 set tclquery "" | |
749 set repair "" | |
750 | |
751 for {set i 0} {$i < [llength $switches]} {incr i} { | |
752 set s [lindex $switches $i] | |
753 set n [string length $s] | |
754 if {$n>=2 && [string equal -length $n $s "-query"]} { | |
755 set tclquery [list execsql [lindex $switches [incr i]]] | |
756 } elseif {$n>=2 && [string equal -length $n $s "-tclquery"]} { | |
757 set tclquery [lindex $switches [incr i]] | |
758 } elseif {$n>=2 && [string equal -length $n $s "-errorformat"]} { | |
759 set errfmt [lindex $switches [incr i]] | |
760 } elseif {$n>=2 && [string equal -length $n $s "-repair"]} { | |
761 set repair [lindex $switches [incr i]] | |
762 } elseif {$n>=2 && [string equal -length $n $s "-count"]} { | |
763 set countonly 1 | |
764 } else { | |
765 error "unknown switch: $s" | |
766 } | |
767 } | |
768 | |
769 if {$countonly && $errfmt!=""} { | |
770 error "Cannot use -count and -errorformat together" | |
771 } | |
772 set nTestlist [llength $testlist] | |
773 if {$nTestlist%3 || $nTestlist==0 } { | |
774 error "SELECT test list contains [llength $testlist] elements" | |
775 } | |
776 | |
777 eval $repair | |
778 foreach {tn sql res} $testlist { | |
779 if {$tclquery != ""} { | |
780 execsql $sql | |
781 uplevel do_test ${prefix}.$tn [list $tclquery] [list [list {*}$res]] | |
782 } elseif {$countonly} { | |
783 set nRow 0 | |
784 db eval $sql {incr nRow} | |
785 uplevel do_test ${prefix}.$tn [list [list set {} $nRow]] [list $res] | |
786 } elseif {$errfmt==""} { | |
787 uplevel do_execsql_test ${prefix}.${tn} [list $sql] [list [list {*}$res]] | |
788 } else { | |
789 set res [list 1 [string trim [format $errfmt {*}$res]]] | |
790 uplevel do_catchsql_test ${prefix}.${tn} [list $sql] [list $res] | |
791 } | |
792 eval $repair | |
793 } | |
794 | |
795 } | |
796 | |
797 proc delete_all_data {} { | |
798 db eval {SELECT tbl_name AS t FROM sqlite_master WHERE type = 'table'} { | |
799 db eval "DELETE FROM '[string map {' ''} $t]'" | |
800 } | |
801 } | |
802 | |
803 # Run an SQL script. | |
804 # Return the number of microseconds per statement. | |
805 # | |
806 proc speed_trial {name numstmt units sql} { | |
807 puts -nonewline [format {%-21.21s } $name...] | |
808 flush stdout | |
809 set speed [time {sqlite3_exec_nr db $sql}] | |
810 set tm [lindex $speed 0] | |
811 if {$tm == 0} { | |
812 set rate [format %20s "many"] | |
813 } else { | |
814 set rate [format %20.5f [expr {1000000.0*$numstmt/$tm}]] | |
815 } | |
816 set u2 $units/s | |
817 puts [format {%12d uS %s %s} $tm $rate $u2] | |
818 global total_time | |
819 set total_time [expr {$total_time+$tm}] | |
820 lappend ::speed_trial_times $name $tm | |
821 } | |
822 proc speed_trial_tcl {name numstmt units script} { | |
823 puts -nonewline [format {%-21.21s } $name...] | |
824 flush stdout | |
825 set speed [time {eval $script}] | |
826 set tm [lindex $speed 0] | |
827 if {$tm == 0} { | |
828 set rate [format %20s "many"] | |
829 } else { | |
830 set rate [format %20.5f [expr {1000000.0*$numstmt/$tm}]] | |
831 } | |
832 set u2 $units/s | |
833 puts [format {%12d uS %s %s} $tm $rate $u2] | |
834 global total_time | |
835 set total_time [expr {$total_time+$tm}] | |
836 lappend ::speed_trial_times $name $tm | |
837 } | |
838 proc speed_trial_init {name} { | |
839 global total_time | |
840 set total_time 0 | |
841 set ::speed_trial_times [list] | |
842 sqlite3 versdb :memory: | |
843 set vers [versdb one {SELECT sqlite_source_id()}] | |
844 versdb close | |
845 puts "SQLite $vers" | |
846 } | |
847 proc speed_trial_summary {name} { | |
848 global total_time | |
849 puts [format {%-21.21s %12d uS TOTAL} $name $total_time] | |
850 | |
851 if { 0 } { | |
852 sqlite3 versdb :memory: | |
853 set vers [lindex [versdb one {SELECT sqlite_source_id()}] 0] | |
854 versdb close | |
855 puts "CREATE TABLE IF NOT EXISTS time(version, script, test, us);" | |
856 foreach {test us} $::speed_trial_times { | |
857 puts "INSERT INTO time VALUES('$vers', '$name', '$test', $us);" | |
858 } | |
859 } | |
860 } | |
861 | |
862 # Run this routine last | |
863 # | |
864 proc finish_test {} { | |
865 catch {db close} | |
866 catch {db1 close} | |
867 catch {db2 close} | |
868 catch {db3 close} | |
869 if {0==[info exists ::SLAVE]} { finalize_testing } | |
870 } | |
871 proc finalize_testing {} { | |
872 global sqlite_open_file_count | |
873 | |
874 set omitList [set_test_counter omit_list] | |
875 | |
876 catch {db close} | |
877 catch {db2 close} | |
878 catch {db3 close} | |
879 | |
880 vfs_unlink_test | |
881 sqlite3 db {} | |
882 # sqlite3_clear_tsd_memdebug | |
883 db close | |
884 sqlite3_reset_auto_extension | |
885 | |
886 sqlite3_soft_heap_limit 0 | |
887 set nTest [incr_ntest] | |
888 set nErr [set_test_counter errors] | |
889 | |
890 set nKnown 0 | |
891 if {[file readable known-problems.txt]} { | |
892 set fd [open known-problems.txt] | |
893 set content [read $fd] | |
894 close $fd | |
895 foreach x $content {set known_error($x) 1} | |
896 foreach x [set_test_counter fail_list] { | |
897 if {[info exists known_error($x)]} {incr nKnown} | |
898 } | |
899 } | |
900 if {$nKnown>0} { | |
901 puts "[expr {$nErr-$nKnown}] new errors and $nKnown known errors\ | |
902 out of $nTest tests" | |
903 } else { | |
904 puts "$nErr errors out of $nTest tests" | |
905 } | |
906 if {$nErr>$nKnown} { | |
907 puts -nonewline "Failures on these tests:" | |
908 foreach x [set_test_counter fail_list] { | |
909 if {![info exists known_error($x)]} {puts -nonewline " $x"} | |
910 } | |
911 puts "" | |
912 } | |
913 foreach warning [set_test_counter warn_list] { | |
914 puts "Warning: $warning" | |
915 } | |
916 run_thread_tests 1 | |
917 if {[llength $omitList]>0} { | |
918 puts "Omitted test cases:" | |
919 set prec {} | |
920 foreach {rec} [lsort $omitList] { | |
921 if {$rec==$prec} continue | |
922 set prec $rec | |
923 puts [format { %-12s %s} [lindex $rec 0] [lindex $rec 1]] | |
924 } | |
925 } | |
926 if {$nErr>0 && ![working_64bit_int]} { | |
927 puts "******************************************************************" | |
928 puts "N.B.: The version of TCL that you used to build this test harness" | |
929 puts "is defective in that it does not support 64-bit integers. Some or" | |
930 puts "all of the test failures above might be a result from this defect" | |
931 puts "in your TCL build." | |
932 puts "******************************************************************" | |
933 } | |
934 if {$::cmdlinearg(binarylog)} { | |
935 vfslog finalize binarylog | |
936 } | |
937 if {$sqlite_open_file_count} { | |
938 puts "$sqlite_open_file_count files were left open" | |
939 incr nErr | |
940 } | |
941 if {[lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1]>0 || | |
942 [sqlite3_memory_used]>0} { | |
943 puts "Unfreed memory: [sqlite3_memory_used] bytes in\ | |
944 [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1] allocations" | |
945 incr nErr | |
946 ifcapable memdebug||mem5||(mem3&&debug) { | |
947 puts "Writing unfreed memory log to \"./memleak.txt\"" | |
948 sqlite3_memdebug_dump ./memleak.txt | |
949 } | |
950 } else { | |
951 puts "All memory allocations freed - no leaks" | |
952 ifcapable memdebug||mem5 { | |
953 sqlite3_memdebug_dump ./memusage.txt | |
954 } | |
955 } | |
956 show_memstats | |
957 puts "Maximum memory usage: [sqlite3_memory_highwater 1] bytes" | |
958 puts "Current memory usage: [sqlite3_memory_highwater] bytes" | |
959 if {[info commands sqlite3_memdebug_malloc_count] ne ""} { | |
960 puts "Number of malloc() : [sqlite3_memdebug_malloc_count] calls" | |
961 } | |
962 if {$::cmdlinearg(malloctrace)} { | |
963 puts "Writing mallocs.sql..." | |
964 memdebug_log_sql | |
965 sqlite3_memdebug_log stop | |
966 sqlite3_memdebug_log clear | |
967 | |
968 if {[sqlite3_memory_used]>0} { | |
969 puts "Writing leaks.sql..." | |
970 sqlite3_memdebug_log sync | |
971 memdebug_log_sql leaks.sql | |
972 } | |
973 } | |
974 foreach f [glob -nocomplain test.db-*-journal] { | |
975 forcedelete $f | |
976 } | |
977 foreach f [glob -nocomplain test.db-mj*] { | |
978 forcedelete $f | |
979 } | |
980 exit [expr {$nErr>0}] | |
981 } | |
982 | |
983 # Display memory statistics for analysis and debugging purposes. | |
984 # | |
985 proc show_memstats {} { | |
986 set x [sqlite3_status SQLITE_STATUS_MEMORY_USED 0] | |
987 set y [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] | |
988 set val [format {now %10d max %10d max-size %10d} \ | |
989 [lindex $x 1] [lindex $x 2] [lindex $y 2]] | |
990 puts "Memory used: $val" | |
991 set x [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] | |
992 set val [format {now %10d max %10d} [lindex $x 1] [lindex $x 2]] | |
993 puts "Allocation count: $val" | |
994 set x [sqlite3_status SQLITE_STATUS_PAGECACHE_USED 0] | |
995 set y [sqlite3_status SQLITE_STATUS_PAGECACHE_SIZE 0] | |
996 set val [format {now %10d max %10d max-size %10d} \ | |
997 [lindex $x 1] [lindex $x 2] [lindex $y 2]] | |
998 puts "Page-cache used: $val" | |
999 set x [sqlite3_status SQLITE_STATUS_PAGECACHE_OVERFLOW 0] | |
1000 set val [format {now %10d max %10d} [lindex $x 1] [lindex $x 2]] | |
1001 puts "Page-cache overflow: $val" | |
1002 set x [sqlite3_status SQLITE_STATUS_SCRATCH_USED 0] | |
1003 set val [format {now %10d max %10d} [lindex $x 1] [lindex $x 2]] | |
1004 puts "Scratch memory used: $val" | |
1005 set x [sqlite3_status SQLITE_STATUS_SCRATCH_OVERFLOW 0] | |
1006 set y [sqlite3_status SQLITE_STATUS_SCRATCH_SIZE 0] | |
1007 set val [format {now %10d max %10d max-size %10d} \ | |
1008 [lindex $x 1] [lindex $x 2] [lindex $y 2]] | |
1009 puts "Scratch overflow: $val" | |
1010 ifcapable yytrackmaxstackdepth { | |
1011 set x [sqlite3_status SQLITE_STATUS_PARSER_STACK 0] | |
1012 set val [format { max %10d} [lindex $x 2]] | |
1013 puts "Parser stack depth: $val" | |
1014 } | |
1015 } | |
1016 | |
1017 # A procedure to execute SQL | |
1018 # | |
1019 proc execsql {sql {db db}} { | |
1020 # puts "SQL = $sql" | |
1021 uplevel [list $db eval $sql] | |
1022 } | |
1023 proc execsql_timed {sql {db db}} { | |
1024 set tm [time { | |
1025 set x [uplevel [list $db eval $sql]] | |
1026 } 1] | |
1027 set tm [lindex $tm 0] | |
1028 puts -nonewline " ([expr {$tm*0.001}]ms) " | |
1029 set x | |
1030 } | |
1031 | |
1032 # Execute SQL and catch exceptions. | |
1033 # | |
1034 proc catchsql {sql {db db}} { | |
1035 # puts "SQL = $sql" | |
1036 set r [catch [list uplevel [list $db eval $sql]] msg] | |
1037 lappend r $msg | |
1038 return $r | |
1039 } | |
1040 | |
1041 # Do an VDBE code dump on the SQL given | |
1042 # | |
1043 proc explain {sql {db db}} { | |
1044 puts "" | |
1045 puts "addr opcode p1 p2 p3 p4 p5 #" | |
1046 puts "---- ------------ ------ ------ ------ --------------- -- -" | |
1047 $db eval "explain $sql" {} { | |
1048 puts [format {%-4d %-12.12s %-6d %-6d %-6d % -17s %s %s} \ | |
1049 $addr $opcode $p1 $p2 $p3 $p4 $p5 $comment | |
1050 ] | |
1051 } | |
1052 } | |
1053 | |
1054 proc explain_i {sql {db db}} { | |
1055 puts "" | |
1056 puts "addr opcode p1 p2 p3 p4 p5 #" | |
1057 puts "---- ------------ ------ ------ ------ ---------------- -- -" | |
1058 | |
1059 | |
1060 # Set up colors for the different opcodes. Scheme is as follows: | |
1061 # | |
1062 # Red: Opcodes that write to a b-tree. | |
1063 # Blue: Opcodes that reposition or seek a cursor. | |
1064 # Green: The ResultRow opcode. | |
1065 # | |
1066 if { [catch {fconfigure stdout -mode}]==0 } { | |
1067 set R "\033\[31;1m" ;# Red fg | |
1068 set G "\033\[32;1m" ;# Green fg | |
1069 set B "\033\[34;1m" ;# Red fg | |
1070 set D "\033\[39;0m" ;# Default fg | |
1071 } else { | |
1072 set R "" | |
1073 set G "" | |
1074 set B "" | |
1075 set D "" | |
1076 } | |
1077 foreach opcode { | |
1078 Seek SeekGe SeekGt SeekLe SeekLt NotFound Last Rewind | |
1079 NoConflict Next Prev VNext VPrev VFilter | |
1080 SorterSort SorterNext | |
1081 } { | |
1082 set color($opcode) $B | |
1083 } | |
1084 foreach opcode {ResultRow} { | |
1085 set color($opcode) $G | |
1086 } | |
1087 foreach opcode {IdxInsert Insert Delete IdxDelete} { | |
1088 set color($opcode) $R | |
1089 } | |
1090 | |
1091 set bSeenGoto 0 | |
1092 $db eval "explain $sql" {} { | |
1093 set x($addr) 0 | |
1094 set op($addr) $opcode | |
1095 | |
1096 if {$opcode == "Goto" && ($bSeenGoto==0 || ($p2 > $addr+10))} { | |
1097 set linebreak($p2) 1 | |
1098 set bSeenGoto 1 | |
1099 } | |
1100 | |
1101 if {$opcode=="Next" || $opcode=="Prev" | |
1102 || $opcode=="VNext" || $opcode=="VPrev" | |
1103 || $opcode=="SorterNext" | |
1104 } { | |
1105 for {set i $p2} {$i<$addr} {incr i} { | |
1106 incr x($i) 2 | |
1107 } | |
1108 } | |
1109 | |
1110 if {$opcode == "Goto" && $p2<$addr && $op($p2)=="Yield"} { | |
1111 for {set i [expr $p2+1]} {$i<$addr} {incr i} { | |
1112 incr x($i) 2 | |
1113 } | |
1114 } | |
1115 | |
1116 if {$opcode == "Halt" && $comment == "End of coroutine"} { | |
1117 set linebreak([expr $addr+1]) 1 | |
1118 } | |
1119 } | |
1120 | |
1121 $db eval "explain $sql" {} { | |
1122 if {[info exists linebreak($addr)]} { | |
1123 puts "" | |
1124 } | |
1125 set I [string repeat " " $x($addr)] | |
1126 | |
1127 set col "" | |
1128 catch { set col $color($opcode) } | |
1129 | |
1130 puts [format {%-4d %s%s%-12.12s%s %-6d %-6d %-6d % -17s %s %s} \ | |
1131 $addr $I $col $opcode $D $p1 $p2 $p3 $p4 $p5 $comment | |
1132 ] | |
1133 } | |
1134 puts "---- ------------ ------ ------ ------ ---------------- -- -" | |
1135 } | |
1136 | |
1137 # Show the VDBE program for an SQL statement but omit the Trace | |
1138 # opcode at the beginning. This procedure can be used to prove | |
1139 # that different SQL statements generate exactly the same VDBE code. | |
1140 # | |
1141 proc explain_no_trace {sql} { | |
1142 set tr [db eval "EXPLAIN $sql"] | |
1143 return [lrange $tr 7 end] | |
1144 } | |
1145 | |
1146 # Another procedure to execute SQL. This one includes the field | |
1147 # names in the returned list. | |
1148 # | |
1149 proc execsql2 {sql} { | |
1150 set result {} | |
1151 db eval $sql data { | |
1152 foreach f $data(*) { | |
1153 lappend result $f $data($f) | |
1154 } | |
1155 } | |
1156 return $result | |
1157 } | |
1158 | |
1159 # Use a temporary in-memory database to execute SQL statements | |
1160 # | |
1161 proc memdbsql {sql} { | |
1162 sqlite3 memdb :memory: | |
1163 set result [memdb eval $sql] | |
1164 memdb close | |
1165 return $result | |
1166 } | |
1167 | |
1168 # Use the non-callback API to execute multiple SQL statements | |
1169 # | |
1170 proc stepsql {dbptr sql} { | |
1171 set sql [string trim $sql] | |
1172 set r 0 | |
1173 while {[string length $sql]>0} { | |
1174 if {[catch {sqlite3_prepare $dbptr $sql -1 sqltail} vm]} { | |
1175 return [list 1 $vm] | |
1176 } | |
1177 set sql [string trim $sqltail] | |
1178 # while {[sqlite_step $vm N VAL COL]=="SQLITE_ROW"} { | |
1179 # foreach v $VAL {lappend r $v} | |
1180 # } | |
1181 while {[sqlite3_step $vm]=="SQLITE_ROW"} { | |
1182 for {set i 0} {$i<[sqlite3_data_count $vm]} {incr i} { | |
1183 lappend r [sqlite3_column_text $vm $i] | |
1184 } | |
1185 } | |
1186 if {[catch {sqlite3_finalize $vm} errmsg]} { | |
1187 return [list 1 $errmsg] | |
1188 } | |
1189 } | |
1190 return $r | |
1191 } | |
1192 | |
1193 # Do an integrity check of the entire database | |
1194 # | |
1195 proc integrity_check {name {db db}} { | |
1196 ifcapable integrityck { | |
1197 do_test $name [list execsql {PRAGMA integrity_check} $db] {ok} | |
1198 } | |
1199 } | |
1200 | |
1201 # Check the extended error code | |
1202 # | |
1203 proc verify_ex_errcode {name expected {db db}} { | |
1204 do_test $name [list sqlite3_extended_errcode $db] $expected | |
1205 } | |
1206 | |
1207 | |
1208 # Return true if the SQL statement passed as the second argument uses a | |
1209 # statement transaction. | |
1210 # | |
1211 proc sql_uses_stmt {db sql} { | |
1212 set stmt [sqlite3_prepare $db $sql -1 dummy] | |
1213 set uses [uses_stmt_journal $stmt] | |
1214 sqlite3_finalize $stmt | |
1215 return $uses | |
1216 } | |
1217 | |
1218 proc fix_ifcapable_expr {expr} { | |
1219 set ret "" | |
1220 set state 0 | |
1221 for {set i 0} {$i < [string length $expr]} {incr i} { | |
1222 set char [string range $expr $i $i] | |
1223 set newstate [expr {[string is alnum $char] || $char eq "_"}] | |
1224 if {$newstate && !$state} { | |
1225 append ret {$::sqlite_options(} | |
1226 } | |
1227 if {!$newstate && $state} { | |
1228 append ret ) | |
1229 } | |
1230 append ret $char | |
1231 set state $newstate | |
1232 } | |
1233 if {$state} {append ret )} | |
1234 return $ret | |
1235 } | |
1236 | |
1237 # Returns non-zero if the capabilities are present; zero otherwise. | |
1238 # | |
1239 proc capable {expr} { | |
1240 set e [fix_ifcapable_expr $expr]; return [expr ($e)] | |
1241 } | |
1242 | |
1243 # Evaluate a boolean expression of capabilities. If true, execute the | |
1244 # code. Omit the code if false. | |
1245 # | |
1246 proc ifcapable {expr code {else ""} {elsecode ""}} { | |
1247 #regsub -all {[a-z_0-9]+} $expr {$::sqlite_options(&)} e2 | |
1248 set e2 [fix_ifcapable_expr $expr] | |
1249 if ($e2) { | |
1250 set c [catch {uplevel 1 $code} r] | |
1251 } else { | |
1252 set c [catch {uplevel 1 $elsecode} r] | |
1253 } | |
1254 return -code $c $r | |
1255 } | |
1256 | |
1257 # This proc execs a seperate process that crashes midway through executing | |
1258 # the SQL script $sql on database test.db. | |
1259 # | |
1260 # The crash occurs during a sync() of file $crashfile. When the crash | |
1261 # occurs a random subset of all unsynced writes made by the process are | |
1262 # written into the files on disk. Argument $crashdelay indicates the | |
1263 # number of file syncs to wait before crashing. | |
1264 # | |
1265 # The return value is a list of two elements. The first element is a | |
1266 # boolean, indicating whether or not the process actually crashed or | |
1267 # reported some other error. The second element in the returned list is the | |
1268 # error message. This is "child process exited abnormally" if the crash | |
1269 # occurred. | |
1270 # | |
1271 # crashsql -delay CRASHDELAY -file CRASHFILE ?-blocksize BLOCKSIZE? $sql | |
1272 # | |
1273 proc crashsql {args} { | |
1274 | |
1275 set blocksize "" | |
1276 set crashdelay 1 | |
1277 set prngseed 0 | |
1278 set opendb { sqlite3 db test.db -vfs crash } | |
1279 set tclbody {} | |
1280 set crashfile "" | |
1281 set dc "" | |
1282 set sql [lindex $args end] | |
1283 | |
1284 for {set ii 0} {$ii < [llength $args]-1} {incr ii 2} { | |
1285 set z [lindex $args $ii] | |
1286 set n [string length $z] | |
1287 set z2 [lindex $args [expr $ii+1]] | |
1288 | |
1289 if {$n>1 && [string first $z -delay]==0} {set crashdelay $z2} \ | |
1290 elseif {$n>1 && [string first $z -opendb]==0} {set opendb $z2} \ | |
1291 elseif {$n>1 && [string first $z -seed]==0} {set prngseed $z2} \ | |
1292 elseif {$n>1 && [string first $z -file]==0} {set crashfile $z2} \ | |
1293 elseif {$n>1 && [string first $z -tclbody]==0} {set tclbody $z2} \ | |
1294 elseif {$n>1 && [string first $z -blocksize]==0} {set blocksize "-s $z2" } \ | |
1295 elseif {$n>1 && [string first $z -characteristics]==0} {set dc "-c {$z2}" }
\ | |
1296 else { error "Unrecognized option: $z" } | |
1297 } | |
1298 | |
1299 if {$crashfile eq ""} { | |
1300 error "Compulsory option -file missing" | |
1301 } | |
1302 | |
1303 # $crashfile gets compared to the native filename in | |
1304 # cfSync(), which can be different then what TCL uses by | |
1305 # default, so here we force it to the "nativename" format. | |
1306 set cfile [string map {\\ \\\\} [file nativename [file join [get_pwd] $crashfi
le]]] | |
1307 | |
1308 set f [open crash.tcl w] | |
1309 puts $f "sqlite3_crash_enable 1" | |
1310 puts $f "sqlite3_crashparams $blocksize $dc $crashdelay $cfile" | |
1311 puts $f "sqlite3_test_control_pending_byte $::sqlite_pending_byte" | |
1312 puts $f $opendb | |
1313 | |
1314 # This block sets the cache size of the main database to 10 | |
1315 # pages. This is done in case the build is configured to omit | |
1316 # "PRAGMA cache_size". | |
1317 puts $f {db eval {SELECT * FROM sqlite_master;}} | |
1318 puts $f {set bt [btree_from_db db]} | |
1319 puts $f {btree_set_cache_size $bt 10} | |
1320 | |
1321 if {$prngseed} { | |
1322 set seed [expr {$prngseed%10007+1}] | |
1323 # puts seed=$seed | |
1324 puts $f "db eval {SELECT randomblob($seed)}" | |
1325 } | |
1326 | |
1327 if {[string length $tclbody]>0} { | |
1328 puts $f $tclbody | |
1329 } | |
1330 if {[string length $sql]>0} { | |
1331 puts $f "db eval {" | |
1332 puts $f "$sql" | |
1333 puts $f "}" | |
1334 } | |
1335 close $f | |
1336 set r [catch { | |
1337 exec [info nameofexec] crash.tcl >@stdout | |
1338 } msg] | |
1339 | |
1340 # Windows/ActiveState TCL returns a slightly different | |
1341 # error message. We map that to the expected message | |
1342 # so that we don't have to change all of the test | |
1343 # cases. | |
1344 if {$::tcl_platform(platform)=="windows"} { | |
1345 if {$msg=="child killed: unknown signal"} { | |
1346 set msg "child process exited abnormally" | |
1347 } | |
1348 } | |
1349 | |
1350 lappend r $msg | |
1351 } | |
1352 | |
1353 proc run_ioerr_prep {} { | |
1354 set ::sqlite_io_error_pending 0 | |
1355 catch {db close} | |
1356 catch {db2 close} | |
1357 catch {forcedelete test.db} | |
1358 catch {forcedelete test.db-journal} | |
1359 catch {forcedelete test2.db} | |
1360 catch {forcedelete test2.db-journal} | |
1361 set ::DB [sqlite3 db test.db; sqlite3_connection_pointer db] | |
1362 sqlite3_extended_result_codes $::DB $::ioerropts(-erc) | |
1363 if {[info exists ::ioerropts(-tclprep)]} { | |
1364 eval $::ioerropts(-tclprep) | |
1365 } | |
1366 if {[info exists ::ioerropts(-sqlprep)]} { | |
1367 execsql $::ioerropts(-sqlprep) | |
1368 } | |
1369 expr 0 | |
1370 } | |
1371 | |
1372 # Usage: do_ioerr_test <test number> <options...> | |
1373 # | |
1374 # This proc is used to implement test cases that check that IO errors | |
1375 # are correctly handled. The first argument, <test number>, is an integer | |
1376 # used to name the tests executed by this proc. Options are as follows: | |
1377 # | |
1378 # -tclprep TCL script to run to prepare test. | |
1379 # -sqlprep SQL script to run to prepare test. | |
1380 # -tclbody TCL script to run with IO error simulation. | |
1381 # -sqlbody TCL script to run with IO error simulation. | |
1382 # -exclude List of 'N' values not to test. | |
1383 # -erc Use extended result codes | |
1384 # -persist Make simulated I/O errors persistent | |
1385 # -start Value of 'N' to begin with (default 1) | |
1386 # | |
1387 # -cksum Boolean. If true, test that the database does | |
1388 # not change during the execution of the test case. | |
1389 # | |
1390 proc do_ioerr_test {testname args} { | |
1391 | |
1392 set ::ioerropts(-start) 1 | |
1393 set ::ioerropts(-cksum) 0 | |
1394 set ::ioerropts(-erc) 0 | |
1395 set ::ioerropts(-count) 100000000 | |
1396 set ::ioerropts(-persist) 1 | |
1397 set ::ioerropts(-ckrefcount) 0 | |
1398 set ::ioerropts(-restoreprng) 1 | |
1399 array set ::ioerropts $args | |
1400 | |
1401 # TEMPORARY: For 3.5.9, disable testing of extended result codes. There are | |
1402 # a couple of obscure IO errors that do not return them. | |
1403 set ::ioerropts(-erc) 0 | |
1404 | |
1405 # Create a single TCL script from the TCL and SQL specified | |
1406 # as the body of the test. | |
1407 set ::ioerrorbody {} | |
1408 if {[info exists ::ioerropts(-tclbody)]} { | |
1409 append ::ioerrorbody "$::ioerropts(-tclbody)\n" | |
1410 } | |
1411 if {[info exists ::ioerropts(-sqlbody)]} { | |
1412 append ::ioerrorbody "db eval {$::ioerropts(-sqlbody)}" | |
1413 } | |
1414 | |
1415 save_prng_state | |
1416 if {$::ioerropts(-cksum)} { | |
1417 run_ioerr_prep | |
1418 eval $::ioerrorbody | |
1419 set ::goodcksum [cksum] | |
1420 } | |
1421 | |
1422 set ::go 1 | |
1423 #reset_prng_state | |
1424 for {set n $::ioerropts(-start)} {$::go} {incr n} { | |
1425 set ::TN $n | |
1426 incr ::ioerropts(-count) -1 | |
1427 if {$::ioerropts(-count)<0} break | |
1428 | |
1429 # Skip this IO error if it was specified with the "-exclude" option. | |
1430 if {[info exists ::ioerropts(-exclude)]} { | |
1431 if {[lsearch $::ioerropts(-exclude) $n]!=-1} continue | |
1432 } | |
1433 if {$::ioerropts(-restoreprng)} { | |
1434 restore_prng_state | |
1435 } | |
1436 | |
1437 # Delete the files test.db and test2.db, then execute the TCL and | |
1438 # SQL (in that order) to prepare for the test case. | |
1439 do_test $testname.$n.1 { | |
1440 run_ioerr_prep | |
1441 } {0} | |
1442 | |
1443 # Read the 'checksum' of the database. | |
1444 if {$::ioerropts(-cksum)} { | |
1445 set ::checksum [cksum] | |
1446 } | |
1447 | |
1448 # Set the Nth IO error to fail. | |
1449 do_test $testname.$n.2 [subst { | |
1450 set ::sqlite_io_error_persist $::ioerropts(-persist) | |
1451 set ::sqlite_io_error_pending $n | |
1452 }] $n | |
1453 | |
1454 # Execute the TCL script created for the body of this test. If | |
1455 # at least N IO operations performed by SQLite as a result of | |
1456 # the script, the Nth will fail. | |
1457 do_test $testname.$n.3 { | |
1458 set ::sqlite_io_error_hit 0 | |
1459 set ::sqlite_io_error_hardhit 0 | |
1460 set r [catch $::ioerrorbody msg] | |
1461 set ::errseen $r | |
1462 set rc [sqlite3_errcode $::DB] | |
1463 if {$::ioerropts(-erc)} { | |
1464 # If we are in extended result code mode, make sure all of the | |
1465 # IOERRs we get back really do have their extended code values. | |
1466 # If an extended result code is returned, the sqlite3_errcode | |
1467 # TCLcommand will return a string of the form: SQLITE_IOERR+nnnn | |
1468 # where nnnn is a number | |
1469 if {[regexp {^SQLITE_IOERR} $rc] && ![regexp {IOERR\+\d} $rc]} { | |
1470 return $rc | |
1471 } | |
1472 } else { | |
1473 # If we are not in extended result code mode, make sure no | |
1474 # extended error codes are returned. | |
1475 if {[regexp {\+\d} $rc]} { | |
1476 return $rc | |
1477 } | |
1478 } | |
1479 # The test repeats as long as $::go is non-zero. $::go starts out | |
1480 # as 1. When a test runs to completion without hitting an I/O | |
1481 # error, that means there is no point in continuing with this test | |
1482 # case so set $::go to zero. | |
1483 # | |
1484 if {$::sqlite_io_error_pending>0} { | |
1485 set ::go 0 | |
1486 set q 0 | |
1487 set ::sqlite_io_error_pending 0 | |
1488 } else { | |
1489 set q 1 | |
1490 } | |
1491 | |
1492 set s [expr $::sqlite_io_error_hit==0] | |
1493 if {$::sqlite_io_error_hit>$::sqlite_io_error_hardhit && $r==0} { | |
1494 set r 1 | |
1495 } | |
1496 set ::sqlite_io_error_hit 0 | |
1497 | |
1498 # One of two things must have happened. either | |
1499 # 1. We never hit the IO error and the SQL returned OK | |
1500 # 2. An IO error was hit and the SQL failed | |
1501 # | |
1502 #puts "s=$s r=$r q=$q" | |
1503 expr { ($s && !$r && !$q) || (!$s && $r && $q) } | |
1504 } {1} | |
1505 | |
1506 set ::sqlite_io_error_hit 0 | |
1507 set ::sqlite_io_error_pending 0 | |
1508 | |
1509 # Check that no page references were leaked. There should be | |
1510 # a single reference if there is still an active transaction, | |
1511 # or zero otherwise. | |
1512 # | |
1513 # UPDATE: If the IO error occurs after a 'BEGIN' but before any | |
1514 # locks are established on database files (i.e. if the error | |
1515 # occurs while attempting to detect a hot-journal file), then | |
1516 # there may 0 page references and an active transaction according | |
1517 # to [sqlite3_get_autocommit]. | |
1518 # | |
1519 if {$::go && $::sqlite_io_error_hardhit && $::ioerropts(-ckrefcount)} { | |
1520 do_test $testname.$n.4 { | |
1521 set bt [btree_from_db db] | |
1522 db_enter db | |
1523 array set stats [btree_pager_stats $bt] | |
1524 db_leave db | |
1525 set nRef $stats(ref) | |
1526 expr {$nRef == 0 || ([sqlite3_get_autocommit db]==0 && $nRef == 1)} | |
1527 } {1} | |
1528 } | |
1529 | |
1530 # If there is an open database handle and no open transaction, | |
1531 # and the pager is not running in exclusive-locking mode, | |
1532 # check that the pager is in "unlocked" state. Theoretically, | |
1533 # if a call to xUnlock() failed due to an IO error the underlying | |
1534 # file may still be locked. | |
1535 # | |
1536 ifcapable pragma { | |
1537 if { [info commands db] ne "" | |
1538 && $::ioerropts(-ckrefcount) | |
1539 && [db one {pragma locking_mode}] eq "normal" | |
1540 && [sqlite3_get_autocommit db] | |
1541 } { | |
1542 do_test $testname.$n.5 { | |
1543 set bt [btree_from_db db] | |
1544 db_enter db | |
1545 array set stats [btree_pager_stats $bt] | |
1546 db_leave db | |
1547 set stats(state) | |
1548 } 0 | |
1549 } | |
1550 } | |
1551 | |
1552 # If an IO error occurred, then the checksum of the database should | |
1553 # be the same as before the script that caused the IO error was run. | |
1554 # | |
1555 if {$::go && $::sqlite_io_error_hardhit && $::ioerropts(-cksum)} { | |
1556 do_test $testname.$n.6 { | |
1557 catch {db close} | |
1558 catch {db2 close} | |
1559 set ::DB [sqlite3 db test.db; sqlite3_connection_pointer db] | |
1560 set nowcksum [cksum] | |
1561 set res [expr {$nowcksum==$::checksum || $nowcksum==$::goodcksum}] | |
1562 if {$res==0} { | |
1563 puts "now=$nowcksum" | |
1564 puts "the=$::checksum" | |
1565 puts "fwd=$::goodcksum" | |
1566 } | |
1567 set res | |
1568 } 1 | |
1569 } | |
1570 | |
1571 set ::sqlite_io_error_hardhit 0 | |
1572 set ::sqlite_io_error_pending 0 | |
1573 if {[info exists ::ioerropts(-cleanup)]} { | |
1574 catch $::ioerropts(-cleanup) | |
1575 } | |
1576 } | |
1577 set ::sqlite_io_error_pending 0 | |
1578 set ::sqlite_io_error_persist 0 | |
1579 unset ::ioerropts | |
1580 } | |
1581 | |
1582 # Return a checksum based on the contents of the main database associated | |
1583 # with connection $db | |
1584 # | |
1585 proc cksum {{db db}} { | |
1586 set txt [$db eval { | |
1587 SELECT name, type, sql FROM sqlite_master order by name | |
1588 }]\n | |
1589 foreach tbl [$db eval { | |
1590 SELECT name FROM sqlite_master WHERE type='table' order by name | |
1591 }] { | |
1592 append txt [$db eval "SELECT * FROM $tbl"]\n | |
1593 } | |
1594 foreach prag {default_synchronous default_cache_size} { | |
1595 append txt $prag-[$db eval "PRAGMA $prag"]\n | |
1596 } | |
1597 set cksum [string length $txt]-[md5 $txt] | |
1598 # puts $cksum-[file size test.db] | |
1599 return $cksum | |
1600 } | |
1601 | |
1602 # Generate a checksum based on the contents of the main and temp tables | |
1603 # database $db. If the checksum of two databases is the same, and the | |
1604 # integrity-check passes for both, the two databases are identical. | |
1605 # | |
1606 proc allcksum {{db db}} { | |
1607 set ret [list] | |
1608 ifcapable tempdb { | |
1609 set sql { | |
1610 SELECT name FROM sqlite_master WHERE type = 'table' UNION | |
1611 SELECT name FROM sqlite_temp_master WHERE type = 'table' UNION | |
1612 SELECT 'sqlite_master' UNION | |
1613 SELECT 'sqlite_temp_master' ORDER BY 1 | |
1614 } | |
1615 } else { | |
1616 set sql { | |
1617 SELECT name FROM sqlite_master WHERE type = 'table' UNION | |
1618 SELECT 'sqlite_master' ORDER BY 1 | |
1619 } | |
1620 } | |
1621 set tbllist [$db eval $sql] | |
1622 set txt {} | |
1623 foreach tbl $tbllist { | |
1624 append txt [$db eval "SELECT * FROM $tbl"] | |
1625 } | |
1626 foreach prag {default_cache_size} { | |
1627 append txt $prag-[$db eval "PRAGMA $prag"]\n | |
1628 } | |
1629 # puts txt=$txt | |
1630 return [md5 $txt] | |
1631 } | |
1632 | |
1633 # Generate a checksum based on the contents of a single database with | |
1634 # a database connection. The name of the database is $dbname. | |
1635 # Examples of $dbname are "temp" or "main". | |
1636 # | |
1637 proc dbcksum {db dbname} { | |
1638 if {$dbname=="temp"} { | |
1639 set master sqlite_temp_master | |
1640 } else { | |
1641 set master $dbname.sqlite_master | |
1642 } | |
1643 set alltab [$db eval "SELECT name FROM $master WHERE type='table'"] | |
1644 set txt [$db eval "SELECT * FROM $master"]\n | |
1645 foreach tab $alltab { | |
1646 append txt [$db eval "SELECT * FROM $dbname.$tab"]\n | |
1647 } | |
1648 return [md5 $txt] | |
1649 } | |
1650 | |
1651 proc memdebug_log_sql {{filename mallocs.sql}} { | |
1652 | |
1653 set data [sqlite3_memdebug_log dump] | |
1654 set nFrame [expr [llength [lindex $data 0]]-2] | |
1655 if {$nFrame < 0} { return "" } | |
1656 | |
1657 set database temp | |
1658 | |
1659 set tbl "CREATE TABLE ${database}.malloc(zTest, nCall, nByte, lStack);" | |
1660 | |
1661 set sql "" | |
1662 foreach e $data { | |
1663 set nCall [lindex $e 0] | |
1664 set nByte [lindex $e 1] | |
1665 set lStack [lrange $e 2 end] | |
1666 append sql "INSERT INTO ${database}.malloc VALUES" | |
1667 append sql "('test', $nCall, $nByte, '$lStack');\n" | |
1668 foreach f $lStack { | |
1669 set frames($f) 1 | |
1670 } | |
1671 } | |
1672 | |
1673 set tbl2 "CREATE TABLE ${database}.frame(frame INTEGER PRIMARY KEY, line);\n" | |
1674 set tbl3 "CREATE TABLE ${database}.file(name PRIMARY KEY, content);\n" | |
1675 | |
1676 foreach f [array names frames] { | |
1677 set addr [format %x $f] | |
1678 set cmd "addr2line -e [info nameofexec] $addr" | |
1679 set line [eval exec $cmd] | |
1680 append sql "INSERT INTO ${database}.frame VALUES($f, '$line');\n" | |
1681 | |
1682 set file [lindex [split $line :] 0] | |
1683 set files($file) 1 | |
1684 } | |
1685 | |
1686 foreach f [array names files] { | |
1687 set contents "" | |
1688 catch { | |
1689 set fd [open $f] | |
1690 set contents [read $fd] | |
1691 close $fd | |
1692 } | |
1693 set contents [string map {' ''} $contents] | |
1694 append sql "INSERT INTO ${database}.file VALUES('$f', '$contents');\n" | |
1695 } | |
1696 | |
1697 set fd [open $filename w] | |
1698 puts $fd "BEGIN; ${tbl}${tbl2}${tbl3}${sql} ; COMMIT;" | |
1699 close $fd | |
1700 } | |
1701 | |
1702 # Drop all tables in database [db] | |
1703 proc drop_all_tables {{db db}} { | |
1704 ifcapable trigger&&foreignkey { | |
1705 set pk [$db one "PRAGMA foreign_keys"] | |
1706 $db eval "PRAGMA foreign_keys = OFF" | |
1707 } | |
1708 foreach {idx name file} [db eval {PRAGMA database_list}] { | |
1709 if {$idx==1} { | |
1710 set master sqlite_temp_master | |
1711 } else { | |
1712 set master $name.sqlite_master | |
1713 } | |
1714 foreach {t type} [$db eval " | |
1715 SELECT name, type FROM $master | |
1716 WHERE type IN('table', 'view') AND name NOT LIKE 'sqliteX_%' ESCAPE 'X' | |
1717 "] { | |
1718 $db eval "DROP $type \"$t\"" | |
1719 } | |
1720 } | |
1721 ifcapable trigger&&foreignkey { | |
1722 $db eval "PRAGMA foreign_keys = $pk" | |
1723 } | |
1724 } | |
1725 | |
1726 #------------------------------------------------------------------------- | |
1727 # If a test script is executed with global variable $::G(perm:name) set to | |
1728 # "wal", then the tests are run in WAL mode. Otherwise, they should be run | |
1729 # in rollback mode. The following Tcl procs are used to make this less | |
1730 # intrusive: | |
1731 # | |
1732 # wal_set_journal_mode ?DB? | |
1733 # | |
1734 # If running a WAL test, execute "PRAGMA journal_mode = wal" using | |
1735 # connection handle DB. Otherwise, this command is a no-op. | |
1736 # | |
1737 # wal_check_journal_mode TESTNAME ?DB? | |
1738 # | |
1739 # If running a WAL test, execute a tests case that fails if the main | |
1740 # database for connection handle DB is not currently a WAL database. | |
1741 # Otherwise (if not running a WAL permutation) this is a no-op. | |
1742 # | |
1743 # wal_is_wal_mode | |
1744 # | |
1745 # Returns true if this test should be run in WAL mode. False otherwise. | |
1746 # | |
1747 proc wal_is_wal_mode {} { | |
1748 expr {[permutation] eq "wal"} | |
1749 } | |
1750 proc wal_set_journal_mode {{db db}} { | |
1751 if { [wal_is_wal_mode] } { | |
1752 $db eval "PRAGMA journal_mode = WAL" | |
1753 } | |
1754 } | |
1755 proc wal_check_journal_mode {testname {db db}} { | |
1756 if { [wal_is_wal_mode] } { | |
1757 $db eval { SELECT * FROM sqlite_master } | |
1758 do_test $testname [list $db eval "PRAGMA main.journal_mode"] {wal} | |
1759 } | |
1760 } | |
1761 | |
1762 proc permutation {} { | |
1763 set perm "" | |
1764 catch {set perm $::G(perm:name)} | |
1765 set perm | |
1766 } | |
1767 proc presql {} { | |
1768 set presql "" | |
1769 catch {set presql $::G(perm:presql)} | |
1770 set presql | |
1771 } | |
1772 | |
1773 #------------------------------------------------------------------------- | |
1774 # | |
1775 proc slave_test_script {script} { | |
1776 | |
1777 # Create the interpreter used to run the test script. | |
1778 interp create tinterp | |
1779 | |
1780 # Populate some global variables that tester.tcl expects to see. | |
1781 foreach {var value} [list \ | |
1782 ::argv0 $::argv0 \ | |
1783 ::argv {} \ | |
1784 ::SLAVE 1 \ | |
1785 ] { | |
1786 interp eval tinterp [list set $var $value] | |
1787 } | |
1788 | |
1789 # The alias used to access the global test counters. | |
1790 tinterp alias set_test_counter set_test_counter | |
1791 | |
1792 # Set up the ::cmdlinearg array in the slave. | |
1793 interp eval tinterp [list array set ::cmdlinearg [array get ::cmdlinearg]] | |
1794 | |
1795 # Set up the ::G array in the slave. | |
1796 interp eval tinterp [list array set ::G [array get ::G]] | |
1797 | |
1798 # Load the various test interfaces implemented in C. | |
1799 load_testfixture_extensions tinterp | |
1800 | |
1801 # Run the test script. | |
1802 interp eval tinterp $script | |
1803 | |
1804 # Check if the interpreter call [run_thread_tests] | |
1805 if { [interp eval tinterp {info exists ::run_thread_tests_called}] } { | |
1806 set ::run_thread_tests_called 1 | |
1807 } | |
1808 | |
1809 # Delete the interpreter used to run the test script. | |
1810 interp delete tinterp | |
1811 } | |
1812 | |
1813 proc slave_test_file {zFile} { | |
1814 set tail [file tail $zFile] | |
1815 | |
1816 if {[info exists ::G(start:permutation)]} { | |
1817 if {[permutation] != $::G(start:permutation)} return | |
1818 unset ::G(start:permutation) | |
1819 } | |
1820 if {[info exists ::G(start:file)]} { | |
1821 if {$tail != $::G(start:file) && $tail!="$::G(start:file).test"} return | |
1822 unset ::G(start:file) | |
1823 } | |
1824 | |
1825 # Remember the value of the shared-cache setting. So that it is possible | |
1826 # to check afterwards that it was not modified by the test script. | |
1827 # | |
1828 ifcapable shared_cache { set scs [sqlite3_enable_shared_cache] } | |
1829 | |
1830 # Run the test script in a slave interpreter. | |
1831 # | |
1832 unset -nocomplain ::run_thread_tests_called | |
1833 reset_prng_state | |
1834 set ::sqlite_open_file_count 0 | |
1835 set time [time { slave_test_script [list source $zFile] }] | |
1836 set ms [expr [lindex $time 0] / 1000] | |
1837 | |
1838 # Test that all files opened by the test script were closed. Omit this | |
1839 # if the test script has "thread" in its name. The open file counter | |
1840 # is not thread-safe. | |
1841 # | |
1842 if {[info exists ::run_thread_tests_called]==0} { | |
1843 do_test ${tail}-closeallfiles { expr {$::sqlite_open_file_count>0} } {0} | |
1844 } | |
1845 set ::sqlite_open_file_count 0 | |
1846 | |
1847 # Test that the global "shared-cache" setting was not altered by | |
1848 # the test script. | |
1849 # | |
1850 ifcapable shared_cache { | |
1851 set res [expr {[sqlite3_enable_shared_cache] == $scs}] | |
1852 do_test ${tail}-sharedcachesetting [list set {} $res] 1 | |
1853 } | |
1854 | |
1855 # Add some info to the output. | |
1856 # | |
1857 puts "Time: $tail $ms ms" | |
1858 show_memstats | |
1859 } | |
1860 | |
1861 # Open a new connection on database test.db and execute the SQL script | |
1862 # supplied as an argument. Before returning, close the new conection and | |
1863 # restore the 4 byte fields starting at header offsets 28, 92 and 96 | |
1864 # to the values they held before the SQL was executed. This simulates | |
1865 # a write by a pre-3.7.0 client. | |
1866 # | |
1867 proc sql36231 {sql} { | |
1868 set B [hexio_read test.db 92 8] | |
1869 set A [hexio_read test.db 28 4] | |
1870 sqlite3 db36231 test.db | |
1871 catch { db36231 func a_string a_string } | |
1872 execsql $sql db36231 | |
1873 db36231 close | |
1874 hexio_write test.db 28 $A | |
1875 hexio_write test.db 92 $B | |
1876 return "" | |
1877 } | |
1878 | |
1879 proc db_save {} { | |
1880 foreach f [glob -nocomplain sv_test.db*] { forcedelete $f } | |
1881 foreach f [glob -nocomplain test.db*] { | |
1882 set f2 "sv_$f" | |
1883 forcecopy $f $f2 | |
1884 } | |
1885 } | |
1886 proc db_save_and_close {} { | |
1887 db_save | |
1888 catch { db close } | |
1889 return "" | |
1890 } | |
1891 proc db_restore {} { | |
1892 foreach f [glob -nocomplain test.db*] { forcedelete $f } | |
1893 foreach f2 [glob -nocomplain sv_test.db*] { | |
1894 set f [string range $f2 3 end] | |
1895 forcecopy $f2 $f | |
1896 } | |
1897 } | |
1898 proc db_restore_and_reopen {{dbfile test.db}} { | |
1899 catch { db close } | |
1900 db_restore | |
1901 sqlite3 db $dbfile | |
1902 } | |
1903 proc db_delete_and_reopen {{file test.db}} { | |
1904 catch { db close } | |
1905 foreach f [glob -nocomplain test.db*] { forcedelete $f } | |
1906 sqlite3 db $file | |
1907 } | |
1908 | |
1909 # If the library is compiled with the SQLITE_DEFAULT_AUTOVACUUM macro set | |
1910 # to non-zero, then set the global variable $AUTOVACUUM to 1. | |
1911 set AUTOVACUUM $sqlite_options(default_autovacuum) | |
1912 | |
1913 # Make sure the FTS enhanced query syntax is disabled. | |
1914 set sqlite_fts3_enable_parentheses 0 | |
1915 | |
1916 # During testing, assume that all database files are well-formed. The | |
1917 # few test cases that deliberately corrupt database files should rescind | |
1918 # this setting by invoking "database_can_be_corrupt" | |
1919 # | |
1920 database_never_corrupt | |
1921 | |
1922 source $testdir/thread_common.tcl | |
1923 source $testdir/malloc_common.tcl | |
OLD | NEW |