OLD | NEW |
| (Empty) |
1 #!/usr/bin/env node | |
2 | |
3 /** | |
4 * marked tests | |
5 * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed) | |
6 * https://github.com/chjj/marked | |
7 */ | |
8 | |
9 /** | |
10 * Modules | |
11 */ | |
12 | |
13 var fs = require('fs') | |
14 , path = require('path') | |
15 , marked = require('../'); | |
16 | |
17 /** | |
18 * Load Tests | |
19 */ | |
20 | |
21 function load() { | |
22 var dir = __dirname + '/tests' | |
23 , files = {} | |
24 , list | |
25 , file | |
26 , i | |
27 , l; | |
28 | |
29 list = fs | |
30 .readdirSync(dir) | |
31 .filter(function(file) { | |
32 return path.extname(file) !== '.html'; | |
33 }) | |
34 .sort(function(a, b) { | |
35 a = path.basename(a).toLowerCase().charCodeAt(0); | |
36 b = path.basename(b).toLowerCase().charCodeAt(0); | |
37 return a > b ? 1 : (a < b ? -1 : 0); | |
38 }); | |
39 | |
40 i = 0; | |
41 l = list.length; | |
42 | |
43 for (; i < l; i++) { | |
44 file = path.join(dir, list[i]); | |
45 files[path.basename(file)] = { | |
46 text: fs.readFileSync(file, 'utf8'), | |
47 html: fs.readFileSync(file.replace(/[^.]+$/, 'html'), 'utf8') | |
48 }; | |
49 } | |
50 | |
51 return files; | |
52 } | |
53 | |
54 /** | |
55 * Test Runner | |
56 */ | |
57 | |
58 function runTests(engine, options) { | |
59 if (typeof engine !== 'function') { | |
60 options = engine; | |
61 engine = null; | |
62 } | |
63 | |
64 var engine = engine || marked | |
65 , options = options || {} | |
66 , files = options.files || load() | |
67 , complete = 0 | |
68 , failed = 0 | |
69 , failures = [] | |
70 , keys = Object.keys(files) | |
71 , i = 0 | |
72 , len = keys.length | |
73 , filename | |
74 , file | |
75 , flags | |
76 , text | |
77 , html | |
78 , j | |
79 , l; | |
80 | |
81 if (options.marked) { | |
82 marked.setOptions(options.marked); | |
83 } | |
84 | |
85 main: | |
86 for (; i < len; i++) { | |
87 filename = keys[i]; | |
88 file = files[filename]; | |
89 | |
90 if (marked._original) { | |
91 marked.defaults = marked._original; | |
92 delete marked._original; | |
93 } | |
94 | |
95 flags = filename.split('.').slice(1, -1); | |
96 if (flags.length) { | |
97 marked._original = marked.defaults; | |
98 marked.defaults = {}; | |
99 Object.keys(marked._original).forEach(function(key) { | |
100 marked.defaults[key] = marked._original[key]; | |
101 }); | |
102 flags.forEach(function(key) { | |
103 var val = true; | |
104 if (key.indexOf('no') === 0) { | |
105 key = key.substring(2); | |
106 val = false; | |
107 } | |
108 if (marked.defaults.hasOwnProperty(key)) { | |
109 marked.defaults[key] = val; | |
110 } | |
111 }); | |
112 } | |
113 | |
114 try { | |
115 text = engine(file.text).replace(/\s/g, ''); | |
116 html = file.html.replace(/\s/g, ''); | |
117 } catch(e) { | |
118 console.log('%s failed.', filename); | |
119 throw e; | |
120 } | |
121 | |
122 j = 0; | |
123 l = html.length; | |
124 | |
125 for (; j < l; j++) { | |
126 if (text[j] !== html[j]) { | |
127 failed++; | |
128 failures.push(filename); | |
129 | |
130 text = text.substring( | |
131 Math.max(j - 30, 0), | |
132 Math.min(j + 30, text.length)); | |
133 | |
134 html = html.substring( | |
135 Math.max(j - 30, 0), | |
136 Math.min(j + 30, html.length)); | |
137 | |
138 console.log( | |
139 '\n#%d. %s failed at offset %d. Near: "%s".\n', | |
140 i + 1, filename, j, text); | |
141 | |
142 console.log('\nGot:\n%s\n', text.trim() || text); | |
143 console.log('\nExpected:\n%s\n', html.trim() || html); | |
144 | |
145 if (options.stop) { | |
146 break main; | |
147 } | |
148 | |
149 continue main; | |
150 } | |
151 } | |
152 | |
153 complete++; | |
154 console.log('#%d. %s completed.', i + 1, filename); | |
155 } | |
156 | |
157 console.log('%d/%d tests completed successfully.', complete, len); | |
158 if (failed) console.log('%d/%d tests failed.', failed, len); | |
159 | |
160 // Tests currently failing. | |
161 if (~failures.indexOf('def_blocks.text') | |
162 && ~failures.indexOf('double_link.text') | |
163 && ~failures.indexOf('gfm_code_hr_list.text')) { | |
164 failed -= 3; | |
165 } | |
166 | |
167 return !failed; | |
168 } | |
169 | |
170 /** | |
171 * Benchmark a function | |
172 */ | |
173 | |
174 function bench(name, func) { | |
175 var files = bench.files || load(); | |
176 | |
177 if (!bench.files) { | |
178 bench.files = files; | |
179 | |
180 // Change certain tests to allow | |
181 // comparison to older benchmark times. | |
182 fs.readdirSync(__dirname + '/new').forEach(function(name) { | |
183 if (path.extname(name) === '.html') return; | |
184 if (name === 'main.text') return; | |
185 delete files[name]; | |
186 }); | |
187 | |
188 files['backslash_escapes.text'] = { | |
189 text: 'hello world \\[how](are you) today' | |
190 }; | |
191 | |
192 files['main.text'].text = files['main.text'].text.replace('* * *\n\n', ''); | |
193 } | |
194 | |
195 var start = Date.now() | |
196 , times = 1000 | |
197 , keys = Object.keys(files) | |
198 , i | |
199 , l = keys.length | |
200 , filename | |
201 , file; | |
202 | |
203 while (times--) { | |
204 for (i = 0; i < l; i++) { | |
205 filename = keys[i]; | |
206 file = files[filename]; | |
207 func(file.text); | |
208 } | |
209 } | |
210 | |
211 console.log('%s completed in %dms.', name, Date.now() - start); | |
212 } | |
213 | |
214 /** | |
215 * Benchmark all engines | |
216 */ | |
217 | |
218 function runBench(options) { | |
219 var options = options || {}; | |
220 | |
221 // Non-GFM, Non-pedantic | |
222 marked.setOptions({ | |
223 gfm: false, | |
224 tables: false, | |
225 breaks: false, | |
226 pedantic: false, | |
227 sanitize: false, | |
228 smartLists: false | |
229 }); | |
230 if (options.marked) { | |
231 marked.setOptions(options.marked); | |
232 } | |
233 bench('marked', marked); | |
234 | |
235 // GFM | |
236 marked.setOptions({ | |
237 gfm: true, | |
238 tables: false, | |
239 breaks: false, | |
240 pedantic: false, | |
241 sanitize: false, | |
242 smartLists: false | |
243 }); | |
244 if (options.marked) { | |
245 marked.setOptions(options.marked); | |
246 } | |
247 bench('marked (gfm)', marked); | |
248 | |
249 // Pedantic | |
250 marked.setOptions({ | |
251 gfm: false, | |
252 tables: false, | |
253 breaks: false, | |
254 pedantic: true, | |
255 sanitize: false, | |
256 smartLists: false | |
257 }); | |
258 if (options.marked) { | |
259 marked.setOptions(options.marked); | |
260 } | |
261 bench('marked (pedantic)', marked); | |
262 | |
263 // robotskirt | |
264 try { | |
265 bench('robotskirt', (function() { | |
266 var rs = require('robotskirt'); | |
267 return function(text) { | |
268 var parser = rs.Markdown.std(); | |
269 return parser.render(text); | |
270 }; | |
271 })()); | |
272 } catch (e) { | |
273 console.log('Could not bench robotskirt.'); | |
274 } | |
275 | |
276 // showdown | |
277 try { | |
278 bench('showdown (reuse converter)', (function() { | |
279 var Showdown = require('showdown'); | |
280 var convert = new Showdown.converter(); | |
281 return function(text) { | |
282 return convert.makeHtml(text); | |
283 }; | |
284 })()); | |
285 bench('showdown (new converter)', (function() { | |
286 var Showdown = require('showdown'); | |
287 return function(text) { | |
288 var convert = new Showdown.converter(); | |
289 return convert.makeHtml(text); | |
290 }; | |
291 })()); | |
292 } catch (e) { | |
293 console.log('Could not bench showdown.'); | |
294 } | |
295 | |
296 // markdown.js | |
297 try { | |
298 bench('markdown.js', require('markdown').parse); | |
299 } catch (e) { | |
300 console.log('Could not bench markdown.js.'); | |
301 } | |
302 } | |
303 | |
304 /** | |
305 * A simple one-time benchmark | |
306 */ | |
307 | |
308 function time(options) { | |
309 var options = options || {}; | |
310 if (options.marked) { | |
311 marked.setOptions(options.marked); | |
312 } | |
313 bench('marked', marked); | |
314 } | |
315 | |
316 /** | |
317 * Markdown Test Suite Fixer | |
318 * This function is responsible for "fixing" | |
319 * the markdown test suite. There are | |
320 * certain aspects of the suite that | |
321 * are strange or might make tests | |
322 * fail for reasons unrelated to | |
323 * conformance. | |
324 */ | |
325 | |
326 function fix(options) { | |
327 ['tests', 'original', 'new'].forEach(function(dir) { | |
328 try { | |
329 fs.mkdirSync(path.resolve(__dirname, dir), 0755); | |
330 } catch (e) { | |
331 ; | |
332 } | |
333 }); | |
334 | |
335 // rm -rf tests | |
336 fs.readdirSync(path.resolve(__dirname, 'tests')).forEach(function(file) { | |
337 fs.unlinkSync(path.resolve(__dirname, 'tests', file)); | |
338 }); | |
339 | |
340 // cp -r original tests | |
341 fs.readdirSync(path.resolve(__dirname, 'original')).forEach(function(file) { | |
342 var nfile = file; | |
343 if (file.indexOf('hard_wrapped_paragraphs_with_list_like_lines.') === 0) { | |
344 nfile = file.replace(/\.(text|html)$/, '.nogfm.$1'); | |
345 } | |
346 fs.writeFileSync(path.resolve(__dirname, 'tests', nfile), | |
347 fs.readFileSync(path.resolve(__dirname, 'original', file))); | |
348 }); | |
349 | |
350 // node fix.js | |
351 var dir = __dirname + '/tests'; | |
352 | |
353 fs.readdirSync(dir).filter(function(file) { | |
354 return path.extname(file) === '.html'; | |
355 }).forEach(function(file) { | |
356 var file = path.join(dir, file) | |
357 , html = fs.readFileSync(file, 'utf8'); | |
358 | |
359 // fix unencoded quotes | |
360 html = html | |
361 .replace(/='([^\n']*)'(?=[^<>\n]*>)/g, '=&__APOS__;$1&__APOS__;') | |
362 .replace(/="([^\n"]*)"(?=[^<>\n]*>)/g, '=&__QUOT__;$1&__QUOT__;') | |
363 .replace(/"/g, '"') | |
364 .replace(/'/g, ''') | |
365 .replace(/&__QUOT__;/g, '"') | |
366 .replace(/&__APOS__;/g, '\''); | |
367 | |
368 // add heading id's | |
369 html = html.replace(/<(h[1-6])>([^<]+)<\/\1>/g, function(s, h, text) { | |
370 var id = text | |
371 .replace(/'/g, '\'') | |
372 .replace(/"/g, '"') | |
373 .replace(/>/g, '>') | |
374 .replace(/</g, '<') | |
375 .replace(/&/g, '&'); | |
376 | |
377 id = id.toLowerCase().replace(/[^\w]+/g, '-'); | |
378 | |
379 return '<' + h + ' id="' + id + '">' + text + '</' + h + '>'; | |
380 }); | |
381 | |
382 fs.writeFileSync(file, html); | |
383 }); | |
384 | |
385 // turn <hr /> into <hr> | |
386 fs.readdirSync(dir).forEach(function(file) { | |
387 var file = path.join(dir, file) | |
388 , text = fs.readFileSync(file, 'utf8'); | |
389 | |
390 text = text.replace(/(<|<)hr\s*\/(>|>)/g, '$1hr$2'); | |
391 | |
392 fs.writeFileSync(file, text); | |
393 }); | |
394 | |
395 // markdown does some strange things. | |
396 // it does not encode naked `>`, marked does. | |
397 (function() { | |
398 var file = dir + '/amps_and_angles_encoding.html'; | |
399 var html = fs.readFileSync(file, 'utf8') | |
400 .replace('6 > 5.', '6 > 5.'); | |
401 | |
402 fs.writeFileSync(file, html); | |
403 })(); | |
404 | |
405 // cp new/* tests/ | |
406 fs.readdirSync(path.resolve(__dirname, 'new')).forEach(function(file) { | |
407 fs.writeFileSync(path.resolve(__dirname, 'tests', file), | |
408 fs.readFileSync(path.resolve(__dirname, 'new', file))); | |
409 }); | |
410 } | |
411 | |
412 /** | |
413 * Argument Parsing | |
414 */ | |
415 | |
416 function parseArg(argv) { | |
417 var argv = process.argv.slice(2) | |
418 , options = {} | |
419 , orphans = [] | |
420 , arg; | |
421 | |
422 function getarg() { | |
423 var arg = argv.shift(); | |
424 | |
425 if (arg.indexOf('--') === 0) { | |
426 // e.g. --opt | |
427 arg = arg.split('='); | |
428 if (arg.length > 1) { | |
429 // e.g. --opt=val | |
430 argv.unshift(arg.slice(1).join('=')); | |
431 } | |
432 arg = arg[0]; | |
433 } else if (arg[0] === '-') { | |
434 if (arg.length > 2) { | |
435 // e.g. -abc | |
436 argv = arg.substring(1).split('').map(function(ch) { | |
437 return '-' + ch; | |
438 }).concat(argv); | |
439 arg = argv.shift(); | |
440 } else { | |
441 // e.g. -a | |
442 } | |
443 } else { | |
444 // e.g. foo | |
445 } | |
446 | |
447 return arg; | |
448 } | |
449 | |
450 while (argv.length) { | |
451 arg = getarg(); | |
452 switch (arg) { | |
453 case '-f': | |
454 case '--fix': | |
455 case 'fix': | |
456 options.fix = true; | |
457 break; | |
458 case '-b': | |
459 case '--bench': | |
460 options.bench = true; | |
461 break; | |
462 case '-s': | |
463 case '--stop': | |
464 options.stop = true; | |
465 break; | |
466 case '-t': | |
467 case '--time': | |
468 options.time = true; | |
469 break; | |
470 default: | |
471 if (arg.indexOf('--') === 0) { | |
472 opt = camelize(arg.replace(/^--(no-)?/, '')); | |
473 if (!marked.defaults.hasOwnProperty(opt)) { | |
474 continue; | |
475 } | |
476 options.marked = options.marked || {}; | |
477 if (arg.indexOf('--no-') === 0) { | |
478 options.marked[opt] = typeof marked.defaults[opt] !== 'boolean' | |
479 ? null | |
480 : false; | |
481 } else { | |
482 options.marked[opt] = typeof marked.defaults[opt] !== 'boolean' | |
483 ? argv.shift() | |
484 : true; | |
485 } | |
486 } else { | |
487 orphans.push(arg); | |
488 } | |
489 break; | |
490 } | |
491 } | |
492 | |
493 return options; | |
494 } | |
495 | |
496 /** | |
497 * Helpers | |
498 */ | |
499 | |
500 function camelize(text) { | |
501 return text.replace(/(\w)-(\w)/g, function(_, a, b) { | |
502 return a + b.toUpperCase(); | |
503 }); | |
504 } | |
505 | |
506 /** | |
507 * Main | |
508 */ | |
509 | |
510 function main(argv) { | |
511 var opt = parseArg(); | |
512 | |
513 if (opt.fix) { | |
514 return fix(opt); | |
515 } | |
516 | |
517 if (opt.bench) { | |
518 return runBench(opt); | |
519 } | |
520 | |
521 if (opt.time) { | |
522 return time(opt); | |
523 } | |
524 | |
525 return runTests(opt); | |
526 } | |
527 | |
528 /** | |
529 * Execute | |
530 */ | |
531 | |
532 if (!module.parent) { | |
533 process.title = 'marked'; | |
534 process.exit(main(process.argv.slice()) ? 0 : 1); | |
535 } else { | |
536 exports = main; | |
537 exports.main = main; | |
538 exports.runTests = runTests; | |
539 exports.runBench = runBench; | |
540 exports.load = load; | |
541 exports.bench = bench; | |
542 module.exports = exports; | |
543 } | |
OLD | NEW |