added stacktrace.js to allow cross-browser stack trace capturing

This commit is contained in:
Spencer Alger 2014-03-13 10:19:56 -07:00
parent 6e96bd404c
commit 22a33d63b8
46 changed files with 8742 additions and 1 deletions

View file

@ -35,7 +35,8 @@
"jquery": "~2.1.0",
"moment": "~2.5.1",
"require-css": "~0.1.2",
"angular-bootstrap": "~0.10.0"
"angular-bootstrap": "~0.10.0",
"stacktrace.js": "https://github.com/stacktracejs/stacktrace.js.git#~0.6.0"
},
"devDependencies": {}
}

View file

@ -0,0 +1,22 @@
{
"name": "stacktrace.js",
"version": "0.6.0",
"main": "./stacktrace.js",
"dependencies": {},
"ignore": [
"**/.*",
"node_modules",
"components"
],
"homepage": "https://github.com/stacktracejs/stacktrace.js",
"_release": "0.6.0",
"_resolution": {
"type": "version",
"tag": "v0.6.0",
"commit": "02b5def17d157b5e56d2bb0e175e48c5c1272aaa"
},
"_source": "https://github.com/stacktracejs/stacktrace.js.git",
"_target": "~0.6.0",
"_originalSource": "https://github.com/stacktracejs/stacktrace.js.git",
"_direct": true
}

View file

@ -0,0 +1,16 @@
## v0.6.0
* Added AMD support using a UMD pattern (thanks @jeffrose)
## v0.5.3
* Fix Chrome 27 detection; Chrome no longer has Error#arguments
## v0.5.1
* Fix Bower integration; Added proper bower.json file
## v0.5.0
* Lots and lots of stuff

View file

@ -0,0 +1,16 @@
## Making contributions
When submitting your pull requests, please do the following to make it easier to incorporate your changes:
* Include unit and/or functional tests that validate changes you're making.
* Run unit tests in the latest IE, Firefox, Chrome, Safari and Opera and make sure they pass.
* Rebase your changes onto origin/HEAD if you can do so cleanly.
* If submitting additional functionality, provide an example of how to use it.
* Please keep code style consistent with surrounding code.
## Testing
There are a few ways to run tests:
* You can run tests in PhantomJS by simply running `gradlew test` from your favorite shell.
* Run tests with JSTestDriver using `gradlew jstd`
* Point any browser to `≤project dir>/test/TestStacktrace.html` for unit tests
* Point your browser to `≤project dir>/test/functional/index.html` for more real-world functional tests

View file

@ -0,0 +1,6 @@
Domain Public by Eric Wendelin http://eriwen.com/ (2008)
Luke Smith http://lucassmith.name/ (2008)
Loic Dachary <loic@dachary.org> (2008)
Johan Euphrosine <proppy@aminche.com> (2008)
Oyvind Sean Kinsey http://kinsey.no/blog (2010)
Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010)

View file

@ -0,0 +1,82 @@
# Welcome to stacktrace.js! [![Code Climate](https://codeclimate.com/github/eriwen/javascript-stacktrace.png)](https://codeclimate.com/github/eriwen/javascript-stacktrace)
A JavaScript tool that allows you to debug your JavaScript by giving you a [stack trace](http://en.wikipedia.org/wiki/Stack_trace) of function calls leading to an error (or any condition you specify)
# How do I use stacktrace.js? #
Just include stacktrace.js file on your page, and call it like so:
```html
<script type="text/javascript" src="https://rawgithub.com/stacktracejs/stacktrace.js/master/stacktrace.js"></script>
<script type="text/javascript">
// your code...
var trace = printStackTrace();
alert(trace.join('\n\n')); // Output however you want!
// more code of yours...
</script>
```
You can also pass in your own Error to get a stacktrace *not available in IE or Safari 5-*
```html
<script type="text/javascript" src="https://rawgithub.com/stacktracejs/stacktrace.js/master/stacktrace.js"></script>
<script type="text/javascript">
try {
// error producing code
} catch(e) {
var trace = printStackTrace({e: e});
alert('Error!\n' + 'Message: ' + e.message + '\nStack trace:\n' + trace.join('\n'));
// do something else with error
}
</script>
```
Note that error message is not included in stack trace.
Bookmarklet available on the [project home page](http://stacktracejs.com).
# Function Instrumentation #
You can now have any (public or privileged) function give you a stacktrace when it is called:
```javascript
function logStackTrace(stack) {
console.log(stack.join('\n'));
}
var p = new printStackTrace.implementation();
p.instrumentFunction(this, 'baz', logStackTrace);
function foo() {
var a = 1;
bar();
}
function bar() {
baz();
}
foo(); //Will log a stacktrace when 'baz()' is called containing 'foo()'!
p.deinstrumentFunction(this, 'baz'); //Remove function instrumentation
```
# What browsers does stacktrace.js support? #
It is currently tested and working on:
- Firefox (and Iceweasel) 0.9+
- Google Chrome 1+
- Safari 3.0+ (including iOS 1+)
- Opera 7+
- IE 5.5+
- Konqueror 3.5+
- Flock 1.0+
- SeaMonkey 1.0+
- K-Meleon 1.5.3+
- Epiphany 2.28.0+
- Iceape 1.1+
## Contributions [![Stories in Ready](http://badge.waffle.io/eriwen/javascript-stacktrace.png)](http://waffle.io/eriwen/javascript-stacktrace)
This project is made possible due to the efforts of these fine people:
* [Eric Wendelin](http://eriwen.com)
* [Luke Smith](http://lucassmith.name/)
* Loic Dachary
* Johan Euphrosine
* Øyvind Sean Kinsey
* Victor Homyakov

View file

@ -0,0 +1,11 @@
{
"name": "stacktrace.js",
"version": "0.6.0",
"main": "./stacktrace.js",
"dependencies": {},
"ignore": [
"**/.*",
"node_modules",
"components"
]
}

View file

@ -0,0 +1,123 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath 'com.eriwen:gradle-js-plugin:1.1'
}
}
apply plugin: 'js'
defaultTasks 'all'
buildDir = 'target'
def srcFile = 'stacktrace.js'
def destFile = "${buildDir}/stacktrace-min.js"
def testDir = 'test'
repositories {
mavenRepo url: 'http://repository.springsource.com/maven/bundles/release'
mavenCentral()
}
task clean(type: Delete) {
delete buildDir
}
task init(type: Directory, dependsOn: 'clean', description: 'Creates artifact output directories') {
outputs.dir(buildDir)
doLast {
file(buildDir).mkdirs()
}
}
task wrapper(type: Wrapper) {
gradleVersion = '1.1'
}
task jshintz(dependsOn: 'init', description: 'runs jshint on all non-test and lib JS files') {
doLast {
def command = "jshint ${new File('stacktrace.js').canonicalPath} --config jshint.json --jslint-reporter"
new File("${buildDir}/jshint.xml").write(command.execute().text)
}
}
task jsduck(type: Exec, dependsOn: 'init', description: 'Generates jsduck documentation') {
inputs.file file(srcFile)
outputs.file file("${buildDir}/docs")
commandLine = ['jsduck', srcFile, '--output', "${buildDir}/docs"]
ignoreExitValue = true
}
minifyJs {
dependsOn << 'init'
source = file(srcFile)
dest = file(destFile)
closure {
warningLevel = 'QUIET'
}
}
gzipJs {
source = minifyJs
dest = file(destFile)
}
task test(dependsOn: 'init') << {
description = 'run QUnit tests and create JUnit test reports'
def specs = []
new File(testDir).eachFile {
if (it.name.endsWith('.html')) {
specs << it
}
}
def phantomJsPath = "which phantomjs".execute().text.trim()
def startTime = new Date().time
def numFailures = 0
def testsFailed = false
specs.each { File spec ->
print "Running ${spec.name}..."
def outputFile = "${buildDir}/TEST-${spec.name.replace('-', '').replace('.html', '.xml')}"
ant.exec(outputproperty: 'cmdOut', errorproperty: 'cmdErr',
resultproperty: 'exitCode', failonerror: 'false', executable: phantomJsPath) {
arg(value: 'test/lib/phantomjs-qunit-runner.js')
arg(value: spec.canonicalPath)
}
// Check exit code
if (ant.project.properties.exitCode != '0') {
testsFailed = true
numFailures++
println 'FAILED'
} else {
println 'PASSED'
}
new File(outputFile).write(ant.project.properties.cmdOut)
}
println "QUnit tests completed in ${new Date().time - startTime}ms"
println "QUnit Tests ${testsFailed ? 'FAILED' : 'PASSED'} - view reports in ${buildDir}"
ant.fail(if: testsFailed, message: 'JS Tests Failed')
}
task jstd(type: Exec, dependsOn: 'init', description: 'runs JS tests through JsTestDriver') {
// Default to MacOS and check for other environments
def firefoxPath = '/Applications/Firefox.app/Contents/MacOS/firefox'
if ("uname".execute().text.trim() != 'Darwin') {
firefoxPath = "which firefox".execute().text.trim()
}
commandLine = ['/usr/bin/env', 'DISPLAY=:1', 'java', '-jar', "test/lib/JsTestDriver-1.3.3d.jar", '--config', "test/jsTestDriver.conf", '--port', '4224', '--browser', firefoxPath, '--tests', 'all', '--testOutput', buildDir]
}
task jsCoverage(type: Exec, dependsOn: 'jstd', description: 'JS code coverage with cobertura') {
commandLine = ['python', "${projectDir}/test/lib/lcov-to-cobertura-xml.py", '-e', 'test.lib', '-o', "${buildDir}/coverage.xml", "${buildDir}/jsTestDriver.conf-coverage.dat"]
}
task all(dependsOn: ['clean', 'jshintz', 'test', 'jsduck', 'minifyJs', 'gzipJs']) << {}

View file

@ -0,0 +1,10 @@
{
"name": "stacktrace.js",
"version": "0.6.0",
"repo": "eriwen/javascript-stacktrace",
"main": "stacktrace.js",
"scripts": [
"stacktrace.js"
],
"dependencies": {}
}

View file

@ -0,0 +1,6 @@
#Tue Aug 21 18:36:49 MDT 2012
zipStorePath=wrapper/dists
distributionPath=wrapper/dists
distributionBase=GRADLE_USER_HOME
zipStoreBase=GRADLE_USER_HOME
distributionUrl=http\://services.gradle.org/distributions/gradle-1.1-bin.zip

164
src/bower_components/stacktrace.js/gradlew vendored Executable file
View file

@ -0,0 +1,164 @@
#!/bin/bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/"
APP_HOME="`pwd -P`"
cd "$SAVED"
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

View file

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -0,0 +1,48 @@
{
"asi" : false,
"bitwise" : true,
"boss" : true,
"browser" : true,
"couch" : true,
"curly" : true,
"debug" : false,
"eqeqeq" : true,
"eqnull" : false,
"es5" : true,
"evil" : false,
"expr" : true,
"forin" : false,
"globalstrict" : true,
"immed" : false,
"latedef" : true,
"laxbreak" : false,
"loopfunc" : true,
"maxerr" : 50,
"newcap" : true,
"noarg" : false,
"node" : true,
"noempty" : true,
"nonew" : true,
"nomen" : false,
"onevar" : false,
"passfail" : false,
"plusplus" : false,
"prototypejs" : true,
"regexdash" : true,
"regexp" : false,
"rhino" : true,
"undef" : true,
"safe" : false,
"shadow" : true,
"strict" : false,
"sub" : true,
"supernew" : true,
"trailing" : true,
"white" : false,
"wsh" : true,
"indent" : 4,
"predef" : [
"ActiveXObject"
]
}

View file

@ -0,0 +1,20 @@
{
"name": "stacktrace-js",
"description": "Framework-agnostic, micro-library for getting stack traces in all environments",
"author": "Eric Wendelin <me@eriwen.com> (http://eriwen.com)",
"version": "0.6.0",
"keywords": ["stack-trace", "cross-browser", "framework-agnostic", "client", "browser"],
"homepage": "http://stacktracejs.com",
"repository": {
"type": "git",
"url": "git://github.com/eriwen/javascript-stacktrace.git"
},
"main": "./stacktrace.js",
"engines": {
"node": "*"
},
"dependencies": {},
"devDependencies": {
"jshint": "0.9.x"
}
}

View file

@ -0,0 +1 @@
javascript:(function(window,document){ldJS=function(){s=document.createElement('script');s.type='text/javascript';s.src='https://github.com/eriwen/javascript-stacktrace/raw/master/stacktrace.js';document.getElementsByTagName('head')[0].appendChild(s);};aT=function(){alert(printStackTrace().join('\n'))};aTWE=function(){window.onerror=aT};aTCF=function(fn){eval('_old_'+fn+'='+fn+';function%20'+fn+'(args){aT();_old_'+fn+'.call(this,args);}')};c=document.createElement('div');cs=c.style;cs.position='fixed';cs.top='0';cs.right='0';cs.backgroundColor='#ddd';cs.padding='0.3em';cs.margin='0%20auto';t=document.createElement('span');ts=t.style;ts.fontWeight='bold';t.innerHTML='Javascript%20Stacktrace:%20';c.appendChild(t);b0=document.createElement('input');b0.type='button';b0.value='Load%20stacktrace.js';b0.style.margin='0%201em';b0.onclick=ldJS;c.appendChild(b0);b1=document.createElement('input');b1.type='button';b1.value='Attach%20to%20window.onerror';b1.style.margin='0%201em';b1.onclick=aTWE;c.appendChild(b1);i=document.createElement('input');i.type='text';c.appendChild(i);b2=document.createElement('input');b2.type='button';b2.value='Attach%20to%20custom%20function';b2.style.marginRight='2em';b2.onclick=function(){aTCF(i.value)};c.appendChild(b2);cl=document.createElement('A');cl.href='javascript:void(0);';cl.onclick=function(){c.parentNode.removeChild(c);};cl.innerHTML='close';c.appendChild(cl);document.body.appendChild(c);})(window,document);

View file

@ -0,0 +1,485 @@
// Domain Public by Eric Wendelin http://eriwen.com/ (2008)
// Luke Smith http://lucassmith.name/ (2008)
// Loic Dachary <loic@dachary.org> (2008)
// Johan Euphrosine <proppy@aminche.com> (2008)
// Oyvind Sean Kinsey http://kinsey.no/blog (2010)
// Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010)
/*global module, exports, define, ActiveXObject*/
(function(global, factory) {
if (typeof exports === 'object') {
// Node
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
// AMD
define(factory);
} else {
// Browser globals
global.printStackTrace = factory();
}
}(this, function() {
/**
* Main function giving a function stack trace with a forced or passed in Error
*
* @cfg {Error} e The error to create a stacktrace from (optional)
* @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
* @return {Array} of Strings with functions, lines, files, and arguments where possible
*/
function printStackTrace(options) {
options = options || {guess: true};
var ex = options.e || null, guess = !!options.guess;
var p = new printStackTrace.implementation(), result = p.run(ex);
return (guess) ? p.guessAnonymousFunctions(result) : result;
}
printStackTrace.implementation = function() {
};
printStackTrace.implementation.prototype = {
/**
* @param {Error} [ex] The error to create a stacktrace from (optional)
* @param {String} [mode] Forced mode (optional, mostly for unit tests)
*/
run: function(ex, mode) {
ex = ex || this.createException();
mode = mode || this.mode(ex);
if (mode === 'other') {
return this.other(arguments.callee);
} else {
return this[mode](ex);
}
},
createException: function() {
try {
this.undef();
} catch (e) {
return e;
}
},
/**
* Mode could differ for different exception, e.g.
* exceptions in Chrome may or may not have arguments or stack.
*
* @return {String} mode of operation for the exception
*/
mode: function(e) {
if (e['arguments'] && e.stack) {
return 'chrome';
}
if (e.stack && e.sourceURL) {
return 'safari';
}
if (e.stack && e.number) {
return 'ie';
}
if (e.stack && e.fileName) {
return 'firefox';
}
if (e.message && e['opera#sourceloc']) {
// e.message.indexOf("Backtrace:") > -1 -> opera9
// 'opera#sourceloc' in e -> opera9, opera10a
// !e.stacktrace -> opera9
if (!e.stacktrace) {
return 'opera9'; // use e.message
}
if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) {
// e.message may have more stack entries than e.stacktrace
return 'opera9'; // use e.message
}
return 'opera10a'; // use e.stacktrace
}
if (e.message && e.stack && e.stacktrace) {
// e.stacktrace && e.stack -> opera10b
if (e.stacktrace.indexOf("called from line") < 0) {
return 'opera10b'; // use e.stacktrace, format differs from 'opera10a'
}
// e.stacktrace && e.stack -> opera11
return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b'
}
if (e.stack && !e.fileName) {
// Chrome 27 does not have e.arguments as earlier versions,
// but still does not have e.fileName as Firefox
return 'chrome';
}
return 'other';
},
/**
* Given a context, function name, and callback function, overwrite it so that it calls
* printStackTrace() first with a callback and then runs the rest of the body.
*
* @param {Object} context of execution (e.g. window)
* @param {String} functionName to instrument
* @param {Function} callback function to call with a stack trace on invocation
*/
instrumentFunction: function(context, functionName, callback) {
context = context || window;
var original = context[functionName];
context[functionName] = function instrumented() {
callback.call(this, printStackTrace().slice(4));
return context[functionName]._instrumented.apply(this, arguments);
};
context[functionName]._instrumented = original;
},
/**
* Given a context and function name of a function that has been
* instrumented, revert the function to it's original (non-instrumented)
* state.
*
* @param {Object} context of execution (e.g. window)
* @param {String} functionName to de-instrument
*/
deinstrumentFunction: function(context, functionName) {
if (context[functionName].constructor === Function &&
context[functionName]._instrumented &&
context[functionName]._instrumented.constructor === Function) {
context[functionName] = context[functionName]._instrumented;
}
},
/**
* Given an Error object, return a formatted Array based on Chrome's stack string.
*
* @param e - Error object to inspect
* @return Array<String> of function calls, files and line numbers
*/
chrome: function(e) {
return (e.stack + '\n')
.replace(/^[\s\S]+?\s+at\s+/, ' at ') // remove message
.replace(/^\s+(at eval )?at\s+/gm, '') // remove 'at' and indentation
.replace(/^([^\(]+?)([\n$])/gm, '{anonymous}() ($1)$2')
.replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}() ($1)')
.replace(/^(.+) \((.+)\)$/gm, '$1@$2')
.split('\n')
.slice(0, -1);
},
/**
* Given an Error object, return a formatted Array based on Safari's stack string.
*
* @param e - Error object to inspect
* @return Array<String> of function calls, files and line numbers
*/
safari: function(e) {
return e.stack.replace(/\[native code\]\n/m, '')
.replace(/^(?=\w+Error\:).*$\n/m, '')
.replace(/^@/gm, '{anonymous}()@')
.split('\n');
},
/**
* Given an Error object, return a formatted Array based on IE's stack string.
*
* @param e - Error object to inspect
* @return Array<String> of function calls, files and line numbers
*/
ie: function(e) {
return e.stack
.replace(/^\s*at\s+(.*)$/gm, '$1')
.replace(/^Anonymous function\s+/gm, '{anonymous}() ')
.replace(/^(.+)\s+\((.+)\)$/gm, '$1@$2')
.split('\n')
.slice(1);
},
/**
* Given an Error object, return a formatted Array based on Firefox's stack string.
*
* @param e - Error object to inspect
* @return Array<String> of function calls, files and line numbers
*/
firefox: function(e) {
return e.stack.replace(/(?:\n@:0)?\s+$/m, '')
.replace(/^(?:\((\S*)\))?@/gm, '{anonymous}($1)@')
.split('\n');
},
opera11: function(e) {
var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;
var lines = e.stacktrace.split('\n'), result = [];
for (var i = 0, len = lines.length; i < len; i += 2) {
var match = lineRE.exec(lines[i]);
if (match) {
var location = match[4] + ':' + match[1] + ':' + match[2];
var fnName = match[3] || "global code";
fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON);
result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
}
}
return result;
},
opera10b: function(e) {
// "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
// "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
// "@file://localhost/G:/js/test/functional/testcase1.html:15"
var lineRE = /^(.*)@(.+):(\d+)$/;
var lines = e.stacktrace.split('\n'), result = [];
for (var i = 0, len = lines.length; i < len; i++) {
var match = lineRE.exec(lines[i]);
if (match) {
var fnName = match[1] ? (match[1] + '()') : "global code";
result.push(fnName + '@' + match[2] + ':' + match[3]);
}
}
return result;
},
/**
* Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
*
* @param e - Error object to inspect
* @return Array<String> of function calls, files and line numbers
*/
opera10a: function(e) {
// " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n"
// " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n"
var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
var lines = e.stacktrace.split('\n'), result = [];
for (var i = 0, len = lines.length; i < len; i += 2) {
var match = lineRE.exec(lines[i]);
if (match) {
var fnName = match[3] || ANON;
result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
}
}
return result;
},
// Opera 7.x-9.2x only!
opera9: function(e) {
// " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n"
// " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n"
var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
var lines = e.message.split('\n'), result = [];
for (var i = 2, len = lines.length; i < len; i += 2) {
var match = lineRE.exec(lines[i]);
if (match) {
result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
}
}
return result;
},
// Safari 5-, IE 9-, and others
other: function(curr) {
var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10;
var slice = Array.prototype.slice;
while (curr && curr['arguments'] && stack.length < maxStackSize) {
fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
args = slice.call(curr['arguments'] || []);
stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
try {
curr = curr.caller;
} catch (e) {
stack[stack.length] = '' + e;
break;
}
}
return stack;
},
/**
* Given arguments array as a String, substituting type names for non-string types.
*
* @param {Arguments,Array} args
* @return {String} stringified arguments
*/
stringifyArguments: function(args) {
var result = [];
var slice = Array.prototype.slice;
for (var i = 0; i < args.length; ++i) {
var arg = args[i];
if (arg === undefined) {
result[i] = 'undefined';
} else if (arg === null) {
result[i] = 'null';
} else if (arg.constructor) {
// TODO constructor comparison does not work for iframes
if (arg.constructor === Array) {
if (arg.length < 3) {
result[i] = '[' + this.stringifyArguments(arg) + ']';
} else {
result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']';
}
} else if (arg.constructor === Object) {
result[i] = '#object';
} else if (arg.constructor === Function) {
result[i] = '#function';
} else if (arg.constructor === String) {
result[i] = '"' + arg + '"';
} else if (arg.constructor === Number) {
result[i] = arg;
} else {
result[i] = '?';
}
}
}
return result.join(',');
},
sourceCache: {},
/**
* @return the text from a given URL
*/
ajax: function(url) {
var req = this.createXMLHTTPObject();
if (req) {
try {
req.open('GET', url, false);
//req.overrideMimeType('text/plain');
//req.overrideMimeType('text/javascript');
req.send(null);
//return req.status == 200 ? req.responseText : '';
return req.responseText;
} catch (e) {
}
}
return '';
},
/**
* Try XHR methods in order and store XHR factory.
*
* @return <Function> XHR function or equivalent
*/
createXMLHTTPObject: function() {
var xmlhttp, XMLHttpFactories = [
function() {
return new XMLHttpRequest();
}, function() {
return new ActiveXObject('Msxml2.XMLHTTP');
}, function() {
return new ActiveXObject('Msxml3.XMLHTTP');
}, function() {
return new ActiveXObject('Microsoft.XMLHTTP');
}
];
for (var i = 0; i < XMLHttpFactories.length; i++) {
try {
xmlhttp = XMLHttpFactories[i]();
// Use memoization to cache the factory
this.createXMLHTTPObject = XMLHttpFactories[i];
return xmlhttp;
} catch (e) {
}
}
},
/**
* Given a URL, check if it is in the same domain (so we can get the source
* via Ajax).
*
* @param url <String> source url
* @return <Boolean> False if we need a cross-domain request
*/
isSameDomain: function(url) {
return typeof location !== "undefined" && url.indexOf(location.hostname) !== -1; // location may not be defined, e.g. when running from nodejs.
},
/**
* Get source code from given URL if in the same domain.
*
* @param url <String> JS source URL
* @return <Array> Array of source code lines
*/
getSource: function(url) {
// TODO reuse source from script tags?
if (!(url in this.sourceCache)) {
this.sourceCache[url] = this.ajax(url).split('\n');
}
return this.sourceCache[url];
},
guessAnonymousFunctions: function(stack) {
for (var i = 0; i < stack.length; ++i) {
var reStack = /\{anonymous\}\(.*\)@(.*)/,
reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,
frame = stack[i], ref = reStack.exec(frame);
if (ref) {
var m = reRef.exec(ref[1]);
if (m) { // If falsey, we did not get any file/line information
var file = m[1], lineno = m[2], charno = m[3] || 0;
if (file && this.isSameDomain(file) && lineno) {
var functionName = this.guessAnonymousFunction(file, lineno, charno);
stack[i] = frame.replace('{anonymous}', functionName);
}
}
}
}
return stack;
},
guessAnonymousFunction: function(url, lineNo, charNo) {
var ret;
try {
ret = this.findFunctionName(this.getSource(url), lineNo);
} catch (e) {
ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString();
}
return ret;
},
findFunctionName: function(source, lineNo) {
// FIXME findFunctionName fails for compressed source
// (more than one function on the same line)
// function {name}({args}) m[1]=name m[2]=args
var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/;
// {name} = function ({args}) TODO args capture
// /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/
var reFunctionExpression = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/;
// {name} = eval()
var reFunctionEvaluation = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/;
// Walk backwards in the source lines until we find
// the line which matches one of the patterns above
var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos;
for (var i = 0; i < maxLines; ++i) {
// lineNo is 1-based, source[] is 0-based
line = source[lineNo - i - 1];
commentPos = line.indexOf('//');
if (commentPos >= 0) {
line = line.substr(0, commentPos);
}
// TODO check other types of comments? Commented code may lead to false positive
if (line) {
code = line + code;
m = reFunctionExpression.exec(code);
if (m && m[1]) {
return m[1];
}
m = reFunctionDeclaration.exec(code);
if (m && m[1]) {
//return m[1] + "(" + (m[2] || "") + ")";
return m[1];
}
m = reFunctionEvaluation.exec(code);
if (m && m[1]) {
return m[1];
}
}
}
return '(?)';
}
};
return printStackTrace;
}));

View file

@ -0,0 +1,352 @@
var CapturedExceptions = {};
CapturedExceptions.opera_854 = {
message: "Statement on line 44: Type mismatch (usually a non-object value used where an object is required)\n" +
"Backtrace:\n" +
" Line 44 of linked script file://localhost/G:/js/stacktrace.js\n" +
" this.undef();\n" +
" Line 31 of linked script file://localhost/G:/js/stacktrace.js\n" +
" ex = ex || this.createException();\n" +
" Line 18 of linked script file://localhost/G:/js/stacktrace.js\n" +
" var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
" Line 4 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" printTrace(printStackTrace());\n" +
" Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" bar(n - 1);\n" +
" Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" bar(2);\n" +
" Line 15 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" foo();\n" +
"",
'opera#sourceloc': 44
};
CapturedExceptions.opera_902 = {
message: "Statement on line 44: Type mismatch (usually a non-object value used where an object is required)\n" +
"Backtrace:\n" +
" Line 44 of linked script file://localhost/G:/js/stacktrace.js\n" +
" this.undef();\n" +
" Line 31 of linked script file://localhost/G:/js/stacktrace.js\n" +
" ex = ex || this.createException();\n" +
" Line 18 of linked script file://localhost/G:/js/stacktrace.js\n" +
" var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
" Line 4 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" printTrace(printStackTrace());\n" +
" Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" bar(n - 1);\n" +
" Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" bar(2);\n" +
" Line 15 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" foo();\n" +
"",
'opera#sourceloc': 44
};
CapturedExceptions.opera_927 = {
message: "Statement on line 43: Type mismatch (usually a non-object value used where an object is required)\n" +
"Backtrace:\n" +
" Line 43 of linked script file://localhost/G:/js/stacktrace.js\n" +
" this.undef();\n" +
" Line 31 of linked script file://localhost/G:/js/stacktrace.js\n" +
" ex = ex || this.createException();\n" +
" Line 18 of linked script file://localhost/G:/js/stacktrace.js\n" +
" var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
" Line 4 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" printTrace(printStackTrace());\n" +
" Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" bar(n - 1);\n" +
" Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" bar(2);\n" +
" Line 15 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" foo();\n" +
"",
'opera#sourceloc': 43
};
CapturedExceptions.opera_964 = {
message: "Statement on line 42: Type mismatch (usually non-object value supplied where object required)\n" +
"Backtrace:\n" +
" Line 42 of linked script file://localhost/G:/js/stacktrace.js\n" +
" this.undef();\n" +
" Line 27 of linked script file://localhost/G:/js/stacktrace.js\n" +
" ex = ex || this.createException();\n" +
" Line 18 of linked script file://localhost/G:/js/stacktrace.js: In function printStackTrace\n" +
" var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
" Line 4 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function bar\n" +
" printTrace(printStackTrace());\n" +
" Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function bar\n" +
" bar(n - 1);\n" +
" Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n" +
" bar(2);\n" +
" Line 15 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" foo();\n" +
"",
'opera#sourceloc': 42,
stacktrace: " ... Line 27 of linked script file://localhost/G:/js/stacktrace.js\n" +
" ex = ex || this.createException();\n" +
" Line 18 of linked script file://localhost/G:/js/stacktrace.js: In function printStackTrace\n" +
" var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
" Line 4 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function bar\n" +
" printTrace(printStackTrace());\n" +
" Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function bar\n" +
" bar(n - 1);\n" +
" Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n" +
" bar(2);\n" +
" Line 15 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" foo();\n" +
""
};
CapturedExceptions.opera_1010 = {
message: "Statement on line 42: Type mismatch (usually non-object value supplied where object required)",
'opera#sourceloc': 42,
stacktrace: " Line 42 of linked script file://localhost/G:/js/stacktrace.js\n" +
" this.undef();\n" +
" Line 27 of linked script file://localhost/G:/js/stacktrace.js\n" +
" ex = ex || this.createException();\n" +
" Line 18 of linked script file://localhost/G:/js/stacktrace.js: In function printStackTrace\n" +
" var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
" Line 4 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function bar\n" +
" printTrace(printStackTrace());\n" +
" Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function bar\n" +
" bar(n - 1);\n" +
" Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n" +
" bar(2);\n" +
" Line 15 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n" +
" foo();\n" +
""
};
CapturedExceptions.opera_1063 = {
message: "'this.undef' is not a function",
stack: "<anonymous function: createException>([arguments not available])@file://localhost/G:/js/stacktrace.js:42\n" +
"<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
"printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
"bar([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:4\n" +
"bar([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:7\n" +
"foo([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:11\n" +
"@file://localhost/G:/js/test/functional/testcase1.html:15",
stacktrace: "<anonymous function: createException>([arguments not available])@file://localhost/G:/js/stacktrace.js:42\n" +
"<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
"printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
"bar([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:4\n" +
"bar([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:7\n" +
"foo([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:11\n" +
"@file://localhost/G:/js/test/functional/testcase1.html:15"
};
CapturedExceptions.opera_1111 = {
message: "'this.undef' is not a function",
stack: "<anonymous function: createException>([arguments not available])@file://localhost/G:/js/stacktrace.js:42\n" +
"<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
"printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
"bar([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:4\n" +
"bar([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:7\n" +
"foo([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:11\n" +
"@file://localhost/G:/js/test/functional/testcase1.html:15",
stacktrace: "Error thrown at line 42, column 12 in <anonymous function: createException>() in file://localhost/G:/js/stacktrace.js:\n" +
" this.undef();\n" +
"called from line 27, column 8 in <anonymous function: run>(ex) in file://localhost/G:/js/stacktrace.js:\n" +
" ex = ex || this.createException();\n" +
"called from line 18, column 4 in printStackTrace(options) in file://localhost/G:/js/stacktrace.js:\n" +
" var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
"called from line 4, column 5 in bar(n) in file://localhost/G:/js/test/functional/testcase1.html:\n" +
" printTrace(printStackTrace());\n" +
"called from line 7, column 4 in bar(n) in file://localhost/G:/js/test/functional/testcase1.html:\n" +
" bar(n - 1);\n" +
"called from line 11, column 4 in foo() in file://localhost/G:/js/test/functional/testcase1.html:\n" +
" bar(2);\n" +
"called from line 15, column 3 in file://localhost/G:/js/test/functional/testcase1.html:\n" +
" foo();"
};
CapturedExceptions.opera_1151 = {
message: "'this.undef' is not a function",
stack: "<anonymous function: createException>([arguments not available])@file://localhost/G:/js/stacktrace.js:42\n" +
"<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
"printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
"bar([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:4\n" +
"bar([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:7\n" +
"foo([arguments not available])@file://localhost/G:/js/test/functional/testcase1.html:11\n" +
"@file://localhost/G:/js/test/functional/testcase1.html:15",
stacktrace: "Error thrown at line 42, column 12 in <anonymous function: createException>() in file://localhost/G:/js/stacktrace.js:\n" +
" this.undef();\n" +
"called from line 27, column 8 in <anonymous function: run>(ex) in file://localhost/G:/js/stacktrace.js:\n" +
" ex = ex || this.createException();\n" +
"called from line 18, column 4 in printStackTrace(options) in file://localhost/G:/js/stacktrace.js:\n" +
" var p = new printStackTrace.implementation(), result = p.run(ex);\n" +
"called from line 4, column 5 in bar(n) in file://localhost/G:/js/test/functional/testcase1.html:\n" +
" printTrace(printStackTrace());\n" +
"called from line 7, column 4 in bar(n) in file://localhost/G:/js/test/functional/testcase1.html:\n" +
" bar(n - 1);\n" +
"called from line 11, column 4 in foo() in file://localhost/G:/js/test/functional/testcase1.html:\n" +
" bar(2);\n" +
"called from line 15, column 3 in file://localhost/G:/js/test/functional/testcase1.html:\n" +
" foo();"
};
CapturedExceptions.opera_1216 = {
message: "Cannot convert 'x' to object",
name: "TypeError",
stack: "<anonymous function>([arguments not available])@http://localhost:63342/javascript-stacktrace/test/functional/ExceptionLab.js:4\n" +
"createException([arguments not available])@http://localhost:63342/javascript-stacktrace/test/functional/ExceptionLab.js:2\n" +
"createException4([arguments not available])@http://localhost:63342/javascript-stacktrace/test/functional/ExceptionLab.html:56\n" +
"dumpException4([arguments not available])@http://localhost:63342/javascript-stacktrace/test/functional/ExceptionLab.html:60\n" +
"<anonymous function>([arguments not available])@http://localhost:63342/javascript-stacktrace/test/functional/ExceptionLab.html:1",
stacktrace: "Error thrown at line 4, column 6 in <anonymous function>(x) in http://localhost:63342/javascript-stacktrace/test/functional/ExceptionLab.js:\n" +
" x.undef();\n" +
"called from line 2, column 2 in createException() in http://localhost:63342/javascript-stacktrace/test/functional/ExceptionLab.js:\n" +
" return ((function(x) {\n" +
"called from line 56, column 8 in createException4() in http://localhost:63342/javascript-stacktrace/test/functional/ExceptionLab.html:\n" +
" return createException();\n" +
"called from line 60, column 8 in dumpException4() in http://localhost:63342/javascript-stacktrace/test/functional/ExceptionLab.html:\n" +
" dumpException(createException4());\n" +
"called from line 1, column 0 in <anonymous function>(event) in http://localhost:63342/javascript-stacktrace/test/functional/ExceptionLab.html:\n" +
" dumpException4();"
};
CapturedExceptions.chrome_15 = {
'arguments': ["undef"],
message: "Object #<Object> has no method 'undef'",
stack: "TypeError: Object #<Object> has no method 'undef'\n" +
" at Object.createException (http://127.0.0.1:8000/js/stacktrace.js:42:18)\n" +
" at Object.run (http://127.0.0.1:8000/js/stacktrace.js:31:25)\n" +
" at printStackTrace (http://127.0.0.1:8000/js/stacktrace.js:18:62)\n" +
" at bar (http://127.0.0.1:8000/js/test/functional/testcase1.html:13:17)\n" +
" at bar (http://127.0.0.1:8000/js/test/functional/testcase1.html:16:5)\n" +
" at foo (http://127.0.0.1:8000/js/test/functional/testcase1.html:20:5)\n" +
" at http://127.0.0.1:8000/js/test/functional/testcase1.html:24:4"
};
CapturedExceptions.chrome_27 = {
message: "Cannot call method 'undef' of null",
name: "TypeError",
stack: "TypeError: Cannot call method 'undef' of null\n" +
" at file:///E:/javascript-stacktrace/test/functional/ExceptionLab.js:4:9\n" +
" at createException (file:///E:/javascript-stacktrace/test/functional/ExceptionLab.js:8:5)\n" +
" at createException4 (file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:56:16)\n" +
" at dumpException4 (file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:60:23)\n" +
" at HTMLButtonElement.onclick (file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:83:126)"
};
CapturedExceptions.chrome_31_multiline_message = {
message: "Object function () {\n" +
" return {\n" +
" name: \"provide multi-line source in exception\"\n" +
" };\n" +
" } has no method 'nonExistentMethod'",
name: "TypeError",
stack: "TypeError: Object function () {\n" +
" return {\n" +
" name: \"provide multi-line source in exception\"\n" +
" };\n" +
" } has no method 'nonExistentMethod'\n" +
" at dumpException6 (file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:82:20)\n" +
" at HTMLButtonElement.onclick (file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:101:122)"
};
CapturedExceptions.firefox_3_6 = {
fileName: "http://127.0.0.1:8000/js/stacktrace.js",
lineNumber: 44,
message: "this.undef is not a function",
name: "TypeError",
stack: "()@http://127.0.0.1:8000/js/stacktrace.js:44\n" +
"(null)@http://127.0.0.1:8000/js/stacktrace.js:31\n" +
"printStackTrace()@http://127.0.0.1:8000/js/stacktrace.js:18\n" +
"bar(1)@http://127.0.0.1:8000/js/test/functional/testcase1.html:13\n" +
"bar(2)@http://127.0.0.1:8000/js/test/functional/testcase1.html:16\n" +
"foo()@http://127.0.0.1:8000/js/test/functional/testcase1.html:20\n" +
"@http://127.0.0.1:8000/js/test/functional/testcase1.html:24\n" +
""
};
CapturedExceptions.firefox_3_6_file = {
fileName: "file:///home/user/js/stacktrace.js",
lineNumber: 44,
message: "this.undef is not a function",
name: "TypeError",
stack: "()@file:///home/user/js/stacktrace.js:44\n" +
"(null)@file:///home/user/js/stacktrace.js:31\n" +
"printStackTrace()@file:///home/user/js/stacktrace.js:18\n" +
"bar(1)@file:///home/user/js/test/functional/testcase1.html:13\n" +
"bar(2)@file:///home/user/js/test/functional/testcase1.html:16\n" +
"foo()@file:///home/user/js/test/functional/testcase1.html:20\n" +
"@file:///home/user/js/test/functional/testcase1.html:24\n" +
""
};
CapturedExceptions.firefox_7 = {
fileName: "file:///G:/js/stacktrace.js",
lineNumber: 44,
stack: "()@file:///G:/js/stacktrace.js:44\n" +
"(null)@file:///G:/js/stacktrace.js:31\n" +
"printStackTrace()@file:///G:/js/stacktrace.js:18\n" +
"bar(1)@file:///G:/js/test/functional/testcase1.html:13\n" +
"bar(2)@file:///G:/js/test/functional/testcase1.html:16\n" +
"foo()@file:///G:/js/test/functional/testcase1.html:20\n" +
"@file:///G:/js/test/functional/testcase1.html:24\n" +
""
};
CapturedExceptions.firefox_14 = {
message: "x is null",
stack: "@file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html:48\n" +
"dumpException3@file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html:52\n" +
"onclick@file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html:1\n" +
"",
fileName: "file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html",
lineNumber: 48
};
CapturedExceptions.firefox_22 = {
message: "x is null",
name: "TypeError",
stack: "@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.js:4\n" +
"createException@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.js:8\n" +
"createException4@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:56\n" +
"dumpException4@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:60\n" +
"onclick@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:1\n" +
"",
fileName: "file:///E:/javascript-stacktrace/test/functional/ExceptionLab.js",
lineNumber: 4,
columnNumber: 6
};
CapturedExceptions.safari_6 = {
message: "'null' is not an object (evaluating 'x.undef')",
stack: "@file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html:48\n" +
"dumpException3@file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html:52\n" +
"onclick@file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html:82\n" +
"[native code]",
line: 48,
sourceURL: "file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html"
};
CapturedExceptions.ie_10 = {
message: "Unable to get property 'undef' of undefined or null reference",
name: "TypeError",
stack: "TypeError: Unable to get property 'undef' of undefined or null reference\n" +
" at Anonymous function (http://jenkins.eriwen.com/job/stacktrace.js/ws/test/functional/ExceptionLab.html:48:13)\n" +
" at dumpException3 (http://jenkins.eriwen.com/job/stacktrace.js/ws/test/functional/ExceptionLab.html:46:9)\n" +
" at onclick (http://jenkins.eriwen.com/job/stacktrace.js/ws/test/functional/ExceptionLab.html:82:1)",
description: "Unable to get property 'undef' of undefined or null reference",
number: -2146823281
};
CapturedExceptions.node_simple = {
message: 'x is not defined',
name: 'ReferenceError',
type: 'not_defined',
stack: 'ReferenceError: x is not defined\n' +
' at repl:1:5\n' +
' at REPLServer.self.eval (repl.js:110:21)\n' +
' at repl.js:249:20\n' +
' at REPLServer.self.eval (repl.js:122:7)\n' +
' at Interface.<anonymous> (repl.js:239:12)\n' +
' at Interface.EventEmitter.emit (events.js:95:17)\n' +
' at Interface._onLine (readline.js:202:10)\n' +
' at Interface._line (readline.js:531:8)\n' +
' at Interface._ttyWrite (readline.js:760:14)\n' +
' at ReadStream.onkeypress (readline.js:99:10)',
'arguments': [ 'x' ]
};

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<!--
Copyright (C) 2008 Johan Euphrosine <proppy@aminche.com>
Copyright (C) 2008 Loic Dachary <loic@dachary.org>
Copyright (C) 2011 Eric Wendelin <emwendelin@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en' dir='ltr' id='html'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<title>stacktrace.js Test Suite</title>
<link rel='stylesheet' href='lib/qunit.css' type='text/css' />
<!-- AMD loader -->
<script src="https://raw.github.com/jrburke/requirejs/master/require.js"></script>
<script type='text/javascript' src='lib/qunit.js'></script>
<script type='text/javascript'>
// Give the AMD loader time to fetch dependencies
QUnit.config.autostart = false;
QUnit.config.reorder = false;
</script>
<script type='text/javascript' src='lib/qunit-junit-outputter.js'></script>
<script type='text/javascript' src='lib/qunit-browserscope.js'></script>
<script type='text/javascript' src='CapturedExceptions.js'></script>
<script type='text/javascript' src='TestAMDStacktrace.js'></script>
</head>
<body>
<h1 id='qunit-header'>stacktrace.js Test Suite</h1>
<h2 id='qunit-banner'></h2>
<h2 id='qunit-userAgent'></h2>
<ol id='qunit-tests'></ol>
</body>
</html>

View file

@ -0,0 +1,43 @@
/*global module, test, equals, expect, ok, printStackTrace, CapturedExceptions */
//
// Copyright (C) 2008 Loic Dachary <loic@dachary.org>
// Copyright (C) 2008 Johan Euphrosine <proppy@aminche.com>
// Copyright (C) 2010 Eric Wendelin <emwendelin@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Setup a mapping to stacktrace for the unit tests
require.config({
paths: {
'stacktrace': '../stacktrace'
}
});
(function(window, document, undefined) {
module("AMD invocation");
test("printStackTrace", function() {
expect(1);
stop();
require(['stacktrace'], function(printStackTrace) {
var r = printStackTrace();
equals(r.constructor, Array, 'printStackTrace returns an array');
start();
});
});
})(window, document);
// Start QUnit since we set autostart to false
QUnit.start();

View file

@ -0,0 +1,47 @@
<!DOCTYPE html>
<!--
Copyright (C) 2008 Johan Euphrosine <proppy@aminche.com>
Copyright (C) 2008 Loic Dachary <loic@dachary.org>
Copyright (C) 2011 Eric Wendelin <emwendelin@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en' dir='ltr' id='html'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
<title>stacktrace.js Test Suite</title>
<link rel='stylesheet' href='lib/qunit.css' type='text/css'/>
<script type='text/javascript' src='lib/qunit.js'></script>
<script>
if (typeof console === "undefined") {
// IE8- stub
console = {
log: function() {
}
};
}
</script>
<script type='text/javascript' src='lib/qunit-junit-outputter.js'></script>
<script type='text/javascript' src='lib/qunit-browserscope.js'></script>
<script type='text/javascript' src='../stacktrace.js'></script>
<script type='text/javascript' src='CapturedExceptions.js'></script>
<script type='text/javascript' src='TestStacktrace.js'></script>
</head>
<body>
<h1 id='qunit-header'>stacktrace.js Test Suite</h1>
<h2 id='qunit-banner'></h2>
<h2 id='qunit-userAgent'></h2>
<ol id='qunit-tests'></ol>
</body>
</html>

View file

@ -0,0 +1,888 @@
/*global module, test, equals, expect, ok, printStackTrace, CapturedExceptions */
/*jshint bitwise:true, curly:true, forin:true, latedef:true, noarg:true, noempty:true, nonew:true, undef:true, trailing:true, indent:4, browser:true */
//
// Copyright (C) 2008 Loic Dachary <loic@dachary.org>
// Copyright (C) 2008 Johan Euphrosine <proppy@aminche.com>
// Copyright (C) 2010 Eric Wendelin <emwendelin@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
(function(window, document, undefined) {
//region setup
var pst = printStackTrace.implementation.prototype;
var impl = function() {
return new printStackTrace.implementation();
};
var ex;
try {
this.undef();
} catch (exception) {
ex = exception;
}
// Testing util functions
var UnitTest = function() {
};
UnitTest.fn = UnitTest.prototype = {
genericError: null,
createGenericError: function() {
if (UnitTest.prototype.genericError != null) {
return UnitTest.prototype.genericError;
}
//return new Error("Generic error");
return new Error();
},
createModeStub: function(mode) {
return function() {
ok(false, 'must not call run() for mode "' + mode + '"');
};
},
createModeStubs: function(p, stub) {
var modes = ['other', 'opera9', 'opera10a', 'opera10b', 'opera11', 'firefox', 'safari', 'ie', 'chrome'];
for (var i = 0, len = modes.length; i < len; i++) {
var mode = modes[i];
p[mode] = stub || this.createModeStub(mode);
}
}
};
//endregion
//region invocation
module("invocation");
test("printStackTrace", function() {
expect(1);
var r = printStackTrace();
equals(r.constructor, Array, 'printStackTrace returns an array');
});
test("printStackTrace options", function() {
expect(1);
var guessAnonymousFunctions = pst.guessAnonymousFunctions;
pst.guessAnonymousFunctions = function() {
pst.guessAnonymousFunctions = guessAnonymousFunctions;
ok(true, 'guessAnonymousFunctions called');
};
printStackTrace({
guess: true
});
});
//endregion
//region mode
module("mode");
test("mode", function() {
expect(1);
equals("chrome safari firefox ie other opera9 opera10a opera10b opera11".indexOf(pst.mode(UnitTest.fn.createGenericError())) >= 0, true);
});
test("run mode", function() {
expect(1);
var p = impl();
UnitTest.fn.createModeStubs(p, function() {
ok(true, 'called mode() successfully');
});
p.run();
});
test("run chrome", function() {
expect(2);
var p = impl();
UnitTest.fn.createModeStubs(p);
p.chrome = function() {
ok(true, 'called run() for "chrome"');
};
p.run(CapturedExceptions.chrome_15);
p.run(CapturedExceptions.chrome_27);
});
test("run safari", function() {
expect(1);
var p = impl();
UnitTest.fn.createModeStubs(p);
p.safari = function() {
ok(true, 'called run() for "safari"');
};
p.run(CapturedExceptions.safari_6);
});
test("run ie", function() {
expect(1);
var p = impl();
UnitTest.fn.createModeStubs(p);
p.ie = function() {
ok(true, 'called run() for "ie"');
};
p.run(CapturedExceptions.ie_10);
});
test("run firefox", function() {
expect(5);
var p = impl();
UnitTest.fn.createModeStubs(p);
p.firefox = function() {
ok(true, 'called run() for "firefox"');
};
p.run(CapturedExceptions.firefox_3_6);
p.run(CapturedExceptions.firefox_3_6_file);
p.run(CapturedExceptions.firefox_7);
p.run(CapturedExceptions.firefox_14);
p.run(CapturedExceptions.firefox_22);
});
test("run opera9", function() {
expect(4);
var p = impl();
UnitTest.fn.createModeStubs(p);
p.opera9 = function() {
ok(true, 'called run() for "opera9"');
};
p.run(CapturedExceptions.opera_854);
p.run(CapturedExceptions.opera_902);
p.run(CapturedExceptions.opera_927);
p.run(CapturedExceptions.opera_964);
});
test("run opera10a", function() {
expect(1);
var p = impl();
UnitTest.fn.createModeStubs(p);
p.opera10a = function() {
ok(true, 'called run() for "opera10a"');
};
p.run(CapturedExceptions.opera_1010);
});
test("run opera10b", function() {
expect(1);
var p = impl();
UnitTest.fn.createModeStubs(p);
p.opera10b = function() {
ok(true, 'called run() for "opera10b"');
};
p.run(CapturedExceptions.opera_1063);
});
test("run opera11", function() {
expect(3);
var p = impl();
UnitTest.fn.createModeStubs(p);
p.opera11 = function() {
ok(true, 'called run() for "opera11"');
};
p.run(CapturedExceptions.opera_1111);
p.run(CapturedExceptions.opera_1151);
p.run(CapturedExceptions.opera_1216);
});
test("run other", function() {
expect(1);
var p = impl();
UnitTest.fn.createModeStubs(p);
p.other = function() {
ok(true, 'called run() for other browser');
};
p.run({});
});
test("function instrumentation", function() {
expect(4);
this.toInstrument = function() {
ok(true, 'called instrumented function');
};
this.callback = function(stacktrace) {
ok(typeof stacktrace !== 'undefined', 'called callback');
};
pst.instrumentFunction(this, 'toInstrument', this.callback);
ok(this.toInstrument._instrumented, 'function instrumented');
this.toInstrument();
pst.deinstrumentFunction(this, 'toInstrument');
ok(!this.toInstrument._instrumented, 'function deinstrumented');
this.toInstrument = this.callback = null;
});
test("firefox", function() {
expect(34);
var message = pst.firefox(CapturedExceptions.firefox_3_6);
// equals(message.join('\n'), '', 'processed stack trace');
equals(message.length, 7, 'Firefox 3.6: 7 stack entries');
equals(message[0], '{anonymous}()@http://127.0.0.1:8000/js/stacktrace.js:44');
equals(message[1], '{anonymous}(null)@http://127.0.0.1:8000/js/stacktrace.js:31');
equals(message[2], 'printStackTrace()@http://127.0.0.1:8000/js/stacktrace.js:18');
equals(message[3], 'bar(1)@http://127.0.0.1:8000/js/test/functional/testcase1.html:13');
equals(message[4], 'bar(2)@http://127.0.0.1:8000/js/test/functional/testcase1.html:16');
equals(message[5], 'foo()@http://127.0.0.1:8000/js/test/functional/testcase1.html:20');
equals(message[6], '{anonymous}()@http://127.0.0.1:8000/js/test/functional/testcase1.html:24');
message = pst.firefox(CapturedExceptions.firefox_3_6_file);
equals(message.length, 7, 'Firefox 3.6: 7 stack entries');
equals(message[0], '{anonymous}()@file:///home/user/js/stacktrace.js:44');
equals(message[1], '{anonymous}(null)@file:///home/user/js/stacktrace.js:31');
equals(message[2], 'printStackTrace()@file:///home/user/js/stacktrace.js:18');
equals(message[3], 'bar(1)@file:///home/user/js/test/functional/testcase1.html:13');
equals(message[4], 'bar(2)@file:///home/user/js/test/functional/testcase1.html:16');
equals(message[5], 'foo()@file:///home/user/js/test/functional/testcase1.html:20');
equals(message[6], '{anonymous}()@file:///home/user/js/test/functional/testcase1.html:24');
message = pst.firefox(CapturedExceptions.firefox_7);
equals(message.length, 7, 'Firefox 7: 7 stack entries');
equals(message[0], '{anonymous}()@file:///G:/js/stacktrace.js:44');
equals(message[1], '{anonymous}(null)@file:///G:/js/stacktrace.js:31');
equals(message[2], 'printStackTrace()@file:///G:/js/stacktrace.js:18');
equals(message[3], 'bar(1)@file:///G:/js/test/functional/testcase1.html:13');
equals(message[4], 'bar(2)@file:///G:/js/test/functional/testcase1.html:16');
equals(message[5], 'foo()@file:///G:/js/test/functional/testcase1.html:20');
equals(message[6], '{anonymous}()@file:///G:/js/test/functional/testcase1.html:24');
message = pst.firefox(CapturedExceptions.firefox_14);
equals(message.length, 3, 'Firefox 14: 3 stack entries');
equals(message[0], '{anonymous}()@file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html:48');
equals(message[1], 'dumpException3@file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html:52');
equals(message[2], 'onclick@file:///Users/eric/src/javascript-stacktrace/test/functional/ExceptionLab.html:1');
message = pst.firefox(CapturedExceptions.firefox_22);
equals(message.length, 5, 'Firefox 22: 7 stack entries');
equals(message[0], '{anonymous}()@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.js:4');
equals(message[1], 'createException@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.js:8');
equals(message[2], 'createException4@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:56');
equals(message[3], 'dumpException4@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:60');
equals(message[4], 'onclick@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:1');
});
if (pst.mode(ex) == 'firefox') {
test("firefox live", function() {
function f1(arg1, arg2) {
try {
return this.undef();
} catch (exception) {
return exception;
}
}
var f2 = function() {
return f1(1, "abc");
};
var e = (function() {
return f2();
})();
expect(2);
var message = pst.firefox(e);
// equals(message.join('\n'), '', 'processed stack trace');
equals(message[0].indexOf('f1@'), 0, message[0] + ' should start with f1@');
equals(message[1].indexOf('f2@'), 0, message[1] + ' should start with f2@');
//equals(message[2].indexOf('{anonymous}()@'), 0, message[2] + ' should start with {anonymous}()@');
});
}
test("chrome", function() {
expect(17);
var message = pst.chrome(CapturedExceptions.chrome_15);
// equals(message.join('\n'), '', 'processed stack trace');
equals(message.length, 7, 'Chrome 15: 7 stack entries');
equals(message[0], 'Object.createException@http://127.0.0.1:8000/js/stacktrace.js:42:18');
equals(message[1], 'Object.run@http://127.0.0.1:8000/js/stacktrace.js:31:25');
equals(message[2], 'printStackTrace@http://127.0.0.1:8000/js/stacktrace.js:18:62');
equals(message[3], 'bar@http://127.0.0.1:8000/js/test/functional/testcase1.html:13:17');
equals(message[4], 'bar@http://127.0.0.1:8000/js/test/functional/testcase1.html:16:5');
equals(message[5], 'foo@http://127.0.0.1:8000/js/test/functional/testcase1.html:20:5');
equals(message[6], '{anonymous}()@http://127.0.0.1:8000/js/test/functional/testcase1.html:24:4');
message = pst.chrome(CapturedExceptions.chrome_27);
equals(message.length, 5, 'Chrome 27: 5 stack entries');
equals(message[0], '{anonymous}()@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.js:4:9');
equals(message[1], 'createException@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.js:8:5');
equals(message[2], 'createException4@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:56:16');
equals(message[3], 'dumpException4@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:60:23');
equals(message[4], 'HTMLButtonElement.onclick@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:83:126');
message = pst.chrome(CapturedExceptions.chrome_31_multiline_message);
equals(message.length, 2, 'Chrome 31: 2 stack entries');
equals(message[0], 'dumpException6@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:82:20');
equals(message[1], 'HTMLButtonElement.onclick@file:///E:/javascript-stacktrace/test/functional/ExceptionLab.html:101:122');
});
if (pst.mode(ex) == 'chrome') {
test("chrome live", function() {
function f1(arg1, arg2) {
try {
return this.undef();
} catch (exception) {
return exception;
}
}
var f2 = function() {
return f1(1, "abc");
};
var e = (function() {
return f2();
})();
expect(3);
var message = pst.chrome(e);
//equals(e.stack, '', 'original stack trace');
//equals(message.join('\n'), '', 'processed stack trace');
equals(message[0].indexOf('f1@'), 0, message[0] + ' should start with f1@');
equals(message[1].indexOf('f2@'), 0, message[1] + ' should start with f2@');
equals(message[2].indexOf('{anonymous}()@'), 0, message[2] + ' should start with {anonymous}()@');
});
}
test("opera9", function() {
var mode = pst.mode(UnitTest.fn.createGenericError()), e = [];
if (mode == 'opera9') {
function discarded() {
try {
this.undef();
} catch (exception) {
e.push(exception);
}
}
function f1(arg1, arg2) {
discarded();
}
var f2 = function() {
f1(1, "abc");
};
f2();
}
expect(3 * e.length);
for (var i = 0; i < e.length; i++) {
var message = pst.opera9(e[i]);
var message_string = message.join("\n");
//equals(message.join("\n"), 'debug', 'debug');
//equals(message[0].indexOf('f1()') >= 0, true, 'f1 function name');
equals(message[1].indexOf('discarded()') >= 0, true, 'discarded() statement in f1: ' + message[1]);
equals(message[2].indexOf('{anonymous}()@') >= 0, true, 'f2 is anonymous: ' + message[2]);
equals(message[2].indexOf('f1(1, "abc")') >= 0, true, 'f1() statement in f2: ' + message[2]);
}
});
test("opera9", function() {
var e = [CapturedExceptions.opera_854, CapturedExceptions.opera_902, CapturedExceptions.opera_927, CapturedExceptions.opera_964];
expect(12); // 3 * e.length
for (var i = 0; i < e.length; i++) {
var message = pst.opera9(e[i]);
//equals(message.join("\n"), 'debug', 'debug');
equals(message.length, 7, 'number of stack entries');
equals(message[0].indexOf('this.undef()') >= 0, true, 'this.undef() is at the top of stack');
equals(message[message.length - 1].indexOf('foo()') >= 0, true, 'foo() is at the bottom of stack');
}
});
test("opera10a", function() {
var e = [CapturedExceptions.opera_1010];
expect(5); // 5 * e.length
for (var i = 0; i < e.length; i++) {
var message = pst.opera10a(e[i]);
//equals(message.join("\n"), 'debug', 'debug');
equals(message.length, 7, 'number of stack entries');
equals(message[0].indexOf('this.undef()') >= 0, true, 'this.undef() is at the top of stack');
equals(message[message.length - 3].indexOf('bar(') >= 0, true, 'bar is 3rd from the bottom of stack');
equals(message[message.length - 2].indexOf('bar(2)') >= 0, true, 'bar is 2nd from the bottom of stack');
equals(message[message.length - 1].indexOf('foo()') >= 0, true, 'foo() is at the bottom of stack');
}
});
test("opera10b", function() {
var e = [CapturedExceptions.opera_1063];
expect(3); // 3 * e.length
for (var i = 0; i < e.length; i++) {
var message = pst.opera10b(e[i]);
//equals(message.join("\n"), 'debug', 'debug');
equals(message.length, 7, 'number of stack entries');
equals(message[0].indexOf('createException') >= 0, true, 'createException() is at the top of stack');
equals(message[message.length - 2].indexOf('foo') >= 0, true, 'foo() is 2nd from the bottom of stack');
}
});
test("opera11", function() {
var e = [CapturedExceptions.opera_1111, CapturedExceptions.opera_1151];
expect(6); // 3 * e.length
for (var i = 0; i < e.length; i++) {
var message = pst.opera11(e[i]);
//equals(message.join("\n"), 'debug', 'debug');
equals(message.length, 7, 'number of stack entries');
equals(message[0].indexOf('createException') >= 0, true, 'createException() is at the top of stack');
equals(message[message.length - 2].indexOf('foo') >= 0, true, 'foo() is 2nd from the bottom of stack');
}
});
test("opera11", function() {
var mode = pst.mode(UnitTest.fn.createGenericError());
var e = [];
if (mode == 'opera11') {
function discarded() {
try {
this.undef();
} catch (exception) {
e.push(exception);
}
}
function f1(arg1, arg2) {
var blah = arg1;
discarded();
}
var f2 = function() {
f1(1, "abc");
};
f2();
}
expect(3 * e.length);
for (var i = 0; i < e.length; i++) {
var stack = pst.opera11(e[i]), stack_string = stack.join('\n');
//equals(stack_string, 'debug', 'debug');
equals(stack_string.indexOf('ignored'), -1, 'ignored');
equals(stack[1].indexOf('f1(') >= 0, true, 'f1 function name: ' + stack[1]);
equals(stack[2].indexOf('{anonymous}()') >= 0, true, 'f2 is anonymous: ' + stack[2]);
}
});
test("safari", function() {
var e = [], ex;
function f0() {
try {
this.undef();
} catch (exception) {
ex = exception;
}
}
function f1(arg1, arg2) {
f0();
}
var f2 = function() {
f1(1, "abc");
};
f2();
if (pst.mode(ex) == 'safari') {
e.push(ex);
}
expect(2 * e.length);
for (var i = 0; i < e.length; i++) {
var stack = pst.safari(e[i]), stack_string = stack.join('\n');
//equals(stack_string, 'debug', 'debug');
equals(stack[0].indexOf('f0') >= 0, true, 'matched f0');
equals(stack[1].indexOf('f1') >= 0, true, 'f1 function name: ' + stack[1]);
}
});
if (pst.mode(ex) == 'ie') {
test("ie10 live", function() {
function f1(arg1, arg2) {
try {
return this.undef();
} catch (exception) {
return exception;
}
}
var f2 = function() {
return f1(1, "abc");
};
var e = (function() {
return f2();
})();
expect(3);
var message = pst.ie(e);
//equals(e.stack, '', 'original stack trace');
//equals(message.join('\n'), '', 'processed stack trace');
equals(message[0].indexOf('f1@'), 0, message[0] + ' should start with f1@');
equals(message[1].indexOf('f2@'), 0, message[1] + ' should start with f2@');
equals(message[2].indexOf('{anonymous}()@'), 0, message[2] + ' should start with {anonymous}()@');
});
}
test("ie10", function() {
expect(4);
var message = pst.ie(CapturedExceptions.ie_10);
equals(message.length, 3, '3 stack entries');
equals(message[0], '{anonymous}()@http://jenkins.eriwen.com/job/stacktrace.js/ws/test/functional/ExceptionLab.html:48:13');
equals(message[1], 'dumpException3@http://jenkins.eriwen.com/job/stacktrace.js/ws/test/functional/ExceptionLab.html:46:9');
equals(message[2], 'onclick@http://jenkins.eriwen.com/job/stacktrace.js/ws/test/functional/ExceptionLab.html:82:1');
});
test("other", function() {
var mode = pst.mode(UnitTest.fn.createGenericError());
var frame = function(args, fun, caller) {
this['arguments'] = args;
this.caller = caller;
this.fun = fun;
};
frame.prototype.toString = function() {
return 'function ' + this.fun + '() {}';
};
function f10() {
}
var frame_f2 = new frame([], '', undefined);
var frame_f1 = new frame([1, 'abc', f10, {
1: {
2: {
3: 4
}
}
}], 'FUNCTION f1 (a,b,c)', frame_f2);
expect(mode == 'other' ? 4 : 2);
var message = pst.other(frame_f1);
equals(message[0].indexOf('f1(1,"abc",#function,#object)') >= 0, true, 'f1');
equals(message[1].indexOf('{anonymous}()') >= 0, true, 'f2 anonymous');
if (mode == 'other') {
function f1(arg1, arg2) {
var message = pst.other(arguments.callee);
//equals(message.join("\n"), '', 'debug');
equals(message[0].indexOf('f1(1,"abc",#function,#object)') >= 0, true, 'f1');
equals(message[1].indexOf('{anonymous}()') >= 0, true, 'f2 anonymous');
}
var f2 = function() {
f1(1, 'abc', f10, {
1: {
2: {
3: 4
}
}
});
};
f2();
}
});
test("other in strict mode", function() {
var results = [];
var p = impl();
function f1() {
try {
this.undef();
} catch (e) {
debugger;
results = p.run(e, 'other');
}
}
function f2() {
f1();
}
function f3() {
"use strict";
f2();
}
f3();
ok(results.length >= 3, 'Stack should contain at least 3 frames in non-strict mode');
//equals(results, '', 'debug');
equals(results[1], 'f1()');
equals(results[2], 'f2()');
});
//endregion
//region util
module("util");
test("stringify", function() {
expect(5);
equals(pst.stringifyArguments(["a", 1, {}, function() {
}, undefined]), '"a",1,#object,#function,undefined');
equals(pst.stringifyArguments([0, 1, 2, 3]), '0,1,2,3');
equals(pst.stringifyArguments([
['a', null]
]), '["a",null]');
equals(pst.stringifyArguments([
[2, 4, 6, 8, 10, 12, 14]
]), '[2...14]');
equals(pst.stringifyArguments([]), '');
});
test("isSameDomain", function() {
expect(1);
ok(pst.isSameDomain(location.href));
});
test("findFunctionName", function() {
expect(13);
equals(pst.findFunctionName(['var a = function aa() {', 'var b = 2;', '};'], 2), 'a');
equals(pst.findFunctionName(['var a = function () {', 'var b = 2;', '};'], 2), 'a');
equals(pst.findFunctionName(['var a = function() {', 'var b = 2;', '};'], 2), 'a');
// FIXME: currently failing because we don't have a way to distinguish which fn is being sought
// equals(pst.findFunctionName(['a:function(){},b:function(){', '};'], 1), 'b');
equals(pst.findFunctionName(['"a": function(){', '};'], 1), 'a');
// different formatting
equals(pst.findFunctionName(['function a() {', 'var b = 2;', '}'], 2), 'a');
equals(pst.findFunctionName(['function a(b,c) {', 'var b = 2;', '}'], 2), 'a');
equals(pst.findFunctionName(['function a () {', '}'], 2), 'a');
equals(pst.findFunctionName(['function\ta\t()\t{', '}'], 2), 'a');
equals(pst.findFunctionName([' function', ' a', ' ()', ' {', ' }'], 3), 'a');
equals(pst.findFunctionName(['var data = new Function("return true;");', ''], 1), 'data');
equals(pst.findFunctionName(['var data = new Function("s,r",', '"return s + r;");'], 1), 'data');
// not found
equals(pst.findFunctionName(['var a = 1;', 'var b = 2;', 'var c = 3;'], 2), '(?)');
// false positive in comment
equals(pst.findFunctionName(['function a() {', ' // function commented()', ' error here', '}'], 3), 'a');
});
test("getSource cache miss", function() {
expect(3);
var p = impl(), file = 'file:///test', lines;
p.ajax = function(fileArg, callback) {
equals(fileArg, file, 'cache miss');
return 'line0\nline1\n';
};
lines = p.getSource(file);
equals(lines[0], 'line0');
equals(lines[1], 'line1');
});
test("getSource cache hit", function() {
expect(2);
var p = impl(), file = 'file:///test', lines;
p.ajax = function(fileArg, callback) {
ok(false, 'not called');
};
p.sourceCache[file] = ['line0', 'line1'];
lines = p.getSource(file);
equals(lines[0], 'line0');
equals(lines[1], 'line1');
});
if (window && window.location && window.location.hostname && window.location.hostname !== 'localhost') {
test("sync ajax", function() {
expect(1);
var p = impl();
var data = p.ajax(document.location.href);
ok(data.indexOf('stacktrace') >= 0, 'synchronous get');
});
}
test("guessAnonymousFunction", function() {
expect(1);
var p = impl();
var file = 'http://' + window.location.hostname + '/file.js';
p.sourceCache[file] = ['var a = function() {', 'var b = 2;', '};'];
equals(p.guessAnonymousFunction(file, 2), 'a');
});
test("guessAnonymousFunction exception", function() {
// FIXME: this test seems to affect guessAnonymousFunction opera11
expect(1);
var p = impl();
var oldGetSource = p.getSource;
p.getSource = function() {
throw 'permission denied';
};
var file = 'file:///test';
equals(p.guessAnonymousFunction(file, 2), 'getSource failed with url: file:///test, exception: permission denied');
// Reset mocked function
p.getSource = oldGetSource;
});
test("guessAnonymousFunctions firefox", function() {
var results = [];
var p = impl();
var file = 'http://' + window.location.hostname + '/file.js';
p.sourceCache[file] = ['var f2 = function () {', 'var b = 2;', '};', 'function run() {', 'return true;', '}'];
results.push(['{anonymous}()@' + file + ':74', '{anonymous}()@' + file + ':5', '{anonymous}()@' + file + ':2']);
(function f2() {
try {
this.undef();
} catch (e) {
if (p.mode(e) == 'firefox') {
results.push(p.run());
}
}
})();
expect(results.length);
for (var i = 0; i < results.length; ++i) {
//equals(results[i], '', 'stack trace');
var functions = p.guessAnonymousFunctions(results[i]);
//equals(functions.join("\n"), '', 'stack trace after guessing');
equals(functions[2].substring(0, 2), 'f2', 'guessed f2 as 3rd result: ' + functions[2]);
//equals(functions[2].indexOf('f2'), 0, 'guessed f2 as 3rd result');
}
});
test("guessAnonymousFunctions chrome", function() {
var results = [];
var p = impl();
var file = 'http://' + window.location.hostname + '/file.js';
p.sourceCache[file] = ['var f2 = function() {', 'var b = 2;', '};'];
results.push(['createException() (' + file + ':1:1)', 'run() (' + file + ':1:1)', 'f2() (' + file + ':1:1)']);
var f2 = function() {
try {
this.undef();
} catch (e) {
if (p.mode(e) == 'chrome') {
results.push(p.run());
}
}
};
f2();
expect(results.length);
for (var i = 0; i < results.length; ++i) {
//equals((results[i]), '', 'debug');
var functions = p.guessAnonymousFunctions(results[i]);
// equals(functions.join("\n"), '', 'debug contents of stack');
equals(functions[2].indexOf('f2'), 0, 'guessed f2 in ' + functions[2]);
}
});
// Test for issue #34
test("guessAnonymousFunctions chrome with eval", function() {
var unit = impl();
var expected = '{anonymous}()@eval at buildTmplFn (http://domain.com/file.js:17:10)';
var actual = unit.guessAnonymousFunctions([expected]);
expect(1);
// Nothing should change since no anonymous function in stack
equals(expected, actual);
});
test("guessAnonymousFunctions opera9", function() {
var results = [];
var p = impl();
var file = 'http://' + window.location.hostname + '/file.js';
p.sourceCache[file] = ['var f2 = function() {', 'bar();', '};'];
results.push(['{anonymous}()@' + file + ':2 -- bar();']);
var f2 = function() {
try {
this.undef();
} catch (e) {
if (p.mode(e) == 'opera9') {
results.push(p.run(e));
}
}
};
f2();
expect(results.length * 1);
for (var i = 0; i < results.length; ++i) {
//equals((results[i]), '', 'debug');
var functions = p.guessAnonymousFunctions(results[i]);
//equals(functions, '', 'debug');
equals(functions[0].indexOf('f2()'), 0, 'guessed f2 in ' + functions[0]);
}
});
test("guessAnonymousFunctions opera10", function() {
// FIXME: currently failing in Opera 10.60
var results = [];
var p = impl();
var file = 'http://' + window.location.hostname + '/file.js';
p.sourceCache[file] = ['var f2 = function() {', 'var b = 2;', '};'];
results.push(["{anonymous}()@" + file + ":1:1", "{anonymous}()@" + file + ":1:1"]);
var f2 = function() {
try {
this.undef();
} catch (e) {
if (p.mode(e) == 'opera10') {
//alert("e.message: " + e.message);
results.push(p.run());
}
}
};
f2();
expect(results.length * 1);
for (var i = 0; i < results.length; ++i) {
//equals((results[i]), '', 'debug');
var functions = p.guessAnonymousFunctions(results[i]);
//equals(functions.join("\n"), '', 'debug');
equals(functions[1].indexOf('f2()'), 0, 'guessed f2 in ' + functions[1]);
}
});
test("guessAnonymousFunctions opera11", function() {
var results = [];
var p = impl();
var file = 'http://' + window.location.hostname + '/file.js';
p.sourceCache[file] = ['var f2 = function() {', 'bar();', '};'];
results.push(["{anonymous}()@" + file + ":2:1 -- bar();"]);
var f2 = function() {
try {
this.undef();
} catch (e) {
if (p.mode(e) == 'opera11') {
results.push(p.run(e));
}
}
};
f2();
expect(results.length * 1);
for (var i = 0; i < results.length; ++i) {
//equals((results[i]), '', 'debug');
var functions = p.guessAnonymousFunctions(results[i]);
//equals(functions.join("\n"), '', 'debug');
equals(functions[0].indexOf('f2()'), 0, 'guessed f2 in ' + functions[0]);
}
});
test("guessAnonymousFunctions other", function() {
var results = [];
var p = impl();
var file = 'http://' + window.location.hostname + '/file.js';
p.sourceCache[file] = ['var f2 = function() {', 'var b = 2;', '};'];
results.push(['{anonymous}()']);
(function f2() {
try {
this.undef();
} catch (e) {
if (p.mode(e) == 'other') {
results.push(p.run());
}
}
})();
expect(results.length);
for (var i = 0; i < results.length; ++i) {
//equals((results[i]), '', 'debug');
equals(p.guessAnonymousFunctions(results[i])[0].indexOf('{anonymous}'), 0, 'no file and line number in "other" mode');
}
});
//endregion
})(window, document);

View file

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Exception Lab</title>
<style>
#info {
height: 25em;
width: 100%;
}
</style>
<script src="../../stacktrace.js"></script>
<script src="ExceptionLab.js"></script>
<script>
var lastException;
function info(text) {
document.getElementById("info").innerHTML = text;
}
function detectMode() {
var p = new printStackTrace.implementation(), mode = p.mode(lastException);
info('Stack tracing mode: ' + mode);
}
function dumpStacktrace(guess) {
var trace = printStackTrace({
e: lastException,
guess: guess
});
info(trace.join("\n\n"));
}
function dumpException(ex) {
var text = "{\n " + ExceptionLab.getExceptionProps(ex).join(",\n ") + "\n}";
info(text);
lastException = ex;
}
function dumpExceptionError() {
dumpException(new Error("Default error"));
}
function dumpExceptionAnonymous() {
dumpException((function(x) {
try {
x.undef();
return null;
} catch (ex) {
return ex;
}
})(null));
}
function dumpExceptionInEval() {
try {
//eval("x.undef()");
eval("getExceptionProps()");
} catch (ex) {
dumpException(ex);
}
}
function dumpExceptionMultiLine() {
var fn = function() {
return {
name: "provide multi-line message in exception"
};
};
try {
fn.nonExistentMethod();
} catch (ex) {
dumpException(ex);
}
}
function dumpDOMException() {
try {
document.body.removeChild(document.createElement('div'));
} catch (ex) {
dumpException(ex);
}
}
function dumpCallChain() {
dumpException({message: 'Error'});
try {
dumpStacktrace();
} catch (ex) {
info('Exception while processing stack trace:\n' + ex);
}
}
function dumpCallChainInStrictMode() {
"use strict";
dumpCallChain();
}
</script>
</head>
<body>
<span id="userAgent">userAgent</span>
<script>
document.getElementById("userAgent").innerHTML = navigator.userAgent;
</script>
<textarea id="info">Info</textarea>
<br />
<button onclick="dumpExceptionError();" title="new Error()">Exception 1</button>
<button onclick="dumpExceptionAnonymous();" title="Stack with anonymous function">Exception 2</button>
<button onclick="dumpExceptionInEval();" title="Exception in eval()">Exception 3</button>
<button onclick="dumpExceptionMultiLine();" title="Multi-line message in exception">Exception 4</button>
<button onclick="dumpDOMException();" title="DOM exception (without stack in Firefox)">Exception 5</button>
<br />
<button onclick="dumpCallChain();" title="Process call chain">Call chain</button>
<button onclick="dumpCallChainInStrictMode();" title="Process call chain in strict mode">Call chain in strict mode
</button>
<br />
<button onclick="detectMode();">Detect mode</button>
<button onclick="dumpStacktrace();">Process stack trace</button>
<button onclick="dumpStacktrace(true);">Guess anonymous functions</button>
</body>
</html>

View file

@ -0,0 +1,69 @@
/*global module, exports, define*/
(function(global) {
function createException() {
return ((function(x) {
try {
x.undef();
return x;
} catch (ex) {
return ex;
}
})(null));
}
function printProp(prop, value) {
if (typeof value === "string") {
value = '"' + value.replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, '\\n" +\n "') + '"';
}
return prop + ': ' + value;
}
function getExceptionProps(ex) {
/*jshint forin:false*/
var prop, props = [], exceptionPropertyNames = {
message: true,
name: true,
stack: true,
stacktrace: true,
'arguments': true,
type: true
};
// find all (including non-enumerable) own properties
if (typeof Object.getOwnPropertyNames === "function") {
var ownPropertyNames = Object.getOwnPropertyNames(ex);
for (var i = 0; i < ownPropertyNames.length; i++) {
exceptionPropertyNames[ownPropertyNames[i]] = true;
}
}
// find own and inherited enumerable properties
for (prop in ex) {
exceptionPropertyNames[prop] = true;
}
for (prop in exceptionPropertyNames) {
var value = ex[prop];
if (typeof value !== "undefined") {
props.push(printProp(prop, value));
}
}
return props;
}
var api = {
createException: createException,
getExceptionProps: getExceptionProps
};
if (typeof exports === 'object') {
// Node
module.exports = api;
} else if (typeof define === 'function' && define.amd) {
// AMD
define(api);
} else {
// Browser globals
global.ExceptionLab = api;
}
}(this));

View file

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>stacktrace.js functional tests</title>
<style>li{font-size:14px;font-weight:bold}</style>
</head>
<body>
<h3><a href="https://github.com/eriwen">eriwen</a> / <strong><a href="https://github.com/eriwen/javascript-stacktrace">stacktrace.js</a></strong></h3>
<ul>
<li>Just include stacktrace.js file on your page, and call it like so:</li>
</ul>
<code>
<pre>&lt;script type="text/javascript" src="path/to/stacktrace.js" /&gt;
&lt;script type="text/javascript"&gt;
... your code ...
if (errorCondition) {
var trace = printStackTrace();
//Output however you want!
alert(trace.join('\n\n'));
}
... more code of yours ...
&lt;/script&gt;</pre>
</code>
<p>Tested in <a href="testcase1.html" target="_blank">No-options test</a></p>
<ul>
<li>You can also pass in your own Error to get a stacktrace:</li>
</ul>
<code>
<pre>&lt;script type="text/javascript"&gt;
var lastError;
try {
// error producing code
} catch(e) {
lastError = e;
// do something else with error
}
// Returns stacktrace from lastError!
printStackTrace({e: lastError});
&lt;/script&gt;</pre>
</code>
<p>Tested in <a href="testcase2.html" target="_blank">passing error test</a></p>
<ul>
<li>Some people recommend just assigning it to window.onerror (Only in IE and FF):</li>
</ul>
<code>
<pre>window.onerror = function(msg, file, line) {
alert(printStackTrace().join('\n\n'));
}</pre>
</code>
<p>Tested in <a href="testcase3.html" target="_blank">window.onerror test</a></p>
<ul>
<li>You can now have any (public or privileged) function give you a stacktrace when it is called:</li>
</ul>
<code>
<pre>var p = new printStackTrace.implementation();
p.instrumentFunction(this, 'bar', logStackTrace);
function logStackTrace(stack) {
console.log(stack.join('\n'));
} function foo() {
var a = 1;
bar();
}
function bar() {
baz();
}
foo(); //Will log a stacktrace when 'bar()' is called containing 'foo()'!
p.deinstrumentFunction(this, 'bar'); //Remove function instrumentation</pre>
</code>
<p>Tested in <a href="testcase4.html" target="_blank">function instrumentation test</a></p>
</body>
</html>

View file

@ -0,0 +1,18 @@
function toList(array) {
return "<ol><li>" + (array.join("</li><li>")) + "</li></ol>";
}
function printTrace(trace) {
var output = document.getElementById("output");
if (!output) {
output = document.createElement("div");
output.id = "output";
document.body.appendChild(output);
}
var content = [];
content.push(toList(trace));
content.push("--------------Expected:-------------------");
content.push(toList(window.expected || []));
output.innerHTML = (content.join("<br/>"));
}

View file

@ -0,0 +1,46 @@
/*global require, console*/
var ExceptionLab = require("./ExceptionLab");
var printStackTrace = require("../../stacktrace");
var lastException;
function info(text) {
console.log(text);
}
function dumpStacktrace(guess) {
var trace = printStackTrace({
e: lastException,
guess: guess
});
info(trace.join("\n"));
}
function dumpException(ex) {
var text = "{\n " + ExceptionLab.getExceptionProps(ex).join(",\n ") + "\n}";
info(text);
//info(ex.arguments);
lastException = ex;
}
function dumpExceptionMultiLine() {
var fn = function() {
return {
name: "provide multi-line message in exception"
};
};
try {
fn.nonExistentMethod();
} catch (ex) {
dumpException(ex);
}
}
info("Exception properties:");
dumpExceptionMultiLine();
var p = new printStackTrace.implementation();
info("\nException mode: " + p.mode(lastException));
info("\nException stack trace:");
dumpStacktrace();

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>No-options test</title>
<script type="text/javascript" src="../../stacktrace.js"></script>
<script type="text/javascript" src="testCommon.js"></script>
</head>
<body>
<div id="output"></div>
<script type="text/javascript">
function bar(n) {
if (n < 2) {
printTrace(printStackTrace());
return;
}
bar(n - 1);
}
function foo() {
eval("bar(2)");
}
var expected = ["bar(1)", "bar(2)", "foo()", "foo()"];
foo();
</script>
</body>
</html>

View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>passing error test</title>
<script type="text/javascript" src="../../stacktrace.js"></script>
<script type="text/javascript" src="testCommon.js"></script>
</head>
<body>
<div id="output"></div>
<script type="text/javascript">
function bar(n) {
if (n < 2) {
window.abc();
}
bar(n - 1);
}
function foo() {
bar(2);
}
var expected=["bar(1)", "bar(2)", "foo()"];
var lastError;
try {
foo();
} catch (e) {
lastError = e;
printTrace(printStackTrace({e:lastError}));
}
</script>
</body>
</html>

View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>window.onerror test</title>
<script type="text/javascript" src="../../stacktrace.js"></script>
<script type="text/javascript" src="testCommon.js"></script>
</head>
<body>
<div id="output"></div>
<script type="text/javascript">
function bar(n) {
if (n < 2) {
window.abc();
}
bar(n - 1);
}
function foo() {
bar(2);
}
window.onerror = function(message, filename, lineno, colno, error) {
printTrace(window.printStackTrace());
var content = ["--------------Arguments:------------------", "message: " + message, "filename: " + filename, "lineno: " + lineno, "colno: " + colno, "error: " + error];
var output = document.getElementById("output");
output.innerHTML += (content.join("<br/>"));
return true;
};
expected = ["bar(1)", "bar(2)", "foo()"];
foo();
</script>
</body>
</html>

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<title>function instrumentation test</title>
<script type="text/javascript" src="../../stacktrace.js"></script>
<script type="text/javascript" src="testCommon.js"></script>
</head>
<body>
<div id="output"></div>
<script type="text/javascript">
function baz() {
var c = 3;
}
function bar() {
var b = 2;
baz();
}
var foo = function() {
var a = 1;
bar();
};
var p = new window.printStackTrace.implementation();
p.instrumentFunction(this, 'baz', printTrace);
expected = ["bar()", "foo()"];
foo();
p.deinstrumentFunction(this, 'bar');
</script>
</body>
</html>

View file

@ -0,0 +1,29 @@
<html>
<head>
<title>Issue #27</title>
<script src="../../stacktrace.js"></script>
<script>
window.onerror = function(errorMsg, url, lineNumber) {
alert(errorMsg + "\nURL: " + url + "\nLine number: " + lineNumber);
// Previous line gets executed
var trace;
try {
// Firefox enters printStackTrace. Leaves silently somewhere in there
trace = printStackTrace().join('\n\n');
// Execution does not arrive here (as evidenced in debugger)
} catch (e) {
// nothing gets thrown/caught in Firefox
// error console empty (the original uncaught exception is logged)
}
// Execution does not arrive here
// (as evidenced in debugger and actual browser window)
alert(trace);
return true;
};
// Somewhere error gets thrown
throw 'An error occurred';
</script>
</head>
<body></body>
</html>

View file

@ -0,0 +1,28 @@
<html>
<head>
<title>Issue #27</title>
<script src="../../stacktrace.js"></script>
<script>
/*function printStackTraceOnError(msg, file, line) {
alert(printStackTrace().join('\n\n'));
}
if (window.addEventListener) {
window.addEventListener('error', printStackTraceOnError, false);
} else if (window.attachEvent) {
window.attachEvent('onerror', printStackTraceOnError);
} else {
window.onerror = printStackTraceOnError;
}*/
window.onerror = function(msg, file, line) {
alert(printStackTrace().join('\n\n'));
};
function test() {
x += 1;
}
</script>
</head>
<body onload="test();">
<button onclick="test();">test</button>
</body>
</html>

View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<head>
<title>Issue #32</title>
<script src="../../stacktrace.js"></script>
<script>
window.onbeforeunload = function(e) {
e = e || window.event;
// FF: currentTarget originalTarget target
// IE: currentTarget srcElement target
// Chrome: currentTarget srcElement target
// capture stack trace
// NOTE:
// FF captures full stack,
// IE captures stack but doesn't include 'onbeforeunload' function in stack,
// CHROME doesn't show alert
var trace = printStackTrace().join('\n\n');
document.getElementById("stacktrace").value = trace;
alert('window.onbeforeunload stacktrace:\n\n' + trace);
var msg = 'Stacktrace:\n\n' + trace;
// For IE and Firefox prior to version 4
if (e) {
e.returnValue = msg;
}
// For Safari
return msg;
};
</script>
</head>
<body>
To invoke onbeforeunload event:
<ul>
<li>Close the current window.</li>
<li>Navigate to another location by entering a new address or selecting a Favorite.</li>
<li>Click an anchor that refers to another document.</li>
<li>Invoke the anchor.click method.</li>
<li>Invoke the document.write method.</li>
<li>Invoke the document.close method.</li>
<li>Invoke the window.close method.</li>
<li>Invoke the window.navigate or NavigateAndFind method.</li>
<li>Invoke the location.replace method.</li>
<li>Invoke the location.reload method.</li>
<li>Specify a new value for the location.href property.</li>
<li>Submit a form to the address specified in the action attribute via the input type=submit control, or invoke the form.submit method.</li>
<li>Invoke the window.open method, providing the possible value _self for the window name.</li>
<li>Invoke the document.open method.</li>
<li>Click the Back, Forward, Refresh, or Home button.</li>
</ul>
<textarea id="stacktrace" style="width:100%; height:10em;">stacktrace</textarea>
</body>
</html>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>Issue #55</title>
<script src="../../stacktrace.js"></script>
</head>
<body>
<pre id="trace"></pre>
<script>
try {
a.b.c();
} catch (e) {
var trace = printStackTrace().join('\n\n');
if (window.console && window.console.log) {
console.log(trace);
}
//alert(trace);
document.getElementById('trace').innerHTML = trace;
}
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
server: http://localhost:4224
load:
- lib/qunit.js
- lib/equiv.js
- lib/sinon-1.2.0.js
- lib/sinon-qunit-1.0.0.js
- lib/QUnitAdapter.js
- ../stacktrace.js
- CapturedExceptions.js
test:
- TestStacktrace.js
plugin:
- name: "coverage"
jar: "lib/plugins/coverage.jar"
module: "com.google.jstestdriver.coverage.CoverageModule"
args: useCoberturaFormat
timeout: 120

View file

@ -0,0 +1,85 @@
/*
QUnitAdapter
Version: 1.1.0
Run qunit tests using JS Test Driver
This provides almost the same api as qunit.
Tests must run sychronously, which means no use of stop and start methods.
You can use jsUnit Clock object to deal with timeouts and intervals:
http://googletesting.blogspot.com/2007/03/javascript-simulating-time-in-jsunit.html
The qunit #main DOM element is not included. If you need to do any DOM manipulation
you need to set it up and tear it down in each test.
*/
(function() {
if(!(window.equiv)) {
throw new Error("QUnitAdapter.js - Unable to find equiv function. Ensure you have added equiv.js to the load section of your jsTestDriver.conf");
}
var QUnitTestCase;
window.module = function(name, lifecycle) {
QUnitTestCase = TestCase(name);
QUnitTestCase.prototype.lifecycle = lifecycle || {};
};
window.test = function(name, expected, test) {
QUnitTestCase.prototype['test ' + name] = function() {
if(this.lifecycle.setup) {
this.lifecycle.setup();
}
if(expected.constructor === Number) {
expectAsserts(expected);
} else {
test = expected;
}
test.call(this.lifecycle);
if(this.lifecycle.teardown) {
this.lifecycle.teardown();
}
};
};
window.expect = function(count) {
expectAsserts(count);
};
window.ok = function(actual, msg) {
assertTrue(msg ? msg : '', !!actual);
};
window.equals = function(a, b, msg) {
assertEquals(msg ? msg : '', b, a);
};
window.start = window.stop = function() {
fail('start and stop methods are not available when using JS Test Driver.\n' +
'Use jsUnit Clock object to deal with timeouts and intervals:\n' +
'http://googletesting.blogspot.com/2007/03/javascript-simulating-time-in-jsunit.html.');
};
window.same = function(a, b, msg) {
assertTrue(msg ? msg : '', window.equiv(b, a));
};
window.reset = function() {
fail('reset method is not available when using JS Test Driver');
};
window.isLocal = function() {
return false;
};
window.QUnit = {
equiv: window.equiv,
ok: window.ok
};
module('Default Module');
})();

View file

@ -0,0 +1,185 @@
// Tests for equality any JavaScript type and structure without unexpected results.
// Discussions and reference: http://philrathe.com/articles/equiv
// Test suites: http://philrathe.com/tests/equiv
// Author: Philippe RathŽ <prathe@gmail.com>
window.equiv = function () {
var innerEquiv; // the real equiv function
var callers = []; // stack to decide between skip/abort functions
// Determine what is o.
function hoozit(o) {
if (typeof o === "string") {
return "string";
} else if (typeof o === "boolean") {
return "boolean";
} else if (typeof o === "number") {
if (isNaN(o)) {
return "nan";
} else {
return "number";
}
} else if (typeof o === "undefined") {
return "undefined";
// consider: typeof null === object
} else if (o === null) {
return "null";
// consider: typeof [] === object
} else if (o instanceof Array) {
return "array";
// consider: typeof new Date() === object
} else if (o instanceof Date) {
return "date";
// consider: /./ instanceof Object;
// /./ instanceof RegExp;
// typeof /./ === "function"; // => false in IE and Opera,
// true in FF and Safari
} else if (o instanceof RegExp) {
return "regexp";
} else if (typeof o === "object") {
return "object";
} else if (o instanceof Function) {
return "function";
}
}
// Call the o related callback with the given arguments.
function bindCallbacks(o, callbacks, args) {
var prop = hoozit(o);
if (prop) {
if (hoozit(callbacks[prop]) === "function") {
return callbacks[prop].apply(callbacks, args);
} else {
return callbacks[prop]; // or undefined
}
}
}
var callbacks = function () {
// for string, boolean, number and null
function useStrictEquality(b, a) {
return a === b;
}
return {
"string": useStrictEquality,
"boolean": useStrictEquality,
"number": useStrictEquality,
"null": useStrictEquality,
"undefined": useStrictEquality,
"nan": function (b) {
return isNaN(b);
},
"date": function (b, a) {
return hoozit(b) === "date" && a.valueOf() === b.valueOf();
},
"regexp": function (b, a) {
return hoozit(b) === "regexp" &&
a.source === b.source && // the regex itself
a.global === b.global && // and its modifers (gmi) ...
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline;
},
// - skip when the property is a method of an instance (OOP)
// - abort otherwise,
// initial === would have catch identical references anyway
"function": function () {
var caller = callers[callers.length - 1];
return caller !== Object &&
typeof caller !== "undefined";
},
"array": function (b, a) {
var i;
var len;
// b could be an object literal here
if ( ! (hoozit(b) === "array")) {
return false;
}
len = a.length;
if (len !== b.length) { // safe and faster
return false;
}
for (i = 0; i < len; i++) {
if( ! innerEquiv(a[i], b[i])) {
return false;
}
}
return true;
},
"object": function (b, a) {
var i;
var eq = true; // unless we can proove it
var aProperties = [], bProperties = []; // collection of strings
// comparing constructors is more strict than using instanceof
if ( a.constructor !== b.constructor) {
return false;
}
// stack constructor before traversing properties
callers.push(a.constructor);
for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
aProperties.push(i); // collect a's properties
if ( ! innerEquiv(a[i], b[i])) {
eq = false;
}
}
callers.pop(); // unstack, we are done
for (i in b) {
bProperties.push(i); // collect b's properties
}
// Ensures identical properties name
return eq && innerEquiv(aProperties.sort(), bProperties.sort());
}
};
}();
innerEquiv = function () { // can take multiple arguments
var args = Array.prototype.slice.apply(arguments);
if (args.length < 2) {
return true; // end transition
}
return (function (a, b) {
if (a === b) {
return true; // catch the most you can
} else if (typeof a !== typeof b || a === null || b === null || typeof a === "undefined" || typeof b === "undefined") {
return false; // don't lose time with error prone cases
} else {
return bindCallbacks(a, callbacks, [b, a]);
}
// apply transition with (1..n) arguments
})(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
};
return innerEquiv;
}(); // equiv

View file

@ -0,0 +1,354 @@
#!/usr/bin/env python
# Copyright 2011-2012 Eric Wendelin
#
# This is free software, licensed under the Apache License, Version 2.0,
# available in the accompanying LICENSE.txt file.
"""
Converts lcov line coverage output to Cobertura-compatible XML for CI
"""
import re, sys, os, time
from xml.dom import minidom
from optparse import OptionParser
VERSION = '1.2'
__all__ = ['LcovCobertura']
class LcovCobertura(object):
"""
Converts code coverage report files in lcov format to Cobertura's XML
report format so that CI servers like Jenkins can aggregate results and
determine build stability etc.
>>> from lcov_cobertura import LcovCobertura
>>> LCOV_INPUT = 'your lcov input'
>>> converter = LcovCobertura(LCOV_INPUT)
>>> cobertura_xml = converter.convert()
>>> print cobertura_xml
"""
def __init__(self, lcov_data, base_dir='.', excludes=None):
"""
Create a new :class:`LcovCobertura` object using the given `lcov_data`
and `options`.
:param lcov_data: Path to LCOV data file
:type lcov_data: string
:param base_dir: Path upon which to base all sources
:type base_dir: string
:param excludes: list of regexes to packages as excluded
:type excludes: [string]
"""
if not excludes:
excludes = []
self.lcov_data = lcov_data
self.base_dir = base_dir
self.excludes = excludes
def convert(self):
"""
Convert lcov file to cobertura XML using options from this instance.
"""
coverage_data = self.parse()
return self.generate_cobertura_xml(coverage_data)
def parse(self):
"""
Generate a data structure representing it that can be serialized in any
logical format.
"""
coverage_data = {
'packages': {},
'summary': {'lines-total': 0, 'lines-covered': 0,
'branches-total': 0, 'branches-covered': 0},
'timestamp': str(int(time.time()))
}
package = None
current_file = None
file_lines_total = 0
file_lines_covered = 0
file_lines = {}
file_methods = {}
file_branches_total = 0
file_branches_covered = 0
for line in self.lcov_data.split('\n'):
if line.strip() == 'end_of_record':
if current_file is not None:
package_dict = coverage_data['packages'][package]
package_dict['lines-total'] += file_lines_total
package_dict['lines-covered'] += file_lines_covered
package_dict['branches-total'] += file_branches_total
package_dict['branches-covered'] += file_branches_covered
file_dict = package_dict['classes'][current_file]
file_dict['lines-total'] = file_lines_total
file_dict['lines-covered'] = file_lines_covered
file_dict['lines'] = dict(file_lines)
file_dict['methods'] = dict(file_methods)
file_dict['branches-total'] = file_branches_total
file_dict['branches-covered'] = file_branches_covered
coverage_data['summary']['lines-total'] += file_lines_total
coverage_data['summary']['lines-covered'] += file_lines_covered
coverage_data['summary']['branches-total'] += file_branches_total
coverage_data['summary']['branches-covered'] += file_branches_covered
line_parts = line.split(':')
input_type = line_parts[0]
if input_type == 'SF':
# Get file name
file_name = line_parts[-1].strip()
relative_file_name = os.path.relpath(file_name, self.base_dir)
package = '.'.join(relative_file_name.split(os.path.sep)[0:-1])
class_name = file_name.split(os.path.sep)[-1]
if package not in coverage_data['packages']:
coverage_data['packages'][package] = {
'classes': {}, 'lines-total': 0, 'lines-covered': 0,
'branches-total': 0, 'branches-covered': 0
}
coverage_data['packages'][package]['classes'][
relative_file_name] = {
'name': class_name, 'lines': {}, 'lines-total': 0,
'lines-covered': 0, 'branches-total': 0,
'branches-covered': 0
}
package = package
current_file = relative_file_name
file_lines_total = 0
file_lines_covered = 0
file_lines.clear()
file_methods.clear()
file_branches_total = 0
file_branches_covered = 0
elif input_type == 'DA':
# DA:2,0
(line_number, line_hits) = line_parts[-1].strip().split(',')
line_number = int(line_number)
if line_number not in file_lines:
file_lines[line_number] = {
'branch': 'false', 'branches-total': 0,
'branches-covered': 0
}
file_lines[line_number]['hits'] = line_hits
# Increment lines total/covered for class and package
if int(line_hits) > 0:
file_lines_covered += 1
file_lines_total += 1
elif input_type == 'BRDA':
# BRDA:1,1,2,0
(line_number, block_number, branch_number, branch_hits) = line_parts[-1].strip().split(',')
line_number = int(line_number)
if line_number not in file_lines:
file_lines[line_number] = {
'branch': 'true', 'branches-total': 0,
'branches-covered': 0, 'hits': 0
}
file_lines[line_number]['branch'] = 'true'
file_lines[line_number]['branches-total'] += 1
file_branches_total += 1
if branch_hits != '-' and int(branch_hits) > 0:
file_lines[line_number]['branches-covered'] += 1
file_branches_covered += 1
elif input_type == 'BRF':
file_branches_total = int(line_parts[1])
elif input_type == 'BRH':
file_branches_covered = int(line_parts[1])
elif input_type == 'FN':
# FN:5,(anonymous_1)
function_name = line_parts[-1].strip().split(',')[1]
file_methods[function_name] = '0'
elif input_type == 'FNDA':
# FNDA:0,(anonymous_1)
(function_hits, function_name) = line_parts[-1].strip().split(',')
file_methods[function_name] = function_hits
# Exclude packages
excluded = [x for x in coverage_data['packages'] for e in self.excludes
if re.match(e, x)]
for package in excluded:
del coverage_data['packages'][package]
# Compute line coverage rates
for package_data in list(coverage_data['packages'].values()):
package_data['line-rate'] = self._percent(
package_data['lines-total'],
package_data['lines-covered'])
package_data['branch-rate'] = self._percent(
package_data['branches-total'],
package_data['branches-covered'])
return coverage_data
def generate_cobertura_xml(self, coverage_data):
"""
Given parsed coverage data, return a String cobertura XML representation.
:param coverage_data: Nested dict representing coverage information.
:type coverage_data: dict
"""
dom_impl = minidom.getDOMImplementation()
doctype = dom_impl.createDocumentType("coverage", None,
"http://cobertura.sourceforge.net/xml/coverage-03.dtd")
document = dom_impl.createDocument(None, "coverage", doctype)
root = document.documentElement
summary = coverage_data['summary']
self._attrs(root, {
'branch-rate': self._percent(summary['branches-total'],
summary['branches-covered']),
'branches-covered': str(summary['branches-covered']),
'branches-valid': str(summary['branches-total']),
'complexity': '0',
'line-rate': self._percent(summary['lines-total'],
summary['lines-covered']),
'lines-valid': str(summary['lines-total']),
'timestamp': coverage_data['timestamp'],
'version': '1.9'
})
sources = self._el(document, 'sources', {})
root.appendChild(sources)
packages_el = self._el(document, 'packages', {})
packages = coverage_data['packages']
for package_name, package_data in list(packages.items()):
package_el = self._el(document, 'package', {
'line-rate': package_data['line-rate'],
'branch-rate': package_data['branch-rate'],
'name': package_name
})
classes_el = self._el(document, 'classes', {})
for class_name, class_data in list(package_data['classes'].items()):
class_el = self._el(document, 'class', {
'branch-rate': self._percent(class_data['branches-total'],
class_data['branches-covered']),
'complexity': '0',
'filename': class_name,
'line-rate': self._percent(class_data['lines-total'],
class_data['lines-covered']),
'name': class_data['name']
})
# Process methods
methods_el = self._el(document, 'methods', {})
for method_name, hits in list(class_data['methods'].items()):
method_el = self._el(document, 'method', {
'name': method_name,
'hits': hits
})
methods_el.appendChild(method_el)
# Process lines
lines_el = self._el(document, 'lines', {})
lines = list(class_data['lines'].keys())
lines.sort()
for line_number in lines:
line_el = self._el(document, 'line', {
'branch': class_data['lines'][line_number]['branch'],
'hits': str(class_data['lines'][line_number]['hits']),
'number': str(line_number)
})
if class_data['lines'][line_number]['branch'] == 'true':
total = int(class_data['lines'][line_number]['branches-total'])
covered = int(class_data['lines'][line_number]['branches-covered'])
percentage = int((covered * 100.0) / total)
line_el.setAttribute('condition-coverage',
'{0}% ({1}/{2})'.format(
percentage, covered, total))
lines_el.appendChild(line_el)
class_el.appendChild(methods_el)
class_el.appendChild(lines_el)
classes_el.appendChild(class_el)
package_el.appendChild(classes_el)
packages_el.appendChild(package_el)
root.appendChild(packages_el)
return document.toprettyxml()
def _el(self, document, name, attrs):
"""
Create an element within document with given name and attributes.
:param document: Document element
:type document: Document
:param name: Element name
:type name: string
:param attrs: Attributes for element
:type attrs: dict
"""
return self._attrs(document.createElement(name), attrs)
def _attrs(self, element, attrs):
"""
Set attributes on given element.
:param element: DOM Element
:type element: Element
:param attrs: Attributes for element
:type attrs: dict
"""
for attr, val in list(attrs.items()):
element.setAttribute(attr, val)
return element
def _percent(self, lines_total, lines_covered):
"""
Get the percentage of lines covered in the total, with formatting.
:param lines_total: Total number of lines in given module
:type lines_total: number
:param lines_covered: Number of lines covered by tests in module
:type lines_covered: number
"""
if lines_total == 0:
return '0.0'
return str(float(float(lines_covered) / float(lines_total)))
if __name__ == '__main__':
def main(argv):
"""
Converts LCOV coverage data to Cobertura-compatible XML for reporting.
Usage:
lcov_cobertura.py lcov-file.dat
lcov_cobertura.py lcov-file.dat -b src/dir -e test.lib -o path/out.xml
By default, XML output will be written to ./coverage.xml
"""
parser = OptionParser()
parser.usage = 'lcov_cobertura.py lcov-file.dat [-b source/dir] [-e <exclude packages regex>] [-o output.xml]'
parser.description = 'Converts lcov output to cobertura-compatible XML'
parser.add_option('-b', '--base-dir', action='store',
help='Directory where source files are located',
dest='base_dir', default='.')
parser.add_option('-e', '--excludes',
help='Comma-separated list of regexes of packages to exclude',
action='append', dest='excludes', default=[])
parser.add_option('-o', '--output',
help='Path to store cobertura xml file',
action='store', dest='output', default='coverage.xml')
(options, args) = parser.parse_args(args=argv)
if len(args) != 2:
print((main.__doc__))
sys.exit(1)
try:
with open(args[1], 'r') as lcov_file:
lcov_data = lcov_file.read()
lcov_cobertura = LcovCobertura(lcov_data, options.base_dir, options.excludes)
cobertura_xml = lcov_cobertura.convert()
with open(options.output, mode='wt') as output_file:
output_file.write(cobertura_xml)
except IOError:
sys.stderr.write("Unable to convert %s to Cobertura XML" % args[1])
main(sys.argv)

View file

@ -0,0 +1,84 @@
/**
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timout is 3s
start = new Date().getTime(),
condition = false,
interval = setInterval(function() {
if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
// If not time-out yet and condition not yet fulfilled
condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
} else {
if(!condition) {
// If condition still not fulfilled (timeout but condition is 'false')
console.log("'waitFor()' timeout");
phantom.exit(1);
} else {
// Condition fulfilled (timeout and/or condition is 'true')
console.debug("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 100);
};
if (phantom.args.length < 1) {
console.log('Usage: phantomjs-test-runner.js [-j|--junit] <some URL>');
phantom.exit();
} else {
var args = phantom.args.slice(),
url = args.pop();
window.console.debug = function() {};
phantom.outputFormat = 'console';
if (args.length) {
var arg = args.pop().toLowerCase();
switch (arg) {
case "-j":
case "--junit":
phantom.outputFormat = 'junit';
break;
default:
}
}
}
var page = new WebPage();
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.open(url, function(status) {
if (status !== "success") {
console.log('Unable to access network');
phantom.exit();
} else {
waitFor(function() {
return page.evaluate(function() {
var el = document.getElementById('qunit-testresult');
if (el && el.innerText.match('completed')) {
return true;
}
return false;
});
}, function() {
var failedNum = page.evaluate(function() {
var el = document.getElementById('qunit-testresult');
try {
return el.getElementsByClassName('failed')[0].innerHTML;
} catch (e) { }
return 10000;
});
phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0);
});
}
});

View file

@ -0,0 +1,38 @@
/*global QUnit */
// global variable - test results for BrowserScope
var _bTestResults = {};
(function() {
var testKey = 'agt1YS1wcm9maWxlcnINCxIEVGVzdBjr68MRDA';
var callbackName = "showBrowserScopeResults";
// Add URL option in QUnit to toggle publishing results to BrowserScope.org
QUnit.config.urlConfig.push("publish");
QUnit.config.testTimeout = 1000; // Timeout for async tests
// Build-up the test results beacon for BrowserScope.org
QUnit.testDone(function(test) {
// make sure all assertions passed successfully
if (!test.failed && test.total === test.passed) {
_bTestResults[test.name] = 1;
} else {
_bTestResults[test.name] = 0;
}
});
// If the user agreed to publish results to BrowserScope.org, go for it!
QUnit.done(function(result) {
if (QUnit.config.publish) {
var newScript = document.createElement('script');
newScript.src = 'http://www.browserscope.org/user/beacon/' + testKey + "?callback=" + callbackName;
var firstScript = document.getElementsByTagName('script')[0];
firstScript.parentNode.insertBefore(newScript, firstScript);
}
});
// Load the results widget from browserscope.org
window[callbackName] = function() {
var script = document.createElement('script');
script.src = "http://www.browserscope.org/user/tests/table/" + testKey + "?o=js";
document.body.appendChild(script);
};
}());

View file

@ -0,0 +1,35 @@
var module;
QUnit.moduleStart = function(context) {
module = context.name;
}
var current_test_assertions = [];
QUnit.testDone = function(result) {
var name = module + ": " + result.name;
if (result.failed) {
console.log("\u001B[31m✖ " + name);
for (var i = 0; i < current_test_assertions.length; i++) {
console.log(" " + current_test_assertions[i]);
}
console.log("\u001B[39m");
}
current_test_assertions = [];
};
QUnit.log = function(details) {
if (details.result) {
return;
}
var response = details.message || "";
if (details.expected) {
if (response) {
response += ", ";
}
response = "expected: " + details.expected + ", but was: " + details.actual;
}
current_test_assertions.push("Failed assertion: " + response);
};
QUnit.done = function(result) {
console.log("Took " + result.runtime + "ms to run " + result.total + " tests. \u001B[32m✔ " + result.passed + "\u001B[39m \u001B[31m✖ " + result.failed + "\u001B[39m ");
return result.failed > 0 ? 1 : 0;
};

View file

@ -0,0 +1,76 @@
/*global QUnit, console */
(function() {
var module, moduleStart, testStart, testCases = [], current_test_assertions = [];
console.log('<?xml version="1.0" encoding="UTF-8"?>');
console.log('<testsuites name="testsuites">');
QUnit.begin(function() {
// That does not work when invoked in PhantomJS
});
QUnit.moduleStart(function(context) {
// context = { name }
moduleStart = new Date();
module = context.name;
testCases = [];
});
QUnit.moduleDone(function(context) {
// context = { name, failed, passed, total }
var xml = '\t<testsuite name="' + context.name + '" errors="0" failures="' + context.failed + '" tests="' + context.total + '" time="' + (new Date() - moduleStart) / 1000 + '"';
if (testCases.length) {
xml += '>\n';
for (var i = 0, l = testCases.length; i < l; i++) {
xml += testCases[i];
}
xml += '\t</testsuite>';
} else {
xml += '/>\n';
}
console.log(xml);
});
QUnit.testStart(function() {
// context = { name }
testStart = new Date();
});
QUnit.testDone(function(result) {
// result = { name, failed, passed, total }
var xml = '\t\t<testcase classname="' + module + '" name="' + result.name + '" time="' + (new Date() - testStart) / 1000 + '"';
if (result.failed) {
xml += '>\n';
for (var i = 0; i < current_test_assertions.length; i++) {
xml += "\t\t\t" + current_test_assertions[i];
}
xml += '\t\t</testcase>\n';
} else {
xml += '/>\n';
}
current_test_assertions = [];
testCases.push(xml);
});
QUnit.log(function(details) {
// details = { result, actual, expected, message }
if (details.result) {
return;
}
var message = details.message || "";
if (details.expected) {
if (message) {
message += ", ";
}
message += "expected: " + details.expected + ", but was: " + details.actual;
}
var xml = '<failure type="failed" message="' + message + '"/>\n';
current_test_assertions.push(xml);
});
QUnit.done(function(result) {
// result = { failed, passed, total, runtime }
console.log('</testsuites>');
return result.failed > 0 ? 1 : 0;
});
}());

View file

@ -0,0 +1,226 @@
/**
* QUnit 1.2.0pre - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2011 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 15px 15px;
-moz-border-radius: 0 0 15px 15px;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
/**
* sinon-qunit 1.0.0, 2010/12/08
*
* @author Christian Johansen (christian@cjohansen.no)
*
* (The BSD License)
*
* Copyright (c) 2010-2011, Christian Johansen, christian@cjohansen.no
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Christian Johansen nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*global sinon, QUnit, test*/
sinon.assert.fail = function (msg) {
QUnit.ok(false, msg);
};
sinon.assert.pass = function (assertion) {
QUnit.ok(true, assertion);
};
sinon.config = {
injectIntoThis: true,
injectInto: null,
properties: ["spy", "stub", "mock", "clock", "sandbox"],
useFakeTimers: true,
useFakeServer: false
};
(function (global) {
var qTest = QUnit.test;
QUnit.test = global.test = function (testName, expected, callback, async) {
if (arguments.length === 2) {
callback = expected;
expected = null;
}
return qTest(testName, expected, sinon.test(callback), async);
};
}(this));