1'use strict';
2
3const common = require('../common');
4const assert = require('assert');
5const fs = require('fs');
6
7// The goal of this test is to make sure that:
8//
9// - Even if --abort_on_uncaught_exception is passed on the command line,
10// setting up a top-level domain error handler and throwing an error
11// within this domain does *not* make the process abort. The process exits
12// gracefully.
13//
14// - When passing --abort_on_uncaught_exception on the command line and
15// setting up a top-level domain error handler, an error thrown
16// within this domain's error handler *does* make the process abort.
17//
18// - When *not* passing --abort_on_uncaught_exception on the command line and
19// setting up a top-level domain error handler, an error thrown within this
20// domain's error handler does *not* make the process abort, but makes it exit
21// with the proper failure exit code.
22//
23// - When throwing an error within the top-level domain's error handler
24// within a try/catch block, the process should exit gracefully, whether or
25// not --abort_on_uncaught_exception is passed on the command line.
26
27const domainErrHandlerExMessage = 'exception from domain error handler';
28
29if (process.argv[2] === 'child') {
30  const domain = require('domain');
31  const d = domain.create();
32
33  process.on('uncaughtException', function onUncaughtException() {
34    // The process' uncaughtException event must not be emitted when
35    // an error handler is setup on the top-level domain.
36    // Exiting with exit code of 42 here so that it would assert when
37    // the parent checks the child exit code.
38    process.exit(42);
39  });
40
41  d.on('error', function(err) {
42    // Swallowing the error on purpose if 'throwInDomainErrHandler' is not
43    // set
44    if (process.argv.includes('throwInDomainErrHandler')) {
45      // If useTryCatch is set, wrap the throw in a try/catch block.
46      // This is to make sure that a caught exception does not trigger
47      // an abort.
48      if (process.argv.includes('useTryCatch')) {
49        try {
50          throw new Error(domainErrHandlerExMessage);
51        } catch {
52          // Continue regardless of error.
53        }
54      } else {
55        throw new Error(domainErrHandlerExMessage);
56      }
57    }
58  });
59
60  d.run(function doStuff() {
61    // Throwing from within different types of callbacks as each of them
62    // handles domains differently
63    process.nextTick(function() {
64      throw new Error('Error from nextTick callback');
65    });
66
67    fs.exists('/non/existing/file', function onExists(exists) {
68      throw new Error('Error from fs.exists callback');
69    });
70
71    setImmediate(function onSetImmediate() {
72      throw new Error('Error from setImmediate callback');
73    });
74
75    setTimeout(function onTimeout() {
76      throw new Error('Error from setTimeout callback');
77    }, 0);
78
79    throw new Error('Error from domain.run callback');
80  });
81} else {
82  const exec = require('child_process').exec;
83
84  function testDomainExceptionHandling(cmdLineOption, options) {
85    if (typeof cmdLineOption === 'object') {
86      options = cmdLineOption;
87      cmdLineOption = undefined;
88    }
89
90    let throwInDomainErrHandlerOpt;
91    if (options.throwInDomainErrHandler)
92      throwInDomainErrHandlerOpt = 'throwInDomainErrHandler';
93
94    let cmdToExec = '';
95    if (!common.isWindows) {
96      // Do not create core files, as it can take a lot of disk space on
97      // continuous testing and developers' machines
98      cmdToExec += 'ulimit -c 0 && ';
99    }
100
101    let useTryCatchOpt;
102    if (options.useTryCatch)
103      useTryCatchOpt = 'useTryCatch';
104
105    cmdToExec += `"${process.argv[0]}" ${cmdLineOption ? cmdLineOption : ''} "${
106      process.argv[1]}" child ${throwInDomainErrHandlerOpt} ${useTryCatchOpt}`;
107
108    const child = exec(cmdToExec);
109
110    if (child) {
111      child.on('exit', function onChildExited(exitCode, signal) {
112        // When throwing errors from the top-level domain error handler
113        // outside of a try/catch block, the process should not exit gracefully
114        if (!options.useTryCatch && options.throwInDomainErrHandler) {
115          if (cmdLineOption === '--abort_on_uncaught_exception') {
116            assert(common.nodeProcessAborted(exitCode, signal),
117                   'process should have aborted, but did not');
118          } else {
119            // By default, uncaught exceptions make node exit with an exit
120            // code of 7.
121            assert.strictEqual(exitCode, 7);
122            assert.strictEqual(signal, null);
123          }
124        } else {
125          // If the top-level domain's error handler does not throw,
126          // the process must exit gracefully, whether or not
127          // --abort_on_uncaught_exception was passed on the command line
128          assert.strictEqual(exitCode, 0);
129          assert.strictEqual(signal, null);
130        }
131      });
132    }
133  }
134
135  testDomainExceptionHandling('--abort_on_uncaught_exception', {
136    throwInDomainErrHandler: false,
137    useTryCatch: false
138  });
139
140  testDomainExceptionHandling('--abort_on_uncaught_exception', {
141    throwInDomainErrHandler: false,
142    useTryCatch: true
143  });
144
145  testDomainExceptionHandling('--abort_on_uncaught_exception', {
146    throwInDomainErrHandler: true,
147    useTryCatch: false
148  });
149
150  testDomainExceptionHandling('--abort_on_uncaught_exception', {
151    throwInDomainErrHandler: true,
152    useTryCatch: true
153  });
154
155  testDomainExceptionHandling({
156    throwInDomainErrHandler: false
157  });
158
159  testDomainExceptionHandling({
160    throwInDomainErrHandler: false,
161    useTryCatch: false
162  });
163
164  testDomainExceptionHandling({
165    throwInDomainErrHandler: true,
166    useTryCatch: true
167  });
168}
169