From f4d7636b7a7101f7110d5e9ba72068498c00277a Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Wed, 10 Oct 2018 16:34:04 -0400 Subject: [PATCH] make code coverage work with refactored tests --- cmd/cmd_controller.js | 2 + lib/core/config.js | 5 ++ lib/modules/blockchain_connector/index.js | 48 +++++++++++++++-- lib/modules/coverage/contract_source.js | 2 +- lib/modules/coverage/index.js | 22 ++++---- lib/modules/coverage/source_map.js | 4 +- lib/modules/tests/test.js | 63 ++++++++--------------- test_apps/test_app/.gitignore | 1 + 8 files changed, 88 insertions(+), 59 deletions(-) diff --git a/cmd/cmd_controller.js b/cmd/cmd_controller.js index 2cb254164..ee9921ffb 100644 --- a/cmd/cmd_controller.js +++ b/cmd/cmd_controller.js @@ -557,6 +557,8 @@ class EmbarkController { engine.startService("web3", {wait: true}); // Empty web3 as Test changes it engine.startService("deployment"); engine.startService("codeGenerator"); + engine.startService("codeRunner"); + engine.startService("codeCoverage"); engine.startService("testRunner"); callback(); }, diff --git a/lib/core/config.js b/lib/core/config.js index f015f5436..d03c53de6 100644 --- a/lib/core/config.js +++ b/lib/core/config.js @@ -34,6 +34,11 @@ var Config = function(options) { cb(self.contractsConfig); }); + self.events.setCommandHandler("config:contractsConfig:set", (config, cb) => { + self.contractsConfig = config; + cb(); + }); + self.events.setCommandHandler("config:contractsFiles", (cb) => { cb(self.contractsFiles); }); diff --git a/lib/modules/blockchain_connector/index.js b/lib/modules/blockchain_connector/index.js index 28f2a0c30..78edcb5ef 100644 --- a/lib/modules/blockchain_connector/index.js +++ b/lib/modules/blockchain_connector/index.js @@ -5,7 +5,7 @@ const utils = require('../../utils/utils'); const constants = require('../../constants'); const embarkJsUtils = require('embarkjs').Utils; -const WEB3_READY = 'web3Ready'; +const WEB3_READY = 'blockchain:ready'; // TODO: consider another name, this is the blockchain connector class BlockchainConnector { @@ -40,6 +40,7 @@ class BlockchainConnector { this.registerRequests(); this.registerWeb3Object(); this.registerEvents(); + this.subscribeToPendingTransactions(); } initWeb3(cb) { @@ -67,10 +68,46 @@ class BlockchainConnector { } if (type === 'vm') { - const sim = this._getSimulator(); - this.provider = sim.provider(this.contractsConfig.deployment); - this.web3.setProvider(this.provider); - this._emitWeb3Ready(); + const sim = self._getSimulator(); + self.provider = sim.provider(self.contractsConfig.deployment); + + if (self.coverage) { + // Here we patch the sendAsync method on the provider. The goal behind this is to force pure/constant/view calls to become + // transactions, so that we can pull in execution traces and account for those executions in code coverage. + // + // Instead of a simple call, here's what happens: + // + // 1) A transaction is sent with the same payload, and a pre-defined gas price; + // 2) We wait for the transaction to be mined by asking for the receipt; + // 3) Once we get the receipt back, we dispatch the real call and pass the original callback; + // + // This will still allow tests to get the return value from the call and run contracts unmodified. + self.provider.realSendAsync = self.provider.sendAsync.bind(self.provider); + self.provider.sendAsync = function(payload, cb) { + if(payload.method !== 'eth_call') { + return self.provider.realSendAsync(payload, cb); + } + self.events.request('reporter:toggleGasListener'); + let newParams = Object.assign({}, payload.params[0], {gasPrice: '0x77359400'}); + let newPayload = { + id: payload.id + 1, + method: 'eth_sendTransaction', + params: [newParams], + jsonrpc: payload.jsonrpc + }; + + self.provider.realSendAsync(newPayload, (_err, response) => { + let txHash = response.result; + self.web3.eth.getTransactionReceipt(txHash, (_err, _res) => { + self.events.request('reporter:toggleGasListener'); + self.provider.realSendAsync(payload, cb); + }); + }); + }; + } + + self.web3.setProvider(self.provider); + self._emitWeb3Ready(); return cb(); } @@ -115,6 +152,7 @@ class BlockchainConnector { this.isWeb3Ready = true; this.events.emit(WEB3_READY); this.registerWeb3Object(); + this.subscribeToPendingTransactions(); } _getSimulator() { diff --git a/lib/modules/coverage/contract_source.js b/lib/modules/coverage/contract_source.js index d8f9eff42..1eb3d135e 100644 --- a/lib/modules/coverage/contract_source.js +++ b/lib/modules/coverage/contract_source.js @@ -12,7 +12,7 @@ class ContractSource { this.lineCount = this.lineLengths.length; this.lineOffsets = this.lineLengths.reduce((sum, _elt, i) => { - sum[i] = (i == 0) ? 0 : self.lineLengths[i-1] + sum[i-1] + 1; + sum[i] = (i === 0) ? 0 : self.lineLengths[i-1] + sum[i-1] + 1; return sum; }, []); diff --git a/lib/modules/coverage/index.js b/lib/modules/coverage/index.js index f71c30b81..8d412d0fa 100644 --- a/lib/modules/coverage/index.js +++ b/lib/modules/coverage/index.js @@ -2,11 +2,6 @@ const fs = require('../../core/fs'); const ContractSources = require('./contract_sources'); -// Set up the web3 extension -web3.extend({ - property: 'debug', - methods: [{name: 'traceTransaction', call: 'debug_traceTransaction', params: 2}] -}); class CodeCoverage { constructor(embark, _options) { @@ -18,9 +13,18 @@ class CodeCoverage { embark.events.on('contracts:run:solc', this.runSolc.bind(this)); embark.events.on('block:header', this.runSolc.bind(this)); - // These events are emitted from a test-specific Embark instance, so we need to - // pull it in from global. - global.embark.events.on('tests:finished', this.updateCoverageReport.bind(this)); + embark.events.on('tests:finished', this.updateCoverageReport.bind(this)); + + embark.events.on('blockchain:ready', () => { + embark.events.request('blockchain:get', (web3) => { + // Set up the web3 extension + web3.extend({ + property: 'debug', + methods: [{name: 'traceTransaction', call: 'debug_traceTransaction', params: 2}] + }); + + }); + }); this.seenTransactions = {}; this.coverageReport = {}; @@ -48,7 +52,7 @@ class CodeCoverage { async runSolc(receipt) { let block = await web3.eth.getBlock(receipt.number); - if(block.transactions.length == 0) return; + if(block.transactions.length === 0) return; let requests = []; for(let i in block.transactions) { diff --git a/lib/modules/coverage/source_map.js b/lib/modules/coverage/source_map.js index 3406645ca..473f5be84 100644 --- a/lib/modules/coverage/source_map.js +++ b/lib/modules/coverage/source_map.js @@ -11,7 +11,7 @@ const EmptySourceMap = { class SourceMap { constructor(sourceMapStringOrOffset, length, id, jump) { - if(typeof sourceMapStringOrOffset == 'string') { + if(typeof sourceMapStringOrOffset === 'string') { let [offset, length, id, jump] = sourceMapStringOrOffset.split(":"); this.offset = parseInt(offset, 10); @@ -46,7 +46,7 @@ class SourceMap { toString(defaultId) { let parts = [this.offset, this.length]; - if(this.id !== undefined && this.id != '') { + if(this.id !== undefined && this.id !== '') { parts.push(this.id); } else if(defaultId !== undefined) { parts.push(defaultId); diff --git a/lib/modules/tests/test.js b/lib/modules/tests/test.js index b7f958e42..287e4df32 100644 --- a/lib/modules/tests/test.js +++ b/lib/modules/tests/test.js @@ -1,5 +1,6 @@ const async = require('async'); const AccountParser = require('../../utils/accountParser'); +const EmbarkJS = require('embarkjs'); class Test { constructor(options) { @@ -46,42 +47,10 @@ class Test { this.blockchainConnector.contractsConfig = this.configObj.contractsConfig; this.blockchainConnector.isWeb3Ready = false; this.blockchainConnector.wait = false; + this.blockchainConnector.coverage = this.options.coverage; // TODO change this - /*if (this.options.coverage) { - // Here we patch the sendAsync method on the provider. The goal behind this is to force pure/constant/view calls to become - // transactions, so that we can pull in execution traces and account for those executions in code coverage. - // - // Instead of a simple call, here's what happens: - // - // 1) A transaction is sent with the same payload, and a pre-defined gas price; - // 2) We wait for the transaction to be mined by asking for the receipt; - // 3) Once we get the receipt back, we dispatch the real call and pass the original callback; - // - // This will still allow tests to get the return value from the call and run contracts unmodified. - simProvider.realSendAsync = simProvider.sendAsync.bind(simProvider); - simProvider.sendAsync = function(payload, cb) { - if(payload.method !== 'eth_call') { - return simProvider.realSendAsync(payload, cb); - } - self.events.request('reporter:toggleGasListener'); - let newParams = Object.assign({}, payload.params[0], {gasPrice: '0x77359400'}); - let newPayload = { - id: payload.id + 1, - method: 'eth_sendTransaction', - params: [newParams], - jsonrpc: payload.jsonrpc - }; - simProvider.realSendAsync(newPayload, (_err, response) => { - let txHash = response.result; - self.web3.eth.getTransactionReceipt(txHash, (_err, _res) => { - self.events.request('reporter:toggleGasListener'); - simProvider.realSendAsync(payload, cb); - }); - }); - }; - }*/ this.blockchainConnector.initWeb3(callback); } @@ -203,6 +172,12 @@ class Test { function checkDeploymentOpts(next) { self.checkDeploymentOptions(options, next); }, + function changeGlobalWeb3(next) { + self.events.request('blockchain:get', (web3) => { + global.web3 = web3; + next(); + }); + }, function compileContracts(next) { if (!self.firstDeployment) { return next(); @@ -242,9 +217,8 @@ class Test { const self = this; async.waterfall([ function getConfig(next) { - // TODO use events instead of modifying directly - self.configObj.contractsConfig = {contracts: config.contracts, versions: self.versions_default}; - next(); + self.events.request('config:contractsConfig:set', + {contracts: config.contracts, versions: self.versions_default}, next); }, function getAccounts(next) { self.events.request('blockchain:getAccounts', (err, accounts) => { @@ -281,13 +255,19 @@ class Test { self.contracts[contract.className] = {}; } - self.events.request('blockchain:contract:create', { - abi: contract.abiDefinition, - address: contract.deployedAddress - }, (newContract) => { + + self.events.request('blockchain:get', (web3) => { + let newContract = new EmbarkJS.Blockchain.Contract({ + abi: contract.abiDefinition, + address: contract.deployedAddress, + from: web3.eth.defaultAccount, + gas: 6000000, + web3: web3 + }); + if (newContract.options) { + newContract.options.from = web3.eth.defaultAccount; newContract.options.data = contract.code; - newContract.options.from = accounts[0]; if (!newContract.options.data.startsWith('0x')) { newContract.options.data = '0x' + newContract.options.data; } @@ -295,7 +275,6 @@ class Test { } Object.setPrototypeOf(self.contracts[contract.className], newContract); - eachCb(); }); }, (err) => { diff --git a/test_apps/test_app/.gitignore b/test_apps/test_app/.gitignore index 77bcafb1a..9fb2a32c1 100644 --- a/test_apps/test_app/.gitignore +++ b/test_apps/test_app/.gitignore @@ -3,3 +3,4 @@ node_modules/ dist/ config/production/password config/livenet/password +coverage/