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. 6
      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={}){ constructor(config={}){
this.instrumentationData = {}; this.instrumentationData = {};
this.injector = new Injector(); this.injector = new Injector();
this.measureStatementCoverage = (config.measureStatementCoverage === false) ? false : true; this.enabled = {
this.measureFunctionCoverage = (config.measureFunctionCoverage === false) ? false: true; statements: (config.measureStatementCoverage === false) ? false : true,
this.measureModifierCoverage = (config.measureModifierCoverage === 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){ _isRootNode(node){
@ -58,9 +62,7 @@ class Instrumenter {
const contract = {}; const contract = {};
this.injector.resetModifierMapping(); this.injector.resetModifierMapping();
parse.configureStatementCoverage(this.measureStatementCoverage) parse.configure(this.enabled);
parse.configureFunctionCoverage(this.measureFunctionCoverage)
parse.configureModifierCoverage(this.measureModifierCoverage)
contract.source = contractSource; contract.source = contractSource;
contract.instrumented = contractSource; contract.instrumented = contractSource;

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

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

@ -21,8 +21,10 @@ const configSchema = {
autoLaunchServer: {type: "boolean"}, autoLaunchServer: {type: "boolean"},
istanbulFolder: {type: "string"}, istanbulFolder: {type: "string"},
measureStatementCoverage: {type: "boolean"}, measureStatementCoverage: {type: "boolean"},
measureFunctionCoverage: {type: "boolean"}, measureFunctionCoverage: {type: "boolean"},
measureModifierCoverage: {type: "boolean"}, measureModifierCoverage: {type: "boolean"},
measureLineCoverage: {type: "boolean"},
measureBranchCoverage: {type: "boolean"},
// Hooks: // Hooks:
onServerReady: {type: "function", format: "isFunction"}, 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", "autoLaunchServer",
"measureStatementCoverage", "measureStatementCoverage",
"measureFunctionCoverage", "measureFunctionCoverage",
"measureModifierCoverage" "measureModifierCoverage",
"measureBranchCoverage",
"measureLineCoverage"
] ]
options.forEach(name => { options.forEach(name => {

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

Loading…
Cancel
Save