Skip to content

Commit fcf376d

Browse files
authored
Add coverage measurement category options (#592)
1 parent 56c0c8d commit fcf376d

File tree

7 files changed

+161
-28
lines changed

7 files changed

+161
-28
lines changed

lib/instrumenter.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ class Instrumenter {
1515
constructor(config={}){
1616
this.instrumentationData = {};
1717
this.injector = new Injector();
18-
this.measureStatementCoverage = (config.measureStatementCoverage === false) ? false : true;
19-
this.measureFunctionCoverage = (config.measureFunctionCoverage === false) ? false: true;
20-
this.measureModifierCoverage = (config.measureModifierCoverage === false) ? false: true;
18+
this.enabled = {
19+
statements: (config.measureStatementCoverage === false) ? false : true,
20+
functions: (config.measureFunctionCoverage === false) ? false: true,
21+
modifiers: (config.measureModifierCoverage === false) ? false: true,
22+
branches: (config.measureBranchCoverage === false) ? false: true,
23+
lines: (config.measureLineCoverage === false) ? false: true
24+
};
2125
}
2226

2327
_isRootNode(node){
@@ -58,9 +62,7 @@ class Instrumenter {
5862
const contract = {};
5963

6064
this.injector.resetModifierMapping();
61-
parse.configureStatementCoverage(this.measureStatementCoverage)
62-
parse.configureFunctionCoverage(this.measureFunctionCoverage)
63-
parse.configureModifierCoverage(this.measureModifierCoverage)
65+
parse.configure(this.enabled);
6466

6567
contract.source = contractSource;
6668
contract.instrumented = contractSource;

lib/parse.js

+2-10
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,8 @@ const register = new Registrar();
99
const parse = {};
1010

1111
// Utilities
12-
parse.configureStatementCoverage = function(val){
13-
register.measureStatementCoverage = val;
14-
}
15-
16-
parse.configureFunctionCoverage = function(val){
17-
register.measureFunctionCoverage = val;
18-
}
19-
20-
parse.configureModifierCoverage = function(val){
21-
register.measureModifierCoverage = val;
12+
parse.configure = function(_enabled){
13+
register.enabled = Object.assign(register.enabled, _enabled);
2214
}
2315

2416
// Nodes

lib/registrar.js

+20-6
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ class Registrar {
1212
this.trackStatements = true;
1313

1414
// These are set by user option and enable/disable the measurement completely
15-
this.measureStatementCoverage = true;
16-
this.measureFunctionCoverage = true;
17-
this.measureModifierCoverage = true;
15+
this.enabled = {
16+
statements: true,
17+
functions: true,
18+
modifiers: true,
19+
branches: true,
20+
lines: true
21+
}
1822
}
1923

2024
/**
@@ -37,7 +41,7 @@ class Registrar {
3741
* @param {Object} expression AST node
3842
*/
3943
statement(contract, expression) {
40-
if (!this.trackStatements || !this.measureStatementCoverage) return;
44+
if (!this.trackStatements || !this.enabled.statements) return;
4145

4246
const startContract = contract.instrumented.slice(0, expression.range[0]);
4347
const startline = ( startContract.match(/\n/g) || [] ).length + 1;
@@ -77,6 +81,8 @@ class Registrar {
7781
* @param {Object} expression AST node
7882
*/
7983
line(contract, expression) {
84+
if (!this.enabled.lines) return;
85+
8086
const startchar = expression.range[0];
8187
const endchar = expression.range[1] + 1;
8288
const lastNewLine = contract.instrumented.slice(0, startchar).lastIndexOf('\n');
@@ -108,7 +114,7 @@ class Registrar {
108114
* @param {Object} expression AST node
109115
*/
110116
functionDeclaration(contract, expression) {
111-
if (!this.measureFunctionCoverage) return;
117+
if (!this.enabled.functions) return;
112118

113119
let start = 0;
114120
contract.fnId += 1;
@@ -123,7 +129,7 @@ class Registrar {
123129
}
124130

125131
// Add modifier branch coverage
126-
if (!this.measureModifierCoverage) continue;
132+
if (!this.enabled.modifiers) continue;
127133

128134
this.addNewModifierBranch(contract, modifier);
129135
this._createInjectionPoint(
@@ -342,6 +348,8 @@ class Registrar {
342348
};
343349

344350
conditional(contract, expression){
351+
if (!this.enabled.branches) return;
352+
345353
this.addNewConditionalBranch(contract, expression);
346354

347355
// Double open parens
@@ -388,6 +396,8 @@ class Registrar {
388396
* @param {Number} injectionIdx pre/post branch index (left=0, right=1)
389397
*/
390398
logicalOR(contract, expression) {
399+
if (!this.enabled.branches) return;
400+
391401
this.addNewLogicalORBranch(contract, expression);
392402

393403
// Left
@@ -433,6 +443,8 @@ class Registrar {
433443
* @param {Object} expression AST node
434444
*/
435445
requireBranch(contract, expression) {
446+
if (!this.enabled.branches) return;
447+
436448
this.addNewBranch(contract, expression);
437449
this._createInjectionPoint(
438450
contract,
@@ -458,6 +470,8 @@ class Registrar {
458470
* @param {Object} expression AST node
459471
*/
460472
ifStatement(contract, expression) {
473+
if (!this.enabled.branches) return;
474+
461475
this.addNewBranch(contract, expression);
462476

463477
if (expression.trueBody.type === 'Block') {

lib/validator.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ const configSchema = {
2121
autoLaunchServer: {type: "boolean"},
2222
istanbulFolder: {type: "string"},
2323
measureStatementCoverage: {type: "boolean"},
24-
measureFunctionCoverage: {type: "boolean"},
25-
measureModifierCoverage: {type: "boolean"},
24+
measureFunctionCoverage: {type: "boolean"},
25+
measureModifierCoverage: {type: "boolean"},
26+
measureLineCoverage: {type: "boolean"},
27+
measureBranchCoverage: {type: "boolean"},
2628

2729
// Hooks:
2830
onServerReady: {type: "function", format: "isFunction"},

test/units/options.js

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
const assert = require('assert');
2+
const util = require('./../util/util.js');
3+
4+
const client = require('ganache-cli');
5+
const Coverage = require('./../../lib/coverage');
6+
const Api = require('./../../lib/api')
7+
8+
describe('measureCoverage options', () => {
9+
let coverage;
10+
let api;
11+
12+
before(async () => {
13+
api = new Api({silent: true});
14+
await api.ganache(client);
15+
})
16+
beforeEach(() => {
17+
api.config = {}
18+
coverage = new Coverage()
19+
});
20+
after(async() => await api.finish());
21+
22+
async function setupAndRun(solidityFile, val){
23+
const contract = await util.bootstrapCoverage(solidityFile, api);
24+
coverage.addContract(contract.instrumented, util.filePath);
25+
26+
/* some methods intentionally fail */
27+
try {
28+
(val)
29+
? await contract.instance.a(val)
30+
: await contract.instance.a();
31+
} catch(e){}
32+
33+
return coverage.generate(contract.data, util.pathPrefix);
34+
}
35+
36+
// if (x == 1 || x == 2) { } else ...
37+
it('should ignore OR branches when measureBranchCoverage = false', async function() {
38+
api.config.measureBranchCoverage = false;
39+
const mapping = await setupAndRun('or/if-or', 1);
40+
41+
assert.deepEqual(mapping[util.filePath].l, {
42+
5: 1, 8: 0
43+
});
44+
assert.deepEqual(mapping[util.filePath].b, {});
45+
assert.deepEqual(mapping[util.filePath].s, {
46+
1: 1, 2: 0,
47+
});
48+
assert.deepEqual(mapping[util.filePath].f, {
49+
1: 1,
50+
});
51+
});
52+
53+
it('should ignore if/else branches when measureBranchCoverage = false', async function() {
54+
api.config.measureBranchCoverage = false;
55+
const mapping = await setupAndRun('if/if-with-brackets', 1);
56+
57+
assert.deepEqual(mapping[util.filePath].l, {
58+
5: 1,
59+
});
60+
assert.deepEqual(mapping[util.filePath].b, {});
61+
assert.deepEqual(mapping[util.filePath].s, {
62+
1: 1, 2: 1,
63+
});
64+
assert.deepEqual(mapping[util.filePath].f, {
65+
1: 1,
66+
});
67+
});
68+
69+
it('should ignore ternary conditionals when measureBranchCoverage = false', async function() {
70+
api.config.measureBranchCoverage = false;
71+
const mapping = await setupAndRun('conditional/sameline-consequent');
72+
73+
assert.deepEqual(mapping[util.filePath].l, {
74+
5: 1, 6: 1, 7: 1,
75+
});
76+
assert.deepEqual(mapping[util.filePath].b, {});
77+
78+
assert.deepEqual(mapping[util.filePath].s, {
79+
1: 1, 2: 1, 3: 1,
80+
});
81+
assert.deepEqual(mapping[util.filePath].f, {
82+
1: 1,
83+
});
84+
});
85+
86+
it('should ignore modifier branches when measureModifierCoverage = false', async function() {
87+
api.config.measureModifierCoverage = false;
88+
const mapping = await setupAndRun('modifiers/same-contract-pass');
89+
90+
assert.deepEqual(mapping[util.filePath].l, {
91+
5: 1, 6: 1, 10: 1,
92+
});
93+
assert.deepEqual(mapping[util.filePath].b, { // Source contains a `require`
94+
1: [1, 0]
95+
});
96+
assert.deepEqual(mapping[util.filePath].s, {
97+
1: 1, 2: 1,
98+
});
99+
assert.deepEqual(mapping[util.filePath].f, {
100+
1: 1, 2: 1
101+
});
102+
});
103+
104+
it('should ignore statements when measureStatementCoverage = false', async function() {
105+
api.config.measureStatementCoverage = false;
106+
const mapping = await setupAndRun('modifiers/same-contract-pass');
107+
assert.deepEqual(mapping[util.filePath].s, {});
108+
});
109+
110+
it('should ignore lines when measureLineCoverage = false', async function() {
111+
api.config.measureLineCoverage = false;
112+
const mapping = await setupAndRun('modifiers/same-contract-pass');
113+
assert.deepEqual(mapping[util.filePath].l, {});
114+
});
115+
116+
it('should ignore functions when measureFunctionCoverage = false', async function() {
117+
api.config.measureFunctionCoverage = false;
118+
const mapping = await setupAndRun('modifiers/same-contract-pass');
119+
assert.deepEqual(mapping[util.filePath].f, {});
120+
});
121+
});

test/units/validator.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ describe('config validation', () => {
4848
"autoLaunchServer",
4949
"measureStatementCoverage",
5050
"measureFunctionCoverage",
51-
"measureModifierCoverage"
51+
"measureModifierCoverage",
52+
"measureBranchCoverage",
53+
"measureLineCoverage"
5254
]
5355

5456
options.forEach(name => {

test/util/util.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ function codeToCompilerInput(code) {
7171
// ============================
7272
// Instrumentation Correctness
7373
// ============================
74-
function instrumentAndCompile(sourceName) {
74+
function instrumentAndCompile(sourceName, api={}) {
7575
const contract = getCode(`${sourceName}.sol`)
76-
const instrumenter = new Instrumenter();
76+
const instrumenter = new Instrumenter(api.config);
7777
const instrumented = instrumenter.instrument(contract, filePath);
7878

7979
return {
@@ -97,7 +97,7 @@ function report(output=[]) {
9797
// Coverage Correctness
9898
// =====================
9999
async function bootstrapCoverage(file, api){
100-
const info = instrumentAndCompile(file);
100+
const info = instrumentAndCompile(file, api);
101101
info.instance = await getDeployedContractInstance(info, api.server.provider);
102102
api.collector._setInstrumentationData(info.data);
103103
return info;

0 commit comments

Comments
 (0)