gusto 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/gusto +28 -0
- data/lib/HtmlReport.coffee +63 -0
- data/lib/Spec/DSL.coffee +119 -0
- data/lib/Spec/DelayedExpectation.coffee +61 -0
- data/lib/Spec/Matchers.coffee +69 -0
- data/lib/Spec/MethodStub/PossibleCall.coffee +81 -0
- data/lib/Spec/MethodStub.coffee +55 -0
- data/lib/Spec/MockObject.coffee +6 -0
- data/lib/Spec/ObjectDSL.coffee +26 -0
- data/lib/Spec/Report.coffee +23 -0
- data/lib/Spec/Suite.coffee +30 -0
- data/lib/Spec/Test.coffee +21 -0
- data/lib/Spec/Util.coffee +86 -0
- data/lib/Spec.coffee +62 -0
- data/lib/gusto/runner.rb +40 -0
- data/lib/gusto/server.rb +34 -0
- data/lib/gusto/version.rb +3 -0
- data/lib/gusto.rb +162 -0
- data/public/ie.js +5 -0
- data/public/jquery-1.5.2.js +8374 -0
- data/public/jsdiff.js +161 -0
- data/public/stacktrace.js +446 -0
- data/views/index.slim +19 -0
- metadata +180 -0
data/public/jsdiff.js
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
/*
|
2
|
+
* Javascript Diff Algorithm
|
3
|
+
* By John Resig (http://ejohn.org/)
|
4
|
+
* Modified by Chu Alan "sprite"
|
5
|
+
*
|
6
|
+
* Released under the MIT license.
|
7
|
+
*
|
8
|
+
* More Info:
|
9
|
+
* http://ejohn.org/projects/javascript-diff-algorithm/
|
10
|
+
*/
|
11
|
+
|
12
|
+
function escape(s) {
|
13
|
+
var n = s;
|
14
|
+
n = n.replace(/&/g, "&");
|
15
|
+
n = n.replace(/</g, "<");
|
16
|
+
n = n.replace(/>/g, ">");
|
17
|
+
n = n.replace(/"/g, """);
|
18
|
+
|
19
|
+
return n;
|
20
|
+
}
|
21
|
+
|
22
|
+
function diffString( o, n ) {
|
23
|
+
o = o.replace(/\s+$/, '');
|
24
|
+
n = n.replace(/\s+$/, '');
|
25
|
+
|
26
|
+
var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/) );
|
27
|
+
var str = "";
|
28
|
+
|
29
|
+
var oSpace = o.match(/\s+/g);
|
30
|
+
if (oSpace == null) {
|
31
|
+
oSpace = ["\n"];
|
32
|
+
} else {
|
33
|
+
oSpace.push("\n");
|
34
|
+
}
|
35
|
+
var nSpace = n.match(/\s+/g);
|
36
|
+
if (nSpace == null) {
|
37
|
+
nSpace = ["\n"];
|
38
|
+
} else {
|
39
|
+
nSpace.push("\n");
|
40
|
+
}
|
41
|
+
|
42
|
+
if (out.n.length == 0) {
|
43
|
+
for (var i = 0; i < out.o.length; i++) {
|
44
|
+
str += '<del>' + escape(out.o[i]) + oSpace[i] + "</del>";
|
45
|
+
}
|
46
|
+
} else {
|
47
|
+
if (out.n[0].text == null) {
|
48
|
+
for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
|
49
|
+
str += '<del>' + escape(out.o[n]) + oSpace[n] + "</del>";
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
for ( var i = 0; i < out.n.length; i++ ) {
|
54
|
+
if (out.n[i].text == null) {
|
55
|
+
str += '<ins>' + escape(out.n[i]) + nSpace[i] + "</ins>";
|
56
|
+
} else {
|
57
|
+
var pre = "";
|
58
|
+
|
59
|
+
for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
|
60
|
+
pre += '<del>' + escape(out.o[n]) + oSpace[n] + "</del>";
|
61
|
+
}
|
62
|
+
str += " " + out.n[i].text + nSpace[i] + pre;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
return str;
|
68
|
+
}
|
69
|
+
|
70
|
+
function randomColor() {
|
71
|
+
return "rgb(" + (Math.random() * 100) + "%, " +
|
72
|
+
(Math.random() * 100) + "%, " +
|
73
|
+
(Math.random() * 100) + "%)";
|
74
|
+
}
|
75
|
+
function diffString2( o, n ) {
|
76
|
+
o = o.replace(/\s+$/, '');
|
77
|
+
n = n.replace(/\s+$/, '');
|
78
|
+
|
79
|
+
var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/) );
|
80
|
+
|
81
|
+
var oSpace = o.match(/\s+/g);
|
82
|
+
if (oSpace == null) {
|
83
|
+
oSpace = ["\n"];
|
84
|
+
} else {
|
85
|
+
oSpace.push("\n");
|
86
|
+
}
|
87
|
+
var nSpace = n.match(/\s+/g);
|
88
|
+
if (nSpace == null) {
|
89
|
+
nSpace = ["\n"];
|
90
|
+
} else {
|
91
|
+
nSpace.push("\n");
|
92
|
+
}
|
93
|
+
|
94
|
+
var os = "";
|
95
|
+
var colors = new Array();
|
96
|
+
for (var i = 0; i < out.o.length; i++) {
|
97
|
+
colors[i] = randomColor();
|
98
|
+
|
99
|
+
if (out.o[i].text != null) {
|
100
|
+
os += '<span style="background-color: ' +colors[i]+ '">' +
|
101
|
+
escape(out.o[i].text) + oSpace[i] + "</span>";
|
102
|
+
} else {
|
103
|
+
os += "<del>" + escape(out.o[i]) + oSpace[i] + "</del>";
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
var ns = "";
|
108
|
+
for (var i = 0; i < out.n.length; i++) {
|
109
|
+
if (out.n[i].text != null) {
|
110
|
+
ns += '<span style="background-color: ' +colors[out.n[i].row]+ '">' +
|
111
|
+
escape(out.n[i].text) + nSpace[i] + "</span>";
|
112
|
+
} else {
|
113
|
+
ns += "<ins>" + escape(out.n[i]) + nSpace[i] + "</ins>";
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
return { o : os , n : ns };
|
118
|
+
}
|
119
|
+
|
120
|
+
function diff( o, n ) {
|
121
|
+
var ns = new Object();
|
122
|
+
var os = new Object();
|
123
|
+
|
124
|
+
for ( var i = 0; i < n.length; i++ ) {
|
125
|
+
if ( ns[ n[i] ] == null )
|
126
|
+
ns[ n[i] ] = { rows: new Array(), o: null };
|
127
|
+
ns[ n[i] ].rows.push( i );
|
128
|
+
}
|
129
|
+
|
130
|
+
for ( var i = 0; i < o.length; i++ ) {
|
131
|
+
if ( os[ o[i] ] == null )
|
132
|
+
os[ o[i] ] = { rows: new Array(), n: null };
|
133
|
+
os[ o[i] ].rows.push( i );
|
134
|
+
}
|
135
|
+
|
136
|
+
for ( var i in ns ) {
|
137
|
+
if ( ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1 ) {
|
138
|
+
n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] };
|
139
|
+
o[ os[i].rows[0] ] = { text: o[ os[i].rows[0] ], row: ns[i].rows[0] };
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
for ( var i = 0; i < n.length - 1; i++ ) {
|
144
|
+
if ( n[i].text != null && n[i+1].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
|
145
|
+
n[i+1] == o[ n[i].row + 1 ] ) {
|
146
|
+
n[i+1] = { text: n[i+1], row: n[i].row + 1 };
|
147
|
+
o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1 };
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
for ( var i = n.length - 1; i > 0; i-- ) {
|
152
|
+
if ( n[i].text != null && n[i-1].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
|
153
|
+
n[i-1] == o[ n[i].row - 1 ] ) {
|
154
|
+
n[i-1] = { text: n[i-1], row: n[i].row - 1 };
|
155
|
+
o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1 };
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
return { o: o, n: n };
|
160
|
+
}
|
161
|
+
|
@@ -0,0 +1,446 @@
|
|
1
|
+
// Domain Public by Eric Wendelin http://eriwen.com/ (2008)
|
2
|
+
// Luke Smith http://lucassmith.name/ (2008)
|
3
|
+
// Loic Dachary <loic@dachary.org> (2008)
|
4
|
+
// Johan Euphrosine <proppy@aminche.com> (2008)
|
5
|
+
// Oyvind Sean Kinsey http://kinsey.no/blog (2010)
|
6
|
+
// Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010)
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Main function giving a function stack trace with a forced or passed in Error
|
10
|
+
*
|
11
|
+
* @cfg {Error} e The error to create a stacktrace from (optional)
|
12
|
+
* @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
|
13
|
+
* @return {Array} of Strings with functions, lines, files, and arguments where possible
|
14
|
+
*/
|
15
|
+
function printStackTrace(options) {
|
16
|
+
options = options || {guess: true};
|
17
|
+
var ex = options.e || null, guess = !!options.guess;
|
18
|
+
var p = new printStackTrace.implementation(), result = p.run(ex);
|
19
|
+
return (guess) ? p.guessAnonymousFunctions(result) : result;
|
20
|
+
}
|
21
|
+
|
22
|
+
if (typeof module !== "undefined" && module.exports) {
|
23
|
+
module.exports = printStackTrace;
|
24
|
+
}
|
25
|
+
|
26
|
+
printStackTrace.implementation = function() {
|
27
|
+
};
|
28
|
+
|
29
|
+
printStackTrace.implementation.prototype = {
|
30
|
+
/**
|
31
|
+
* @param {Error} ex The error to create a stacktrace from (optional)
|
32
|
+
* @param {String} mode Forced mode (optional, mostly for unit tests)
|
33
|
+
*/
|
34
|
+
run: function(ex, mode) {
|
35
|
+
ex = ex || this.createException();
|
36
|
+
// examine exception properties w/o debugger
|
37
|
+
//for (var prop in ex) {alert("Ex['" + prop + "']=" + ex[prop]);}
|
38
|
+
mode = mode || this.mode(ex);
|
39
|
+
if (mode === 'other') {
|
40
|
+
return this.other(arguments.callee);
|
41
|
+
} else {
|
42
|
+
return this[mode](ex);
|
43
|
+
}
|
44
|
+
},
|
45
|
+
|
46
|
+
createException: function() {
|
47
|
+
try {
|
48
|
+
this.undef();
|
49
|
+
} catch (e) {
|
50
|
+
return e;
|
51
|
+
}
|
52
|
+
},
|
53
|
+
|
54
|
+
/**
|
55
|
+
* Mode could differ for different exception, e.g.
|
56
|
+
* exceptions in Chrome may or may not have arguments or stack.
|
57
|
+
*
|
58
|
+
* @return {String} mode of operation for the exception
|
59
|
+
*/
|
60
|
+
mode: function(e) {
|
61
|
+
if (e['arguments'] && e.stack) {
|
62
|
+
return 'chrome';
|
63
|
+
} else if (e.stack && e.sourceURL) {
|
64
|
+
return 'safari';
|
65
|
+
} else if (e.stack && e.number) {
|
66
|
+
return 'ie';
|
67
|
+
} else if (typeof e.message === 'string' && typeof window !== 'undefined' && window.opera) {
|
68
|
+
// e.message.indexOf("Backtrace:") > -1 -> opera
|
69
|
+
// !e.stacktrace -> opera
|
70
|
+
if (!e.stacktrace) {
|
71
|
+
return 'opera9'; // use e.message
|
72
|
+
}
|
73
|
+
// 'opera#sourceloc' in e -> opera9, opera10a
|
74
|
+
if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) {
|
75
|
+
return 'opera9'; // use e.message
|
76
|
+
}
|
77
|
+
// e.stacktrace && !e.stack -> opera10a
|
78
|
+
if (!e.stack) {
|
79
|
+
return 'opera10a'; // use e.stacktrace
|
80
|
+
}
|
81
|
+
// e.stacktrace && e.stack -> opera10b
|
82
|
+
if (e.stacktrace.indexOf("called from line") < 0) {
|
83
|
+
return 'opera10b'; // use e.stacktrace, format differs from 'opera10a'
|
84
|
+
}
|
85
|
+
// e.stacktrace && e.stack -> opera11
|
86
|
+
return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b'
|
87
|
+
} else if (e.stack) {
|
88
|
+
return 'firefox';
|
89
|
+
}
|
90
|
+
return 'other';
|
91
|
+
},
|
92
|
+
|
93
|
+
/**
|
94
|
+
* Given a context, function name, and callback function, overwrite it so that it calls
|
95
|
+
* printStackTrace() first with a callback and then runs the rest of the body.
|
96
|
+
*
|
97
|
+
* @param {Object} context of execution (e.g. window)
|
98
|
+
* @param {String} functionName to instrument
|
99
|
+
* @param {Function} function to call with a stack trace on invocation
|
100
|
+
*/
|
101
|
+
instrumentFunction: function(context, functionName, callback) {
|
102
|
+
context = context || window;
|
103
|
+
var original = context[functionName];
|
104
|
+
context[functionName] = function instrumented() {
|
105
|
+
callback.call(this, printStackTrace().slice(4));
|
106
|
+
return context[functionName]._instrumented.apply(this, arguments);
|
107
|
+
};
|
108
|
+
context[functionName]._instrumented = original;
|
109
|
+
},
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Given a context and function name of a function that has been
|
113
|
+
* instrumented, revert the function to it's original (non-instrumented)
|
114
|
+
* state.
|
115
|
+
*
|
116
|
+
* @param {Object} context of execution (e.g. window)
|
117
|
+
* @param {String} functionName to de-instrument
|
118
|
+
*/
|
119
|
+
deinstrumentFunction: function(context, functionName) {
|
120
|
+
if (context[functionName].constructor === Function &&
|
121
|
+
context[functionName]._instrumented &&
|
122
|
+
context[functionName]._instrumented.constructor === Function) {
|
123
|
+
context[functionName] = context[functionName]._instrumented;
|
124
|
+
}
|
125
|
+
},
|
126
|
+
|
127
|
+
/**
|
128
|
+
* Given an Error object, return a formatted Array based on Chrome's stack string.
|
129
|
+
*
|
130
|
+
* @param e - Error object to inspect
|
131
|
+
* @return Array<String> of function calls, files and line numbers
|
132
|
+
*/
|
133
|
+
chrome: function(e) {
|
134
|
+
var stack = (e.stack + '\n').replace(/^\S[^\(]+?[\n$]/gm, '').
|
135
|
+
replace(/^\s+(at eval )?at\s+/gm, '').
|
136
|
+
replace(/^([^\(]+?)([\n$])/gm, '{anonymous}()@$1$2').
|
137
|
+
replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}()@$1').split('\n');
|
138
|
+
stack.pop();
|
139
|
+
return stack;
|
140
|
+
},
|
141
|
+
|
142
|
+
/**
|
143
|
+
* Given an Error object, return a formatted Array based on Safari's stack string.
|
144
|
+
*
|
145
|
+
* @param e - Error object to inspect
|
146
|
+
* @return Array<String> of function calls, files and line numbers
|
147
|
+
*/
|
148
|
+
safari: function(e) {
|
149
|
+
return e.stack.replace(/\[native code\]\n/m, '')
|
150
|
+
.replace(/^(?=\w+Error\:).*$\n/m, '')
|
151
|
+
.replace(/^@/gm, '{anonymous}()@')
|
152
|
+
.split('\n');
|
153
|
+
},
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Given an Error object, return a formatted Array based on IE's stack string.
|
157
|
+
*
|
158
|
+
* @param e - Error object to inspect
|
159
|
+
* @return Array<String> of function calls, files and line numbers
|
160
|
+
*/
|
161
|
+
ie: function(e) {
|
162
|
+
var lineRE = /^.*at (\w+) \(([^\)]+)\)$/gm;
|
163
|
+
return e.stack.replace(/at Anonymous function /gm, '{anonymous}()@')
|
164
|
+
.replace(/^(?=\w+Error\:).*$\n/m, '')
|
165
|
+
.replace(lineRE, '$1@$2')
|
166
|
+
.split('\n');
|
167
|
+
},
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Given an Error object, return a formatted Array based on Firefox's stack string.
|
171
|
+
*
|
172
|
+
* @param e - Error object to inspect
|
173
|
+
* @return Array<String> of function calls, files and line numbers
|
174
|
+
*/
|
175
|
+
firefox: function(e) {
|
176
|
+
return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^[\(@]/gm, '{anonymous}()@').split('\n');
|
177
|
+
},
|
178
|
+
|
179
|
+
opera11: function(e) {
|
180
|
+
var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;
|
181
|
+
var lines = e.stacktrace.split('\n'), result = [];
|
182
|
+
|
183
|
+
for (var i = 0, len = lines.length; i < len; i += 2) {
|
184
|
+
var match = lineRE.exec(lines[i]);
|
185
|
+
if (match) {
|
186
|
+
var location = match[4] + ':' + match[1] + ':' + match[2];
|
187
|
+
var fnName = match[3] || "global code";
|
188
|
+
fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON);
|
189
|
+
result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
return result;
|
194
|
+
},
|
195
|
+
|
196
|
+
opera10b: function(e) {
|
197
|
+
// "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
|
198
|
+
// "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
|
199
|
+
// "@file://localhost/G:/js/test/functional/testcase1.html:15"
|
200
|
+
var lineRE = /^(.*)@(.+):(\d+)$/;
|
201
|
+
var lines = e.stacktrace.split('\n'), result = [];
|
202
|
+
|
203
|
+
for (var i = 0, len = lines.length; i < len; i++) {
|
204
|
+
var match = lineRE.exec(lines[i]);
|
205
|
+
if (match) {
|
206
|
+
var fnName = match[1]? (match[1] + '()') : "global code";
|
207
|
+
result.push(fnName + '@' + match[2] + ':' + match[3]);
|
208
|
+
}
|
209
|
+
}
|
210
|
+
|
211
|
+
return result;
|
212
|
+
},
|
213
|
+
|
214
|
+
/**
|
215
|
+
* Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
|
216
|
+
*
|
217
|
+
* @param e - Error object to inspect
|
218
|
+
* @return Array<String> of function calls, files and line numbers
|
219
|
+
*/
|
220
|
+
opera10a: function(e) {
|
221
|
+
// " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n"
|
222
|
+
// " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n"
|
223
|
+
var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
|
224
|
+
var lines = e.stacktrace.split('\n'), result = [];
|
225
|
+
|
226
|
+
for (var i = 0, len = lines.length; i < len; i += 2) {
|
227
|
+
var match = lineRE.exec(lines[i]);
|
228
|
+
if (match) {
|
229
|
+
var fnName = match[3] || ANON;
|
230
|
+
result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
return result;
|
235
|
+
},
|
236
|
+
|
237
|
+
// Opera 7.x-9.2x only!
|
238
|
+
opera9: function(e) {
|
239
|
+
// " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n"
|
240
|
+
// " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n"
|
241
|
+
var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
|
242
|
+
var lines = e.message.split('\n'), result = [];
|
243
|
+
|
244
|
+
for (var i = 2, len = lines.length; i < len; i += 2) {
|
245
|
+
var match = lineRE.exec(lines[i]);
|
246
|
+
if (match) {
|
247
|
+
result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
|
248
|
+
}
|
249
|
+
}
|
250
|
+
|
251
|
+
return result;
|
252
|
+
},
|
253
|
+
|
254
|
+
// Safari 5-, IE 9-, and others
|
255
|
+
other: function(curr) {
|
256
|
+
var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10;
|
257
|
+
while (curr && curr['arguments'] && stack.length < maxStackSize) {
|
258
|
+
fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
|
259
|
+
args = Array.prototype.slice.call(curr['arguments'] || []);
|
260
|
+
stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
|
261
|
+
curr = curr.caller;
|
262
|
+
}
|
263
|
+
return stack;
|
264
|
+
},
|
265
|
+
|
266
|
+
/**
|
267
|
+
* Given arguments array as a String, subsituting type names for non-string types.
|
268
|
+
*
|
269
|
+
* @param {Arguments} args
|
270
|
+
* @return {Array} of Strings with stringified arguments
|
271
|
+
*/
|
272
|
+
stringifyArguments: function(args) {
|
273
|
+
var result = [];
|
274
|
+
var slice = Array.prototype.slice;
|
275
|
+
for (var i = 0; i < args.length; ++i) {
|
276
|
+
var arg = args[i];
|
277
|
+
if (arg === undefined) {
|
278
|
+
result[i] = 'undefined';
|
279
|
+
} else if (arg === null) {
|
280
|
+
result[i] = 'null';
|
281
|
+
} else if (arg.constructor) {
|
282
|
+
if (arg.constructor === Array) {
|
283
|
+
if (arg.length < 3) {
|
284
|
+
result[i] = '[' + this.stringifyArguments(arg) + ']';
|
285
|
+
} else {
|
286
|
+
result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']';
|
287
|
+
}
|
288
|
+
} else if (arg.constructor === Object) {
|
289
|
+
result[i] = '#object';
|
290
|
+
} else if (arg.constructor === Function) {
|
291
|
+
result[i] = '#function';
|
292
|
+
} else if (arg.constructor === String) {
|
293
|
+
result[i] = '"' + arg + '"';
|
294
|
+
} else if (arg.constructor === Number) {
|
295
|
+
result[i] = arg;
|
296
|
+
}
|
297
|
+
}
|
298
|
+
}
|
299
|
+
return result.join(',');
|
300
|
+
},
|
301
|
+
|
302
|
+
sourceCache: {},
|
303
|
+
|
304
|
+
/**
|
305
|
+
* @return the text from a given URL
|
306
|
+
*/
|
307
|
+
ajax: function(url) {
|
308
|
+
var req = this.createXMLHTTPObject();
|
309
|
+
if (req) {
|
310
|
+
try {
|
311
|
+
req.open('GET', url, false);
|
312
|
+
//req.overrideMimeType('text/plain');
|
313
|
+
//req.overrideMimeType('text/javascript');
|
314
|
+
req.send(null);
|
315
|
+
//return req.status == 200 ? req.responseText : '';
|
316
|
+
return req.responseText;
|
317
|
+
} catch (e) {
|
318
|
+
}
|
319
|
+
}
|
320
|
+
return '';
|
321
|
+
},
|
322
|
+
|
323
|
+
/**
|
324
|
+
* Try XHR methods in order and store XHR factory.
|
325
|
+
*
|
326
|
+
* @return <Function> XHR function or equivalent
|
327
|
+
*/
|
328
|
+
createXMLHTTPObject: function() {
|
329
|
+
var xmlhttp, XMLHttpFactories = [
|
330
|
+
function() {
|
331
|
+
return new XMLHttpRequest();
|
332
|
+
}, function() {
|
333
|
+
return new ActiveXObject('Msxml2.XMLHTTP');
|
334
|
+
}, function() {
|
335
|
+
return new ActiveXObject('Msxml3.XMLHTTP');
|
336
|
+
}, function() {
|
337
|
+
return new ActiveXObject('Microsoft.XMLHTTP');
|
338
|
+
}
|
339
|
+
];
|
340
|
+
for (var i = 0; i < XMLHttpFactories.length; i++) {
|
341
|
+
try {
|
342
|
+
xmlhttp = XMLHttpFactories[i]();
|
343
|
+
// Use memoization to cache the factory
|
344
|
+
this.createXMLHTTPObject = XMLHttpFactories[i];
|
345
|
+
return xmlhttp;
|
346
|
+
} catch (e) {
|
347
|
+
}
|
348
|
+
}
|
349
|
+
},
|
350
|
+
|
351
|
+
/**
|
352
|
+
* Given a URL, check if it is in the same domain (so we can get the source
|
353
|
+
* via Ajax).
|
354
|
+
*
|
355
|
+
* @param url <String> source url
|
356
|
+
* @return False if we need a cross-domain request
|
357
|
+
*/
|
358
|
+
isSameDomain: function(url) {
|
359
|
+
return typeof location !== "undefined" && url.indexOf(location.hostname) !== -1; // location may not be defined, e.g. when running from nodejs.
|
360
|
+
},
|
361
|
+
|
362
|
+
/**
|
363
|
+
* Get source code from given URL if in the same domain.
|
364
|
+
*
|
365
|
+
* @param url <String> JS source URL
|
366
|
+
* @return <Array> Array of source code lines
|
367
|
+
*/
|
368
|
+
getSource: function(url) {
|
369
|
+
// TODO reuse source from script tags?
|
370
|
+
if (!(url in this.sourceCache)) {
|
371
|
+
this.sourceCache[url] = this.ajax(url).split('\n');
|
372
|
+
}
|
373
|
+
return this.sourceCache[url];
|
374
|
+
},
|
375
|
+
|
376
|
+
guessAnonymousFunctions: function(stack) {
|
377
|
+
for (var i = 0; i < stack.length; ++i) {
|
378
|
+
var reStack = /\{anonymous\}\(.*\)@(.*)/,
|
379
|
+
reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,
|
380
|
+
frame = stack[i], ref = reStack.exec(frame);
|
381
|
+
|
382
|
+
if (ref) {
|
383
|
+
var m = reRef.exec(ref[1]);
|
384
|
+
if (m) { // If falsey, we did not get any file/line information
|
385
|
+
var file = m[1], lineno = m[2], charno = m[3] || 0;
|
386
|
+
if (file && this.isSameDomain(file) && lineno) {
|
387
|
+
var functionName = this.guessAnonymousFunction(file, lineno, charno);
|
388
|
+
stack[i] = frame.replace('{anonymous}', functionName);
|
389
|
+
}
|
390
|
+
}
|
391
|
+
}
|
392
|
+
}
|
393
|
+
return stack;
|
394
|
+
},
|
395
|
+
|
396
|
+
guessAnonymousFunction: function(url, lineNo, charNo) {
|
397
|
+
var ret;
|
398
|
+
try {
|
399
|
+
ret = this.findFunctionName(this.getSource(url), lineNo);
|
400
|
+
} catch (e) {
|
401
|
+
ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString();
|
402
|
+
}
|
403
|
+
return ret;
|
404
|
+
},
|
405
|
+
|
406
|
+
findFunctionName: function(source, lineNo) {
|
407
|
+
// FIXME findFunctionName fails for compressed source
|
408
|
+
// (more than one function on the same line)
|
409
|
+
// function {name}({args}) m[1]=name m[2]=args
|
410
|
+
var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/;
|
411
|
+
// {name} = function ({args}) TODO args capture
|
412
|
+
// /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/
|
413
|
+
var reFunctionExpression = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/;
|
414
|
+
// {name} = eval()
|
415
|
+
var reFunctionEvaluation = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/;
|
416
|
+
// Walk backwards in the source lines until we find
|
417
|
+
// the line which matches one of the patterns above
|
418
|
+
var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos;
|
419
|
+
for (var i = 0; i < maxLines; ++i) {
|
420
|
+
// lineNo is 1-based, source[] is 0-based
|
421
|
+
line = source[lineNo - i - 1];
|
422
|
+
commentPos = line.indexOf('//');
|
423
|
+
if (commentPos >= 0) {
|
424
|
+
line = line.substr(0, commentPos);
|
425
|
+
}
|
426
|
+
// TODO check other types of comments? Commented code may lead to false positive
|
427
|
+
if (line) {
|
428
|
+
code = line + code;
|
429
|
+
m = reFunctionExpression.exec(code);
|
430
|
+
if (m && m[1]) {
|
431
|
+
return m[1];
|
432
|
+
}
|
433
|
+
m = reFunctionDeclaration.exec(code);
|
434
|
+
if (m && m[1]) {
|
435
|
+
//return m[1] + "(" + (m[2] || "") + ")";
|
436
|
+
return m[1];
|
437
|
+
}
|
438
|
+
m = reFunctionEvaluation.exec(code);
|
439
|
+
if (m && m[1]) {
|
440
|
+
return m[1];
|
441
|
+
}
|
442
|
+
}
|
443
|
+
}
|
444
|
+
return '(?)';
|
445
|
+
}
|
446
|
+
};
|
data/views/index.slim
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
doctype html
|
2
|
+
head
|
3
|
+
script type="text/javascript" src="ie.js"
|
4
|
+
script type="text/javascript" src="jquery-1.5.2.js"
|
5
|
+
script type="text/javascript" src="stacktrace.js"
|
6
|
+
script type="text/javascript" src="jsdiff.js"
|
7
|
+
link rel="stylesheet" href="/assets/spec.css" type="text/css"
|
8
|
+
script type="text/javascript" src="/assets/Spec.js"
|
9
|
+
script type="text/javascript" src="/assets/HtmlReport.js"
|
10
|
+
|
11
|
+
body
|
12
|
+
javascript:
|
13
|
+
- @scripts.each do |file_name|
|
14
|
+
script type="text/javascript" src="/assets/#{{file_name.sub /\.coffee$/, '.js'}}"
|
15
|
+
javascript:
|
16
|
+
$(function() {
|
17
|
+
window.report = new HtmlReport(document.body);
|
18
|
+
report.run();
|
19
|
+
});
|