kab
Version:
Know About Better - test your code
303 lines (255 loc) • 8.36 kB
JavaScript
/**
* @file edp-test 的静态文件服务
* @author chris<wfsr@foxmail.com>
*/
var fs = require('fs');
var path = require('path');
var istanbul = require('babel-istanbul');
var edp = require('edp-core');
var log = require('./log');
var config = require('./config').config;
/**
* 生成所有样式的 HTML 代码
*
* @param {Array.<string>} files 所有指定的 CSS 文件路径
* @return {string} 生成的 HTML 代码
*/
function buildCSSes(files) {
var html = '';
files.forEach(function (file) {
html += '<link rel="stylesheet" href="' + file + '">\n';
});
return html;
}
function buildStyles(codes) {
var html = '<style>\n';
html += codes.join('\n');
html += '\n</style>';
return html;
}
/**
* 生成全局 require 中的所有 Spec 代码
*
* @param {Array.<string>} files 所有指定的 Spec 文件路径
* @return {string} 生成的 Spec 参数
*/
function buildSpecs(files) {
var specs = [];
files.forEach(function (file) {
specs.push('\'' + file.replace(/\.(js|ts|dart|coffee)$/, '') + '\'');
});
return specs.join(', ');
}
function buildCases(file, opts) {
var code = '\nsuite = new Benchmark.Suite(\'' + file.title + '\');\n';
if (file.js[0]) {
code += 'Benchmark.prototype.setup = function () {\n' + file.js[0] + '\n};\n';
}
if (file.js[1]) {
code += 'Benchmark.prototype.teardown = function () {\n' + file.js[1] + '\n};\n';
}
var count = (opts.count | 0) || 10;
code += file.cases.map(function (item) {
return ''
+ 'suite.add(\''
+ item.name
+ '\', function () {\n'
+ item.js.join('\n')
+ '\n},'
+ '{initCount: ' + count + '});\n';
}).join('');
return code;
}
/**
* 根据配置获取 runner、context 与 debug 的 HTML 模板内容
*
* @param {string} type 模板类型
* @return {string} 模板内容
*/
function getTemplate(type) {
var clientDir = path.resolve(__dirname, '../client/');
var file;
if (config.templates) {
var tplFile = config.templates[type];
tplFile = tplFile && path.resolve(process.cwd(), 'test/' + tplFile);
if (fs.existsSync(tplFile)) {
file = tplFile;
}
}
file = file || path.resolve(clientDir, type + '.html');
return fs.readFileSync(file, 'utf-8');
}
/**
* 判断指定路径是否排除不作代码覆盖统计
*
* @param {string} pathname js 文件相对根目录的访问路径
* @return {boolean} 是否命中被排除
*/
function isExcluded(pathname) {
var exclude = config.coverageReporter.exclude || [];
if (!exclude.length) {
return false;
}
return edp.glob.match(pathname, exclude);
}
function buildTPL(tpl, files, frameworks, requireConfig, opts) {
return tpl
.replace(/<\!\-\-%css%\-\->/ig, function () {
return buildCSSes(files.css || []);
})
.replace(/<\!\-\-%style%\-\->/ig, function () {
return buildStyles(files.style || []);
})
.replace(/\s*\/\*specs\*\/\s*/ig, function () {
return buildSpecs(files.js || []);
})
.replace(/\s*\/\*cases\*\/\s*/ig, function () {
return buildCases(files, opts);
})
.replace(/<\!\-\-%html%\-\->/ig, function () {
return (files.html || []).join('\n');
})
.replace(/\s*\/\*requireConfig\*\/\s*/ig, requireConfig)
.replace(/<\!\-\-%frameworks%\-\->/ig, frameworks);
}
/**
* 缓存 runner、context 与 debug 的 HTML 内容
*
* @namespace
*/
var cached = {};
/**
* 生成 runner、context 与 debug 的 HTML 内容
*
* @param {EventEmitter} emitter 全局事件对象实例
* @return {boolean} 是否成功生成
*/
exports.build = function (emitter) {
log.trace('Build runner...');
var contextTpl = getTemplate('context');
var debugTpl = getTemplate('debug');
var files = require('./files').readAll(config.files, config.exclude);
if (files.js.length < 1 && (config.singleRun || !config.watch)) {
return false;
}
var frameworks = require('./frameworks').render(config.frameworks);
var requireConfig = JSON.stringify(config.requireConfig);
config.requireConfig.urlArgs = 'debug=' + (+new Date()).toString(36);
var debugRequireConfig = JSON.stringify(config.requireConfig);
contextTpl = buildTPL(contextTpl, files, frameworks, requireConfig);
debugTpl = buildTPL(debugTpl, files, frameworks, debugRequireConfig);
cached.runner = getTemplate('runner');
cached.context = contextTpl;
cached.debug = debugTpl;
log.trace('Runner build.');
if (config.watch) {
var watcher = require('chokidar').watch(files.css);
watcher.add(files.js);
watcher.on('change', function (path) {
log.trace('%s changed.', path);
emitter.emit('change', path);
});
}
return true;
};
exports.bench = function (emitter, args, opts) {
log.trace('Build runner...');
var contextTpl = getTemplate('bm-context');
var debugTpl = getTemplate('bm-debug');
var files = require('./markdowns').readAll(args);
var filesKeys = Object.keys(files);
if (filesKeys.length < 1 && (config.singleRun || !config.watch)) {
return false;
}
var frameworks = require('./frameworks').render(['benchmark']);
var requireConfig = JSON.stringify(config.requireConfig);
filesKeys.forEach(function (key) {
var file = files[key];
var tpl = buildTPL(contextTpl, file, frameworks, requireConfig, opts);
cached['bm-context-' + key] = tpl;
cached['bm-debug-' + key] = buildTPL(debugTpl, file, frameworks, requireConfig, opts);
});
cached['bm-runner'] = cached['bm-runner']
|| getTemplate('bm-runner').replace(/\s*\/\*pages\*\/\s*/ig, function () {
return '\'' + filesKeys.join('\', \'') + '\'';
});
log.trace('Runner build.');
if (config.watch) {
var watcher = require('chokidar').watch(Object.keys(files));
watcher.on('change', function (path) {
log.trace('%s changed.', path);
emitter.emit('change', path);
});
}
return true;
};
/**
* 简单的文件扩展名与 MIME 映射表
*
* @namespace
*/
var mimes = {
js: 'application/javascript',
css: 'text/css',
html: 'text/html'
};
/**
* 处理 runner、context 与 debug 等内置页面
*
* @param {string} name cached 对应的键名
* @return {Function}
*/
exports.handler = function (name) {
return function (context) {
context.header['content-type'] = mimes.html;
var query = context.request.query;
var key = name;
if ((key === 'bm-context' || key === 'bm-debug') && query && query.name) {
key += '-' + query.name;
}
context.content = cached[key] || '';
};
};
/**
* 使用 instanbul instrument 代码
*
* @return {Functioin}
*/
exports.istanbul = function () {
return function (context) {
var pathname = context.request.pathname;
if (context.request.query.debug) {
return;
}
var docRoot = context.conf.documentRoot;
var src = docRoot + pathname;
context.header['content-type'] = mimes.js;
if (context.map) {
var maps = global.maps;
if (!maps) {
maps = global.maps = {};
}
maps[src] = context.map;
delete context.map;
}
var content = context.content || fs.readFileSync(src, 'utf-8');
context.content = isExcluded(pathname)
? content
: new istanbul.Instrumenter().instrumentSync(content, src);
};
};
/**
* 提供 jasmine 之类的静态文件服务
*
* @return {Function}
*/
exports.serve = function () {
return function (context) {
var pathname = context.request.pathname;
var matches = pathname.match(/^\/.kab\/([^\?]+\.([^\?\.]+))/i);
var src = path.resolve(__dirname, '../client', matches[1]);
var ext = path.extname(pathname).slice(1).toLowerCase();
context.header['content-type'] = mimes[ext] || mimes.html;
context.content = fs.readFileSync(src, 'utf-8');
};
};