text: /*
  	MIT License http://www.opensource.org/licenses/mit-license.php
  	Author Tobias Koppers @sokra
  */
  "use strict";
  
  describe("HookTester", () => {
  	it("should run", () => {});
  });
  
  process.on("unhandledRejection", err => console.error(err.stack));
  
  class HookTester {
  	constructor(hookCreator, sync) {
  		this.hookCreator = hookCreator;
  		this.sync = sync;
  	}
  
  	async run(syncOnly) {
  		const result = {
  			sync: {},
  			async: {},
  			intercept: {}
  		};
  
  		if (syncOnly) {
  			await this.runSync(result.sync, "call");
  		} else {
  			await this.runAsync(result.async, "callAsync");
  			await this.runAsync(result.async, "promise");
  
  			await this.runIntercept(result.intercept, "callAsync");
  			await this.runIntercept(result.intercept, "promise");
  		}
  
  		await this.runSync(result.sync, "callAsync");
  		await this.runSync(result.sync, "promise");
  
  		return result;
  	}
  
  	async runForLoop(syncOnly) {
  		const result = {
  			sync: {},
  			async: {}
  		};
  
  		if (syncOnly) {
  			await this.runForLoopSync(result.sync, "call");
  		} else {
  			await this.runForLoopAsync(result.async, "callAsync");
  			await this.runForLoopAsync(result.async, "promise");
  		}
  
  		await this.runForLoopSync(result.sync, "callAsync");
  		await this.runForLoopSync(result.sync, "promise");
  
  		return result;
  	}
  
  	async runForLoopAsync(result, method) {
  		{
  			const hook = this.createHook([], `${method}BrokenPromise`);
  			hook.tapPromise("promise", () => "this is not a promise");
  			result[`${method}BrokenPromise`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  		{
  			const hook = this.createHook([], `${method}SinglePromise`);
  			hook.tapPromise("promise", () => {
  				result[`${method}SinglePromiseCalled`] =
  					(result[`${method}SinglePromiseCalled`] || 0) + 1;
  				if (result[`${method}SinglePromiseCalled`] < 42)
  					return Promise.resolve().then(() => true);
  				return Promise.resolve().then(() => {});
  			});
  			result[`${method}SinglePromise`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${method}MultiplePromise`);
  			hook.tapPromise("promise1", () => {
  				result[`${method}MultiplePromiseCalled1`] =
  					(result[`${method}MultiplePromiseCalled1`] || 0) + 1;
  				if (result[`${method}MultiplePromiseCalled1`] < 42)
  					return Promise.resolve().then(() => true);
  				return Promise.resolve().then(() => {});
  			});
  			hook.tapPromise("promise2", () => {
  				result[`${method}MultiplePromiseCalled2`] =
  					(result[`${method}MultiplePromiseCalled2`] || 0) + 1;
  				if (result[`${method}MultiplePromiseCalled2`] < 42)
  					return Promise.resolve().then(() => true);
  				return Promise.resolve().then(() => {});
  			});
  			result[`${method}MultiplePromise`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${method}SingleAsync`);
  			hook.tapAsync("async", callback => {
  				result[`${method}SingleAsyncCalled`] =
  					(result[`${method}SingleAsyncCalled`] || 0) + 1;
  				if (result[`${method}SingleAsyncCalled`] < 42)
  					return Promise.resolve().then(() => callback(null, true));
  				return Promise.resolve().then(() => callback());
  			});
  			result[`${method}SingleAsync`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${method}MultipleAsync`);
  			hook.tapAsync("async1", callback => {
  				result[`${method}MultipleAsyncCalled1`] =
  					(result[`${method}MultipleAsyncCalled1`] || 0) + 1;
  				if (result[`${method}MultipleAsyncCalled1`] < 42)
  					return Promise.resolve().then(() => callback(null, true));
  				return Promise.resolve().then(() => callback());
  			});
  			hook.tapAsync("async2", callback => {
  				result[`${method}MultipleAsyncCalled2`] =
  					(result[`${method}MultipleAsyncCalled2`] || 0) + 1;
  				if (result[`${method}MultipleAsyncCalled2`] < 42)
  					return Promise.resolve().then(() => callback(null, true));
  				return Promise.resolve().then(() => callback());
  			});
  			result[`${method}MultipleAsync`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${method}Mixed`);
  			hook.tapAsync("async1", callback => {
  				result[`${method}MixedCalled1`] =
  					(result[`${method}MixedCalled1`] || 0) + 1;
  				if (result[`${method}MixedCalled1`] < 42)
  					return Promise.resolve().then(() => callback(null, true));
  				return Promise.resolve().then(() => callback());
  			});
  			hook.tap("sync2", () => {
  				result[`${method}MixedCalled2`] =
  					(result[`${method}MixedCalled2`] || 0) + 1;
  				if (result[`${method}MixedCalled2`] < 42) return true;
  			});
  			hook.tapPromise("promise3", () => {
  				result[`${method}MixedCalled3`] =
  					(result[`${method}MixedCalled3`] || 0) + 1;
  				if (result[`${method}MixedCalled3`] < 42)
  					return Promise.resolve().then(() => true);
  				return Promise.resolve().then(() => {});
  			});
  			result[`${method}Mixed`] = await this.gainResult(cb => hook[method](cb));
  		}
  	}
  
  	async runForLoopSync(result, method) {
  		{
  			const hook = this.createHook([], `${method}None`);
  			result[`${method}None`] = await this.gainResult(cb => hook[method](cb));
  		}
  
  		{
  			const hook = this.createHook(["arg"], `${method}NoneWithArg`);
  			result[`${method}NoneWithArg`] = await this.gainResult(cb =>
  				hook[method](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${method}SingleSync`);
  			hook.tap("sync", () => {
  				result[`${method}SingleSyncCalled`] =
  					(result[`${method}SingleSyncCalled`] || 0) + 1;
  				if (result[`${method}SingleSyncCalled`] < 42) return true;
  			});
  			result[`${method}SingleSync`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${method}MultipleSync`);
  			hook.tap("sync1", () => {
  				result[`${method}MultipleSyncCalled1`] =
  					(result[`${method}MultipleSyncCalled1`] || 0) + 1;
  				if (result[`${method}MultipleSyncCalled1`] < 42) return true;
  			});
  			hook.tap("sync2", () => {
  				result[`${method}MultipleSyncCalled2`] =
  					(result[`${method}MultipleSyncCalled2`] || 0) + 1;
  				if (result[`${method}MultipleSyncCalled2`] < 42) return true;
  			});
  			result[`${method}MultipleSync`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${method}InterceptedSync`);
  			hook.tap("sync1", () => {
  				result[`${method}InterceptedSyncCalled1`] =
  					(result[`${method}InterceptedSyncCalled1`] || 0) + 1;
  				if (result[`${method}InterceptedSyncCalled1`] < 42) return true;
  			});
  			hook.tap("sync2", () => {
  				result[`${method}InterceptedSyncCalled2`] =
  					(result[`${method}InterceptedSyncCalled2`] || 0) + 1;
  				if (result[`${method}InterceptedSyncCalled2`] < 42) return true;
  			});
  			hook.intercept({
  				call: a =>
  					(result[`${method}InterceptedSyncCalledCall`] =
  						(result[`${method}InterceptedSyncCalledCall`] || 0) + 1),
  				loop: a =>
  					(result[`${method}InterceptedSyncCalledLoop`] =
  						(result[`${method}InterceptedSyncCalledLoop`] || 0) + 1),
  				tap: tap => {
  					result[`${method}InterceptedSyncCalledTap`] =
  						(result[`${method}InterceptedSyncCalledTap`] || 0) + 1;
  				}
  			});
  			result[`${method}InterceptedSync`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  	}
  
  	async runSync(result, method) {
  		{
  			const hook = this.createHook([], `${method}None`);
  			result[`${method}None`] = await this.gainResult(cb => hook[method](cb));
  		}
  
  		{
  			const hook = this.createHook(["arg"], `${method}NoneWithArg`);
  			result[`${method}NoneWithArg`] = await this.gainResult(cb =>
  				hook[method](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${method}SingleSync`);
  			hook.tap("sync", () => {
  				result[`${method}SingleSyncCalled`] = true;
  				return 42;
  			});
  			result[`${method}SingleSync`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(["myArg"], `${method}SingleSyncWithArg`);
  			hook.tap("sync", nr => {
  				result[`${method}SingleSyncWithArgCalled`] = nr;
  				return nr;
  			});
  			result[`${method}SingleSyncWithArg`] = await this.gainResult(cb =>
  				hook[method](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${method}MultipleSync`);
  			hook.tap("sync1", () => {
  				result[`${method}MultipleSyncCalled1`] = true;
  				return 42;
  			});
  			hook.tap("sync2", () => {
  				result[`${method}MultipleSyncCalled2`] = true;
  				return 43;
  			});
  			result[`${method}MultipleSync`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(["a"], `${method}MultipleSyncWithArg`);
  			hook.tap("sync1", a => {
  				result[`${method}MultipleSyncWithArgCalled1`] = a;
  				return 42 + a;
  			});
  			hook.tap("sync2", a => {
  				result[`${method}MultipleSyncWithArgCalled2`] = a;
  				return 43 + a;
  			});
  			result[`${method}MultipleSyncWithArg`] = await this.gainResult(cb =>
  				hook[method](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["a"],
  				`${method}MultipleSyncWithArgNoReturn`
  			);
  			hook.tap("sync1", a => {
  				result[`${method}MultipleSyncWithArgNoReturnCalled1`] = a;
  			});
  			hook.tap("sync2", a => {
  				result[`${method}MultipleSyncWithArgNoReturnCalled2`] = a;
  			});
  			result[`${method}MultipleSyncWithArgNoReturn`] = await this.gainResult(
  				cb => hook[method](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["a"],
  				`${method}MultipleSyncWithArgFirstReturn`
  			);
  			hook.tap("sync1", a => {
  				result[`${method}MultipleSyncWithArgFirstReturnCalled1`] = a;
  				return 42 + a;
  			});
  			hook.tap("sync2", a => {
  				result[`${method}MultipleSyncWithArgFirstReturnCalled2`] = a;
  			});
  			result[`${method}MultipleSyncWithArgFirstReturn`] = await this.gainResult(
  				cb => hook[method](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["a"],
  				`${method}MultipleSyncWithArgLastReturn`
  			);
  			hook.tap("sync1", a => {
  				result[`${method}MultipleSyncWithArgLastReturnCalled1`] = a;
  			});
  			hook.tap("sync2", a => {
  				result[`${method}MultipleSyncWithArgLastReturnCalled2`] = a;
  				return 43 + a;
  			});
  			result[`${method}MultipleSyncWithArgLastReturn`] = await this.gainResult(
  				cb => hook[method](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["a", "b", "c"],
  				`${method}MultipleSyncWithArgs`
  			);
  			hook.tap("sync1", (a, b, c) => {
  				result[`${method}MultipleSyncWithArgsCalled1`] = [a, b, c];
  				return a + b + c;
  			});
  			hook.tap("sync2", (a, b, c) => {
  				result[`${method}MultipleSyncWithArgsCalled2`] = [a, b, c];
  				return a + b + c + 1;
  			});
  			result[`${method}MultipleSyncWithArgs`] = await this.gainResult(cb =>
  				hook[method](42, 43, 44, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${method}MultipleSyncError`);
  			hook.tap("sync1", () => {
  				result[`${method}MultipleSyncErrorCalled1`] = true;
  			});
  			hook.tap("sync2", () => {
  				result[`${method}MultipleSyncErrorCalled2`] = true;
  				throw new Error("Error in sync2");
  			});
  			hook.tap("sync3", () => {
  				result[`${method}MultipleSyncErrorCalled3`] = true;
  			});
  			result[`${method}MultipleSyncError`] = await this.gainResult(cb =>
  				hook[method](cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(["a", "b", "c"], `${method}Intercepted`);
  			hook.intercept({
  				call: (a, b, c) => {
  					result[`${method}InterceptedCall1`] = [a, b, c];
  				},
  				tap: tap => {
  					result[`${method}InterceptedTap1`] = Object.assign({}, tap, {
  						fn: tap.fn.length
  					});
  				}
  			});
  			hook.intercept({
  				call: (a, b, c) => {
  					result[`${method}InterceptedCall2`] = [a, b, c];
  				},
  				tap: tap => {
  					if (!result[`${method}InterceptedTap2`])
  						result[`${method}InterceptedTap2`] = Object.assign({}, tap, {
  							fn: tap.fn.length
  						});
  				}
  			});
  			hook.tap("sync1", (a, b, c) => a + b + c);
  			hook.tap("sync2", (a, b) => a + b + 1);
  			result[`${method}Intercepted`] = await this.gainResult(cb =>
  				hook[method](1, 2, 3, cb)
  			);
  		}
  	}
  
  	async runAsync(result, type) {
  		{
  			const hook = this.createHook([], `${type}None`);
  			result[`${type}None`] = await this.gainResult(cb => hook[type](cb));
  		}
  
  		{
  			const hook = this.createHook(["arg"], `${type}NoneWithArg`);
  			result[`${type}NoneWithArg`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook([], `${type}SingleSync`);
  			hook.tap("sync", () => {
  				result[`${type}SingleSyncCalled1`] = true;
  				return 42;
  			});
  			result[`${type}SingleSync`] = await this.gainResult(cb => hook[type](cb));
  		}
  
  		{
  			const hook = this.createHook(["x"], `${type}SingleSyncWithArg`);
  			hook.tap("sync", arg => {
  				result[`${type}SingleSyncWithArgCalled1`] = arg;
  				return arg + 1;
  			});
  			result[`${type}SingleSyncWithArg`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(["x"], `${type}SingleSyncWithArgNoReturn`);
  			hook.tap("sync", arg => {
  				result[`${type}SingleSyncWithArgNoReturnCalled1`] = arg;
  			});
  			result[`${type}SingleSyncWithArgNoReturn`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.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 = this.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 = this.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 = this.createHook(["arg"], `${type}MultipleSyncWithArg`);
  			hook.tap("sync1", arg => {
  				result[`${type}MultipleSyncWithArgCalled1`] = arg;
  				return arg + 1;
  			});
  			hook.tap("sync2", arg => {
  				result[`${type}MultipleSyncWithArgCalled2`] = arg;
  				return arg + 2;
  			});
  			result[`${type}MultipleSyncWithArg`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["arg"],
  				`${type}MultipleSyncWithArgNoReturn`
  			);
  			hook.tap("sync1", arg => {
  				result[`${type}MultipleSyncWithArgNoReturnCalled1`] = arg;
  			});
  			hook.tap("sync2", arg => {
  				result[`${type}MultipleSyncWithArgNoReturnCalled2`] = arg;
  			});
  			result[`${type}MultipleSyncWithArgNoReturn`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["arg"],
  				`${type}MultipleSyncWithArgLastReturn`
  			);
  			hook.tap("sync1", arg => {
  				result[`${type}MultipleSyncWithArgLastReturnCalled1`] = arg;
  			});
  			hook.tap("sync2", arg => {
  				result[`${type}MultipleSyncWithArgLastReturnCalled2`] = arg;
  				return arg + 2;
  			});
  			result[`${type}MultipleSyncWithArgLastReturn`] = await this.gainResult(
  				cb => hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["arg"],
  				`${type}MultipleSyncWithArgFirstReturn`
  			);
  			hook.tap("sync1", arg => {
  				result[`${type}MultipleSyncWithArgFirstReturnCalled1`] = arg;
  				return arg + 1;
  			});
  			hook.tap("sync2", arg => {
  				result[`${type}MultipleSyncWithArgFirstReturnCalled2`] = arg;
  			});
  			result[`${type}MultipleSyncWithArgFirstReturn`] = await this.gainResult(
  				cb => hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.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 = this.createHook(["x"], `${type}MultipleAsyncWithArg`);
  			hook.tapAsync("async1", (arg, callback) => {
  				result[`${type}MultipleAsyncWithArgCalled1`] = arg;
  				callback(null, arg + 1);
  			});
  			hook.tapAsync("async2", (arg, callback) => {
  				result[`${type}MultipleAsyncWithArgCalled2`] = arg;
  				callback(null, arg + 2);
  			});
  			result[`${type}MultipleAsyncWithArg`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["x"],
  				`${type}MultipleAsyncWithArgNoReturn`
  			);
  			hook.tapAsync("async1", (arg, callback) => {
  				result[`${type}MultipleAsyncWithArgNoReturnCalled1`] = arg;
  				callback();
  			});
  			hook.tapAsync("async2", (arg, callback) => {
  				result[`${type}MultipleAsyncWithArgNoReturnCalled2`] = arg;
  				callback();
  			});
  			result[`${type}MultipleAsyncWithArgNoReturn`] = await this.gainResult(
  				cb => hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["x"],
  				`${type}MultipleAsyncWithArgFirstReturn`
  			);
  			hook.tapAsync("async1", (arg, callback) => {
  				result[`${type}MultipleAsyncWithArgFirstReturnCalled1`] = arg;
  				callback(null, arg + 1);
  			});
  			hook.tapAsync("async2", (arg, callback) => {
  				result[`${type}MultipleAsyncWithArgFirstReturnCalled2`] = arg;
  				callback();
  			});
  			result[`${type}MultipleAsyncWithArgFirstReturn`] = await this.gainResult(
  				cb => hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["x"],
  				`${type}MultipleAsyncWithArgLastReturn`
  			);
  			hook.tapAsync("async1", (arg, callback) => {
  				result[`${type}MultipleAsyncWithArgLastReturnCalled1`] = arg;
  				callback();
  			});
  			hook.tapAsync("async2", (arg, callback) => {
  				result[`${type}MultipleAsyncWithArgLastReturnCalled2`] = arg;
  				callback(null, arg + 2);
  			});
  			result[`${type}MultipleAsyncWithArgLastReturn`] = await this.gainResult(
  				cb => hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(["x"], `${type}SinglePromiseWithArg`);
  			hook.tapPromise("promise", arg => {
  				result[`${type}SinglePromiseWithArgCalled1`] = arg;
  				return Promise.resolve(arg + 1);
  			});
  			result[`${type}SinglePromiseWithArg`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(["x"], `${type}MultiplePromiseWithArg`);
  			hook.tapPromise("promise1", arg => {
  				result[`${type}MultiplePromiseWithArgCalled1`] = arg;
  				return Promise.resolve(arg + 1);
  			});
  			hook.tapPromise("promise2", arg => {
  				result[`${type}MultiplePromiseWithArgCalled2`] = arg;
  				return Promise.resolve(arg + 2);
  			});
  			result[`${type}MultiplePromiseWithArg`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["x"],
  				`${type}MultiplePromiseWithArgNoReturn`
  			);
  			hook.tapPromise("promise1", arg => {
  				result[`${type}MultiplePromiseWithArgNoReturnCalled1`] = arg;
  				return Promise.resolve();
  			});
  			hook.tapPromise("promise2", arg => {
  				result[`${type}MultiplePromiseWithArgNoReturnCalled2`] = arg;
  				return Promise.resolve();
  			});
  			result[`${type}MultiplePromiseWithArgNoReturn`] = await this.gainResult(
  				cb => hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["x"],
  				`${type}MultiplePromiseWithArgFirstReturn`
  			);
  			hook.tapPromise("promise1", arg => {
  				result[`${type}MultiplePromiseWithArgFirstReturnCalled1`] = arg;
  				return Promise.resolve(arg + 1);
  			});
  			hook.tapPromise("promise2", arg => {
  				result[`${type}MultiplePromiseWithArgFirstReturnCalled2`] = arg;
  				return Promise.resolve();
  			});
  			result[
  				`${type}MultiplePromiseWithArgFirstReturn`
  			] = await this.gainResult(cb => hook[type](42, cb));
  		}
  
  		{
  			const hook = this.createHook(
  				["x"],
  				`${type}MultiplePromiseWithArgLastReturn`
  			);
  			hook.tapPromise("promise1", arg => {
  				result[`${type}MultiplePromiseWithArgLastReturnCalled1`] = arg;
  				return Promise.resolve();
  			});
  			hook.tapPromise("promise2", arg => {
  				result[`${type}MultiplePromiseWithArgLastReturnCalled2`] = arg;
  				return Promise.resolve(arg + 2);
  			});
  			result[`${type}MultiplePromiseWithArgLastReturn`] = await this.gainResult(
  				cb => hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(["x"], `${type}MultipleMixed1WithArg`);
  			hook.tapAsync("async", (arg, callback) => {
  				result[`${type}MultipleMixed1WithArgCalled1`] = arg;
  				callback(null, arg + 1);
  			});
  			hook.tapPromise("promise", arg => {
  				result[`${type}MultipleMixed1WithArgCalled2`] = arg;
  				return Promise.resolve(arg + 2);
  			});
  			hook.tap("sync", arg => {
  				result[`${type}MultipleMixed1WithArgCalled3`] = arg;
  				return arg + 3;
  			});
  			result[`${type}MultipleMixed1WithArg`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(["x"], `${type}MultipleMixed2WithArg`);
  			hook.tapAsync("async", (arg, callback) => {
  				result[`${type}MultipleMixed2WithArgCalled1`] = arg;
  				setTimeout(() => callback(null, arg + 1), 100);
  			});
  			hook.tapPromise("promise", arg => {
  				result[`${type}MultipleMixed2WithArgCalled2`] = arg;
  				return Promise.resolve(arg + 2);
  			});
  			result[`${type}MultipleMixed2WithArg`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(["x"], `${type}MultipleMixed3WithArg`);
  			hook.tapAsync("async1", (arg, callback) => {
  				result[`${type}MultipleMixed3WithArgCalled1`] = arg;
  				callback(null, arg + 1);
  			});
  			hook.tapPromise("promise", arg => {
  				result[`${type}MultipleMixed3WithArgCalled2`] = arg;
  				return Promise.resolve(arg + 2);
  			});
  			hook.tapAsync("async2", (arg, callback) => {
  				result[`${type}MultipleMixed3WithArgCalled3`] = arg;
  				setTimeout(() => callback(null, arg + 3), 100);
  			});
  			result[`${type}MultipleMixed3WithArg`] = await this.gainResult(cb =>
  				hook[type](42, cb)
  			);
  		}
  
  		{
  			const hook = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 = this.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 runIntercept(result, type) {
  		{
  			const hook = this.createHook(["a", "b", "c"], `${type}Intercepted`);
  			hook.intercept({
  				call: (a, b, c) => {
  					result[`${type}InterceptedCall1`] = [a, b, c];
  				},
  
  				done: () => {
  					result[`${type}InterceptedDone1`] = true;
  				},
  
  				result: r => {
  					result[`${type}InterceptedResult1`] = r;
  				},
  
  				error: err => {
  					result[`${type}InterceptedError1`] = err;
  				},
  
  				tap: tap => {
  					result[`${type}InterceptedTap1`] = Object.assign({}, tap, {
  						fn: tap.fn.length
  					});
  				}
  			});
  			hook.intercept({
  				call: (a, b, c) => {
  					result[`${type}InterceptedCall2`] = [a, b, c];
  				},
  
  				done: () => {
  					result[`${type}InterceptedDone2`] = true;
  				},
  
  				result: r => {
  					result[`${type}InterceptedResult2`] = r;
  				},
  
  				error: err => {
  					result[`${type}InterceptedError2`] = err;
  				},
  
  				tap: tap => {
  					if (!result[`${type}InterceptedTap2`])
  						result[`${type}InterceptedTap2`] = Object.assign({}, tap, {
  							fn: tap.fn.length
  						});
  				}
  			});
  			hook.tap("sync", (a, b, c) => a + b + c);
  			hook.tapPromise("promise", (a, b) => Promise.resolve(a + b + 1));
  			result[`${type}Intercepted`] = await this.gainResult(cb =>
  				hook[type](1, 2, 3, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["a", "b", "c"],
  				`${type}ContextIntercepted`
  			);
  			hook.intercept({
  				call: (context, a, b, c) => {
  					context.number = 42;
  					result[`${type}ContextInterceptedCall1`] = [context, a, b, c];
  				},
  				loop: (context, a, b, c) => {
  					context.number2 = 88;
  					result[`${type}ContextInterceptedLoop1`] = [context, a, b, c];
  				},
  				tap: (context, tap) => {
  					result[`${type}ContextInterceptedTap1`] = context;
  				},
  				context: true
  			});
  			hook.intercept({
  				call: (a, b, c) => {
  					result[`${type}ContextInterceptedCall2`] = [a, b, c];
  				}
  			});
  			hook.tap(
  				{
  					name: "sync",
  					context: true
  				},
  				(context, a, b, c) => context.number + a + b + c
  			);
  			result[`${type}ContextIntercepted`] = await this.gainResult(cb =>
  				hook[type](1, 2, 3, cb)
  			);
  		}
  
  		{
  			const hook = this.createHook(
  				["a", "b", "c"],
  				`${type}UnusedContextIntercepted`
  			);
  			hook.intercept({
  				call: (context, a, b, c) => {
  					result[`${type}UnusedContextInterceptedCall1`] = [context, a, b, c];
  				},
  				tap: (context, tap) => {
  					result[`${type}UnusedContextInterceptedTap1`] = context;
  				},
  				context: true
  			});
  			hook.intercept({
  				call: (a, b, c) => {
  					result[`${type}UnusedContextInterceptedCall2`] = [a, b, c];
  				}
  			});
  			hook.tap("sync", (a, b, c) => a + b + c);
  			result[`${type}UnusedContextIntercepted`] = 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"
  						}),
  					1000
  				);
  			})
  		]);
  	}
  
  	createHook(args, name) {
  		try {
  			return this.hookCreator(args, name);
  		} catch (err) {
  			return {
  				tap: () => {},
  				tapPromise: () => {},
  				tapAsync: () => {},
  				intercept: () => {},
  				call: () => {
  					throw err;
  				},
  				callAsync: () => {
  					throw err;
  				},
  				promise: () => {
  					throw err;
  				}
  			};
  		}
  	}
  }
  
  module.exports = HookTester;
  