mirror of https://github.com/embarklabs/embark.git
feat: run coverage for bytecode and deployedBytecode
This commit is contained in:
parent
3406ae833c
commit
f84d7f1d21
|
@ -2,17 +2,15 @@ const SourceMap = require('./source_map');
|
|||
|
||||
class ContractSource {
|
||||
constructor(file, path, body) {
|
||||
let self = this;
|
||||
|
||||
this.file = file;
|
||||
this.path = path;
|
||||
this.body = body;
|
||||
|
||||
this.lineLengths = body.split("\n").map((line) => { return line.length; });
|
||||
this.lineLengths = body.split("\n").map(line => line.length);
|
||||
this.lineCount = this.lineLengths.length;
|
||||
|
||||
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 : this.lineLengths[i-1] + sum[i-1] + 1;
|
||||
return sum;
|
||||
}, []);
|
||||
|
||||
|
@ -57,43 +55,20 @@ class ContractSource {
|
|||
this.id = source.id;
|
||||
this.ast = source.ast;
|
||||
this.contractBytecode = {};
|
||||
this.contractDeployedBytecode = {};
|
||||
|
||||
for(var contractName in contracts) {
|
||||
this.contractBytecode[contractName] = {};
|
||||
this.contractDeployedBytecode[contractName] = {};
|
||||
|
||||
var contract = contracts[contractName];
|
||||
var bytecodeMapping = this.contractBytecode[contractName];
|
||||
var opcodes = contract.evm.deployedBytecode.opcodes.trim().split(' ');
|
||||
var sourceMaps = contract.evm.deployedBytecode.sourceMap.split(';');
|
||||
var opcodes = contract.evm.bytecode.opcodes.trim().split(' ');
|
||||
var deployedOpcodes = contract.evm.deployedBytecode.opcodes.trim().split(' ');
|
||||
var sourceMaps = contract.evm.bytecode.sourceMap.split(';');
|
||||
var deployedSourceMaps = contract.evm.deployedBytecode.sourceMap.split(';');
|
||||
|
||||
var bytecodeIdx = 0;
|
||||
var pc = 0;
|
||||
var instructions = 0;
|
||||
var previousSourceMap = null;
|
||||
|
||||
do {
|
||||
let sourceMap;
|
||||
|
||||
if(previousSourceMap === null) {
|
||||
sourceMap = new SourceMap(sourceMaps[instructions]);
|
||||
} else {
|
||||
sourceMap = previousSourceMap.createRelativeTo(sourceMaps[instructions]);
|
||||
}
|
||||
|
||||
var instruction = opcodes[bytecodeIdx];
|
||||
var length = this._instructionLength(instruction);
|
||||
bytecodeMapping[pc] = {
|
||||
instruction: instruction,
|
||||
sourceMap: sourceMap,
|
||||
jump: sourceMap.jump,
|
||||
seen: false
|
||||
};
|
||||
|
||||
pc += length;
|
||||
instructions++;
|
||||
bytecodeIdx += (length > 1) ? 2 : 1;
|
||||
previousSourceMap = sourceMap;
|
||||
} while(bytecodeIdx < opcodes.length);
|
||||
this._buildContractBytecode(contractName, this.contractBytecode, opcodes, sourceMaps);
|
||||
this._buildContractBytecode(contractName, this.contractDeployedBytecode, deployedOpcodes, deployedSourceMaps);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +77,7 @@ class ContractSource {
|
|||
Object.values(this.contractBytecode).every((contractBytecode) => { return (Object.values(contractBytecode).length <= 1); });
|
||||
}
|
||||
|
||||
/*eslint complexity: ["error", 44]*/
|
||||
/*eslint complexity: ["error", 42]*/
|
||||
generateCodeCoverage(trace) {
|
||||
if(!this.ast || !this.contractBytecode) throw new Error('Error generating coverage: solc output was not assigned');
|
||||
|
||||
|
@ -128,7 +103,6 @@ class ContractSource {
|
|||
let children = [];
|
||||
let markLocations = [];
|
||||
let location;
|
||||
|
||||
switch(node.nodeType) {
|
||||
case 'Assignment':
|
||||
case 'EventDefinition':
|
||||
|
@ -280,7 +254,6 @@ class ContractSource {
|
|||
}
|
||||
|
||||
default:
|
||||
//console.log(`Don't know how to handle node type ${node.nodeType}`);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -294,13 +267,21 @@ class ContractSource {
|
|||
|
||||
} while(nodesRequiringVisiting.length > 0);
|
||||
|
||||
this._generateCodeCoverageForBytecode(trace, coverage, sourceMapToNodeType, this.contractBytecode);
|
||||
this._generateCodeCoverageForBytecode(trace, coverage, sourceMapToNodeType, this.contractDeployedBytecode);
|
||||
|
||||
return coverage;
|
||||
}
|
||||
|
||||
_generateCodeCoverageForBytecode(trace, coverage, sourceMapToNodeType, contractBytecode) {
|
||||
var contractMatches = true;
|
||||
for(var contractName in this.contractBytecode) {
|
||||
var bytecode = this.contractBytecode[contractName];
|
||||
for(var contractName in contractBytecode) {
|
||||
var bytecode = contractBytecode[contractName];
|
||||
|
||||
// Try to match the contract to the bytecode. If it doesn't,
|
||||
// then we bail.
|
||||
contractMatches = trace.structLogs.every((step) => { return bytecode[step.pc]; });
|
||||
|
||||
contractMatches = trace.structLogs.every((step) => bytecode[step.pc]);
|
||||
if(!contractMatches) continue;
|
||||
|
||||
trace.structLogs.forEach((step) => {
|
||||
|
@ -336,8 +317,38 @@ class ContractSource {
|
|||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return coverage;
|
||||
_buildContractBytecode(contractName, contractBytecode, opcodes, sourceMaps) {
|
||||
var bytecodeMapping = contractBytecode[contractName];
|
||||
var bytecodeIdx = 0;
|
||||
var pc = 0;
|
||||
var instructions = 0;
|
||||
var previousSourceMap = null;
|
||||
|
||||
do {
|
||||
let sourceMap;
|
||||
const sourceMapArgs = sourceMaps[instructions];
|
||||
if(previousSourceMap === null) {
|
||||
sourceMap = new SourceMap(sourceMapArgs);
|
||||
} else {
|
||||
sourceMap = previousSourceMap.createRelativeTo(sourceMapArgs);
|
||||
}
|
||||
|
||||
var instruction = opcodes[bytecodeIdx];
|
||||
var length = this._instructionLength(instruction);
|
||||
bytecodeMapping[pc] = {
|
||||
instruction: instruction,
|
||||
sourceMap: sourceMap,
|
||||
jump: sourceMap.jump,
|
||||
seen: false
|
||||
};
|
||||
|
||||
pc += length;
|
||||
instructions++;
|
||||
bytecodeIdx += (length > 1) ? 2 : 1;
|
||||
previousSourceMap = sourceMap;
|
||||
} while(bytecodeIdx < opcodes.length);
|
||||
}
|
||||
|
||||
_instructionLength(instruction) {
|
||||
|
|
|
@ -16,10 +16,6 @@ function loadFixture(fixture) {
|
|||
return fs.readFileSync(fixturePath(fixture)).toString();
|
||||
}
|
||||
|
||||
function dumpToFile(obj, path) {
|
||||
return fs.writeFileSync(path, JSON.stringify(obj));
|
||||
}
|
||||
|
||||
describe('ContractSources', () => {
|
||||
describe('constructor', () => {
|
||||
it('should read files and create instances of ContractSource', (done) => {
|
||||
|
@ -148,6 +144,29 @@ contract x {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#parseSolcOutput', () => {
|
||||
it('should parse the deployed bytecode output correctly', (done) => {
|
||||
var solcOutput = JSON.parse(loadFixture('solc-output.json'));
|
||||
const contractPath = fixturePath('cont.sol');
|
||||
var cs = new ContractSources(contractPath);
|
||||
cs.parseSolcOutput(solcOutput);
|
||||
|
||||
var contractSource = cs.files['cont.sol'];
|
||||
|
||||
assert.isNotEmpty(contractSource.contractDeployedBytecode);
|
||||
assert.isNotEmpty(contractSource.contractDeployedBytecode['x']);
|
||||
|
||||
var bytecode = contractSource.contractDeployedBytecode['x'];
|
||||
|
||||
assert.deepEqual({instruction: 'PUSH1', sourceMap: {offset: 26, length: 487, id: 0, jump: '-'}, jump: '-', seen: false}, bytecode[0]);
|
||||
assert.deepEqual({instruction: 'PUSH1', sourceMap: SourceMap.empty(), seen: false, jump: undefined}, bytecode[2]);
|
||||
assert.deepEqual({instruction: 'MSTORE', sourceMap: SourceMap.empty(), seen: false, jump: undefined}, bytecode[4]);
|
||||
assert.deepEqual({instruction: 'PUSH1', sourceMap: SourceMap.empty(), seen: false, jump: undefined}, bytecode[5]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#parseSolcOutput', () => {
|
||||
it('should parse the bytecode output correctly', (done) => {
|
||||
var solcOutput = JSON.parse(loadFixture('solc-output.json'));
|
||||
|
@ -165,7 +184,7 @@ contract x {
|
|||
assert.deepEqual({instruction: 'PUSH1', sourceMap: {offset: 26, length: 487, id: 0, jump: '-'}, jump: '-', seen: false}, bytecode[0]);
|
||||
assert.deepEqual({instruction: 'PUSH1', sourceMap: SourceMap.empty(), seen: false, jump: undefined}, bytecode[2]);
|
||||
assert.deepEqual({instruction: 'MSTORE', sourceMap: SourceMap.empty(), seen: false, jump: undefined}, bytecode[4]);
|
||||
assert.deepEqual({instruction: 'PUSH1', sourceMap: SourceMap.empty(), seen: false, jump: undefined}, bytecode[5]);
|
||||
assert.deepEqual({instruction: 'CALLVALUE', sourceMap: {offset: 71, length: 60, jump: undefined}, seen: false, jump: undefined}, bytecode[5]);
|
||||
|
||||
done();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue