From 551c3d30a25cbc74e0a620978f0e3e5f3bba937b Mon Sep 17 00:00:00 2001 From: Alexey Yaroshevich Date: Thu, 31 Mar 2016 14:45:02 +0300 Subject: [PATCH] perf(tmpl-spec): improve runtime Rework a little where and how clearRequire-require combo works. Just it improved performance a lot. Also stopped usign this combo for references. --- lib/assets/it.jst | 6 +- lib/assets/tmpl-spec.jst | 102 +++++++++------------------- lib/node-configurator.js | 20 ++++-- lib/techs/tmpl-spec.js | 141 +++++++++++++++------------------------ 4 files changed, 101 insertions(+), 168 deletions(-) diff --git a/lib/assets/it.jst b/lib/assets/it.jst index 58cb2e2..bb5e7f1 100644 --- a/lib/assets/it.jst +++ b/lib/assets/it.jst @@ -10,12 +10,14 @@ if (saveHtml) { } %> it('should be equal `${ it }` by ${ engine.name }${ titleSuffix }', function (<% if (saveHtml || engine.async) { %>done<% } %>) { + ${ before } + var bemjson = references['${ it }']${ subreference }.bemjson, expected = references['${ it }']${ subreference }.html;<% if (!engine.async) { %> // sync mode - var actual = engines['${ engine.name }']${ subreference }.apply(bemjson); + var actual = engine.apply(bemjson); <% if (saveHtml) { %> @@ -25,7 +27,7 @@ if (!engine.async) { %> <% } } else { // engine.async %> // async mode - engines['${ engine.name }']${ subreference }.apply(bemjson, function(errs, actual) { + engine.apply(bemjson, function(errs, actual) { if (errs !== null) { done(errs); return; diff --git a/lib/assets/tmpl-spec.jst b/lib/assets/tmpl-spec.jst index 5b49bf3..38a96ac 100644 --- a/lib/assets/tmpl-spec.jst +++ b/lib/assets/tmpl-spec.jst @@ -21,104 +21,64 @@ var assert = require('assert'), clearRequire = require('${ paths['clear-require'] }'), HtmlDiffer = require('${ paths['html-differ'] }').HtmlDiffer, htmlDiffer = new HtmlDiffer(${ JSON.stringify(diffOpts) }), - referencesFilename = require.resolve('${ paths.references }'); + referencesJSON = JSON.stringify(require('${ paths.references }')), + getReferences = function () { + return JSON.parse(referencesJSON); + }; describe('${ describe }', function() { - var engines = {}, - references; - - beforeEach(function () { - // reload references - clearRequire(referencesFilename); - references = require(referencesFilename); - }); - -<% if (!(langs && langs.length)) { %> - // - // no langs mode - // - - beforeEach(function () { - // reload engines artifacts - engines = {}; - <% _.forEach(engines, function(engine) { - reRequire('engines', engine); - }); %> - }); - <% _.forEach(its, function(it) { %> describe('${ it }', function() { <% _.forEach(engines, function(engine) { + var lang = engine.lang; + print(template('it', { it: it, + before: [ + 'var references = getReferences();', + 'var engine = ' + reRequire(engine.target, engine.exportName) + ';', + ].join('\n'), engine: engine, - lang: null, + lang: lang.isReal ? lang.name : null, saveHtml: saveHtml })); }); %> - }); // describe ${ it } - <% }); // _.forEach its %> - -<% } else { // langs.length > 0 %> - // - // langs mode - // - - <% _.forEach(langs, function (lang) { %> - describe('with lang `${ lang }`', function() { - - beforeEach(function () { - <% _.forEach(engines, function(engine) { - var engineObject = 'engines[\'' + engine.name + '\']'; - print(engineObject + ' = {};\n'); - reRequire(engineObject, engine.langs.filter(function (_lang) { return lang === _lang.name; })[0]); - }); %> - }); - - <% _.forEach(its, function(it) { %> - describe('${ it }', function() { - - <% _.forEach(engines, function(engine) { - print(template('it', { - it: it, - engine: engine, - lang: lang, - saveHtml: saveHtml - })); - }); %> - - }); - <% }); %> - }); <% }); %> -<% }; // /if langs %> - }); // /describe ${ describe } -<% -function reRequire(where, what) { - var name = what.name, - target = what.target, - exportName = what.exportName; - print('clearRequire(require.resolve(\'' + target + '\'));\n'); - print(where + '[\'' + name + '\'] = require(\'' + target + '\')' + exportName + ';\n'); -} -%> + function assertHtml(actual, expected, done, filename) { <% if (saveHtml) {%> saveHtmlFile(filename, actual, function() { <% } %> if (htmlDiffer.isEqual(actual, expected)) { + // Throw error if actual is empty assert.ok(actual); done && done(null); } else { - - assert.fail(actual, expected, null, '\n'); + assert.fail(actual, expected, null, '\n'); } <% if (saveHtml) {%> }); <% } %> } +<% +function reRequire(target, exportName) { + return 'reRequire(' + JSON.stringify(target) + ', ' + JSON.stringify(exportName) + ')'; +} +%> +function reRequire(file, exportName) { + try { + file = require.resolve(file); + clearRequire(file); + var res = require(file); + exportName && (res = res[exportName]); + return res; + } catch (e) { + console.error(e.stack); + return { apply: function() { return e.stack; } }; + } +} diff --git a/lib/node-configurator.js b/lib/node-configurator.js index d12cb7d..065de2f 100644 --- a/lib/node-configurator.js +++ b/lib/node-configurator.js @@ -39,6 +39,7 @@ exports.configure = function (config, options) { engines = options.engines, coverageEngines = options.coverage.engines, engineTargets = [], + specTargets = [], nodePath = nodeConfig.getPath(), isCoverageBundle = coverageRe.test(nodePath), sourceLevels = [].concat(options.sourceLevels, options.sublevels[nodePath] || []); @@ -71,6 +72,7 @@ exports.configure = function (config, options) { // Add engines' techs engines.forEach(function (engine) { + engine.hasOwnProperty('needsCoverage') || (engine.needsCoverage = _.contains(coverageEngines, engine.name)); nodeConfig.addTech([engine.tech, engine.options]); }); @@ -107,7 +109,7 @@ exports.configure = function (config, options) { } // Add instrumented tech if we need coverage - if (_.contains(coverageEngines, engine.name)) { + if (engine.needsCoverage) { target = destTarget; destTarget = instrumentedTarget(destTarget); @@ -115,21 +117,25 @@ exports.configure = function (config, options) { } // For each engine and lang we have one target: - engineTargets.push(destTarget); nodeConfig.addTarget(destTarget); + + engineTargets.push(destTarget); + specTargets.push({ + target: destTarget, + engine: engine, + lang: { name: lang, isMock: isMock, isReal: isReal } + }); }); }); nodeConfig.addTechs([ [references, { dirsTarget: '?.base.dirs' }], [spec, { - engines: engines, - langs: langs, - diffOpts: options.diffOpts, engineTargets: engineTargets, + specTargets: specTargets, + diffOpts: options.diffOpts, saveHtml: options.saveHtml, - coverageEngines: coverageEngines, - coverageBundle: isCoverageBundle + isCoverageBundle: isCoverageBundle }] ]); diff --git a/lib/techs/tmpl-spec.js b/lib/techs/tmpl-spec.js index 2d952a7..e76ffe1 100644 --- a/lib/techs/tmpl-spec.js +++ b/lib/techs/tmpl-spec.js @@ -1,31 +1,34 @@ var path = require('path'), + + _ = require('lodash'), vow = require('vow'), + enb = require('enb'), buildFlow = enb.buildFlow || require('enb/lib/build-flow'), vfs = enb.asyncFs || require('enb/lib/fs/async-fs'), + assetsDirname = path.join(__dirname, '..', 'assets'), - readAssets = vow.all([ - vfs.read(path.join(assetsDirname, 'tmpl-spec.jst'), 'utf-8'), - vfs.read(path.join(assetsDirname, 'it.jst'), 'utf-8') - ]), - lodash = require('lodash'), - template = lodash.template, - contains = lodash.contains, + + // Helpers for tests: clearRequireFilename = require.resolve('clear-require'), htmlDifferFilename = require.resolve('html-differ'), jsBeautifyFilename = require.resolve('js-beautify'), - instrumentedTarget = require('../util').instrumentedTarget, - backSlash = new RegExp('\\\\', 'g'); + relativePath = function (dirname, filename) { + return './' + path.relative(dirname, filename).replace(/\\\\/g, '/'); + }, + + readAssets = vow.all([ + vfs.read(path.join(assetsDirname, 'tmpl-spec.jst'), 'utf-8'), + vfs.read(path.join(assetsDirname, 'it.jst'), 'utf-8') + ]); module.exports = buildFlow.create() .name('tmpl-spec') .target('target', '?.tmpl-spec.js') - .defineRequiredOption('engines') - .defineOption('langs', []) - .defineOption('saveHtml', false) + .defineRequiredOption('specTargets') .defineOption('diffOpts') - .defineOption('coverageEngines', []) - .defineOption('coverageBundle', false) + .defineOption('saveHtml', false) + .defineOption('isCoverageBundle', false) .useSourceFilename('references', '?.references.js') .useSourceListFilenames('engineTargets', []) .needRebuild(function (cache) { @@ -38,89 +41,51 @@ module.exports = buildFlow.create() var node = this.node, nodePath = node.getPath(), nodeDirname = node.getDir(), - references = require(referencesFilename), - engines = this._engines, - langs = this._langs, + + specTargets = this._specTargets, diffOpts = this._diffOpts, - oneLang = langs === true, - manyLangs = Array.isArray(langs) && langs.length > 0, saveHtml = this._saveHtml, - coverageEngines = this._coverageEngines, - coverageBundle = this._coverageBundle, - its = !this._coverageBundle ? Object.keys(references) : []; - return readAssets.spread(function (asset, it) { - var templates = { - it: it - }, - data = { - describe: path.basename(nodePath) + ' (' + path.dirname(nodePath) + ')', - its: its, - diffOpts: diffOpts, - engines: engines.map(function (engine) { - var target = contains(coverageEngines, engine.name) ? - instrumentedTarget(engine.target) : engine.target, - - exportName = engine.exportName ? '.' + engine.exportName : ''; - - if (oneLang) { - target = coverageBundle ? - target.replace('.js.instr.js', '.lang.js.instr.js') : - target.replace('.js', '.lang.js'); - } + references = require(referencesFilename), + its = !this._isCoverageBundle ? Object.keys(references) : [], - target = './' + node.unmaskTargetName(target); + data = { + describe: path.basename(nodePath) + ' (' + path.dirname(nodePath) + ')', + its: its, + engines: specTargets.map(function (specTarget) { + var target = specTarget.target, + engine = specTarget.engine, + exportName = engine.exportName; - if (manyLangs) { - return { - name: engine.name, - langs: langs.map(function (lang) { - var langTarget = target.replace('.js', '.' + lang + '.js'); - return { - name: lang, - async: engine.async, - target: langTarget, - exportName: exportName - }; - }) - }; - } + target = './' + node.unmaskTargetName(target); - return { - name: engine.name, - async: engine.async, - target: target, - exportName: exportName - }; - }), - langs: langs, - paths: { - references: [ - '.', path.relative(nodeDirname, referencesFilename).replace(backSlash, '/') - ].join('/'), - 'clear-require': [ - '.', path.relative(nodeDirname, clearRequireFilename).replace(backSlash, '/') - ].join('/'), - 'html-differ': [ - '.', path.relative(nodeDirname, htmlDifferFilename).replace(backSlash, '/') - ].join('/'), - 'js-beautify': [ - '.', path.relative(nodeDirname, jsBeautifyFilename).replace(backSlash, '/') - ].join('/') - }, - saveHtml: saveHtml - }, - compiled = template(asset, { - imports: { - template: function (name, data) { - var compiled = template(templates[name]); + return { + name: engine.name, + async: engine.async, + lang: specTarget.lang, + target: target, + exportName: exportName + }; + }), + paths: _.mapValues({ + references: referencesFilename, + 'clear-require': clearRequireFilename, + 'html-differ': htmlDifferFilename, + 'js-beautify': jsBeautifyFilename + }, _.partial(relativePath, nodeDirname)), + diffOpts: diffOpts, + saveHtml: saveHtml + }; - return compiled(data); - } + return readAssets.spread(function (asset, it) { + var templates = { it: it }, + imports = { + template: function (name, data) { + return _.template(templates[name])(data); } - }); + }; - return compiled(data); + return _.template(asset, { imports: imports })(data); }); }) .createTech();