diff --git a/.circleci/config.yml b/.circleci/config.yml index a75635894f1df..6da04eaee88c0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -553,8 +553,11 @@ jobs: - install-node-version: node_version: "10.19.0" - run-tests: - title: "core2.test_hello_world" - test_targets: "core2.test_hello_world" + title: "selected subset" + test_targets: " + other.test_native_call_before_init + other.test_node_unhandled_rejection + core2.test_hello_world" # Run a few test with the most recent version of node # In particular we have some tests that require node flags on older # versions of node but not with the most recent version. @@ -562,8 +565,14 @@ jobs: - run-tests: # Run tests that on older versions of node would require flags, but # those flags should not be injected on newer versions. - title: "core2 subset" - test_targets: "-v core2.test_pthread_create core2.test_i64_invoke_bigint core2.test_source_map core2.test_exceptions_wasm" + title: "selected subset" + test_targets: "-v + other.test_native_call_before_init + other.test_node_unhandled_rejection + core2.test_pthread_create + core2.test_i64_invoke_bigint + core2.test_source_map + core2.test_exceptions_wasm" - upload-test-results test-other: executor: bionic diff --git a/emcc.py b/emcc.py index 09f433e07803e..0685cf8447e10 100755 --- a/emcc.py +++ b/emcc.py @@ -2157,10 +2157,20 @@ def phase_linker_setup(options, state, newargs): settings.MIN_IE_VERSION = 0 settings.MIN_EDGE_VERSION = 0 settings.MIN_CHROME_VERSION = 0 + settings.MIN_NODE_VERSION = 0 if settings.MIN_CHROME_VERSION <= 37: settings.WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG = 1 + # 10.19.0 is the oldest version of node that we do any testing with. + # Keep this in sync with the test-node-compat in .circleci/config.yml + # and MINIMUM_NODE_VERSION in tools/shared.py + if settings.MIN_NODE_VERSION: + if settings.MIN_NODE_VERSION < 101900: + exit_with_error('targeting node older than 10.19.00 is not supported') + if settings.MIN_NODE_VERSION >= 150000: + default_setting('NODEJS_CATCH_REJECTION', 0) + setup_environment_settings() if options.use_closure_compiler != 0: diff --git a/src/runtime_debug.js b/src/runtime_debug.js index 182fb891f64d0..3b1237617dafb 100644 --- a/src/runtime_debug.js +++ b/src/runtime_debug.js @@ -39,13 +39,15 @@ function isExportedByForceFilesystem(name) { } function missingGlobal(sym, msg) { - Object.defineProperty(globalThis, sym, { - configurable: true, - get: function() { - warnOnce('`' + sym + '` is not longer defined by emscripten. ' + msg); - return undefined; - } - }); + if (typeof globalThis !== 'undefined') { + Object.defineProperty(globalThis, sym, { + configurable: true, + get: function() { + warnOnce('`' + sym + '` is not longer defined by emscripten. ' + msg); + return undefined; + } + }); + } } missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); diff --git a/src/settings.js b/src/settings.js index c13a25b07aa31..52f384dfd9dea 100644 --- a/src/settings.js +++ b/src/settings.js @@ -725,21 +725,22 @@ var DISABLE_EXCEPTION_THROWING = false; // By default we handle exit() in node, by catching the Exit exception. However, // this means we catch all process exceptions. If you disable this, then we no -// longer do that, and exceptions work normally, which can be useful for libraries -// or programs that don't need exit() to work. - +// longer do that, and exceptions work normally, which can be useful for +// libraries or programs that don't need exit() to work. +// // Emscripten uses an ExitStatus exception to halt when exit() is called. // With this option, we prevent that from showing up as an unhandled // exception. // [link] var NODEJS_CATCH_EXIT = true; -// Catch unhandled rejections in node. Without this, node may print the error, -// and that this behavior will change in future node, wait a few seconds, and -// then exit with 0 (which hides the error if you don't read the log). With -// this, we catch any unhandled rejection and throw an actual error, which will -// make the process exit immediately with a non-0 return code. -// This should be fixed in Node 15+. +// Catch unhandled rejections in node. This only effect versions of node older +// than 15. Without this, old version node will print a warning, but exit +// with a zero return code. With this setting enabled, we handle any unhandled +// rejection and throw an exception, which will cause the process exit +// immediately with a non-0 return code. +// This not needed in Node 15+ so this setting will default to false if +// MIN_NODE_VERSION is 150000 or above. // [link] var NODEJS_CATCH_REJECTION = true; @@ -1785,6 +1786,12 @@ var MIN_EDGE_VERSION = 0x7FFFFFFF; // [link] var MIN_CHROME_VERSION = 75; +// Specifies minimum node version to target for the generated code. This is +// distinct from the minimum version required run the emscripten compiler. +// This version aligns with the current Ubuuntu TLS 20.04 (Focal). +// Version is encoded in MMmmVV, e.g. 1814101 denotes Node 18.14.01. +var MIN_NODE_VERSION = 101900; + // Tracks whether we are building with errno support enabled. Set to 0 // to disable compiling errno support in altogether. This saves a little // bit of generated code size in applications that do not care about diff --git a/src/shell.js b/src/shell.js index 97b597bda7d7d..f43547e574a5e 100644 --- a/src/shell.js +++ b/src/shell.js @@ -245,7 +245,10 @@ if (ENVIRONMENT_IS_NODE) { // not be needed with node v15 and about because it is now the default // behaviour: // See https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode - process['on']('unhandledRejection', function(reason) { throw reason; }); + var nodeMajor = process.version.match(/^v(\d+)\./)[1]; + if (nodeMajor < 15) { + process['on']('unhandledRejection', function(reason) { throw reason; }); + } #endif quit_ = (status, toThrow) => { diff --git a/test/test_other.py b/test/test_other.py index d7c4b303bd787..5029d0a91dd4c 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -12032,18 +12032,19 @@ def test_node_unhandled_rejection(self): # exit code and log the stack trace correctly. self.run_process([EMCC, '--pre-js=pre.js', '-sNODEJS_CATCH_REJECTION', 'main.c']) output = self.run_js('a.out.js', assert_returncode=NON_ZERO) + self.assertContained('unhandledRejection', read_file('a.out.js')) self.assertContained('ReferenceError: missing is not defined', output) self.assertContained('at foo (', output) - version = self.run_process(config.NODE_JS + ['--version'], stdout=PIPE).stdout.strip() - version = [int(v) for v in version.replace('v', '').replace('-pre', '').split('.')] - if version[0] >= 15: - self.skipTest('old behaviour of node JS cannot be tested on node v15 or above') - # Without NODEJS_CATCH_REJECTION we expect node to log the unhandled rejection # but return 0. self.node_args = [a for a in self.node_args if '--unhandled-rejections' not in a] self.run_process([EMCC, '--pre-js=pre.js', '-sNODEJS_CATCH_REJECTION=0', 'main.c']) + self.assertNotContained('unhandledRejection', read_file('a.out.js')) + + if shared.check_node_version()[0] >= 15: + self.skipTest('old behaviour of node JS cannot be tested on node v15 or above') + output = self.run_js('a.out.js') self.assertContained('ReferenceError: missing is not defined', output) self.assertContained('at foo (', output)