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