2018-08-08 18:26:40 +00:00
|
|
|
/*global describe, it*/
|
|
|
|
const {assert} = require('chai');
|
2018-08-07 19:26:39 +00:00
|
|
|
const fs = require('fs');
|
|
|
|
const path = require('path');
|
|
|
|
const sinon = require('sinon');
|
|
|
|
|
|
|
|
const ContractSources = require('../lib/modules/coverage/contract_sources');
|
|
|
|
const ContractSource = require('../lib/modules/coverage/contract_source');
|
|
|
|
const SourceMap = require('../lib/modules/coverage/source_map');
|
|
|
|
|
|
|
|
function fixturePath(fixture) {
|
|
|
|
return path.join(__dirname, 'fixtures', fixture);
|
|
|
|
}
|
|
|
|
|
|
|
|
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) => {
|
|
|
|
const contractPath = fixturePath('cont.sol');
|
|
|
|
var cs = new ContractSources([contractPath]);
|
|
|
|
assert.instanceOf(cs.files['cont.sol'], ContractSource);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work when a single path is passed', (done) => {
|
|
|
|
const contractPath = fixturePath('cont.sol');
|
|
|
|
var cs = new ContractSources(contractPath);
|
|
|
|
assert.instanceOf(cs.files['cont.sol'], ContractSource);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should throw an error when the file does not exist', (done) => {
|
|
|
|
assert.throws(() => {
|
2018-08-08 18:26:40 +00:00
|
|
|
new ContractSources(['fixtures/404.sol']);
|
2018-08-30 17:29:33 +00:00
|
|
|
}, /ENOENT: no such file or directory, open/);
|
2018-08-07 19:26:39 +00:00
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#toSolcInputs', () => {
|
|
|
|
it('should build the hash in the format that solc likes', (done) => {
|
|
|
|
const contractPath = fixturePath('cont.sol');
|
|
|
|
var cs = new ContractSources([contractPath]);
|
|
|
|
assert.deepEqual({
|
|
|
|
'cont.sol': {content: cs.files['cont.sol'].body}
|
|
|
|
}, cs.toSolcInputs());
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#parseSolcOutput', () => {
|
|
|
|
it('should send the output to each of the ContractSource instances', (done) => {
|
|
|
|
const contractPath = fixturePath('cont.sol');
|
|
|
|
var cs = new ContractSources([contractPath]);
|
|
|
|
|
|
|
|
var parseSolcOutputSpy = sinon.spy(cs.files['cont.sol'], 'parseSolcOutput');
|
|
|
|
const solcOutput = JSON.parse(loadFixture('solc-output.json'));
|
|
|
|
cs.parseSolcOutput(solcOutput);
|
|
|
|
|
|
|
|
assert(parseSolcOutputSpy.calledOnce);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('ContractSource', () => {
|
|
|
|
const contractSource = `
|
|
|
|
pragma solidity ^0.4.24;
|
|
|
|
|
|
|
|
contract x {
|
|
|
|
int number;
|
|
|
|
string name;
|
|
|
|
|
|
|
|
constructor(string _name)
|
|
|
|
public
|
|
|
|
{
|
|
|
|
name = _name;
|
|
|
|
}
|
|
|
|
|
|
|
|
function g(int _number)
|
|
|
|
public
|
|
|
|
returns (int _multiplication)
|
|
|
|
{
|
|
|
|
number = _number;
|
|
|
|
return _number * 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
function f(int _foo, int _bar)
|
|
|
|
public
|
|
|
|
pure
|
|
|
|
returns (int _addition)
|
|
|
|
{
|
|
|
|
return _foo + _bar;
|
|
|
|
}
|
|
|
|
|
|
|
|
function h(int _bar)
|
|
|
|
public
|
|
|
|
pure
|
|
|
|
returns (bool _great)
|
|
|
|
{
|
|
|
|
if(_bar > 25) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`.trim();
|
|
|
|
|
2018-08-21 20:23:00 +00:00
|
|
|
const cs = new ContractSource('contract.sol', '/tmp/contract.sol', contractSource);
|
2018-08-07 19:26:39 +00:00
|
|
|
|
|
|
|
describe('constructor', () => {
|
|
|
|
it('should set line offsets and line lengths correctly', (done) => {
|
|
|
|
// +1 here accounts for a newline
|
|
|
|
assert.equal("pragma solidity ^0.4.24;".length + 1, cs.lineOffsets[1]);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#sourceMapToLocations', () => {
|
|
|
|
it('should return objects that indicate start and end location and columns', (done) => {
|
|
|
|
// constructor function
|
|
|
|
var loc = cs.sourceMapToLocations('71:60:0');
|
|
|
|
assert.deepEqual({line: 7, column: 2}, loc.start);
|
|
|
|
assert.deepEqual({line: 11, column: 3}, loc.end);
|
|
|
|
|
|
|
|
// f function
|
|
|
|
loc = cs.sourceMapToLocations('257:104:0');
|
|
|
|
assert.deepEqual({line: 21, column: 2}, loc.start);
|
|
|
|
assert.deepEqual({line: 27, column: 3}, loc.end);
|
|
|
|
|
|
|
|
// g function
|
|
|
|
loc = cs.sourceMapToLocations('135:118:0');
|
|
|
|
assert.deepEqual({line: 13, column: 2}, loc.start);
|
|
|
|
assert.deepEqual({line: 19, column: 3}, loc.end);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#parseSolcOutput', () => {
|
|
|
|
it('should parse the 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.contractBytecode);
|
|
|
|
assert.isNotEmpty(contractSource.contractBytecode['x']);
|
|
|
|
|
|
|
|
var bytecode = contractSource.contractBytecode['x'];
|
|
|
|
|
2018-08-28 17:51:55 +00:00
|
|
|
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]);
|
2018-08-07 19:26:39 +00:00
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#generateCodeCoverage', () => {
|
|
|
|
it('should return an error when solc output was not parsed', (done) => {
|
|
|
|
const contractPath = fixturePath('cont.sol');
|
|
|
|
var cs = new ContractSources(contractPath);
|
|
|
|
var contractSource = cs.files['cont.sol'];
|
|
|
|
var trace = JSON.parse(loadFixture('geth-debugtrace-output-g.json'));
|
|
|
|
|
|
|
|
assert.throws(() => {
|
|
|
|
contractSource.generateCodeCoverage(trace);
|
|
|
|
}, 'Error generating coverage: solc output was not assigned');
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return a coverage report when solc output was parsed', (done) => {
|
|
|
|
var solcOutput = JSON.parse(loadFixture('solc-output.json'));
|
|
|
|
const contractPath = fixturePath('cont.sol');
|
|
|
|
var cs = new ContractSources(contractPath);
|
|
|
|
cs.parseSolcOutput(solcOutput);
|
|
|
|
|
|
|
|
var trace = JSON.parse(loadFixture('geth-debugtrace-output-h-5.json'));
|
|
|
|
var coverage = cs.generateCodeCoverage(trace);
|
2018-08-20 17:54:44 +00:00
|
|
|
assert.exists(coverage);
|
2018-08-07 19:26:39 +00:00
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should merge coverages as we add more traces', (done) => {
|
|
|
|
const contractPath = fixturePath('cont.sol');
|
|
|
|
var cs = new ContractSources(contractPath);
|
|
|
|
|
|
|
|
const solcOutput = JSON.parse(loadFixture('solc-output.json'));
|
|
|
|
cs.parseSolcOutput(solcOutput);
|
|
|
|
|
|
|
|
var trace = JSON.parse(loadFixture('geth-debugtrace-output-h-5.json'));
|
|
|
|
cs.generateCodeCoverage(trace);
|
|
|
|
|
|
|
|
trace = JSON.parse(loadFixture('geth-debugtrace-output-h-50.json'));
|
|
|
|
var coverage = cs.generateCodeCoverage(trace)['cont.sol'];
|
|
|
|
|
|
|
|
// In the fixture, the branch has an ID of 61, and the function has the
|
|
|
|
// ID of 63
|
2018-08-28 17:51:55 +00:00
|
|
|
assert.deepEqual([1,0], coverage.b['61']);
|
|
|
|
assert.equal(6, coverage.f['63']);
|
2018-08-07 19:26:39 +00:00
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('SourceMap', () => {
|
|
|
|
describe('#subtract', () => {
|
|
|
|
it('should return the correct values', (done) => {
|
|
|
|
var sm1 = new SourceMap('365:146:0');
|
|
|
|
var sm2 = new SourceMap('428:83:0');
|
|
|
|
|
|
|
|
var result = sm1.subtract(sm2);
|
|
|
|
|
|
|
|
assert.equal(365, result.offset);
|
|
|
|
assert.equal(63, result.length);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2018-08-20 17:54:44 +00:00
|
|
|
|
|
|
|
describe('#createRelativeTo', () => {
|
|
|
|
it('should return an empty source map on an empty string', (done) => {
|
|
|
|
var sm1 = new SourceMap('192:10:0');
|
|
|
|
var sm2 = sm1.createRelativeTo('');
|
|
|
|
|
|
|
|
assert.equal('', sm2.toString());
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return the correct source map on a relative string', (done) => {
|
|
|
|
var sm1 = new SourceMap('192:10:0');
|
|
|
|
var sm2 = sm1.createRelativeTo(':14');
|
|
|
|
|
|
|
|
assert.equal(192, sm2.offset);
|
|
|
|
assert.equal(14, sm2.length);
|
|
|
|
assert.equal(0, sm2.id);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2018-08-07 19:26:39 +00:00
|
|
|
});
|