1 /*
2 Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
3 Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
4 Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
5 Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
6 Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
7 Copyright (C) 2011 Yusuke Suzuki <utatane.tea@gmail.com>
8 Copyright (C) 2011 Arpad Borsos <arpad.borsos@googlemail.com>
10 Redistribution and use in source and binary forms, with or without
11 modification, are permitted provided that the following conditions are met:
13 * Redistributions of source code must retain the above copyright
14 notice, this list of conditions and the following disclaimer.
15 * Redistributions in binary form must reproduce the above copyright
16 notice, this list of conditions and the following disclaimer in the
17 documentation and/or other materials provided with the distribution.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
23 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
31 /*jslint browser:true node:true */
32 /*global esprima:true, testFixture:true */
34 var runTests;
36 // Special handling for regular expression literal since we need to
37 // convert it to a string literal, otherwise it will be decoded
38 // as object "{}" and the regular expression would be lost.
39 function adjustRegexLiteral(key, value) {
40 'use strict';
41 if (key === 'value' && value instanceof RegExp) {
42 value = value.toString();
43 }
44 return value;
45 }
47 function NotMatchingError(expected, actual) {
48 'use strict';
49 Error.call(this, 'Expected ');
50 this.expected = expected;
51 this.actual = actual;
52 }
53 NotMatchingError.prototype = new Error();
55 function errorToObject(e) {
56 'use strict';
57 var msg = e.toString();
59 // Opera 9.64 produces an non-standard string in toString().
60 if (msg.substr(0, 6) !== 'Error:') {
61 if (typeof e.message === 'string') {
62 msg = 'Error: ' + e.message;
63 }
64 }
66 return {
67 index: e.index,
68 lineNumber: e.lineNumber,
69 column: e.column,
70 message: msg
71 };
72 }
74 function hasAttachedComment(syntax) {
75 var key;
76 for (key in syntax) {
77 if (key === 'leadingComments' || key === 'trailingComments') {
78 return true;
79 }
80 if (typeof syntax[key] === 'object' && syntax[key] !== null) {
81 if (hasAttachedComment(syntax[key])) {
82 return true;
83 }
84 }
85 }
86 return false;
87 }
89 function testParse(esprima, code, syntax) {
90 'use strict';
91 var expected, tree, actual, options, StringObject, i, len, err;
93 // alias, so that JSLint does not complain.
94 StringObject = String;
96 options = {
97 comment: (typeof syntax.comments !== 'undefined'),
98 range: true,
99 loc: true,
100 tokens: (typeof syntax.tokens !== 'undefined'),
101 raw: true,
102 tolerant: (typeof syntax.errors !== 'undefined'),
103 source: null
104 };
106 if (options.comment) {
107 options.attachComment = hasAttachedComment(syntax);
108 }
110 if (typeof syntax.tokens !== 'undefined') {
111 if (syntax.tokens.length > 0) {
112 options.range = (typeof syntax.tokens[0].range !== 'undefined');
113 options.loc = (typeof syntax.tokens[0].loc !== 'undefined');
114 }
115 }
117 if (typeof syntax.comments !== 'undefined') {
118 if (syntax.comments.length > 0) {
119 options.range = (typeof syntax.comments[0].range !== 'undefined');
120 options.loc = (typeof syntax.comments[0].loc !== 'undefined');
121 }
122 }
124 if (options.loc) {
125 options.source = syntax.loc.source;
126 }
128 expected = JSON.stringify(syntax, null, 4);
129 try {
130 // Some variations of the options.
131 tree = esprima.parse(code, { tolerant: options.tolerant });
132 tree = esprima.parse(code, { tolerant: options.tolerant, range: true });
133 tree = esprima.parse(code, { tolerant: options.tolerant, loc: true });
135 tree = esprima.parse(code, options);
136 tree = (options.comment || options.tokens || options.tolerant) ? tree : tree.body[0];
138 if (options.tolerant) {
139 for (i = 0, len = tree.errors.length; i < len; i += 1) {
140 tree.errors[i] = errorToObject(tree.errors[i]);
141 }
142 }
144 actual = JSON.stringify(tree, adjustRegexLiteral, 4);
146 // Only to ensure that there is no error when using string object.
147 esprima.parse(new StringObject(code), options);
149 } catch (e) {
150 throw new NotMatchingError(expected, e.toString());
151 }
152 if (expected !== actual) {
153 throw new NotMatchingError(expected, actual);
154 }
156 function filter(key, value) {
157 if (key === 'value' && value instanceof RegExp) {
158 value = value.toString();
159 }
160 return (key === 'loc' || key === 'range') ? undefined : value;
161 }
163 if (options.tolerant) {
164 return;
165 }
168 // Check again without any location info.
169 options.range = false;
170 options.loc = false;
171 expected = JSON.stringify(syntax, filter, 4);
172 try {
173 tree = esprima.parse(code, options);
174 tree = (options.comment || options.tokens) ? tree : tree.body[0];
176 if (options.tolerant) {
177 for (i = 0, len = tree.errors.length; i < len; i += 1) {
178 tree.errors[i] = errorToObject(tree.errors[i]);
179 }
180 }
182 actual = JSON.stringify(tree, filter, 4);
183 } catch (e) {
184 throw new NotMatchingError(expected, e.toString());
185 }
186 if (expected !== actual) {
187 throw new NotMatchingError(expected, actual);
188 }
189 }
191 function testTokenize(esprima, code, tokens) {
192 'use strict';
193 var options, expected, actual, tree;
195 options = {
196 comment: true,
197 tolerant: true,
198 loc: true,
199 range: true
200 };
202 expected = JSON.stringify(tokens, null, 4);
204 try {
205 tree = esprima.tokenize(code, options);
206 actual = JSON.stringify(tree, null, 4);
207 } catch (e) {
208 throw new NotMatchingError(expected, e.toString());
209 }
210 if (expected !== actual) {
211 throw new NotMatchingError(expected, actual);
212 }
213 }
215 function testError(esprima, code, exception) {
216 'use strict';
217 var i, options, expected, actual, err, handleInvalidRegexFlag, tokenize;
219 // Different parsing options should give the same error.
220 options = [
221 {},
222 { comment: true },
223 { raw: true },
224 { raw: true, comment: true }
225 ];
227 // If handleInvalidRegexFlag is true, an invalid flag in a regular expression
228 // will throw an exception. In some old version V8, this is not the case
229 // and hence handleInvalidRegexFlag is false.
230 handleInvalidRegexFlag = false;
231 try {
232 'test'.match(new RegExp('[a-z]', 'x'));
233 } catch (e) {
234 handleInvalidRegexFlag = true;
235 }
237 exception.description = exception.message.replace(/Error: Line [0-9]+: /, '');
239 if (exception.tokenize) {
240 tokenize = true;
241 exception.tokenize = undefined;
242 }
243 expected = JSON.stringify(exception);
245 for (i = 0; i < options.length; i += 1) {
247 try {
248 if (tokenize) {
249 esprima.tokenize(code, options[i])
250 } else {
251 esprima.parse(code, options[i]);
252 }
253 } catch (e) {
254 err = errorToObject(e);
255 err.description = e.description;
256 actual = JSON.stringify(err);
257 }
259 if (expected !== actual) {
261 // Compensate for old V8 which does not handle invalid flag.
262 if (exception.message.indexOf('Invalid regular expression') > 0) {
263 if (typeof actual === 'undefined' && !handleInvalidRegexFlag) {
264 return;
265 }
266 }
268 throw new NotMatchingError(expected, actual);
269 }
271 }
272 }
274 function testAPI(esprima, code, result) {
275 'use strict';
276 var expected, res, actual;
278 expected = JSON.stringify(result.result, null, 4);
279 try {
280 if (typeof result.property !== 'undefined') {
281 res = esprima[result.property];
282 } else {
283 res = esprima[result.call].apply(esprima, result.args);
284 }
285 actual = JSON.stringify(res, adjustRegexLiteral, 4);
286 } catch (e) {
287 throw new NotMatchingError(expected, e.toString());
288 }
289 if (expected !== actual) {
290 throw new NotMatchingError(expected, actual);
291 }
292 }
294 function runTest(esprima, code, result) {
295 'use strict';
296 if (result.hasOwnProperty('lineNumber')) {
297 testError(esprima, code, result);
298 } else if (result.hasOwnProperty('result')) {
299 testAPI(esprima, code, result);
300 } else if (result instanceof Array) {
301 testTokenize(esprima, code, result);
302 } else {
303 testParse(esprima, code, result);
304 }
305 }
307 if (typeof window !== 'undefined') {
308 // Run all tests in a browser environment.
309 runTests = function () {
310 'use strict';
311 var total = 0,
312 failures = 0,
313 category,
314 fixture,
315 source,
316 tick,
317 expected,
318 index,
319 len;
321 function setText(el, str) {
322 if (typeof el.innerText === 'string') {
323 el.innerText = str;
324 } else {
325 el.textContent = str;
326 }
327 }
329 function startCategory(category) {
330 var report, e;
331 report = document.getElementById('report');
332 e = document.createElement('h4');
333 setText(e, category);
334 report.appendChild(e);
335 }
337 function reportSuccess(code) {
338 var report, e;
339 report = document.getElementById('report');
340 e = document.createElement('pre');
341 e.setAttribute('class', 'code');
342 setText(e, code);
343 report.appendChild(e);
344 }
346 function reportFailure(code, expected, actual) {
347 var report, e;
349 report = document.getElementById('report');
351 e = document.createElement('p');
352 setText(e, 'Code:');
353 report.appendChild(e);
355 e = document.createElement('pre');
356 e.setAttribute('class', 'code');
357 setText(e, code);
358 report.appendChild(e);
360 e = document.createElement('p');
361 setText(e, 'Expected');
362 report.appendChild(e);
364 e = document.createElement('pre');
365 e.setAttribute('class', 'expected');
366 setText(e, expected);
367 report.appendChild(e);
369 e = document.createElement('p');
370 setText(e, 'Actual');
371 report.appendChild(e);
373 e = document.createElement('pre');
374 e.setAttribute('class', 'actual');
375 setText(e, actual);
376 report.appendChild(e);
377 }
379 setText(document.getElementById('version'), esprima.version);
381 tick = new Date();
382 for (category in testFixture) {
383 if (testFixture.hasOwnProperty(category)) {
384 startCategory(category);
385 fixture = testFixture[category];
386 for (source in fixture) {
387 if (fixture.hasOwnProperty(source)) {
388 expected = fixture[source];
389 total += 1;
390 try {
391 runTest(esprima, source, expected);
392 reportSuccess(source, JSON.stringify(expected, null, 4));
393 } catch (e) {
394 failures += 1;
395 reportFailure(source, e.expected, e.actual);
396 }
397 }
398 }
399 }
400 }
401 tick = (new Date()) - tick;
403 if (failures > 0) {
404 document.getElementById('status').className = 'alert-box alert';
405 setText(document.getElementById('status'), total + ' tests. ' +
406 'Failures: ' + failures + '. ' + tick + ' ms.');
407 } else {
408 document.getElementById('status').className = 'alert-box success';
409 setText(document.getElementById('status'), total + ' tests. ' +
410 'No failure. ' + tick + ' ms.');
411 }
412 };
413 } else {
414 (function () {
415 'use strict';
417 var esprima = require('../esprima'),
418 vm = require('vm'),
419 fs = require('fs'),
420 diff = require('json-diff').diffString,
421 total = 0,
422 failures = [],
423 tick = new Date(),
424 expected,
425 header;
427 vm.runInThisContext(fs.readFileSync(__dirname + '/test.js', 'utf-8'));
429 Object.keys(testFixture).forEach(function (category) {
430 Object.keys(testFixture[category]).forEach(function (source) {
431 total += 1;
432 expected = testFixture[category][source];
433 try {
434 runTest(esprima, source, expected);
435 } catch (e) {
436 e.source = source;
437 failures.push(e);
438 }
439 });
440 });
441 tick = (new Date()) - tick;
443 header = total + ' tests. ' + failures.length + ' failures. ' +
444 tick + ' ms';
445 if (failures.length) {
446 console.error(header);
447 failures.forEach(function (failure) {
448 try {
449 var expectedObject = JSON.parse(failure.expected);
450 var actualObject = JSON.parse(failure.actual);
452 console.error(failure.source + ': Expected\n ' +
453 failure.expected.split('\n').join('\n ') +
454 '\nto match\n ' + failure.actual + '\nDiff:\n' +
455 diff(expectedObject, actualObject));
456 } catch (ex) {
457 console.error(failure.source + ': Expected\n ' +
458 failure.expected.split('\n').join('\n ') +
459 '\nto match\n ' + failure.actual);
460 }
461 });
462 } else {
463 console.log(header);
464 }
465 process.exit(failures.length === 0 ? 0 : 1);
466 }());
467 }