]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Fix testrunner.tcl so that it checks for memory leaks.
authordan <Dan Kennedy>
Wed, 13 Jul 2022 17:46:42 +0000 (17:46 +0000)
committerdan <Dan Kennedy>
Wed, 13 Jul 2022 17:46:42 +0000 (17:46 +0000)
FossilOrigin-Name: 106f6724d54ccec3edf8c9a0422b89c4f227adb26021ed6f0fc91392ef4b3fc5

manifest
manifest.uuid
test/testrunner.tcl

index 6bf93332c7a5b37fbc30e96bce1e7b1beef51594..0bd8756440bf1138a906c2f037d44fbf4ab829fc 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\stest/testrunner.tcl,\san\sexperimental\sscript\sfor\sdistributing\sthe\swork\sof\sveryquick.test\sbetween\smultiple\sprocesses.
-D 2022-07-12T20:31:16.301
+C Fix\stestrunner.tcl\sso\sthat\sit\schecks\sfor\smemory\sleaks.
+D 2022-07-13T17:46:42.435
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -1507,7 +1507,7 @@ F test/temptable2.test d2940417496e2b9548e01d09990763fbe88c316504033256d51493e1f
 F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
 F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
 F test/tester.tcl 76771269dcc20b2c2d1d6f1175dd50d1eebddc004aebac865483f1829a5cd398
-F test/testrunner.tcl bfaaddd58df6176af83159e3b27767c53abd87db68619a300234ad7fbf9aeed1
+F test/testrunner.tcl 3bdd2d32319c65f34d1aafe6fe66aac4881e78ded4880719f48203aeea13b1c4
 F test/thread001.test b61a29dd87cf669f5f6ac96124a7c97d71b0c80d9012746072055877055cf9ef
 F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
 F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
@@ -1980,11 +1980,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 6d0f677291d2b5ec68c86292da240c5557422aae1290c0844223974449ce539b
-R 8212f2952574d93e95a3664f57ee334d
-T *branch * testrunner
-T *sym-testrunner *
-T -sym-trunk *
+P ef229cbb7ffbeb8c8877dff70e9d6d43050d2297dee582a37df3a0caaebd2a41
+R 9ae28163af03b5806c7e690857875476
 U dan
-Z a723c28ab5e45345241e356c4c65467f
+Z 14e1b9ab4edb92095656b55f758ae601
 # Remove this line to create a well-formed Fossil manifest.
index 0e8e50979bb0eda830662fb1f741814b82f6b90a..38cc6028ad92d34fda6bbd51c3fa01593f11d5d7 100644 (file)
@@ -1 +1 @@
-ef229cbb7ffbeb8c8877dff70e9d6d43050d2297dee582a37df3a0caaebd2a41
\ No newline at end of file
+106f6724d54ccec3edf8c9a0422b89c4f227adb26021ed6f0fc91392ef4b3fc5
\ No newline at end of file
index 7ebbbb712f5f595eb95b3ee381728f0b0d3a2446..ce99a3a09f316983a94e86b51ec647d9ed796163 100644 (file)
@@ -12,7 +12,90 @@ proc usage {} {
 }
 #-------------------------------------------------------------------------
 
+#-------------------------------------------------------------------------
+# The database schema used by the testrunner.db database.
+#
+set R(schema) {
+  DROP TABLE IF EXISTS script;
+  DROP TABLE IF EXISTS msg;
+  DROP TABLE IF EXISTS malloc;
+
+  CREATE TABLE script(
+    filename TEXT PRIMARY KEY,    -- full path to test script
+    state TEXT CHECK( state IN ('ready', 'running', 'done') ),
+    testfixtureid,                -- Id of process that ran script
+    time INTEGER,                 -- Time in ms
+    nerr INTEGER,                 -- if 'done', the number of errors
+    ntest INTEGER,                -- if 'done', the number of tests
+    output TEXT                   -- full output of test script
+  );
+
+  CREATE TABLE malloc(
+    id INTEGER PRIMARY KEY,
+    nmalloc INTEGER,
+    nbyte INTEGER,
+    leaker TEXT
+  );
+
+  CREATE TABLE msg(
+    id INTEGER PRIMARY KEY,
+    msg TEXT
+  );
+}
+#-------------------------------------------------------------------------
+
+#-------------------------------------------------------------------------
+# Try to estimate a the number of processes to use.
+#
+# Command [guess_number_of_cores] attempts to glean the number of logical
+# cores. Command [default_njob] returns the default value for the --jobs
+# switch.
+#
+proc guess_number_of_cores {} {
+  set ret 4
+  catch {
+    set fd [open "|nproc" r]
+    set ret [gets $fd]
+    close $fd
+    set ret [expr $ret]
+  }
+  return $ret
+}
+
+proc default_njob {} {
+  set nCore [guess_number_of_cores]
+  set nHelper [expr int($nCore*0.75)]
+  expr $nHelper>0 ? $nHelper : 1
+}
+#-------------------------------------------------------------------------
+
 
+
+set R(dbname) [file normalize testrunner.db]
+set R(logname) [file normalize testrunner.log]
+set R(info_script) [file normalize [info script]]
+set R(timeout) 10000              ;# Default busy-timeout for testrunner.
+set R(nJob)    [default_njob]     ;# Default number of helper processes
+set R(leaker)  ""                 ;# Name of first script to leak memory
+
+
+# Parse the command line options. There are two ways to invoke this
+# script - to create a helper or coordinator process. If there are
+# no helper processes, the coordinator runs test scripts.
+#
+# To create a helper process:
+#
+#    testrunner.tcl helper ID
+#
+# where ID is an integer greater than 0. The process will create and
+# run tests in the "testdir$ID" directory. Helper processes are only
+# created by coordinators - there is no need for a user to create
+# helper processes manually.
+#
+# If the first argument is anything other than "helper", then a coordinator
+# process is started. See the implementation of the [usage] proc above for
+# details.
+#
 switch -- [lindex $argv 0] {
   helper {
     set R(helper) 1
@@ -23,16 +106,9 @@ switch -- [lindex $argv 0] {
   default {
     set R(helper) 0
     set R(helper_id) 0
+
   }
 }
-
-
-set R(dbname) [file normalize testrunner.db]
-set R(logname) [file normalize testrunner.log]
-set R(timeout) 10000
-set R(nHelper) 4
-set R(info_script) [file normalize [info script]]
-
 if {$R(helper)==0} {
   for {set ii 0} {$ii < [llength $argv]} {incr ii} {
     set a [lindex $argv $ii]
@@ -40,7 +116,7 @@ if {$R(helper)==0} {
 
     if {($n>2 && [string match "$a*" --jobs]) || $a=="-j"} {
       incr ii
-      set R(nHelper) [lindex $argv $ii]
+      set R(nJob) [lindex $argv $ii]
     } else {
       usage
     }
@@ -53,28 +129,6 @@ set testdir [file dirname $argv0]
 source $testdir/tester.tcl
 db close
 
-# The database schema used by the testset database.
-#
-set R(schema) {
-  DROP TABLE IF EXISTS script;
-  DROP TABLE IF EXISTS msg;
-
-  CREATE TABLE script(
-    filename TEXT PRIMARY KEY,    -- full path to test script
-    state TEXT CHECK( state IN ('ready', 'running', 'done') ),
-    testfixtureid,                -- Id of process that ran script
-    nerr INTEGER,                 -- if 'done', the number of errors
-    ntest INTEGER,                -- if 'done', the number of tests
-    output TEXT                   -- full output of test script
-  );
-
-  CREATE TABLE msg(
-    id INTEGER PRIMARY KEY,
-    msg TEXT
-  );
-}
-
-
 #--------------------------------------------------------------------
 # This is temporary!
 # 
@@ -109,6 +163,7 @@ proc all_veryquick_scripts {} {
   memleak.test    permutations.test  soak.test   fts3.test
   mallocAll.test  rtree.test         full.test   extraquick.test
   session.test    rbu.test
+
   }
 
   set testdir [file normalize $::testdir]
@@ -131,68 +186,48 @@ proc all_veryquick_scripts {} {
 
   set ret
 }
-#proc all_veryquick_scripts {} {
-#  set testdir [file normalize $::testdir]
-#  glob -nocomplain $testdir/select*.test
-#}
 #--------------------------------------------------------------------
 
 
-proc make_new_testset {} {
+proc r_write_db {tcl} {
   global R
-
   sqlite3 db $R(dbname)
-  db eval $R(schema)
-  foreach s [all_veryquick_scripts] {
-    db eval { INSERT INTO script(filename, state) VALUES ($s, 'ready') }
-  }
-
-  # db eval { SELECT filename FROM Script ORDER BY 1 } { puts $filename }
-  # exit
+  db timeout $R(timeout)
+  db eval { BEGIN EXCLUSIVE }
 
+  uplevel $tcl
 
+  db eval { COMMIT }
   db close
 }
 
-proc get_next_test {} {
+proc make_new_testset {} {
   global R
-  set myid $R(helper_id)
 
-  sqlite3 db $R(dbname)
-  db timeout $R(timeout)
-  db eval { BEGIN EXCLUSIVE }
-  set f [db one { 
-    SELECT filename FROM script WHERE state='ready' ORDER BY 1 LIMIT 1 
-  }]
-  if {$f!=""} {
-    db eval { 
-      UPDATE script SET state='running', testfixtureid=$myid WHERE filename=$f 
+  r_write_db {
+    db eval $R(schema)
+    foreach s [all_veryquick_scripts] {
+      db eval { INSERT INTO script(filename, state) VALUES ($s, 'ready') }
     }
   }
-  db eval { COMMIT }
-  db close
-
-  return $f
 }
 
-proc r_write_db {tcl} {
+proc get_next_test {} {
   global R
-  sqlite3 db $R(dbname)
-  db timeout $R(timeout)
-  db eval { BEGIN EXCLUSIVE }
-  uplevel $tcl
-  db eval { COMMIT }
-  db close
-}
+  set myid $R(helper_id)
 
-proc r_read_db {tcl} {
-  global R
-  sqlite3 db $R(dbname)
-  db timeout $R(timeout)
-  db eval { BEGIN }
-  uplevel $tcl
-  db eval { COMMIT }
-  db close
+  r_write_db {
+    set f [db one { 
+      SELECT filename FROM script WHERE state='ready' ORDER BY 1 LIMIT 1 
+    }]
+    if {$f!=""} {
+      db eval { 
+        UPDATE script SET state='running', testfixtureid=$myid WHERE filename=$f
+      }
+    }
+  }
+
+  return $f
 }
 
 proc r_set_test_result {filename ms nerr ntest output} {
@@ -209,10 +244,17 @@ proc r_set_test_result {filename ms nerr ntest output} {
     append msg " (helper $R(helper_id))"
   }
 
+  sqlite3_shutdown
+  set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1]
+  set nByte   [sqlite3_memory_used]
+  if {($nByte>0 || $nMalloc>0) && $R(leaker)==""} {
+    set R(leaker) $filename
+  }
+
   r_write_db {
     db eval {
       UPDATE script 
-        SET state='done', output=$output, nerr=$nerr, ntest=$ntest
+        SET state='done', output=$output, nerr=$nerr, ntest=$ntest, time=$ms
       WHERE filename=$filename;
 
       INSERT INTO msg(msg) VALUES ($msg);
@@ -245,10 +287,30 @@ proc r_get_messages {{db ""}} {
   set ret
 }
 
-#--------------------------------------------------------------------------
+# This is called after all tests have been run to write the leaked memory
+# report into the malloc table of testrunner.db.
 #
+proc r_memory_report {} {
+  global R
 
+  sqlite3_shutdown
 
+  set nMalloc [lindex [sqlite3_status SQLITE_STATUS_MALLOC_COUNT 0] 1]
+  set nByte   [sqlite3_memory_used]
+  set id $R(helper_id)
+  set leaker $R(leaker)
+
+  r_write_db {
+    db eval {
+      INSERT INTO malloc(id, nMalloc, nByte, leaker) 
+        VALUES($id, $nMalloc, $nByte, $leaker)
+    }
+  }
+}
+
+
+#--------------------------------------------------------------------------
+#
 set ::R_INSTALL_PUTS_WRAPPER {
   proc puts_sts_wrapper {args} {
     set n [llength $args]
@@ -370,11 +432,88 @@ proc puts_into_caller {args} {
   }
 }
 
-set R(nHelperRunning) 0
+#-------------------------------------------------------------------------
+#
+proc r_final_report {} {
+  global R
+
+  sqlite3 db $R(dbname)
+  db timeout $R(timeout)
+
+  set errcode 0
+
+  # Create the text log file. This is just the concatenation of the 
+  # 'output' column of the database for every script that was run.
+  set fd [open $R(logname) w]
+  db eval {SELECT output FROM script ORDER BY filename} {
+    puts $fd $output
+  }
+  close $fd
+
+  # Check if any scripts reported errors. If so, print one line noting
+  # how many errors, and another identifying the scripts in which they
+  # occured. Or, if no errors occurred, print out "no errors at all!".
+  sqlite3 db $R(dbname)
+  db timeout $R(timeout)
+  db eval { SELECT sum(nerr) AS nerr, sum(ntest) AS ntest FROM script } { }
+  puts "$nerr errors from $ntest tests."
+  if {$nerr>0} {
+    db eval { SELECT filename FROM script WHERE nerr>0 } {
+      lappend errlist [file tail $filename]
+    }
+    puts "Errors in: $errlist"
+    set errcode 1
+  }
+
+  # Check if any scripts were not run or did not finish. Print out a
+  # line identifying them if there are any. 
+  set errlist [list]
+  db eval { SELECT filename FROM script WHERE state!='done' } {
+    lappend errlist [file tail $filename]
+  }
+  if {$errlist!=[list]} {
+    puts "Tests DID NOT FINISH (crashed?): $errlist"
+    set errcode 1
+  }
+
+  set bLeak 0
+  db eval {
+    SELECT id, nmalloc, nbyte, leaker FROM malloc 
+      WHERE nmalloc>0 OR nbyte>0
+  } {
+    if {$id==0} { 
+      set line "This process " 
+    } else {
+      set line "Helper $id "
+    }
+    append line "leaked $nbyte byte in $nmalloc allocations"
+    if {$leaker!=""} { append line " (perhaps in [file tail $leaker])" }
+    puts $line
+    set bLeak 1
+  }
+  if {$bLeak==0} {
+    puts "No leaks - all allocations freed."
+  }
+
+  db close
+
+  puts "Test database is $R(dbname)"
+  puts "Test log file is $R(logname)"
+  if {$errcode} {
+    puts "This test has FAILED."
+  }
+  return $errcode
+}
+
+
 if {$R(helper)==0} {
-  cd $cmdlinearg(TESTFIXTURE_HOME)
   make_new_testset
-  for {set ii 1} {$ii <= $R(nHelper)} {incr ii} {
+}
+
+set R(nHelperRunning) 0
+if {$R(helper)==0 && $R(nJob)>1} {
+  cd $cmdlinearg(TESTFIXTURE_HOME)
+  for {set ii 1} {$ii <= $R(nJob)} {incr ii} {
     set cmd "[info nameofexec] $R(info_script) helper $ii 2>@1"
     puts "Launching helper $ii ($cmd)"
     set chan [open "|$cmd" r]
@@ -386,70 +525,48 @@ if {$R(helper)==0} {
 }
 
 proc r_helper_readable {id chan} {
-  puts "helper $id:[gets $chan]"
+  set data [gets $chan]
+  if {$data!=""} { puts "helper $id:[gets $chan]" }
   if {[eof $chan]} {
-    puts "helper $id is FINISHED"
+    puts "helper $id is finished"
     incr ::R(nHelperRunning) -1
     close $chan
   }
 }
 
-if {$R(helper) || $R(nHelper)<4} {
+if {$R(nHelperRunning)==0} {
   while { ""!=[set f [get_next_test]] } {
     set R(output) ""
     set TC(count) 0
     set TC(errors) 0
     set ms [slave_test_file $f]
+
     r_set_test_result $f $ms $TC(errors) $TC(count) $R(output)
   
     if {$R(helper)==0} {
       foreach msg [r_get_messages] { puts $msg }
     }
   }
-}
 
-set TTT 0
-sqlite3 db $R(dbname)
-db timeout $R(timeout)
-while {$R(nHelperRunning)>0} {
-  after 250 { incr TTT }
-  vwait TTT
-  foreach msg [r_get_messages db] { puts $msg }
-}
-db close
+  # Tests are finished - write a record into testrunner.db describing 
+  # any memory leaks. 
+  r_memory_report
 
-set errcode 0
-if {$R(helper)==0} {
+} else {
+  set TTT 0
   sqlite3 db $R(dbname)
   db timeout $R(timeout)
-  db eval { SELECT sum(nerr) AS nerr, sum(ntest) AS ntest FROM script } {
-    puts "$nerr errors from $ntest tests."
+  while {$R(nHelperRunning)>0} {
+    after 250 { incr TTT }
+    vwait TTT
+    foreach msg [r_get_messages db] { puts $msg }
   }
-  if {$nerr>0} {
-    db eval { SELECT filename FROM script WHERE nerr>0 } {
-      lappend errlist [file tail $filename]
-    }
-    puts "Errors in: $errlist"
-    set errcode 1
-  }
-
-  set errlist [list]
-  db eval { SELECT filename FROM script WHERE state!='done' } {
-    lappend errlist [file tail $filename]
-  }
-  if {$errlist!=[list]} {
-    puts "Tests DID NOT FINISH (crashed?): $errlist"
-    set errcode 1
-  }
-
-  set fd [open $R(logname) w]
-  db eval {SELECT output FROM script ORDER BY filename} {
-    puts $fd $output
-  }
-  close $fd
+  db close
+}
 
-  puts "Test database is $R(dbname)"
-  puts "Test log file is $R(logname)"
+set errcode 0
+if {$R(helper)==0} {
+  set errcode [r_final_report]
 }
 
 exit $errcode