/* * Test Steps Explained * ==================== * * Initializing hooks: * * We initialize 3 hooks. For hook2 and hook3 we register a callback for the * "before" and in case of hook3 also for the "after" invocations. * * Enabling hooks initially: * * We only enable hook1 and hook3 initially. * * Enabling hook2: * * When hook3's "before" invocation occurs we enable hook2. Since this * happens right before calling `onfirstImmediate` hook2 will miss all hook * invocations until then, including the "init" and "before" of the first * Immediate. * However afterwards it collects all invocations that follow on the first * Immediate as well as all invocations on the second Immediate. * * This shows that a hook can enable another hook inside a life time event * callback. * * * Disabling hook1 * * Since we registered the "before" callback for hook2 it will execute it * right before `onsecondImmediate` is called. * At that point we disable hook1 which is why it will miss all invocations * afterwards and thus won't include the second "after" as well as the * "destroy" invocations * * This shows that a hook can disable another hook inside a life time event * callback. * * Disabling hook3 * * When the second "after" invocation occurs (after onsecondImmediate), hook3 * disables itself. * As a result it will not receive the "destroy" invocation. * * This shows that a hook can disable itself inside a life time event callback. * * Sample Test Log * =============== * * - setting up first Immediate * hook1.init.uid-5 * hook3.init.uid-5 * - finished setting first Immediate * hook1.before.uid-5 * hook3.before.uid-5 * - enabled hook2 * - entering onfirstImmediate * - setting up second Immediate * hook1.init.uid-6 * hook3.init.uid-6 * hook2.init.uid-6 * - finished setting second Immediate * - exiting onfirstImmediate * hook1.after.uid-5 * hook3.after.uid-5 * hook2.after.uid-5 * hook1.destroy.uid-5 * hook3.destroy.uid-5 * hook2.destroy.uid-5 * hook1.before.uid-6 * hook3.before.uid-6 * hook2.before.uid-6 * - disabled hook1 * - entering onsecondImmediate * - exiting onsecondImmediate * hook3.after.uid-6 * - disabled hook3 * hook2.after.uid-6 * hook2.destroy.uid-6 */ 'use strict'; const common = require('../common'); const assert = require('assert'); const tick = require('../common/tick'); const initHooks = require('./init-hooks'); const { checkInvocations } = require('./hook-checks'); if (!common.isMainThread) common.skip('Worker bootstrapping works differently -> different timing'); // Include "Unknown"s because hook2 will not be able to identify // the type of the first Immediate since it will miss its `init` invocation. const types = [ 'Immediate', 'Unknown' ]; // // Initializing hooks // const hook1 = initHooks(); const hook2 = initHooks({ onbefore: onhook2Before, allowNoInit: true }); const hook3 = initHooks({ onbefore: onhook3Before, onafter: onhook3After }); // // Enabling hook1 and hook3 only, hook2 is still disabled // hook1.enable(); // Verify that the hook is enabled even if .enable() is called twice. hook1.enable(); hook3.enable(); // // Enabling hook2 // let enabledHook2 = false; function onhook3Before() { if (enabledHook2) return; hook2.enable(); enabledHook2 = true; } // // Disabling hook1 // let disabledHook3 = false; function onhook2Before() { if (disabledHook3) return; hook1.disable(); // Verify that the hook is disabled even if .disable() is called twice. hook1.disable(); disabledHook3 = true; } // // Disabling hook3 during the second "after" invocations it sees // let count = 2; function onhook3After() { if (!--count) { hook3.disable(); } } setImmediate(common.mustCall(onfirstImmediate)); // // onfirstImmediate is called after all "init" and "before" callbacks of the // active hooks were invoked // function onfirstImmediate() { const as1 = hook1.activitiesOfTypes(types); const as2 = hook2.activitiesOfTypes(types); const as3 = hook3.activitiesOfTypes(types); assert.strictEqual(as1.length, 1); // hook2 was not enabled yet .. it is enabled after hook3's "before" completed assert.strictEqual(as2.length, 0); assert.strictEqual(as3.length, 1); // Check that hook1 and hook3 captured the same Immediate and that it is valid const firstImmediate = as1[0]; assert.strictEqual(as3[0].uid, as1[0].uid); assert.strictEqual(firstImmediate.type, 'Immediate'); assert.strictEqual(typeof firstImmediate.uid, 'number'); assert.strictEqual(typeof firstImmediate.triggerAsyncId, 'number'); checkInvocations(as1[0], { init: 1, before: 1 }, 'hook1[0]: on first immediate'); checkInvocations(as3[0], { init: 1, before: 1 }, 'hook3[0]: on first immediate'); // Setup the second Immediate, note that now hook2 is enabled and thus // will capture all lifetime events of this Immediate setImmediate(common.mustCall(onsecondImmediate)); } // // Once we exit onfirstImmediate the "after" callbacks of the active hooks are // invoked // let hook1First, hook2First, hook3First; let hook1Second, hook2Second, hook3Second; // // onsecondImmediate is called after all "before" callbacks of the active hooks // are invoked again // function onsecondImmediate() { const as1 = hook1.activitiesOfTypes(types); const as2 = hook2.activitiesOfTypes(types); const as3 = hook3.activitiesOfTypes(types); assert.strictEqual(as1.length, 2); assert.strictEqual(as2.length, 2); assert.strictEqual(as3.length, 2); // Assign the info collected by each hook for each immediate for easier // reference. // hook2 saw the "init" of the second immediate before the // "after" of the first which is why they are ordered the opposite way hook1First = as1[0]; hook1Second = as1[1]; hook2First = as2[1]; hook2Second = as2[0]; hook3First = as3[0]; hook3Second = as3[1]; // Check that all hooks captured the same Immediate and that it is valid const secondImmediate = hook1Second; assert.strictEqual(hook2Second.uid, hook3Second.uid); assert.strictEqual(hook1Second.uid, hook3Second.uid); assert.strictEqual(secondImmediate.type, 'Immediate'); assert.strictEqual(typeof secondImmediate.uid, 'number'); assert.strictEqual(typeof secondImmediate.triggerAsyncId, 'number'); checkInvocations(hook1First, { init: 1, before: 1, after: 1, destroy: 1 }, 'hook1First: on second immediate'); checkInvocations(hook1Second, { init: 1, before: 1 }, 'hook1Second: on second immediate'); // hook2 missed the "init" and "before" since it was enabled after they // occurred checkInvocations(hook2First, { after: 1, destroy: 1 }, 'hook2First: on second immediate'); checkInvocations(hook2Second, { init: 1, before: 1 }, 'hook2Second: on second immediate'); checkInvocations(hook3First, { init: 1, before: 1, after: 1, destroy: 1 }, 'hook3First: on second immediate'); checkInvocations(hook3Second, { init: 1, before: 1 }, 'hook3Second: on second immediate'); tick(1); } // // Once we exit onsecondImmediate the "after" callbacks of the active hooks are // invoked again. // During this second "after" invocation hook3 disables itself // (see onhook3After). // process.on('exit', onexit); function onexit() { hook1.disable(); hook2.disable(); hook3.disable(); hook1.sanityCheck(); hook2.sanityCheck(); hook3.sanityCheck(); checkInvocations(hook1First, { init: 1, before: 1, after: 1, destroy: 1 }, 'hook1First: when process exits'); // hook1 was disabled during hook2's "before" of the second immediate // and thus did not see "after" and "destroy" checkInvocations(hook1Second, { init: 1, before: 1 }, 'hook1Second: when process exits'); // hook2 missed the "init" and "before" since it was enabled after they // occurred checkInvocations(hook2First, { after: 1, destroy: 1 }, 'hook2First: when process exits'); checkInvocations(hook2Second, { init: 1, before: 1, after: 1, destroy: 1 }, 'hook2Second: when process exits'); checkInvocations(hook3First, { init: 1, before: 1, after: 1, destroy: 1 }, 'hook3First: when process exits'); // We don't see a "destroy" invocation here since hook3 disabled itself // during its "after" invocation checkInvocations(hook3Second, { init: 1, before: 1, after: 1 }, 'hook3Second: when process exits'); }