add async parallel hooks

This commit is contained in:
Tobias Koppers 2017-08-29 16:27:59 +02:00
parent 9e3ba99fde
commit cbdd1a6063
7 changed files with 1640 additions and 8 deletions

View File

@ -0,0 +1,226 @@
"use strict";
const Hook = require("./Hook");
const simpleAsyncCases = require("./simpleAsyncCases");
class AsyncParallelBailHook extends Hook {
constructor(args) {
super(args);
this.call = this._call = undefined;
}
template(options) {
const simpleCase = simpleAsyncCases.bailing(options);
if(simpleCase) return simpleCase;
const args = options.args.join(", ");
const argsWithCallback = args ? `${args}, _callback` : "_callback";
const argsWithComma = args ? `${args}, ` : "";
const tap = options.tap;
const type = options.type;
const isInspect = tap == "inspect";
switch(`${tap} ${type}`) {
case "multiple-async async":
return `function(${argsWithCallback}) {
const _fns = this._x;
let _done = new Set();
let _currentPos = _fns.length;
let _currentError, _currentResult;
for(let _i = 0; _i < _fns.length; _i++) {
if(_i >= _currentPos) return; // early ignore
_fns[_i](${argsWithComma}((_i) => (_err, _result) => {
if(_i >= _currentPos) return; // ignore
if(_err || _result !== undefined) {
_currentPos = _i;
for(const _k of _done)
if(_k >= _i)
_done.delete(_k);
_currentError = _err;
_currentResult = _result;
} else {
_done.add(_i);
}
if(_done.size === _currentPos) {
_currentPos = 0;
_callback(_currentError, _currentResult);
}
})(_i));
}
}`;
case "multiple-async promise":
return `function(${args}) {
return new Promise((_resolve, _reject) => {
const _fns = this._x;
let _done = new Set();
let _currentPos = _fns.length;
let _currentError, _currentResult;
for(let _i = 0; _i < _fns.length; _i++) {
if(_i >= _currentPos) return; // early ignore
_fns[_i](${argsWithComma}((_i) => (_err, _result) => {
if(_i >= _currentPos) return; // ignore
if(_err || _result !== undefined) {
_currentPos = _i;
for(const _k of _done)
if(_k >= _i)
_done.delete(_k);
_currentError = _err;
_currentResult = _result;
} else {
_done.add(_i);
}
if(_done.size === _currentPos) {
_currentPos = 0;
if(_currentError) {
_reject(_currentError);
return;
}
_resolve(_currentResult);
}
})(_i));
}
});
}`;
case "multiple-promise async":
return `function(${argsWithCallback}) {
const _fns = this._x;
const _promises = _fns.map(_fn => _fn(${args}));
Promise.race(_promises).catch(() => {}); // prevent unhandled rejections
let _i = 0;
(function _next() {
_promises[_i].then(_result => {
if(_result !== undefined) {
_callback(null, _result);
return;
}
if(++_i >= _promises.length) {
_callback();
return;
}
_next();
}, _err => {
_callback(_err);
});
}());
}`;
case "multiple-promise promise":
return `function(${args}) {
const _fns = this._x;
const _promises = _fns.map(_fn => _fn(${args}));
Promise.race(_promises).catch(() => {}); // prevent unhandled rejections
let _i = 0;
return (function _next() {
return _promises[_i].then(_result => {
if(_result !== undefined) {
return _result;
}
if(++_i >= _promises.length) {
return;
}
return _next();
});
}());
}`;
case "multiple async":
case "inspect async":
return `function(${argsWithCallback}) {
const _taps = this._x;
${isInspect ? `const _inspect = this.inspectors;
for(let _j = 0; _j < _inspect.length; _j++)
_inspect[_j].call(${args});
` : ""}
const _promises = _taps.map(_tap => {
${isInspect ? `for(let _j = 0; _j < _inspect.length; _j++)
_tap = _inspect[_j].tap(_tap);
` : ""}
switch(_tap.type) {
case "sync":
try {
return Promise.resolve(_tap.fn(${args}));
} catch(_err) {
return Promise.resolve().then(() => { throw _err; });
}
case "async":
return new Promise((_resolve, _reject) => {
_tap.fn(${argsWithComma}(_err, _result) => {
if(_err) {
_reject(_err);
return;
}
_resolve(_result);
});
});
case "promise":
return Promise.resolve(_tap.fn(${args}));
}
});
Promise.race(_promises).catch(() => {}); // prevent unhandled rejections
let _i = 0;
(function _next() {
_promises[_i].then(_result => {
if(_result !== undefined) {
_callback(null, _result);
return;
}
if(++_i >= _promises.length) {
_callback();
return;
}
_next();
}, _err => {
_callback(_err);
});
}());
}`;
case "multiple promise":
case "inspect promise":
return `function(${args}) {
const _taps = this._x;
${isInspect ? `const _inspect = this.inspectors;
for(let _j = 0; _j < _inspect.length; _j++)
_inspect[_j].call(${args});
` : ""}
const _promises = _taps.map(_tap => {
${isInspect ? `for(let _j = 0; _j < _inspect.length; _j++)
_tap = _inspect[_j].tap(_tap);
` : ""}
switch(_tap.type) {
case "sync":
try {
return Promise.resolve(_tap.fn(${args}));
} catch(_err) {
return Promise.resolve().then(() => { throw _err; });
}
case "async":
return new Promise((_resolve, _reject) => {
_tap.fn(${argsWithComma}(_err, _result) => {
if(_err) {
_reject(_err);
return;
}
_resolve(_result);
});
});
case "promise":
return Promise.resolve(_tap.fn(${args}));
}
});
Promise.race(_promises).catch(() => {}); // prevent unhandled rejections
let _i = 0;
return (function _next() {
return _promises[_i].then(_result => {
if(_result !== undefined) {
return _result;
}
if(++_i >= _promises.length) {
return;
}
return _next();
});
}());
}`;
default:
throw new Error(`Unsupported tap '${tap}' or type '${type}'`);
}
}
}
module.exports = AsyncParallelBailHook;

185
lib/AsyncParallelHook.js Normal file
View File

@ -0,0 +1,185 @@
"use strict";
const Hook = require("./Hook");
const simpleAsyncCases = require("./simpleAsyncCases");
class AsyncParallelHook extends Hook {
constructor(args) {
super(args);
this.call = this._call = undefined;
}
template(options) {
const simpleCase = simpleAsyncCases.notBailing(options);
if(simpleCase) return simpleCase;
const args = options.args.join(", ");
const argsWithCallback = args ? `${args}, _callback` : "_callback";
const argsWithComma = args ? `${args}, ` : "";
const tap = options.tap;
const type = options.type;
const isInspect = tap == "inspect";
switch(`${tap} ${type}`) {
case "multiple-async async":
return `function(${argsWithCallback}) {
const _fns = this._x;
let _remaining = _fns.length;
const _handler = (_err) => {
if(_err && _remaining > 0) {
_remaining = -1;
_callback(_err);
return;
}
if(--_remaining === 0) {
_callback();
}
};
for(let _i = 0; _i < _fns.length; _i++) {
_fns[_i](${argsWithComma}_handler);
}
}`;
case "multiple-async promise":
return `function(${args}) {
return new Promise((_resolve, _reject) => {
const _fns = this._x;
let _remaining = _fns.length;
const _handler = (_err) => {
if(_err && _remaining > 0) {
_remaining = -1;
_reject(_err);
return;
}
if(--_remaining === 0) {
_resolve();
}
};
for(let _i = 0; _i < _fns.length; _i++) {
_fns[_i](${argsWithComma}_handler);
}
});
}`;
case "multiple-promise async":
return `function(${argsWithCallback}) {
const _fns = this._x;
let _remaining = _fns.length;
const _handler = () => {
if(--_remaining === 0) {
_callback();
}
}
const _handlerErr = (_err) => {
if(_remaining > 0) {
_remaining = -1;
_callback(_err);
}
}
for(let _i = 0; _i < _fns.length; _i++) {
Promise.resolve(_fns[_i](${args})).then(_handler, _handlerErr);
}
}`;
case "multiple-promise promise":
return `function(${args}) {
const _fns = this._x;
return Promise.all(_fns.map(_fn => _fn(${args}))).then(() => {});
}`;
case "multiple async":
case "inspect async":
return `function(${argsWithCallback}) {
const _taps = this._x;
${isInspect ? `const _inspect = this.inspectors;
for(let _j = 0; _j < _inspect.length; _j++)
_inspect[_j].call(${args});
` : ""}
let _remaining = _taps.length;
const _handler = (_err) => {
if(_err && _remaining > 0) {
_remaining = -1;
_callback(_err);
return;
}
if(--_remaining === 0) {
_callback();
}
};
const _handlerSuccess = () => {
if(--_remaining === 0) {
_callback();
}
}
const _handlerErr = (_err) => {
if(_remaining > 0) {
_remaining = -1;
_callback(_err);
}
}
for(let _i = 0; _i < _taps.length; _i++) {
${isInspect ? `let _tap = _taps[_i];
for(let _j = 0; _j < _inspect.length; _j++)
_tap = _inspect[_j].tap(_tap);
` : `const _tap = _taps[_i];`}
switch(_tap.type) {
case "sync":
try {
_tap.fn(${args});
} catch(_err) {
_handlerErr(_err);
break;
}
_handlerSuccess();
break;
case "async":
_tap.fn(${argsWithComma}_handler);
break;
case "promise":
Promise.resolve(_tap.fn(${args})).then(_handlerSuccess, _handlerErr);
break;
}
}
}`;
case "multiple promise":
case "inspect promise":
return `function(${args}) {
const _taps = this._x;
${isInspect ? `const _inspect = this.inspectors;
for(let _j = 0; _j < _inspect.length; _j++)
_inspect[_j].call(${args});
` : ""}
let _earlyAbort = false;
return Promise.all(_taps.map(_tap => {
if(_earlyAbort) return;
${isInspect ? `for(let _j = 0; _j < _inspect.length; _j++)
_tap = _inspect[_j].tap(_tap);
` : ""}
switch(_tap.type) {
case "sync":
try {
_tap.fn(${args});
} catch(_err) {
_earlyAbort = true;
return Promise.reject(_err);
}
return Promise.resolve();
case "async":
return new Promise((_resolve, _reject) => {
_tap.fn(${argsWithComma}_err => {
if(_err) {
_earlyAbort = true;
_reject(_err);
return;
}
_resolve();
});
});
break;
case "promise":
return _tap.fn(${args});
break;
}
})).then(() => {});
}`;
default:
throw new Error(`Unsupported tap '${tap}' or type '${type}'`);
}
}
}
module.exports = AsyncParallelHook;

View File

@ -38,16 +38,12 @@ class Hook {
createCall(type) {
const tap = this.getTapType();
if(tap === "sync")
if(tap === "sync" || tap === "async" || tap === "promise")
this._x = this.taps[0].fn;
else if(tap === "async")
this._x = this.taps[0].fn;
else if(tap === "promise")
this._x = this.taps[0].fn;
else if(tap === "inspect")
this._x = this.taps;
else
else if(tap === "multiple-sync" || tap === "multiple-async" || tap === "multiple-promise")
this._x = this.taps.map(t => t.fn);
else
this._x = this.taps;
return this.compile({
args: this._args,
tap: tap,

375
lib/__tests__/AsyncHooks.js Normal file
View File

@ -0,0 +1,375 @@
const HookTester = require("./HookTester");
const AsyncParallelHook = require("../AsyncParallelHook");
const AsyncParallelBailHook = require("../AsyncParallelBailHook");
describe("AsyncParallelHook", () => {
it("should have to correct behavior", async () => {
const tester = new HookTester((args) => new AsyncParallelHook(args));
const result = await tester.run();
expect(result).toEqual({
callNone: { error: 'hook.call is not a function' },
callNoneWithArg: { error: 'hook.call is not a function' },
callSingleSync: { error: 'hook.call is not a function' },
callSingleSyncWithArg: { error: 'hook.call is not a function' },
callMultipleSync: { error: 'hook.call is not a function' },
callMultipleSyncWithArg: { error: 'hook.call is not a function' },
callMultipleSyncWithArgs: { error: 'hook.call is not a function' },
callMultipleSyncError: { error: 'hook.call is not a function' },
callInspected: { error: 'hook.call is not a function' },
callAsyncNone: { type: 'async', value: undefined },
callAsyncNoneWithArg: { type: 'async', value: undefined },
callAsyncSingleSyncCalled1: true,
callAsyncSingleSync: { type: 'async', value: undefined },
callAsyncSingleSyncWithArgCalled1: 42,
callAsyncSingleSyncWithArg: { type: 'async', value: undefined },
callAsyncMultipleSyncCalled1: true,
callAsyncMultipleSyncCalled2: true,
callAsyncMultipleSync: { type: 'async', value: undefined },
callAsyncMultipleSyncLastReturnCalled1: true,
callAsyncMultipleSyncLastReturnCalled2: true,
callAsyncMultipleSyncLastReturn: { type: 'async', value: undefined },
callAsyncMultipleSyncNoReturnCalled1: true,
callAsyncMultipleSyncNoReturnCalled2: true,
callAsyncMultipleSyncNoReturn: { type: 'async', value: undefined },
callAsyncMultipleSyncWithArgCalled1: 42,
callAsyncMultipleSyncWithArgCalled2: 42,
callAsyncMultipleSyncWithArg: { type: 'async', value: undefined },
callAsyncSingleAsyncWithArgCalled1: 42,
callAsyncSingleAsyncWithArg: { type: 'async', value: undefined },
callAsyncMultipleAsyncWithArgCalled1: 42,
callAsyncMultipleAsyncWithArgCalled2: 42,
callAsyncMultipleAsyncWithArg: { type: 'async', value: undefined },
callAsyncSinglePromiseWithArgCalled1: 42,
callAsyncSinglePromiseWithArg: { type: 'async', value: undefined },
callAsyncMultiplePromiseWithArgCalled1: 42,
callAsyncMultiplePromiseWithArgCalled2: 42,
callAsyncMultiplePromiseWithArg: { type: 'async', value: undefined },
callAsyncMultipleMixed1WithArgCalled1: 42,
callAsyncMultipleMixed1WithArgCalled2: 42,
callAsyncMultipleMixed1WithArgCalled3: 42,
callAsyncMultipleMixed1WithArg: { type: 'async', value: undefined },
callAsyncMultipleMixed2WithArgCalled1: 42,
callAsyncMultipleMixed2WithArgCalled2: 42,
callAsyncMultipleMixed2WithArg: { type: 'async', value: undefined },
callAsyncMultipleMixed3WithArgCalled1: 42,
callAsyncMultipleMixed3WithArgCalled2: 42,
callAsyncMultipleMixed3WithArgCalled3: 42,
callAsyncMultipleMixed3WithArg: { type: 'async', value: undefined },
callAsyncMultipleSyncErrorCalled1: true,
callAsyncMultipleSyncError: { type: 'async', error: 'Error in sync2' },
callAsyncMultipleAsyncErrorCalled1: true,
callAsyncMultipleAsyncErrorCalled3: true,
callAsyncMultipleAsyncError: { type: 'async', error: 'Error in async2' },
callAsyncMultipleAsyncLateErrorCalled1: true,
callAsyncMultipleAsyncLateErrorCalled3: true,
callAsyncMultipleAsyncLateError: { type: 'async', error: 'Error in async2' },
callAsyncMultipleAsyncLateErrorEarlyResult1Called1: true,
callAsyncMultipleAsyncLateErrorEarlyResult1Called3: true,
callAsyncMultipleAsyncLateErrorEarlyResult1: { type: "async", error: "Error in async2" },
callAsyncMultipleAsyncLateErrorEarlyResult2Called1: true,
callAsyncMultipleAsyncLateErrorEarlyResult2Called3: true,
callAsyncMultipleAsyncLateErrorEarlyResult2: { type: "async", error: "Error in async2" },
callAsyncMultipleAsyncEarlyErrorCalled1: true,
callAsyncMultipleAsyncEarlyErrorCalled3: true,
callAsyncMultipleAsyncEarlyError: { type: 'async', error: 'Error in async2' },
callAsyncMultiplePromiseErrorCalled1: true,
callAsyncMultiplePromiseErrorCalled3: true,
callAsyncMultiplePromiseError: { type: 'async', error: 'Error in async2' },
callAsyncMultiplePromiseLateErrorCalled1: true,
callAsyncMultiplePromiseLateErrorCalled3: true,
callAsyncMultiplePromiseLateError: { type: 'async', error: 'Error in async2' },
callAsyncMultiplePromiseEarlyErrorCalled1: true,
callAsyncMultiplePromiseEarlyErrorCalled3: true,
callAsyncMultiplePromiseEarlyError: { type: 'async', error: 'Error in async2' },
callAsyncMultipleMixedError1WithArgCalled1: 42,
callAsyncMultipleMixedError1WithArgCalled2: 42,
callAsyncMultipleMixedError1WithArgCalled3: 42,
callAsyncMultipleMixedError1WithArg: { type: 'async', error: 'Error in sync' },
callAsyncMultipleMixedError2WithArgCalled1: 42,
callAsyncMultipleMixedError2WithArgCalled2: 42,
callAsyncMultipleMixedError2WithArgCalled3: 42,
callAsyncMultipleMixedError2WithArg: { type: 'async', error: 'Error in promise' },
callAsyncMultipleMixedError3WithArgCalled1: 42,
callAsyncMultipleMixedError3WithArgCalled2: 42,
callAsyncMultipleMixedError3WithArgCalled3: 42,
callAsyncMultipleMixedError3WithArg: { type: 'async', error: 'Error in async' },
callAsyncMultipleMixedLateErrorCalled1: true,
callAsyncMultipleMixedLateErrorCalled2: true,
callAsyncMultipleMixedLateErrorCalled3: true,
callAsyncMultipleMixedLateError: { type: 'async', error: 'Error in async' },
promiseNone: { type: 'promise', value: undefined },
promiseNoneWithArg: { type: 'promise', value: undefined },
promiseSingleSyncCalled1: true,
promiseSingleSync: { type: 'promise', value: undefined },
promiseSingleSyncWithArgCalled1: 42,
promiseSingleSyncWithArg: { type: 'promise', value: undefined },
promiseMultipleSyncCalled1: true,
promiseMultipleSyncCalled2: true,
promiseMultipleSync: { type: 'promise', value: undefined },
promiseMultipleSyncLastReturnCalled1: true,
promiseMultipleSyncLastReturnCalled2: true,
promiseMultipleSyncLastReturn: { type: 'promise', value: undefined },
promiseMultipleSyncNoReturnCalled1: true,
promiseMultipleSyncNoReturnCalled2: true,
promiseMultipleSyncNoReturn: { type: 'promise', value: undefined },
promiseMultipleSyncWithArgCalled1: 42,
promiseMultipleSyncWithArgCalled2: 42,
promiseMultipleSyncWithArg: { type: 'promise', value: undefined },
promiseSingleAsyncWithArgCalled1: 42,
promiseSingleAsyncWithArg: { type: 'promise', value: undefined },
promiseMultipleAsyncWithArgCalled1: 42,
promiseMultipleAsyncWithArgCalled2: 42,
promiseMultipleAsyncWithArg: { type: 'promise', value: undefined },
promiseSinglePromiseWithArgCalled1: 42,
promiseSinglePromiseWithArg: { type: 'promise', value: undefined },
promiseMultiplePromiseWithArgCalled1: 42,
promiseMultiplePromiseWithArgCalled2: 42,
promiseMultiplePromiseWithArg: { type: 'promise', value: undefined },
promiseMultipleMixed1WithArgCalled1: 42,
promiseMultipleMixed1WithArgCalled2: 42,
promiseMultipleMixed1WithArgCalled3: 42,
promiseMultipleMixed1WithArg: { type: 'promise', value: undefined },
promiseMultipleMixed2WithArgCalled1: 42,
promiseMultipleMixed2WithArgCalled2: 42,
promiseMultipleMixed2WithArg: { type: 'promise', value: undefined },
promiseMultipleMixed3WithArgCalled1: 42,
promiseMultipleMixed3WithArgCalled2: 42,
promiseMultipleMixed3WithArgCalled3: 42,
promiseMultipleMixed3WithArg: { type: 'promise', value: undefined },
promiseMultipleSyncErrorCalled1: true,
promiseMultipleSyncError: { type: 'promise', error: 'Error in sync2' },
promiseMultipleAsyncErrorCalled1: true,
promiseMultipleAsyncErrorCalled3: true,
promiseMultipleAsyncError: { type: 'promise', error: 'Error in async2' },
promiseMultipleAsyncLateErrorCalled1: true,
promiseMultipleAsyncLateErrorCalled3: true,
promiseMultipleAsyncLateError: { type: 'promise', error: 'Error in async2' },
promiseMultipleAsyncLateErrorEarlyResult1Called1: true,
promiseMultipleAsyncLateErrorEarlyResult1Called3: true,
promiseMultipleAsyncLateErrorEarlyResult1: { type: 'promise', error: 'Error in async2' },
promiseMultipleAsyncLateErrorEarlyResult2Called1: true,
promiseMultipleAsyncLateErrorEarlyResult2Called3: true,
promiseMultipleAsyncLateErrorEarlyResult2: { type: 'promise', error: 'Error in async2' },
promiseMultipleAsyncEarlyErrorCalled1: true,
promiseMultipleAsyncEarlyErrorCalled3: true,
promiseMultipleAsyncEarlyError: { type: 'promise', error: 'Error in async2' },
promiseMultiplePromiseErrorCalled1: true,
promiseMultiplePromiseErrorCalled3: true,
promiseMultiplePromiseError: { type: 'promise', error: 'Error in async2' },
promiseMultiplePromiseLateErrorCalled1: true,
promiseMultiplePromiseLateErrorCalled3: true,
promiseMultiplePromiseLateError: { type: 'promise', error: 'Error in async2' },
promiseMultiplePromiseEarlyErrorCalled1: true,
promiseMultiplePromiseEarlyErrorCalled3: true,
promiseMultiplePromiseEarlyError: { type: 'promise', error: 'Error in async2' },
promiseMultipleMixedError1WithArgCalled1: 42,
promiseMultipleMixedError1WithArgCalled2: 42,
promiseMultipleMixedError1WithArgCalled3: 42,
promiseMultipleMixedError1WithArg: { type: 'promise', error: 'Error in sync' },
promiseMultipleMixedError2WithArgCalled1: 42,
promiseMultipleMixedError2WithArgCalled2: 42,
promiseMultipleMixedError2WithArgCalled3: 42,
promiseMultipleMixedError2WithArg: { type: 'promise', error: 'Error in promise' },
promiseMultipleMixedError3WithArgCalled1: 42,
promiseMultipleMixedError3WithArg: { type: 'promise', error: 'Error in async' },
promiseMultipleMixedLateErrorCalled1: true,
promiseMultipleMixedLateErrorCalled2: true,
promiseMultipleMixedLateErrorCalled3: true,
promiseMultipleMixedLateError: { type: 'promise', error: 'Error in async' },
callAsyncInspectedCall1: [ 1, 2, 3 ],
callAsyncInspectedCall2: [ 1, 2, 3 ],
callAsyncInspectedTap1: { type: 'promise', fn: 2, name: 'promise' },
callAsyncInspectedTap2: { type: 'sync', fn: 3, name: 'sync' },
callAsyncInspected: { type: 'async', value: undefined },
promiseInspectedCall1: [ 1, 2, 3 ],
promiseInspectedCall2: [ 1, 2, 3 ],
promiseInspectedTap1: { type: 'promise', fn: 2, name: 'promise' },
promiseInspectedTap2: { type: 'sync', fn: 3, name: 'sync' },
promiseInspected: { type: 'promise', value: undefined }
})
})
})
describe("AsyncParallelBailHook", () => {
it("should have to correct behavior", async () => {
const tester = new HookTester((args) => new AsyncParallelBailHook(args));
const result = await tester.run();
expect(result).toEqual({
callNone: { error: "hook.call is not a function" },
callNoneWithArg: { error: "hook.call is not a function" },
callSingleSync: { error: "hook.call is not a function" },
callSingleSyncWithArg: { error: "hook.call is not a function" },
callMultipleSync: { error: "hook.call is not a function" },
callMultipleSyncWithArg: { error: "hook.call is not a function" },
callMultipleSyncWithArgs: { error: "hook.call is not a function" },
callMultipleSyncError: { error: "hook.call is not a function" },
callInspected: { error: "hook.call is not a function" },
callAsyncNone: { type: "async", value: undefined },
callAsyncNoneWithArg: { type: "async", value: undefined },
callAsyncSingleSyncCalled1: true,
callAsyncSingleSync: { type: "async", value: 42 },
callAsyncSingleSyncWithArgCalled1: 42,
callAsyncSingleSyncWithArg: { type: "async", value: 42 },
callAsyncMultipleSyncCalled1: true,
callAsyncMultipleSync: { type: "async", value: 42 },
callAsyncMultipleSyncLastReturnCalled1: true,
callAsyncMultipleSyncLastReturnCalled2: true,
callAsyncMultipleSyncLastReturn: { type: "async", value: 43 },
callAsyncMultipleSyncNoReturnCalled1: true,
callAsyncMultipleSyncNoReturnCalled2: true,
callAsyncMultipleSyncNoReturn: { type: "async", value: undefined },
callAsyncMultipleSyncWithArgCalled1: 42,
callAsyncMultipleSyncWithArg: { type: "async", value: 42 },
callAsyncSingleAsyncWithArgCalled1: 42,
callAsyncSingleAsyncWithArg: { type: "async", value: 42 },
callAsyncMultipleAsyncWithArgCalled1: 42,
callAsyncMultipleAsyncWithArg: { type: "async", value: 42 },
callAsyncSinglePromiseWithArgCalled1: 42,
callAsyncSinglePromiseWithArg: { type: "async", value: 42 },
callAsyncMultiplePromiseWithArgCalled1: 42,
callAsyncMultiplePromiseWithArgCalled2: 42,
callAsyncMultiplePromiseWithArg: { type: "async", value: 42 },
callAsyncMultipleMixed1WithArgCalled1: 42,
callAsyncMultipleMixed1WithArgCalled2: 42,
callAsyncMultipleMixed1WithArgCalled3: 42,
callAsyncMultipleMixed1WithArg: { type: "async", value: 42 },
callAsyncMultipleMixed2WithArgCalled1: 42,
callAsyncMultipleMixed2WithArgCalled2: 42,
callAsyncMultipleMixed2WithArg: { type: "async", value: 42 },
callAsyncMultipleMixed3WithArgCalled1: 42,
callAsyncMultipleMixed3WithArgCalled2: 42,
callAsyncMultipleMixed3WithArgCalled3: 42,
callAsyncMultipleMixed3WithArg: { type: "async", value: 42 },
callAsyncMultipleSyncErrorCalled1: true,
callAsyncMultipleSyncError: { type: "async", error: "Error in sync2" },
callAsyncMultipleAsyncErrorCalled1: true,
callAsyncMultipleAsyncError: { type: "async", error: "Error in async2" },
callAsyncMultipleAsyncLateErrorCalled1: true,
callAsyncMultipleAsyncLateErrorCalled3: true,
callAsyncMultipleAsyncLateError: { type: "async", error: "Error in async2" },
callAsyncMultipleAsyncLateErrorEarlyResult1Called1: true,
callAsyncMultipleAsyncLateErrorEarlyResult1Called3: true,
callAsyncMultipleAsyncLateErrorEarlyResult1: { type: "async", error: "Error in async2" },
callAsyncMultipleAsyncLateErrorEarlyResult2Called1: true,
callAsyncMultipleAsyncLateErrorEarlyResult2Called3: true,
callAsyncMultipleAsyncLateErrorEarlyResult2: { type: "async", value: 42 },
callAsyncMultipleAsyncEarlyErrorCalled1: true,
callAsyncMultipleAsyncEarlyError: { type: "async", error: "Error in async2" },
callAsyncMultiplePromiseErrorCalled1: true,
callAsyncMultiplePromiseErrorCalled3: true,
callAsyncMultiplePromiseError: { type: "async", error: "Error in async2" },
callAsyncMultiplePromiseLateErrorCalled1: true,
callAsyncMultiplePromiseLateErrorCalled3: true,
callAsyncMultiplePromiseLateError: { type: "async", error: "Error in async2" },
callAsyncMultiplePromiseEarlyErrorCalled1: true,
callAsyncMultiplePromiseEarlyErrorCalled3: true,
callAsyncMultiplePromiseEarlyError: { type: "async", error: "Error in async2" },
callAsyncMultipleMixedError1WithArgCalled1: 42,
callAsyncMultipleMixedError1WithArgCalled2: 42,
callAsyncMultipleMixedError1WithArgCalled3: 42,
callAsyncMultipleMixedError1WithArg: { type: "async", value: 42 },
callAsyncMultipleMixedError2WithArgCalled1: 42,
callAsyncMultipleMixedError2WithArgCalled2: 42,
callAsyncMultipleMixedError2WithArgCalled3: 42,
callAsyncMultipleMixedError2WithArg: { type: "async", value: 42 },
callAsyncMultipleMixedError3WithArgCalled1: 42,
callAsyncMultipleMixedError3WithArgCalled2: 42,
callAsyncMultipleMixedError3WithArgCalled3: 42,
callAsyncMultipleMixedError3WithArg: { type: "async", error: "Error in async" },
callAsyncMultipleMixedLateErrorCalled1: true,
callAsyncMultipleMixedLateErrorCalled2: true,
callAsyncMultipleMixedLateErrorCalled3: true,
callAsyncMultipleMixedLateError: { type: "async", error: "Error in async" },
promiseNone: { type: "promise", value: undefined },
promiseNoneWithArg: { type: "promise", value: undefined },
promiseSingleSyncCalled1: true,
promiseSingleSync: { type: "promise", value: 42 },
promiseSingleSyncWithArgCalled1: 42,
promiseSingleSyncWithArg: { type: "promise", value: 42 },
promiseMultipleSyncCalled1: true,
promiseMultipleSync: { type: "promise", value: 42 },
promiseMultipleSyncLastReturnCalled1: true,
promiseMultipleSyncLastReturnCalled2: true,
promiseMultipleSyncLastReturn: { type: "promise", value: 43 },
promiseMultipleSyncNoReturnCalled1: true,
promiseMultipleSyncNoReturnCalled2: true,
promiseMultipleSyncNoReturn: { type: "promise", value: undefined },
promiseMultipleSyncWithArgCalled1: 42,
promiseMultipleSyncWithArg: { type: "promise", value: 42 },
promiseSingleAsyncWithArgCalled1: 42,
promiseSingleAsyncWithArg: { type: "promise", value: 42 },
promiseMultipleAsyncWithArgCalled1: 42,
promiseMultipleAsyncWithArg: { type: "promise", value: 42 },
promiseSinglePromiseWithArgCalled1: 42,
promiseSinglePromiseWithArg: { type: "promise", value: 42 },
promiseMultiplePromiseWithArgCalled1: 42,
promiseMultiplePromiseWithArgCalled2: 42,
promiseMultiplePromiseWithArg: { type: "promise", value: 42 },
promiseMultipleMixed1WithArgCalled1: 42,
promiseMultipleMixed1WithArgCalled2: 42,
promiseMultipleMixed1WithArgCalled3: 42,
promiseMultipleMixed1WithArg: { type: "promise", value: 42 },
promiseMultipleMixed2WithArgCalled1: 42,
promiseMultipleMixed2WithArgCalled2: 42,
promiseMultipleMixed2WithArg: { type: "promise", value: 42 },
promiseMultipleMixed3WithArgCalled1: 42,
promiseMultipleMixed3WithArgCalled2: 42,
promiseMultipleMixed3WithArgCalled3: 42,
promiseMultipleMixed3WithArg: { type: "promise", value: 42 },
promiseMultipleSyncErrorCalled1: true,
promiseMultipleSyncError: { type: "promise", error: "Error in sync2" },
promiseMultipleAsyncErrorCalled1: true,
promiseMultipleAsyncError: { type: "promise", error: "Error in async2" },
promiseMultipleAsyncLateErrorCalled1: true,
promiseMultipleAsyncLateErrorCalled3: true,
promiseMultipleAsyncLateError: { type: "promise", error: "Error in async2" },
promiseMultipleAsyncLateErrorEarlyResult1Called1: true,
promiseMultipleAsyncLateErrorEarlyResult1Called3: true,
promiseMultipleAsyncLateErrorEarlyResult1: { type: 'promise', error: 'Error in async2' },
promiseMultipleAsyncLateErrorEarlyResult2Called1: true,
promiseMultipleAsyncLateErrorEarlyResult2Called3: true,
promiseMultipleAsyncLateErrorEarlyResult2: { type: 'promise', value: 42 },
promiseMultipleAsyncEarlyErrorCalled1: true,
promiseMultipleAsyncEarlyError: { type: "promise", error: "Error in async2" },
promiseMultiplePromiseErrorCalled1: true,
promiseMultiplePromiseErrorCalled3: true,
promiseMultiplePromiseError: { type: "promise", error: "Error in async2" },
promiseMultiplePromiseLateErrorCalled1: true,
promiseMultiplePromiseLateErrorCalled3: true,
promiseMultiplePromiseLateError: { type: "promise", error: "Error in async2" },
promiseMultiplePromiseEarlyErrorCalled1: true,
promiseMultiplePromiseEarlyErrorCalled3: true,
promiseMultiplePromiseEarlyError: { type: "promise", error: "Error in async2" },
promiseMultipleMixedError1WithArgCalled1: 42,
promiseMultipleMixedError1WithArgCalled2: 42,
promiseMultipleMixedError1WithArgCalled3: 42,
promiseMultipleMixedError1WithArg: { type: "promise", value: 42 },
promiseMultipleMixedError2WithArgCalled1: 42,
promiseMultipleMixedError2WithArgCalled2: 42,
promiseMultipleMixedError2WithArgCalled3: 42,
promiseMultipleMixedError2WithArg: { type: "promise", value: 42 },
promiseMultipleMixedError3WithArgCalled1: 42,
promiseMultipleMixedError3WithArgCalled2: 42,
promiseMultipleMixedError3WithArgCalled3: 42,
promiseMultipleMixedError3WithArg: { type: "promise", error: "Error in async" },
promiseMultipleMixedLateErrorCalled1: true,
promiseMultipleMixedLateErrorCalled2: true,
promiseMultipleMixedLateErrorCalled3: true,
promiseMultipleMixedLateError: { type: "promise", error: "Error in async" },
callAsyncInspectedCall1: [1, 2, 3],
callAsyncInspectedTap1: { fn: 2, name: "promise", type: "promise" },
callAsyncInspectedCall2: [1, 2, 3],
callAsyncInspectedTap2: { fn: 3, name: "sync", type: "sync" },
callAsyncInspected: { type: "async", value: 6 },
promiseInspectedCall1: [1, 2, 3],
promiseInspectedTap1: { fn: 2, name: "promise", type: "promise" },
promiseInspectedCall2: [1, 2, 3],
promiseInspectedTap2: { fn: 3, name: "sync", type: "sync" },
promiseInspected: { type: "promise", value: 6 },
})
})
})

597
lib/__tests__/HookTester.js Normal file
View File

@ -0,0 +1,597 @@
describe("HookTester", () => {
it("should run", () => {});
});
process.on("unhandledRejection", err => console.error(err.stack));
class HookTester {
constructor(createHook, sync) {
this.createHook = createHook;
this.sync = sync;
}
async run(syncOnly) {
const createHook = this.createHook;
const result = {};
await this.runSync(result);
if(!syncOnly) {
await this.runAsync(result, "callAsync");
await this.runAsync(result, "promise");
await this.runInspect(result, "callAsync");
await this.runInspect(result, "promise");
}
return result;
}
async runSync(result) {
const createHook = this.createHook;
{
const hook = createHook([], "callNone");
result.callNone = await this.gainResult(() => hook.call());
}
{
const hook = createHook(["arg"], "callNoneWithArg");
result.callNoneWithArg = await this.gainResult(() => hook.call(42));
}
{
const hook = createHook([], "callSingleSync");
hook.tap("sync", () => {
result.callSingleSyncCalled = true;
return 42;
});
result.callSingleSync = await this.gainResult(() => hook.call());
}
{
const hook = createHook(["myArg"], "callSingleSyncWithArg");
hook.tap("sync", (nr) => {
result.callSingleSyncWithArgCalled = nr;
return nr;
});
result.callSingleSyncWithArg = await this.gainResult(() => hook.call(42));
}
{
const hook = createHook([], "callMultipleSync");
hook.tap("sync1", () => {
result.callMultipleSyncCalled1 = true;
return 42;
});
hook.tap("sync2", () => {
result.callMultipleSyncCalled2 = true;
return 43;
});
result.callMultipleSync = await this.gainResult(() => hook.call());
}
{
const hook = createHook(["a"], "callMultipleSyncWithArg");
hook.tap("sync1", (a) => {
result.callMultipleSyncWithArgCalled1 = a;
return 42 + a;
});
hook.tap("sync2", (a) => {
result.callMultipleSyncWithArgCalled2 = a;
return 43 + a;
});
result.callMultipleSyncWithArg = await this.gainResult(() => hook.call(42));
}
{
const hook = createHook(["a", "b", "c"], "callMultipleSyncWithArgs");
hook.tap("sync1", (a, b, c) => {
result.callMultipleSyncWithArgsCalled1 = [a, b, c];
return a + b + c;
});
hook.tap("sync2", (a, b, c) => {
result.callMultipleSyncWithArgsCalled2 = [a, b, c];
return a + b + c + 1;
});
result.callMultipleSyncWithArgs = await this.gainResult(() => hook.call(42, 43, 44));
}
{
const hook = createHook([], "callMultipleSyncError");
hook.tap("sync1", () => {
result.callMultipleSyncErrorCalled1 = true;
});
hook.tap("sync2", () => {
result.callMultipleSyncErrorCalled2 = true;
throw new Error("Error in sync2")
});
hook.tap("sync3", () => {
result.callMultipleSyncErrorCalled3 = true;
});
result.callMultipleSyncError = await this.gainResult(() => hook.call());
}
{
const hook = createHook(["a", "b", "c"], "callInspected");
hook.inspect({
call: (a, b, c) => {
result.callInspectedCall1 = [a, b, c];
},
tap: (tap) => {
result.callInspectedTap1 = Object.assign({}, tap, { fn: tap.fn.length });
return tap;
}
});
hook.inspect({
call: (a, b, c) => {
result.callInspectedCall2 = [a, b, c];
},
tap: (tap) => {
if(!result.callInspectedTap2)
result.callInspectedTap2 = Object.assign({}, tap, { fn: tap.fn.length });
return tap;
}
});
hook.tap("sync1", (a, b, c) => a + b + c);
hook.tap("sync2", (a, b) => a + b + 1);
result.callInspected = await this.gainResult((cb) => hook.call(1, 2, 3, cb));
}
}
async runAsync(result, type) {
const createHook = this.createHook;
{
const hook = createHook([], `${type}None`);
result[`${type}None`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook(["arg"], `${type}NoneWithArg`);
result[`${type}NoneWithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook([], `${type}SingleSync`);
hook.tap("sync", () => {
result[`${type}SingleSyncCalled1`] = true;
return 42;
});
result[`${type}SingleSync`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook(["x"], `${type}SingleSyncWithArg`);
hook.tap("sync", arg => {
result[`${type}SingleSyncWithArgCalled1`] = arg;
return arg;
});
result[`${type}SingleSyncWithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook([], `${type}MultipleSync`);
hook.tap("sync1", () => {
result[`${type}MultipleSyncCalled1`] = true;
return 42;
});
hook.tap("sync2", () => {
result[`${type}MultipleSyncCalled2`] = true;
return 43;
});
result[`${type}MultipleSync`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook([], `${type}MultipleSyncLastReturn`);
hook.tap("sync1", () => {
result[`${type}MultipleSyncLastReturnCalled1`] = true;
});
hook.tap("sync2", () => {
result[`${type}MultipleSyncLastReturnCalled2`] = true;
return 43;
});
result[`${type}MultipleSyncLastReturn`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook([], `${type}MultipleSyncNoReturn`);
hook.tap("sync1", () => {
result[`${type}MultipleSyncNoReturnCalled1`] = true;
});
hook.tap("sync2", () => {
result[`${type}MultipleSyncNoReturnCalled2`] = true;
});
result[`${type}MultipleSyncNoReturn`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook(["arg"], `${type}MultipleSyncWithArg`);
hook.tap("sync1", arg => {
result[`${type}MultipleSyncWithArgCalled1`] = arg;
return arg;
});
hook.tap("sync2", arg => {
result[`${type}MultipleSyncWithArgCalled2`] = arg;
return arg + 1;
});
result[`${type}MultipleSyncWithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook(["x"], `${type}SingleAsyncWithArg`);
hook.tapAsync("async", (arg, callback) => {
result[`${type}SingleAsyncWithArgCalled1`] = arg;
callback(null, arg);
});
result[`${type}SingleAsyncWithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook(["x"], `${type}MultipleAsyncWithArg`);
hook.tapAsync("async1", (arg, callback) => {
result[`${type}MultipleAsyncWithArgCalled1`] = arg;
callback(null, arg);
});
hook.tapAsync("async2", (arg, callback) => {
result[`${type}MultipleAsyncWithArgCalled2`] = arg;
callback(null, arg + 1);
});
result[`${type}MultipleAsyncWithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook(["x"], `${type}SinglePromiseWithArg`);
hook.tapPromise("promise", arg => {
result[`${type}SinglePromiseWithArgCalled1`] = arg;
return Promise.resolve(arg);
});
result[`${type}SinglePromiseWithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook(["x"], `${type}MultiplePromiseWithArg`);
hook.tapPromise("promise1", arg => {
result[`${type}MultiplePromiseWithArgCalled1`] = arg;
return Promise.resolve(arg);
});
hook.tapPromise("promise2", arg => {
result[`${type}MultiplePromiseWithArgCalled2`] = arg;
return Promise.resolve(arg + 1);
});
result[`${type}MultiplePromiseWithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook(["x"], `${type}MultipleMixed1WithArg`);
hook.tapAsync("async", (arg, callback) => {
result[`${type}MultipleMixed1WithArgCalled1`] = arg;
callback(null, arg);
});
hook.tapPromise("promise", arg => {
result[`${type}MultipleMixed1WithArgCalled2`] = arg;
return Promise.resolve(arg + 1);
});
hook.tap("sync", arg => {
result[`${type}MultipleMixed1WithArgCalled3`] = arg;
return arg + 2;
});
result[`${type}MultipleMixed1WithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook(["x"], `${type}MultipleMixed2WithArg`);
hook.tapAsync("async", (arg, callback) => {
result[`${type}MultipleMixed2WithArgCalled1`] = arg;
setTimeout(() => callback(null, arg), 100);
});
hook.tapPromise("promise", arg => {
result[`${type}MultipleMixed2WithArgCalled2`] = arg;
return Promise.resolve(arg + 1);
});
result[`${type}MultipleMixed2WithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook(["x"], `${type}MultipleMixed3WithArg`);
hook.tapAsync("async1", (arg, callback) => {
result[`${type}MultipleMixed3WithArgCalled1`] = arg;
callback(null, arg);
});
hook.tapPromise("promise", arg => {
result[`${type}MultipleMixed3WithArgCalled2`] = arg;
return Promise.resolve(arg + 1);
});
hook.tapAsync("async2", (arg, callback) => {
result[`${type}MultipleMixed3WithArgCalled3`] = arg;
setTimeout(() => callback(null, arg), 100);
});
result[`${type}MultipleMixed3WithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook([], `${type}MultipleSyncError`);
hook.tap("sync1", () => {
result[`${type}MultipleSyncErrorCalled1`] = true;
});
hook.tap("sync2", () => {
throw new Error("Error in sync2")
});
hook.tap("sync3", () => {
result[`${type}MultipleSyncErrorCalled3`] = true;
});
result[`${type}MultipleSyncError`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook([], `${type}MultipleAsyncError`);
hook.tapAsync("async1", (callback) => {
result[`${type}MultipleAsyncErrorCalled1`] = true;
callback();
});
hook.tapAsync("async2", (callback) => {
callback(new Error("Error in async2"));
});
hook.tapAsync("async3", (callback) => {
result[`${type}MultipleAsyncErrorCalled3`] = true;
callback();
});
result[`${type}MultipleAsyncError`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook([], `${type}MultipleAsyncLateError`);
hook.tapAsync("async1", (callback) => {
result[`${type}MultipleAsyncLateErrorCalled1`] = true;
callback();
});
hook.tapAsync("async2", (callback) => {
setTimeout(() => callback(new Error("Error in async2")), 100);
});
hook.tapAsync("async3", (callback) => {
result[`${type}MultipleAsyncLateErrorCalled3`] = true;
callback();
});
result[`${type}MultipleAsyncLateError`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook([], `${type}MultipleAsyncLateErrorEarlyResult1`);
hook.tapAsync("async1", (callback) => {
result[`${type}MultipleAsyncLateErrorEarlyResult1Called1`] = true;
callback();
});
hook.tapAsync("async2", (callback) => {
setTimeout(() => callback(new Error("Error in async2")), 100);
});
hook.tapAsync("async3", (callback) => {
result[`${type}MultipleAsyncLateErrorEarlyResult1Called3`] = true;
callback(null, 7);
});
result[`${type}MultipleAsyncLateErrorEarlyResult1`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook([], `${type}MultipleAsyncLateErrorEarlyResult2`);
hook.tapAsync("async1", (callback) => {
result[`${type}MultipleAsyncLateErrorEarlyResult2Called1`] = true;
setTimeout(() => callback(null, 42), 200);
});
hook.tapAsync("async2", (callback) => {
setTimeout(() => callback(new Error("Error in async2")), 100);
});
hook.tapAsync("async3", (callback) => {
result[`${type}MultipleAsyncLateErrorEarlyResult2Called3`] = true;
callback(null, 7);
});
result[`${type}MultipleAsyncLateErrorEarlyResult2`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook([], `${type}MultipleAsyncEarlyError`);
hook.tapAsync("async1", (callback) => {
result[`${type}MultipleAsyncEarlyErrorCalled1`] = true;
setTimeout(() => callback(), 100);
});
hook.tapAsync("async2", (callback) => {
callback(new Error("Error in async2"));
});
hook.tapAsync("async3", (callback) => {
result[`${type}MultipleAsyncEarlyErrorCalled3`] = true;
setTimeout(() => callback(), 100);
});
result[`${type}MultipleAsyncEarlyError`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook([], `${type}MultiplePromiseError`);
hook.tapPromise("promise1", () => {
result[`${type}MultiplePromiseErrorCalled1`] = true;
return Promise.resolve();
});
hook.tapPromise("promise2", () => {
return Promise.resolve().then(() => { throw new Error("Error in async2"); });
});
hook.tapPromise("promise3", () => {
result[`${type}MultiplePromiseErrorCalled3`] = true;
return Promise.resolve();
});
result[`${type}MultiplePromiseError`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook([], `${type}MultiplePromiseLateError`);
hook.tapPromise("promise1", () => {
result[`${type}MultiplePromiseLateErrorCalled1`] = true;
return Promise.resolve();
});
hook.tapPromise("promise2", () => {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Error in async2")), 100);
});
});
hook.tapPromise("promise3", () => {
result[`${type}MultiplePromiseLateErrorCalled3`] = true;
return Promise.resolve();
});
result[`${type}MultiplePromiseLateError`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook([], `${type}MultiplePromiseEarlyError`);
hook.tapPromise("promise1", () => {
result[`${type}MultiplePromiseEarlyErrorCalled1`] = true;
return new Promise(resolve => setTimeout(() => resolve(), 100));
});
hook.tapPromise("promise2", () => {
return Promise.resolve().then(() => { throw new Error("Error in async2"); });
});
hook.tapPromise("promise3", () => {
result[`${type}MultiplePromiseEarlyErrorCalled3`] = true;
return new Promise(resolve => setTimeout(() => resolve(), 100));
});
result[`${type}MultiplePromiseEarlyError`] = await this.gainResult((cb) => hook[type](cb));
}
{
const hook = createHook(["x"], `${type}MultipleMixedError1WithArg`);
hook.tapAsync("async", (arg, callback) => {
result[`${type}MultipleMixedError1WithArgCalled1`] = arg;
callback(null, arg);
});
hook.tapPromise("promise", arg => {
result[`${type}MultipleMixedError1WithArgCalled2`] = arg;
return Promise.resolve(arg + 1);
});
hook.tap("sync", arg => {
result[`${type}MultipleMixedError1WithArgCalled3`] = arg;
throw new Error("Error in sync");
});
result[`${type}MultipleMixedError1WithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook(["x"], `${type}MultipleMixedError2WithArg`);
hook.tapAsync("async", (arg, callback) => {
result[`${type}MultipleMixedError2WithArgCalled1`] = arg;
callback(null, arg);
});
hook.tapPromise("promise", arg => {
result[`${type}MultipleMixedError2WithArgCalled2`] = arg;
return Promise.resolve().then(() => { throw new Error("Error in promise"); });
});
hook.tap("sync", arg => {
result[`${type}MultipleMixedError2WithArgCalled3`] = arg;
return arg + 2;
});
result[`${type}MultipleMixedError2WithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook(["x"], `${type}MultipleMixedError3WithArg`);
hook.tapAsync("async", (arg, callback) => {
result[`${type}MultipleMixedError3WithArgCalled1`] = arg;
callback(new Error("Error in async"));
});
hook.tapPromise("promise", arg => {
result[`${type}MultipleMixedError3WithArgCalled2`] = arg;
return Promise.resolve(arg + 1);
});
hook.tap("sync", arg => {
result[`${type}MultipleMixedError3WithArgCalled3`] = arg;
return arg + 2;
});
result[`${type}MultipleMixedError3WithArg`] = await this.gainResult((cb) => hook[type](42, cb));
}
{
const hook = createHook([], `${type}MultipleMixedLateError`);
hook.tapAsync("async", (callback) => {
result[`${type}MultipleMixedLateErrorCalled1`] = true;
setTimeout(() => callback(new Error("Error in async")), 100);
});
hook.tapPromise("promise", () => {
result[`${type}MultipleMixedLateErrorCalled2`] = true;
return Promise.resolve(42);
});
hook.tap("sync", () => {
result[`${type}MultipleMixedLateErrorCalled3`] = true;
return 43;
});
result[`${type}MultipleMixedLateError`] = await this.gainResult((cb) => hook[type](cb));
}
}
async runInspect(result, type) {
const createHook = this.createHook;
{
const hook = createHook(["a", "b", "c"], `${type}Inspected`);
hook.inspect({
call: (a, b, c) => {
result[`${type}InspectedCall1`] = [a, b, c];
},
tap: (tap) => {
result[`${type}InspectedTap1`] = Object.assign({}, tap, { fn: tap.fn.length });
return tap;
}
});
hook.inspect({
call: (a, b, c) => {
result[`${type}InspectedCall2`] = [a, b, c];
},
tap: (tap) => {
if(!result[`${type}InspectedTap2`])
result[`${type}InspectedTap2`] = Object.assign({}, tap, { fn: tap.fn.length });
return tap;
}
});
hook.tap("sync", (a, b, c) => a + b + c);
hook.tapPromise("promise", (a, b) => Promise.resolve(a + b + 1));
result[`${type}Inspected`] = await this.gainResult((cb) => hook[type](1, 2, 3, cb));
}
}
gainResult(fn) {
return Promise.race([new Promise(resolve => {
try {
const ret = fn((err, result) => {
if(err) {
resolve({
type: "async",
error: err.message
});
} else {
resolve({
type: "async",
value: result
});
}
});
if(ret instanceof Promise) {
resolve(ret.then(res => ({
type: "promise",
value: res
}), err => ({
type: "promise",
error: err.message
})));
} else if(ret !== undefined) {
resolve({
type: "return",
value: ret
})
}
} catch(e) {
resolve({
error: e.message
});
}
}), new Promise(resolve => {
setTimeout(() => resolve({
type: "no result"
}), 300);
})]);
}
}
module.exports = HookTester;

View File

@ -0,0 +1,68 @@
const HookTester = require("./HookTester");
const SyncHook = require("../SyncHook");
const SyncBailHook = require("../SyncBailHook");
describe("SyncHook", () => {
it("should have to correct behavior", async () => {
const tester = new HookTester((args) => new SyncHook(args));
const result = await tester.run(true);
expect(result).toEqual({
callNone: { type: 'no result' },
callNoneWithArg: { type: 'no result' },
callSingleSyncCalled: true,
callSingleSync: { type: 'no result' },
callSingleSyncWithArgCalled: 42,
callSingleSyncWithArg: { type: 'no result' },
callMultipleSyncCalled1: true,
callMultipleSyncCalled2: true,
callMultipleSync: { type: 'no result' },
callMultipleSyncWithArgCalled1: 42,
callMultipleSyncWithArgCalled2: 42,
callMultipleSyncWithArg: { type: 'no result' },
callMultipleSyncWithArgsCalled1: [ 42, 43, 44 ],
callMultipleSyncWithArgsCalled2: [ 42, 43, 44 ],
callMultipleSyncWithArgs: { type: 'no result' },
callMultipleSyncErrorCalled1: true,
callMultipleSyncErrorCalled2: true,
callMultipleSyncError: { error: 'Error in sync2' },
callInspectedCall1: [ 1, 2, 3 ],
callInspectedCall2: [ 1, 2, 3 ],
callInspectedTap1: { type: 'sync', fn: 2, name: 'sync2' },
callInspectedTap2: { type: 'sync', fn: 3, name: 'sync1' },
callInspected: { type: 'no result' },
})
})
})
describe("SyncBailHook", () => {
it("should have to correct behavior", async () => {
const tester = new HookTester((args) => new SyncBailHook(args));
const result = await tester.run(true);
expect(result).toEqual({
callNone: { type: 'no result' },
callNoneWithArg: { type: 'no result' },
callSingleSyncCalled: true,
callSingleSync: { type: 'return', value: 42 },
callSingleSyncWithArgCalled: 42,
callSingleSyncWithArg: { type: 'return', value: 42 },
callMultipleSyncCalled1: true,
callMultipleSync: { type: 'return', value: 42 },
callMultipleSyncWithArgCalled1: 42,
callMultipleSyncWithArg: { type: 'return', value: 42 + 42 },
callMultipleSyncWithArgsCalled1: [ 42, 43, 44 ],
callMultipleSyncWithArgs: { type: 'return', value: 42 + 43 + 44 },
callMultipleSyncErrorCalled1: true,
callMultipleSyncErrorCalled2: true,
callMultipleSyncError: { error: 'Error in sync2' },
callInspectedCall1: [ 1, 2, 3 ],
callInspectedCall2: [ 1, 2, 3 ],
callInspectedTap1: { type: 'sync', fn: 3, name: 'sync1' },
callInspectedTap2: { type: 'sync', fn: 3, name: 'sync1' },
callInspected: { type: 'return', value: 1 + 2 + 3 },
})
})
})

185
lib/simpleAsyncCases.js Normal file
View File

@ -0,0 +1,185 @@
exports.notBailing = (options) => {
const args = options.args.join(", ");
const argsWithCallback = args ? `${args}, _callback` : "_callback";
const argsWithComma = args ? `${args}, ` : "";
const tap = options.tap;
const type = options.type;
switch(`${tap} ${type}`) {
case "none async":
return `function(${argsWithCallback}) {
_callback();
}`;
case "none promise":
return `function(${args}) {
return Promise.resolve();
}`;
case "sync async":
return `function(${argsWithCallback}) {
try {
this._x(${args});
} catch(_e) {
_callback(_e);
return;
}
_callback();
}`;
case "sync promise":
return `function(${args}) {
return new Promise(_resolve => {
this._x(${args});
_resolve();
});
}`;
case "async async":
return `function(${argsWithCallback}) {
this._x(${argsWithComma}(_err) => {
if(_err) {
_callback(_err);
return;
}
_callback();
});
}`;
case "async promise":
return `function(${args}) {
return new Promise((_resolve, _reject) => {
this._x(${argsWithComma}_err => {
if(_err) {
_reject(_err);
return;
}
_resolve();
});
});
}`;
case "promise async":
return `function(${argsWithCallback}) {
Promise.resolve(this._x(${args})).then(() => {
_callback();
}, _err => {
_callback(_err);
});
}`;
case "promise promise":
return `function(${args}) {
return this._x(${args}).then(() => {});
}`;
case "multiple-sync async":
return `function(${argsWithCallback}) {
try {
const _fns = this._x;
for(let _i = 0; _i < _fns.length; _i++) {
_fns[_i](${args});
}
} catch(_err) {
_callback(_err);
return;
}
_callback();
}`;
case "multiple-sync promise":
return `function(${args}) {
return new Promise(_resolve => {
const _fns = this._x;
for(let _i = 0; _i < _fns.length; _i++) {
_fns[_i](${args});
}
_resolve();
});
}`;
}
}
exports.bailing = (options) => {
const args = options.args.join(", ");
const argsWithCallback = args ? `${args}, _callback` : "_callback";
const argsWithComma = args ? `${args}, ` : "";
const tap = options.tap;
const type = options.type;
switch(`${tap} ${type}`) {
case "none async":
return `function(${argsWithCallback}) {
_callback();
}`;
case "none promise":
return `function(${args}) {
return Promise.resolve();
}`;
case "sync async":
return `function(${argsWithCallback}) {
let _result;
try {
_result = this._x(${args});
} catch(_e) {
_callback(_e);
return;
}
_callback(null, _result);
}`;
case "sync promise":
return `function(${args}) {
return new Promise(_resolve => {
_resolve(this._x(${args}));
});
}`;
case "async async":
return `function(${argsWithCallback}) {
this._x(${argsWithCallback});
}`;
case "async promise":
return `function(${args}) {
return new Promise((_resolve, _reject) => {
this._x(${argsWithComma}(_err, _result) => {
if(_err) {
_reject(_err);
return;
}
_resolve(_result);
});
});
}`;
case "promise async":
return `function(${argsWithCallback}) {
Promise.resolve(this._x(${args})).then(_result => {
_callback(null, _result);
}, _err => {
_callback(_err);
});
}`;
case "promise promise":
return `function(${args}) {
return this._x(${args});
}`;
case "multiple-sync async":
return `function(${argsWithCallback}) {
try {
const _fns = this._x;
for(let _i = 0; _i < _fns.length; _i++) {
const _result = _fns[_i](${args});
if(_result !== undefined) {
_callback(null, _result);
return;
}
}
} catch(_err) {
_callback(_err);
return;
}
_callback();
}`;
case "multiple-sync promise":
return `function(${args}) {
return new Promise(_resolve => {
const _fns = this._x;
for(let _i = 0; _i < _fns.length; _i++) {
const _result = _fns[_i](${args});
if(_result !== undefined) {
_resolve(_result);
return;
}
}
_resolve();
});
}`;
}
}