3 const logCss
= (function(){
4 const mapToString
= (v
)=>{
6 case 'number': case 'string': case 'boolean':
7 case 'undefined': case 'bigint':
11 if(null===v
) return 'null';
12 if(v
instanceof Error
){
19 return JSON
.stringify(v
,undefined,2);
21 const normalizeArgs
= (args
)=>args
.map(mapToString
);
22 const logTarget
= document
.querySelector('#test-output');
23 const logCss = function(cssClass
,...args
){
24 const ln
= document
.createElement('div');
26 for(const c
of (Array
.isArray(cssClass
) ? cssClass
: [cssClass
])){
30 ln
.append(document
.createTextNode(normalizeArgs(args
).join(' ')));
33 const cbReverse
= document
.querySelector('#cb-log-reverse');
34 const cbReverseKey
= 'tester1:cb-log-reverse';
35 const cbReverseIt
= ()=>{
36 logTarget
.classList
[cbReverse
.checked
? 'add' : 'remove']('reverse');
37 localStorage
.setItem(cbReverseKey
, cbReverse
.checked
? 1 : 0);
39 cbReverse
.addEventListener('change', cbReverseIt
, true);
40 if(localStorage
.getItem(cbReverseKey
)){
41 cbReverse
.checked
= !!(+localStorage
.getItem(cbReverseKey
));
46 const stdout
= (...args
)=>logCss('',...args
);
47 const stderr
= (...args
)=>logCss('error',...args
);
49 const wait
= async (ms
)=>{
50 return new Promise((resolve
)=>setTimeout(resolve
,ms
));
53 const urlArgsJs
= new URL(document
.currentScript
.src
).searchParams
;
54 const urlArgsHtml
= new URL(self
.location
.href
).searchParams
;
55 const options
= Object
.create(null);
56 options
.sqlite3Dir
= urlArgsJs
.get('sqlite3.dir');
57 options
.workerCount
= (
58 urlArgsHtml
.has('workers') ? +urlArgsHtml
.get('workers') : 3
60 options
.opfsVerbose
= (
61 urlArgsHtml
.has('verbose') ? +urlArgsHtml
.get('verbose') : 1
64 urlArgsHtml
.has('interval') ? +urlArgsHtml
.get('interval') : 1000
66 options
.iterations
= (
67 urlArgsHtml
.has('iterations') ? +urlArgsHtml
.get('iterations') : 10
69 options
.unlockAsap
= (
70 urlArgsHtml
.has('unlock-asap') ? +urlArgsHtml
.get('unlock-asap') : 0
72 options
.noUnlink
= !!urlArgsHtml
.has('no-unlink');
74 workers
.post
= (type
,...args
)=>{
75 for(const w
of workers
) w
.postMessage({type
, payload
:args
});
77 workers
.counts
= {loaded
: 0, passed
: 0, failed
: 0};
78 const checkFinished = function(){
79 if(workers
.counts
.passed
+ workers
.counts
.failed
!== workers
.length
){
82 if(workers
.counts
.failed
>0){
83 logCss('tests-fail',"Finished with",workers
.counts
.failed
,"failure(s).");
85 logCss('tests-pass',"All",workers
.length
,"workers finished.");
88 workers
.onmessage = function(msg
){
90 const prefix
= 'Worker #'+msg
.worker
+':';
93 stdout(prefix
,"loaded");
94 if(++workers
.counts
.loaded
=== workers
.length
){
95 stdout("All",workers
.length
,"workers loaded. Telling them to run...");
99 case 'stdout': stdout(prefix
,...msg
.payload
); break;
100 case 'stderr': stderr(prefix
,...msg
.payload
); break;
101 case 'error': stderr(prefix
,"ERROR:",...msg
.payload
); break;
103 ++workers
.counts
.passed
;
104 logCss('tests-pass',prefix
,...msg
.payload
);
108 ++workers
.counts
.failed
;
109 logCss('tests-fail',prefix
,"FAILED:",...msg
.payload
);
112 default: logCss('error',"Unhandled message type:",msg
); break;
116 stdout("Launching",options
.workerCount
,"workers. Options:",options
);
119 + 'sqlite3.dir='+options
.sqlite3Dir
120 + '&interval='+options
.interval
121 + '&iterations='+options
.iterations
122 + '&opfs-verbose='+options
.opfsVerbose
123 + '&opfs-unlock-asap='+options
.unlockAsap
125 for(let i
= 0; i
< options
.workerCount
; ++i
){
126 stdout("Launching worker...");
127 workers
.push(new Worker(
128 workers
.uri
+'&workerId='+(i
+1)+(
129 (i
|| options
.noUnlink
) ? '' : '&unlink-db'
133 // Have to delay onmessage assignment until after the loop
134 // to avoid that early workers get an undue head start.
135 workers
.forEach((w
)=>w
.onmessage
= workers
.onmessage
);