marked/test/bench.js

286 lines
6.2 KiB
JavaScript

import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import { isEqual } from './helpers/html-differ.js';
import { loadFiles } from './helpers/load.js';
import { marked as esmMarked } from '../lib/marked.esm.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
let marked;
/**
* Load specs
*/
export function load() {
const dir = resolve(__dirname, './specs/commonmark');
const sections = loadFiles(dir);
let specs = [];
for (const section in sections) {
specs = specs.concat(sections[section].specs);
}
return specs;
}
/**
* Run all benchmarks
*/
export async function runBench(options) {
options = options || {};
const specs = load();
// Non-GFM, Non-pedantic
marked.setOptions({
gfm: false,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: false
});
if (options.marked) {
marked.setOptions(options.marked);
}
await bench('cjs marked', specs, marked.parse);
esmMarked.setOptions({
gfm: false,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: false
});
if (options.marked) {
esmMarked.setOptions(options.marked);
}
await bench('esm marked', specs, esmMarked.parse);
// GFM
marked.setOptions({
gfm: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: false
});
if (options.marked) {
marked.setOptions(options.marked);
}
await bench('cjs marked (gfm)', specs, marked.parse);
esmMarked.setOptions({
gfm: true,
breaks: false,
pedantic: false,
sanitize: false,
smartLists: false
});
if (options.marked) {
esmMarked.setOptions(options.marked);
}
await bench('esm marked (gfm)', specs, esmMarked.parse);
// Pedantic
marked.setOptions({
gfm: false,
breaks: false,
pedantic: true,
sanitize: false,
smartLists: false
});
if (options.marked) {
marked.setOptions(options.marked);
}
await bench('cjs marked (pedantic)', specs, marked.parse);
esmMarked.setOptions({
gfm: false,
breaks: false,
pedantic: true,
sanitize: false,
smartLists: false
});
if (options.marked) {
esmMarked.setOptions(options.marked);
}
await bench('esm marked (pedantic)', specs, esmMarked.parse);
try {
await bench('commonmark', specs, (await (async() => {
const { Parser, HtmlRenderer } = await import('commonmark');
const parser = new Parser();
const writer = new HtmlRenderer();
return function(text) {
return writer.render(parser.parse(text));
};
})()));
} catch (e) {
console.error('Could not bench commonmark. (Error: %s)', e.message);
}
try {
await bench('markdown-it', specs, (await (async() => {
const MarkdownIt = (await import('markdown-it')).default;
const md = new MarkdownIt();
return md.render.bind(md);
})()));
} catch (e) {
console.error('Could not bench markdown-it. (Error: %s)', e.message);
}
}
export async function bench(name, specs, engine) {
const before = process.hrtime();
for (let i = 0; i < 1e3; i++) {
for (const spec of specs) {
await engine(spec.markdown);
}
}
const elapsed = process.hrtime(before);
const ms = prettyElapsedTime(elapsed).toFixed();
let correct = 0;
for (const spec of specs) {
if (await isEqual(spec.html, await engine(spec.markdown))) {
correct++;
}
}
const percent = (correct / specs.length * 100).toFixed(2);
console.log('%s completed in %sms and passed %s%', name, ms, percent);
}
/**
* A simple one-time benchmark
*/
export async function time(options) {
options = options || {};
const specs = load();
if (options.marked) {
marked.setOptions(options.marked);
}
await bench('marked', specs, marked);
}
/**
* Argument Parsing
*/
function parseArg(argv) {
argv = argv.slice(2);
const options = {};
const orphans = [];
function getarg() {
let arg = argv.shift();
if (arg.indexOf('--') === 0) {
// e.g. --opt
arg = arg.split('=');
if (arg.length > 1) {
// e.g. --opt=val
argv.unshift(arg.slice(1).join('='));
}
arg = arg[0];
} else if (arg[0] === '-') {
if (arg.length > 2) {
// e.g. -abc
argv = arg.substring(1).split('').map(ch => {
return `-${ch}`;
}).concat(argv);
arg = argv.shift();
} else {
// e.g. -a
}
} else {
// e.g. foo
}
return arg;
}
const defaults = marked.getDefaults();
while (argv.length) {
const arg = getarg();
switch (arg) {
case '-t':
case '--time':
options.time = true;
break;
case '-m':
case '--minified':
options.minified = true;
break;
default:
if (arg.indexOf('--') === 0) {
const opt = camelize(arg.replace(/^--(no-)?/, ''));
if (!defaults.hasOwnProperty(opt)) {
continue;
}
options.marked = options.marked || {};
if (arg.indexOf('--no-') === 0) {
options.marked[opt] = typeof defaults[opt] !== 'boolean'
? null
: false;
} else {
options.marked[opt] = typeof defaults[opt] !== 'boolean'
? argv.shift()
: true;
}
} else {
orphans.push(arg);
}
break;
}
}
if (orphans.length > 0) {
console.error();
console.error('The following arguments are not used:');
orphans.forEach(arg => console.error(` ${arg}`));
console.error();
}
return options;
}
/**
* Helpers
*/
function camelize(text) {
return text.replace(/(\w)-(\w)/g, (_, a, b) => a + b.toUpperCase());
}
/**
* Main
*/
export default async function main(argv) {
marked = (await import('../lib/marked.cjs')).marked;
const opt = parseArg(argv);
if (opt.minified) {
marked = (await import('../marked.min.js')).marked;
}
if (opt.time) {
await time(opt);
} else {
await runBench(opt);
}
}
/**
* returns time to millisecond granularity
*/
function prettyElapsedTime(hrtimeElapsed) {
const seconds = hrtimeElapsed[0];
const frac = Math.round(hrtimeElapsed[1] / 1e3) / 1e3;
return seconds * 1e3 + frac;
}
process.title = 'marked bench';
main(process.argv.slice());