diff --git a/lib/cmd.js b/lib/cmd.js index 419e194d5..eb0294132 100644 --- a/lib/cmd.js +++ b/lib/cmd.js @@ -188,12 +188,13 @@ class Cmd { test() { program .command('test [file]') + .option('-d , --gasDetails', __('When set, will print the gas cost for each contract deploy')) .option('--locale [locale]', __('language to use (default: en)')) .option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'warn') .description(__('run tests')) .action(function (file, options) { i18n.setOrDetectLocale(options.locale); - embark.runTests({file, loglevel: options.loglevel}); + embark.runTests({file, loglevel: options.loglevel, gasDetails: options.gasDetails}); }); } diff --git a/lib/contracts/contract_deployer.js b/lib/contracts/contract_deployer.js index b97bb4750..50140085b 100644 --- a/lib/contracts/contract_deployer.js +++ b/lib/contracts/contract_deployer.js @@ -242,6 +242,7 @@ class ContractDeployer { self.logger.info(contract.className.bold.cyan + " " + __("deployed at").green + " " + receipt.contractAddress.bold.cyan); contract.deployedAddress = receipt.contractAddress; contract.transactionHash = receipt.transactionHash; + receipt.className = contract.className; self.events.emit("deploy:contract:receipt", receipt); self.events.emit("deploy:contract:deployed", contract); diff --git a/lib/core/events.js b/lib/core/events.js index 42f6958a5..6bc9bcd1f 100644 --- a/lib/core/events.js +++ b/lib/core/events.js @@ -14,7 +14,7 @@ function log(eventType, eventName) { //console.log(eventType, eventName); } -EventEmitter.prototype._maxListeners = 300; +EventEmitter.prototype._maxListeners = 350; const _on = EventEmitter.prototype.on; const _setHandler = EventEmitter.prototype.setHandler; diff --git a/lib/tests/reporter.js b/lib/tests/reporter.js new file mode 100644 index 000000000..75d8ff01c --- /dev/null +++ b/lib/tests/reporter.js @@ -0,0 +1,145 @@ +const Base = require('mocha/lib/reporters/base'); +const ms = require('mocha/lib/ms'); +const color = Base.color; + +class EmbarkSpec extends Base { + constructor(runner, options) { + super(runner, options); + + const self = this; + self.embarkEvents = options.reporterOptions.events; + self.gasDetails = options.reporterOptions.gasDetails; + self.gasLimit = options.reporterOptions.gasLimit; + let indents = 0; + let n = 0; + self.stats.totalGasCost = 0; + self.stats.test = {}; + self.stats.test.gasUsed = 0; + + function onContractReceipt(receipt) { + const fmt = color('bright pass', ' ') + + color('suite', ' %s') + + color('light', ' deployed for ') + + color(self.getGasColor(receipt.gasUsed), '%s') + + color('light', ' gas'); + + console.log(fmt, receipt.className, receipt.gasUsed); + } + + function onBlockHeader(blockHeader) { + self.stats.totalGasCost += blockHeader.gasUsed; + self.stats.test.gasUsed += blockHeader.gasUsed; + } + + if (self.gasDetails) { + self.embarkEvents.on("deploy:contract:receipt", onContractReceipt); + } + self.embarkEvents.on("block:header", onBlockHeader); + + function indent() { + return Array(indents).join(' '); + } + + runner.on('start', function () { + console.log(); + }); + + runner.on('suite', function (suite) { + ++indents; + if (self.gasDetails) { + console.log(); + } + console.log(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on('suite end', function () { + --indents; + if (indents === 1) { + console.log(); + } + }); + + runner.on('pending', function (test) { + const fmt = indent() + color('pending', ' - %s'); + console.log(fmt, test.title); + }); + + + runner.on('test', function () { + self.stats.test.gasUsed = 0; + }); + + runner.on('pass', function (test) { + let fmt = + indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s') + + color(test.speed, ' (%dms)') + + ' - ' + + color(self.getGasColor(self.stats.test.gasUsed), '[%d gas]'); + console.log(fmt, test.title, test.duration, self.stats.test.gasUsed); + }); + + runner.on('fail', function (test) { + console.log(indent() + color('fail', ' %d) %s') + ' - ' + color(self.getGasColor(self.stats.test.gasUsed), '[%d gas]'), + ++n, test.title, self.stats.test.gasUsed); + }); + + runner.once('end', function () { + runner.removeAllListeners(); + self.embarkEvents.removeListener("deploy:contract:receipt", onContractReceipt); + self.embarkEvents.removeListener("block:header", onBlockHeader); + self.epilogue(); + }); + } + + getGasColor(gasCost) { + if (gasCost <= this.gasLimit / 10) { + return 'fast'; + } + if (gasCost <= 3 * (this.gasLimit / 4)) { + return 'medium'; + } + return 'slow'; + } + + epilogue() { + const stats = this.stats; + let fmt; + + console.log(); + + // passes + fmt = color('bright pass', ' ') + + color('green', ' %d passing') + + color('light', ' (%s)') + + color('light', ' - [Total: %s gas]'); + + console.log(fmt, + stats.passes || 0, + ms(stats.duration), + stats.totalGasCost); + + // pending + if (stats.pending) { + fmt = color('pending', ' ') + + color('pending', ' %d pending'); + + console.log(fmt, stats.pending); + } + + // failures + if (stats.failures) { + fmt = color('fail', ' %d failing'); + + console.log(fmt, stats.failures); + + Base.list(this.failures); + console.log(); + } + + console.log(); + } +} + +module.exports = EmbarkSpec; diff --git a/lib/tests/run_tests.js b/lib/tests/run_tests.js index 80d238705..d544e6ef0 100644 --- a/lib/tests/run_tests.js +++ b/lib/tests/run_tests.js @@ -4,6 +4,7 @@ const Mocha = require('mocha'); const path = require('path'); const assert = require('assert'); const Test = require('./test'); +const EmbarkSpec = require('./reporter'); function getFilesFromDir(filePath, cb) { fs.readdir(filePath, (err, files) => { @@ -100,6 +101,11 @@ module.exports = { function executeForAllFiles(files, next) { async.eachLimit(files, 1, (file, eachCb) => { const mocha = new Mocha(); + mocha.reporter(EmbarkSpec, { + events: global.embark.engine.events, + gasDetails: options.gasDetails, + gasLimit: global.embark.engine.deployManager.gasLimit + }); mocha.addFile(file); mocha.suite.timeout(0); @@ -112,6 +118,7 @@ module.exports = { mocha.run(function (fails) { failures += fails; + mocha.suite.removeAllListeners(); // Mocha prints the error already eachCb(); }); diff --git a/lib/tests/test.js b/lib/tests/test.js index af7f81699..f13f01135 100644 --- a/lib/tests/test.js +++ b/lib/tests/test.js @@ -39,11 +39,13 @@ class Test { this.error = false; this.builtContracts = {}; this.compiledContracts = {}; + this.logsSubscription = null; this.web3 = new Web3(); } initWeb3Provider(callback) { + const self = this; if (this.provider) { this.provider.stop(); } @@ -70,8 +72,14 @@ class Test { return callback(err); } - this.provider = new Provider(providerOptions); - this.provider.startWeb3Provider(callback); + self.provider = new Provider(providerOptions); + return self.provider.startWeb3Provider((err) => { + if (err) { + return callback(err); + } + self.subscribeToPendingTransactions(); + callback(); + }); }); } @@ -87,9 +95,22 @@ class Test { this.sim = getSimulator(); } this.web3.setProvider(this.sim.provider(this.simOptions)); + this.subscribeToPendingTransactions(); callback(); } + subscribeToPendingTransactions() { + const self = this; + if (self.logsSubscription) { + self.logsSubscription.unsubscribe(); + } + self.logsSubscription = self.web3.eth + .subscribe('newBlockHeaders') + .on("data", function (blockHeader) { + self.engine.events.emit('block:header', blockHeader); + }); + } + initDeployServices() { this.engine.startService("web3", { web3: this.web3 @@ -98,6 +119,8 @@ class Test { trackContracts: false //ipcRole: 'client' // disabled for now due to issues with ipc file }); + this.engine.deployManager.gasLimit = 6000000; + this.engine.contractsManager.gasLimit = 6000000; } init(callback) { @@ -282,8 +305,6 @@ class Test { }); }, function deploy(accounts, next) { - self.engine.deployManager.gasLimit = 6000000; - self.engine.contractsManager.gasLimit = 6000000; self.engine.deployManager.fatalErrors = true; self.engine.deployManager.deployOnlyOnConfig = true; self.engine.events.request('deploy:contracts', () => {