tests: added test-browser static files

This commit is contained in:
Richard Moore 2023-05-06 15:14:36 +09:00
parent 25ab579634
commit 96d5e7b206
7 changed files with 21359 additions and 14 deletions

107
misc/test-browser/assert.js Normal file

@ -0,0 +1,107 @@
function throwError(message, info) {
const error = new Error(`AssertionError: ${ message }`);
error.code = "ERR_ASSERTION";
for (const key of info) { error[key] = info[key]; }
throw error;
}
export function equal(actual, expected, reason) {
if (actual != expected) {
if (reason == null) { reason = `${ actual } == ${ expected }`; }
throwError(reason, { actual, expected, operator: "==" });
}
}
function isDeepEqual(actual, expected, memo) {
if (actual === expected) {
return true;
}
// One or both things aren't objects
if (actual === null || typeof(expected) !== 'object') {
if (expected === null || typeof(expected) !== 'object') {
return actual == expected;
}
return false;
} else if (expected === null || typeof(expected) !== 'object') {
return false;
}
if (Array.isArray(actual)) {
if (!Array.isArray(expected) || actual.length !== expected.length) {
return false;
}
for (let i = 0; i < actual.length; i++) {
if (!isDeepEqual(actual[i], expected[i])) { return false; }
}
return true;
}
// Object
const keysActual = Object.keys(actual).sort(), keysExpected = Object.keys(expected).sort();
if (!isDeepEqual(keysActual, keysExpected)) { return false; }
for (const key of keysActual) {
if (!isDeepEqual(actual[key], expected[key], memo)) { return false; }
}
return true;
}
export function deepEqual(actual, expected, reason) {
const memo = [ ];
const isOk = isDeepEqual(actual, expected, memo);
if (!isOk) {
equal(actual, expected, reason);
}
}
export function ok(check, reason) {
equal(!!check, true, reason);
}
export function throws(func, checkFunc, reason) {
try {
func();
} catch (e) {
if (checkFunc(e)) { return true; }
throwError(`The expected exception validation function returned false`, {
actual: e,
expected: checkFunc,
operation: "throws"
});
}
throwError("Missing expected exception", {
operator: "throws"
});
}
export async function rejects(func, checkFunc, reason) {
try {
await func();
} catch (e) {
if (checkFunc(e)) { return true; }
throwError(`The rejection validation function returned false`, {
actual: e,
expected: checkFunc,
operation: "throws"
});
}
throwError("Missing rejection", {
operator: "rejects"
});
}
export default {
equal, deepEqual, ok, rejects, throws
};

@ -0,0 +1,26 @@
<html>
<body>
<h1>Hello World!!</h1>
<div>Please check the console for test output...</div>
<div id="mocha"></div>
<script type="module">
// Must import Mocha first; completely
await import("./static/mocha.js");
// Load our custom Reporter (after importing mocha)
import { MyReporter } from "/static/reporter.js";
// Setup the global environment and set out reporter
mocha.setup({ ui: 'bdd' });
mocha.reporter(MyReporter);
// Import the tests
await import("./tests/index.js");
// Run Mocha!
mocha.run();
</script>
</body>
</html>

20617
misc/test-browser/mocha.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,218 @@
'use strict';
/* c8 ignore start */
await import("/static/mocha.js");
const {
EVENT_RUN_BEGIN,
EVENT_RUN_END,
EVENT_TEST_BEGIN,
EVENT_TEST_END,
EVENT_TEST_FAIL,
EVENT_TEST_PASS,
EVENT_SUITE_BEGIN,
EVENT_SUITE_END
} = Mocha.Runner.constants;
// See: https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
let disableColor = true;
const Colors = {
"blue": "\x1b[0;34m",
"blue+": "\x1b[0;1;34m",
"cyan": "\x1b[0;36m",
"cyan+": "\x1b[0;1;36m",
"green": "\x1b[0;32m",
"green+": "\x1b[0;1;32m",
"magenta-": "\x1b[0;2;35m",
"magenta": "\x1b[0;35m",
"magenta+": "\x1b[0;1;35m",
"red": "\x1b[0;31m",
"red+": "\x1b[0;1;31m",
"yellow": "\x1b[0;33m",
"yellow+": "\x1b[0;1;33m",
"dim": "\x1b[0;2;37m",
"bold": "\x1b[0;1;37m",
"normal": "\x1b[0m"
};
function colorify(text) {
return unescapeColor(text.replace(/(<([a-z+]+)>)/g, (all, _, color) => {
if (disableColor) { return ""; }
const seq = Colors[color];
if (seq == null) {
console.log("UNKNOWN COLOR:", color);
return "";
}
return seq;
})) + (disableColor ? "": Colors.normal);
}
function escapeColor(text) {
return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function unescapeColor(text) {
return text.replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&");
}
function getString(value) {
if (value instanceof Error) {
return value.stack;
}
return String(value);
}
// To prevent environments from thinking we're dead due to lack of
// output, we force output after 20s
function getTime() { return (new Date()).getTime(); }
const KEEP_ALIVE = 20 * 1000;
// this reporter outputs test results, indenting two spaces per suite
export class MyReporter {
constructor(runner) {
this._errors = [ ];
this._indents = 1;
this._lastLog = getTime();
this._lastPass = "";
this._lastPrefix = null;
this._lastPrefixHeader = null;
this._testLogs = [ ];
this._suiteLogs = [ ];
this._prefixCount = 0;
const stats = runner.stats;
runner.once(EVENT_RUN_BEGIN, () => {
}).on(EVENT_SUITE_BEGIN, (suite) => {
this._suiteLogs.push([ ]);
suite._ethersLog = (text) => {
this._suiteLogs[this._suiteLogs.length - 1].push(getString(text))
};
if (suite.title.trim()) {
this.log(`<blue+>Suite: ${ escapeColor(suite.title) }`)
}
this.increaseIndent();
}).on(EVENT_SUITE_END, (suite) => {
this.flush(true);
this.decreaseIndent();
const logs = this._suiteLogs.pop();
if (logs.length) {
logs.join("\n").split("\n").forEach((line) => {
this.log(` <magenta+>&gt;&gt; <dim>${ escapeColor(line) }`);
});
}
if (suite.title.trim()) { this.log(""); }
}).on(EVENT_TEST_BEGIN, (test) => {
this._testLogs.push([ ]);
test._ethersLog = (text) => {
this._testLogs[this._testLogs.length - 1].push(getString(text))
};
}).on(EVENT_TEST_END, (test) => {
const logs = this._testLogs.pop();
if (logs.length) {
this.flush(false);
logs.join("\n").split("\n").forEach((line) => {
this.log(` <cyan+>&gt;&gt; <cyan->${ escapeColor(line) }`);
});
}
}).on(EVENT_TEST_PASS, (test) => {
this.addPass(test.title);
}).on(EVENT_TEST_FAIL, (test, error) => {
this.flush();
this._errors.push({ test, error });
this.log(
` [ <red+>fail(${ this._errors.length }): <red>${ escapeColor(test.title) } - <normal>${ escapeColor(error.message) } ]`
);
}).once(EVENT_RUN_END, () => {
this.flush(true);
this.indent = 0;
if (this._errors.length) {
this._errors.forEach(({ test, error }, index) => {
this.log("<cyan+>---------------------");
this.log(`<red+>ERROR ${ index + 1 }: <red>${ escapeColor(test.title) }`);
this.log(escapeColor(error.toString()));
});
this.log("<cyan+>=====================");
}
const { duration, passes, failures } = stats;
const total = passes + failures;
this.log(`<bold>Done: <green+>${ passes }<green>/${ total } passed <red>(<red+>${ failures } <red>failed)`);
const status = (failures > 0) ? 1: 0;
this.log(`#status=${ status }`);
});
}
log(line) {
this._lastLog = getTime();
const indent = Array(this._indents).join(' ');
console.log(`${ indent }${ colorify(line) }`);
}
addPass(line) {
const prefix = line.split(":")[0];
if (prefix === this._lastPrefix) {
this._prefixCount++;
if (getTime() - this._lastLog > KEEP_ALIVE) {
const didLog = this.flush(false);
// Nothing was output, so show *something* so the
// environment knows we're still alive and kicking
if (!didLog) {
this.log(" <yellow>[ keep-alive; forced output ]")
}
}
} else {
this.flush(true);
this._lastPrefixHeader = null;
this._lastPrefix = prefix;
this._prefixCount = 1;
}
this._lastLine = line;
}
flush(reset) {
let didLog = false;
if (this._lastPrefix != null) {
if (this._prefixCount === 1 && this._lastPrefixHeader == null) {
this.log(escapeColor(this._lastLine));
didLog = true;
} else if (this._prefixCount > 0) {
if (this._lastPrefixHeader !== this._lastPrefix) {
this.log(`<cyan>${ escapeColor(this._lastPrefix) }:`);
this._lastPrefixHeader = this._lastPrefix;
}
this.log(` - ${ this._prefixCount } tests passed (prefix coalesced)`);
didLog = true;
}
}
if (reset) {
this._lastPrefixHeader = null;
this._lastPrefix = null;
}
this._prefixCount = 0;
return didLog;
}
increaseIndent() { this._indents++; }
decreaseIndent() { this._indents--; }
}
//module.exports = MyReporter;
/* c8 ignore stop */

@ -0,0 +1,376 @@
var TINF_OK = 0;
var TINF_DATA_ERROR = -3;
function Tree() {
this.table = new Uint16Array(16); /* table of code length counts */
this.trans = new Uint16Array(288); /* code -> symbol translation table */
}
function Data(source, dest) {
this.source = source;
this.sourceIndex = 0;
this.tag = 0;
this.bitcount = 0;
this.dest = dest;
this.destLen = 0;
this.ltree = new Tree(); /* dynamic length/symbol tree */
this.dtree = new Tree(); /* dynamic distance tree */
}
/* --------------------------------------------------- *
* -- uninitialized global data (static structures) -- *
* --------------------------------------------------- */
var sltree = new Tree();
var sdtree = new Tree();
/* extra bits and base tables for length codes */
var length_bits = new Uint8Array(30);
var length_base = new Uint16Array(30);
/* extra bits and base tables for distance codes */
var dist_bits = new Uint8Array(30);
var dist_base = new Uint16Array(30);
/* special ordering of code length codes */
var clcidx = new Uint8Array([
16, 17, 18, 0, 8, 7, 9, 6,
10, 5, 11, 4, 12, 3, 13, 2,
14, 1, 15
]);
/* used by tinf_decode_trees, avoids allocations every call */
var code_tree = new Tree();
var lengths = new Uint8Array(288 + 32);
/* ----------------------- *
* -- utility functions -- *
* ----------------------- */
/* build extra bits and base tables */
function tinf_build_bits_base(bits, base, delta, first) {
var i, sum;
/* build bits table */
for (i = 0; i < delta; ++i) bits[i] = 0;
for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0;
/* build base table */
for (sum = first, i = 0; i < 30; ++i) {
base[i] = sum;
sum += 1 << bits[i];
}
}
/* build the fixed huffman trees */
function tinf_build_fixed_trees(lt, dt) {
var i;
/* build fixed length tree */
for (i = 0; i < 7; ++i) lt.table[i] = 0;
lt.table[7] = 24;
lt.table[8] = 152;
lt.table[9] = 112;
for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i;
for (i = 0; i < 144; ++i) lt.trans[24 + i] = i;
for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i;
for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i;
/* build fixed distance tree */
for (i = 0; i < 5; ++i) dt.table[i] = 0;
dt.table[5] = 32;
for (i = 0; i < 32; ++i) dt.trans[i] = i;
}
/* given an array of code lengths, build a tree */
var offs = new Uint16Array(16);
function tinf_build_tree(t, lengths, off, num) {
var i, sum;
/* clear code length count table */
for (i = 0; i < 16; ++i) t.table[i] = 0;
/* scan symbol lengths, and sum code length counts */
for (i = 0; i < num; ++i) t.table[lengths[off + i]]++;
t.table[0] = 0;
/* compute offset table for distribution sort */
for (sum = 0, i = 0; i < 16; ++i) {
offs[i] = sum;
sum += t.table[i];
}
/* create code->symbol translation table (symbols sorted by code) */
for (i = 0; i < num; ++i) {
if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i;
}
}
/* ---------------------- *
* -- decode functions -- *
* ---------------------- */
/* get one bit from source stream */
function tinf_getbit(d) {
/* check if tag is empty */
if (!d.bitcount--) {
/* load next tag */
d.tag = d.source[d.sourceIndex++];
d.bitcount = 7;
}
/* shift bit out of tag */
var bit = d.tag & 1;
d.tag >>>= 1;
return bit;
}
/* read a num bit value from a stream and add base */
function tinf_read_bits(d, num, base) {
if (!num)
return base;
while (d.bitcount < 24) {
d.tag |= d.source[d.sourceIndex++] << d.bitcount;
d.bitcount += 8;
}
var val = d.tag & (0xffff >>> (16 - num));
d.tag >>>= num;
d.bitcount -= num;
return val + base;
}
/* given a data stream and a tree, decode a symbol */
function tinf_decode_symbol(d, t) {
while (d.bitcount < 24) {
d.tag |= d.source[d.sourceIndex++] << d.bitcount;
d.bitcount += 8;
}
var sum = 0, cur = 0, len = 0;
var tag = d.tag;
/* get more bits while code value is above sum */
do {
cur = 2 * cur + (tag & 1);
tag >>>= 1;
++len;
sum += t.table[len];
cur -= t.table[len];
} while (cur >= 0);
d.tag = tag;
d.bitcount -= len;
return t.trans[sum + cur];
}
/* given a data stream, decode dynamic trees from it */
function tinf_decode_trees(d, lt, dt) {
var hlit, hdist, hclen;
var i, num, length;
/* get 5 bits HLIT (257-286) */
hlit = tinf_read_bits(d, 5, 257);
/* get 5 bits HDIST (1-32) */
hdist = tinf_read_bits(d, 5, 1);
/* get 4 bits HCLEN (4-19) */
hclen = tinf_read_bits(d, 4, 4);
for (i = 0; i < 19; ++i) lengths[i] = 0;
/* read code lengths for code length alphabet */
for (i = 0; i < hclen; ++i) {
/* get 3 bits code length (0-7) */
var clen = tinf_read_bits(d, 3, 0);
lengths[clcidx[i]] = clen;
}
/* build code length tree */
tinf_build_tree(code_tree, lengths, 0, 19);
/* decode code lengths for the dynamic trees */
for (num = 0; num < hlit + hdist;) {
var sym = tinf_decode_symbol(d, code_tree);
switch (sym) {
case 16:
/* copy previous code length 3-6 times (read 2 bits) */
var prev = lengths[num - 1];
for (length = tinf_read_bits(d, 2, 3); length; --length) {
lengths[num++] = prev;
}
break;
case 17:
/* repeat code length 0 for 3-10 times (read 3 bits) */
for (length = tinf_read_bits(d, 3, 3); length; --length) {
lengths[num++] = 0;
}
break;
case 18:
/* repeat code length 0 for 11-138 times (read 7 bits) */
for (length = tinf_read_bits(d, 7, 11); length; --length) {
lengths[num++] = 0;
}
break;
default:
/* values 0-15 represent the actual code lengths */
lengths[num++] = sym;
break;
}
}
/* build dynamic trees */
tinf_build_tree(lt, lengths, 0, hlit);
tinf_build_tree(dt, lengths, hlit, hdist);
}
/* ----------------------------- *
* -- block inflate functions -- *
* ----------------------------- */
/* given a stream and two trees, inflate a block of data */
function tinf_inflate_block_data(d, lt, dt) {
while (1) {
var sym = tinf_decode_symbol(d, lt);
/* check for end of block */
if (sym === 256) {
return TINF_OK;
}
if (sym < 256) {
d.dest[d.destLen++] = sym;
} else {
var length, dist, offs;
var i;
sym -= 257;
/* possibly get more bits from length code */
length = tinf_read_bits(d, length_bits[sym], length_base[sym]);
dist = tinf_decode_symbol(d, dt);
/* possibly get more bits from distance code */
offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]);
/* copy match */
for (i = offs; i < offs + length; ++i) {
d.dest[d.destLen++] = d.dest[i];
}
}
}
}
/* inflate an uncompressed block of data */
function tinf_inflate_uncompressed_block(d) {
var length, invlength;
var i;
/* unread from bitbuffer */
while (d.bitcount > 8) {
d.sourceIndex--;
d.bitcount -= 8;
}
/* get length */
length = d.source[d.sourceIndex + 1];
length = 256 * length + d.source[d.sourceIndex];
/* get one's complement of length */
invlength = d.source[d.sourceIndex + 3];
invlength = 256 * invlength + d.source[d.sourceIndex + 2];
/* check length */
if (length !== (~invlength & 0x0000ffff))
return TINF_DATA_ERROR;
d.sourceIndex += 4;
/* copy block */
for (i = length; i; --i)
d.dest[d.destLen++] = d.source[d.sourceIndex++];
/* make sure we start next block on a byte boundary */
d.bitcount = 0;
return TINF_OK;
}
/* inflate stream from source to dest */
function tinf_uncompress(source, dest) {
var d = new Data(source, dest);
var bfinal, btype, res;
do {
/* read final block flag */
bfinal = tinf_getbit(d);
/* read block type (2 bits) */
btype = tinf_read_bits(d, 2, 0);
/* decompress block */
switch (btype) {
case 0:
/* decompress uncompressed block */
res = tinf_inflate_uncompressed_block(d);
break;
case 1:
/* decompress block with fixed huffman trees */
res = tinf_inflate_block_data(d, sltree, sdtree);
break;
case 2:
/* decompress block with dynamic huffman trees */
tinf_decode_trees(d, d.ltree, d.dtree);
res = tinf_inflate_block_data(d, d.ltree, d.dtree);
break;
default:
res = TINF_DATA_ERROR;
}
if (res !== TINF_OK)
throw new Error('Data error');
} while (!bfinal);
if (d.destLen < d.dest.length) {
if (typeof d.dest.slice === 'function')
return d.dest.slice(0, d.destLen);
else
return d.dest.subarray(0, d.destLen);
}
return d.dest;
}
/* -------------------- *
* -- initialization -- *
* -------------------- */
/* build fixed huffman trees */
tinf_build_fixed_trees(sltree, sdtree);
/* build extra bits and base tables */
tinf_build_bits_base(length_bits, length_base, 4, 3);
tinf_build_bits_base(dist_bits, dist_base, 2, 1);
/* fix a special case */
length_bits[28] = 0;
length_base[28] = 258;
//module.exports = tinf_uncompress;
export const inflate = tinf_uncompress;

@ -146,8 +146,6 @@ export class CDPSession {
this.websocket.onerror = (error) => {
console.log(`WARN: WebSocket error - ${ JSON.stringify(error) }`);
};
//this.send("Target.setDiscoverTargets", { discover: true });
}
get target(): string {
@ -196,9 +194,6 @@ export type Options = {
};
//function transform(source: string): string {
//}
const TestData = (function() {
function load(tag: string): any {
const filename = resolve("testcases", tag + ".json.gz");
@ -229,8 +224,6 @@ const TestData = (function() {
export function start(_root: string, options: Options): Promise<Server> {
//if (_root == null) { throw new Error("root required"); }
//const root = resolve(_root);
if (options == null) { options = { }; }
if (options.port == null) { options.port = 8000; }
@ -242,14 +235,14 @@ export function start(_root: string, options: Options): Promise<Server> {
let filename: string;
if (url === "/") {
filename = "./output/index.html";
filename = "./misc/test-browser/index.html";
} else if (url === "/ethers.js" || url === "/index.js") {
filename = "./dist/ethers.js";
} else if (url === "/ethers.js.map") {
filename = "./dist/ethers.js.map";
} else if (url.startsWith("/static/")) {
filename = "./output/" + url.substring(8);
filename = "./misc/test-browser/" + url.substring(8);
} else if (url === "/tests/utils.js") {
//console.log({ status: 200, content: `<<in-memory ${ TestData.length } bytes>>` });
@ -341,7 +334,15 @@ export function start(_root: string, options: Options): Promise<Server> {
(async function() {
await start(resolve("."), { port: 8000 });
const cmd = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
const cmds = [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/usr/bin/chromium"
].filter((f) => { try { fs.accessSync(f); return true; } catch (error) { return false; } });
if (cmds.length === 0) { throw new Error("no installed browser found"); }
const cmd = cmds[0];
const args = [ "--headless", "--disable-gpu", "--remote-debugging-port=8022" ];
const browser = child_process.spawn(cmd, args);
@ -361,16 +362,15 @@ export function start(_root: string, options: Options): Promise<Server> {
}
});
});
console.log(url);
//url = "ws://127.0.0.1:8022/devtools/browser/e02e20e9-3e5f-47f6-bc23-1c050acc6da6";
console.log("URL:", url);
const session = new CDPSession(url);
// "ws://127.0.0.1:8022/devtools/browser/cab84776-4714-4a0f-aae3-acec84feddd9");
await session.ready;
await session.send("Console.enable", { });
await session.navigate("http:/\/localhost:8000");
const status = await session.done;
console.log(status);
console.log("STATUS:", status);
process.exit(status);
})();