summaryrefslogtreecommitdiff
path: root/test/addons/make-callback-recurse/test.js
blob: 4a540003acd8d15ddfba2ebf7fb131e29ab2e988 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
'use strict';

const common = require('../../common');
const assert = require('assert');
const domain = require('domain');
const binding = require(`./build/${common.buildType}/binding`);
const makeCallback = binding.makeCallback;

// Make sure this is run in the future.
const mustCallCheckDomains = common.mustCall(checkDomains);

// Make sure that using MakeCallback allows the error to propagate.
assert.throws(() => {
  makeCallback({}, () => {
    throw new Error('hi from domain error');
  });
}, /^Error: hi from domain error$/);


// Check the execution order of the nextTickQueue and MicrotaskQueue in
// relation to running multiple MakeCallback's from bootstrap,
// node::MakeCallback() and node::AsyncWrap::MakeCallback().
// TODO(trevnorris): Is there a way to verify this is being run during
// bootstrap?
(function verifyExecutionOrder(arg) {
  const results = [];

  // Processing of the MicrotaskQueue is manually handled by node. They are not
  // processed until after the nextTickQueue has been processed.
  Promise.resolve(1).then(common.mustCall(() => {
    results.push(7);
  }));

  // The nextTick should run after all immediately invoked calls.
  process.nextTick(common.mustCall(() => {
    results.push(3);

    // Run same test again but while processing the nextTickQueue to make sure
    // the following MakeCallback call breaks in the middle of processing the
    // queue and allows the script to run normally.
    process.nextTick(common.mustCall(() => {
      results.push(6);
    }));

    makeCallback({}, common.mustCall(() => {
      results.push(4);
    }));

    results.push(5);
  }));

  results.push(0);

  // MakeCallback is calling the function immediately, but should then detect
  // that a script is already in the middle of execution and return before
  // either the nextTickQueue or MicrotaskQueue are processed.
  makeCallback({}, common.mustCall(() => {
    results.push(1);
  }));

  // This should run before either the nextTickQueue or MicrotaskQueue are
  // processed. Previously MakeCallback would not detect this circumstance
  // and process them immediately.
  results.push(2);

  setImmediate(common.mustCall(() => {
    for (let i = 0; i < results.length; i++) {
      assert.strictEqual(results[i], i,
                         `verifyExecutionOrder(${arg}) results: ${results}`);
    }
    if (arg === 1) {
      // The tests are first run on bootstrap during LoadEnvironment() in
      // src/node.cc. Now run the tests through node::MakeCallback().
      setImmediate(() => {
        makeCallback({}, common.mustCall(() => {
          verifyExecutionOrder(2);
        }));
      });
    } else if (arg === 2) {
      // Make sure there are no conflicts using node::MakeCallback()
      // within timers.
      setTimeout(common.mustCall(() => {
        verifyExecutionOrder(3);
      }), 10);
    } else if (arg === 3) {
      mustCallCheckDomains();
    } else {
      throw new Error('UNREACHABLE');
    }
  }));
}(1));


function checkDomains() {
  // Check that domains are properly entered/exited when called in multiple
  // levels from both node::MakeCallback() and AsyncWrap::MakeCallback
  setImmediate(common.mustCall(() => {
    const d1 = domain.create();
    const d2 = domain.create();
    const d3 = domain.create();

    makeCallback({ domain: d1 }, common.mustCall(() => {
      assert.strictEqual(d1, process.domain);
      makeCallback({ domain: d2 }, common.mustCall(() => {
        assert.strictEqual(d2, process.domain);
        makeCallback({ domain: d3 }, common.mustCall(() => {
          assert.strictEqual(d3, process.domain);
        }));
        assert.strictEqual(d2, process.domain);
      }));
      assert.strictEqual(d1, process.domain);
    }));
  }));

  setTimeout(common.mustCall(() => {
    const d1 = domain.create();
    const d2 = domain.create();
    const d3 = domain.create();

    makeCallback({ domain: d1 }, common.mustCall(() => {
      assert.strictEqual(d1, process.domain);
      makeCallback({ domain: d2 }, common.mustCall(() => {
        assert.strictEqual(d2, process.domain);
        makeCallback({ domain: d3 }, common.mustCall(() => {
          assert.strictEqual(d3, process.domain);
        }));
        assert.strictEqual(d2, process.domain);
      }));
      assert.strictEqual(d1, process.domain);
    }));
  }), 1);

  function testTimer(id) {
    // Make sure nextTick, setImmediate and setTimeout can all recover properly
    // after a thrown makeCallback call.
    const d = domain.create();
    d.on('error', common.mustCall((e) => {
      assert.strictEqual(e.message, `throw from domain ${id}`);
    }));
    makeCallback({ domain: d }, () => {
      throw new Error(`throw from domain ${id}`);
    });
    throw new Error('UNREACHABLE');
  }

  process.nextTick(common.mustCall(testTimer), 3);
  setImmediate(common.mustCall(testTimer), 2);
  setTimeout(common.mustCall(testTimer), 1, 1);
}