parent
15fa08b566
commit
fc3115138c
@ -1,8 +1,7 @@ |
||||
allFiredEvents |
||||
scTopics |
||||
scDebugLog |
||||
coverage.json |
||||
coverage/ |
||||
node_modules/ |
||||
.changelog |
||||
.DS_Store |
||||
test/artifacts |
||||
test/cache |
||||
|
@ -0,0 +1,3 @@ |
||||
test/ |
||||
.circleci/ |
||||
docs/ |
@ -0,0 +1,18 @@ |
||||
|
||||
// Mute compiler warnings - this will need to be addressed properly in
|
||||
// the Buidler plugin by overloading TASK_COMPILE_COMPILE.
|
||||
const originalLog = console.log; |
||||
console.warn = () => {}; |
||||
console.log = val => val === '\n' ? null : originalLog(val); |
||||
|
||||
module.exports = { |
||||
solc: { |
||||
version: "0.5.8" |
||||
}, |
||||
paths: { |
||||
artifacts: "./test/artifacts", |
||||
cache: "./test/cache", |
||||
test: "./test/units", |
||||
sources: "./test/sources/contracts", |
||||
} |
||||
} |
@ -0,0 +1,39 @@ |
||||
const web3Utils = require('web3-utils') |
||||
|
||||
class DataCollector { |
||||
constructor(config={}, instrumentationData={}){ |
||||
this.instrumentationData = instrumentationData; |
||||
|
||||
this.vm = config.vmResolver |
||||
? config.vmResolver(config.provider) |
||||
: this._ganacheVMResolver(config.provider); |
||||
|
||||
this._connect(); |
||||
} |
||||
|
||||
// Subscribes to vm.on('step').
|
||||
_connect(){ |
||||
const self = this; |
||||
this.vm.on("step", function(info){ |
||||
|
||||
if (info.opcode.name.includes("PUSH") && info.stack.length > 0){ |
||||
const idx = info.stack.length - 1; |
||||
const hash = web3Utils.toHex(info.stack[idx]).toString(); |
||||
|
||||
if(self.instrumentationData[hash]){ |
||||
self.instrumentationData[hash].hits++; |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
_ganacheVMResolver(provider){ |
||||
return provider.engine.manager.state.blockchain.vm; |
||||
} |
||||
|
||||
_setInstrumentationData(data){ |
||||
this.instrumentationData = data; |
||||
} |
||||
} |
||||
|
||||
module.exports = DataCollector; |
@ -0,0 +1,112 @@ |
||||
/** |
||||
* Converts instrumentation data accumulated a the vm steps to an instanbul spec coverage object. |
||||
* @type {Coverage} |
||||
*/ |
||||
class Coverage { |
||||
|
||||
constructor() { |
||||
this.data = {}; |
||||
this.assertData = {}; |
||||
this.lineTopics = []; |
||||
this.functionTopics = []; |
||||
this.branchTopics = []; |
||||
this.statementTopics = []; |
||||
this.assertPreTopics = []; |
||||
this.assertPostTopics = []; |
||||
} |
||||
|
||||
/** |
||||
* Initializes a coverage map object for contract |
||||
* + instrumented per `info` |
||||
* + located at `canonicalContractPath` |
||||
* @param {Object} info `info = getIntrumentedVersion(contract, fileName, true)` |
||||
* @param {String} canonicalContractPath path to contract file |
||||
* @return {Object} coverage map with all values set to zero |
||||
*/ |
||||
|
||||
addContract(info, canonicalContractPath) { |
||||
this.data[canonicalContractPath] = { |
||||
l: {}, |
||||
path: canonicalContractPath, |
||||
s: {}, |
||||
b: {}, |
||||
f: {}, |
||||
fnMap: {}, |
||||
statementMap: {}, |
||||
branchMap: {}, |
||||
}; |
||||
this.assertData[canonicalContractPath] = { }; |
||||
|
||||
info.runnableLines.forEach((item, idx) => { |
||||
this.data[canonicalContractPath].l[info.runnableLines[idx]] = 0; |
||||
}); |
||||
|
||||
this.data[canonicalContractPath].fnMap = info.fnMap; |
||||
for (let x = 1; x <= Object.keys(info.fnMap).length; x++) { |
||||
this.data[canonicalContractPath].f[x] = 0; |
||||
} |
||||
|
||||
this.data[canonicalContractPath].branchMap = info.branchMap; |
||||
for (let x = 1; x <= Object.keys(info.branchMap).length; x++) { |
||||
this.data[canonicalContractPath].b[x] = [0, 0]; |
||||
this.assertData[canonicalContractPath][x] = { |
||||
preEvents: 0, |
||||
postEvents: 0, |
||||
}; |
||||
} |
||||
|
||||
this.data[canonicalContractPath].statementMap = info.statementMap; |
||||
for (let x = 1; x <= Object.keys(info.statementMap).length; x++) { |
||||
this.data[canonicalContractPath].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; |
||||
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 'assertPre': this.assertData[contractPath][id].preEvents = hits; break; |
||||
case 'assertPost': this.assertData[contractPath][id].postEvents = hits; break; |
||||
} |
||||
} |
||||
|
||||
// Finally, interpret the assert pre/post events
|
||||
const contractPaths = Object.keys(this.assertData); |
||||
|
||||
for (let contractPath of contractPaths){ |
||||
const contract = this.data[contractPath]; |
||||
|
||||
Object.keys(contract.b).forEach((item, i) => { |
||||
const branch = this.assertData[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; |
@ -1,145 +0,0 @@ |
||||
|
||||
/** |
||||
* 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); |
||||
} |
||||
}; |
@ -1,77 +1,184 @@ |
||||
const injector = {}; |
||||
|
||||
// These functions are used to actually inject the instrumentation events.
|
||||
|
||||
injector.callEvent = function injectCallEvent(contract, fileName, injectionPoint) { |
||||
const linecount = (contract.instrumented.slice(0, injectionPoint).match(/\n/g) || []).length + 1; |
||||
const sha1 = require("sha1"); |
||||
const web3Utils = require("web3-utils"); |
||||
|
||||
class Injector { |
||||
constructor(){ |
||||
this.hashCounter = 0; |
||||
this.definitionCounter = 0; |
||||
} |
||||
|
||||
/** |
||||
* Generates solidity statement to inject for line, stmt, branch, fn 'events' |
||||
* @param {String} memoryVariable |
||||
* @param {String} hash hash key to an instrumentationData entry (see _getHash) |
||||
* @param {String} type instrumentation type, e.g. line, statement |
||||
* @return {String} ex: _sc_82e0891[0] = bytes32(0xdc08...08ed1); // function
|
||||
*/ |
||||
_getInjectable(memoryVariable, hash, type){ |
||||
return `${memoryVariable}[0] = bytes32(${hash}); /* ${type} */ \n`; |
||||
} |
||||
|
||||
|
||||
_getHash(fileName) { |
||||
this.hashCounter++; |
||||
return web3Utils.keccak256(`${fileName}:${this.hashCounter}`); |
||||
} |
||||
|
||||
/** |
||||
* Generates a solidity statement injection. Declared once per fn. |
||||
* Definition is the same for every fn in file. |
||||
* @param {String} fileName |
||||
* @return {String} ex: bytes32[1] memory _sc_82e0891 |
||||
*/ |
||||
_getMemoryVariableDefinition(fileName){ |
||||
this.definitionCounter++; |
||||
return `\nbytes32[1] memory _sc_${sha1(fileName).slice(0,7)};\n`; |
||||
} |
||||
|
||||
_getMemoryVariableAssignment(fileName){ |
||||
return `\n_sc_${sha1(fileName).slice(0,7)}`; |
||||
} |
||||
|
||||
|
||||
injectLine(contract, fileName, injectionPoint, injection, instrumentation){ |
||||
const type = 'line'; |
||||
const start = contract.instrumented.slice(0, injectionPoint); |
||||
const end = contract.instrumented.slice(injectionPoint); |
||||
|
||||
const newLines = start.match(/\n/g); |
||||
const linecount = ( newLines || []).length + 1; |
||||
contract.runnableLines.push(linecount); |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + |
||||
'emit __Coverage' + contract.contractName + '(\'' + fileName + '\',' + linecount + ');\n' + |
||||
contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
injector.callFunctionEvent = function injectCallFunctionEvent(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + |
||||
'emit __FunctionCoverage' + contract.contractName + '(\'' + fileName + '\',' + injection.fnId + ');\n' + |
||||
contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
injector.callBranchEvent = function injectCallFunctionEvent(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + |
||||
(injection.openBracket ? '{' : '') + |
||||
'emit __BranchCoverage' + contract.contractName + '(\'' + fileName + '\',' + injection.branchId + ',' + injection.locationIdx + ')' + |
||||
(injection.comma ? ',' : ';') + |
||||
contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
injector.callEmptyBranchEvent = function injectCallEmptyBranchEvent(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + |
||||
'else { emit __BranchCoverage' + contract.contractName + '(\'' + fileName + '\',' + injection.branchId + ',' + injection.locationIdx + ');}\n' + |
||||
contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
|
||||
injector.callAssertPreEvent = function callAssertPreEvent(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + |
||||
'emit __AssertPreCoverage' + contract.contractName + '(\'' + fileName + '\',' + injection.branchId + ');\n' + |
||||
contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
injector.callAssertPostEvent = function callAssertPostEvent(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + |
||||
'emit __AssertPostCoverage' + contract.contractName + '(\'' + fileName + '\',' + injection.branchId + ');\n' + |
||||
contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
injector.openParen = function injectOpenParen(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + '(' + contract.instrumented.slice(injectionPoint); |
||||
const hash = this._getHash(fileName); |
||||
const memoryVariable = this._getMemoryVariableAssignment(fileName); |
||||
const injectable = this._getInjectable(memoryVariable, hash , type) |
||||
|
||||
instrumentation[hash] = { |
||||
id: linecount, |
||||
type: type, |
||||
contractPath: fileName, |
||||
hits: 0 |
||||
} |
||||
|
||||
contract.instrumented = `${start}${injectable}${end}`; |
||||
} |
||||
|
||||
injectStatement(contract, fileName, injectionPoint, injection, instrumentation) { |
||||
const type = 'statement'; |
||||
const start = contract.instrumented.slice(0, injectionPoint); |
||||
const end = contract.instrumented.slice(injectionPoint); |
||||
|
||||
const hash = this._getHash(fileName); |
||||
const memoryVariable = this._getMemoryVariableAssignment(fileName); |
||||
const injectable = this._getInjectable(memoryVariable, hash, type) |
||||
|
||||
instrumentation[hash] = { |
||||
id: injection.statementId, |
||||
type: type, |
||||
contractPath: fileName, |
||||
hits: 0 |
||||
} |
||||
|
||||
contract.instrumented = `${start}${injectable}${end}`; |
||||
}; |
||||
|
||||
injector.closeParen = function injectCloseParen(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + ')' + contract.instrumented.slice(injectionPoint); |
||||
injectFunction(contract, fileName, injectionPoint, injection, instrumentation){ |
||||
const type = 'function'; |
||||
const start = contract.instrumented.slice(0, injectionPoint); |
||||
const end = contract.instrumented.slice(injectionPoint); |
||||
|
||||
const hash = this._getHash(fileName); |
||||
const memoryVariableDefinition = this._getMemoryVariableDefinition(fileName); |
||||
const memoryVariable = this._getMemoryVariableAssignment(fileName); |
||||
const injectable = this._getInjectable(memoryVariable, hash, type); |
||||
|
||||
instrumentation[hash] = { |
||||
id: injection.fnId, |
||||
type: type, |
||||
contractPath: fileName, |
||||
hits: 0 |
||||
} |
||||
|
||||
contract.instrumented = `${start}${memoryVariableDefinition}${injectable}${end}`; |
||||
} |
||||
|
||||
injectBranch(contract, fileName, injectionPoint, injection, instrumentation){ |
||||
const type = 'branch'; |
||||
const start = contract.instrumented.slice(0, injectionPoint); |
||||
const end = contract.instrumented.slice(injectionPoint); |
||||
|
||||
const hash = this._getHash(fileName); |
||||
const memoryVariable = this._getMemoryVariableAssignment(fileName); |
||||
const injectable = this._getInjectable(memoryVariable, hash, type); |
||||
|
||||
instrumentation[hash] = { |
||||
id: injection.branchId, |
||||
locationIdx: injection.locationIdx, |
||||
type: type, |
||||
contractPath: fileName, |
||||
hits: 0 |
||||
} |
||||
|
||||
contract.instrumented = `${start}${injectable}${end}`; |
||||
} |
||||
|
||||
injectEmptyBranch(contract, fileName, injectionPoint, injection, instrumentation) { |
||||
const type = 'branch'; |
||||
const start = contract.instrumented.slice(0, injectionPoint); |
||||
const end = contract.instrumented.slice(injectionPoint); |
||||
|
||||
const hash = this._getHash(fileName); |
||||
const memoryVariable = this._getMemoryVariableAssignment(fileName); |
||||
const injectable = this._getInjectable(memoryVariable, hash, type); |
||||
|
||||
instrumentation[hash] = { |
||||
id: injection.branchId, |
||||
locationIdx: injection.locationIdx, |
||||
type: type, |
||||
contractPath: fileName, |
||||
hits: 0 |
||||
} |
||||
|
||||
contract.instrumented = `${start}else { ${injectable}}${end}`; |
||||
} |
||||
|
||||
injectAssertPre(contract, fileName, injectionPoint, injection, instrumentation) { |
||||
const type = 'assertPre'; |
||||
const start = contract.instrumented.slice(0, injectionPoint); |
||||
const end = contract.instrumented.slice(injectionPoint); |
||||
|
||||
const hash = this._getHash(fileName); |
||||
const memoryVariable = this._getMemoryVariableAssignment(fileName); |
||||
const injectable = this._getInjectable(memoryVariable, hash, type); |
||||
|
||||
instrumentation[hash] = { |
||||
id: injection.branchId, |
||||
type: type, |
||||
contractPath: fileName, |
||||
hits: 0 |
||||
} |
||||
|
||||
contract.instrumented = `${start}${injectable}${end}`; |
||||
} |
||||
|
||||
injectAssertPost(contract, fileName, injectionPoint, injection, instrumentation) { |
||||
const type = 'assertPost'; |
||||
const start = contract.instrumented.slice(0, injectionPoint); |
||||
const end = contract.instrumented.slice(injectionPoint); |
||||
|
||||
const hash = this._getHash(fileName); |
||||
const memoryVariable = this._getMemoryVariableAssignment(fileName); |
||||
const injectable = this._getInjectable(memoryVariable, hash, type); |
||||
|
||||
instrumentation[hash] = { |
||||
id: injection.branchId, |
||||
type: type, |
||||
contractPath: fileName, |
||||
hits: 0 |
||||
} |
||||
|
||||
contract.instrumented = `${start}${injectable}${end}`; |
||||
} |
||||
}; |
||||
|
||||
injector.literal = function injectLiteral(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + injection.string + contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
injector.statement = function injectStatement(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + |
||||
'emit __StatementCoverage' + contract.contractName + '(\'' + fileName + '\',' + injection.statementId + ');\n' + |
||||
contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
injector.eventDefinition = function injectEventDefinition(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + |
||||
'event __Coverage' + contract.contractName + '(string fileName, uint256 lineNumber);\n' + |
||||
'event __FunctionCoverage' + contract.contractName + '(string fileName, uint256 fnId);\n' + |
||||
'event __StatementCoverage' + contract.contractName + '(string fileName, uint256 statementId);\n' + |
||||
'event __BranchCoverage' + contract.contractName + '(string fileName, uint256 branchId, uint256 locationIdx);\n' + |
||||
'event __AssertPreCoverage' + contract.contractName + '(string fileName, uint256 branchId);\n' + |
||||
'event __AssertPostCoverage' + contract.contractName + '(string fileName, uint256 branchId);\n' + |
||||
|
||||
contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
|
||||
module.exports = injector; |
||||
module.exports = Injector; |
||||
|
@ -1,65 +0,0 @@ |
||||
const SolidityParser = require('solidity-parser-antlr'); |
||||
const preprocessor = require('./preprocessor'); |
||||
const injector = require('./injector'); |
||||
const parse = require('./parse'); |
||||
|
||||
const path = require('path'); |
||||
|
||||
module.exports = function instrumentSolidity(contractSource, fileName) { |
||||
const contract = {}; |
||||
contract.source = contractSource; |
||||
contract.instrumented = contractSource; |
||||
|
||||
contract.runnableLines = []; |
||||
contract.fnMap = {}; |
||||
contract.fnId = 0; |
||||
contract.branchMap = {}; |
||||
contract.branchId = 0; |
||||
contract.statementMap = {}; |
||||
contract.statementId = 0; |
||||
contract.injectionPoints = {}; |
||||
|
||||
// First, we run over the original contract to get the source mapping.
|
||||
let ast = SolidityParser.parse(contract.source, {range: true}); |
||||
parse[ast.type](contract, ast); |
||||
const retValue = JSON.parse(JSON.stringify(contract)); |
||||
|
||||
// Now, we reset almost everything and use the preprocessor first to increase our effectiveness.
|
||||
contract.runnableLines = []; |
||||
contract.fnMap = {}; |
||||
contract.fnId = 0; |
||||
contract.branchMap = {}; |
||||
contract.branchId = 0; |
||||
contract.statementMap = {}; |
||||
contract.statementId = 0; |
||||
contract.injectionPoints = {}; |
||||
|
||||
contract.preprocessed = preprocessor.run(contract.source); |
||||
contract.instrumented = contract.preprocessed; |
||||
ast = SolidityParser.parse(contract.preprocessed, {range: true}); |
||||
const contractStatement = ast.children.filter(node => (node.type === 'ContractDefinition' || |
||||
node.type === 'LibraryDefinition' || |
||||
node.type === 'InterfaceDefinition')); |
||||
contract.contractName = contractStatement[0].name; |
||||
|
||||
parse[ast.type](contract, ast); |
||||
|
||||
// We have to iterate through these injection points in descending order to not mess up
|
||||
// the injection process.
|
||||
const sortedPoints = Object.keys(contract.injectionPoints).sort((a, b) => b - a); |
||||
sortedPoints.forEach(injectionPoint => { |
||||
// Line instrumentation has to happen first
|
||||
contract.injectionPoints[injectionPoint].sort((a, b) => { |
||||
const eventTypes = ['openParen', 'callBranchEvent', 'callEmptyBranchEvent', 'callEvent']; |
||||
return eventTypes.indexOf(b.type) - eventTypes.indexOf(a.type); |
||||
}); |
||||
contract.injectionPoints[injectionPoint].forEach(injection => { |
||||
injector[injection.type](contract, fileName, injectionPoint, injection); |
||||
}); |
||||
}); |
||||
retValue.runnableLines = contract.runnableLines; |
||||
retValue.contract = contract.instrumented; |
||||
retValue.contractName = contractStatement[0].name; |
||||
|
||||
return retValue; |
||||
}; |
@ -1,254 +1,106 @@ |
||||
const instrumenter = {}; |
||||
|
||||
// These functions work out where in an expression we can inject our
|
||||
// instrumenation events.
|
||||
|
||||
function createOrAppendInjectionPoint(contract, key, value) { |
||||
if (contract.injectionPoints[key]) { |
||||
contract.injectionPoints[key].push(value); |
||||
} else { |
||||
contract.injectionPoints[key] = [value]; |
||||
} |
||||
const SolidityParser = require('solidity-parser-antlr'); |
||||
const path = require('path'); |
||||
|
||||
const Injector = require('./injector'); |
||||
const preprocess = require('./preprocessor'); |
||||
const parse = require('./parse'); |
||||
|
||||
/** |
||||
* Top level controller for the instrumentation sequence. Also hosts the instrumentation data map |
||||
* which the vm step listener writes its output to. This only needs to be instantiated once |
||||
* per coverage run. |
||||
*/ |
||||
class Instrumenter { |
||||
|
||||
constructor(){ |
||||
this.instrumentationData = {}; |
||||
this.injector = new Injector(); |
||||
} |
||||
|
||||
instrumenter.prePosition = function prePosition(expression) { |
||||
if (expression.right.type === 'ConditionalExpression' && |
||||
expression.left.type === 'MemberExpression') { |
||||
expression.range[0] -= 2; |
||||
} |
||||
}; |
||||
|
||||
instrumenter.instrumentAssignmentExpression = function instrumentAssignmentExpression(contract, expression) { |
||||
|
||||
// This is suspended for 0.5.0 which tries to accomodate the new `emit` keyword.
|
||||
// Solc is not allowing us to use the construction `emit SomeEvent()` within the parens :/
|
||||
return; |
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// The only time we instrument an assignment expression is if there's a conditional expression on
|
||||
// the right
|
||||
/*if (expression.right.type === 'ConditionalExpression') { |
||||
if (expression.left.type === 'DeclarativeExpression' || expression.left.type === 'Identifier') { |
||||
// Then we need to go from bytes32 varname = (conditional expression)
|
||||
// to bytes32 varname; (,varname) = (conditional expression)
|
||||
createOrAppendInjectionPoint(contract, expression.left.range[1], { |
||||
type: 'literal', string: '; (,' + expression.left.name + ')', |
||||
}); |
||||
instrumenter.instrumentConditionalExpression(contract, expression.right); |
||||
} else if (expression.left.type === 'MemberExpression') { |
||||
createOrAppendInjectionPoint(contract, expression.left.range[0], { |
||||
type: 'literal', string: '(,', |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.left.range[1], { |
||||
type: 'literal', string: ')', |
||||
}); |
||||
instrumenter.instrumentConditionalExpression(contract, expression.right); |
||||
} else { |
||||
const err = 'Error instrumenting assignment expression @ solidity-coverage/lib/instrumenter.js'; |
||||
console.log(err, contract, expression.left); |
||||
process.exit(); |
||||
_isRootNode(node){ |
||||
return (node.type === 'ContractDefinition' || |
||||
node.type === 'LibraryDefinition' || |
||||
node.type === 'InterfaceDefinition'); |
||||
} |
||||
}*/ |
||||
}; |
||||
|
||||
instrumenter.instrumentConditionalExpression = function instrumentConditionalExpression(contract, expression) { |
||||
// ----------------------------------------------------------------------------------------------
|
||||
// This is suspended for 0.5.0 which tries to accomodate the new `emit` keyword.
|
||||
// Solc is not allowing us to use the construction `emit SomeEvent()` within the parens :/
|
||||
// Very sad, this is the coolest thing in here.
|
||||
return; |
||||
// ----------------------------------------------------------------------------------------------
|
||||
|
||||
/*contract.branchId += 1; |
||||
|
||||
const startline = (contract.instrumented.slice(0, expression.range[0]).match(/\n/g) || []).length + 1; |
||||
const startcol = expression.range[0] - contract.instrumented.slice(0, expression.range[0]).lastIndexOf('\n') - 1; |
||||
const consequentStartCol = startcol + (contract, expression.trueBody.range[0] - expression.range[0]); |
||||
const consequentEndCol = consequentStartCol + (contract, expression.trueBody.range[1] - expression.trueBody.range[0]); |
||||
const alternateStartCol = startcol + (contract, expression.falseBody.range[0] - expression.range[0]); |
||||
const alternateEndCol = alternateStartCol + (contract, expression.falseBody.range[1] - expression.falseBody.range[0]); |
||||
// NB locations for conditional branches in istanbul are length 1 and associated with the : and ?.
|
||||
contract.branchMap[contract.branchId] = { |
||||
line: startline, |
||||
type: 'cond-expr', |
||||
locations: [{ |
||||
start: { |
||||
line: startline, column: consequentStartCol, |
||||
}, |
||||
end: { |
||||
line: startline, column: consequentEndCol, |
||||
}, |
||||
}, { |
||||
start: { |
||||
line: startline, column: alternateStartCol, |
||||
}, |
||||
end: { |
||||
line: startline, column: alternateEndCol, |
||||
}, |
||||
}], |
||||
}; |
||||
// Right, this could be being used just by itself or as an assignment. In the case of the latter, because
|
||||
// the comma operator doesn't exist, we're going to have to get funky.
|
||||
// if we're on a line by ourselves, this is easier
|
||||
//
|
||||
// Now if we've got to wrap the expression it's being set equal to, do that...
|
||||
|
||||
|
||||
// Wrap the consequent
|
||||
createOrAppendInjectionPoint(contract, expression.trueBody.range[0], { |
||||
type: 'openParen', |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.trueBody.range[0], { |
||||
type: 'callBranchEvent', comma: true, branchId: contract.branchId, locationIdx: 0, |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.trueBody.range[1], { |
||||
type: 'closeParen', |
||||
}); |
||||
|
||||
// Wrap the alternate
|
||||
createOrAppendInjectionPoint(contract, expression.falseBody.range[0], { |
||||
type: 'openParen', |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.falseBody.range[0], { |
||||
type: 'callBranchEvent', comma: true, branchId: contract.branchId, locationIdx: 1, |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.falseBody.range[1], { |
||||
type: 'closeParen', |
||||
});*/ |
||||
}; |
||||
|
||||
instrumenter.instrumentStatement = function instrumentStatement(contract, expression) { |
||||
contract.statementId += 1; |
||||
// We need to work out the lines and columns the expression starts and ends
|
||||
const startline = (contract.instrumented.slice(0, expression.range[0]).match(/\n/g) || []).length + 1; |
||||
const startcol = expression.range[0] - contract.instrumented.slice(0, expression.range[0]).lastIndexOf('\n') - 1; |
||||
const expressionContent = contract.instrumented.slice(expression.range[0], expression.range[1] + 1); |
||||
|
||||
const endline = startline + (contract, expressionContent.match('/\n/g') || []).length; |
||||
let endcol; |
||||
if (expressionContent.lastIndexOf('\n') >= 0) { |
||||
endcol = contract.instrumented.slice(expressionContent.lastIndexOf('\n'), expression.range[1]).length; |
||||
} else { |
||||
endcol = startcol + (contract, expressionContent.length - 1); |
||||
} |
||||
contract.statementMap[contract.statementId] = { |
||||
start: { |
||||
line: startline, column: startcol, |
||||
}, |
||||
end: { |
||||
line: endline, column: endcol, |
||||
}, |
||||
}; |
||||
createOrAppendInjectionPoint(contract, expression.range[0], { |
||||
type: 'statement', statementId: contract.statementId, |
||||
}); |
||||
}; |
||||
|
||||
instrumenter.instrumentLine = function instrumentLine(contract, expression) { |
||||
// what's the position of the most recent newline?
|
||||
const startchar = expression.range[0]; |
||||
const endchar = expression.range[1] + 1; |
||||
const lastNewLine = contract.instrumented.slice(0, startchar).lastIndexOf('\n'); |
||||
const nextNewLine = startchar + contract.instrumented.slice(startchar).indexOf('\n'); |
||||
const contractSnipped = contract.instrumented.slice(lastNewLine, nextNewLine); |
||||
const restOfLine = contract.instrumented.slice(endchar, nextNewLine); |
||||
|
||||
if (contract.instrumented.slice(lastNewLine, startchar).trim().length === 0 && |
||||
(restOfLine.replace(';', '').trim().length === 0 || restOfLine.replace(';', '').trim().substring(0, 2) === '//')) { |
||||
createOrAppendInjectionPoint(contract, lastNewLine + 1, { |
||||
type: 'callEvent', |
||||
}); |
||||
} else if (contract.instrumented.slice(lastNewLine, startchar).replace('{', '').trim().length === 0 && |
||||
contract.instrumented.slice(endchar, nextNewLine).replace(/[;}]/g, '').trim().length === 0) { |
||||
createOrAppendInjectionPoint(contract, expression.range[0], { |
||||
type: 'callEvent', |
||||
}); |
||||
_initializeCoverageFields(contract){ |
||||
contract.runnableLines = []; |
||||
contract.fnMap = {}; |
||||
contract.fnId = 0; |
||||
contract.branchMap = {}; |
||||
contract.branchId = 0; |
||||
contract.statementMap = {}; |
||||
contract.statementId = 0; |
||||
contract.injectionPoints = {}; |
||||
} |
||||
// Is everything before us and after us on this line whitespace?
|
||||
}; |
||||
|
||||
instrumenter.instrumentFunctionDeclaration = function instrumentFunctionDeclaration(contract, expression) { |
||||
contract.fnId += 1; |
||||
const startline = (contract.instrumented.slice(0, expression.range[0]).match(/\n/g) || []).length + 1; |
||||
// We need to work out the lines and columns the function declaration starts and ends
|
||||
const startcol = expression.range[0] - contract.instrumented.slice(0, expression.range[0]).lastIndexOf('\n') - 1; |
||||
const endlineDelta = contract.instrumented.slice(expression.range[0]).indexOf('{'); |
||||
const functionDefinition = contract.instrumented.slice(expression.range[0], expression.range[0] + endlineDelta); |
||||
const endline = startline + (functionDefinition.match(/\n/g) || []).length; |
||||
const endcol = functionDefinition.length - functionDefinition.lastIndexOf('\n'); |
||||
contract.fnMap[contract.fnId] = { |
||||
name: expression.isConstructor ? 'constructor' : expression.name, |
||||
line: startline, |
||||
loc: { |
||||
start: { |
||||
line: startline, column: startcol, |
||||
}, |
||||
end: { |
||||
line: endline, column: endcol, |
||||
}, |
||||
}, |
||||
}; |
||||
createOrAppendInjectionPoint(contract, expression.range[0] + endlineDelta + 1, { |
||||
type: 'callFunctionEvent', fnId: contract.fnId, |
||||
|
||||
|
||||
/** |
||||
* Per `contractSource`: |
||||
* - wraps any unbracketed singleton consequents of if, for, while stmts (preprocessor.js) |
||||
* - walks the file's AST, creating an instrumentation map (parse.js, registrar.js) |
||||
* - injects `instrumentation` solidity statements into the target solidity source (injector.js) |
||||
* |
||||
* @param {String} contractSource solidity source code |
||||
* @param {String} fileName absolute path to source file |
||||
* @return {Object} instrumented `contract` object |
||||
* { |
||||
* contract: instrumented solidity source code, |
||||
* contractName: contract name, |
||||
* runnableLines: integer |
||||
* } |
||||
* |
||||
*/ |
||||
instrument(contractSource, fileName) { |
||||
const contract = {}; |
||||
|
||||
contract.source = contractSource; |
||||
contract.instrumented = contractSource; |
||||
|
||||
this._initializeCoverageFields(contract); |
||||
|
||||
// First, we run over the original contract to get the source mapping.
|
||||
let ast = SolidityParser.parse(contract.source, {range: true}); |
||||
parse[ast.type](contract, ast); |
||||
const retValue = JSON.parse(JSON.stringify(contract)); // ?????
|
||||
|
||||
// Now, we reset almost everything and use the preprocessor to increase our effectiveness.
|
||||
this._initializeCoverageFields(contract); |
||||
contract.instrumented = preprocess(contract.source); |
||||
|
||||
// Walk the AST, recording injection points
|
||||
ast = SolidityParser.parse(contract.instrumented, {range: true}); |
||||
contract.contractName = ast.children.filter(node => this._isRootNode(node))[0].name; |
||||
parse[ast.type](contract, ast); |
||||
|
||||
// We have to iterate through these points in descending order
|
||||
const sortedPoints = Object.keys(contract.injectionPoints).sort((a, b) => b - a); |
||||
|
||||
sortedPoints.forEach(injectionPoint => { |
||||
|
||||
// Line instrumentation has to happen first
|
||||
contract.injectionPoints[injectionPoint].sort((a, b) => { |
||||
const injections = ['injectBranch', 'injectEmptyBranch', 'injectLine']; |
||||
return injections.indexOf(b.type) - injections.indexOf(a.type); |
||||
}); |
||||
}; |
||||
|
||||
instrumenter.addNewBranch = function addNewBranch(contract, expression) { |
||||
contract.branchId += 1; |
||||
const startline = (contract.instrumented.slice(0, expression.range[0]).match(/\n/g) || []).length + 1; |
||||
const startcol = expression.range[0] - contract.instrumented.slice(0, expression.range[0]).lastIndexOf('\n') - 1; |
||||
// NB locations for if branches in istanbul are zero length and associated with the start of the if.
|
||||
contract.branchMap[contract.branchId] = { |
||||
line: startline, |
||||
type: 'if', |
||||
locations: [{ |
||||
start: { |
||||
line: startline, column: startcol, |
||||
}, |
||||
end: { |
||||
line: startline, column: startcol, |
||||
}, |
||||
}, { |
||||
start: { |
||||
line: startline, column: startcol, |
||||
}, |
||||
end: { |
||||
line: startline, column: startcol, |
||||
}, |
||||
}], |
||||
}; |
||||
}; |
||||
|
||||
instrumenter.instrumentAssertOrRequire = function instrumentAssertOrRequire(contract, expression) { |
||||
instrumenter.addNewBranch(contract, expression); |
||||
createOrAppendInjectionPoint(contract, expression.range[0], { |
||||
type: 'callAssertPreEvent', branchId: contract.branchId, |
||||
|
||||
contract.injectionPoints[injectionPoint].forEach(injection => { |
||||
this.injector[injection.type]( |
||||
contract, |
||||
fileName, |
||||
injectionPoint, |
||||
injection, |
||||
this.instrumentationData |
||||
); |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.range[1] + 2, { |
||||
type: 'callAssertPostEvent', branchId: contract.branchId, |
||||
}); |
||||
}; |
||||
|
||||
instrumenter.instrumentIfStatement = function instrumentIfStatement(contract, expression) { |
||||
instrumenter.addNewBranch(contract, expression); |
||||
if (expression.trueBody.type === 'Block') { |
||||
createOrAppendInjectionPoint(contract, expression.trueBody.range[0] + 1, { |
||||
type: 'callBranchEvent', branchId: contract.branchId, locationIdx: 0, |
||||
}); |
||||
retValue.runnableLines = contract.runnableLines; |
||||
retValue.contract = contract.instrumented; |
||||
retValue.contractName = contract.contractName; |
||||
|
||||
return retValue; |
||||
} |
||||
if (expression.falseBody && expression.falseBody.type === 'IfStatement') { |
||||
// Do nothing - we must be pre-preprocessor, so don't bother instrumenting -
|
||||
// when we're actually instrumenting, this will never happen (we've wrapped it in
|
||||
// a block statement)
|
||||
} else if (expression.falseBody && expression.falseBody.type === 'Block') { |
||||
createOrAppendInjectionPoint(contract, expression.falseBody.range[0] + 1, { |
||||
type: 'callBranchEvent', branchId: contract.branchId, locationIdx: 1, |
||||
}); |
||||
} else { |
||||
createOrAppendInjectionPoint(contract, expression.trueBody.range[1] + 1, { |
||||
type: 'callEmptyBranchEvent', branchId: contract.branchId, locationIdx: 1, |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
module.exports = instrumenter; |
||||
module.exports = Instrumenter; |
||||
|
@ -0,0 +1,251 @@ |
||||
/** |
||||
* Registers injections points (e.g source location, type) and their associated data with |
||||
* a contract / instrumentation target. Run during the `parse` step. This data is |
||||
* consumed by the Injector as it modifies the source code in instrumentation's final step. |
||||
*/ |
||||
class Registrar { |
||||
|
||||
constructor(){} |
||||
|
||||
/** |
||||
* Adds injection point to injection points map |
||||
* @param {Object} contract instrumentation target |
||||
* @param {String} key injection point `type` |
||||
* @param {Number} value injection point `id` |
||||
*/ |
||||
_createInjectionPoint(contract, key, value) { |
||||
(contract.injectionPoints[key]) |
||||
? contract.injectionPoints[key].push(value) |
||||
: contract.injectionPoints[key] = [value]; |
||||
} |
||||
|
||||
/** |
||||
* TODO - idk what this is anymore |
||||
* @param {Object} expression AST node |
||||
*/ |
||||
prePosition(expression) { |
||||
if ( |
||||
expression.right.type === 'ConditionalExpression' && |
||||
expression.left.type === 'MemberExpression' |
||||
) { |
||||
expression.range[0] -= 2; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Registers injections for statement measurements |
||||
* @param {Object} contract instrumentation target |
||||
* @param {Object} expression AST node |
||||
*/ |
||||
statement(contract, expression) { |
||||
const startContract = contract.instrumented.slice(0, expression.range[0]); |
||||
const startline = ( startContract.match(/\n/g) || [] ).length + 1; |
||||
const startcol = expression.range[0] - startContract.lastIndexOf('\n') - 1; |
||||
|
||||
const expressionContent = contract.instrumented.slice( |
||||
expression.range[0], |
||||
expression.range[1] + 1 |
||||
); |
||||
|
||||
const endline = startline + (contract, expressionContent.match('/\n/g') || []).length; |
||||
|
||||
let endcol; |
||||
if (expressionContent.lastIndexOf('\n') >= 0) { |
||||
|
||||
endcol = contract.instrumented.slice( |
||||
expressionContent.lastIndexOf('\n'), |
||||
expression.range[1] |
||||
).length; |
||||
|
||||
} else endcol = startcol + (contract, expressionContent.length - 1); |
||||
|
||||
contract.statementId += 1; |
||||
contract.statementMap[contract.statementId] = { |
||||
start: { line: startline, column: startcol }, |
||||
end: { line: endline, column: endcol }, |
||||
}; |
||||
|
||||
this._createInjectionPoint(contract, expression.range[0], { |
||||
type: 'injectStatement', statementId: contract.statementId, |
||||
}); |
||||
}; |
||||
|
||||
/** |
||||
* Registers injections for line measurements |
||||
* @param {Object} contract instrumentation target |
||||
* @param {Object} expression AST node |
||||
*/ |
||||
line(contract, expression) { |
||||
const startchar = expression.range[0]; |
||||
const endchar = expression.range[1] + 1; |
||||
const lastNewLine = contract.instrumented.slice(0, startchar).lastIndexOf('\n'); |
||||
const nextNewLine = startchar + contract.instrumented.slice(startchar).indexOf('\n'); |
||||
const contractSnipped = contract.instrumented.slice(lastNewLine, nextNewLine); |
||||
const restOfLine = contract.instrumented.slice(endchar, nextNewLine); |
||||
|
||||
if ( |
||||
contract.instrumented.slice(lastNewLine, startchar).trim().length === 0 && |
||||
( |
||||
restOfLine.replace(';', '').trim().length === 0 || |
||||
restOfLine.replace(';', '').trim().substring(0, 2) === '//' |
||||
) |
||||
) |
||||
{ |
||||
this._createInjectionPoint(contract, lastNewLine + 1, { type: 'injectLine' }); |
||||
|
||||
} else if ( |
||||
contract.instrumented.slice(lastNewLine, startchar).replace('{', '').trim().length === 0 && |
||||
contract.instrumented.slice(endchar, nextNewLine).replace(/[;}]/g, '').trim().length === 0) |
||||
{ |
||||
this._createInjectionPoint(contract, expression.range[0], { type: 'injectLine' }); |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* Registers injections for function measurements |
||||
* @param {Object} contract instrumentation target |
||||
* @param {Object} expression AST node |
||||
*/ |
||||
functionDeclaration(contract, expression) { |
||||
const startContract = contract.instrumented.slice(0, expression.range[0]); |
||||
const startline = ( startContract.match(/\n/g) || [] ).length + 1; |
||||
const startcol = expression.range[0] - startContract.lastIndexOf('\n') - 1; |
||||
|
||||
const endlineDelta = contract.instrumented.slice(expression.range[0]).indexOf('{'); |
||||
const functionDefinition = contract.instrumented.slice( |
||||
expression.range[0], |
||||
expression.range[0] + endlineDelta |
||||
); |
||||
const endline = startline + (functionDefinition.match(/\n/g) || []).length; |
||||
const endcol = functionDefinition.length - functionDefinition.lastIndexOf('\n'); |
||||
|
||||
contract.fnId += 1; |
||||
contract.fnMap[contract.fnId] = { |
||||
name: expression.isConstructor ? 'constructor' : expression.name, |
||||
line: startline, |
||||
loc: { |
||||
start: { line: startline, column: startcol }, |
||||
end: { line: endline, column: endcol }, |
||||
}, |
||||
}; |
||||
|
||||
this._createInjectionPoint( |
||||
contract, |
||||
expression.range[0] + endlineDelta + 1, |
||||
{ |
||||
type: 'injectFunction', |
||||
fnId: contract.fnId, |
||||
} |
||||
); |
||||
}; |
||||
|
||||
/** |
||||
* Registers injections for branch measurements. This generic is consumed by |
||||
* the `assert/require` and `if` registration methods. |
||||
* @param {Object} contract instrumentation target |
||||
* @param {Object} expression AST node |
||||
*/ |
||||
addNewBranch(contract, expression) { |
||||
const startContract = contract.instrumented.slice(0, expression.range[0]); |
||||
const startline = ( startContract.match(/\n/g) || [] ).length + 1; |
||||
const startcol = expression.range[0] - startContract.lastIndexOf('\n') - 1; |
||||
|
||||
contract.branchId += 1; |
||||
|
||||
// NB locations for if branches in istanbul are zero
|
||||
// length and associated with the start of the if.
|
||||
contract.branchMap[contract.branchId] = { |
||||
line: startline, |
||||
type: 'if', |
||||
locations: [{ |
||||
start: { |
||||
line: startline, column: startcol, |
||||
}, |
||||
end: { |
||||
line: startline, column: startcol, |
||||
}, |
||||
}, { |
||||
start: { |
||||
line: startline, column: startcol, |
||||
}, |
||||
end: { |
||||
line: startline, column: startcol, |
||||
}, |
||||
}], |
||||
}; |
||||
}; |
||||
|
||||
/** |
||||
* Registers injections for assert/require statement measurements (branches) |
||||
* @param {Object} contract instrumentation target |
||||
* @param {Object} expression AST node |
||||
*/ |
||||
assertOrRequire(contract, expression) { |
||||
this.addNewBranch(contract, expression); |
||||
this._createInjectionPoint( |
||||
contract, |
||||
expression.range[0], |
||||
{ |
||||
type: 'injectAssertPre', |
||||
branchId: contract.branchId, |
||||
} |
||||
); |
||||
this._createInjectionPoint( |
||||
contract, |
||||
expression.range[1] + 2, |
||||
{ |
||||
type: 'injectAssertPost', |
||||
branchId: contract.branchId, |
||||
} |
||||
); |
||||
}; |
||||
|
||||
/** |
||||
* Registers injections for if statement measurements (branches) |
||||
* @param {Object} contract instrumentation target |
||||
* @param {Object} expression AST node |
||||
*/ |
||||
ifStatement(contract, expression) { |
||||
this.addNewBranch(contract, expression); |
||||
|
||||
if (expression.trueBody.type === 'Block') { |
||||
this._createInjectionPoint( |
||||
contract, |
||||
expression.trueBody.range[0] + 1, |
||||
{ |
||||
type: 'injectBranch', |
||||
branchId: contract.branchId, |
||||
locationIdx: 0, |
||||
} |
||||
); |
||||
} |
||||
|
||||
if (expression.falseBody && expression.falseBody.type === 'IfStatement') { |
||||
|
||||
// Do nothing - we must be pre-preprocessing
|
||||
|
||||
} else if (expression.falseBody && expression.falseBody.type === 'Block') { |
||||
this._createInjectionPoint( |
||||
contract, |
||||
expression.falseBody.range[0] + 1, |
||||
{ |
||||
type: 'injectBranch', |
||||
branchId: contract.branchId, |
||||
locationIdx: 1, |
||||
} |
||||
); |
||||
} else { |
||||
this._createInjectionPoint( |
||||
contract, |
||||
expression.trueBody.range[1] + 1, |
||||
{ |
||||
type: 'injectEmptyBranch', |
||||
branchId: contract.branchId, |
||||
locationIdx: 1, |
||||
} |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
module.exports = Registrar; |
@ -0,0 +1,121 @@ |
||||
/** |
||||
* This is logic to instrument ternary conditional assignment statements. Preserving |
||||
* here for the time being, because instrumentation of these became impossible in |
||||
* solc >= 0.5.0 |
||||
*/ |
||||
|
||||
function instrumentAssignmentExpression(contract, expression) { |
||||
|
||||
// This is suspended for 0.5.0 which tries to accomodate the new `emit` keyword.
|
||||
// Solc is not allowing us to use the construction `emit SomeEvent()` within the parens :/
|
||||
return; |
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// The only time we instrument an assignment expression is if there's a conditional expression on
|
||||
// the right
|
||||
/*if (expression.right.type === 'ConditionalExpression') { |
||||
if (expression.left.type === 'DeclarativeExpression' || expression.left.type === 'Identifier') { |
||||
// Then we need to go from bytes32 varname = (conditional expression)
|
||||
// to bytes32 varname; (,varname) = (conditional expression)
|
||||
createOrAppendInjectionPoint(contract, expression.left.range[1], { |
||||
type: 'literal', string: '; (,' + expression.left.name + ')', |
||||
}); |
||||
instrumenter.instrumentConditionalExpression(contract, expression.right); |
||||
} else if (expression.left.type === 'MemberExpression') { |
||||
createOrAppendInjectionPoint(contract, expression.left.range[0], { |
||||
type: 'literal', string: '(,', |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.left.range[1], { |
||||
type: 'literal', string: ')', |
||||
}); |
||||
instrumenter.instrumentConditionalExpression(contract, expression.right); |
||||
} else { |
||||
const err = 'Error instrumenting assignment expression @ solidity-coverage/lib/instrumenter.js'; |
||||
console.log(err, contract, expression.left); |
||||
process.exit(); |
||||
} |
||||
}*/ |
||||
}; |
||||
|
||||
function instrumentConditionalExpression(contract, expression) { |
||||
// ----------------------------------------------------------------------------------------------
|
||||
// This is suspended for 0.5.0 which tries to accomodate the new `emit` keyword.
|
||||
// Solc is not allowing us to use the construction `emit SomeEvent()` within the parens :/
|
||||
// Very sad, this is the coolest thing in here.
|
||||
return; |
||||
// ----------------------------------------------------------------------------------------------
|
||||
|
||||
/*contract.branchId += 1; |
||||
|
||||
const startline = (contract.instrumented.slice(0, expression.range[0]).match(/\n/g) || []).length + 1; |
||||
const startcol = expression.range[0] - contract.instrumented.slice(0, expression.range[0]).lastIndexOf('\n') - 1; |
||||
const consequentStartCol = startcol + (contract, expression.trueBody.range[0] - expression.range[0]); |
||||
const consequentEndCol = consequentStartCol + (contract, expression.trueBody.range[1] - expression.trueBody.range[0]); |
||||
const alternateStartCol = startcol + (contract, expression.falseBody.range[0] - expression.range[0]); |
||||
const alternateEndCol = alternateStartCol + (contract, expression.falseBody.range[1] - expression.falseBody.range[0]); |
||||
// NB locations for conditional branches in istanbul are length 1 and associated with the : and ?.
|
||||
contract.branchMap[contract.branchId] = { |
||||
line: startline, |
||||
type: 'cond-expr', |
||||
locations: [{ |
||||
start: { |
||||
line: startline, column: consequentStartCol, |
||||
}, |
||||
end: { |
||||
line: startline, column: consequentEndCol, |
||||
}, |
||||
}, { |
||||
start: { |
||||
line: startline, column: alternateStartCol, |
||||
}, |
||||
end: { |
||||
line: startline, column: alternateEndCol, |
||||
}, |
||||
}], |
||||
}; |
||||
// Right, this could be being used just by itself or as an assignment. In the case of the latter, because
|
||||
// the comma operator doesn't exist, we're going to have to get funky.
|
||||
// if we're on a line by ourselves, this is easier
|
||||
//
|
||||
// Now if we've got to wrap the expression it's being set equal to, do that...
|
||||
|
||||
|
||||
// Wrap the consequent
|
||||
createOrAppendInjectionPoint(contract, expression.trueBody.range[0], { |
||||
type: 'openParen', |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.trueBody.range[0], { |
||||
type: 'callBranchEvent', comma: true, branchId: contract.branchId, locationIdx: 0, |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.trueBody.range[1], { |
||||
type: 'closeParen', |
||||
}); |
||||
|
||||
// Wrap the alternate
|
||||
createOrAppendInjectionPoint(contract, expression.falseBody.range[0], { |
||||
type: 'openParen', |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.falseBody.range[0], { |
||||
type: 'callBranchEvent', comma: true, branchId: contract.branchId, locationIdx: 1, |
||||
}); |
||||
createOrAppendInjectionPoint(contract, expression.falseBody.range[1], { |
||||
type: 'closeParen', |
||||
});*/ |
||||
}; |
||||
|
||||
// Paren / Literal injectors
|
||||
/* |
||||
|
||||
injector.openParen = function injectOpenParen(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + '(' + contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
injector.closeParen = function injectCloseParen(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + ')' + contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
injector.literal = function injectLiteral(contract, fileName, injectionPoint, injection) { |
||||
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + injection.string + contract.instrumented.slice(injectionPoint); |
||||
}; |
||||
|
||||
*/ |
@ -1,24 +0,0 @@ |
||||
/* eslint-env node, mocha */ |
||||
|
||||
const assert = require('assert'); |
||||
const fs = require('fs'); |
||||
|
||||
// Fake event for Simple.sol
|
||||
const fakeEvent = {"address":"6d6cf716c2a7672047e15a255d4c9624db60f215","topics":["34b35f4b1a8c3eb2caa69f05fb5aadc827cedd2d8eb3bb3623b6c4bba3baec17"],"data":"00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003a2f55736572732f757365722f53697465732f73632d666f726b732f6d657461636f696e2f636f6e7472616374732f4d657461436f696e2e736f6c000000000000"} |
||||
|
||||
/* { |
||||
address: '7c548f8a5ba3a37774440587743bb50f58c7e91c', |
||||
topics: ['1accf53d733f86cbefdf38d52682bc905cf6715eb3d860be0b5b052e58b0741d'], |
||||
data: '0', |
||||
};*/ |
||||
// Tests whether or not the testCommand option is invoked by exec.js
|
||||
// Mocha's default timeout is 2000 - here we fake the creation of
|
||||
// allFiredEvents at 4000.
|
||||
describe('Test uses mocha', () => { |
||||
it('should run "mocha --timeout 5000" successfully', done => { |
||||
setTimeout(() => { |
||||
fs.writeFileSync('./../allFiredEvents', JSON.stringify(fakeEvent) + '\n'); |
||||
done(); |
||||
}, 4000); |
||||
}); |
||||
}); |
@ -1,21 +0,0 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const Events = artifacts.require('./Events.sol'); |
||||
|
||||
contract('Events', accounts => { |
||||
it('logs events correctly', done => { |
||||
const loggedEvents = []; |
||||
Events.deployed().then(instance => { |
||||
const allEvents = instance.allEvents(); |
||||
|
||||
allEvents.on("data", event => { loggedEvents.push(event); }); |
||||
|
||||
instance.test(5).then(() => { |
||||
const bad = loggedEvents.filter(e => e.event !== 'LogEventOne' && e.event !== 'LogEventTwo'); |
||||
assert(bad.length === 0, 'Did not filter events correctly'); |
||||
done(); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -1,274 +0,0 @@ |
||||
/* eslint-env node, mocha */ |
||||
|
||||
const solc = require('solc'); |
||||
const path = require('path'); |
||||
const getInstrumentedVersion = require('./../lib/instrumentSolidity.js'); |
||||
const util = require('./util/util.js'); |
||||
const CoverageMap = require('./../lib/coverageMap'); |
||||
const vm = require('./util/vm'); |
||||
const assert = require('assert'); |
||||
|
||||
describe('if, else, and else if statements', () => { |
||||
const filePath = path.resolve('./test.sol'); |
||||
const pathPrefix = './'; |
||||
|
||||
it('should compile after instrumenting multiple if-elses', () => { |
||||
const contract = util.getCode('if/else-if-unbracketed-multi.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const output = JSON.parse(solc.compile(util.codeToCompilerInput(info.contract))); |
||||
util.report(output.errors); |
||||
}); |
||||
|
||||
it('should compile after instrumenting unbracketed if-elses', () => { |
||||
const contract = util.getCode('if/if-else-no-brackets.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const output = JSON.parse(solc.compile(util.codeToCompilerInput(info.contract))); |
||||
util.report(output.errors); |
||||
}); |
||||
|
||||
|
||||
it('should cover an if statement with a bracketed consequent', done => { |
||||
const contract = util.getCode('if/if-with-brackets.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const coverage = new CoverageMap(); |
||||
coverage.addContract(info, filePath); |
||||
|
||||
// Runs: a(1) => if (x == 1) { x = 3; }
|
||||
vm.execute(info.contract, 'a', [1]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, { |
||||
5: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].b, { |
||||
1: [1, 0], |
||||
}); |
||||
assert.deepEqual(mapping[filePath].s, { |
||||
1: 1, 2: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].f, { |
||||
1: 1, |
||||
}); |
||||
done(); |
||||
}).catch(done); |
||||
}); |
||||
|
||||
// Runs: a(1) => if (x == 1) x = 2;
|
||||
it('should cover an unbracketed if consequent (single line)', done => { |
||||
const contract = util.getCode('if/if-no-brackets.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const coverage = new CoverageMap(); |
||||
coverage.addContract(info, filePath); |
||||
|
||||
// Same results as previous test
|
||||
vm.execute(info.contract, 'a', [1]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, { |
||||
5: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].b, { |
||||
1: [1, 0], |
||||
}); |
||||
assert.deepEqual(mapping[filePath].s, { |
||||
1: 1, 2: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].f, { |
||||
1: 1, |
||||
}); |
||||
done(); |
||||
}).catch(done); |
||||
}); |
||||
|
||||
it('should cover an if statement with multiline bracketed consequent', done => { |
||||
const contract = util.getCode('if/if-with-brackets-multiline.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const coverage = new CoverageMap(); |
||||
coverage.addContract(info, filePath); |
||||
|
||||
// Runs: a(1) => if (x == 1){\n x = 3; }
|
||||
vm.execute(info.contract, 'a', [1]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, { |
||||
5: 1, 6: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].b, { |
||||
1: [1, 0], |
||||
}); |
||||
assert.deepEqual(mapping[filePath].s, { |
||||
1: 1, 2: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].f, { |
||||
1: 1, |
||||
}); |
||||
done(); |
||||
}).catch(done); |
||||
}); |
||||
|
||||
// Runs: a(1) => if (x == 1)\n x = 3;
|
||||
it('should cover an unbracketed if consequent (multi-line)', done => { |
||||
const contract = util.getCode('if/if-no-brackets-multiline.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const coverage = new CoverageMap(); |
||||
coverage.addContract(info, filePath); |
||||
// Same results as previous test
|
||||
vm.execute(info.contract, 'a', [1]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, { |
||||
5: 1, 6: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].b, { |
||||
1: [1, 0], |
||||
}); |
||||
assert.deepEqual(mapping[filePath].s, { |
||||
1: 1, 2: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].f, { |
||||
1: 1, |
||||
}); |
||||
done(); |
||||
}).catch(done); |
||||
}); |
||||
|
||||
it('should cover a simple if statement with a failing condition', done => { |
||||
const contract = util.getCode('if/if-with-brackets.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const coverage = new CoverageMap(); |
||||
coverage.addContract(info, filePath); |
||||
|
||||
// Runs: a(2) => if (x == 1) { x = 3; }
|
||||
vm.execute(info.contract, 'a', [2]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, { |
||||
5: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].b, { |
||||
1: [0, 1], |
||||
}); |
||||
assert.deepEqual(mapping[filePath].s, { |
||||
1: 1, 2: 0, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].f, { |
||||
1: 1, |
||||
}); |
||||
done(); |
||||
}).catch(done); |
||||
}); |
||||
|
||||
// Runs: a(2) => if (x == 1){\n throw;\n }else{\n x = 5; \n}
|
||||
it('should cover an if statement with a bracketed alternate', done => { |
||||
const contract = util.getCode('if/else-with-brackets.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const coverage = new CoverageMap(); |
||||
coverage.addContract(info, filePath); |
||||
|
||||
vm.execute(info.contract, 'a', [2]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, { |
||||
5: 1, 6: 0, 8: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].b, { |
||||
1: [0, 1], |
||||
}); |
||||
assert.deepEqual(mapping[filePath].s, { |
||||
1: 1, 2: 0, 3: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].f, { |
||||
1: 1, |
||||
}); |
||||
done(); |
||||
}).catch(done); |
||||
}); |
||||
|
||||
it('should cover an if statement with an unbracketed alternate', done => { |
||||
const contract = util.getCode('if/else-without-brackets.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const coverage = new CoverageMap(); |
||||
coverage.addContract(info, filePath); |
||||
|
||||
vm.execute(info.contract, 'a', [2]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, { |
||||
5: 1, 6: 0, 8: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].b, { |
||||
1: [0, 1], |
||||
}); |
||||
assert.deepEqual(mapping[filePath].s, { |
||||
1: 1, 2: 0, 3: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].f, { |
||||
1: 1, |
||||
}); |
||||
done(); |
||||
}).catch(done); |
||||
}); |
||||
|
||||
it('should cover an else if statement with an unbracketed alternate', done => { |
||||
const contract = util.getCode('if/else-if-without-brackets.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const coverage = new CoverageMap(); |
||||
coverage.addContract(info, filePath); |
||||
|
||||
vm.execute(info.contract, 'a', [2]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, { |
||||
5: 1, 6: 0, 8: 0, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].b, { |
||||
1: [0, 1], 2: [0, 1] |
||||
}); |
||||
assert.deepEqual(mapping[filePath].s, { |
||||
1: 1, 2: 0, 3: 1, 4: 0 |
||||
}); |
||||
assert.deepEqual(mapping[filePath].f, { |
||||
1: 1, |
||||
}); |
||||
done(); |
||||
}).catch(done); |
||||
}); |
||||
|
||||
it('should cover nested if statements with missing else statements', done => { |
||||
const contract = util.getCode('if/nested-if-missing-else.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const coverage = new CoverageMap(); |
||||
coverage.addContract(info, filePath); |
||||
vm.execute(info.contract, 'a', [2, 3, 3]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, { |
||||
5: 1, 7: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].b, { |
||||
1: [0, 1], 2: [1, 0], 3: [1, 0], |
||||
}); |
||||
assert.deepEqual(mapping[filePath].s, { |
||||
1: 1, 2: 1, 3: 1, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].f, { |
||||
1: 1, |
||||
}); |
||||
done(); |
||||
}).catch(done); |
||||
}); |
||||
|
||||
it('should cover if-elseif-else statements that are at the same depth as each other', done => { |
||||
const contract = util.getCode('if/if-elseif-else.sol'); |
||||
const info = getInstrumentedVersion(contract, filePath); |
||||
const coverage = new CoverageMap(); |
||||
coverage.addContract(info, filePath); |
||||
vm.execute(info.contract, 'a', [2, 3, 3]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, { |
||||
5: 1, 6: 0, 8: 1, 10: 0, 13: 1, 14: 0, 16: 1, 18: 0, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].b, { |
||||
1: [0, 1], 2: [1, 0], 3: [0, 1], 4: [1, 0] |
||||
}); |
||||
assert.deepEqual(mapping[filePath].s, { |
||||
1: 1, 2: 0, 3: 1, 4: 1, 5: 0, 6: 1, 7: 0, 8: 1, 9: 1, 10: 0, |
||||
}); |
||||
assert.deepEqual(mapping[filePath].f, { |
||||
1: 1, |
||||
}); |
||||
done(); |
||||
}).catch(done); |
||||
}); |
||||
}); |
@ -1,6 +1,3 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const Owned = artifacts.require('./Owned.sol'); |
||||
const Proxy = artifacts.require('./Proxy.sol'); |
||||
|
@ -1,6 +1,3 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const Simple = artifacts.require('./Simple.sol'); |
||||
|
||||
contract('Simple', () => { |
@ -0,0 +1,9 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
contract Test { |
||||
function a(bool _a, bool _b, bool _c) public { |
||||
require(_a && |
||||
_b && |
||||
_c); |
||||
} |
||||
} |
@ -1,8 +1,8 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
import "./../assets/Face.sol"; |
||||
import "./../assets/PureView.sol"; |
||||
import "./../assets/CLibrary.sol"; |
||||
import "./../../externalSources/Face.sol"; |
||||
import "./../../externalSources/PureView.sol"; |
||||
import "./../../externalSources/CLibrary.sol"; |
||||
|
||||
contract TotallyPure is PureView, Face { |
||||
uint onehundred = 99; |
@ -1,3 +1,5 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
contract Test { |
||||
struct Fn { |
||||
function(bytes32) internal view returns(bool) startConditions; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue