1/* 2 * Test Steps Explained 3 * ==================== 4 * 5 * Initializing hooks: 6 * 7 * We initialize 3 hooks. For hook2 and hook3 we register a callback for the 8 * "before" and in case of hook3 also for the "after" invocations. 9 * 10 * Enabling hooks initially: 11 * 12 * We only enable hook1 and hook3 initially. 13 * 14 * Enabling hook2: 15 * 16 * When hook3's "before" invocation occurs we enable hook2. Since this 17 * happens right before calling `onfirstImmediate` hook2 will miss all hook 18 * invocations until then, including the "init" and "before" of the first 19 * Immediate. 20 * However afterwards it collects all invocations that follow on the first 21 * Immediate as well as all invocations on the second Immediate. 22 * 23 * This shows that a hook can enable another hook inside a life time event 24 * callback. 25 * 26 * 27 * Disabling hook1 28 * 29 * Since we registered the "before" callback for hook2 it will execute it 30 * right before `onsecondImmediate` is called. 31 * At that point we disable hook1 which is why it will miss all invocations 32 * afterwards and thus won't include the second "after" as well as the 33 * "destroy" invocations 34 * 35 * This shows that a hook can disable another hook inside a life time event 36 * callback. 37 * 38 * Disabling hook3 39 * 40 * When the second "after" invocation occurs (after onsecondImmediate), hook3 41 * disables itself. 42 * As a result it will not receive the "destroy" invocation. 43 * 44 * This shows that a hook can disable itself inside a life time event callback. 45 * 46 * Sample Test Log 47 * =============== 48 * 49 * - setting up first Immediate 50 * hook1.init.uid-5 51 * hook3.init.uid-5 52 * - finished setting first Immediate 53 54 * hook1.before.uid-5 55 * hook3.before.uid-5 56 * - enabled hook2 57 * - entering onfirstImmediate 58 59 * - setting up second Immediate 60 * hook1.init.uid-6 61 * hook3.init.uid-6 62 * hook2.init.uid-6 63 * - finished setting second Immediate 64 65 * - exiting onfirstImmediate 66 * hook1.after.uid-5 67 * hook3.after.uid-5 68 * hook2.after.uid-5 69 * hook1.destroy.uid-5 70 * hook3.destroy.uid-5 71 * hook2.destroy.uid-5 72 * hook1.before.uid-6 73 * hook3.before.uid-6 74 * hook2.before.uid-6 75 * - disabled hook1 76 * - entering onsecondImmediate 77 * - exiting onsecondImmediate 78 * hook3.after.uid-6 79 * - disabled hook3 80 * hook2.after.uid-6 81 * hook2.destroy.uid-6 82 */ 83 84'use strict'; 85 86const common = require('../common'); 87const assert = require('assert'); 88const tick = require('../common/tick'); 89const initHooks = require('./init-hooks'); 90const { checkInvocations } = require('./hook-checks'); 91 92if (!common.isMainThread) 93 common.skip('Worker bootstrapping works differently -> different timing'); 94 95// Include "Unknown"s because hook2 will not be able to identify 96// the type of the first Immediate since it will miss its `init` invocation. 97const types = [ 'Immediate', 'Unknown' ]; 98 99// 100// Initializing hooks 101// 102const hook1 = initHooks(); 103const hook2 = initHooks({ onbefore: onhook2Before, allowNoInit: true }); 104const hook3 = initHooks({ onbefore: onhook3Before, onafter: onhook3After }); 105 106// 107// Enabling hook1 and hook3 only, hook2 is still disabled 108// 109hook1.enable(); 110// Verify that the hook is enabled even if .enable() is called twice. 111hook1.enable(); 112hook3.enable(); 113 114// 115// Enabling hook2 116// 117let enabledHook2 = false; 118function onhook3Before() { 119 if (enabledHook2) return; 120 hook2.enable(); 121 enabledHook2 = true; 122} 123 124// 125// Disabling hook1 126// 127let disabledHook3 = false; 128function onhook2Before() { 129 if (disabledHook3) return; 130 hook1.disable(); 131 // Verify that the hook is disabled even if .disable() is called twice. 132 hook1.disable(); 133 disabledHook3 = true; 134} 135 136// 137// Disabling hook3 during the second "after" invocations it sees 138// 139let count = 2; 140function onhook3After() { 141 if (!--count) { 142 hook3.disable(); 143 } 144} 145 146setImmediate(common.mustCall(onfirstImmediate)); 147 148// 149// onfirstImmediate is called after all "init" and "before" callbacks of the 150// active hooks were invoked 151// 152function onfirstImmediate() { 153 const as1 = hook1.activitiesOfTypes(types); 154 const as2 = hook2.activitiesOfTypes(types); 155 const as3 = hook3.activitiesOfTypes(types); 156 assert.strictEqual(as1.length, 1); 157 // hook2 was not enabled yet .. it is enabled after hook3's "before" completed 158 assert.strictEqual(as2.length, 0); 159 assert.strictEqual(as3.length, 1); 160 161 // Check that hook1 and hook3 captured the same Immediate and that it is valid 162 const firstImmediate = as1[0]; 163 assert.strictEqual(as3[0].uid, as1[0].uid); 164 assert.strictEqual(firstImmediate.type, 'Immediate'); 165 assert.strictEqual(typeof firstImmediate.uid, 'number'); 166 assert.strictEqual(typeof firstImmediate.triggerAsyncId, 'number'); 167 checkInvocations(as1[0], { init: 1, before: 1 }, 168 'hook1[0]: on first immediate'); 169 checkInvocations(as3[0], { init: 1, before: 1 }, 170 'hook3[0]: on first immediate'); 171 172 // Setup the second Immediate, note that now hook2 is enabled and thus 173 // will capture all lifetime events of this Immediate 174 setImmediate(common.mustCall(onsecondImmediate)); 175} 176 177// 178// Once we exit onfirstImmediate the "after" callbacks of the active hooks are 179// invoked 180// 181 182let hook1First, hook2First, hook3First; 183let hook1Second, hook2Second, hook3Second; 184 185// 186// onsecondImmediate is called after all "before" callbacks of the active hooks 187// are invoked again 188// 189function onsecondImmediate() { 190 const as1 = hook1.activitiesOfTypes(types); 191 const as2 = hook2.activitiesOfTypes(types); 192 const as3 = hook3.activitiesOfTypes(types); 193 assert.strictEqual(as1.length, 2); 194 assert.strictEqual(as2.length, 2); 195 assert.strictEqual(as3.length, 2); 196 197 // Assign the info collected by each hook for each immediate for easier 198 // reference. 199 // hook2 saw the "init" of the second immediate before the 200 // "after" of the first which is why they are ordered the opposite way 201 hook1First = as1[0]; 202 hook1Second = as1[1]; 203 hook2First = as2[1]; 204 hook2Second = as2[0]; 205 hook3First = as3[0]; 206 hook3Second = as3[1]; 207 208 // Check that all hooks captured the same Immediate and that it is valid 209 const secondImmediate = hook1Second; 210 assert.strictEqual(hook2Second.uid, hook3Second.uid); 211 assert.strictEqual(hook1Second.uid, hook3Second.uid); 212 assert.strictEqual(secondImmediate.type, 'Immediate'); 213 assert.strictEqual(typeof secondImmediate.uid, 'number'); 214 assert.strictEqual(typeof secondImmediate.triggerAsyncId, 'number'); 215 216 checkInvocations(hook1First, { init: 1, before: 1, after: 1, destroy: 1 }, 217 'hook1First: on second immediate'); 218 checkInvocations(hook1Second, { init: 1, before: 1 }, 219 'hook1Second: on second immediate'); 220 // hook2 missed the "init" and "before" since it was enabled after they 221 // occurred 222 checkInvocations(hook2First, { after: 1, destroy: 1 }, 223 'hook2First: on second immediate'); 224 checkInvocations(hook2Second, { init: 1, before: 1 }, 225 'hook2Second: on second immediate'); 226 checkInvocations(hook3First, { init: 1, before: 1, after: 1, destroy: 1 }, 227 'hook3First: on second immediate'); 228 checkInvocations(hook3Second, { init: 1, before: 1 }, 229 'hook3Second: on second immediate'); 230 tick(1); 231} 232 233// 234// Once we exit onsecondImmediate the "after" callbacks of the active hooks are 235// invoked again. 236// During this second "after" invocation hook3 disables itself 237// (see onhook3After). 238// 239 240process.on('exit', onexit); 241 242function onexit() { 243 hook1.disable(); 244 hook2.disable(); 245 hook3.disable(); 246 hook1.sanityCheck(); 247 hook2.sanityCheck(); 248 hook3.sanityCheck(); 249 250 checkInvocations(hook1First, { init: 1, before: 1, after: 1, destroy: 1 }, 251 'hook1First: when process exits'); 252 // hook1 was disabled during hook2's "before" of the second immediate 253 // and thus did not see "after" and "destroy" 254 checkInvocations(hook1Second, { init: 1, before: 1 }, 255 'hook1Second: when process exits'); 256 // hook2 missed the "init" and "before" since it was enabled after they 257 // occurred 258 checkInvocations(hook2First, { after: 1, destroy: 1 }, 259 'hook2First: when process exits'); 260 checkInvocations(hook2Second, { init: 1, before: 1, after: 1, destroy: 1 }, 261 'hook2Second: when process exits'); 262 checkInvocations(hook3First, { init: 1, before: 1, after: 1, destroy: 1 }, 263 'hook3First: when process exits'); 264 // We don't see a "destroy" invocation here since hook3 disabled itself 265 // during its "after" invocation 266 checkInvocations(hook3Second, { init: 1, before: 1, after: 1 }, 267 'hook3Second: when process exits'); 268} 269