[Pkg-javascript-commits] [node-stack-utils] 01/04: Import Upstream version 1.0.0
Abhishek Lolage
abhisheklolage-guest at moszumanska.debian.org
Wed Feb 8 09:38:15 UTC 2017
This is an automated email from the git hooks/post-receive script.
abhisheklolage-guest pushed a commit to branch master
in repository node-stack-utils.
commit e5e0e5e7a9f3de4e0aac49879f47b21e8172abb5
Author: Abhishek Lolage <abhisheklolage at gmail.com>
Date: Wed Feb 8 15:01:40 2017 +0000
Import Upstream version 1.0.0
---
.editorconfig | 15 +
.gitattributes | 1 +
.gitignore | 3 +
index.js | 289 +++++++++++++++++
license | 23 ++
package.json | 34 ++
readme.md | 132 ++++++++
test/_utils.js | 13 +
test/fixtures/capture-fixture.js | 62 ++++
test/fixtures/internal-error.js | 18 ++
test/fixtures/internal-then.js | 17 +
test/fixtures/long-stack-traces.js | 16 +
test/fixtures/nested-errors.js | 49 +++
test/fixtures/produce-long-stack-traces.js | 50 +++
test/long-stack-traces.js | 134 ++++++++
test/test.js | 493 +++++++++++++++++++++++++++++
16 files changed, 1349 insertions(+)
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..8f9d77e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+indent_style = tab
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[{package.json,*.yml}]
+indent_style = space
+indent_size = 2
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..176a458
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1fd04da
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+coverage
+.nyc_output
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..214e2e0
--- /dev/null
+++ b/index.js
@@ -0,0 +1,289 @@
+module.exports = StackUtils;
+
+function StackUtils(opts) {
+ if (!(this instanceof StackUtils)) {
+ throw new Error('StackUtils constructor must be called with new');
+ }
+ opts = opts || {};
+ this._cwd = (opts.cwd || process.cwd()).replace(/\\/g, '/');
+ this._internals = opts.internals || [];
+ this._wrapCallSite = opts.wrapCallSite || false;
+}
+
+module.exports.nodeInternals = nodeInternals;
+
+function nodeInternals() {
+ if (!module.exports.natives) {
+ module.exports.natives = Object.keys(process.binding('natives'));
+ module.exports.natives.push('bootstrap_node', 'node');
+ }
+
+ return module.exports.natives.map(function (n) {
+ return new RegExp('\\(' + n + '\\.js:\\d+:\\d+\\)$');
+ }).concat([
+ /\s*at (bootstrap_)?node\.js:\d+:\d+?$/,
+ /\/\.node-spawn-wrap-\w+-\w+\/node:\d+:\d+\)?$/
+ ]);
+}
+
+StackUtils.prototype.clean = function (stack) {
+ if (!Array.isArray(stack)) {
+ stack = stack.split('\n');
+ }
+
+ if (!(/^\s*at /.test(stack[0])) &&
+ (/^\s*at /.test(stack[1]))) {
+ stack = stack.slice(1);
+ }
+
+ var outdent = false;
+ var lastNonAtLine = null;
+ var result = [];
+
+ stack.forEach(function (st) {
+ st = st.replace(/\\/g, '/');
+ var isInternal = this._internals.some(function (internal) {
+ return internal.test(st);
+ });
+
+ if (isInternal) {
+ return null;
+ }
+
+ var isAtLine = /^\s*at /.test(st);
+
+ if (outdent) {
+ st = st.replace(/\s+$/, '').replace(/^(\s+)at /, '$1');
+ } else {
+ st = st.trim();
+ if (isAtLine) {
+ st = st.substring(3);
+ }
+ }
+
+ st = st.replace(this._cwd + '/', '');
+
+ if (st) {
+ if (isAtLine) {
+ if (lastNonAtLine) {
+ result.push(lastNonAtLine);
+ lastNonAtLine = null;
+ }
+ result.push(st);
+ } else {
+ outdent = true;
+ lastNonAtLine = st;
+ }
+ }
+ }, this);
+
+ stack = result.join('\n').trim();
+
+ if (stack) {
+ return stack + '\n';
+ }
+ return '';
+};
+
+StackUtils.prototype.captureString = function (limit, fn) {
+ if (typeof limit === 'function') {
+ fn = limit;
+ limit = Infinity;
+ }
+ if (!fn) {
+ fn = this.captureString;
+ }
+
+ var limitBefore = Error.stackTraceLimit;
+ if (limit) {
+ Error.stackTraceLimit = limit;
+ }
+
+ var obj = {};
+
+ Error.captureStackTrace(obj, fn);
+ var stack = obj.stack;
+ Error.stackTraceLimit = limitBefore;
+
+ return this.clean(stack);
+};
+
+StackUtils.prototype.capture = function (limit, fn) {
+ if (typeof limit === 'function') {
+ fn = limit;
+ limit = Infinity;
+ }
+ if (!fn) {
+ fn = this.capture;
+ }
+ var prepBefore = Error.prepareStackTrace;
+ var limitBefore = Error.stackTraceLimit;
+ var wrapCallSite = this._wrapCallSite;
+
+ Error.prepareStackTrace = function (obj, site) {
+ if (wrapCallSite) {
+ return site.map(wrapCallSite);
+ }
+ return site;
+ };
+
+ if (limit) {
+ Error.stackTraceLimit = limit;
+ }
+
+ var obj = {};
+ Error.captureStackTrace(obj, fn);
+ var stack = obj.stack;
+ Error.prepareStackTrace = prepBefore;
+ Error.stackTraceLimit = limitBefore;
+
+ return stack;
+};
+
+StackUtils.prototype.at = function at(fn) {
+ if (!fn) {
+ fn = at;
+ }
+
+ var site = this.capture(1, fn)[0];
+
+ if (!site) {
+ return {};
+ }
+
+ var res = {
+ line: site.getLineNumber(),
+ column: site.getColumnNumber()
+ };
+
+ this._setFile(res, site.getFileName());
+
+ if (site.isConstructor()) {
+ res.constructor = true;
+ }
+
+ if (site.isEval()) {
+ res.evalOrigin = site.getEvalOrigin();
+ }
+
+ if (site.isNative()) {
+ res.native = true;
+ }
+
+ var typename = null;
+ try {
+ typename = site.getTypeName();
+ } catch (er) {}
+
+ if (typename &&
+ typename !== 'Object' &&
+ typename !== '[object Object]') {
+ res.type = typename;
+ }
+
+ var fname = site.getFunctionName();
+ if (fname) {
+ res.function = fname;
+ }
+
+ var meth = site.getMethodName();
+ if (meth && fname !== meth) {
+ res.method = meth;
+ }
+
+ return res;
+};
+
+StackUtils.prototype._setFile = function (result, filename) {
+ if (filename) {
+ filename = filename.replace(/\\/g, '/');
+ if ((filename.indexOf(this._cwd + '/') === 0)) {
+ filename = filename.substr(this._cwd.length + 1);
+ }
+ result.file = filename;
+ }
+};
+
+var re = new RegExp(
+ '^' +
+ // Sometimes we strip out the ' at' because it's noisy
+ '(?:\\s*at )?' +
+ // $1 = ctor if 'new'
+ '(?:(new) )?' +
+ // Object.method [as foo] (, maybe
+ // $2 = function name
+ // $3 = method name
+ '(?:([^\\(\\[]*)(?: \\[as ([^\\]]+)\\])? \\()?' +
+ // (eval at <anonymous> (file.js:1:1),
+ // $4 = eval origin
+ // $5:$6:$7 are eval file/line/col, but not normally reported
+ '(?:eval at ([^ ]+) \\((.+?):(\\d+):(\\d+)\\), )?' +
+ // file:line:col
+ // $8:$9:$10
+ // $11 = 'native' if native
+ '(?:(.+?):(\\d+):(\\d+)|(native))' +
+ // maybe close the paren, then end
+ '\\)?$'
+);
+
+StackUtils.prototype.parseLine = function parseLine(line) {
+ var match = line && line.match(re);
+ if (!match) {
+ return null;
+ }
+
+ var ctor = match[1] === 'new';
+ var fname = match[2];
+ var meth = match[3];
+ var evalOrigin = match[4];
+ var evalFile = match[5];
+ var evalLine = Number(match[6]);
+ var evalCol = Number(match[7]);
+ var file = match[8];
+ var lnum = match[9];
+ var col = match[10];
+ var native = match[11] === 'native';
+
+ var res = {};
+
+ if (lnum) {
+ res.line = Number(lnum);
+ }
+
+ if (col) {
+ res.column = Number(col);
+ }
+
+ this._setFile(res, file);
+
+ if (ctor) {
+ res.constructor = true;
+ }
+
+ if (evalOrigin) {
+ res.evalOrigin = evalOrigin;
+ res.evalLine = evalLine;
+ res.evalColumn = evalCol;
+ res.evalFile = evalFile && evalFile.replace(/\\/g, '/');
+ }
+
+ if (native) {
+ res.native = true;
+ }
+
+ if (fname) {
+ res.function = fname;
+ }
+
+ if (meth && fname !== meth) {
+ res.method = meth;
+ }
+
+ return res;
+};
+
+var bound = new StackUtils();
+
+Object.keys(StackUtils.prototype).forEach(function (key) {
+ StackUtils[key] = bound[key].bind(bound);
+});
diff --git a/license b/license
new file mode 100644
index 0000000..8dc0833
--- /dev/null
+++ b/license
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+
+Copyright (c) Isaac Z. Schlueter <i at izs.me>, James Talmage <james at talmage.io> (github.com/jamestalmage), and Contributors
+
+Extracted from code in node-tap http://www.node-tap.org/
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b1c5b76
--- /dev/null
+++ b/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "stack-utils",
+ "version": "1.0.0",
+ "description": "Captures and cleans stack traces",
+ "license": "MIT",
+ "repository": "tapjs/stack-utils",
+ "author": {
+ "name": "James Talmage",
+ "email": "james at talmage.io",
+ "url": "github.com/jamestalmage"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "scripts": {
+ "test": "tap test/*.js --cov"
+ },
+ "files": [
+ "index.js"
+ ],
+ "keywords": [
+ ""
+ ],
+ "dependencies": {},
+ "devDependencies": {
+ "bluebird": "^3.1.1",
+ "coveralls": "^2.11.6",
+ "flatten": "0.0.1",
+ "nested-error-stacks": "^2.0.0",
+ "pify": "^2.3.0",
+ "q": "^1.4.1",
+ "tap": "^10.0.0"
+ }
+}
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..2223735
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,132 @@
+# stack-utils
+
+> Captures and cleans stack traces.
+
+[![Linux Build](https://travis-ci.org/tapjs/stack-utils.svg?branch=master)](https://travis-ci.org/tapjs/stack-utils) [![Build status](https://ci.appveyor.com/api/projects/status/fb9i157knoixe3iq/branch/master?svg=true)](https://ci.appveyor.com/project/jamestalmage/stack-utils-oiw96/branch/master) [![Coverage](https://coveralls.io/repos/tapjs/stack-utils/badge.svg?branch=master&service=github)](https://coveralls.io/github/tapjs/stack-utils?branch=master)
+
+
+Extracted from `lib/stack.js` in the [`node-tap` project](https://github.com/tapjs/node-tap)
+
+## Install
+
+```
+$ npm install --save stack-utils
+```
+
+
+## Usage
+
+```js
+const StackUtils = require('stack-utils');
+const stack = new StackUtils({cwd: process.cwd(), internals: StackUtils.nodeInternals()});
+
+console.log(stack.clean(new Error().stack));
+// outputs a beautified stack trace
+```
+
+
+## API
+
+
+### new StackUtils([options])
+
+Creates a new `stackUtils` instance.
+
+#### options
+
+##### internals
+
+Type: `array` of `RegularExpression`s
+
+A set of regular expressions that match internal stack stack trace lines which should be culled from the stack trace.
+`StackUtils.nodeInternals()` returns a relatively set of sensible defaults for use on the node platform.
+
+##### cwd
+
+Type: `string`
+
+The path to the current working directory. File names in the stack trace will be shown relative to this directory.
+
+##### wrapCallSite
+
+Type: `function(CallSite)`
+
+A mapping function for manipulating CallSites before processing. The first argument is a CallSite instance, and the function should return a modified CallSite. This is useful for providing source map support.
+
+
+### StackUtils.nodeInternals()
+
+Returns an array of regular expressions that be used to cull lines from the stack trace that reference common Node.js internal files.
+
+
+### stackUtils.clean(stack)
+
+Cleans up a stack trace by deleting any lines that match the `internals` passed to the constructor, and shortening file names relative to `cwd`.
+
+Returns a `string` with the cleaned up stack (always terminated with a `\n` newline character).
+
+#### stack
+
+*Required*
+Type: `string` or an `array` of `string`s
+
+
+### stackUtils.capture([limit], [startStackFunction])
+
+Captures the current stack trace, returning an array of `CallSite`s. There are good overviews of the available CallSite methods [here](https://github.com/v8/v8/wiki/Stack%20Trace%20API#customizing-stack-traces), and [here](https://github.com/sindresorhus/callsites#api).
+
+#### limit
+
+Type: `number`
+Default: `Infinity`
+
+Limits the number of lines returned by dropping all lines in excess of the limit. This removes lines from the stack trace.
+
+#### startStackFunction
+
+Type: `function`
+
+The function where the stack trace should start. The first line of the stack trace will be the function that called `startStackFunction`. This removes lines from the end of the stack trace.
+
+
+### stackUtils.captureString([limit], [startStackFunction])
+
+Captures the current stack trace, cleans it using `stackUtils.clean(stack)`, and returns a string with the cleaned stack trace. It takes the same arguments as `stackUtils.capture`.
+
+
+### stackUtils.at([startStackFunction])
+
+Captures the first line of the stack trace (or the first line after `startStackFunction` if supplied), and returns a `CallSite` like object that is serialization friendly (properties are actual values instead of getter functions).
+
+The available properties are:
+
+ - `line`: `number`
+ - `column`: `number`
+ - `file`: `string`
+ - `constructor`: `boolean`
+ - `evalOrigin`: `string`
+ - `native`: `boolean`
+ - `typename`: `string`
+ - `function`: `string`
+ - `method`: `string`
+
+### stackUtils.parseLine(line)
+
+Parses a `string` (which should be a single line from a stack trace), and generates an object with the following properties:
+
+ - `line`: `number`
+ - `column`: `number`
+ - `file`: `string`
+ - `constructor`: `boolean`
+ - `evalOrigin`: `string`
+ - `evalLine`: `number`
+ - `evalColumn`: `number`
+ - `evalFile`: `string`
+ - `native`: `boolean`
+ - `function`: `string`
+ - `method`: `string`
+
+
+## License
+
+MIT © [Isaac Z. Schlueter](http://github.com/isaacs), [James Talmage](http://github.com/jamestalmage)
diff --git a/test/_utils.js b/test/_utils.js
new file mode 100644
index 0000000..b80c7e7
--- /dev/null
+++ b/test/_utils.js
@@ -0,0 +1,13 @@
+var flatten = require('flatten');
+var path = require('path');
+
+module.exports.join = join;
+module.exports.fixtureDir = path.join(__dirname, 'fixtures');
+
+function join() {
+ var args = Array.prototype.slice.call(arguments);
+ return flatten(args).join('\n') + '\n';
+}
+
+if (module === require.main)
+ require('tap').pass('this is fine')
diff --git a/test/fixtures/capture-fixture.js b/test/fixtures/capture-fixture.js
new file mode 100644
index 0000000..0fe8c44
--- /dev/null
+++ b/test/fixtures/capture-fixture.js
@@ -0,0 +1,62 @@
+'use strict';
+module.exports = CaptureFixture;
+
+function CaptureFixture(stack) {
+ this.stack = stack;
+}
+
+CaptureFixture.prototype.redirect1 = function () {
+ var args = Array.prototype.slice.call(arguments);
+ var method = args.shift();
+ return this[method].apply(this, args);
+};
+
+CaptureFixture.prototype.redirect2 = function () {
+ var args = Array.prototype.slice.call(arguments);
+ var method = args.shift();
+ return this[method].apply(this, args);
+};
+
+CaptureFixture.prototype.call = function () {
+ var args = Array.prototype.slice.call(arguments);
+ var method = args.shift();
+ return this.stack[method].apply(this.stack, args);
+};
+
+CaptureFixture.prototype.const = function () {
+ var args = Array.prototype.slice.call(arguments);
+ var method = args.shift();
+ var self = this;
+
+ function Constructor() {
+ this.val = self[method].apply(self, args);
+ }
+
+ return new Constructor().val;
+};
+
+CaptureFixture.prototype.obj = function () {
+ var args = Array.prototype.slice.call(arguments);
+ var methodName = args.shift();
+ var method = args.shift();
+ var self = this;
+
+ var obj = {};
+ obj[methodName] = function () {
+ return self[method].apply(self, args);
+ };
+
+ return obj[methodName]();
+};
+
+CaptureFixture.prototype.eval = function () {
+ var args = Array.prototype.slice.call(arguments);
+ var method = args.shift();
+ var self = this;
+
+ return eval('self[method].apply(self, args)');
+};
+
+CaptureFixture.prototype.error = function (message) {
+ return new Error(message);
+};
diff --git a/test/fixtures/internal-error.js b/test/fixtures/internal-error.js
new file mode 100644
index 0000000..d524c2f
--- /dev/null
+++ b/test/fixtures/internal-error.js
@@ -0,0 +1,18 @@
+'use strict';
+var NestedError = require('nested-error-stacks');
+var util = require('util');
+
+function InternalError(message, nested) {
+ NestedError.call(this, message, nested);
+}
+
+util.inherits(InternalError, NestedError);
+InternalError.prototype.name = 'InternalError';
+
+module.exports = function (cb, err) {
+ setTimeout(bound.bind(null, cb, err), 0);
+};
+
+function bound(cb, err) {
+ cb(new InternalError('internal' + (err ? ': ' + err.message : ''), err));
+}
diff --git a/test/fixtures/internal-then.js b/test/fixtures/internal-then.js
new file mode 100644
index 0000000..6b20a49
--- /dev/null
+++ b/test/fixtures/internal-then.js
@@ -0,0 +1,17 @@
+
+//var p = global.InternalPromise.resolve().then(function () {});
+
+module.exports = function internalLibraryOuterFn(then) {
+ return global.InternalPromise.resolve().then(function internalLibraryInnerFn() {
+ return global.InternalPromise.resolve().then(then);
+ });
+};
+
+module.exports.reject = function internalLibraryOuterReject() {
+ return global.InternalPromise.resolve().then(function internalLibraryInnerReject() {
+ return global.InternalPromise.reject(new Error('inner')).catch(function (e) {
+ return e.stack;
+ });
+ });
+};
+
diff --git a/test/fixtures/long-stack-traces.js b/test/fixtures/long-stack-traces.js
new file mode 100644
index 0000000..a79cbbc
--- /dev/null
+++ b/test/fixtures/long-stack-traces.js
@@ -0,0 +1,16 @@
+'use strict';
+
+var Q = require('q');
+Q.longStackSupport = true;
+global.InternalPromise = Q;
+module.exports.q = require('./produce-long-stack-traces');
+
+var longStackTracePath = require.resolve('./produce-long-stack-traces');
+var internalThen = require.resolve('./internal-then');
+delete require.cache[longStackTracePath];
+delete require.cache[internalThen];
+
+var bluebird = require('bluebird');
+bluebird.config({longStackTraces: true});
+global.InternalPromise = bluebird;
+module.exports.bluebird = require('./produce-long-stack-traces');
diff --git a/test/fixtures/nested-errors.js b/test/fixtures/nested-errors.js
new file mode 100644
index 0000000..b43a37e
--- /dev/null
+++ b/test/fixtures/nested-errors.js
@@ -0,0 +1,49 @@
+'use strict';
+
+var NestedError = require('nested-error-stacks');
+var util = require('util');
+var internal = require('./internal-error');
+
+function foo(cb) {
+ bar(function nested(err) {
+ cb(new FooError('foo' + err.message, err));
+ });
+}
+
+function bar(cb) {
+ internal(function moreNested(err) {
+ cb(new BarError('bar: ' + err.message, err));
+ });
+}
+
+function FooError(message, nested) {
+ NestedError.call(this, message, nested);
+}
+
+util.inherits(FooError, NestedError);
+FooError.prototype.name = 'FooError';
+
+function BarError(message, nested) {
+ NestedError.call(this, message, nested);
+}
+
+util.inherits(BarError, NestedError);
+BarError.prototype.name = 'BarError';
+
+module.exports.top = function(cb) {
+ internal(function (err) {
+ cb(err.stack);
+ }, new Error('baz'));
+};
+
+module.exports.middle = function (cb) {
+ internal(function (err) {
+ cb(new FooError('foo', err).stack);
+ }, new Error('bar'));
+};
+
+module.exports.bottom = function (cb) {
+ foo(function (err){
+ cb(err.stack);
+ });
+};
diff --git a/test/fixtures/produce-long-stack-traces.js b/test/fixtures/produce-long-stack-traces.js
new file mode 100644
index 0000000..32ae07e
--- /dev/null
+++ b/test/fixtures/produce-long-stack-traces.js
@@ -0,0 +1,50 @@
+'use strict';
+
+var Promise = global.InternalPromise;
+var internalThen = require('./internal-then');
+
+module.exports = Promise.resolve().then(function outer() {
+ return Promise.resolve().then(function inner() {
+ return Promise.resolve().then(function evenMoreInner() {
+ return Promise.resolve().then(function mostInner() {
+ a.b.c.d()
+ }).catch(function catcher(e) {
+ return e.stack;
+ });
+ });
+ });
+});
+
+module.exports.middle = Promise.resolve().then(function outer() {
+ return Promise.resolve().then(function inner() {
+ return internalThen(function evenMoreInner() {
+ return Promise.resolve().then(function mostInner() {
+ a.b.c.d()
+ }).catch(function catcher(e) {
+ return e.stack;
+ });
+ });
+ });
+});
+
+module.exports.top = Promise.resolve().then(function outer() {
+ return Promise.resolve().then(function inner() {
+ return Promise.resolve().then(function evenMoreInner() {
+ return Promise.resolve().then(internalThen.reject);
+ });
+ });
+});
+
+module.exports.bottom = new Promise(function (resolve) {
+ setTimeout(internalThen.bind(null, function outer() {
+ return Promise.resolve().then(function inner() {
+ return Promise.resolve().then(function evenMoreInner() {
+ return Promise.resolve().then(function mostInner() {
+ a.b.c.d()
+ }).catch(function catcher(e) {
+ resolve(e.stack);
+ });
+ });
+ });
+ }),0);
+});
diff --git a/test/long-stack-traces.js b/test/long-stack-traces.js
new file mode 100644
index 0000000..c77d6da
--- /dev/null
+++ b/test/long-stack-traces.js
@@ -0,0 +1,134 @@
+const t = require('tap');
+const StackUtils = require('../');
+const longStackTraces = require('./fixtures/long-stack-traces');
+const pify = require('pify');
+const nestedErrors = pify(require('./fixtures/nested-errors'), Promise);
+
+const {join, fixtureDir} = require('./_utils');
+
+function internals() {
+ return StackUtils.nodeInternals().concat([
+ /\/long-stack-traces\.js:\d+:\d+\)?$/,
+ /\/internal-error\.js:\d+:\d+\)?$/,
+ /\/internal-then\.js:\d+:\d+\)?$/,
+ /\/node_modules\//
+ ]);
+}
+
+const stackUtils = new StackUtils({internals: internals(), cwd: fixtureDir});
+
+t.test('indents lines after first "From previous event:"', async t => {
+ const cleanedStack = stackUtils.clean(await longStackTraces.bluebird);
+ const expected = join([
+ 'mostInner (produce-long-stack-traces.js:10:5)',
+ 'From previous event:',
+ ' evenMoreInner (produce-long-stack-traces.js:9:29)',
+ 'From previous event:',
+ ' inner (produce-long-stack-traces.js:8:28)',
+ 'From previous event:',
+ ' outer (produce-long-stack-traces.js:7:27)',
+ 'From previous event:',
+ ' Object.<anonymous> (produce-long-stack-traces.js:6:36)'
+ ]);
+
+ t.is(cleanedStack, expected);
+});
+
+t.test('removes empty "From previous event:" sections from the bottom', async t => {
+ const stack = await longStackTraces.bluebird.bottom;
+ const cleanedStack = stackUtils.clean(stack);
+
+ const expected = join([
+ 'mostInner (produce-long-stack-traces.js:43:6)',
+ 'From previous event:',
+ ' evenMoreInner (produce-long-stack-traces.js:42:30)',
+ 'From previous event:',
+ ' inner (produce-long-stack-traces.js:41:29)',
+ 'From previous event:',
+ ' outer (produce-long-stack-traces.js:40:28)'
+ ]);
+
+ t.is(cleanedStack, expected);
+});
+
+t.test('removes empty "From previous event:" sections from the top', async t => {
+ const stack = await longStackTraces.bluebird.top;
+ const cleanedStack = stackUtils.clean(stack);
+
+ const expected = join([
+ 'From previous event:',
+ ' evenMoreInner (produce-long-stack-traces.js:33:29)',
+ 'From previous event:',
+ ' inner (produce-long-stack-traces.js:32:28)',
+ 'From previous event:',
+ ' outer (produce-long-stack-traces.js:31:27)',
+ 'From previous event:',
+ ' Object.<anonymous> (produce-long-stack-traces.js:30:40)'
+ ]);
+
+ t.is(cleanedStack, expected);
+});
+
+t.test('removes empty "From previous event:" sections from the middle', async t => {
+ const stack = await longStackTraces.bluebird.middle;
+ const cleanedStack = stackUtils.clean(stack);
+
+ const expected = join([
+ 'mostInner (produce-long-stack-traces.js:22:5)',
+ 'From previous event:',
+ ' evenMoreInner (produce-long-stack-traces.js:21:29)',
+ 'From previous event:',
+ ' inner (produce-long-stack-traces.js:20:10)',
+ 'From previous event:',
+ ' outer (produce-long-stack-traces.js:19:27)',
+ 'From previous event:',
+ ' Object.<anonymous> (produce-long-stack-traces.js:18:43)'
+ ]);
+
+ t.match(cleanedStack, expected);
+});
+
+t.test('removes empty "Caused by:" sections from the top', t => {
+ nestedErrors.top(stack => {
+ const cleanedStack = stackUtils.clean(stack);
+
+ const expected = join([
+ 'Caused By: Error: baz',
+ ' Object.module.exports.top (nested-errors.js:36:5)'
+ ]);
+
+ t.match(cleanedStack, expected);
+ t.end();
+ });
+});
+
+t.test('removes empty "Caused by:" sections from the bottom', t => {
+ nestedErrors.bottom(stack => {
+ const cleanedStack = stackUtils.clean(stack);
+
+ const expected = join([
+ 'nested (nested-errors.js:9:6)',
+ 'moreNested (nested-errors.js:15:3)',
+ 'Caused By: BarError: bar: internal',
+ ' moreNested (nested-errors.js:15:6)'
+ ]);
+
+ t.is(cleanedStack, expected);
+ t.end();
+ });
+});
+
+t.test('removes empty "Caused by:" sections from the middle', t => {
+ nestedErrors.middle(stack => {
+ const cleanedStack = stackUtils.clean(stack);
+
+ const expected = join([
+ 'nested-errors.js:41:6',
+ 'Caused By: Error: bar',
+ ' Object.module.exports.middle (nested-errors.js:42:5)'
+ ]);
+
+ t.match(cleanedStack, expected);
+ t.end();
+ });
+});
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..6bb8634
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,493 @@
+const path = require('path');
+const t = require('tap');
+const StackUtils = require('../');
+const CaptureFixture = require('./fixtures/capture-fixture');
+const {join, fixtureDir} = require('./_utils');
+
+// Use a fixed known set of native modules, since this changes
+// depending on the version of Node we're testing with.
+StackUtils.natives = [
+ 'internal/bootstrap_node',
+ '_debug_agent',
+ '_debugger',
+ 'assert',
+ 'buffer',
+ 'child_process',
+ 'console',
+ 'constants',
+ 'crypto',
+ 'cluster',
+ 'dgram',
+ 'dns',
+ 'domain',
+ 'events',
+ 'fs',
+ 'http',
+ '_http_agent',
+ '_http_client',
+ '_http_common',
+ '_http_incoming',
+ '_http_outgoing',
+ '_http_server',
+ 'https',
+ '_linklist',
+ 'module',
+ 'net',
+ 'os',
+ 'path',
+ 'process',
+ 'punycode',
+ 'querystring',
+ 'readline',
+ 'repl',
+ 'stream',
+ '_stream_readable',
+ '_stream_writable',
+ '_stream_duplex',
+ '_stream_transform',
+ '_stream_passthrough',
+ '_stream_wrap',
+ 'string_decoder',
+ 'sys',
+ 'timers',
+ 'tls',
+ '_tls_common',
+ '_tls_legacy',
+ '_tls_wrap',
+ 'tty',
+ 'url',
+ 'util',
+ 'v8',
+ 'vm',
+ 'zlib',
+ 'internal/buffer',
+ 'internal/child_process',
+ 'internal/cluster',
+ 'internal/freelist',
+ 'internal/fs',
+ 'internal/linkedlist',
+ 'internal/net',
+ 'internal/module',
+ 'internal/process/next_tick',
+ 'internal/process/promises',
+ 'internal/process/stdio',
+ 'internal/process/warning',
+ 'internal/process',
+ 'internal/readline',
+ 'internal/repl',
+ 'internal/socket_list',
+ 'internal/url',
+ 'internal/util',
+ 'internal/v8_prof_polyfill',
+ 'internal/v8_prof_processor',
+ 'internal/streams/lazy_transform',
+ 'internal/streams/BufferList',
+ 'v8/tools/splaytree',
+ 'v8/tools/codemap',
+ 'v8/tools/consarray',
+ 'v8/tools/csvparser',
+ 'v8/tools/profile',
+ 'v8/tools/profile_view',
+ 'v8/tools/logreader',
+ 'v8/tools/tickprocessor',
+ 'v8/tools/SourceMap',
+ 'v8/tools/tickprocessor-driver',
+ 'bootstrap_node',
+ 'node'
+];
+
+const LinuxStack1 = join(linuxStack1(), internalStack());
+const WindowsStack1 = join(windowsStack1(), internalStack());
+
+const version = process.version.slice(1).split('.').map(function (val) {
+ return parseInt(val, 10);
+});
+
+t.test('must be called with new', t => {
+ t.is(typeof StackUtils, 'function');
+ const stackUtils = StackUtils;
+ t.throws(() => stackUtils());
+ t.end()
+});
+
+t.test('clean: truncates cwd', t => {
+ const expected = join([
+ 'foo (foo.js:3:8)',
+ 'bar (foo.js:7:2)',
+ 'bar (bar.js:4:2)',
+ 'Object.<anonymous> (bar.js:7:1)',
+ 'ontimeout (timers.js:365:14)',
+ 'tryOnTimeout (timers.js:237:5)',
+ 'Timer.listOnTimeout (timers.js:207:5)',
+ '_combinedTickCallback (internal/process/next_tick.js:67:7)',
+ 'process._tickCallback (internal/process/next_tick.js:98:9)',
+ 'Module.runMain (module.js:645:11)',
+ 'Module._compile (module.js:398:26)',
+ 'Object.Module._extensions..js (module.js:405:10)',
+ 'Module.load (module.js:344:32)',
+ 'Function.Module._load (module.js:301:12)',
+ 'Function.Module.runMain (module.js:430:10)',
+ 'run (bootstrap_node.js:420:7)',
+ 'startup (bootstrap_node.js:139:9)',
+ 'bootstrap_node.js:535:3',
+ 'startup (node.js:141:18)'
+ ]);
+
+ let stack = new StackUtils({cwd: '/user/dev/project'});
+ t.is(stack.clean(LinuxStack1), expected, 'accepts a linux string');
+ t.is(stack.clean(LinuxStack1.split('\n')), expected, 'accepts an array');
+ t.is(stack.clean(LinuxStack1.split('\n').slice(1)), expected, 'slices off the message line');
+
+ stack = new StackUtils({cwd: 'Z:\\user\\dev\\project'});
+ t.is(stack.clean(WindowsStack1), expected, 'accepts a windows string');
+ t.end()
+});
+
+t.test('clean: eliminates internals', t => {
+ let stack = new StackUtils({cwd: '/user/dev', internals: StackUtils.nodeInternals()});
+ var expected = join([
+ 'foo (project/foo.js:3:8)',
+ 'bar (project/foo.js:7:2)',
+ 'bar (project/bar.js:4:2)',
+ 'Object.<anonymous> (project/bar.js:7:1)'
+ ]);
+ t.is(stack.clean(LinuxStack1), expected);
+
+ stack = new StackUtils({cwd: 'Z:\\user\\dev', internals: StackUtils.nodeInternals()});
+ t.is(stack.clean(WindowsStack1), expected);
+ t.end()
+});
+
+t.test('clean: returns null if it is all internals', t => {
+ const stack = new StackUtils({internals: StackUtils.nodeInternals()});
+ t.is(stack.clean(join(internalStack())), '');
+ t.end()
+});
+
+t.test('captureString: two redirects', t => {
+ const stack = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stack);
+
+ const capturedString = capture.redirect1('redirect2', 'call', 'captureString');
+ t.is(capturedString, join([
+ 'CaptureFixture.call (capture-fixture.js:23:28)',
+ 'CaptureFixture.redirect2 (capture-fixture.js:17:22)',
+ 'CaptureFixture.redirect1 (capture-fixture.js:11:22)'
+ ]));
+ t.end()
+});
+
+t.test('captureString: with startStack function', t => {
+ const stack = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stack);
+
+ const capturedString = capture.redirect1('redirect2', 'call', 'captureString', capture.call);
+ t.is(capturedString, join([
+ 'CaptureFixture.redirect2 (capture-fixture.js:17:22)',
+ 'CaptureFixture.redirect1 (capture-fixture.js:11:22)'
+ ]));
+ t.end()
+});
+
+t.test('captureString: with limit', t => {
+ const stack = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stack);
+
+ const capturedString = capture.redirect1('redirect2', 'call', 'captureString', 1);
+ t.is(capturedString, join([
+ 'CaptureFixture.call (capture-fixture.js:23:28)'
+ ]));
+ t.end()
+});
+
+t.test('captureString: with limit and startStack', t => {
+ const stack = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stack);
+
+ const capturedString = capture.redirect1('redirect2', 'call', 'captureString', 1, capture.call);
+ t.is(capturedString, join([
+ 'CaptureFixture.redirect2 (capture-fixture.js:17:22)'
+ ]));
+ t.end()
+});
+
+t.test('capture returns an array of call sites', t => {
+ const stackUtil = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stackUtil);
+ const stack = capture.redirect1('call', 'capture').slice(0, 2);
+ t.is(stack[0].getFileName(), path.join(fixtureDir, 'capture-fixture.js'));
+ t.is(stack[0].getFunctionName(), 'CaptureFixture.call');
+ t.is(stack[1].getFunctionName(), 'CaptureFixture.redirect1');
+ t.end()
+});
+
+t.test('capture: with limit', t => {
+ const stackUtil = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stackUtil);
+ const stack = capture.redirect1('redirect2', 'call', 'capture', 1);
+ t.is(stack.length, 1);
+ t.is(stack[0].getFunctionName(), 'CaptureFixture.call');
+ t.end()
+});
+
+t.test('capture: with stackStart function', t => {
+ const stackUtil = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stackUtil);
+ const stack = capture.redirect1('redirect2', 'call', 'capture', capture.call);
+ t.true(stack.length > 1);
+ t.is(stack[0].getFunctionName(), 'CaptureFixture.redirect2');
+ t.end()
+});
+
+t.test('capture: with limit and stackStart function', t => {
+ const stackUtil = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stackUtil);
+ const stack = capture.redirect1('redirect2', 'call', 'capture', 1, capture.call);
+ t.is(stack.length, 1);
+ t.is(stack[0].getFunctionName(), 'CaptureFixture.redirect2');
+ t.end()
+});
+
+t.test('capture: with wrapCallSite function', t => {
+ const wrapper = function (callsite) {
+ return {
+ getMethodName: function () {
+ return callsite.getMethodName();
+ },
+ getFunctionName: function () {
+ return 'testOverrideFunctionName';
+ }
+ };
+ };
+ const stackUtil = new StackUtils({internals: internals(), cwd: fixtureDir, wrapCallSite: wrapper});
+ const capture = new CaptureFixture(stackUtil);
+ const stack = capture.redirect1('redirect2', 'call', 'capture', 1, capture.call);
+ t.is(stack.length, 1);
+ t.is(stack[0].getFunctionName(), 'testOverrideFunctionName');
+ t.is(stack[0].getMethodName(), 'redirect2');
+ t.end()
+});
+
+t.test('at', t => {
+ const stackUtil = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stackUtil);
+ const at = capture.redirect1('call', 'at');
+
+ t.same(at, {
+ file: 'capture-fixture.js',
+ line: 23,
+ column: 28,
+ type: 'CaptureFixture',
+ function: 'CaptureFixture.call',
+ method: 'call'
+ });
+ t.end()
+});
+
+t.test('at: with stackStart', t => {
+ const stackUtil = new StackUtils({internals: internals(), cwd: __dirname});
+ const capture = new CaptureFixture(stackUtil);
+
+ const at = capture.redirect1('call', 'at', capture.call);
+
+ t.same(at, {
+ file: `fixtures/capture-fixture.js`,
+ line: 11,
+ column: 22,
+ type: 'CaptureFixture',
+ function: 'CaptureFixture.redirect1',
+ method: 'redirect1'
+ });
+ t.end()
+});
+
+t.test('at: inside a constructor call', t => {
+ const stackUtil = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stackUtil);
+
+ const at = capture.const('call', 'at', capture.call);
+
+ // TODO: File an issue - if this assert fails, the power assert diagram renderer blows up.
+ t.same(at, {
+ file: 'capture-fixture.js',
+ line: 32,
+ column: 27,
+ constructor: true,
+ type: 'Constructor',
+ function: 'Constructor'
+ });
+ t.end()
+});
+
+t.test('at: method on an [Object] instance', t => {
+ const stackUtil = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stackUtil);
+
+ const at = capture.const('obj', 'foo', 'call', 'at', capture.call);
+
+ t.same(at, {
+ file: 'capture-fixture.js',
+ line: 46,
+ column: 23,
+ function: 'obj.(anonymous function)',
+ method: 'foo'
+ });
+ t.end()
+});
+
+t.test('at: returns empty object if #capture() returns an empty stack', t => {
+ const stackUtil = new StackUtils();
+ stackUtil.capture = function () {
+ return [];
+ };
+ t.same(stackUtil.at(), {});
+ t.end()
+});
+
+t.test('at: eval', t => {
+ const stackUtil = new StackUtils({internals: internals(), cwd: fixtureDir});
+ const capture = new CaptureFixture(stackUtil);
+
+ const at = capture.eval('call', 'at', capture.call);
+ const expected = {
+ line: 1,
+ column: 14,
+ evalOrigin: /eval at (<anonymous>|CaptureFixture.eval) \(.*capture-fixture.js:57:9\)/,
+ function: 'eval'
+ };
+
+ // TODO: There are some inconsistencies between this and how `parseLine` works.
+ if (version[0] < 4) {
+ expected.type = 'CaptureFixture';
+ expected.function = 'eval';
+ }
+
+ t.match(at, expected);
+ t.end()
+});
+
+t.test('parseLine', t => {
+ const stack = new StackUtils({internals: internals(), cwd: '/user/dev/project'});
+ const capture = new CaptureFixture(stack);
+
+ t.same(stack.parseLine('foo'), null, 'should not match');
+
+ t.same(stack.parseLine(' at bar (/user/dev/project/foo.js:3:8)'), {
+ file: 'foo.js',
+ line: 3,
+ column: 8,
+ function: 'bar'
+ });
+
+ t.same(stack.parseLine(' at SomeClass.someFunc (/user/dev/project/foo.js:3:8)'), {
+ file: 'foo.js',
+ line: 3,
+ column: 8,
+ function: 'SomeClass.someFunc'
+ });
+
+ t.same(stack.parseLine(' at foo (/some/other/dir/file.js:3:8)'), {
+ file: '/some/other/dir/file.js',
+ line: 3,
+ column: 8,
+ function: 'foo'
+ });
+
+ // TODO: report issue - this also causes power-assert diagram renderer to fail
+ t.same(stack.parseLine(' at new Foo (/user/dev/project/foo.js:3:8)'), {
+ file: 'foo.js',
+ line: 3,
+ column: 8,
+ constructor: true,
+ function: 'Foo'
+ });
+
+ // EVAL
+ const evalStack = capture.eval('error', 'foo').stack.split('\n');
+
+ const expected = {
+ file: '<anonymous>',
+ line: 1,
+ column: 14,
+ evalOrigin: /CaptureFixture.eval|<anonymous>/,
+ evalLine: 57,
+ evalColumn: 9,
+ evalFile: path.join(fixtureDir, 'capture-fixture.js').replace(/\\/g, '/'),
+ function: 'eval'
+ };
+
+ if (version[0] < 4) {
+ expected.function = 'CaptureFixture.eval';
+ }
+
+ const actual = stack.parseLine(evalStack[2]);
+
+ t.match(actual, expected);
+ t.end()
+});
+
+t.test('parseLine: handles native errors', t => {
+ t.same(StackUtils.parseLine(' at Error (native)'), {
+ native: true,
+ function: 'Error'
+ });
+ t.end()
+});
+
+t.test('parseLine: handles parens', t => {
+ var line = ' at X.<anonymous> (/USER/Db (Person)/x/y.js:14:11)';
+ t.same(StackUtils.parseLine(line), {
+ line: 14,
+ column: 11,
+ file: '/USER/Db (Person)/x/y.js',
+ function: 'X.<anonymous>'
+ });
+ t.end()
+});
+
+function linuxStack1() {
+ return [
+ 'Error: foo',
+ ' at foo (/user/dev/project/foo.js:3:8)',
+ ' at bar (/user/dev/project/foo.js:7:2)',
+ ' at bar (/user/dev/project/bar.js:4:2)',
+ ' at Object.<anonymous> (/user/dev/project/bar.js:7:1)'
+ ];
+}
+
+function windowsStack1() {
+ return [
+ 'Error: foo',
+ ' at foo (Z:\\user\\dev\\project\\foo.js:3:8)',
+ ' at bar (Z:\\user\\dev\\project\\foo.js:7:2)',
+ ' at bar (Z:\\user\\dev\\project\\bar.js:4:2)',
+ ' at Object.<anonymous> (Z:\\user\\dev\\project\\bar.js:7:1)'
+ ];
+}
+
+function internalStack() {
+ return [
+ ' at ontimeout (timers.js:365:14)',
+ ' at tryOnTimeout (timers.js:237:5)',
+ ' at Timer.listOnTimeout (timers.js:207:5)',
+ ' at _combinedTickCallback (internal/process/next_tick.js:67:7)',
+ ' at process._tickCallback (internal/process/next_tick.js:98:9)',
+ ' at Module.runMain (module.js:645:11)',
+ ' at Module._compile (module.js:398:26)',
+ ' at Object.Module._extensions..js (module.js:405:10)',
+ ' at Module.load (module.js:344:32)',
+ ' at Function.Module._load (module.js:301:12)',
+ ' at Function.Module.runMain (module.js:430:10)',
+ ' at run (bootstrap_node.js:420:7)',
+ ' at startup (bootstrap_node.js:139:9)',
+ ' at bootstrap_node.js:535:3',
+ ' at startup (node.js:141:18)'
+ ];
+}
+
+function internals() {
+ return StackUtils.nodeInternals().concat([
+ /test\.js:\d+:\d+\)?$/,
+ /\/node_modules\//
+ ]);
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-stack-utils.git
More information about the Pkg-javascript-commits
mailing list