UNPKG

15.6 kBJavaScriptView Raw
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>
9
10 Redistribution and use in source and binary forms, with or without
11 modification, are permitted provided that the following conditions are met:
12
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.
18
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*/
30
31/*jslint browser:true node:true */
32/*global esprima:true, testFixture:true */
33
34var runTests;
35
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.
39function adjustRegexLiteral(key, value) {
40 'use strict';
41 if (key === 'value' && value instanceof RegExp) {
42 value = value.toString();
43 }
44 return value;
45}
46
47function NotMatchingError(expected, actual) {
48 'use strict';
49 Error.call(this, 'Expected ');
50 this.expected = expected;
51 this.actual = actual;
52}
53NotMatchingError.prototype = new Error();
54
55function errorToObject(e) {
56 'use strict';
57 var msg = e.toString();
58
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 }
65
66 return {
67 index: e.index,
68 lineNumber: e.lineNumber,
69 column: e.column,
70 message: msg
71 };
72}
73
74function needLoc(syntax) {
75 var need = true;
76 if (typeof syntax.tokens !== 'undefined' && syntax.tokens.length > 0) {
77 need = (typeof syntax.tokens[0].loc !== 'undefined');
78 }
79 if (typeof syntax.comments !== 'undefined' && syntax.comments.length > 0) {
80 need = (typeof syntax.comments[0].loc !== 'undefined');
81 }
82 return need;
83}
84
85function needRange(syntax) {
86 var need = true;
87 if (typeof syntax.tokens !== 'undefined' && syntax.tokens.length > 0) {
88 need = (typeof syntax.tokens[0].range !== 'undefined');
89 }
90 if (typeof syntax.comments !== 'undefined' && syntax.comments.length > 0) {
91 need = (typeof syntax.comments[0].range !== 'undefined');
92 }
93 return need;
94}
95
96function testParse(esprima, code, syntax) {
97 'use strict';
98 var expected, tree, actual, options, StringObject, i, len, err;
99
100 // alias, so that JSLint does not complain.
101 StringObject = String;
102
103 options = {
104 comment: (typeof syntax.comments !== 'undefined'),
105 range: needRange(syntax),
106 loc: needLoc(syntax),
107 tokens: (typeof syntax.tokens !== 'undefined'),
108 raw: true,
109 tolerant: (typeof syntax.errors !== 'undefined'),
110 source: null
111 };
112
113 if (options.loc) {
114 options.source = syntax.loc.source;
115 }
116
117 expected = JSON.stringify(syntax, null, 4);
118 try {
119 tree = esprima.parse(code, options);
120 tree = (options.comment || options.tokens || options.tolerant) ? tree : tree.body[0];
121
122 if (options.tolerant) {
123 for (i = 0, len = tree.errors.length; i < len; i += 1) {
124 tree.errors[i] = errorToObject(tree.errors[i]);
125 }
126 }
127
128 actual = JSON.stringify(tree, adjustRegexLiteral, 4);
129
130 // Only to ensure that there is no error when using string object.
131 esprima.parse(new StringObject(code), options);
132
133 } catch (e) {
134 throw new NotMatchingError(expected, e.toString());
135 }
136 if (expected !== actual) {
137 throw new NotMatchingError(expected, actual);
138 }
139
140 function filter(key, value) {
141 if (key === 'value' && value instanceof RegExp) {
142 value = value.toString();
143 }
144 return (key === 'loc' || key === 'range') ? undefined : value;
145 }
146
147 if (options.tolerant) {
148 return;
149 }
150
151
152 // Check again without any location info.
153 options.range = false;
154 options.loc = false;
155 expected = JSON.stringify(syntax, filter, 4);
156 try {
157 tree = esprima.parse(code, options);
158 tree = (options.comment || options.tokens) ? tree : tree.body[0];
159
160 if (options.tolerant) {
161 for (i = 0, len = tree.errors.length; i < len; i += 1) {
162 tree.errors[i] = errorToObject(tree.errors[i]);
163 }
164 }
165
166 actual = JSON.stringify(tree, filter, 4);
167 } catch (e) {
168 throw new NotMatchingError(expected, e.toString());
169 }
170 if (expected !== actual) {
171 throw new NotMatchingError(expected, actual);
172 }
173}
174
175function mustHaveLocRange(testName, node, needLoc, needRange, stack) {
176 var error;
177 if (node.hasOwnProperty('type')) {
178 if (needLoc && !node.loc) {
179 error = "doesn't have 'loc' property";
180 }
181 if (needRange && !node.range) {
182 error = "doesn't have 'range' property";
183 }
184 if (error) {
185 stack = stack.length ? ' at [' + stack.join('][') + ']' : '';
186 throw new Error("Test '" + testName + "'" + stack + " (type = " + node.type + ") " + error);
187 }
188 }
189 for (i in node) {
190 if (node.hasOwnProperty(i) && node[i] !== null && typeof node[i] === 'object') {
191 stack.push(i);
192 mustHaveLocRange(testName, node[i], needLoc, needRange, stack);
193 stack.pop();
194 }
195 }
196}
197
198function testTokenize(esprima, code, tokens) {
199 'use strict';
200 var options, expected, actual, tree;
201
202 options = {
203 comment: true,
204 tolerant: true,
205 loc: true,
206 range: true
207 };
208
209 expected = JSON.stringify(tokens, null, 4);
210
211 try {
212 tree = esprima.tokenize(code, options);
213 actual = JSON.stringify(tree, null, 4);
214 } catch (e) {
215 throw new NotMatchingError(expected, e.toString());
216 }
217 if (expected !== actual) {
218 throw new NotMatchingError(expected, actual);
219 }
220}
221
222function testError(esprima, code, exception) {
223 'use strict';
224 var i, options, expected, actual, err, handleInvalidRegexFlag, tokenize;
225
226 // Different parsing options should give the same error.
227 options = [
228 {},
229 { comment: true },
230 { raw: true },
231 { raw: true, comment: true }
232 ];
233
234 // If handleInvalidRegexFlag is true, an invalid flag in a regular expression
235 // will throw an exception. In some old version V8, this is not the case
236 // and hence handleInvalidRegexFlag is false.
237 handleInvalidRegexFlag = false;
238 try {
239 'test'.match(new RegExp('[a-z]', 'x'));
240 } catch (e) {
241 handleInvalidRegexFlag = true;
242 }
243
244 exception.description = exception.message.replace(/Error: Line [0-9]+: /, '');
245
246 if (exception.tokenize) {
247 tokenize = true;
248 exception.tokenize = undefined;
249 }
250 expected = JSON.stringify(exception);
251
252 for (i = 0; i < options.length; i += 1) {
253
254 try {
255 if (tokenize) {
256 esprima.tokenize(code, options[i])
257 } else {
258 esprima.parse(code, options[i]);
259 }
260 } catch (e) {
261 err = errorToObject(e);
262 err.description = e.description;
263 actual = JSON.stringify(err);
264 }
265
266 if (expected !== actual) {
267
268 // Compensate for old V8 which does not handle invalid flag.
269 if (exception.message.indexOf('Invalid regular expression') > 0) {
270 if (typeof actual === 'undefined' && !handleInvalidRegexFlag) {
271 return;
272 }
273 }
274
275 throw new NotMatchingError(expected, actual);
276 }
277
278 }
279}
280
281function testAPI(esprima, code, result) {
282 'use strict';
283 var expected, res, actual;
284
285 expected = JSON.stringify(result.result, null, 4);
286 try {
287 if (typeof result.property !== 'undefined') {
288 res = esprima[result.property];
289 } else {
290 res = esprima[result.call].apply(esprima, result.args);
291 }
292 actual = JSON.stringify(res, adjustRegexLiteral, 4);
293 } catch (e) {
294 throw new NotMatchingError(expected, e.toString());
295 }
296 if (expected !== actual) {
297 throw new NotMatchingError(expected, actual);
298 }
299}
300
301function runTest(esprima, code, result) {
302 'use strict';
303 if (result.hasOwnProperty('lineNumber')) {
304 testError(esprima, code, result);
305 } else if (result.hasOwnProperty('result')) {
306 testAPI(esprima, code, result);
307 } else if (result instanceof Array) {
308 testTokenize(esprima, code, result);
309 } else {
310 testParse(esprima, code, result);
311 }
312}
313
314if (typeof window !== 'undefined') {
315 // Run all tests in a browser environment.
316 runTests = function () {
317 'use strict';
318 var total = 0,
319 failures = 0,
320 category,
321 fixture,
322 source,
323 tick,
324 expected,
325 index,
326 len;
327
328 function setText(el, str) {
329 if (typeof el.innerText === 'string') {
330 el.innerText = str;
331 } else {
332 el.textContent = str;
333 }
334 }
335
336 function startCategory(category) {
337 var report, e;
338 report = document.getElementById('report');
339 e = document.createElement('h4');
340 setText(e, category);
341 report.appendChild(e);
342 }
343
344 function reportSuccess(code) {
345 var report, e;
346 report = document.getElementById('report');
347 e = document.createElement('pre');
348 e.setAttribute('class', 'code');
349 setText(e, code);
350 report.appendChild(e);
351 }
352
353 function reportFailure(code, expected, actual) {
354 var report, e;
355
356 report = document.getElementById('report');
357
358 e = document.createElement('p');
359 setText(e, 'Code:');
360 report.appendChild(e);
361
362 e = document.createElement('pre');
363 e.setAttribute('class', 'code');
364 setText(e, code);
365 report.appendChild(e);
366
367 e = document.createElement('p');
368 setText(e, 'Expected');
369 report.appendChild(e);
370
371 e = document.createElement('pre');
372 e.setAttribute('class', 'expected');
373 setText(e, expected);
374 report.appendChild(e);
375
376 e = document.createElement('p');
377 setText(e, 'Actual');
378 report.appendChild(e);
379
380 e = document.createElement('pre');
381 e.setAttribute('class', 'actual');
382 setText(e, actual);
383 report.appendChild(e);
384 }
385
386 setText(document.getElementById('version'), esprima.version);
387
388 tick = new Date();
389 for (category in testFixture) {
390 if (testFixture.hasOwnProperty(category)) {
391 startCategory(category);
392 fixture = testFixture[category];
393 for (source in fixture) {
394 if (fixture.hasOwnProperty(source)) {
395 expected = fixture[source];
396 total += 1;
397 try {
398 runTest(esprima, source, expected);
399 reportSuccess(source, JSON.stringify(expected, null, 4));
400 } catch (e) {
401 failures += 1;
402 reportFailure(source, e.expected, e.actual);
403 }
404 }
405 }
406 }
407 }
408 tick = (new Date()) - tick;
409
410 if (failures > 0) {
411 document.getElementById('status').className = 'alert-box alert';
412 setText(document.getElementById('status'), total + ' tests. ' +
413 'Failures: ' + failures + '. ' + tick + ' ms.');
414 } else {
415 document.getElementById('status').className = 'alert-box success';
416 setText(document.getElementById('status'), total + ' tests. ' +
417 'No failure. ' + tick + ' ms.');
418 }
419 };
420} else {
421 (function () {
422 'use strict';
423
424 var esprima = require('../esprima'),
425 vm = require('vm'),
426 fs = require('fs'),
427 diff = require('json-diff').diffString,
428 total = 0,
429 failures = [],
430 tick = new Date(),
431 expected,
432 header;
433
434 vm.runInThisContext(fs.readFileSync(__dirname + '/test.js', 'utf-8'));
435 vm.runInThisContext(fs.readFileSync(__dirname + '/harmonytest.js', 'utf-8'));
436 vm.runInThisContext(fs.readFileSync(__dirname + '/fbtest.js', 'utf-8'));
437
438 Object.keys(testFixture).forEach(function (category) {
439 Object.keys(testFixture[category]).forEach(function (source) {
440 total += 1;
441 expected = testFixture[category][source];
442 if (!expected.hasOwnProperty('lineNumber') && !expected.hasOwnProperty('result')) {
443 mustHaveLocRange(source, expected, needLoc(expected), needRange(expected), []);
444 }
445 try {
446 runTest(esprima, source, expected);
447 } catch (e) {
448 e.source = source;
449 failures.push(e);
450 }
451 });
452 });
453 tick = (new Date()) - tick;
454
455 header = total + ' tests. ' + failures.length + ' failures. ' +
456 tick + ' ms';
457 if (failures.length) {
458 console.error(header);
459 failures.forEach(function (failure) {
460 try {
461 var expectedObject = JSON.parse(failure.expected);
462 var actualObject = JSON.parse(failure.actual);
463
464 console.error(failure.source + ': Expected\n ' +
465 failure.expected.split('\n').join('\n ') +
466 '\nto match\n ' + failure.actual + '\nDiff:\n' +
467 diff(expectedObject, actualObject));
468 } catch (ex) {
469 console.error(failure.source + ': Expected\n ' +
470 failure.expected.split('\n').join('\n ') +
471 '\nto match\n ' + failure.actual);
472 }
473 });
474 } else {
475 console.log(header);
476 }
477 process.exit(failures.length === 0 ? 0 : 1);
478 }());
479}