| /* |
| * 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'); |
| } |