xref: /sqlite-3.40.0/test/testrunner.tcl (revision 4f28ee9d)
1
2
3#-------------------------------------------------------------------------
4# Usage:
5#
6proc usage {} {
7  set a0 testrunner.tcl
8
9  puts stderr "Usage: $a0 ?SWITCHES? ?all|veryquick? ?PATTERNS?"
10  puts stderr ""
11  puts stderr "where SWITCHES are:"
12  puts stderr "    --jobs NUMBER-OF-JOBS"
13  puts stderr ""
14  puts stderr "Examples:"
15  puts stderr "    $a0                    # Run veryquick.test tests"
16  puts stderr "    $a0 all                # Run all tests"
17  puts stderr "    $a0 veryquick rtree%   # Run all test scripts from veryquick.test that match 'rtree%'"
18  puts stderr "    $a0 alter% fts5%       # Run all test scripts that match 'alter%' or 'rtree%'"
19
20  exit 1
21}
22#-------------------------------------------------------------------------
23
24#-------------------------------------------------------------------------
25# The database schema used by the testrunner.db database.
26#
27set R(schema) {
28  DROP TABLE IF EXISTS script;
29  DROP TABLE IF EXISTS msg;
30  DROP TABLE IF EXISTS malloc;
31
32  CREATE TABLE script(
33    filename TEXT PRIMARY KEY,    -- full path to test script
34    state TEXT CHECK( state IN ('ready', 'running', 'done') ),
35    testfixtureid,                -- Id of process that ran script
36    time INTEGER,                 -- Time in ms
37    nerr INTEGER,                 -- if 'done', the number of errors
38    ntest INTEGER,                -- if 'done', the number of tests
39    output TEXT                   -- full output of test script
40  );
41
42  CREATE TABLE malloc(
43    id INTEGER PRIMARY KEY,
44    nmalloc INTEGER,
45    nbyte INTEGER,
46    leaker TEXT
47  );
48
49  CREATE TABLE msg(
50    id INTEGER PRIMARY KEY,
51    msg TEXT
52  );
53}
54#-------------------------------------------------------------------------
55
56#-------------------------------------------------------------------------
57# Try to estimate a the number of processes to use.
58#
59# Command [guess_number_of_cores] attempts to glean the number of logical
60# cores. Command [default_njob] returns the default value for the --jobs
61# switch.
62#
63proc guess_number_of_cores {} {
64  set ret 4
65  catch {
66    set fd [open "|nproc" r]
67    set ret [gets $fd]
68    close $fd
69    set ret [expr $ret]
70  }
71  return $ret
72}
73
74proc default_njob {} {
75  set nCore [guess_number_of_cores]
76  set nHelper [expr int($nCore*0.75)]
77  expr $nHelper>0 ? $nHelper : 1
78}
79#-------------------------------------------------------------------------
80
81
82set R(dbname) [file normalize testrunner.db]
83set R(logname) [file normalize testrunner.log]
84set R(info_script) [file normalize [info script]]
85set R(timeout) 10000              ;# Default busy-timeout for testrunner.
86set R(nJob)    [default_njob]     ;# Default number of helper processes
87set R(leaker)  ""                 ;# Name of first script to leak memory
88
89set R(patternlist) [list]
90
91set testdir [file dirname $argv0]
92source $testdir/testset.tcl
93
94# Parse the command line options. There are two ways to invoke this
95# script - to create a helper or coordinator process. If there are
96# no helper processes, the coordinator runs test scripts.
97#
98# To create a helper process:
99#
100#    testrunner.tcl helper ID
101#
102# where ID is an integer greater than 0. The process will create and
103# run tests in the "testdir$ID" directory. Helper processes are only
104# created by coordinators - there is no need for a user to create
105# helper processes manually.
106#
107# If the first argument is anything other than "helper", then a coordinator
108# process is started. See the implementation of the [usage] proc above for
109# details.
110#
111switch -- [lindex $argv 0] {
112  helper {
113    set R(helper) 1
114    set R(helper_id) [lindex $argv 1]
115    set argv [list --testdir=testdir$R(helper_id)]
116  }
117
118  default {
119    set R(helper) 0
120    set R(helper_id) 0
121
122  }
123}
124if {$R(helper)==0} {
125  for {set ii 0} {$ii < [llength $argv]} {incr ii} {
126    set a [lindex $argv $ii]
127    set n [string length $a]
128
129    if {[string range $a 0 0]=="-"} {
130      if {($n>2 && [string match "$a*" --jobs]) || $a=="-j"} {
131        incr ii
132          set R(nJob) [lindex $argv $ii]
133      } else {
134        usage
135      }
136    } else {
137      lappend R(patternlist) [string map {% * _ .} $a]
138    }
139  }
140
141  set argv [list]
142}
143
144source $testdir/tester.tcl
145db close
146
147
148proc r_write_db {tcl} {
149  global R
150  sqlite3 db $R(dbname)
151  db timeout $R(timeout)
152  db eval { BEGIN EXCLUSIVE }
153
154  uplevel $tcl
155
156  db eval { COMMIT }
157  db close
158}
159
160proc make_new_testset {} {
161  global R
162
163  set scripts [testset_patternlist $R(patternlist)]
164  r_write_db {
165    db eval $R(schema)
166    foreach s $scripts {
167      db eval { INSERT INTO script(filename, state) VALUES ($s, 'ready') }
168    }
169  }
170}
171
172proc get_next_test {} {
173  global R
174  set myid $R(helper_id)
175
176  r_write_db {
177    set f [db one {
178      SELECT filename FROM script WHERE state='ready' ORDER BY 1 LIMIT 1
179    }]
180    if {$f!=""} {
181      db eval {
182        UPDATE script SET state='running', testfixtureid=$myid WHERE filename=$f
183      }
184    }
185  }
186
187  return $f
188}
189
190proc r_set_test_result {filename ms nerr ntest output} {
191  global R
192
193  set f [file tail $filename]
194  if {$nerr==0} {
195    set msg "$f... Ok"
196  } else {
197    set msg "$f... FAILED - $nerr errors of $ntest tests"
198  }
199  append msg " (${ms}ms)"
200  if {$R(helper)} {
201    append msg " (helper $R(helper_id))"
202  }
203
204  sqlite3_shutdown
205  set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1]
206  set nByte   [sqlite3_memory_used]
207  if {($nByte>0 || $nMalloc>0) && $R(leaker)==""} {
208    set R(leaker) $filename
209  }
210
211  r_write_db {
212    db eval {
213      UPDATE script
214        SET state='done', output=$output, nerr=$nerr, ntest=$ntest, time=$ms
215      WHERE filename=$filename;
216
217      INSERT INTO msg(msg) VALUES ($msg);
218    }
219  }
220}
221
222set R(iNextMsg) 1
223proc r_get_messages {{db ""}} {
224  global R
225
226  if {$db==""} {
227    sqlite3 rgmhandle $R(dbname)
228    set dbhandle rgmhandle
229    $dbhandle timeout $R(timeout)
230  } else {
231    set dbhandle $db
232  }
233
234  $dbhandle transaction {
235    set next $R(iNextMsg)
236    set ret [$dbhandle eval {SELECT msg FROM msg WHERE id>=$next}]
237    set R(iNextMsg) [$dbhandle one {SELECT COALESCE(max(id), 0)+1 FROM msg}]
238  }
239
240  if {$db==""} {
241    rgmhandle close
242  }
243
244  set ret
245}
246
247# This is called after all tests have been run to write the leaked memory
248# report into the malloc table of testrunner.db.
249#
250proc r_memory_report {} {
251  global R
252
253  sqlite3_shutdown
254
255  set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1]
256  set nByte   [sqlite3_memory_used]
257  set id $R(helper_id)
258  set leaker $R(leaker)
259
260  r_write_db {
261    db eval {
262      INSERT INTO malloc(id, nMalloc, nByte, leaker)
263        VALUES($id, $nMalloc, $nByte, $leaker)
264    }
265  }
266}
267
268
269#--------------------------------------------------------------------------
270#
271set ::R_INSTALL_PUTS_WRAPPER {
272  proc puts_sts_wrapper {args} {
273    set n [llength $args]
274    if {$n==1 || ($n==2 && [string first [lindex $args 0] -nonewline]==0)} {
275      uplevel puts_into_caller $args
276    } else {
277      # A channel was explicitly specified.
278      uplevel puts_sts_original $args
279    }
280  }
281  rename puts puts_sts_original
282  proc puts {args} { uplevel puts_sts_wrapper $args }
283}
284
285proc r_install_puts_wrapper {} $::R_INSTALL_PUTS_WRAPPER
286proc r_uninstall_puts_wrapper {} {
287  rename puts ""
288  rename puts_sts_original puts
289}
290
291proc slave_test_script {script} {
292
293  # Create the interpreter used to run the test script.
294  interp create tinterp
295
296  # Populate some global variables that tester.tcl expects to see.
297  foreach {var value} [list              \
298    ::argv0 $::argv0                     \
299    ::argv  {}                           \
300    ::SLAVE 1                            \
301  ] {
302    interp eval tinterp [list set $var $value]
303  }
304
305  # The alias used to access the global test counters.
306  tinterp alias set_test_counter set_test_counter
307
308  # Set up an empty ::cmdlinearg array in the slave.
309  interp eval tinterp [list array set ::cmdlinearg [array get ::cmdlinearg]]
310
311  # Set up the ::G array in the slave.
312  interp eval tinterp [list array set ::G [array get ::G]]
313  interp eval tinterp [list set ::G(runner.tcl) 1]
314
315  interp eval tinterp $::R_INSTALL_PUTS_WRAPPER
316  tinterp alias puts_into_caller puts_into_caller
317
318  # Load the various test interfaces implemented in C.
319  load_testfixture_extensions tinterp
320
321  # Run the test script.
322  set rc [catch { interp eval tinterp $script } msg opt]
323  if {$rc} {
324    puts_into_caller $msg
325    puts_into_caller [dict get $opt -errorinfo]
326    incr ::TC(errors)
327  }
328
329  # Check if the interpreter call [run_thread_tests]
330  if { [interp eval tinterp {info exists ::run_thread_tests_called}] } {
331    set ::run_thread_tests_called 1
332  }
333
334  # Delete the interpreter used to run the test script.
335  interp delete tinterp
336}
337
338proc slave_test_file {zFile} {
339  set tail [file tail $zFile]
340
341  # Remember the value of the shared-cache setting. So that it is possible
342  # to check afterwards that it was not modified by the test script.
343  #
344  ifcapable shared_cache { set scs [sqlite3_enable_shared_cache] }
345
346  # Run the test script in a slave interpreter.
347  #
348  unset -nocomplain ::run_thread_tests_called
349  reset_prng_state
350  set ::sqlite_open_file_count 0
351  set time [time { slave_test_script [list source $zFile] }]
352  set ms [expr [lindex $time 0] / 1000]
353
354  r_install_puts_wrapper
355
356  # Test that all files opened by the test script were closed. Omit this
357  # if the test script has "thread" in its name. The open file counter
358  # is not thread-safe.
359  #
360  if {[info exists ::run_thread_tests_called]==0} {
361    do_test ${tail}-closeallfiles { expr {$::sqlite_open_file_count>0} } {0}
362  }
363  set ::sqlite_open_file_count 0
364
365  # Test that the global "shared-cache" setting was not altered by
366  # the test script.
367  #
368  ifcapable shared_cache {
369    set res [expr {[sqlite3_enable_shared_cache] == $scs}]
370    do_test ${tail}-sharedcachesetting [list set {} $res] 1
371  }
372
373  # Add some info to the output.
374  #
375  output2 "Time: $tail $ms ms"
376  show_memstats
377
378  r_uninstall_puts_wrapper
379  return $ms
380}
381
382proc puts_into_caller {args} {
383  global R
384  if {[llength $args]==1} {
385    append R(output) [lindex $args 0]
386    append R(output) "\n"
387  } else {
388    append R(output) [lindex $args 1]
389  }
390}
391
392#-------------------------------------------------------------------------
393#
394proc r_final_report {} {
395  global R
396
397  sqlite3 db $R(dbname)
398  db timeout $R(timeout)
399
400  set errcode 0
401
402  # Create the text log file. This is just the concatenation of the
403  # 'output' column of the database for every script that was run.
404  set fd [open $R(logname) w]
405  db eval {SELECT output FROM script ORDER BY filename} {
406    puts $fd $output
407  }
408  close $fd
409
410  # Check if any scripts reported errors. If so, print one line noting
411  # how many errors, and another identifying the scripts in which they
412  # occured. Or, if no errors occurred, print out "no errors at all!".
413  sqlite3 db $R(dbname)
414  db timeout $R(timeout)
415  db eval { SELECT sum(nerr) AS nerr, sum(ntest) AS ntest FROM script } { }
416  puts "$nerr errors from $ntest tests."
417  if {$nerr>0} {
418    db eval { SELECT filename FROM script WHERE nerr>0 } {
419      lappend errlist [file tail $filename]
420    }
421    puts "Errors in: $errlist"
422    set errcode 1
423  }
424
425  # Check if any scripts were not run or did not finish. Print out a
426  # line identifying them if there are any.
427  set errlist [list]
428  db eval { SELECT filename FROM script WHERE state!='done' } {
429    lappend errlist [file tail $filename]
430  }
431  if {$errlist!=[list]} {
432    puts "Tests DID NOT FINISH (crashed?): $errlist"
433    set errcode 1
434  }
435
436  set bLeak 0
437  db eval {
438    SELECT id, nmalloc, nbyte, leaker FROM malloc
439      WHERE nmalloc>0 OR nbyte>0
440  } {
441    if {$id==0} {
442      set line "This process "
443    } else {
444      set line "Helper $id "
445    }
446    append line "leaked $nbyte byte in $nmalloc allocations"
447    if {$leaker!=""} { append line " (perhaps in [file tail $leaker])" }
448    puts $line
449    set bLeak 1
450  }
451  if {$bLeak==0} {
452    puts "No leaks - all allocations freed."
453  }
454
455  db close
456
457  puts "Test database is $R(dbname)"
458  puts "Test log file is $R(logname)"
459  if {$errcode} {
460    puts "This test has FAILED."
461  }
462  return $errcode
463}
464
465
466if {$R(helper)==0} {
467  make_new_testset
468}
469
470set R(nHelperRunning) 0
471if {$R(helper)==0 && $R(nJob)>1} {
472  cd $cmdlinearg(TESTFIXTURE_HOME)
473  for {set ii 1} {$ii <= $R(nJob)} {incr ii} {
474    set cmd "[info nameofexec] $R(info_script) helper $ii 2>@1"
475    puts "Launching helper $ii ($cmd)"
476    set chan [open "|$cmd" r]
477    fconfigure $chan -blocking false
478    fileevent $chan readable [list r_helper_readable $ii $chan]
479    incr R(nHelperRunning)
480  }
481  cd $cmdlinearg(testdir)
482}
483
484proc r_helper_readable {id chan} {
485  set data [gets $chan]
486  if {$data!=""} { puts "helper $id:[gets $chan]" }
487  if {[eof $chan]} {
488    puts "helper $id is finished"
489    incr ::R(nHelperRunning) -1
490    close $chan
491  }
492}
493
494if {$R(nHelperRunning)==0} {
495  while { ""!=[set f [get_next_test]] } {
496    set R(output) ""
497    set TC(count) 0
498    set TC(errors) 0
499    set ms [slave_test_file $f]
500
501    r_set_test_result $f $ms $TC(errors) $TC(count) $R(output)
502
503    if {$R(helper)==0} {
504      foreach msg [r_get_messages] { puts $msg }
505    }
506  }
507
508  # Tests are finished - write a record into testrunner.db describing
509  # any memory leaks.
510  r_memory_report
511
512} else {
513  set TTT 0
514  sqlite3 db $R(dbname)
515  db timeout $R(timeout)
516  while {$R(nHelperRunning)>0} {
517    after 250 { incr TTT }
518    vwait TTT
519    foreach msg [r_get_messages db] { puts $msg }
520  }
521  db close
522}
523
524set errcode 0
525if {$R(helper)==0} {
526  set errcode [r_final_report]
527}
528
529exit $errcode
530
531