UNPKG

double.js

Version:

Emulated float128 or double-double arithmetic. A floating point expansion with 31 accurate decimal digits.

634 lines (610 loc) 19.1 kB
const startTestMessage = (test, offset) => ({ type: "TEST_START" /* TEST_START */, data: test, offset }); const assertionMessage = (assertion, offset) => ({ type: "ASSERTION" /* ASSERTION */, data: assertion, offset }); const endTestMessage = (test, offset) => ({ type: "TEST_END" /* TEST_END */, data: test, offset }); const bailout = (error, offset) => ({ type: "BAIL_OUT" /* BAIL_OUT */, data: error, offset }); const delegateToCounter = (counter) => (target) => Object.defineProperties(target, { skipCount: { get() { return counter.skipCount; }, }, failureCount: { get() { return counter.failureCount; } }, successCount: { get() { return counter.successCount; } }, count: { get() { return counter.count; } } }); const counter = () => { let success = 0; let failure = 0; let skip = 0; return Object.defineProperties({ update(assertion) { const { pass, skip: isSkipped } = assertion; if (isSkipped) { skip++; } else if (!isAssertionResult(assertion)) { skip += assertion.skipCount; success += assertion.successCount; failure += assertion.failureCount; } else if (pass) { success++; } else { failure++; } } }, { successCount: { get() { return success; } }, failureCount: { get() { return failure; } }, skipCount: { get() { return skip; } }, count: { get() { return skip + success + failure; } } }); }; const defaultTestOptions = Object.freeze({ offset: 0, skip: false }); const noop = () => { }; const tester = (description, spec, { offset = 0, skip = false } = defaultTestOptions) => { let id = 0; let pass = true; let executionTime = 0; let error = null; const testCounter = counter(); const withTestCounter = delegateToCounter(testCounter); const assertions = []; const collect = item => assertions.push(item); const specFunction = skip === true ? noop : function zora_spec_fn() { return spec(assert(collect, offset)); }; const testRoutine = (async function () { try { const start = Date.now(); const result = await specFunction(); executionTime = Date.now() - start; return result; } catch (e) { error = e; } })(); return Object.defineProperties(withTestCounter({ [Symbol.asyncIterator]: async function* () { await testRoutine; for (const assertion of assertions) { assertion.id = ++id; if (assertion[Symbol.asyncIterator]) { // Sub test yield startTestMessage({ description: assertion.description }, offset); yield* assertion; if (assertion.error !== null) { // Bubble up the error and return error = assertion.error; pass = false; return; } } yield assertionMessage(assertion, offset); pass = pass && assertion.pass; testCounter.update(assertion); } return error !== null ? yield bailout(error, offset) : yield endTestMessage(this, offset); } }), { description: { enumerable: true, value: description }, pass: { enumerable: true, get() { return pass; } }, executionTime: { enumerable: true, get() { return executionTime; } }, length: { get() { return assertions.length; } }, error: { get() { return error; } }, routine: { value: testRoutine }, skip: { value: skip } }); }; var isArray = Array.isArray; var keyList = Object.keys; var hasProp = Object.prototype.hasOwnProperty; var fastDeepEqual = function equal(a, b) { if (a === b) return true; if (a && b && typeof a == 'object' && typeof b == 'object') { var arrA = isArray(a) , arrB = isArray(b) , i , length , key; if (arrA && arrB) { length = a.length; if (length != b.length) return false; for (i = length; i-- !== 0;) if (!equal(a[i], b[i])) return false; return true; } if (arrA != arrB) return false; var dateA = a instanceof Date , dateB = b instanceof Date; if (dateA != dateB) return false; if (dateA && dateB) return a.getTime() == b.getTime(); var regexpA = a instanceof RegExp , regexpB = b instanceof RegExp; if (regexpA != regexpB) return false; if (regexpA && regexpB) return a.toString() == b.toString(); var keys = keyList(a); length = keys.length; if (length !== keyList(b).length) return false; for (i = length; i-- !== 0;) if (!hasProp.call(b, keys[i])) return false; for (i = length; i-- !== 0;) { key = keys[i]; if (!equal(a[key], b[key])) return false; } return true; } return a!==a && b!==b; }; const isAssertionResult = (result) => { return 'operator' in result; }; const specFnRegexp = /zora_spec_fn/; const nodeInternal = /node_modules\/.*|\(internal\/.*/; const getAssertionLocation = () => { const err = new Error(); const stack = (err.stack || '') .split('\n') .filter(l => !nodeInternal.test(l) && l !== ''); const userLandIndex = stack.findIndex(l => specFnRegexp.test(l)); return (userLandIndex >= 1 ? stack[userLandIndex - 1] : (stack[stack.length - 1] || 'N/A')) .trim() .replace(/^at|^@/, ''); }; const assertMethodHook = (fn) => function (...args) { // @ts-ignore return this.collect(fn(...args)); }; const aliasMethodHook = (methodName) => function (...args) { return this[methodName](...args); }; const AssertPrototype = { equal: assertMethodHook((actual, expected, description = 'should be equivalent') => ({ pass: fastDeepEqual(actual, expected), actual, expected, description, operator: "equal" /* EQUAL */ })), equals: aliasMethodHook('equal'), eq: aliasMethodHook('equal'), deepEqual: aliasMethodHook('equal'), notEqual: assertMethodHook((actual, expected, description = 'should not be equivalent') => ({ pass: !fastDeepEqual(actual, expected), actual, expected, description, operator: "notEqual" /* NOT_EQUAL */ })), notEquals: aliasMethodHook('notEqual'), notEq: aliasMethodHook('notEqual'), notDeepEqual: aliasMethodHook('notEqual'), is: assertMethodHook((actual, expected, description = 'should be the same') => ({ pass: Object.is(actual, expected), actual, expected, description, operator: "is" /* IS */ })), same: aliasMethodHook('is'), isNot: assertMethodHook((actual, expected, description = 'should not be the same') => ({ pass: !Object.is(actual, expected), actual, expected, description, operator: "isNot" /* IS_NOT */ })), notSame: aliasMethodHook('isNot'), ok: assertMethodHook((actual, description = 'should be truthy') => ({ pass: Boolean(actual), actual, expected: 'truthy value', description, operator: "ok" /* OK */ })), truthy: aliasMethodHook('ok'), notOk: assertMethodHook((actual, description = 'should be falsy') => ({ pass: !Boolean(actual), actual, expected: 'falsy value', description, operator: "notOk" /* NOT_OK */ })), falsy: aliasMethodHook('notOk'), fail: assertMethodHook((description = 'fail called') => ({ pass: false, actual: 'fail called', expected: 'fail not called', description, operator: "fail" /* FAIL */ })), throws: assertMethodHook((func, expected, description) => { let caught; let pass; let actual; if (typeof expected === 'string') { [expected, description] = [description, expected]; } try { func(); } catch (err) { caught = { error: err }; } pass = caught !== undefined; actual = caught && caught.error; if (expected instanceof RegExp) { pass = expected.test(actual) || expected.test(actual && actual.message); actual = actual && actual.message || actual; expected = String(expected); } else if (typeof expected === 'function' && caught) { pass = actual instanceof expected; actual = actual.constructor; } return { pass, actual, expected, description: description || 'should throw', operator: "throws" /* THROWS */, }; }), doesNotThrow: assertMethodHook((func, expected, description) => { let caught; if (typeof expected === 'string') { [expected, description] = [description, expected]; } try { func(); } catch (err) { caught = { error: err }; } return { pass: caught === undefined, expected: 'no thrown error', actual: caught && caught.error, operator: "doesNotThrow" /* DOES_NOT_THROW */, description: description || 'should not throw' }; }) }; const assert = (collect, offset) => { const actualCollect = item => { if (!item.pass) { item.at = getAssertionLocation(); } collect(item); return item; }; return Object.assign(Object.create(AssertPrototype, { collect: { value: actualCollect } }), { test(description, spec, opts = defaultTestOptions) { const subTest = tester(description, spec, Object.assign({}, defaultTestOptions, opts, { offset: offset + 1 })); collect(subTest); return subTest.routine; }, skip(description, spec = noop, opts = defaultTestOptions) { return this.test(description, spec, Object.assign({}, opts, { skip: true })); } }); }; // with two arguments const curry = (fn) => (a, b) => b === void 0 ? b => fn(a, b) : fn(a, b); const toCurriedIterable = gen => curry((a, b) => ({ [Symbol.asyncIterator]() { return gen(a, b); } })); const map = toCurriedIterable(async function* (fn, asyncIterable) { let index = 0; for await (const i of asyncIterable) { yield fn(i, index, asyncIterable); index++; } }); const filter = toCurriedIterable(async function* (fn, asyncIterable) { let index = 0; for await (const i of asyncIterable) { if (fn(i, index, asyncIterable) === true) { yield i; } index++; } }); const print = (message, offset = 0) => { console.log(message.padStart(message.length + (offset * 4))); // 4 white space used as indent (see tap-parser) }; const stringifySymbol = (key, value) => { if (typeof value === 'symbol') { return value.toString(); } return value; }; const printYAML = (obj, offset = 0) => { const YAMLOffset = offset + 0.5; print('---', YAMLOffset); for (const [prop, value] of Object.entries(obj)) { print(`${prop}: ${JSON.stringify(stringifySymbol(null, value), stringifySymbol)}`, YAMLOffset + 0.5); } print('...', YAMLOffset); }; const comment = (value, offset) => { print(`# ${value}`, offset); }; const subTestPrinter = (prefix = '') => (message) => { const { data } = message; const value = `${prefix}${data.description}`; comment(value, message.offset); }; const mochaTapSubTest = subTestPrinter('Subtest: '); const tapeSubTest = subTestPrinter(); const assertPrinter = (diagnostic) => (message) => { const { data, offset } = message; const { pass, description, id } = data; const label = pass === true ? 'ok' : 'not ok'; if (isAssertionResult(data)) { print(`${label} ${id} - ${description}`, offset); if (pass === false) { printYAML(diagnostic(data), offset); } } else { const comment = data.skip === true ? 'SKIP' : `${data.executionTime}ms`; print(`${pass ? 'ok' : 'not ok'} ${id} - ${description} # ${comment}`, message.offset); } }; const tapeAssert = assertPrinter(({ id, pass, description, ...rest }) => rest); const mochaTapAssert = assertPrinter(({ expected, id, pass, description, actual, operator, at, ...rest }) => ({ wanted: expected, found: actual, at, operator, ...rest })); const testEnd = (message) => { const length = message.data.length; const { offset } = message; print(`1..${length}`, offset); }; const printBailout = (message) => { print('Bail out! Unhandled error.'); }; const reportAsMochaTap = (message) => { switch (message.type) { case "TEST_START" /* TEST_START */: mochaTapSubTest(message); break; case "ASSERTION" /* ASSERTION */: mochaTapAssert(message); break; case "TEST_END" /* TEST_END */: testEnd(message); break; case "BAIL_OUT" /* BAIL_OUT */: printBailout(); throw message.data; } }; const reportAsTapeTap = (message) => { switch (message.type) { case "TEST_START" /* TEST_START */: tapeSubTest(message); break; case "ASSERTION" /* ASSERTION */: tapeAssert(message); break; case "BAIL_OUT" /* BAIL_OUT */: printBailout(); throw message.data; } }; const flatFilter = filter((message) => { return message.type === "TEST_START" /* TEST_START */ || message.type === "BAIL_OUT" /* BAIL_OUT */ || (message.type === "ASSERTION" /* ASSERTION */ && (isAssertionResult(message.data) || message.data.skip === true)); }); const flattenStream = (stream) => { let id = 0; const mapper = map(message => { if (message.type === "ASSERTION" /* ASSERTION */) { const mappedData = Object.assign(message.data, { id: ++id }); return assertionMessage(mappedData, 0); } return Object.assign({}, message, { offset: 0 }); }); return mapper(flatFilter(stream)); }; const printSummary = (harness) => { print('', 0); comment(harness.pass ? 'ok' : 'not ok', 0); comment(`success: ${harness.successCount}`, 0); comment(`skipped: ${harness.skipCount}`, 0); comment(`failure: ${harness.failureCount}`, 0); }; const tapeTapLike = async (stream) => { print('TAP version 13'); const streamInstance = flattenStream(stream); for await (const message of streamInstance) { reportAsTapeTap(message); } print(`1..${stream.count}`, 0); printSummary(stream); }; const mochaTapLike = async (stream) => { print('TAP version 13'); for await (const message of stream) { reportAsMochaTap(message); } printSummary(stream); }; const harnessFactory = () => { const tests = []; const testCounter = counter(); const withTestCounter = delegateToCounter(testCounter); const rootOffset = 0; const collect = item => tests.push(item); const api = assert(collect, rootOffset); let pass = true; let id = 0; const instance = Object.create(api, { length: { get() { return tests.length; } }, pass: { get() { return pass; } } }); return withTestCounter(Object.assign(instance, { [Symbol.asyncIterator]: async function* () { for (const t of tests) { t.id = ++id; if (t[Symbol.asyncIterator]) { // Sub test yield startTestMessage({ description: t.description }, rootOffset); yield* t; if (t.error !== null) { pass = false; return; } } yield assertionMessage(t, rootOffset); pass = pass && t.pass; testCounter.update(t); } yield endTestMessage(this, 0); }, report: async (reporter = tapeTapLike) => { return reporter(instance); } })); }; let autoStart = true; let indent = false; const defaultTestHarness = harnessFactory(); const rootTest = defaultTestHarness.test.bind(defaultTestHarness); rootTest.indent = () => indent = true; const test = rootTest; const skip = (description, spec, options = {}) => rootTest(description, spec, Object.assign({}, options, { skip: true })); rootTest.skip = skip; const equal = defaultTestHarness.equal.bind(defaultTestHarness); const equals = equal; const eq = equal; const deepEqual = equal; const notEqual = defaultTestHarness.notEqual.bind(defaultTestHarness); const notEquals = notEqual; const notEq = notEqual; const notDeepEqual = notEqual; const is = defaultTestHarness.is.bind(defaultTestHarness); const same = is; const isNot = defaultTestHarness.isNot.bind(defaultTestHarness); const notSame = isNot; const ok = defaultTestHarness.ok.bind(defaultTestHarness); const truthy = ok; const notOk = defaultTestHarness.notOk.bind(defaultTestHarness); const falsy = notOk; const fail = defaultTestHarness.fail.bind(defaultTestHarness); const throws = defaultTestHarness.throws.bind(defaultTestHarness); const doesNotThrow = defaultTestHarness.doesNotThrow.bind(defaultTestHarness); const createHarness = () => { autoStart = false; return harnessFactory(); }; const start = () => { if (autoStart) { defaultTestHarness.report(indent ? mochaTapLike : tapeTapLike); } }; // on next tick start reporting // @ts-ignore if (typeof window === 'undefined') { setTimeout(start, 0); } else { // @ts-ignore window.addEventListener('load', start); } export { AssertPrototype, createHarness, deepEqual, doesNotThrow, eq, equal, equals, fail, falsy, is, isNot, mochaTapLike, notDeepEqual, notEq, notEqual, notEquals, notOk, notSame, ok, same, skip, tapeTapLike, test, throws, truthy };