[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