built-in js|coffee script code evaluation and execution; on uncaught exception, …
…error file location is now handled efficiently
Showing
5 changed files
with
72 additions
and
56 deletions
... | @@ -27,6 +27,8 @@ | ... | @@ -27,6 +27,8 @@ |
27 | * DEALINGS IN THE SOFTWARE. | 27 | * DEALINGS IN THE SOFTWARE. |
28 | * | 28 | * |
29 | */ | 29 | */ |
30 | var sourceIds = {}; | ||
31 | |||
30 | if (!phantom.casperLoaded) { | 32 | if (!phantom.casperLoaded) { |
31 | // see http://semver.org/ | 33 | // see http://semver.org/ |
32 | phantom.casperVersion = { | 34 | phantom.casperVersion = { |
... | @@ -67,7 +69,44 @@ if (!phantom.casperLoaded) { | ... | @@ -67,7 +69,44 @@ if (!phantom.casperLoaded) { |
67 | // casper root path | 69 | // casper root path |
68 | phantom.casperPath = fs.absolute(phantom.libraryScript); | 70 | phantom.casperPath = fs.absolute(phantom.libraryScript); |
69 | 71 | ||
70 | var sourceIds = {}; | 72 | // Embedded, up-to-date, validatable & controlable CoffeeScript |
73 | phantom.injectJs(fs.pathJoin(phantom.casperPath, 'modules', 'vendors', 'coffee-script.js')); | ||
74 | |||
75 | // Adding built-in capabilities to phantom object | ||
76 | phantom.getErrorMessage = function(e) { | ||
77 | return (e.fileName || sourceIds[e.sourceId]) + ':' + e.line + ' ' + e; | ||
78 | }; | ||
79 | |||
80 | phantom.getScriptCode = function(file, onError) { | ||
81 | var scriptCode = fs.read(file); | ||
82 | if (/\.coffee$/i.test(file)) { | ||
83 | try { | ||
84 | scriptCode = CoffeeScript.compile(scriptCode); | ||
85 | } catch (e) { | ||
86 | phantom.processScriptError(e, file, onError); | ||
87 | } | ||
88 | } | ||
89 | // trick to locate source file location on error | ||
90 | scriptCode += ";var __fe__ = new Error('__sourceId__')"; | ||
91 | scriptCode += ";__fe__.fileName = '" + file + "'"; | ||
92 | scriptCode += ";throw __fe__;"; | ||
93 | return scriptCode; | ||
94 | }; | ||
95 | |||
96 | phantom.processScriptError = function(error, file, callback) { | ||
97 | if (!sourceIds.hasOwnProperty(error.sourceId)) { | ||
98 | sourceIds[error.sourceId] = file; | ||
99 | } | ||
100 | if (error.message === "__sourceId__") { | ||
101 | return; | ||
102 | } | ||
103 | if (typeof callback === "function") { | ||
104 | callback(error); | ||
105 | } else { | ||
106 | console.error(phantom.getErrorMessage(error)); | ||
107 | phantom.exit(1); | ||
108 | } | ||
109 | }; | ||
71 | 110 | ||
72 | // Patching require() | 111 | // Patching require() |
73 | // Inspired by phantomjs-nodify: https://github.com/jgonera/phantomjs-nodify/ | 112 | // Inspired by phantomjs-nodify: https://github.com/jgonera/phantomjs-nodify/ |
... | @@ -122,34 +161,10 @@ if (!phantom.casperLoaded) { | ... | @@ -122,34 +161,10 @@ if (!phantom.casperLoaded) { |
122 | if (file in requireCache) { | 161 | if (file in requireCache) { |
123 | return requireCache[file].exports; | 162 | return requireCache[file].exports; |
124 | } | 163 | } |
125 | code = fs.read(file); | ||
126 | if (file.match(/\.js$/i)) { | ||
127 | try { | 164 | try { |
128 | // TODO: esprima syntax check | 165 | new Function('module', 'exports', phantom.getScriptCode(file))(module, module.exports); |
129 | } catch (e) { | 166 | } catch (e) { |
130 | e.fileName = file; | 167 | phantom.processScriptError(e); |
131 | throw e; | ||
132 | } | ||
133 | } else if (file.match(/\.coffee$/i)) { | ||
134 | try { | ||
135 | code = CoffeeScript.compile(code); | ||
136 | } catch (e) { | ||
137 | e.fileName = file; | ||
138 | throw e; | ||
139 | } | ||
140 | } | ||
141 | // a trick to associate Error's sourceId with file | ||
142 | code += ";throw new Error('__sourceId__');"; | ||
143 | try { | ||
144 | fn = new Function('module', 'exports', code); | ||
145 | fn(module, module.exports); | ||
146 | } catch (e) { | ||
147 | if (!sourceIds.hasOwnProperty(e.sourceId)) { | ||
148 | sourceIds[e.sourceId] = file; | ||
149 | } | ||
150 | if (e.message !== '__sourceId__') { | ||
151 | throw e; | ||
152 | } | ||
153 | } | 168 | } |
154 | requireCache[file] = module; | 169 | requireCache[file] = module; |
155 | return module.exports; | 170 | return module.exports; |
... | @@ -169,7 +184,7 @@ if (!phantom.casperLoaded) { | ... | @@ -169,7 +184,7 @@ if (!phantom.casperLoaded) { |
169 | if (this._stack) { | 184 | if (this._stack) { |
170 | return this._stack; | 185 | return this._stack; |
171 | } else if (this.fileName || this.sourceId) { | 186 | } else if (this.fileName || this.sourceId) { |
172 | return this.toString() + '\nat ' + getErrorMessage(this); | 187 | return this.toString() + '\nat ' + phantom.getErrorMessage(this); |
173 | } | 188 | } |
174 | return this.toString() + '\nat unknown'; | 189 | return this.toString() + '\nat unknown'; |
175 | }, | 190 | }, |
... | @@ -183,30 +198,33 @@ if (!phantom.casperLoaded) { | ... | @@ -183,30 +198,33 @@ if (!phantom.casperLoaded) { |
183 | 198 | ||
184 | // loaded status | 199 | // loaded status |
185 | phantom.casperLoaded = true; | 200 | phantom.casperLoaded = true; |
201 | } | ||
186 | 202 | ||
187 | if (!!phantom.casperArgs.options.version) { | 203 | if (!!phantom.casperArgs.options.version) { |
188 | console.log(phantom.casperVersion.toString()); | 204 | console.log(phantom.casperVersion.toString()); |
189 | phantom.exit(0); | 205 | phantom.exit(0); |
190 | } else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) { | 206 | } else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) { |
191 | console.log('CasperJS version ' + phantom.casperVersion.toString()); | 207 | console.log('CasperJS version ' + phantom.casperVersion.toString()); |
192 | console.log('Usage: casperjs script.(js|coffee) [options...]'); | 208 | console.log('Usage: casperjs script.(js|coffee) [options...]'); |
193 | console.log('Read the docs http://n1k0.github.com/casperjs/'); | 209 | console.log('Read the docs http://n1k0.github.com/casperjs/'); |
194 | phantom.exit(0); | 210 | phantom.exit(0); |
195 | } | 211 | } |
196 | 212 | ||
197 | phantom.casperScript = phantom.casperArgs.get(0); | 213 | phantom.casperScript = phantom.casperArgs.get(0); |
198 | 214 | ||
199 | if (!fs.isFile(phantom.casperScript)) { | 215 | if (!fs.isFile(phantom.casperScript)) { |
200 | console.log('Unable to open file: ' + phantom.casperScript); | 216 | console.error('Unable to open file: ' + phantom.casperScript); |
201 | phantom.exit(1); | 217 | phantom.exit(1); |
202 | } | 218 | } |
203 | 219 | ||
204 | // filter out the called script name from casper args | 220 | // filter out the called script name from casper args |
205 | phantom.casperArgs.args = phantom.casperArgs.args.filter(function(arg) { | 221 | phantom.casperArgs.args = phantom.casperArgs.args.filter(function(arg) { |
206 | return arg !== phantom.casperScript; | 222 | return arg !== phantom.casperScript; |
207 | }); | 223 | }); |
208 | } | ||
209 | 224 | ||
210 | // passed script execution | 225 | // passed casperjs script execution |
211 | // TODO: proper syntax validation? | 226 | try { |
212 | phantom.injectJs(phantom.casperScript); | 227 | new Function(phantom.getScriptCode(phantom.casperScript))(); |
228 | } catch (e) { | ||
229 | phantom.processScriptError(e, phantom.casperScript); | ||
230 | } | ... | ... |
... | @@ -274,9 +274,8 @@ var Tester = function(casper, options) { | ... | @@ -274,9 +274,8 @@ var Tester = function(casper, options) { |
274 | }; | 274 | }; |
275 | 275 | ||
276 | /** | 276 | /** |
277 | * Executes a file. We can't use phantom.injectJs(testFile) because we | 277 | * Executes a file, wraping and evaluating its code in an isolated |
278 | * wouldn't be able to catch any thrown exception. So eval is evil, but | 278 | * environment where only the current `casper` instance is passed. |
279 | * evil actually works^W helps sometimes. | ||
280 | * | 279 | * |
281 | * @param String file Absolute path to some js/coffee file | 280 | * @param String file Absolute path to some js/coffee file |
282 | */ | 281 | */ |
... | @@ -284,17 +283,16 @@ var Tester = function(casper, options) { | ... | @@ -284,17 +283,16 @@ var Tester = function(casper, options) { |
284 | if (!fs.isFile(file) || !utils.isJsFile(file)) { | 283 | if (!fs.isFile(file) || !utils.isJsFile(file)) { |
285 | throw new Error("Can only exec() files with .js or .coffee extensions"); | 284 | throw new Error("Can only exec() files with .js or .coffee extensions"); |
286 | } | 285 | } |
287 | if (utils.fileExt(file) === "coffee") { | ||
288 | phantom.injectJs(file); // FIXME: syntax validation? | ||
289 | } else { | ||
290 | var testContents = fs.read(file); | ||
291 | var parsed; | ||
292 | try { | 286 | try { |
293 | parsed = esprima.parse(testContents); | 287 | new Function('casper', phantom.getScriptCode(file))(casper); |
294 | } catch(e) { | 288 | } catch (e) { |
295 | throw new Error("Unable to parse test file " + file + ": " + e.toString()); | 289 | var self = this; |
296 | } | 290 | phantom.processScriptError(e, file, function(error) { |
297 | eval(testContents); | 291 | // do not abort the whole suite, just fail fast displaying the |
292 | // caught error and process next suite | ||
293 | self.fail(phantom.getErrorMessage(e)); | ||
294 | self.done(); | ||
295 | }); | ||
298 | } | 296 | } |
299 | }; | 297 | }; |
300 | 298 | ... | ... |
modules/vendors/coffee-script.js
0 → 100755
This diff could not be displayed because it is too large.
... | @@ -18,7 +18,7 @@ | ... | @@ -18,7 +18,7 @@ |
18 | for (var what in testCases) { | 18 | for (var what in testCases) { |
19 | var source = testCases[what]; | 19 | var source = testCases[what]; |
20 | var encoded = clientutils.encode(source); | 20 | var encoded = clientutils.encode(source); |
21 | t.assertEquals(clientutils.decode(encoded), source, 'Clientclientutils can encode and decode ' + what); | 21 | t.assertEquals(clientutils.decode(encoded), source, 'ClientUtils can encode and decode ' + what); |
22 | } | 22 | } |
23 | 23 | ||
24 | t.done(); | 24 | t.done(); | ... | ... |
-
Please register or sign in to post a comment