|
|
(async function(self){ |
|
|
|
|
|
const logCss = (function(){ |
|
|
const mapToString = (v)=>{ |
|
|
switch(typeof v){ |
|
|
case 'number': case 'string': case 'boolean': |
|
|
case 'undefined': case 'bigint': |
|
|
return ''+v; |
|
|
default: break; |
|
|
} |
|
|
if(null===v) return 'null'; |
|
|
if(v instanceof Error){ |
|
|
v = { |
|
|
message: v.message, |
|
|
stack: v.stack, |
|
|
errorClass: v.name |
|
|
}; |
|
|
} |
|
|
return JSON.stringify(v,undefined,2); |
|
|
}; |
|
|
const normalizeArgs = (args)=>args.map(mapToString); |
|
|
const logTarget = document.querySelector('#test-output'); |
|
|
const logCss = function(cssClass,...args){ |
|
|
const ln = document.createElement('div'); |
|
|
if(cssClass){ |
|
|
for(const c of (Array.isArray(cssClass) ? cssClass : [cssClass])){ |
|
|
ln.classList.add(c); |
|
|
} |
|
|
} |
|
|
ln.append(document.createTextNode(normalizeArgs(args).join(' '))); |
|
|
logTarget.append(ln); |
|
|
}; |
|
|
const cbReverse = document.querySelector('#cb-log-reverse'); |
|
|
const cbReverseKey = 'tester1:cb-log-reverse'; |
|
|
const cbReverseIt = ()=>{ |
|
|
logTarget.classList[cbReverse.checked ? 'add' : 'remove']('reverse'); |
|
|
localStorage.setItem(cbReverseKey, cbReverse.checked ? 1 : 0); |
|
|
}; |
|
|
cbReverse.addEventListener('change', cbReverseIt, true); |
|
|
if(localStorage.getItem(cbReverseKey)){ |
|
|
cbReverse.checked = !!(+localStorage.getItem(cbReverseKey)); |
|
|
} |
|
|
cbReverseIt(); |
|
|
return logCss; |
|
|
})(); |
|
|
const stdout = (...args)=>logCss('',...args); |
|
|
const stderr = (...args)=>logCss('error',...args); |
|
|
|
|
|
const wait = async (ms)=>{ |
|
|
return new Promise((resolve)=>setTimeout(resolve,ms)); |
|
|
}; |
|
|
|
|
|
const urlArgsJs = new URL(document.currentScript.src).searchParams; |
|
|
const urlArgsHtml = new URL(self.location.href).searchParams; |
|
|
const options = Object.create(null); |
|
|
options.sqlite3Dir = urlArgsJs.get('sqlite3.dir'); |
|
|
options.workerCount = ( |
|
|
urlArgsHtml.has('workers') ? +urlArgsHtml.get('workers') : 3 |
|
|
) || 4; |
|
|
options.opfsVerbose = ( |
|
|
urlArgsHtml.has('verbose') ? +urlArgsHtml.get('verbose') : 1 |
|
|
) || 1; |
|
|
options.interval = ( |
|
|
urlArgsHtml.has('interval') ? +urlArgsHtml.get('interval') : 1000 |
|
|
) || 1000; |
|
|
options.iterations = ( |
|
|
urlArgsHtml.has('iterations') ? +urlArgsHtml.get('iterations') : 10 |
|
|
) || 10; |
|
|
options.unlockAsap = ( |
|
|
urlArgsHtml.has('unlock-asap') ? +urlArgsHtml.get('unlock-asap') : 0 |
|
|
) || 0; |
|
|
options.noUnlink = !!urlArgsHtml.has('no-unlink'); |
|
|
const workers = []; |
|
|
workers.post = (type,...args)=>{ |
|
|
for(const w of workers) w.postMessage({type, payload:args}); |
|
|
}; |
|
|
workers.counts = {loaded: 0, passed: 0, failed: 0}; |
|
|
const checkFinished = function(){ |
|
|
if(workers.counts.passed + workers.counts.failed !== workers.length){ |
|
|
return; |
|
|
} |
|
|
if(workers.counts.failed>0){ |
|
|
logCss('tests-fail',"Finished with",workers.counts.failed,"failure(s)."); |
|
|
}else{ |
|
|
logCss('tests-pass',"All",workers.length,"workers finished."); |
|
|
} |
|
|
}; |
|
|
workers.onmessage = function(msg){ |
|
|
msg = msg.data; |
|
|
const prefix = 'Worker #'+msg.worker+':'; |
|
|
switch(msg.type){ |
|
|
case 'loaded': |
|
|
stdout(prefix,"loaded"); |
|
|
if(++workers.counts.loaded === workers.length){ |
|
|
stdout("All",workers.length,"workers loaded. Telling them to run..."); |
|
|
workers.post('run'); |
|
|
} |
|
|
break; |
|
|
case 'stdout': stdout(prefix,...msg.payload); break; |
|
|
case 'stderr': stderr(prefix,...msg.payload); break; |
|
|
case 'error': stderr(prefix,"ERROR:",...msg.payload); break; |
|
|
case 'finished': |
|
|
++workers.counts.passed; |
|
|
logCss('tests-pass',prefix,...msg.payload); |
|
|
checkFinished(); |
|
|
break; |
|
|
case 'failed': |
|
|
++workers.counts.failed; |
|
|
logCss('tests-fail',prefix,"FAILED:",...msg.payload); |
|
|
checkFinished(); |
|
|
break; |
|
|
default: logCss('error',"Unhandled message type:",msg); break; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const eTestLinks = document.querySelector('#testlinks'); |
|
|
const optArgs = function(obj){ |
|
|
const li = []; |
|
|
for(const k of ['interval','iterations','workers','verbose','unlock-asap']){ |
|
|
if( obj.hasOwnProperty(k) ) li.push(k+'='+obj[k]); |
|
|
} |
|
|
return li.join('&'); |
|
|
}; |
|
|
for(const opt of [ |
|
|
{interval: 1000, workers: 5, iterations: 30}, |
|
|
{interval: 500, workers: 5, iterations: 30}, |
|
|
{interval: 250, workers: 3, iterations: 30}, |
|
|
{interval: 600, workers: 5, iterations: 100} |
|
|
]){ |
|
|
const li = document.createElement('li'); |
|
|
eTestLinks.appendChild(li); |
|
|
const a = document.createElement('a'); |
|
|
li.appendChild(a); |
|
|
const args = optArgs(opt); |
|
|
a.setAttribute('href', '?'+args); |
|
|
a.innerText = args; |
|
|
} |
|
|
|
|
|
stdout("Launching",options.workerCount,"workers. Options:",options); |
|
|
workers.uri = ( |
|
|
'worker.js?' |
|
|
+ 'sqlite3.dir='+options.sqlite3Dir |
|
|
+ '&interval='+options.interval |
|
|
+ '&iterations='+options.iterations |
|
|
+ '&opfs-verbose='+options.opfsVerbose |
|
|
+ '&opfs-unlock-asap='+options.unlockAsap |
|
|
); |
|
|
for(let i = 0; i < options.workerCount; ++i){ |
|
|
stdout("Launching worker..."); |
|
|
workers.push(new Worker( |
|
|
workers.uri+'&workerId='+(i+1)+( |
|
|
(i || options.noUnlink) ? '' : '&unlink-db' |
|
|
) |
|
|
)); |
|
|
} |
|
|
|
|
|
|
|
|
workers.forEach((w)=>w.onmessage = workers.onmessage); |
|
|
})(self); |
|
|
|