System.Data.SQLite

Artifact [31bfc2a4c4]
Login

Artifact 31bfc2a4c4a1cc7d074e140aa1492ce675c339fd:


###############################################################################
#
# stress.eagle --
#
# Written by Joe Mistachkin.
# Released to the public domain, use at your own risk!
#
###############################################################################

package require Eagle
package require Eagle.Library
package require Eagle.Test

runTestPrologue

###############################################################################

package require System.Data.SQLite.Test
runSQLiteTestPrologue

###############################################################################

#
# NOTE: Make sure that SQLite core library is completely shutdown prior to
#       starting any of the tests in this file.
#
if {[haveConstraint SQLite]} then {
  object invoke -flags +NonPublic System.Data.SQLite.UnsafeNativeMethods \
      sqlite3_shutdown
}

###############################################################################

runTest {test stress-1.1 {multithreaded stress testing} -setup {
  unset -nocomplain result thread index workload noWorkload srcDb db \
      fileName compiled options count times logFileName logListener \
      connection

  #############################################################################

  proc expectedError { error } {
    return [expr {[regexp -- {\sno such table: t1\s} $error] || \
        [regexp -- {\sdatabase is locked\s} $error]}]
  }

  #############################################################################

  proc showTest { indicator } {
    tputs $::test_channel $indicator
    after [expr {int(rand() * 1000 + $::count(2))}]
  }

  #############################################################################

  proc failTest { error } {
    set level [expr {[info level] - 1}]

    tputs $::test_channel [appendArgs \
        \n [info level $level] ": " $error \n]

    exit Failure; # halt all testing now.
  }

  #############################################################################

  proc setupLogging { fileName } {
    if {![info exists ::logListener]} then {
      set ::logListener [object create -alias \
          System.Diagnostics.TextWriterTraceListener $fileName]
    }

    object invoke System.Diagnostics.Trace.Listeners Add $::logListener
    object invoke System.Data.SQLite.SQLiteLog Initialize

    tputs $::test_channel [appendArgs \
        "---- enabled SQLite trace logging to file \"" $fileName \"\n]
  }

  #############################################################################

  proc cleanupLogging { fileName } {
    if {[info exists ::logListener]} then {
      object invoke System.Diagnostics.Trace.Listeners Remove $::logListener
      $::logListener Close
    }

    #
    # NOTE: Copy the trace listener log file to the main test log file.
    #
    tlog "---- BEGIN TRACE LISTENER OUTPUT\n"
    tlog [readFile $fileName]
    tlog "\n---- END TRACE LISTENER OUTPUT\n"

    #
    # NOTE: Delete the trace listener log file because its contents have
    #       been copied to the main test log file.
    #
    cleanupFile $fileName

    tputs $::test_channel [appendArgs \
        "---- disabled SQLite trace logging to file \"" $fileName \"\n]
  }

  #############################################################################

  #
  # NOTE: The trace listener used with the SQLiteLog class to capture output
  #       from the core SQLite library requires its own log file because the
  #       TextWriterTraceListener class opens and locks the log file it uses
  #       as the basis of the output stream.  Before this test is complete,
  #       the entire contents of this trace log file will be copied into the
  #       main test log file and then deleted.
  #
  set logFileName [appendArgs $test_log .trace]
  setupLogging $logFileName

  #############################################################################

  set count(0) 1
  set count(1) 5
  set count(2) 300
  set count(3) 57
  set count(4) 10000000
  set noWorkload [list]

  if {[info exists argv] && [llength $argv] > 0} then {
    parse options -flags \
        {-StopOnUnknownOption +IgnoreOnUnknownOption SkipOnUnknownOption} -- \
        [list [list null MustHaveIntegerValue -1 -1 -count0 $count(0)] \
        [list null MustHaveIntegerValue -1 -1 -count1 $count(1)] \
        [list null MustHaveIntegerValue -1 -1 -count2 $count(2)] \
        [list null MustHaveIntegerValue -1 -1 -count3 $count(3)] \
        [list null MustHaveIntegerValue -1 -1 -count4 $count(4)] \
        [list null MustHaveListValue -1 -1 -noWorkload $noWorkload]] $argv

    set count(0) $options(-count0,value)
    set count(1) $options(-count1,value)
    set count(2) $options(-count2,value)
    set count(3) $options(-count3,value)
    set count(4) $options(-count4,value)
    set noWorkload $options(-noWorkload,value)
  }

  #############################################################################

  tputs $test_channel [appendArgs \
      "---- workloads will repeat " $count(0) " time(s)...\n"]

  tputs $test_channel [appendArgs \
      "---- workloads will have " $count(1) " iteration(s)...\n"]

  tputs $test_channel [appendArgs \
      "---- workloads will wait at least " $count(2) " millisecond(s)...\n"]

  tputs $test_channel [appendArgs \
      "---- workload \"small\" chunk size is " $count(3) " byte(s)...\n"]

  tputs $test_channel [appendArgs \
      "---- workload \"big\" chunk size is " $count(4) " byte(s)...\n"]

  if {[llength $noWorkload] > 0} then {
    tputs $test_channel [appendArgs \
        "---- workloads to be skipped... " $noWorkload \n]
  }

  #############################################################################

  set compiled(12) ""
  set compiled(13) ""

  #############################################################################

  #
  # NOTE: This is an in-memory database with shared cache enabled.
  #
  set fileName(1) "file::memory:?cache=shared"

  #############################################################################

  #
  # NOTE: This is a normal on-disk database.
  #
  set fileName(2) [file join [getDatabaseDirectory] [appendArgs \
      stress- [pid] - [string trim [clock seconds] -] .db]]

  #############################################################################

  #
  # NOTE: Make sure to remove any stale database from previous test runs.
  #
  cleanupFile $fileName(2)

  #############################################################################

  setupDb $fileName(1) "" "" "" "" "" false false true true srcDb
  setupDb $fileName(2)

  #############################################################################

  set connection [getDbConnection]

  $connection LogMessage Ok [appendArgs \
      "starting stress test using database \"" $fileName(2) "\"..."]

  #############################################################################

  set workload(1) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #1, CREATE TABLE statements.
    #
    lappend ::times(1) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 2} {$index <= $count} {incr index} {
        if {[catch {
          sql execute $db [appendArgs \
              "CREATE TABLE IF NOT EXISTS t" $index "(x PRIMARY KEY, y, z);"]
          showTest A
        } error]} then {
          if {[expectedError $error]} then {
            showTest a
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(2) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #2, DROP TABLE statements.
    #
    lappend ::times(2) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 2} {$index <= $count} {incr index} {
        if {[catch {
          sql execute $db [appendArgs \
              "DROP TABLE IF EXISTS t" $index \;]
          showTest B
        } error]} then {
          if {[expectedError $error]} then {
            showTest b
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(3) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #3, "small" SELECT statements.
    #
    lappend ::times(3) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          sql execute -execute reader $db [appendArgs \
              "SELECT x, y FROM " $table " WHERE z = 'small';"]
          showTest C
        } error]} then {
          if {[expectedError $error]} then {
            showTest c
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(4) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #4, "big" SELECT statements.
    #
    lappend ::times(4) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          sql execute -execute reader $db [appendArgs \
              "SELECT x, y FROM " $table " WHERE z = 'big';"]
          showTest D
        } error]} then {
          if {[expectedError $error]} then {
            showTest d
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(5) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #5, "small" INSERT statements.
    #
    lappend ::times(5) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          sql execute $db [appendArgs \
              "INSERT INTO " $table "(x, y, z) VALUES('" \
              [format %lX [expr {random()}]] "', '" \
              [base64 encode -- [expr {randstr($::count(3))}]] \
              "', 'small');"]
          showTest E
        } error]} then {
          if {[expectedError $error]} then {
            showTest e
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(6) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #6, "big" INSERT statements.
    #
    lappend ::times(6) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          sql execute $db [appendArgs \
              "INSERT INTO " $table "(x, y, z) VALUES('" \
              [format %lX [expr {random()}]] \
              "', RANDOMBLOB(" $::count(4) "), 'big');"]
          showTest F
        } error]} then {
          if {[expectedError $error]} then {
            showTest f
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(7) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #7, "small" UPDATE statements.
    #
    lappend ::times(7) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          sql execute $db [appendArgs "UPDATE " $table \
              " SET y = '" [base64 encode -- [expr {randstr($::count(3))}]] \
              "' WHERE x LIKE '" [format %X $index] "%' AND z = 'small';"]
          showTest G
        } error]} then {
          if {[expectedError $error]} then {
            showTest g
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(8) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #8, "big" UPDATE statements.
    #
    lappend ::times(8) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          sql execute $db [appendArgs "UPDATE " $table \
              " SET y = RANDOMBLOB(" $::count(4) \
              ") WHERE x LIKE '" [format %X $index] \
              "%' AND z = 'big';"]
          showTest H
        } error]} then {
          if {[expectedError $error]} then {
            showTest h
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(9) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #9, "small" DELETE statements.
    #
    lappend ::times(9) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          sql execute $db [appendArgs "DELETE FROM " $table \
              " WHERE x LIKE '" [format %X $index] "%' AND z = 'small';"]
          showTest I
        } error]} then {
          if {[expectedError $error]} then {
            showTest i
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(10) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #10, "big" DELETE statements.
    #
    lappend ::times(10) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          sql execute $db [appendArgs "DELETE FROM " $table \
              " WHERE x LIKE '" [format %X $index] "%' AND z = 'big';"]
          showTest J
        } error]} then {
          if {[expectedError $error]} then {
            showTest j
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(11) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #11, VACUUM statement.
    #
    lappend ::times(11) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          sql execute $db "VACUUM;"
          showTest K
        } error]} then {
          if {[expectedError $error]} then {
            showTest k
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(12) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #12, backup to in-memory database.
    #
    lappend ::times(12) [lindex [time {
      for {set index 1} {$index <= $count} {incr index} {
        if {[string is integer -strict $::compiled(12)]} then {
          set id $::compiled(12); # NOTE: Already compiled.
          if {[catch {
            object invoke _Dynamic${id}.Test${id} BackupAndGetData
            showTest L
          } error]} then {
            if {[expectedError $error]} then {
              showTest l
            } else {
              failTest $error
            }
          }
        } else {
          set id [object invoke Interpreter.GetActive NextId]
          set code [compileCSharpWith [subst {
            using System;
            using System.Data.SQLite;
            using System.Text;

            namespace _Dynamic${id}
            {
              public static class Test${id}
              {
                public static void BackupAndGetData()
                {
                  using (SQLiteConnection source = new SQLiteConnection(
                      "FullUri=${dstFileName};"))
                  {
                    source.Open();
                    using (SQLiteConnection destination = new SQLiteConnection(
                        "FullUri=${srcFileName};"))
                    {
                      destination.Open();

                      source.BackupDatabase(
                          destination, "main", "main", -1, null, 0);
                    }
                  }
                }

                ///////////////////////////////////////////////////////////////

                public static void Main()
                {
                  // do nothing.
                }
              }
            }
          }] true true true results errors System.Data.SQLite.dll]
          if {$code eq "Ok"} then {
            set ::compiled(12) $id; # NOTE: Compiled OK.
            if {[catch {
              object invoke _Dynamic${id}.Test${id} BackupAndGetData
              showTest L
            } error]} then {
              if {[expectedError $error]} then {
                showTest l
              } else {
                failTest $error
              }
            }
          } else {
            error $errors
          }
        }
      }
    }] 0]
  }]

  #############################################################################

  set workload(13) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #13, backup from an in-memory database.
    #
    lappend ::times(13) [lindex [time {
      for {set index 1} {$index <= $count} {incr index} {
        if {[string is integer -strict $::compiled(13)]} then {
          set id $::compiled(13); # NOTE: Already compiled.
          if {[catch {
            object invoke _Dynamic${id}.Test${id} BackupAndGetData
            showTest M
          } error]} then {
            if {[expectedError $error]} then {
              showTest m
            } else {
              failTest $error
            }
          }
        } else {
          set id [object invoke Interpreter.GetActive NextId]
          set code [compileCSharpWith [subst {
            using System;
            using System.Data.SQLite;
            using System.Text;

            namespace _Dynamic${id}
            {
              public static class Test${id}
              {
                public static void BackupAndGetData()
                {
                  using (SQLiteConnection source = new SQLiteConnection(
                      "FullUri=${srcFileName};"))
                  {
                    source.Open();
                    using (SQLiteConnection destination = new SQLiteConnection(
                        "FullUri=${dstFileName};"))
                    {
                      destination.Open();

                      source.BackupDatabase(
                          destination, "main", "main", -1, null, 0);
                    }
                  }
                }

                ///////////////////////////////////////////////////////////////

                public static void Main()
                {
                  // do nothing.
                }
              }
            }
          }] true true true results errors System.Data.SQLite.dll]
          if {$code eq "Ok"} then {
            set ::compiled(13) $id; # NOTE: Compiled OK.
            if {[catch {
              object invoke _Dynamic${id}.Test${id} BackupAndGetData
              showTest M
            } error]} then {
              if {[expectedError $error]} then {
                showTest m
              } else {
                failTest $error
              }
            }
          } else {
            error $errors
          }
        }
      }
    }] 0]
  }]

  #############################################################################

  set workload(14) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #14, PRAGMA integrity check statement.
    #
    lappend ::times(14) [lindex [time {
      setupDb $dstFileName "" "" "" "" "" true false
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          set result [sql execute -execute scalar $db \
              "PRAGMA integrity_check;"]
          if {$result eq "ok"} then {
            showTest N
          } else {
            error [appendArgs "integrity check failed: " $result]
          }
        } error]} then {
          if {[expectedError $error]} then {
            showTest n
          } else {
            failTest $error
          }
        }
      }
      cleanupDb $dstFileName db false true false
    }] 0]
  }]

  #############################################################################

  set workload(15) [list [list srcFileName dstFileName table count] {
    #
    # NOTE: Workload #15, force managed garbage collection
    #
    lappend ::times(15) [lindex [time {
      for {set index 1} {$index <= $count} {incr index} {
        if {[catch {
          collectGarbage $::test_channel
          showTest O
        } error]} then {
          if {[expectedError $error]} then {
            showTest o
          } else {
            failTest $error
          }
        }
      }
    }] 0]
  }]
} -body {
  for {set index(0) 0} {$index(0) < $count(0)} {incr index(0)} {
    sql execute $db "CREATE TABLE IF NOT EXISTS t1(x PRIMARY KEY, y, z);"

    unset -nocomplain thread

    foreach index(1) [lsort -integer [array names workload]] {
      if {[lsearch -exact $noWorkload $index(1)] == -1} then {
        set thread($index(1)) [object create -alias System.Threading.Thread \
            [list apply $workload($index(1)) $fileName(1) $fileName(2) t1 \
            $count(1)] 1048576]
      }
    }

    foreach index(1) [list Start Join] {
      foreach index(2) [array names thread] {
        $thread($index(2)) $index(1)
      }
    }

    unset -nocomplain thread
  }

  tputs $test_channel \n

  foreach index(0) [lsort -integer [array names times]] {
    set times(length) [llength $times($index(0))]

    if {$times(length) > 0} then {
      tputs $test_channel [appendArgs \
          "---- average time for workload (" $index(0) ") is about " \
          [expr int(([join $times($index(0)) +])/$times(length)/1000.0)] \
          " milliseconds\n"]
    } else {
      tputs $test_channel [appendArgs \
          "---- no times for workload (" $index(0) ")\n"]
    }
  }

  set result [sql execute -execute scalar $srcDb "PRAGMA integrity_check;"]

  if {$result eq "ok"} then {
    tputs $test_channel "---- integrity check ok (srcDb)\n"
  } else {
    error [appendArgs "integrity check failed (srcDb): " $result]
  }

  set result [sql execute -execute scalar $db "PRAGMA integrity_check;"]

  if {$result eq "ok"} then {
    tputs $test_channel "---- integrity check ok (db)\n"
  } else {
    error [appendArgs "integrity check failed (db): " $result]
  }
} -cleanup {
  rename failTest ""
  rename showTest ""
  rename expectedError ""

  cleanupDb $fileName(2)
  cleanupDb $fileName(1) srcDb

  foreach index(0) [array names workload] {
    catch {
      object removecallback [list apply $workload($index(0)) $fileName(1) \
          $fileName(2) t1 $count(1)]
    }
  }

  freeDbConnection

  cleanupLogging $logFileName

  rename cleanupLogging ""
  rename setupLogging ""

  unset -nocomplain result thread index workload noWorkload srcDb db \
      fileName compiled options count times logFileName logListener \
      connection
} -constraints \
{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result {}}

###############################################################################

runSQLiteTestEpilogue
runTestEpilogue