Commit 9fa750e8 9fa750e834c620da11cf7b3185306c7309bf774e by Nicolas Perriault

built-in js|coffee script code evaluation and execution; on uncaught exception, …

…error file location is now handled efficiently
1 parent 75a6f921
...@@ -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
......
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();
......