dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 1 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 2 | #------------------------------------------------------------------------- |
| 3 | # Usage: |
| 4 | # |
| 5 | proc usage {} { |
dan | 2bb2d53 | 2022-07-13 21:02:07 +0000 | [diff] [blame] | 6 | set a0 testrunner.tcl |
| 7 | |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 8 | set ::argv [list] |
| 9 | uplevel [list source $::testdir/permutations.test] |
| 10 | |
| 11 | puts stderr "Usage: $a0 ?SWITCHES? ?PERMUTATION? ?PATTERNS?" |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 12 | puts stderr "" |
| 13 | puts stderr "where SWITCHES are:" |
| 14 | puts stderr " --jobs NUMBER-OF-JOBS" |
dan | 2bb2d53 | 2022-07-13 21:02:07 +0000 | [diff] [blame] | 15 | puts stderr "" |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 16 | puts stderr "available PERMUTATION values are:" |
| 17 | set ii 0 |
| 18 | foreach name [lsort [array names ::testspec]] { |
| 19 | if {($ii % 3)==0} { puts -nonewline stderr " " } |
| 20 | puts -nonewline stderr [format "% -22s" $name] |
| 21 | if {($ii % 3)==2} { puts stderr "" } |
| 22 | incr ii |
| 23 | } |
| 24 | puts stderr "" |
| 25 | puts stderr "" |
dan | 2bb2d53 | 2022-07-13 21:02:07 +0000 | [diff] [blame] | 26 | puts stderr "Examples:" |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 27 | puts stderr " 1) Run the veryquick tests:" |
| 28 | puts stderr " $a0" |
| 29 | puts stderr " 2) Run all test scripts in the source tree:" |
| 30 | puts stderr " $a0 full" |
| 31 | puts stderr " 2) Run the 'memsubsys1' permutation:" |
| 32 | puts stderr " $a0 memsubsys1" |
| 33 | puts stderr " 3) Run all permutations usually run by \[make fulltest\]" |
| 34 | puts stderr " $a0 release" |
| 35 | puts stderr " 4) Run all scripts that match the pattern 'select%':" |
| 36 | puts stderr " $a0 select%" |
| 37 | puts stderr " $a0 all select%" |
| 38 | puts stderr " $a0 full select%" |
| 39 | puts stderr " 5) Run all scripts that are part of the veryquick permutation and match the pattern 'select%':" |
| 40 | puts stderr " $a0 veryquick select%" |
| 41 | puts stderr " 6) Run the 'memsubsys1' permutation, but just those scripts that match 'window%':" |
| 42 | puts stderr " $a0 memsubsys1 window%" |
| 43 | puts stderr " 7) Run all the permutations, but only the scripts that match either 'fts5%' or 'rtree%':" |
| 44 | puts stderr " $a0 release fts5% rtree%" |
dan | 2bb2d53 | 2022-07-13 21:02:07 +0000 | [diff] [blame] | 45 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 46 | exit 1 |
| 47 | } |
| 48 | #------------------------------------------------------------------------- |
| 49 | |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 50 | #------------------------------------------------------------------------- |
| 51 | # The database schema used by the testrunner.db database. |
| 52 | # |
| 53 | set R(schema) { |
| 54 | DROP TABLE IF EXISTS script; |
| 55 | DROP TABLE IF EXISTS msg; |
| 56 | DROP TABLE IF EXISTS malloc; |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 57 | |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 58 | CREATE TABLE script( |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 59 | config TEXT, |
| 60 | filename TEXT, -- full path to test script |
dan | cf2ad7a | 2022-07-18 19:32:30 +0000 | [diff] [blame] | 61 | slow BOOLEAN, -- true if script is "slow" |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 62 | state TEXT CHECK( state IN ('ready', 'running', 'done') ), |
| 63 | testfixtureid, -- Id of process that ran script |
| 64 | time INTEGER, -- Time in ms |
| 65 | nerr INTEGER, -- if 'done', the number of errors |
| 66 | ntest INTEGER, -- if 'done', the number of tests |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 67 | output TEXT, -- full output of test script |
| 68 | PRIMARY KEY(config, filename) |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 69 | ); |
| 70 | |
| 71 | CREATE TABLE malloc( |
| 72 | id INTEGER PRIMARY KEY, |
| 73 | nmalloc INTEGER, |
| 74 | nbyte INTEGER, |
| 75 | leaker TEXT |
| 76 | ); |
| 77 | |
| 78 | CREATE TABLE msg( |
| 79 | id INTEGER PRIMARY KEY, |
| 80 | msg TEXT |
| 81 | ); |
| 82 | } |
| 83 | #------------------------------------------------------------------------- |
| 84 | |
| 85 | #------------------------------------------------------------------------- |
| 86 | # Try to estimate a the number of processes to use. |
| 87 | # |
| 88 | # Command [guess_number_of_cores] attempts to glean the number of logical |
| 89 | # cores. Command [default_njob] returns the default value for the --jobs |
| 90 | # switch. |
| 91 | # |
| 92 | proc guess_number_of_cores {} { |
| 93 | set ret 4 |
dan | 615aece | 2022-07-13 21:28:19 +0000 | [diff] [blame] | 94 | |
| 95 | if {$::tcl_platform(os)=="Darwin"} { |
| 96 | set cmd "sysctl -n hw.logicalcpu" |
| 97 | } else { |
| 98 | set cmd "nproc" |
| 99 | } |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 100 | catch { |
dan | 615aece | 2022-07-13 21:28:19 +0000 | [diff] [blame] | 101 | set fd [open "|$cmd" r] |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 102 | set ret [gets $fd] |
| 103 | close $fd |
| 104 | set ret [expr $ret] |
| 105 | } |
| 106 | return $ret |
| 107 | } |
| 108 | |
| 109 | proc default_njob {} { |
| 110 | set nCore [guess_number_of_cores] |
| 111 | set nHelper [expr int($nCore*0.75)] |
| 112 | expr $nHelper>0 ? $nHelper : 1 |
| 113 | } |
| 114 | #------------------------------------------------------------------------- |
| 115 | |
| 116 | |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 117 | set R(dbname) [file normalize testrunner.db] |
| 118 | set R(logname) [file normalize testrunner.log] |
| 119 | set R(info_script) [file normalize [info script]] |
| 120 | set R(timeout) 10000 ;# Default busy-timeout for testrunner. |
| 121 | set R(nJob) [default_njob] ;# Default number of helper processes |
| 122 | set R(leaker) "" ;# Name of first script to leak memory |
| 123 | |
dan | 95cc6a5 | 2022-07-13 19:57:35 +0000 | [diff] [blame] | 124 | set R(patternlist) [list] |
| 125 | |
| 126 | set testdir [file dirname $argv0] |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 127 | |
| 128 | # Parse the command line options. There are two ways to invoke this |
| 129 | # script - to create a helper or coordinator process. If there are |
| 130 | # no helper processes, the coordinator runs test scripts. |
| 131 | # |
| 132 | # To create a helper process: |
| 133 | # |
| 134 | # testrunner.tcl helper ID |
| 135 | # |
| 136 | # where ID is an integer greater than 0. The process will create and |
| 137 | # run tests in the "testdir$ID" directory. Helper processes are only |
| 138 | # created by coordinators - there is no need for a user to create |
| 139 | # helper processes manually. |
| 140 | # |
| 141 | # If the first argument is anything other than "helper", then a coordinator |
| 142 | # process is started. See the implementation of the [usage] proc above for |
| 143 | # details. |
| 144 | # |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 145 | switch -- [lindex $argv 0] { |
| 146 | helper { |
| 147 | set R(helper) 1 |
| 148 | set R(helper_id) [lindex $argv 1] |
| 149 | set argv [list --testdir=testdir$R(helper_id)] |
| 150 | } |
| 151 | |
| 152 | default { |
| 153 | set R(helper) 0 |
| 154 | set R(helper_id) 0 |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 155 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 156 | } |
| 157 | } |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 158 | if {$R(helper)==0} { |
| 159 | for {set ii 0} {$ii < [llength $argv]} {incr ii} { |
| 160 | set a [lindex $argv $ii] |
| 161 | set n [string length $a] |
| 162 | |
dan | 95cc6a5 | 2022-07-13 19:57:35 +0000 | [diff] [blame] | 163 | if {[string range $a 0 0]=="-"} { |
| 164 | if {($n>2 && [string match "$a*" --jobs]) || $a=="-j"} { |
| 165 | incr ii |
| 166 | set R(nJob) [lindex $argv $ii] |
| 167 | } else { |
| 168 | usage |
| 169 | } |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 170 | } else { |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 171 | lappend R(patternlist) [string map {% *} $a] |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 172 | } |
| 173 | } |
| 174 | |
| 175 | set argv [list] |
| 176 | } |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 177 | source $testdir/permutations.test |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 178 | |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 179 | #------------------------------------------------------------------------- |
| 180 | # Return a list of tests to run. Each element of the list is itself a |
| 181 | # list of two elements - the name of a permuations.test configuration |
| 182 | # followed by the full path to a test script. i.e.: |
| 183 | # |
| 184 | # {CONFIG FILENAME} {CONFIG FILENAME} ... |
| 185 | # |
| 186 | proc testset_patternlist {patternlist} { |
| 187 | |
| 188 | set first [lindex $patternlist 0] |
| 189 | if {$first=="all"} { set first "full" } |
| 190 | |
| 191 | if {$first=="release"} { |
| 192 | |
| 193 | # The following mirrors the set of test suites invoked by "all.test". |
| 194 | # |
| 195 | set clist { |
| 196 | full |
| 197 | no_optimization memsubsys1 memsubsys2 singlethread |
| 198 | multithread onefile utf16 exclusive persistent_journal |
| 199 | persistent_journal_error no_journal no_journal_error |
| 200 | autovacuum_ioerr no_mutex_try fullmutex journaltest |
| 201 | inmemory_journal pcache0 pcache10 pcache50 pcache90 |
| 202 | pcache100 prepare mmap |
| 203 | } |
| 204 | ifcapable rbu { lappend clist rbu } |
| 205 | if {$::tcl_platform(platform)=="unix"} { |
| 206 | ifcapable !default_autovacuum { |
| 207 | lappend clist autovacuum_crash |
| 208 | } |
| 209 | } |
| 210 | set patternlist [lrange $patternlist 1 end] |
| 211 | |
| 212 | } elseif {[info exists ::testspec($first)]} { |
| 213 | set clist $first |
| 214 | set patternlist [lrange $patternlist 1 end] |
| 215 | } elseif { [llength $patternlist]==0 } { |
| 216 | set clist veryquick |
| 217 | } else { |
| 218 | set clist full |
| 219 | } |
| 220 | |
| 221 | set testset [list] |
| 222 | |
| 223 | foreach config $clist { |
| 224 | catch { array unset O } |
| 225 | array set O $::testspec($config) |
| 226 | foreach f $O(-files) { |
| 227 | if {[file pathtype $f]!="absolute"} { |
| 228 | set f [file join $::testdir $f] |
| 229 | } |
| 230 | lappend testset [list $config [file normalize $f]] |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | if {[llength $patternlist]>0} { |
| 235 | foreach t $testset { |
| 236 | set tail [file tail [lindex $t 1]] |
| 237 | foreach p $patternlist { |
| 238 | if {[string match $p $tail]} { |
| 239 | lappend ret $t |
| 240 | break; |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | } else { |
| 245 | set ret $testset |
| 246 | } |
| 247 | |
| 248 | set ret |
| 249 | } |
| 250 | #-------------------------------------------------------------------------- |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 251 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 252 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 253 | proc r_write_db {tcl} { |
| 254 | global R |
dan | de353fb | 2022-07-14 21:17:22 +0000 | [diff] [blame] | 255 | |
| 256 | sqlite3_test_control_pending_byte 0x010000 |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 257 | sqlite3 db $R(dbname) |
| 258 | db timeout $R(timeout) |
| 259 | db eval { BEGIN EXCLUSIVE } |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 260 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 261 | uplevel $tcl |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 262 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 263 | db eval { COMMIT } |
| 264 | db close |
| 265 | } |
| 266 | |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 267 | proc make_new_testset {} { |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 268 | global R |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 269 | |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 270 | set tests [testset_patternlist $R(patternlist)] |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 271 | r_write_db { |
| 272 | db eval $R(schema) |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 273 | foreach t $tests { |
| 274 | foreach {c s} $t {} |
dan | cf2ad7a | 2022-07-18 19:32:30 +0000 | [diff] [blame] | 275 | set slow 0 |
| 276 | |
| 277 | set fd [open $s] |
| 278 | for {set ii 0} {$ii<100 && ![eof $fd]} {incr ii} { |
| 279 | set line [gets $fd] |
| 280 | if {[string match -nocase *testrunner:* $line]} { |
| 281 | regexp -nocase {.*testrunner:(.*)} $line -> properties |
| 282 | foreach p $properties { |
| 283 | if {$p=="slow"} { set slow 1 } |
| 284 | } |
| 285 | } |
| 286 | } |
| 287 | close $fd |
| 288 | |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 289 | db eval { |
dan | cf2ad7a | 2022-07-18 19:32:30 +0000 | [diff] [blame] | 290 | INSERT INTO script(config, filename, slow, state) |
| 291 | VALUES ($c, $s, $slow, 'ready') |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 292 | } |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 293 | } |
| 294 | } |
| 295 | } |
| 296 | |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 297 | # Find the next job in the database and mark it as 'running'. Then return |
| 298 | # a list consisting of the |
| 299 | # |
| 300 | # CONFIG FILENAME |
| 301 | # |
| 302 | # pair for the test. |
| 303 | # |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 304 | proc get_next_test {} { |
| 305 | global R |
| 306 | set myid $R(helper_id) |
| 307 | |
| 308 | r_write_db { |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 309 | set f "" |
| 310 | set c "" |
| 311 | db eval { |
| 312 | SELECT config, filename FROM script WHERE state='ready' |
dan | cf2ad7a | 2022-07-18 19:32:30 +0000 | [diff] [blame] | 313 | ORDER BY |
| 314 | (slow * (($myid+1) % 2)) DESC, |
| 315 | config!='full', |
| 316 | config, |
| 317 | filename |
| 318 | LIMIT 1 |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 319 | } { |
| 320 | set c $config |
| 321 | set f $filename |
| 322 | } |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 323 | if {$f!=""} { |
| 324 | db eval { |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 325 | UPDATE script SET state='running', testfixtureid=$myid |
| 326 | WHERE (config, filename) = ($c, $f) |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 327 | } |
| 328 | } |
| 329 | } |
| 330 | |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 331 | if {$f==""} { return "" } |
| 332 | list $c $f |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 333 | } |
| 334 | |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 335 | proc r_testname {config filename} { |
| 336 | set name [file tail $filename] |
| 337 | if {$config!="" && $config!="full" && $config!="veryquick"} { |
| 338 | set name "$config-$name" |
| 339 | } |
| 340 | return $name |
| 341 | } |
| 342 | |
| 343 | proc r_set_test_result {config filename ms nerr ntest output} { |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 344 | global R |
| 345 | |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 346 | set f [r_testname $config $filename] |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 347 | if {$nerr==0} { |
| 348 | set msg "$f... Ok" |
| 349 | } else { |
| 350 | set msg "$f... FAILED - $nerr errors of $ntest tests" |
| 351 | } |
| 352 | append msg " (${ms}ms)" |
| 353 | if {$R(helper)} { |
| 354 | append msg " (helper $R(helper_id))" |
| 355 | } |
| 356 | |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 357 | sqlite3_shutdown |
| 358 | set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1] |
| 359 | set nByte [sqlite3_memory_used] |
| 360 | if {($nByte>0 || $nMalloc>0) && $R(leaker)==""} { |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 361 | set R(leaker) $f |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 362 | } |
| 363 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 364 | r_write_db { |
| 365 | db eval { |
| 366 | UPDATE script |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 367 | SET state='done', output=$output, nerr=$nerr, ntest=$ntest, time=$ms |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 368 | WHERE (config, filename)=($config, $filename); |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 369 | |
| 370 | INSERT INTO msg(msg) VALUES ($msg); |
| 371 | } |
| 372 | } |
| 373 | } |
| 374 | |
| 375 | set R(iNextMsg) 1 |
| 376 | proc r_get_messages {{db ""}} { |
| 377 | global R |
| 378 | |
dan | de353fb | 2022-07-14 21:17:22 +0000 | [diff] [blame] | 379 | sqlite3_test_control_pending_byte 0x010000 |
| 380 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 381 | if {$db==""} { |
| 382 | sqlite3 rgmhandle $R(dbname) |
| 383 | set dbhandle rgmhandle |
| 384 | $dbhandle timeout $R(timeout) |
| 385 | } else { |
| 386 | set dbhandle $db |
| 387 | } |
| 388 | |
| 389 | $dbhandle transaction { |
| 390 | set next $R(iNextMsg) |
| 391 | set ret [$dbhandle eval {SELECT msg FROM msg WHERE id>=$next}] |
| 392 | set R(iNextMsg) [$dbhandle one {SELECT COALESCE(max(id), 0)+1 FROM msg}] |
| 393 | } |
| 394 | |
| 395 | if {$db==""} { |
| 396 | rgmhandle close |
| 397 | } |
| 398 | |
| 399 | set ret |
| 400 | } |
| 401 | |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 402 | # This is called after all tests have been run to write the leaked memory |
| 403 | # report into the malloc table of testrunner.db. |
| 404 | # |
| 405 | proc r_memory_report {} { |
| 406 | global R |
| 407 | |
| 408 | sqlite3_shutdown |
| 409 | |
| 410 | set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1] |
| 411 | set nByte [sqlite3_memory_used] |
| 412 | set id $R(helper_id) |
| 413 | set leaker $R(leaker) |
| 414 | |
| 415 | r_write_db { |
| 416 | db eval { |
| 417 | INSERT INTO malloc(id, nMalloc, nByte, leaker) |
| 418 | VALUES($id, $nMalloc, $nByte, $leaker) |
| 419 | } |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 424 | #-------------------------------------------------------------------------- |
| 425 | # |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 426 | set ::R_INSTALL_PUTS_WRAPPER { |
| 427 | proc puts_sts_wrapper {args} { |
| 428 | set n [llength $args] |
| 429 | if {$n==1 || ($n==2 && [string first [lindex $args 0] -nonewline]==0)} { |
| 430 | uplevel puts_into_caller $args |
| 431 | } else { |
| 432 | # A channel was explicitly specified. |
| 433 | uplevel puts_sts_original $args |
| 434 | } |
| 435 | } |
| 436 | rename puts puts_sts_original |
| 437 | proc puts {args} { uplevel puts_sts_wrapper $args } |
| 438 | } |
| 439 | |
| 440 | proc r_install_puts_wrapper {} $::R_INSTALL_PUTS_WRAPPER |
| 441 | proc r_uninstall_puts_wrapper {} { |
| 442 | rename puts "" |
| 443 | rename puts_sts_original puts |
| 444 | } |
| 445 | |
| 446 | proc slave_test_script {script} { |
| 447 | |
| 448 | # Create the interpreter used to run the test script. |
| 449 | interp create tinterp |
| 450 | |
| 451 | # Populate some global variables that tester.tcl expects to see. |
| 452 | foreach {var value} [list \ |
| 453 | ::argv0 $::argv0 \ |
| 454 | ::argv {} \ |
| 455 | ::SLAVE 1 \ |
| 456 | ] { |
| 457 | interp eval tinterp [list set $var $value] |
| 458 | } |
| 459 | |
| 460 | # The alias used to access the global test counters. |
| 461 | tinterp alias set_test_counter set_test_counter |
| 462 | |
| 463 | # Set up an empty ::cmdlinearg array in the slave. |
| 464 | interp eval tinterp [list array set ::cmdlinearg [array get ::cmdlinearg]] |
| 465 | |
| 466 | # Set up the ::G array in the slave. |
| 467 | interp eval tinterp [list array set ::G [array get ::G]] |
| 468 | interp eval tinterp [list set ::G(runner.tcl) 1] |
| 469 | |
| 470 | interp eval tinterp $::R_INSTALL_PUTS_WRAPPER |
| 471 | tinterp alias puts_into_caller puts_into_caller |
| 472 | |
| 473 | # Load the various test interfaces implemented in C. |
| 474 | load_testfixture_extensions tinterp |
| 475 | |
| 476 | # Run the test script. |
| 477 | set rc [catch { interp eval tinterp $script } msg opt] |
| 478 | if {$rc} { |
| 479 | puts_into_caller $msg |
| 480 | puts_into_caller [dict get $opt -errorinfo] |
| 481 | incr ::TC(errors) |
| 482 | } |
| 483 | |
| 484 | # Check if the interpreter call [run_thread_tests] |
| 485 | if { [interp eval tinterp {info exists ::run_thread_tests_called}] } { |
| 486 | set ::run_thread_tests_called 1 |
| 487 | } |
| 488 | |
| 489 | # Delete the interpreter used to run the test script. |
| 490 | interp delete tinterp |
| 491 | } |
| 492 | |
| 493 | proc slave_test_file {zFile} { |
| 494 | set tail [file tail $zFile] |
| 495 | |
| 496 | # Remember the value of the shared-cache setting. So that it is possible |
| 497 | # to check afterwards that it was not modified by the test script. |
| 498 | # |
| 499 | ifcapable shared_cache { set scs [sqlite3_enable_shared_cache] } |
| 500 | |
| 501 | # Run the test script in a slave interpreter. |
| 502 | # |
| 503 | unset -nocomplain ::run_thread_tests_called |
| 504 | reset_prng_state |
| 505 | set ::sqlite_open_file_count 0 |
| 506 | set time [time { slave_test_script [list source $zFile] }] |
| 507 | set ms [expr [lindex $time 0] / 1000] |
| 508 | |
| 509 | r_install_puts_wrapper |
| 510 | |
| 511 | # Test that all files opened by the test script were closed. Omit this |
| 512 | # if the test script has "thread" in its name. The open file counter |
| 513 | # is not thread-safe. |
| 514 | # |
| 515 | if {[info exists ::run_thread_tests_called]==0} { |
| 516 | do_test ${tail}-closeallfiles { expr {$::sqlite_open_file_count>0} } {0} |
| 517 | } |
| 518 | set ::sqlite_open_file_count 0 |
| 519 | |
| 520 | # Test that the global "shared-cache" setting was not altered by |
| 521 | # the test script. |
| 522 | # |
| 523 | ifcapable shared_cache { |
| 524 | set res [expr {[sqlite3_enable_shared_cache] == $scs}] |
| 525 | do_test ${tail}-sharedcachesetting [list set {} $res] 1 |
| 526 | } |
| 527 | |
| 528 | # Add some info to the output. |
| 529 | # |
| 530 | output2 "Time: $tail $ms ms" |
| 531 | show_memstats |
| 532 | |
| 533 | r_uninstall_puts_wrapper |
| 534 | return $ms |
| 535 | } |
| 536 | |
| 537 | proc puts_into_caller {args} { |
| 538 | global R |
| 539 | if {[llength $args]==1} { |
| 540 | append R(output) [lindex $args 0] |
| 541 | append R(output) "\n" |
| 542 | } else { |
| 543 | append R(output) [lindex $args 1] |
| 544 | } |
| 545 | } |
| 546 | |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 547 | #------------------------------------------------------------------------- |
| 548 | # |
| 549 | proc r_final_report {} { |
| 550 | global R |
| 551 | |
dan | de353fb | 2022-07-14 21:17:22 +0000 | [diff] [blame] | 552 | sqlite3_test_control_pending_byte 0x010000 |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 553 | sqlite3 db $R(dbname) |
dan | de353fb | 2022-07-14 21:17:22 +0000 | [diff] [blame] | 554 | |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 555 | db timeout $R(timeout) |
| 556 | |
| 557 | set errcode 0 |
| 558 | |
| 559 | # Create the text log file. This is just the concatenation of the |
| 560 | # 'output' column of the database for every script that was run. |
| 561 | set fd [open $R(logname) w] |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 562 | db eval {SELECT output FROM script ORDER BY config!='full',config,filename} { |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 563 | puts $fd $output |
| 564 | } |
| 565 | close $fd |
| 566 | |
| 567 | # Check if any scripts reported errors. If so, print one line noting |
| 568 | # how many errors, and another identifying the scripts in which they |
| 569 | # occured. Or, if no errors occurred, print out "no errors at all!". |
| 570 | sqlite3 db $R(dbname) |
| 571 | db timeout $R(timeout) |
| 572 | db eval { SELECT sum(nerr) AS nerr, sum(ntest) AS ntest FROM script } { } |
| 573 | puts "$nerr errors from $ntest tests." |
| 574 | if {$nerr>0} { |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 575 | db eval { SELECT config, filename FROM script WHERE nerr>0 } { |
| 576 | lappend errlist [r_testname $config $filename] |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 577 | } |
| 578 | puts "Errors in: $errlist" |
| 579 | set errcode 1 |
| 580 | } |
| 581 | |
| 582 | # Check if any scripts were not run or did not finish. Print out a |
| 583 | # line identifying them if there are any. |
| 584 | set errlist [list] |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 585 | db eval { SELECT config, filename FROM script WHERE state!='done' } { |
| 586 | lappend errlist [r_testname $config $filename] |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 587 | } |
| 588 | if {$errlist!=[list]} { |
| 589 | puts "Tests DID NOT FINISH (crashed?): $errlist" |
| 590 | set errcode 1 |
| 591 | } |
| 592 | |
| 593 | set bLeak 0 |
| 594 | db eval { |
| 595 | SELECT id, nmalloc, nbyte, leaker FROM malloc |
| 596 | WHERE nmalloc>0 OR nbyte>0 |
| 597 | } { |
| 598 | if {$id==0} { |
| 599 | set line "This process " |
| 600 | } else { |
| 601 | set line "Helper $id " |
| 602 | } |
| 603 | append line "leaked $nbyte byte in $nmalloc allocations" |
| 604 | if {$leaker!=""} { append line " (perhaps in [file tail $leaker])" } |
| 605 | puts $line |
| 606 | set bLeak 1 |
| 607 | } |
| 608 | if {$bLeak==0} { |
| 609 | puts "No leaks - all allocations freed." |
| 610 | } |
| 611 | |
| 612 | db close |
| 613 | |
| 614 | puts "Test database is $R(dbname)" |
| 615 | puts "Test log file is $R(logname)" |
| 616 | if {$errcode} { |
| 617 | puts "This test has FAILED." |
| 618 | } |
| 619 | return $errcode |
| 620 | } |
| 621 | |
| 622 | |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 623 | if {$R(helper)==0} { |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 624 | make_new_testset |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 625 | } |
| 626 | |
| 627 | set R(nHelperRunning) 0 |
| 628 | if {$R(helper)==0 && $R(nJob)>1} { |
| 629 | cd $cmdlinearg(TESTFIXTURE_HOME) |
| 630 | for {set ii 1} {$ii <= $R(nJob)} {incr ii} { |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 631 | set cmd "[info nameofexec] $R(info_script) helper $ii 2>@1" |
| 632 | puts "Launching helper $ii ($cmd)" |
| 633 | set chan [open "|$cmd" r] |
| 634 | fconfigure $chan -blocking false |
| 635 | fileevent $chan readable [list r_helper_readable $ii $chan] |
| 636 | incr R(nHelperRunning) |
| 637 | } |
| 638 | cd $cmdlinearg(testdir) |
| 639 | } |
| 640 | |
| 641 | proc r_helper_readable {id chan} { |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 642 | set data [gets $chan] |
dan | de353fb | 2022-07-14 21:17:22 +0000 | [diff] [blame] | 643 | if {$data!=""} { puts "helper $id:$data" } |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 644 | if {[eof $chan]} { |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 645 | puts "helper $id is finished" |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 646 | incr ::R(nHelperRunning) -1 |
| 647 | close $chan |
| 648 | } |
| 649 | } |
| 650 | |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 651 | if {$R(nHelperRunning)==0} { |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 652 | while { ""!=[set t [get_next_test]] } { |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 653 | set R(output) "" |
| 654 | set TC(count) 0 |
| 655 | set TC(errors) 0 |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 656 | |
dan | ca0720a | 2022-07-14 18:09:56 +0000 | [diff] [blame] | 657 | foreach {config filename} $t {} |
| 658 | |
| 659 | array set O $::testspec($config) |
| 660 | set ::G(perm:name) $config |
| 661 | set ::G(perm:prefix) $O(-prefix) |
| 662 | set ::G(isquick) 1 |
| 663 | set ::G(perm:dbconfig) $O(-dbconfig) |
| 664 | set ::G(perm:presql) $O(-presql) |
| 665 | |
| 666 | eval $O(-initialize) |
| 667 | set ms [slave_test_file $filename] |
| 668 | eval $O(-shutdown) |
| 669 | |
| 670 | unset -nocomplain ::G(perm:sqlite3_args) |
| 671 | unset ::G(perm:name) |
| 672 | unset ::G(perm:prefix) |
| 673 | unset ::G(perm:dbconfig) |
| 674 | unset ::G(perm:presql) |
| 675 | |
| 676 | r_set_test_result $config $filename $ms $TC(errors) $TC(count) $R(output) |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 677 | |
| 678 | if {$R(helper)==0} { |
| 679 | foreach msg [r_get_messages] { puts $msg } |
| 680 | } |
| 681 | } |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 682 | |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 683 | # Tests are finished - write a record into testrunner.db describing |
| 684 | # any memory leaks. |
| 685 | r_memory_report |
| 686 | |
| 687 | } else { |
| 688 | set TTT 0 |
| 689 | sqlite3 db $R(dbname) |
| 690 | db timeout $R(timeout) |
| 691 | while {$R(nHelperRunning)>0} { |
| 692 | after 250 { incr TTT } |
| 693 | vwait TTT |
| 694 | foreach msg [r_get_messages db] { puts $msg } |
| 695 | } |
| 696 | db close |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 697 | } |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 698 | |
| 699 | set errcode 0 |
| 700 | if {$R(helper)==0} { |
dan | e9a7ebe | 2022-07-13 17:46:42 +0000 | [diff] [blame] | 701 | set errcode [r_final_report] |
dan | 0505851 | 2022-07-12 20:31:16 +0000 | [diff] [blame] | 702 | } |
| 703 | |
| 704 | exit $errcode |
| 705 | |