bmocha
Version:
Alternative implementation of mocha
621 lines (482 loc) • 12.2 kB
JavaScript
;
/* global __REQUIRES__ */
/* global __FUNCTIONS__ */
/* global __BFILE__ */
/* global __OPTIONS__ */
/* global __PLATFORM__ */
/* global document */
/* global XMLHttpRequest */
const fs = require('fs');
const {resolve} = require('path');
const util = require('util');
const bmocha = require('../../bmocha');
const common = require('./common');
const {
Array,
Date,
Object
} = global;
const {
Mocha,
Stream,
SendStream,
ConsoleStream,
DOMStream
} = bmocha;
/*
* Constants
*/
const options = __OPTIONS__;
const platform = __PLATFORM__;
const body = document.getElementById('bmocha');
let stream = null;
/*
* HTTP
*/
const request = (args, callback) => {
const xhr = new XMLHttpRequest();
const parse = (xhr, args) => {
const body = String(xhr.responseText || '').trim();
const status = xhr.status >>> 0;
const error = status < 200 || status >= 400;
let json = Object.create(null);
try {
if (body.length > 0)
json = JSON.parse(body);
if (!json || typeof json !== 'object')
throw new Error('Invalid JSON body.');
} catch (e) {
if (error)
return [new Error(`Status code: ${status}`), null];
return [e, null];
}
if (error) {
const msg = String(json.message || '');
const err = new Error(msg);
if (json.name)
err.name = String(json.name);
if (json.type)
err.type = String(json.type);
if (json.errno != null)
err.errno = json.errno | 0;
if (json.code)
err.code = String(json.code);
if (json.syscall)
err.syscall = String(json.syscall);
if (json.stack) {
try {
err.stack = String(json.stack);
} catch (e) {
;
}
}
if (typeof args[1] === 'string')
err.path = args[1];
return [err, null];
}
switch (args[0]) {
case 'access': {
return [null, undefined];
}
case 'exists': {
return [null, Boolean(json.exists)];
}
case 'lstat':
case 'stat': {
const stat = Object.assign({}, json, {
isBlockDevice: () => json.isBlockDevice,
isCharacterDevice: () => json.isCharacterDevice,
isDirectory: () => json.isDirectory,
isFIFO: () => json.isFIFO,
isFile: () => json.isFile,
isSocket: () => json.isSocket,
isSymbolicLink: () => json.isSymbolicLink,
atimeMs: json.atime,
mtimeMs: json.mtime,
ctimeMs: json.ctime,
birthtimeMs: json.birthtime,
atime: new Date(json.atime),
mtime: new Date(json.mtime),
ctime: new Date(json.ctime),
birthtime: new Date(json.birthtime)
});
return [null, stat];
}
case 'notify': {
return [null, undefined];
}
case 'readdir': {
return [null, json];
}
case 'readfile': {
let raw = String(json.data);
if (args.length < 3 || !args[2])
raw = Buffer.from(raw, 'base64');
return [null, raw];
}
case 'register': {
return [null, json.result];
}
case 'write': {
return [null, json.result];
}
}
return [null, json];
};
xhr.open('POST', '/', Boolean(callback));
xhr.send(JSON.stringify(args));
if (callback) {
xhr.onreadystatechange = () => {
const readyState = xhr.readyState >>> 0;
if (readyState === 4) {
const [err, res] = parse(xhr, args);
callback(err, res);
}
};
return undefined;
}
const [err, res] = parse(xhr, args);
if (err)
throw err;
return res;
};
const call = (args) => {
return new Promise((resolve, reject) => {
const cb = (err, res) => {
if (err)
reject(err);
else
resolve(res);
};
try {
request(args, cb);
} catch (e) {
reject(e);
}
});
};
const write = (str, cb) => {
request(['write', String(str)], cb);
};
const close = (code) => {
stream.flush(() => {
request(['close', code >>> 0]);
});
};
const exit = (code) => {
stream.flush(() => {
request(['exit', code >>> 0]);
});
};
/*
* Stream
*/
stream = new SendStream(write, options.isTTY, options.columns);
if (!options.headless) {
const {chrome} = global;
const isTTY = Boolean(chrome && chrome.app);
stream = options.console
? new ConsoleStream(console, isTTY)
: new DOMStream(body);
}
/*
* Process
*/
process.argv = platform.argv;
process.env = platform.env;
process.env.BMOCHA = '1';
process.env.NODE_TEST = '1';
process.stdin = new Stream();
process.stdin.readable = true;
process.stdout = stream;
process.stderr = stream;
if (options.backend)
process.env.NODE_BACKEND = options.backend;
for (const key of Object.keys(options.env)) {
const value = options.env[key];
if (value != null)
process.env[key] = value;
else
delete process.env[key];
}
if (options.headless) {
process.abort = function abort() {
exit(6 | 0x80);
};
// eslint-disable-next-line
process.exit = function _exit(code) {
if (code == null)
code = process.exitCode;
exit(code >>> 0);
};
}
/*
* Console
*/
if (!options.console) {
const format = (opts, ...args) => {
if (args.length > 0 && typeof args[0] === 'string')
return util.format(...args);
return util.inspect(args[0], opts);
};
console.log = function log(...args) {
const opts = { colors: options.colors };
const str = format(opts, ...args);
stream.write(str + '\n');
};
console.info = console.log;
console.warn = console.log;
console.error = console.log;
console.dir = function dir(obj, opts) {
if (opts == null || typeof opts !== 'object')
opts = {};
opts = Object.assign({}, opts);
if (opts.colors == null)
opts.colors = false;
if (opts.customInspect == null)
opts.customInspect = false;
const str = format(opts, obj);
stream.write(str + '\n');
};
}
/*
* FS
*/
fs.constants = platform.constants;
fs.accessSync = (file, mode) => {
if (mode == null)
mode = null;
if (typeof file !== 'string')
throw new Error('File must be a string.');
if (mode != null && typeof mode !== 'number')
throw new Error('Mode must be a number.');
return request(['access', file, mode]);
};
fs.existsSync = (file) => {
if (typeof file !== 'string')
throw new Error('File must be a string.');
try {
return request(['exists', file]);
} catch (e) {
return false;
}
};
fs.lstatSync = (file) => {
if (typeof file !== 'string')
throw new Error('File must be a string.');
return request(['lstat', file]);
};
fs.readdirSync = (path) => {
if (typeof path !== 'string')
throw new Error('Path must be a string.');
return request(['readdir', path]);
};
fs.readFileSync = (file, enc) => {
if (enc == null)
enc = null;
if (typeof file !== 'string')
throw new Error('File must be a string.');
if (enc != null && typeof enc !== 'string')
throw new Error('Encoding must be a string.');
return request(['readfile', file, enc]);
};
fs.statSync = (file) => {
if (typeof file !== 'string')
throw new Error('File must be a string.');
return request(['stat', file]);
};
fs.access = (file, mode, cb) => {
if (typeof mode === 'function') {
cb = mode;
mode = null;
}
if (mode == null)
mode = null;
if (typeof cb !== 'function')
throw new Error('Callback must be a function.');
if (typeof file !== 'string') {
cb(new Error('File must be a string.'));
return;
}
if (mode != null && typeof mode !== 'number') {
cb(new Error('Mode must be a number.'));
return;
}
request(['access', file, mode], cb);
};
fs.exists = (file, cb) => {
if (typeof cb !== 'function')
throw new Error('Callback must be a function.');
if (typeof file !== 'string') {
cb(new Error('File must be a string.'));
return;
}
request(['exists', file], (err, res) => {
cb(err ? false : res);
});
};
fs.lstat = (file, cb) => {
if (typeof cb !== 'function')
throw new Error('Callback must be a function.');
if (typeof file !== 'string') {
cb(new Error('File must be a string.'));
return;
}
request(['lstat', file], cb);
};
fs.readdir = (path, cb) => {
if (typeof cb !== 'function')
throw new Error('Callback must be a function.');
if (typeof path !== 'string') {
cb(new Error('Path must be a string.'));
return;
}
request(['readdir', path], cb);
};
fs.readFile = (file, enc, cb) => {
if (typeof enc === 'function') {
cb = enc;
enc = null;
}
if (enc == null)
enc = null;
if (typeof cb !== 'function')
throw new Error('Callback must be a function.');
if (typeof file !== 'string') {
cb(new Error('File must be a string.'));
return;
}
if (enc != null && typeof enc !== 'string') {
cb(new Error('Encoding must be a string.'));
return;
}
request(['readfile', file, enc], cb);
};
fs.stat = (file, cb) => {
if (typeof cb !== 'function')
throw new Error('Callback must be a function.');
if (typeof file !== 'string') {
cb(new Error('File must be a string.'));
return;
}
request(['stat', file], cb);
};
/*
* bfile
*/
try {
const bfs = require(__BFILE__);
const wrap = (func) => {
return function promisified(...args) {
return new Promise((resolve, reject) => {
const cb = (err, res) => {
if (func === fs.exists) {
resolve(err);
return;
}
if (err)
reject(err);
else
resolve(res);
};
args.push(cb);
try {
func(...args);
} catch (e) {
reject(e);
}
});
};
};
bfs.constants = fs.constants;
bfs.accessSync = fs.accessSync;
bfs.existsSync = fs.existsSync;
bfs.lstatSync = fs.lstatSync;
bfs.readdirSync = fs.readdirSync;
bfs.readJSONSync = function readJSONSync(file) {
return JSON.parse(bfs.readFileSync(file, 'utf8'));
};
bfs.readFileSync = fs.readFileSync;
bfs.statSync = fs.statSync;
bfs.access = wrap(fs.access);
bfs.exists = wrap(fs.exists);
bfs.lstat = wrap(fs.lstat);
bfs.readdir = wrap(fs.readdir);
bfs.readJSON = async function readJSON(file) {
return JSON.parse(await bfs.readFile(file, 'utf8'));
};
bfs.readFile = wrap(fs.readFile);
bfs.stat = wrap(fs.stat);
} catch (e) {
;
}
/*
* Workers
*/
global.register = (name, path) => {
if (typeof name !== 'string')
throw new TypeError('Name must be a string.');
if (!Array.isArray(path))
throw new TypeError('Path must be an array.');
request(['register', name, resolve(...path)]);
};
/*
* Notifications
*/
async function notify(stats) {
if (!options.headless) {
if (await common.notify(stats))
return;
}
await call(['notify', {
passes: stats.passes,
failures: stats.failures,
total: stats.total,
duration: stats.duration
}]);
}
/*
* Mocha
*/
options.stream = stream;
const mocha = new Mocha(options);
if (options.colors !== options.isTTY)
mocha.colors = options.colors;
if (options.growl)
mocha.notify = notify;
if (!options.allowUncaught)
mocha.catcher = common.catcher;
if (process.exit)
mocha.exit = process.exit;
/*
* Execute
*/
const requires = [
__REQUIRES__
];
const funcs = [
__FUNCTIONS__
];
if (options.console) {
body.innerHTML = 'Running... (press Ctrl+Shift+I) '
+ '<a href="index.js.html">[source]</a>';
}
(async () => {
for (const [loader, file] of requires)
await mocha.plugin(await loader(), file);
const code = await mocha.run(funcs);
if (mocha.results.length > 0 || mocha.failZero) {
if (!options.headless && !options.console)
stream.put('<a href="index.js.html">[source]</a>');
if (options.headless && !options.exit) {
close(code);
return;
}
}
if (options.headless && options.exit)
exit(code);
})().catch((err) => {
stream.write(err.stack + '\n');
if (options.headless)
exit(1);
});