diff options
-rw-r--r-- | doc/api/process.md | 101 | ||||
-rw-r--r-- | doc/api/report.md | 40 | ||||
-rw-r--r-- | lib/internal/bootstrap/pre_execution.js | 19 | ||||
-rw-r--r-- | lib/internal/process/execution.js | 17 | ||||
-rw-r--r-- | lib/internal/process/report.js | 161 | ||||
-rw-r--r-- | src/node_options.cc | 8 | ||||
-rw-r--r-- | src/node_report_module.cc | 223 | ||||
-rw-r--r-- | test/report/test-report-config.js | 87 | ||||
-rw-r--r-- | test/report/test-report-signal.js | 1 | ||||
-rw-r--r-- | test/report/test-report-triggerreport.js | 8 | ||||
-rw-r--r-- | test/report/test-report-uncaught-exception.js | 2 |
11 files changed, 390 insertions, 277 deletions
diff --git a/doc/api/process.md b/doc/api/process.md index d88c89b5c3..36fefe73d5 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -1669,6 +1669,21 @@ added: v11.8.0 reports for the current process. Additional documentation is available in the [report documentation][]. +## process.report.directory +<!-- YAML +added: REPLACEME +--> + +* {string} + +Directory where the report is written. The default value is the empty string, +indicating that reports are written to the current working directory of the +Node.js process. + +```js +console.log(`Report directory is ${process.report.directory}`); +``` + ### process.report.getReport([err]) <!-- YAML added: v11.8.0 @@ -1687,43 +1702,75 @@ console.log(data); Additional documentation is available in the [report documentation][]. -### process.report.setOptions([options]); +## process.report.filename <!-- YAML -added: v11.8.0 +added: REPLACEME --> -* `options` {Object} - * `events` {string[]} - * `signal`: Generate a report in response to a signal raised on the process. - * `exception`: Generate a report on unhandled exceptions. - * `fatalerror`: Generate a report on internal fault - (such as out of memory errors or native assertions). - * `signal` {string} Sets or resets the signal for report generation - (not supported on Windows). **Default:** `'SIGUSR2'`. - * `filename` {string} Name of the file where the report is written. - * `path` {string} Directory where the report is written. - **Default:** the current working directory of the Node.js process. +* {string} -Configures the diagnostic reporting behavior. Upon invocation, the runtime -is reconfigured to generate reports based on `options`. Several usage examples -are shown below. +Filename where the report is written. If set to the empty string, the output +filename will be comprised of a timestamp, PID, and sequence number. The default +value is the empty string. ```js -// Trigger a report on uncaught exceptions or fatal errors. -process.report.setOptions({ events: ['exception', 'fatalerror'] }); +console.log(`Report filename is ${process.report.filename}`); +``` + +## process.report.reportOnFatalError +<!-- YAML +added: REPLACEME +--> + +* {boolean} -// Change the default path and filename of the report. -process.report.setOptions({ filename: 'foo.json', path: '/home' }); +If `true`, a diagnostic report is generated on fatal errors, such as out of +memory errors or failed C++ assertions. -// Produce the report onto stdout, when generated. Special meaning is attached -// to `stdout` and `stderr`. Usage of these will result in report being written -// to the associated standard streams. URLs are not supported. -process.report.setOptions({ filename: 'stdout' }); +```js +console.log(`Report on fatal error: ${process.report.reportOnFatalError}`); ``` -Signal based report generation is not supported on Windows. +## process.report.reportOnSignal +<!-- YAML +added: REPLACEME +--> -Additional documentation is available in the [report documentation][]. +* {boolean} + +If `true`, a diagnostic report is generated when the process receives the +signal specified by `process.report.signal`. + +```js +console.log(`Report on signal: ${process.report.reportOnSignal}`); +``` + +## process.report.reportOnUncaughtException +<!-- YAML +added: REPLACEME +--> + +* {boolean} + +If `true`, a diagnostic report is generated on uncaught exception. + +```js +console.log(`Report on exception: ${process.report.reportOnUncaughtException}`); +``` + +## process.report.signal +<!-- YAML +added: REPLACEME +--> + +* {string} + +The signal used to trigger the creation of a diagnostic report. Defaults to +`SIGUSR2`. + +```js +console.log(`Report signal: ${process.report.signal}`); +``` ### process.report.triggerReport([filename][, err]) <!-- YAML @@ -1732,7 +1779,7 @@ added: v11.8.0 * `filename` {string} Name of the file where the report is written. This should be a relative path, that will be appended to the directory specified in - `process.report.setOptions`, or the current working directory of the Node.js + `process.report.directory`, or the current working directory of the Node.js process, if unspecified. * `err` {Error} A custom error used for reporting the JavaScript stack. diff --git a/doc/api/report.md b/doc/api/report.md index 856cd80119..5aff191ee4 100644 --- a/doc/api/report.md +++ b/doc/api/report.md @@ -484,21 +484,17 @@ times for the same Node.js process. ## Configuration -Additional runtime configuration that influences the report generation -constraints are available using `setOptions()` API. +Additional runtime configuration of report generation is available via +the following properties of `process.report`: -```js -process.report.setOptions({ - events: ['exception', 'fatalerror', 'signal'], - signal: 'SIGUSR2', - filename: 'myreport.json', - path: '/home/nodeuser' -}); -``` +`reportOnFatalError` triggers diagnostic reporting on fatal errors when `true`. +Defaults to `false`. + +`reportOnSignal` triggers diagnostic reporting on signal when `true`. This is +not supported on Windows. Defaults to `false`. -The `events` array contains one or more of the report triggering options. -The only valid entries are `'exception'`, `'fatalerror'` and `'signal'`. -By default, a report is not produced on any of these events. +`reportOnUncaughtException` triggers diagnostic reporting on uncaught exception +when `true`. Defaults to `false`. `signal` specifies the POSIX signal identifier that will be used to intercept external triggers for report generation. Defaults to @@ -507,24 +503,30 @@ to intercept external triggers for report generation. Defaults to `filename` specifies the name of the output file in the file system. Special meaning is attached to `stdout` and `stderr`. Usage of these will result in report being written to the associated standard streams. -In such cases when standard streams are used, value in `'path'` is ignored. +In cases where standard streams are used, the value in `'directory'` is ignored. URLs are not supported. Defaults to a composite filename that contains timestamp, PID and sequence number. -`path` specifies the filesystem directory where the report will be written to. +`directory` specifies the filesystem directory where the report will be written. URLs are not supported. Defaults to the current working directory of the Node.js process. ```js // Trigger report only on uncaught exceptions. -process.report.setOptions({ events: ['exception'] }); +process.report.reportOnFatalError = false; +process.report.reportOnSignal = false; +process.report.reportOnUncaughtException = true; // Trigger report for both internal errors as well as external signal. -process.report.setOptions({ events: ['fatalerror', 'signal'] }); +process.report.reportOnFatalError = true; +process.report.reportOnSignal = true; +process.report.reportOnUncaughtException = false; // Change the default signal to `SIGQUIT` and enable it. -process.report.setOptions( - { events: ['signal'], signal: 'SIGQUIT' }); +process.report.reportOnFatalError = false; +process.report.reportOnUncaughtException = false; +process.report.reportOnSignal = true; +process.report.signal = 'SIGQUIT'; ``` Configuration on module initialization is also available via diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index 3578e7c112..b6e606cf9d 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -40,14 +40,8 @@ function initializeReport() { if (!getOptionValue('--experimental-report')) { return; } - const { - config, - report, - syncConfig - } = require('internal/process/report'); + const { report } = require('internal/process/report'); process.report = report; - // Download the CLI / ENV config into JS land. - syncConfig(config, false); } function setupSignalHandlers() { @@ -68,13 +62,10 @@ function initializeReportSignalHandlers() { if (!getOptionValue('--experimental-report')) { return; } - const { - config, - handleSignal - } = require('internal/process/report'); - if (config.events.includes('signal')) { - process.on(config.signal, handleSignal); - } + + const { addSignalHandler } = require('internal/process/report'); + + addSignalHandler(); } function setupTraceCategoryState() { diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index eb06dc0480..310acc14b4 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -109,18 +109,11 @@ function createFatalException() { if (er == null || er.domain == null) { try { const report = internalBinding('report'); - if (report != null && - require('internal/options') - .getOptionValue('--experimental-report')) { - const config = {}; - report.syncConfig(config, false); - if (Array.isArray(config.events) && - config.events.includes('exception')) { - report.triggerReport(er ? er.message : 'Exception', - 'Exception', - null, - er ? er.stack : undefined); - } + if (report != null && report.shouldReportOnUncaughtException()) { + report.triggerReport(er ? er.message : 'Exception', + 'Exception', + null, + er ? er.stack : undefined); } } catch {} // Ignore the exception. Diagnostic reporting is unavailable. } diff --git a/lib/internal/process/report.js b/lib/internal/process/report.js index daf948258c..96729b2ee3 100644 --- a/lib/internal/process/report.js +++ b/lib/internal/process/report.js @@ -7,72 +7,9 @@ const { ERR_INVALID_ARG_TYPE, ERR_SYNTHETIC } = require('internal/errors').codes; - -// If report is enabled, extract the binding and -// wrap the APIs with thin layers, with some error checks. -// user options can come in from CLI / ENV / API. -// CLI and ENV is intercepted in C++ and the API call here (JS). -// So sync up with both sides as appropriate - initially from -// C++ to JS and from JS to C++ whenever the API is called. -// Some events are controlled purely from JS (signal | exception) -// and some from C++ (fatalerror) so this sync-up is essential for -// correct behavior and alignment with the supplied tunables. +const { validateString } = require('internal/validators'); const nr = internalBinding('report'); - -// Keep it un-exposed; lest programs play with it -// leaving us with a lot of unwanted sanity checks. -let config = { - events: [], - signal: 'SIGUSR2', - filename: '', - path: '' -}; const report = { - setOptions(options) { - emitExperimentalWarning('report'); - const previousConfig = config; - const newConfig = {}; - - if (options === null || typeof options !== 'object') - options = {}; - - if (Array.isArray(options.events)) - newConfig.events = options.events.slice(); - else if (options.events === undefined) - newConfig.events = []; - else - throw new ERR_INVALID_ARG_TYPE('events', 'Array', options.events); - - if (typeof options.filename === 'string') - newConfig.filename = options.filename; - else if (options.filename === undefined) - newConfig.filename = ''; - else - throw new ERR_INVALID_ARG_TYPE('filename', 'string', options.filename); - - if (typeof options.path === 'string') - newConfig.path = options.path; - else if (options.path === undefined) - newConfig.path = ''; - else - throw new ERR_INVALID_ARG_TYPE('path', 'string', options.path); - - if (typeof options.signal === 'string') - newConfig.signal = convertToValidSignal(options.signal); - else if (options.signal === undefined) - newConfig.signal = 'SIGUSR2'; - else - throw new ERR_INVALID_ARG_TYPE('signal', 'string', options.signal); - - if (previousConfig.signal) - process.removeListener(previousConfig.signal, handleSignal); - - if (newConfig.events.includes('signal')) - process.on(newConfig.signal, handleSignal); - - config = newConfig; - nr.syncConfig(config, true); - }, triggerReport(file, err) { emitExperimentalWarning('report'); @@ -98,18 +35,98 @@ const report = { throw new ERR_INVALID_ARG_TYPE('err', 'Object', err); return nr.getReport(err.stack); + }, + get directory() { + emitExperimentalWarning('report'); + return nr.getDirectory(); + }, + set directory(dir) { + emitExperimentalWarning('report'); + validateString(dir, 'directory'); + return nr.setDirectory(dir); + }, + get filename() { + emitExperimentalWarning('report'); + return nr.getFilename(); + }, + set filename(name) { + emitExperimentalWarning('report'); + validateString(name, 'filename'); + return nr.setFilename(name); + }, + get signal() { + emitExperimentalWarning('report'); + return nr.getSignal(); + }, + set signal(sig) { + emitExperimentalWarning('report'); + validateString(sig, 'signal'); + convertToValidSignal(sig); // Validate that the signal is recognized. + removeSignalHandler(); + addSignalHandler(sig); + return nr.setSignal(sig); + }, + get reportOnFatalError() { + emitExperimentalWarning('report'); + return nr.shouldReportOnFatalError(); + }, + set reportOnFatalError(trigger) { + emitExperimentalWarning('report'); + + if (typeof trigger !== 'boolean') + throw new ERR_INVALID_ARG_TYPE('trigger', 'boolean', trigger); + + return nr.setReportOnFatalError(trigger); + }, + get reportOnSignal() { + emitExperimentalWarning('report'); + return nr.shouldReportOnSignal(); + }, + set reportOnSignal(trigger) { + emitExperimentalWarning('report'); + + if (typeof trigger !== 'boolean') + throw new ERR_INVALID_ARG_TYPE('trigger', 'boolean', trigger); + + nr.setReportOnSignal(trigger); + removeSignalHandler(); + addSignalHandler(); + }, + get reportOnUncaughtException() { + emitExperimentalWarning('report'); + return nr.shouldReportOnUncaughtException(); + }, + set reportOnUncaughtException(trigger) { + emitExperimentalWarning('report'); + + if (typeof trigger !== 'boolean') + throw new ERR_INVALID_ARG_TYPE('trigger', 'boolean', trigger); + + return nr.setReportOnUncaughtException(trigger); } }; -function handleSignal(signo) { - if (typeof signo !== 'string') - signo = config.signal; - nr.triggerReport(signo, 'Signal', null, ''); +function addSignalHandler(sig) { + if (nr.shouldReportOnSignal()) { + if (typeof sig !== 'string') + sig = nr.getSignal(); + + process.on(sig, signalHandler); + } +} + +function removeSignalHandler() { + const sig = nr.getSignal(); + + if (sig) + process.removeListener(sig, signalHandler); +} + +function signalHandler(sig) { + nr.triggerReport(sig, 'Signal', null, ''); } module.exports = { - config, - handleSignal, - report, - syncConfig: nr.syncConfig + addSignalHandler, + report }; diff --git a/src/node_options.cc b/src/node_options.cc index 7107549bd4..f2961b2147 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -57,8 +57,14 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors) { void PerIsolateOptions::CheckOptions(std::vector<std::string>* errors) { per_env->CheckOptions(errors); #ifdef NODE_REPORT - if (per_env->experimental_report) + if (per_env->experimental_report) { + // Assign the report_signal default value here. Once the + // --experimental-report flag is dropped, move this initialization to + // node_options.h, where report_signal is declared. + if (report_signal.empty()) + report_signal = "SIGUSR2"; return; + } if (!report_directory.empty()) { errors->push_back("--diagnostic-report-directory option is valid only when " diff --git a/src/node_report_module.cc b/src/node_report_module.cc index cf17db62db..202730d3b6 100644 --- a/src/node_report_module.cc +++ b/src/node_report_module.cc @@ -18,10 +18,7 @@ namespace report { using node::Environment; -using node::FIXED_ONE_BYTE_STRING; -using node::PerIsolateOptions; using node::Utf8Value; -using v8::Array; using v8::Boolean; using v8::Context; using v8::Function; @@ -31,7 +28,6 @@ using v8::Isolate; using v8::Local; using v8::Object; using v8::String; -using v8::V8; using v8::Value; // External JavaScript API for triggering a report @@ -75,129 +71,89 @@ void GetReport(const FunctionCallbackInfo<Value>& info) { .ToLocalChecked()); } -// A method to sync up data elements in the JS land with its -// corresponding elements in the C++ world. Required because -// (i) the tunables are first intercepted through the CLI but -// later modified via APIs. (ii) the report generation events -// are controlled partly from C++ and partly from JS. -void SyncConfig(const FunctionCallbackInfo<Value>& info) { +static void GetDirectory(const FunctionCallbackInfo<Value>& info) { Environment* env = Environment::GetCurrent(info); - Local<Context> context = env->context(); - std::shared_ptr<PerIsolateOptions> options = env->isolate_data()->options(); - - CHECK_EQ(info.Length(), 2); - Local<Object> obj; - if (!info[0]->ToObject(context).ToLocal(&obj)) return; - bool sync = info[1].As<Boolean>()->Value(); - - // Events array - Local<String> eventskey = FIXED_ONE_BYTE_STRING(env->isolate(), "events"); - Local<Value> events_unchecked; - if (!obj->Get(context, eventskey).ToLocal(&events_unchecked)) return; - Local<Array> events; - if (events_unchecked->IsUndefined() || events_unchecked->IsNull()) { - events_unchecked = Array::New(env->isolate(), 0); - if (obj->Set(context, eventskey, events_unchecked).IsNothing()) return; - } - events = events_unchecked.As<Array>(); - - // Signal - Local<String> signalkey = env->signal_string(); - Local<Value> signal_unchecked; - if (!obj->Get(context, signalkey).ToLocal(&signal_unchecked)) return; - Local<String> signal; - if (signal_unchecked->IsUndefined() || signal_unchecked->IsNull()) - signal_unchecked = signalkey; - signal = signal_unchecked.As<String>(); - - Utf8Value signalstr(env->isolate(), signal); - - // Report file - Local<String> filekey = FIXED_ONE_BYTE_STRING(env->isolate(), "filename"); - Local<Value> file_unchecked; - if (!obj->Get(context, filekey).ToLocal(&file_unchecked)) return; - Local<String> file; - if (file_unchecked->IsUndefined() || file_unchecked->IsNull()) - file_unchecked = filekey; - file = file_unchecked.As<String>(); - - Utf8Value filestr(env->isolate(), file); - - // Report file path - Local<String> pathkey = FIXED_ONE_BYTE_STRING(env->isolate(), "path"); - Local<Value> path_unchecked; - if (!obj->Get(context, pathkey).ToLocal(&path_unchecked)) return; - Local<String> path; - if (path_unchecked->IsUndefined() || path_unchecked->IsNull()) - path_unchecked = pathkey; - path = path_unchecked.As<String>(); - - Utf8Value pathstr(env->isolate(), path); - - if (sync) { - static const std::string e = "exception"; - static const std::string s = "signal"; - static const std::string f = "fatalerror"; - for (uint32_t i = 0; i < events->Length(); i++) { - Local<Value> v; - if (!events->Get(context, i).ToLocal(&v)) return; - Local<String> elem; - if (!v->ToString(context).ToLocal(&elem)) return; - String::Utf8Value buf(env->isolate(), elem); - if (*buf == e) { - options->report_uncaught_exception = true; - } else if (*buf == s) { - options->report_on_signal = true; - } else if (*buf == f) { - options->report_on_fatalerror = true; - } - } - CHECK_NOT_NULL(*signalstr); - options->report_signal = *signalstr; - CHECK_NOT_NULL(*filestr); - options->report_filename = *filestr; - CHECK_NOT_NULL(*pathstr); - options->report_directory = *pathstr; - } else { - int i = 0; - if (options->report_uncaught_exception && - events - ->Set(context, - i++, - FIXED_ONE_BYTE_STRING(env->isolate(), "exception")) - .IsNothing()) - return; - if (options->report_on_signal && - events - ->Set(context, i++, FIXED_ONE_BYTE_STRING(env->isolate(), "signal")) - .IsNothing()) - return; - if (options->report_on_fatalerror && - events - ->Set( - context, i, FIXED_ONE_BYTE_STRING(env->isolate(), "fatalerror")) - .IsNothing()) - return; - - Local<Value> signal_value; - Local<Value> file_value; - Local<Value> path_value; - std::string signal = options->report_signal; - if (signal.empty()) signal = "SIGUSR2"; - if (!node::ToV8Value(context, signal).ToLocal(&signal_value)) - return; - if (!obj->Set(context, signalkey, signal_value).FromJust()) return; - - if (!node::ToV8Value(context, options->report_filename) - .ToLocal(&file_value)) - return; - if (!obj->Set(context, filekey, file_value).FromJust()) return; - - if (!node::ToV8Value(context, options->report_directory) - .ToLocal(&path_value)) - return; - if (!obj->Set(context, pathkey, path_value).FromJust()) return; - } + std::string directory = env->isolate_data()->options()->report_directory; + auto result = String::NewFromUtf8(env->isolate(), + directory.c_str(), + v8::NewStringType::kNormal); + info.GetReturnValue().Set(result.ToLocalChecked()); +} + +static void SetDirectory(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + CHECK(info[0]->IsString()); + Utf8Value dir(env->isolate(), info[0].As<String>()); + env->isolate_data()->options()->report_directory = *dir; +} + +static void GetFilename(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + std::string filename = env->isolate_data()->options()->report_filename; + auto result = String::NewFromUtf8(env->isolate(), + filename.c_str(), + v8::NewStringType::kNormal); + info.GetReturnValue().Set(result.ToLocalChecked()); +} + +static void SetFilename(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + CHECK(info[0]->IsString()); + Utf8Value name(env->isolate(), info[0].As<String>()); + env->isolate_data()->options()->report_filename = *name; +} + +static void GetSignal(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + std::string signal = env->isolate_data()->options()->report_signal; + auto result = String::NewFromUtf8(env->isolate(), + signal.c_str(), + v8::NewStringType::kNormal); + info.GetReturnValue().Set(result.ToLocalChecked()); +} + +static void SetSignal(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + CHECK(info[0]->IsString()); + Utf8Value signal(env->isolate(), info[0].As<String>()); + env->isolate_data()->options()->report_signal = *signal; +} + +static void ShouldReportOnFatalError(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + info.GetReturnValue().Set( + env->isolate_data()->options()->report_on_fatalerror); +} + +static void SetReportOnFatalError(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + CHECK(info[0]->IsBoolean()); + env->isolate_data()->options()->report_on_fatalerror = info[0]->IsTrue(); +} + +static void ShouldReportOnSignal(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + info.GetReturnValue().Set(env->isolate_data()->options()->report_on_signal); +} + +static void SetReportOnSignal(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + CHECK(info[0]->IsBoolean()); + env->isolate_data()->options()->report_on_signal = info[0]->IsTrue(); +} + +static void ShouldReportOnUncaughtException( + const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + info.GetReturnValue().Set( + env->isolate_data()->options()->report_uncaught_exception); +} + +static void SetReportOnUncaughtException( + const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + CHECK(info[0]->IsBoolean()); + env->isolate_data()->options()->report_uncaught_exception = info[0]->IsTrue(); } static void Initialize(Local<Object> exports, @@ -207,7 +163,20 @@ static void Initialize(Local<Object> exports, env->SetMethod(exports, "triggerReport", TriggerReport); env->SetMethod(exports, "getReport", GetReport); - env->SetMethod(exports, "syncConfig", SyncConfig); + env->SetMethod(exports, "getDirectory", GetDirectory); + env->SetMethod(exports, "setDirectory", SetDirectory); + env->SetMethod(exports, "getFilename", GetFilename); + env->SetMethod(exports, "setFilename", SetFilename); + env->SetMethod(exports, "getSignal", GetSignal); + env->SetMethod(exports, "setSignal", SetSignal); + env->SetMethod(exports, "shouldReportOnFatalError", ShouldReportOnFatalError); + env->SetMethod(exports, "setReportOnFatalError", SetReportOnFatalError); + env->SetMethod(exports, "shouldReportOnSignal", ShouldReportOnSignal); + env->SetMethod(exports, "setReportOnSignal", SetReportOnSignal); + env->SetMethod(exports, "shouldReportOnUncaughtException", + ShouldReportOnUncaughtException); + env->SetMethod(exports, "setReportOnUncaughtException", + SetReportOnUncaughtException); } } // namespace report diff --git a/test/report/test-report-config.js b/test/report/test-report-config.js new file mode 100644 index 0000000000..796f63d2b8 --- /dev/null +++ b/test/report/test-report-config.js @@ -0,0 +1,87 @@ +// Flags: --experimental-report --diagnostic-report-on-fatalerror --diagnostic-report-on-signal --diagnostic-report-uncaught-exception +'use strict'; +const common = require('../common'); +common.skipIfReportDisabled(); +const assert = require('assert'); + +common.expectWarning('ExperimentalWarning', + 'report is an experimental feature. This feature could ' + + 'change at any time'); + +// Verify that process.report.directory behaves properly. +assert.strictEqual(process.report.directory, ''); +process.report.directory = __dirname; +assert.strictEqual(process.report.directory, __dirname); +common.expectsError(() => { + process.report.directory = {}; +}, { code: 'ERR_INVALID_ARG_TYPE' }); +assert.strictEqual(process.report.directory, __dirname); + +// Verify that process.report.filename behaves properly. +assert.strictEqual(process.report.filename, ''); +process.report.filename = 'test-report.json'; +assert.strictEqual(process.report.filename, 'test-report.json'); +common.expectsError(() => { + process.report.filename = {}; +}, { code: 'ERR_INVALID_ARG_TYPE' }); +assert.strictEqual(process.report.filename, 'test-report.json'); + +// Verify that process.report.reportOnFatalError behaves properly. +assert.strictEqual(process.report.reportOnFatalError, true); +process.report.reportOnFatalError = false; +assert.strictEqual(process.report.reportOnFatalError, false); +process.report.reportOnFatalError = true; +assert.strictEqual(process.report.reportOnFatalError, true); +common.expectsError(() => { + process.report.reportOnFatalError = {}; +}, { code: 'ERR_INVALID_ARG_TYPE' }); +assert.strictEqual(process.report.reportOnFatalError, true); + + +// Verify that process.report.reportOnUncaughtException behaves properly. +assert.strictEqual(process.report.reportOnUncaughtException, true); +process.report.reportOnUncaughtException = false; +assert.strictEqual(process.report.reportOnUncaughtException, false); +process.report.reportOnUncaughtException = true; +assert.strictEqual(process.report.reportOnUncaughtException, true); +common.expectsError(() => { + process.report.reportOnUncaughtException = {}; +}, { code: 'ERR_INVALID_ARG_TYPE' }); +assert.strictEqual(process.report.reportOnUncaughtException, true); + +// Verify that process.report.reportOnSignal behaves properly. +assert.strictEqual(process.report.reportOnSignal, true); +process.report.reportOnSignal = false; +assert.strictEqual(process.report.reportOnSignal, false); +process.report.reportOnSignal = true; +assert.strictEqual(process.report.reportOnSignal, true); +common.expectsError(() => { + process.report.reportOnSignal = {}; +}, { code: 'ERR_INVALID_ARG_TYPE' }); +assert.strictEqual(process.report.reportOnSignal, true); + +if (!common.isWindows) { + // Verify that process.report.signal behaves properly. + assert.strictEqual(process.report.signal, 'SIGUSR2'); + common.expectsError(() => { + process.report.signal = {}; + }, { code: 'ERR_INVALID_ARG_TYPE' }); + common.expectsError(() => { + process.report.signal = 'foo'; + }, { code: 'ERR_UNKNOWN_SIGNAL' }); + assert.strictEqual(process.report.signal, 'SIGUSR2'); + process.report.signal = 'SIGUSR1'; + assert.strictEqual(process.report.signal, 'SIGUSR1'); + + // Verify that the interaction between reportOnSignal and signal is correct. + process.report.signal = 'SIGUSR2'; + process.report.reportOnSignal = false; + assert.strictEqual(process.listenerCount('SIGUSR2'), 0); + process.report.reportOnSignal = true; + assert.strictEqual(process.listenerCount('SIGUSR2'), 1); + process.report.signal = 'SIGUSR1'; + assert.strictEqual(process.listenerCount('SIGUSR2'), 0); + assert.strictEqual(process.listenerCount('SIGUSR1'), 1); + process.report.reportOnSignal = false; + assert.strictEqual(process.listenerCount('SIGUSR1'), 0); +} diff --git a/test/report/test-report-signal.js b/test/report/test-report-signal.js index 129933b546..68f5c25402 100644 --- a/test/report/test-report-signal.js +++ b/test/report/test-report-signal.js @@ -64,6 +64,7 @@ if (process.argv[2] === 'child') { }); child.on('exit', common.mustCall((code, signal) => { console.log('child exited'); + console.log(stderr); const report_msg = 'No reports found'; const process_msg = 'Process exited unexpectedly'; const signal_msg = 'Process exited with unexpected signal'; diff --git a/test/report/test-report-triggerreport.js b/test/report/test-report-triggerreport.js index 4ac8f1a9a5..c9ad8cdee0 100644 --- a/test/report/test-report-triggerreport.js +++ b/test/report/test-report-triggerreport.js @@ -14,7 +14,7 @@ common.expectWarning('ExperimentalWarning', 'report is an experimental feature. This feature could ' + 'change at any time'); tmpdir.refresh(); -process.report.setOptions({ path: tmpdir.path }); +process.report.directory = tmpdir.path; function validate() { const reports = helper.findReports(process.pid, tmpdir.path); @@ -59,11 +59,11 @@ function validate() { { // Test with a filename option. - const filename = path.join(tmpdir.path, 'custom-name-3.json'); - process.report.setOptions({ filename }); + process.report.filename = 'custom-name-3.json'; const file = process.report.triggerReport(); assert.strictEqual(helper.findReports(process.pid, tmpdir.path).length, 0); - assert.strictEqual(file, filename); + const filename = path.join(process.report.directory, 'custom-name-3.json'); + assert.strictEqual(file, process.report.filename); helper.validate(filename); fs.unlinkSync(filename); } diff --git a/test/report/test-report-uncaught-exception.js b/test/report/test-report-uncaught-exception.js index b3da7c4244..34e4759ea2 100644 --- a/test/report/test-report-uncaught-exception.js +++ b/test/report/test-report-uncaught-exception.js @@ -12,7 +12,7 @@ common.expectWarning('ExperimentalWarning', 'report is an experimental feature. This feature could ' + 'change at any time'); tmpdir.refresh(); -process.report.setOptions({ path: tmpdir.path }); +process.report.directory = tmpdir.path; process.on('uncaughtException', common.mustCall((err) => { assert.strictEqual(err, error); |