gusto 1.0.0.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
});
|