|
|
|
/**
|
|
|
|
* Converts instrumentation data accumulated a the vm steps to an instanbul spec coverage object.
|
|
|
|
* @type {Coverage}
|
|
|
|
*/
|
|
|
|
|
|
|
|
const util = require('util');
|
|
|
|
|
|
|
|
class Coverage {
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this.data = {};
|
|
|
|
this.requireData = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes an entry in the coverage map for an instrumented contract. Tracks by
|
|
|
|
* its canonical contract path, e.g. *not* by its location in the temp folder.
|
|
|
|
* @param {Object} info 'info = instrumenter.instrument(contract, fileName, true)'
|
|
|
|
* @param {String} contractPath canonical path to contract file
|
|
|
|
*/
|
|
|
|
|
|
|
|
addContract(info, contractPath) {
|
|
|
|
this.data[contractPath] = {
|
|
|
|
l: {},
|
|
|
|
path: contractPath,
|
|
|
|
s: {},
|
|
|
|
b: {},
|
|
|
|
f: {},
|
|
|
|
fnMap: {},
|
|
|
|
statementMap: {},
|
|
|
|
branchMap: {},
|
|
|
|
};
|
|
|
|
this.requireData[contractPath] = { };
|
|
|
|
|
|
|
|
info.runnableLines.forEach((item, idx) => {
|
|
|
|
this.data[contractPath].l[info.runnableLines[idx]] = 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
this.data[contractPath].fnMap = info.fnMap;
|
|
|
|
for (let x = 1; x <= Object.keys(info.fnMap).length; x++) {
|
|
|
|
this.data[contractPath].f[x] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.data[contractPath].branchMap = info.branchMap;
|
|
|
|
for (let x = 1; x <= Object.keys(info.branchMap).length; x++) {
|
|
|
|
this.data[contractPath].b[x] = [0, 0];
|
|
|
|
this.requireData[contractPath][x] = {
|
|
|
|
preEvents: 0,
|
|
|
|
postEvents: 0,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
this.data[contractPath].statementMap = info.statementMap;
|
|
|
|
for (let x = 1; x <= Object.keys(info.statementMap).length; x++) {
|
|
|
|
this.data[contractPath].s[x] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Populates an empty coverage map with values derived from a hash map of
|
|
|
|
* data collected as the instrumented contracts are tested
|
|
|
|
* @param {Object} map of collected instrumentation data
|
|
|
|
* @return {Object} coverage map.
|
|
|
|
*/
|
|
|
|
generate(collectedData) {
|
|
|
|
const hashes = Object.keys(collectedData);
|
|
|
|
|
|
|
|
for (let hash of hashes){
|
|
|
|
const data = collectedData[hash];
|
|
|
|
const contractPath = collectedData[hash].contractPath;
|
|
|
|
const id = collectedData[hash].id;
|
|
|
|
|
|
|
|
// NB: Any branch using the injected fn which returns boolean will have artificially
|
|
|
|
// doubled hits (because of something internal to Solidity about how the stack is managed)
|
|
|
|
const hits = collectedData[hash].hits;
|
|
|
|
|
|
|
|
switch(collectedData[hash].type){
|
|
|
|
case 'line': this.data[contractPath].l[id] = hits; break;
|
|
|
|
case 'function': this.data[contractPath].f[id] = hits; break;
|
|
|
|
case 'statement': this.data[contractPath].s[id] = hits; break;
|
|
|
|
case 'branch': this.data[contractPath].b[id][data.locationIdx] = hits; break;
|
|
|
|
case 'logicalOR': this.data[contractPath].b[id][data.locationIdx] = hits / 2; break;
|
|
|
|
case 'requirePre': this.requireData[contractPath][id].preEvents = hits; break;
|
|
|
|
case 'requirePost': this.requireData[contractPath][id].postEvents = hits; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, interpret the assert pre/post events
|
|
|
|
const contractPaths = Object.keys(this.requireData);
|
|
|
|
|
|
|
|
for (let contractPath of contractPaths){
|
|
|
|
const contract = this.data[contractPath];
|
|
|
|
|
|
|
|
for (let i = 1; i <= Object.keys(contract.b).length; i++) {
|
|
|
|
const branch = this.requireData[contractPath][i];
|
|
|
|
|
|
|
|
// Was it an assert branch?
|
|
|
|
if (branch && branch.preEvents > 0){
|
|
|
|
this.data[contractPath].b[i] = [
|
|
|
|
branch.postEvents,
|
|
|
|
branch.preEvents - branch.postEvents
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Object.assign({}, this.data);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = Coverage;
|