Skip to content

Add coverage measurement category options #592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions lib/instrumenter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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){
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 2 additions & 10 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,8 @@ const register = new Registrar();
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
Expand Down
26 changes: 20 additions & 6 deletions lib/registrar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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;
Expand All @@ -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(
Expand Down Expand Up @@ -342,6 +348,8 @@ class Registrar {
};

conditional(contract, expression){
if (!this.enabled.branches) return;

this.addNewConditionalBranch(contract, expression);

// Double open parens
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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') {
Expand Down
6 changes: 4 additions & 2 deletions lib/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ const configSchema = {
autoLaunchServer: {type: "boolean"},
istanbulFolder: {type: "string"},
measureStatementCoverage: {type: "boolean"},
measureFunctionCoverage: {type: "boolean"},
measureModifierCoverage: {type: "boolean"},
measureFunctionCoverage: {type: "boolean"},
measureModifierCoverage: {type: "boolean"},
measureLineCoverage: {type: "boolean"},
measureBranchCoverage: {type: "boolean"},

// Hooks:
onServerReady: {type: "function", format: "isFunction"},
Expand Down
121 changes: 121 additions & 0 deletions test/units/options.js
Original file line number Diff line number Diff line change
@@ -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, {});
});
});
4 changes: 3 additions & 1 deletion test/units/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ describe('config validation', () => {
"autoLaunchServer",
"measureStatementCoverage",
"measureFunctionCoverage",
"measureModifierCoverage"
"measureModifierCoverage",
"measureBranchCoverage",
"measureLineCoverage"
]

options.forEach(name => {
Expand Down
6 changes: 3 additions & 3 deletions test/util/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down