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