• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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