Skip to content

Commit

Permalink
feat: add coverage events
Browse files Browse the repository at this point in the history
Instead of looking at the transaction, instrument the
source to publish coverage event
  • Loading branch information
alaibe authored and iurimatias committed Dec 20, 2018
1 parent 7574e14 commit 8a6d075
Show file tree
Hide file tree
Showing 56 changed files with 2,540 additions and 2,526 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
"follow-redirects": "1.5.7",
"fs-extra": "7.0.0",
"fuzzy": "0.1.3",
"ganache-cli": "6.1.8",
"ganache-cli": "6.2.3",
"glob": "7.1.3",
"globule": "1.2.1",
"handlebars": "4.0.12",
Expand Down Expand Up @@ -153,6 +153,7 @@
"shelljs": "0.5.3",
"simples": "0.8.8",
"solc": "0.5.0",
"solidity-parser-antlr": "0.3.2",
"source-map-support": "0.5.9",
"stream-json": "1.1.3",
"string-replace-async": "1.2.1",
Expand Down Expand Up @@ -191,6 +192,7 @@
"@babel/cli": "7.1.2",
"@babel/plugin-proposal-optional-chaining": "7.0.0",
"@types/async": "2.0.50",
"@types/globule": "1.1.3",
"@types/handlebars": "4.0.39",
"@types/i18n": "0.8.3",
"@types/node": "10.11.7",
Expand Down
3 changes: 2 additions & 1 deletion src/lib/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"restart": "restart"
},
"tests": {
"gasLimit": 6000000
"gasLimit": 6000000,
"coverageGasLimit": 4503599627370495
},
"codeGenerator": {
"gasLimit": 6000000
Expand Down
5 changes: 5 additions & 0 deletions src/lib/core/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ function ensureFileSync() {
return restrictPath(fs.ensureFileSync, fs.ensureFileSync, 1, arguments);
}

function ensureDirSync() {
return restrictPath(fs.ensureDirSync, fs.ensureDirSync, 1, arguments);
}

function access() {
return restrictPath(fs.access, fs.access, 1, arguments);
}
Expand Down Expand Up @@ -195,6 +199,7 @@ module.exports = {
embarkPath,
existsSync,
ensureFileSync,
ensureDirSync,
mkdirp,
mkdirpSync,
move,
Expand Down
5 changes: 3 additions & 2 deletions src/lib/modules/blockchain_connector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ class BlockchainConnector {

if (type === 'vm') {
const sim = self._getSimulator();
self.provider = sim.provider(self.config.contractsConfig.deployment);
const options = Object.assign({}, self.config.contractsConfig.deployment, {gasPrice: "0x01", gasLimit: "0xfffffffffffff"});
self.provider = sim.provider(options);

if (coverage) {
// Here we patch the sendAsync method on the provider. The goal behind this is to force pure/constant/view calls to become
Expand All @@ -124,7 +125,7 @@ class BlockchainConnector {
return self.provider.realSendAsync(payload, cb);
}
self.events.request('reporter:toggleGasListener');
let newParams = Object.assign({}, payload.params[0], {gasPrice: '0x77359400'});
let newParams = Object.assign({}, payload.params[0]);
let newPayload = {
id: payload.id + 1,
method: 'eth_sendTransaction',
Expand Down
143 changes: 143 additions & 0 deletions src/lib/modules/coverage/contractEnhanced.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import * as path from "path";
import parser, { Location } from "solidity-parser-antlr";
import { EventLog } from "web3/types";

import { decrypt } from "./eventId";
import { Injector } from "./injector";
import { Instrumenter } from "./instrumenter";
import { InstrumentWalker } from "./instrumentWalker";
import { coverageContractsPath } from "./path";
import { Suppressor } from "./suppressor";
import { BranchType, Coverage } from "./types";

const fs = require("../../core/fs");

enum EventEnum {
statement = "__StatementCoverage",
branch = "__BranchCoverage",
function = "__FunctionCoverage",
}

let id = 0;
function nextId() {
id++;
return id;
}

export class ContractEnhanced {
public id: number;
public coverage: Coverage;
public originalSource: string;
public source: string;
private ast: parser.ASTNode;
private coverageFilepath: string;

constructor(public filepath: string) {
this.id = nextId();
this.source = fs.readFileSync(filepath, "utf-8");
this.originalSource = this.source;

this.coverageFilepath = path.join(coverageContractsPath(), this.filepath);

this.coverage = {
b: {},
branchMap: {},
code: this.originalSource,
f: {},
fnMap: {},
l: {},
path: filepath,
s: {},
statementMap: {},
};
this.ast = parser.parse(this.source, {loc: true, range: true});
}

public instrument() {
new Suppressor(this).process();
const instrumenter = new Instrumenter(this);
const instrumentWalker = new InstrumentWalker(instrumenter);
instrumentWalker.walk(this.ast);

const injector = new Injector(this);
instrumenter.getInjectionPoints().forEach(injector.process.bind(injector));
}

public save() {
fs.ensureFileSync(this.coverageFilepath);
fs.writeFileSync(this.coverageFilepath, this.source);
}

public updateCoverage(events: EventLog[]) {
events.filter(this.filterCoverageEvent).forEach((event) => {
const value = parseInt(event.returnValues[0], 10);
const {contractId, injectionPointId, locationIdx} = decrypt(value);

if (contractId !== this.id) {
return;
}

switch (event.event) {
case "__StatementCoverage": {
this.coverage.s[injectionPointId] += 1;
const statement = this.coverage.statementMap[injectionPointId];
this.coverage.l[statement.start.line] += 1;
break;
}
case "__FunctionCoverage": {
this.coverage.f[injectionPointId] += 1;
const fn = this.coverage.fnMap[injectionPointId];
this.coverage.l[fn.line] += 1;
break;
}
case "__BranchCoverage": {
this.coverage.b[injectionPointId][locationIdx] += 1;
break;
}
}
});
}

public addStatement(location: Location) {
const coverageId = this.getNewCoverageId(this.coverage.statementMap);
this.coverage.statementMap[coverageId] = location;
this.coverage.s[coverageId] = 0;
this.coverage.l[location.start.line] = 0;
return coverageId;
}

public addBranch(line: number, type: BranchType, locations: Location[]) {
const coverageId = this.getNewCoverageId(this.coverage.branchMap);
this.coverage.branchMap[coverageId] = {
line,
locations,
type,
};
this.coverage.b[coverageId] = locations.map(() => 0);
this.coverage.l[line] = 0;
return coverageId;
}

public addFunction(location: Location, name: string) {
const coverageId = this.getNewCoverageId(this.coverage.fnMap);
const line = location.start.line;
this.coverage.fnMap[coverageId] = {
line,
loc: location,
name,
};
this.coverage.f[coverageId] = 0;
this.coverage.l[line] = 0;
return coverageId;
}

private getNewCoverageId(object: {[key: string]: any}) {
const lastId = Object.keys(object).map(Number).sort((a, b) => b - a)[0] || 0;
return lastId + 1;
}

private filterCoverageEvent(event: EventLog) {
return [EventEnum.function, EventEnum.branch, EventEnum.statement].includes(event.event as EventEnum);
}

}
Loading

0 comments on commit 8a6d075

Please sign in to comment.