2022-08-10 11:26:08 +00:00
/ *
2022 - 05 - 22
The author disclaims copyright to this source code . In place of a
legal notice , here is a blessing :
* May you do good and not evil .
* May you find forgiveness for yourself and forgive others .
* May you share freely , never taking more than you give .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2022-08-17 16:44:05 +00:00
A basic test script for sqlite3 - worker1 . js .
2022-09-27 13:40:12 +00:00
Note that the wrapper interface demonstrated in
2022-10-19 07:34:36 +00:00
demo - worker1 - promiser . js is much easier to use from client code , as it
2022-09-27 13:40:12 +00:00
lacks the message - passing acrobatics demonstrated in this file .
2022-08-10 11:26:08 +00:00
* /
'use strict' ;
( function ( ) {
const T = self . SqliteTestUtil ;
2022-10-19 04:44:58 +00:00
const SW = new Worker ( "jswasm/sqlite3-worker1.js" ) ;
2022-08-10 11:26:08 +00:00
const DbState = {
id : undefined
} ;
const eOutput = document . querySelector ( '#test-output' ) ;
2022-08-13 13:56:00 +00:00
const log = console . log . bind ( console ) ;
2022-08-10 11:26:08 +00:00
const logHtml = function ( cssClass , ... args ) {
log . apply ( this , args ) ;
const ln = document . createElement ( 'div' ) ;
if ( cssClass ) ln . classList . add ( cssClass ) ;
ln . append ( document . createTextNode ( args . join ( ' ' ) ) ) ;
eOutput . append ( ln ) ;
} ;
const warn = console . warn . bind ( console ) ;
const error = console . error . bind ( console ) ;
const toss = ( ... args ) => { throw new Error ( args . join ( ' ' ) ) } ;
SW . onerror = function ( event ) {
error ( "onerror" , event ) ;
} ;
let startTime ;
2022-08-17 16:44:05 +00:00
2022-08-10 11:26:08 +00:00
/ * *
A queue for callbacks which are to be run in response to async
DB commands . See the notes in runTests ( ) for why we need
this . The event - handling plumbing of this file requires that
any DB command which includes a ` messageId ` property also have
a queued callback entry , as the existence of that property in
response payloads is how it knows whether or not to shift an
entry off of the queue .
* /
const MsgHandlerQueue = {
queue : [ ] ,
id : 0 ,
push : function ( type , callback ) {
this . queue . push ( callback ) ;
return type + '-' + ( ++ this . id ) ;
} ,
shift : function ( ) {
return this . queue . shift ( ) ;
}
} ;
2023-03-05 07:33:11 +00:00
2022-08-10 11:26:08 +00:00
const testCount = ( ) => {
logHtml ( "" , "Total test count:" , T . counter + ". Total time =" , ( performance . now ( ) - startTime ) , "ms" ) ;
} ;
2022-08-24 00:10:45 +00:00
const logEventResult = function ( ev ) {
const evd = ev . result ;
2022-08-10 11:26:08 +00:00
logHtml ( evd . errorClass ? 'error' : '' ,
2022-08-24 00:10:45 +00:00
"runOneTest" , ev . messageId , "Worker time =" ,
( ev . workerRespondTime - ev . workerReceivedTime ) , "ms." ,
2022-08-10 11:26:08 +00:00
"Round-trip event time =" ,
2022-08-24 00:10:45 +00:00
( performance . now ( ) - ev . departureTime ) , "ms." ,
2023-03-05 07:33:11 +00:00
( evd . errorClass ? evd . message : "" ) //, JSON.stringify(evd)
2022-08-10 11:26:08 +00:00
) ;
} ;
2022-08-24 00:10:45 +00:00
const runOneTest = function ( eventType , eventArgs , callback ) {
T . assert ( eventArgs && 'object' === typeof eventArgs ) ;
2022-08-10 11:26:08 +00:00
/ * ^ ^ ^ t h a t i s f o r t h e t e s t i n g a n d m e s s a g e I d - r e l a t e d c o d e , n o t
a hard requirement of all of the Worker - exposed APIs . * /
2022-08-24 00:10:45 +00:00
const messageId = MsgHandlerQueue . push ( eventType , function ( ev ) {
logEventResult ( ev ) ;
2022-08-10 11:26:08 +00:00
if ( callback instanceof Function ) {
callback ( ev ) ;
testCount ( ) ;
}
} ) ;
2022-08-24 00:10:45 +00:00
const msg = {
type : eventType ,
args : eventArgs ,
dbId : DbState . id ,
messageId : messageId ,
departureTime : performance . now ( )
} ;
log ( "Posting" , eventType , "message to worker dbId=" + ( DbState . id || 'default' ) + ':' , msg ) ;
SW . postMessage ( msg ) ;
2022-08-10 11:26:08 +00:00
} ;
/ * * M e t h o d s w h i c h m a p d i r e c t l y t o o n m e s s a g e ( ) e v e n t . t y p e k e y s .
They get passed the inbound event . data . * /
const dbMsgHandler = {
open : function ( ev ) {
DbState . id = ev . dbId ;
2022-08-24 00:10:45 +00:00
log ( "open result" , ev ) ;
2022-08-10 11:26:08 +00:00
} ,
exec : function ( ev ) {
2022-08-24 00:10:45 +00:00
log ( "exec result" , ev ) ;
2022-08-10 11:26:08 +00:00
} ,
export : function ( ev ) {
2022-08-24 00:10:45 +00:00
log ( "export result" , ev ) ;
2022-08-10 11:26:08 +00:00
} ,
error : function ( ev ) {
2022-08-24 00:10:45 +00:00
error ( "ERROR from the worker:" , ev ) ;
logEventResult ( ev ) ;
2022-08-10 11:26:08 +00:00
} ,
resultRowTest1 : function f ( ev ) {
if ( undefined === f . counter ) f . counter = 0 ;
2022-08-24 18:39:46 +00:00
if ( null === ev . rowNumber ) {
/* End of result set. */
2022-08-24 20:57:37 +00:00
T . assert ( undefined === ev . row )
. assert ( Array . isArray ( ev . columnNames ) )
. assert ( ev . columnNames . length ) ;
2022-08-24 18:39:46 +00:00
} else {
T . assert ( ev . rowNumber > 0 ) ;
++ f . counter ;
}
//log("exec() result row:",ev);
T . assert ( null === ev . rowNumber || 'number' === typeof ev . row . b ) ;
2022-08-10 11:26:08 +00:00
}
} ;
/ * *
"The problem" now is that the test results are async . We
know , however , that the messages posted to the worker will
be processed in the order they are passed to it , so we can
create a queue of callbacks to handle them . The problem
with that approach is that it ' s not error - handling
friendly , in that an error can cause us to bypass a result
handler queue entry . We have to perform some extra
acrobatics to account for that .
Problem # 2 is that we cannot simply start posting events : we
first have to post an 'open' event , wait for it to respond , and
collect its db ID before continuing . If we don ' t wait , we may
well fire off 10 + messages before the open actually responds .
* /
const runTests2 = function ( ) {
const mustNotReach = ( ) => {
throw new Error ( "This is not supposed to be reached." ) ;
} ;
runOneTest ( 'exec' , {
2022-08-25 13:27:52 +00:00
sql : [ "create table t(a,b);" ,
2022-08-10 11:26:08 +00:00
"insert into t(a,b) values(1,2),(3,4),(5,6)"
2022-08-25 13:27:52 +00:00
] ,
2022-08-10 11:26:08 +00:00
resultRows : [ ] , columnNames : [ ]
} , function ( ev ) {
2022-08-24 00:10:45 +00:00
ev = ev . result ;
2022-08-10 11:26:08 +00:00
T . assert ( 0 === ev . resultRows . length )
. assert ( 0 === ev . columnNames . length ) ;
} ) ;
runOneTest ( 'exec' , {
sql : 'select a a, b b from t order by a' ,
2022-08-25 13:27:52 +00:00
resultRows : [ ] , columnNames : [ ] , saveSql : [ ]
2022-08-10 11:26:08 +00:00
} , function ( ev ) {
2022-08-24 00:10:45 +00:00
ev = ev . result ;
2022-08-10 11:26:08 +00:00
T . assert ( 3 === ev . resultRows . length )
. assert ( 1 === ev . resultRows [ 0 ] [ 0 ] )
. assert ( 6 === ev . resultRows [ 2 ] [ 1 ] )
. assert ( 2 === ev . columnNames . length )
. assert ( 'b' === ev . columnNames [ 1 ] ) ;
} ) ;
2022-08-25 13:27:52 +00:00
//if(1){ error("Returning prematurely for testing."); return; }
2022-08-10 11:26:08 +00:00
runOneTest ( 'exec' , {
sql : 'select a a, b b from t order by a' ,
resultRows : [ ] , columnNames : [ ] ,
rowMode : 'object'
} , function ( ev ) {
2022-08-24 00:10:45 +00:00
ev = ev . result ;
2022-08-10 11:26:08 +00:00
T . assert ( 3 === ev . resultRows . length )
. assert ( 1 === ev . resultRows [ 0 ] . a )
. assert ( 6 === ev . resultRows [ 2 ] . b )
} ) ;
runOneTest ( 'exec' , { sql : 'intentional_error' } , mustNotReach ) ;
// Ensure that the message-handler queue survives ^^^ that error...
runOneTest ( 'exec' , {
sql : 'select 1' ,
resultRows : [ ] ,
//rowMode: 'array', // array is the default in the Worker interface
} , function ( ev ) {
2022-08-24 00:10:45 +00:00
ev = ev . result ;
2022-08-10 11:26:08 +00:00
T . assert ( 1 === ev . resultRows . length )
. assert ( 1 === ev . resultRows [ 0 ] [ 0 ] ) ;
} ) ;
runOneTest ( 'exec' , {
sql : 'select a a, b b from t order by a' ,
callback : 'resultRowTest1' ,
rowMode : 'object'
} , function ( ev ) {
T . assert ( 3 === dbMsgHandler . resultRowTest1 . counter ) ;
dbMsgHandler . resultRowTest1 . counter = 0 ;
} ) ;
runOneTest ( 'exec' , {
sql : [
2022-08-25 13:27:52 +00:00
"pragma foreign_keys=0;" ,
2022-08-10 11:26:08 +00:00
// ^^^ arbitrary query with no result columns
2022-08-25 13:27:52 +00:00
"select a, b from t order by a desc;" ,
"select a from t;"
2023-05-23 19:11:42 +00:00
// exec() only honors SELECT results from the first
// statement with result columns (regardless of whether
2022-08-10 11:26:08 +00:00
// it has any rows).
] ,
rowMode : 1 ,
resultRows : [ ]
} , function ( ev ) {
2022-08-24 00:10:45 +00:00
const rows = ev . result . resultRows ;
2022-08-10 11:26:08 +00:00
T . assert ( 3 === rows . length ) .
assert ( 6 === rows [ 0 ] ) ;
} ) ;
runOneTest ( 'exec' , { sql : 'delete from t where a>3' } ) ;
runOneTest ( 'exec' , {
sql : 'select count(a) from t' ,
resultRows : [ ]
} , function ( ev ) {
2022-08-24 00:10:45 +00:00
ev = ev . result ;
2022-08-10 11:26:08 +00:00
T . assert ( 1 === ev . resultRows . length )
. assert ( 2 === ev . resultRows [ 0 ] [ 0 ] ) ;
} ) ;
2022-11-02 11:53:31 +00:00
runOneTest ( 'export' , { } , function ( ev ) {
ev = ev . result ;
log ( "export result:" , ev ) ;
T . assert ( 'string' === typeof ev . filename )
. assert ( ev . byteArray instanceof Uint8Array )
. assert ( ev . byteArray . length > 1024 )
. assert ( 'application/x-sqlite3' === ev . mimetype ) ;
} ) ;
2022-08-10 11:26:08 +00:00
/***** close() tests must come last. *****/
runOneTest ( 'close' , { unlink : true } , function ( ev ) {
2022-08-24 00:10:45 +00:00
ev = ev . result ;
2022-08-10 11:26:08 +00:00
T . assert ( 'string' === typeof ev . filename ) ;
} ) ;
runOneTest ( 'close' , { unlink : true } , function ( ev ) {
2022-08-24 00:10:45 +00:00
ev = ev . result ;
2022-08-10 11:26:08 +00:00
T . assert ( undefined === ev . filename ) ;
2022-09-30 20:35:37 +00:00
logHtml ( 'warning' , "This is the final test." ) ;
2022-08-10 11:26:08 +00:00
} ) ;
2022-09-30 20:35:37 +00:00
logHtml ( 'warning' , "Finished posting tests. Waiting on async results." ) ;
2022-08-10 11:26:08 +00:00
} ;
const runTests = function ( ) {
/ * *
Design decision time : all remaining tests depend on the 'open'
command having succeeded . In order to support multiple DBs , the
upcoming commands ostensibly have to know the ID of the DB they
want to talk to . We have two choices :
1 ) We run 'open' and wait for its response , which contains the
db id .
2 ) We have the Worker automatically use the current " default
db " ( the one which was most recently opened ) if no db id is
provided in the message . When we do this , the main thread may
well fire off _all _ of the test messages before the 'open'
actually responds , but because the messages are handled on a
FIFO basis , those after the initial 'open' will pick up the
"default" db . However , if the open fails , then all pending
messages ( until next next 'open' , at least ) except for 'close'
will fail and we have no way of cancelling them once they ' ve
been posted to the worker .
2022-08-17 16:44:05 +00:00
Which approach we use below depends on the boolean value of
waitForOpen .
2022-08-10 11:26:08 +00:00
* /
const waitForOpen = 1 ,
simulateOpenError = 0 / * if true , the remaining tests will
all barf if waitForOpen is
false . * / ;
logHtml ( '' ,
"Sending 'open' message and" , ( waitForOpen ? "" : "NOT " ) +
"waiting for its response before continuing." ) ;
startTime = performance . now ( ) ;
runOneTest ( 'open' , {
filename : 'testing2.sqlite3' ,
simulateError : simulateOpenError
} , function ( ev ) {
2022-08-24 00:10:45 +00:00
log ( "open result" , ev ) ;
T . assert ( 'testing2.sqlite3' === ev . result . filename )
. assert ( ev . dbId )
2022-11-01 07:49:49 +00:00
. assert ( ev . messageId )
. assert ( 'string' === typeof ev . result . vfs ) ;
2022-08-24 00:10:45 +00:00
DbState . id = ev . dbId ;
2022-08-10 11:26:08 +00:00
if ( waitForOpen ) setTimeout ( runTests2 , 0 ) ;
} ) ;
if ( ! waitForOpen ) runTests2 ( ) ;
} ;
SW . onmessage = function ( ev ) {
if ( ! ev . data || 'object' !== typeof ev . data ) {
warn ( "Unknown sqlite3-worker message type:" , ev ) ;
return ;
}
ev = ev . data /*expecting a nested object*/ ;
//log("main window onmessage:",ev);
2022-08-24 00:10:45 +00:00
if ( ev . result && ev . messageId ) {
2022-08-10 11:26:08 +00:00
/* We're expecting a queued-up callback handler. */
const f = MsgHandlerQueue . shift ( ) ;
if ( 'error' === ev . type ) {
dbMsgHandler . error ( ev ) ;
return ;
}
T . assert ( f instanceof Function ) ;
f ( ev ) ;
return ;
}
switch ( ev . type ) {
case 'sqlite3-api' :
2022-08-24 00:10:45 +00:00
switch ( ev . result ) {
2022-08-17 16:44:05 +00:00
case 'worker1-ready' :
2022-08-10 11:26:08 +00:00
log ( "Message:" , ev ) ;
self . sqlite3TestModule . setStatus ( null ) ;
runTests ( ) ;
return ;
default :
warn ( "Unknown sqlite3-api message type:" , ev ) ;
return ;
}
default :
if ( dbMsgHandler . hasOwnProperty ( ev . type ) ) {
try { dbMsgHandler [ ev . type ] ( ev ) ; }
catch ( err ) {
error ( "Exception while handling db result message" ,
ev , ":" , err ) ;
}
return ;
}
warn ( "Unknown sqlite3-api message type:" , ev ) ;
}
} ;
log ( "Init complete, but async init bits may still be running." ) ;
2022-11-02 11:53:31 +00:00
log ( "Installing Worker into global scope SW for dev purposes." ) ;
self . SW = SW ;
2022-08-10 11:26:08 +00:00
} ) ( ) ;