|
|
|
|
|
|
|
/**
|
|
|
|
* This file contains methods that produce a coverage map to pass to instanbul
|
|
|
|
* from data generated by `instrumentSolidity.js`
|
|
|
|
*/
|
|
|
|
const { AbiCoder } = require('web3-eth-abi');
|
|
|
|
const SolidityCoder = AbiCoder();
|
|
|
|
const path = require('path');
|
|
|
|
const keccak = require('keccakjs');
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts solcover event data into an object that can be
|
|
|
|
* be passed to instanbul to produce coverage reports.
|
|
|
|
* @type {CoverageMap}
|
|
|
|
*/
|
|
|
|
module.exports = class CoverageMap {
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this.coverage = {};
|
|
|
|
this.assertCoverage = {};
|
|
|
|
this.lineTopics = [];
|
|
|
|
this.functionTopics = [];
|
|
|
|
this.branchTopics = [];
|
|
|
|
this.statementTopics = [];
|
|
|
|
this.assertPreTopics = [];
|
|
|
|
this.assertPostTopics = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes a coverage map object for contract instrumented per `info` and located
|
|
|
|
* at `canonicalContractPath`
|
|
|
|
* @param {Object} info `info = getIntrumentedVersion(contract, fileName, true)`
|
|
|
|
* @param {String} canonicalContractPath target file location
|
|
|
|
* @return {Object} coverage map with all values set to zero
|
|
|
|
*/
|
|
|
|
|
|
|
|
addContract(info, canonicalContractPath) {
|
|
|
|
this.coverage[canonicalContractPath] = {
|
|
|
|
l: {},
|
|
|
|
path: canonicalContractPath,
|
|
|
|
s: {},
|
|
|
|
b: {},
|
|
|
|
f: {},
|
|
|
|
fnMap: {},
|
|
|
|
statementMap: {},
|
|
|
|
branchMap: {},
|
|
|
|
};
|
|
|
|
this.assertCoverage[canonicalContractPath] = { };
|
|
|
|
|
|
|
|
info.runnableLines.forEach((item, idx) => {
|
|
|
|
this.coverage[canonicalContractPath].l[info.runnableLines[idx]] = 0;
|
|
|
|
});
|
|
|
|
this.coverage[canonicalContractPath].fnMap = info.fnMap;
|
|
|
|
for (let x = 1; x <= Object.keys(info.fnMap).length; x++) {
|
|
|
|
this.coverage[canonicalContractPath].f[x] = 0;
|
|
|
|
}
|
|
|
|
this.coverage[canonicalContractPath].branchMap = info.branchMap;
|
|
|
|
for (let x = 1; x <= Object.keys(info.branchMap).length; x++) {
|
|
|
|
this.coverage[canonicalContractPath].b[x] = [0, 0];
|
|
|
|
this.assertCoverage[canonicalContractPath][x] = {
|
|
|
|
preEvents: 0,
|
|
|
|
postEvents: 0,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
this.coverage[canonicalContractPath].statementMap = info.statementMap;
|
|
|
|
for (let x = 1; x <= Object.keys(info.statementMap).length; x++) {
|
|
|
|
this.coverage[canonicalContractPath].s[x] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const keccakhex = (x => {
|
|
|
|
const hash = new keccak(256); // eslint-disable-line new-cap
|
|
|
|
hash.update(x);
|
|
|
|
return hash.digest('hex');
|
|
|
|
});
|
|
|
|
|
|
|
|
const lineHash = keccakhex('__Coverage' + info.contractName + '(string,uint256)');
|
|
|
|
const fnHash = keccakhex('__FunctionCoverage' + info.contractName + '(string,uint256)');
|
|
|
|
const branchHash = keccakhex('__BranchCoverage' + info.contractName + '(string,uint256,uint256)');
|
|
|
|
const statementHash = keccakhex('__StatementCoverage' + info.contractName + '(string,uint256)');
|
|
|
|
const assertPreHash = keccakhex('__AssertPreCoverage' + info.contractName + '(string,uint256)');
|
|
|
|
const assertPostHash = keccakhex('__AssertPostCoverage' + info.contractName + '(string,uint256)');
|
|
|
|
|
|
|
|
this.lineTopics.push(lineHash);
|
|
|
|
this.functionTopics.push(fnHash);
|
|
|
|
this.branchTopics.push(branchHash);
|
|
|
|
this.statementTopics.push(statementHash);
|
|
|
|
this.assertPreTopics.push(assertPreHash);
|
|
|
|
this.assertPostTopics.push(assertPostHash);
|
|
|
|
|
|
|
|
const topics = `${lineHash}\n${fnHash}\n${branchHash}\n${statementHash}\n${assertPreHash}\n${assertPostHash}\n`;
|
|
|
|
fs.appendFileSync('./scTopics', topics);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Populates an empty coverage map with values derived from an array of events
|
|
|
|
* fired by instrumented contracts as they are tested
|
|
|
|
* @param {Array} events
|
|
|
|
* @param {String} relative path to host contracts eg: './../contracts'
|
|
|
|
* @return {Object} coverage map.
|
|
|
|
*/
|
|
|
|
generate(events, pathPrefix) {
|
|
|
|
for (let idx = 0; idx < events.length; idx++) {
|
|
|
|
const event = JSON.parse(events[idx]);
|
|
|
|
|
|
|
|
if (event.topics.filter(t => this.lineTopics.indexOf(t) >= 0).length > 0) {
|
|
|
|
const data = SolidityCoder.decodeParameters(['string', 'uint256'], `0x${event.data}`);
|
|
|
|
const canonicalContractPath = data[0];
|
|
|
|
this.coverage[canonicalContractPath].l[parseInt(data[1], 10)] += 1;
|
|
|
|
} else if (event.topics.filter(t => this.functionTopics.indexOf(t) >= 0).length > 0) {
|
|
|
|
const data = SolidityCoder.decodeParameters(['string', 'uint256'], `0x${event.data}`);
|
|
|
|
const canonicalContractPath = data[0];
|
|
|
|
this.coverage[canonicalContractPath].f[parseInt(data[1], 10)] += 1;
|
|
|
|
} else if (event.topics.filter(t => this.branchTopics.indexOf(t) >= 0).length > 0) {
|
|
|
|
const data = SolidityCoder.decodeParameters(['string', 'uint256', 'uint256'], `0x${event.data}`);
|
|
|
|
const canonicalContractPath = data[0];
|
|
|
|
this.coverage[canonicalContractPath].b[parseInt(data[1], 10)][parseInt(data[2], 10)] += 1;
|
|
|
|
} else if (event.topics.filter(t => this.statementTopics.indexOf(t) >= 0).length > 0) {
|
|
|
|
const data = SolidityCoder.decodeParameters(['string', 'uint256'], `0x${event.data}`);
|
|
|
|
const canonicalContractPath = data[0];
|
|
|
|
this.coverage[canonicalContractPath].s[parseInt(data[1], 10)] += 1;
|
|
|
|
} else if (event.topics.filter(t => this.assertPreTopics.indexOf(t) >= 0).length > 0) {
|
|
|
|
const data = SolidityCoder.decodeParameters(['string', 'uint256'], `0x${event.data}`);
|
|
|
|
const canonicalContractPath = data[0];
|
|
|
|
this.assertCoverage[canonicalContractPath][parseInt(data[1], 10)].preEvents += 1;
|
|
|
|
} else if (event.topics.filter(t => this.assertPostTopics.indexOf(t) >= 0).length > 0) {
|
|
|
|
const data = SolidityCoder.decodeParameters(['string', 'uint256'], `0x${event.data}`);
|
|
|
|
const canonicalContractPath = data[0];
|
|
|
|
this.assertCoverage[canonicalContractPath][parseInt(data[1], 10)].postEvents += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Finally, interpret the assert pre/post events
|
|
|
|
Object.keys(this.assertCoverage).forEach(contractPath => {
|
|
|
|
const contract = this.coverage[contractPath];
|
|
|
|
for (let i = 1; i <= Object.keys(contract.b).length; i++) {
|
|
|
|
const branch = this.assertCoverage[contractPath][i];
|
|
|
|
if (branch.preEvents > 0) {
|
|
|
|
// Then it was an assert branch.
|
|
|
|
this.coverage[contractPath].b[i] = [branch.postEvents, branch.preEvents - branch.postEvents];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return Object.assign({}, this.coverage);
|
|
|
|
}
|
|
|
|
};
|