| 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 |