Add coverage measurement category options (#592)

experimental-options
cgewecke 4 years ago
parent c1bd4bcd64
commit 0ac8e742d7
  1. 14
      lib/instrumenter.js
  2. 12
      lib/parse.js
  3. 26
      lib/registrar.js
  4. 2
      lib/validator.js
  5. 121
      test/units/options.js
  6. 4
      test/units/validator.js
  7. 6
      test/util/util.js

@ -15,9 +15,13 @@ class Instrumenter {
constructor(config={}){
this.instrumentationData = {};
this.injector = new Injector();
this.measureStatementCoverage = (config.measureStatementCoverage === false) ? false : true;
this.measureFunctionCoverage = (config.measureFunctionCoverage === false) ? false: true;
this.measureModifierCoverage = (config.measureModifierCoverage === false) ? false: true;
this.enabled = {
statements: (config.measureStatementCoverage === false) ? false : true,
functions: (config.measureFunctionCoverage === false) ? false: true,
modifiers: (config.measureModifierCoverage === false) ? false: true,
branches: (config.measureBranchCoverage === false) ? false: true,
lines: (config.measureLineCoverage === false) ? false: true
};
}
_isRootNode(node){
@ -58,9 +62,7 @@ class Instrumenter {
const contract = {};
this.injector.resetModifierMapping();
parse.configureStatementCoverage(this.measureStatementCoverage)
parse.configureFunctionCoverage(this.measureFunctionCoverage)
parse.configureModifierCoverage(this.measureModifierCoverage)
parse.configure(this.enabled);
contract.source = contractSource;
contract.instrumented = contractSource;

@ -11,16 +11,8 @@ const FILE_SCOPED_ID = "fileScopedId";
const parse = {};
// Utilities
parse.configureStatementCoverage = function(val){
register.measureStatementCoverage = val;
}
parse.configureFunctionCoverage = function(val){
register.measureFunctionCoverage = val;
}
parse.configureModifierCoverage = function(val){
register.measureModifierCoverage = val;
parse.configure = function(_enabled){
register.enabled = Object.assign(register.enabled, _enabled);
}
// Nodes

@ -12,9 +12,13 @@ class Registrar {
this.trackStatements = true;
// These are set by user option and enable/disable the measurement completely
this.measureStatementCoverage = true;
this.measureFunctionCoverage = true;
this.measureModifierCoverage = true;
this.enabled = {
statements: true,
functions: true,
modifiers: true,
branches: true,
lines: true
}
}
/**
@ -37,7 +41,7 @@ class Registrar {
* @param {Object} expression AST node
*/
statement(contract, expression) {
if (!this.trackStatements || !this.measureStatementCoverage) return;
if (!this.trackStatements || !this.enabled.statements) return;
const startContract = contract.instrumented.slice(0, expression.range[0]);
const startline = ( startContract.match(/\n/g) || [] ).length + 1;
@ -77,6 +81,8 @@ class Registrar {
* @param {Object} expression AST node
*/
line(contract, expression) {
if (!this.enabled.lines) return;
const startchar = expression.range[0];
const endchar = expression.range[1] + 1;
const lastNewLine = contract.instrumented.slice(0, startchar).lastIndexOf('\n');
@ -108,7 +114,7 @@ class Registrar {
* @param {Object} expression AST node
*/
functionDeclaration(contract, expression) {
if (!this.measureFunctionCoverage) return;
if (!this.enabled.functions) return;
let start = 0;
contract.fnId += 1;
@ -123,7 +129,7 @@ class Registrar {
}
// Add modifier branch coverage
if (!this.measureModifierCoverage) continue;
if (!this.enabled.modifiers) continue;
this.addNewModifierBranch(contract, modifier);
this._createInjectionPoint(
@ -342,6 +348,8 @@ class Registrar {
};
conditional(contract, expression){
if (!this.enabled.branches) return;
this.addNewConditionalBranch(contract, expression);
// Double open parens
@ -388,6 +396,8 @@ class Registrar {
* @param {Number} injectionIdx pre/post branch index (left=0, right=1)
*/
logicalOR(contract, expression) {
if (!this.enabled.branches) return;
this.addNewLogicalORBranch(contract, expression);
// Left
@ -433,6 +443,8 @@ class Registrar {
* @param {Object} expression AST node
*/
requireBranch(contract, expression) {
if (!this.enabled.branches) return;
this.addNewBranch(contract, expression);
this._createInjectionPoint(
contract,
@ -458,6 +470,8 @@ class Registrar {
* @param {Object} expression AST node
*/
ifStatement(contract, expression) {
if (!this.enabled.branches) return;
this.addNewBranch(contract, expression);
if (expression.trueBody.type === 'Block') {

@ -23,6 +23,8 @@ const configSchema = {
measureStatementCoverage: {type: "boolean"},
measureFunctionCoverage: {type: "boolean"},
measureModifierCoverage: {type: "boolean"},
measureLineCoverage: {type: "boolean"},
measureBranchCoverage: {type: "boolean"},
// Hooks:
onServerReady: {type: "function", format: "isFunction"},

@ -0,0 +1,121 @@
const assert = require('assert');
const util = require('./../util/util.js');
const client = require('ganache-cli');
const Coverage = require('./../../lib/coverage');
const Api = require('./../../lib/api')
describe('measureCoverage options', () => {
let coverage;
let api;
before(async () => {
api = new Api({silent: true});
await api.ganache(client);
})
beforeEach(() => {
api.config = {}
coverage = new Coverage()
});
after(async() => await api.finish());
async function setupAndRun(solidityFile, val){
const contract = await util.bootstrapCoverage(solidityFile, api);
coverage.addContract(contract.instrumented, util.filePath);
/* some methods intentionally fail */
try {
(val)
? await contract.instance.a(val)
: await contract.instance.a();
} catch(e){}
return coverage.generate(contract.data, util.pathPrefix);
}
// if (x == 1 || x == 2) { } else ...
it('should ignore OR branches when measureBranchCoverage = false', async function() {
api.config.measureBranchCoverage = false;
const mapping = await setupAndRun('or/if-or', 1);
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 8: 0
});
assert.deepEqual(mapping[util.filePath].b, {});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 0,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should ignore if/else branches when measureBranchCoverage = false', async function() {
api.config.measureBranchCoverage = false;
const mapping = await setupAndRun('if/if-with-brackets', 1);
assert.deepEqual(mapping[util.filePath].l, {
5: 1,
});
assert.deepEqual(mapping[util.filePath].b, {});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should ignore ternary conditionals when measureBranchCoverage = false', async function() {
api.config.measureBranchCoverage = false;
const mapping = await setupAndRun('conditional/sameline-consequent');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[util.filePath].b, {});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should ignore modifier branches when measureModifierCoverage = false', async function() {
api.config.measureModifierCoverage = false;
const mapping = await setupAndRun('modifiers/same-contract-pass');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 10: 1,
});
assert.deepEqual(mapping[util.filePath].b, { // Source contains a `require`
1: [1, 0]
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1, 2: 1
});
});
it('should ignore statements when measureStatementCoverage = false', async function() {
api.config.measureStatementCoverage = false;
const mapping = await setupAndRun('modifiers/same-contract-pass');
assert.deepEqual(mapping[util.filePath].s, {});
});
it('should ignore lines when measureLineCoverage = false', async function() {
api.config.measureLineCoverage = false;
const mapping = await setupAndRun('modifiers/same-contract-pass');
assert.deepEqual(mapping[util.filePath].l, {});
});
it('should ignore functions when measureFunctionCoverage = false', async function() {
api.config.measureFunctionCoverage = false;
const mapping = await setupAndRun('modifiers/same-contract-pass');
assert.deepEqual(mapping[util.filePath].f, {});
});
});

@ -48,7 +48,9 @@ describe('config validation', () => {
"autoLaunchServer",
"measureStatementCoverage",
"measureFunctionCoverage",
"measureModifierCoverage"
"measureModifierCoverage",
"measureBranchCoverage",
"measureLineCoverage"
]
options.forEach(name => {

@ -71,9 +71,9 @@ function codeToCompilerInput(code) {
// ============================
// Instrumentation Correctness
// ============================
function instrumentAndCompile(sourceName) {
function instrumentAndCompile(sourceName, api={}) {
const contract = getCode(`${sourceName}.sol`)
const instrumenter = new Instrumenter();
const instrumenter = new Instrumenter(api.config);
const instrumented = instrumenter.instrument(contract, filePath);
return {
@ -97,7 +97,7 @@ function report(output=[]) {
// Coverage Correctness
// =====================
async function bootstrapCoverage(file, api){
const info = instrumentAndCompile(file);
const info = instrumentAndCompile(file, api);
info.instance = await getDeployedContractInstance(info, api.server.provider);
api.collector._setInstrumentationData(info.data);
return info;

Loading…
Cancel
Save