make code coverage work with refactored tests

This commit is contained in:
Jonathan Rainville 2018-10-10 16:34:04 -04:00 committed by Pascal Precht
parent 7516bad619
commit f4d7636b7a
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
8 changed files with 88 additions and 59 deletions

View File

@ -557,6 +557,8 @@ class EmbarkController {
engine.startService("web3", {wait: true}); // Empty web3 as Test changes it engine.startService("web3", {wait: true}); // Empty web3 as Test changes it
engine.startService("deployment"); engine.startService("deployment");
engine.startService("codeGenerator"); engine.startService("codeGenerator");
engine.startService("codeRunner");
engine.startService("codeCoverage");
engine.startService("testRunner"); engine.startService("testRunner");
callback(); callback();
}, },

View File

@ -34,6 +34,11 @@ var Config = function(options) {
cb(self.contractsConfig); cb(self.contractsConfig);
}); });
self.events.setCommandHandler("config:contractsConfig:set", (config, cb) => {
self.contractsConfig = config;
cb();
});
self.events.setCommandHandler("config:contractsFiles", (cb) => { self.events.setCommandHandler("config:contractsFiles", (cb) => {
cb(self.contractsFiles); cb(self.contractsFiles);
}); });

View File

@ -5,7 +5,7 @@ const utils = require('../../utils/utils');
const constants = require('../../constants'); const constants = require('../../constants');
const embarkJsUtils = require('embarkjs').Utils; const embarkJsUtils = require('embarkjs').Utils;
const WEB3_READY = 'web3Ready'; const WEB3_READY = 'blockchain:ready';
// TODO: consider another name, this is the blockchain connector // TODO: consider another name, this is the blockchain connector
class BlockchainConnector { class BlockchainConnector {
@ -40,6 +40,7 @@ class BlockchainConnector {
this.registerRequests(); this.registerRequests();
this.registerWeb3Object(); this.registerWeb3Object();
this.registerEvents(); this.registerEvents();
this.subscribeToPendingTransactions();
} }
initWeb3(cb) { initWeb3(cb) {
@ -67,10 +68,46 @@ class BlockchainConnector {
} }
if (type === 'vm') { if (type === 'vm') {
const sim = this._getSimulator(); const sim = self._getSimulator();
this.provider = sim.provider(this.contractsConfig.deployment); self.provider = sim.provider(self.contractsConfig.deployment);
this.web3.setProvider(this.provider);
this._emitWeb3Ready(); 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(); return cb();
} }
@ -115,6 +152,7 @@ class BlockchainConnector {
this.isWeb3Ready = true; this.isWeb3Ready = true;
this.events.emit(WEB3_READY); this.events.emit(WEB3_READY);
this.registerWeb3Object(); this.registerWeb3Object();
this.subscribeToPendingTransactions();
} }
_getSimulator() { _getSimulator() {

View File

@ -12,7 +12,7 @@ class ContractSource {
this.lineCount = this.lineLengths.length; this.lineCount = this.lineLengths.length;
this.lineOffsets = this.lineLengths.reduce((sum, _elt, i) => { 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; return sum;
}, []); }, []);

View File

@ -2,11 +2,6 @@
const fs = require('../../core/fs'); const fs = require('../../core/fs');
const ContractSources = require('./contract_sources'); const ContractSources = require('./contract_sources');
// Set up the web3 extension
web3.extend({
property: 'debug',
methods: [{name: 'traceTransaction', call: 'debug_traceTransaction', params: 2}]
});
class CodeCoverage { class CodeCoverage {
constructor(embark, _options) { constructor(embark, _options) {
@ -18,9 +13,18 @@ class CodeCoverage {
embark.events.on('contracts:run:solc', this.runSolc.bind(this)); embark.events.on('contracts:run:solc', this.runSolc.bind(this));
embark.events.on('block:header', 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 embark.events.on('tests:finished', this.updateCoverageReport.bind(this));
// pull it in from global.
global.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.seenTransactions = {};
this.coverageReport = {}; this.coverageReport = {};
@ -48,7 +52,7 @@ class CodeCoverage {
async runSolc(receipt) { async runSolc(receipt) {
let block = await web3.eth.getBlock(receipt.number); let block = await web3.eth.getBlock(receipt.number);
if(block.transactions.length == 0) return; if(block.transactions.length === 0) return;
let requests = []; let requests = [];
for(let i in block.transactions) { for(let i in block.transactions) {

View File

@ -11,7 +11,7 @@ const EmptySourceMap = {
class SourceMap { class SourceMap {
constructor(sourceMapStringOrOffset, length, id, jump) { constructor(sourceMapStringOrOffset, length, id, jump) {
if(typeof sourceMapStringOrOffset == 'string') { if(typeof sourceMapStringOrOffset === 'string') {
let [offset, length, id, jump] = sourceMapStringOrOffset.split(":"); let [offset, length, id, jump] = sourceMapStringOrOffset.split(":");
this.offset = parseInt(offset, 10); this.offset = parseInt(offset, 10);
@ -46,7 +46,7 @@ class SourceMap {
toString(defaultId) { toString(defaultId) {
let parts = [this.offset, this.length]; let parts = [this.offset, this.length];
if(this.id !== undefined && this.id != '') { if(this.id !== undefined && this.id !== '') {
parts.push(this.id); parts.push(this.id);
} else if(defaultId !== undefined) { } else if(defaultId !== undefined) {
parts.push(defaultId); parts.push(defaultId);

View File

@ -1,5 +1,6 @@
const async = require('async'); const async = require('async');
const AccountParser = require('../../utils/accountParser'); const AccountParser = require('../../utils/accountParser');
const EmbarkJS = require('embarkjs');
class Test { class Test {
constructor(options) { constructor(options) {
@ -46,42 +47,10 @@ class Test {
this.blockchainConnector.contractsConfig = this.configObj.contractsConfig; this.blockchainConnector.contractsConfig = this.configObj.contractsConfig;
this.blockchainConnector.isWeb3Ready = false; this.blockchainConnector.isWeb3Ready = false;
this.blockchainConnector.wait = false; this.blockchainConnector.wait = false;
this.blockchainConnector.coverage = this.options.coverage;
// TODO change this // 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); this.blockchainConnector.initWeb3(callback);
} }
@ -203,6 +172,12 @@ class Test {
function checkDeploymentOpts(next) { function checkDeploymentOpts(next) {
self.checkDeploymentOptions(options, next); self.checkDeploymentOptions(options, next);
}, },
function changeGlobalWeb3(next) {
self.events.request('blockchain:get', (web3) => {
global.web3 = web3;
next();
});
},
function compileContracts(next) { function compileContracts(next) {
if (!self.firstDeployment) { if (!self.firstDeployment) {
return next(); return next();
@ -242,9 +217,8 @@ class Test {
const self = this; const self = this;
async.waterfall([ async.waterfall([
function getConfig(next) { function getConfig(next) {
// TODO use events instead of modifying directly self.events.request('config:contractsConfig:set',
self.configObj.contractsConfig = {contracts: config.contracts, versions: self.versions_default}; {contracts: config.contracts, versions: self.versions_default}, next);
next();
}, },
function getAccounts(next) { function getAccounts(next) {
self.events.request('blockchain:getAccounts', (err, accounts) => { self.events.request('blockchain:getAccounts', (err, accounts) => {
@ -281,13 +255,19 @@ class Test {
self.contracts[contract.className] = {}; self.contracts[contract.className] = {};
} }
self.events.request('blockchain:contract:create', {
abi: contract.abiDefinition, self.events.request('blockchain:get', (web3) => {
address: contract.deployedAddress let newContract = new EmbarkJS.Blockchain.Contract({
}, (newContract) => { abi: contract.abiDefinition,
address: contract.deployedAddress,
from: web3.eth.defaultAccount,
gas: 6000000,
web3: web3
});
if (newContract.options) { if (newContract.options) {
newContract.options.from = web3.eth.defaultAccount;
newContract.options.data = contract.code; newContract.options.data = contract.code;
newContract.options.from = accounts[0];
if (!newContract.options.data.startsWith('0x')) { if (!newContract.options.data.startsWith('0x')) {
newContract.options.data = '0x' + newContract.options.data; newContract.options.data = '0x' + newContract.options.data;
} }
@ -295,7 +275,6 @@ class Test {
} }
Object.setPrototypeOf(self.contracts[contract.className], newContract); Object.setPrototypeOf(self.contracts[contract.className], newContract);
eachCb(); eachCb();
}); });
}, (err) => { }, (err) => {

View File

@ -3,3 +3,4 @@ node_modules/
dist/ dist/
config/production/password config/production/password
config/livenet/password config/livenet/password
coverage/