Add coverage for ternary conditionals (#587)

experimental-options
cgewecke 4 years ago
parent 292819dc64
commit 7403f3f0e1
  1. 3
      lib/coverage.js
  2. 52
      lib/injector.js
  3. 23
      lib/parse.js
  4. 92
      lib/registrar.js
  5. 185
      lib/ternary/conditional.js
  6. 121
      lib/ternary/ternary.js
  7. 2
      test/integration/projects/ternary-and-logical-or/.solcover.js
  8. 6
      test/integration/projects/ternary-and-logical-or/contracts/Contract_OR.sol
  9. 54
      test/integration/projects/ternary-and-logical-or/contracts/Contract_ternary.sol
  10. 0
      test/integration/projects/ternary-and-logical-or/hardhat.config.js
  11. 4
      test/integration/projects/ternary-and-logical-or/test/test_or.js
  12. 16
      test/integration/projects/ternary-and-logical-or/test/test_ternary.js
  13. 0
      test/integration/projects/ternary-and-logical-or/truffle-config.js
  14. 9
      test/sources/solidity/contracts/conditional/and-condition.sol
  15. 9
      test/sources/solidity/contracts/conditional/or-always-false-condition.sol
  16. 9
      test/sources/solidity/contracts/conditional/or-condition.sol
  17. 9
      test/sources/solidity/contracts/conditional/unbracketed-condition.sol
  18. 9
      test/sources/solidity/contracts/conditional/unbracketed-or-condition.sol
  19. 232
      test/units/conditional.js
  20. 15
      test/units/hardhat/standard.js
  21. 12
      test/units/truffle/standard.js

@ -79,7 +79,8 @@ class Coverage {
case 'function': this.data[contractPath].f[id] = hits; break; case 'function': this.data[contractPath].f[id] = hits; break;
case 'statement': this.data[contractPath].s[id] = hits; break; case 'statement': this.data[contractPath].s[id] = hits; break;
case 'branch': this.data[contractPath].b[id][data.locationIdx] = hits; break; case 'branch': this.data[contractPath].b[id][data.locationIdx] = hits; break;
case 'logicalOR': this.data[contractPath].b[id][data.locationIdx] = hits / 2; break; case 'and-true': this.data[contractPath].b[id][data.locationIdx] = hits / 2; break;
case 'or-false': this.data[contractPath].b[id][data.locationIdx] = hits / 2; break;
case 'requirePre': this.requireData[contractPath][id].preEvents = hits; break; case 'requirePre': this.requireData[contractPath][id].preEvents = hits; break;
case 'requirePost': this.requireData[contractPath][id].postEvents = hits; break; case 'requirePost': this.requireData[contractPath][id].postEvents = hits; break;
} }

@ -14,8 +14,10 @@ class Injector {
_getInjectable(id, hash, type){ _getInjectable(id, hash, type){
switch(type){ switch(type){
case 'logicalOR': case 'and-true':
return ` && ${this._getTrueMethodIdentifier(id)}(${hash}))`; return ` && ${this._getTrueMethodIdentifier(id)}(${hash}))`;
case 'or-false':
return ` || ${this._getFalseMethodIdentifier(id)}(${hash}))`;
default: default:
return `${this._getDefaultMethodIdentifier(id)}(${hash}); /* ${type} */ \n`; return `${this._getDefaultMethodIdentifier(id)}(${hash}); /* ${type} */ \n`;
} }
@ -31,11 +33,16 @@ class Injector {
return `c_${web3Utils.keccak256(id).slice(0,10)}` return `c_${web3Utils.keccak256(id).slice(0,10)}`
} }
// Method returns boolean true // Method returns boolean: true
_getTrueMethodIdentifier(id){ _getTrueMethodIdentifier(id){
return `c_true${web3Utils.keccak256(id).slice(0,10)}` return `c_true${web3Utils.keccak256(id).slice(0,10)}`
} }
// Method returns boolean: false
_getFalseMethodIdentifier(id){
return `c_false${web3Utils.keccak256(id).slice(0,10)}`
}
_getInjectionComponents(contract, injectionPoint, id, type){ _getInjectionComponents(contract, injectionPoint, id, type){
const { start, end } = this._split(contract, injectionPoint); const { start, end } = this._split(contract, injectionPoint);
const hash = this._getHash(id) const hash = this._getHash(id)
@ -82,7 +89,19 @@ class Injector {
_getTrueMethodDefinition(id){ _getTrueMethodDefinition(id){
const hash = web3Utils.keccak256(id).slice(0,10); const hash = web3Utils.keccak256(id).slice(0,10);
const method = this._getTrueMethodIdentifier(id); const method = this._getTrueMethodIdentifier(id);
return `\nfunction ${method}(bytes32 c__${hash}) public pure returns (bool){ return true; }\n`; return `function ${method}(bytes32 c__${hash}) public pure returns (bool){ return true; }\n`;
}
/**
* Generates a solidity statement injection defining a method
* *which returns boolean false* to pass instrumentation hash to.
* @param {String} fileName
* @return {String} ex: bytes32[1] memory _sc_82e0891
*/
_getFalseMethodDefinition(id){
const hash = web3Utils.keccak256(id).slice(0,10);
const method = this._getFalseMethodIdentifier(id);
return `function ${method}(bytes32 c__${hash}) public pure returns (bool){ return false; }\n`;
} }
injectLine(contract, fileName, injectionPoint, injection, instrumentation){ injectLine(contract, fileName, injectionPoint, injection, instrumentation){
@ -247,6 +266,7 @@ class Injector {
contract.instrumented = `${start}` + contract.instrumented = `${start}` +
`${defaultMethodDefinition}` + `${defaultMethodDefinition}` +
`${this._getTrueMethodDefinition(id)}` + `${this._getTrueMethodDefinition(id)}` +
`${this._getFalseMethodDefinition(id)}` +
`${end}`; `${end}`;
} }
@ -256,8 +276,30 @@ class Injector {
contract.instrumented = `${start}(${end}`; contract.instrumented = `${start}(${end}`;
} }
injectLogicalOR(contract, fileName, injectionPoint, injection, instrumentation){ injectAndTrue(contract, fileName, injectionPoint, injection, instrumentation){
const type = 'logicalOR'; const type = 'and-true';
const id = `${fileName}:${injection.contractName}`;
const {
start,
end,
hash,
injectable
} = this._getInjectionComponents(contract, injectionPoint, id, type);
instrumentation[hash] = {
id: injection.branchId,
locationIdx: injection.locationIdx,
type: type,
contractPath: fileName,
hits: 0
}
contract.instrumented = `${start}${injectable}${end}`;
}
injectOrFalse(contract, fileName, injectionPoint, injection, instrumentation){
const type = 'or-false';
const id = `${fileName}:${injection.contractName}`; const id = `${fileName}:${injection.contractName}`;
const { const {

@ -33,11 +33,21 @@ parse.Block = function(contract, expression) {
}; };
parse.BinaryOperation = function(contract, expression, skipStatementRegistry) { parse.BinaryOperation = function(contract, expression, skipStatementRegistry) {
// Free-floating ternary conditional
if (expression.left && expression.left.type === 'Conditional'){
parse[expression.left.type](contract, expression.left, true);
register.statement(contract, expression);
// Ternary conditional assignment
} else if (expression.right && expression.right.type === 'Conditional'){
parse[expression.right.type](contract, expression.right, true);
register.statement(contract, expression);
// Regular binary operation // Regular binary operation
if (!skipStatementRegistry){ } else if(!skipStatementRegistry){
register.statement(contract, expression); register.statement(contract, expression);
// LogicalOR conditional search... // LogicalOR condition search...
} else { } else {
parse[expression.left.type] && parse[expression.left.type] &&
parse[expression.left.type](contract, expression.left, true); parse[expression.left.type](contract, expression.left, true);
@ -83,12 +93,10 @@ parse.FunctionCall = function(contract, expression, skipStatementRegistry) {
}; };
parse.Conditional = function(contract, expression, skipStatementRegistry) { parse.Conditional = function(contract, expression, skipStatementRegistry) {
if (!skipStatementRegistry){
register.statement(contract, expression);
}
parse[expression.condition.type] && parse[expression.condition.type] &&
parse[expression.condition.type](contract, expression.condition, skipStatementRegistry); parse[expression.condition.type](contract, expression.condition, skipStatementRegistry);
register.conditional(contract, expression);
}; };
parse.ContractDefinition = function(contract, expression) { parse.ContractDefinition = function(contract, expression) {
@ -291,6 +299,9 @@ parse.UsingStatement = function (contract, expression) {
}; };
parse.VariableDeclarationStatement = function (contract, expression) { parse.VariableDeclarationStatement = function (contract, expression) {
if (expression.initialValue && expression.initialValue.type === 'Conditional'){
parse[expression.initialValue.type](contract, expression.initialValue, true)
}
register.statement(contract, expression); register.statement(contract, expression);
}; };

@ -186,6 +186,54 @@ class Registrar {
}; };
}; };
addNewConditionalBranch(contract, expression){
let start;
// Instabul HTML highlighting location data...
const trueZero = expression.trueExpression.range[0];
const trueOne = expression.trueExpression.range[1];
const falseZero = expression.falseExpression.range[0];
const falseOne = expression.falseExpression.range[1];
start = contract.instrumented.slice(0, trueZero);
const trueStartLine = ( start.match(/\n/g) || [] ).length + 1;
const trueStartCol = trueZero - start.lastIndexOf('\n') - 1;
start = contract.instrumented.slice(0, trueOne);
const trueEndLine = ( start.match(/\n/g) || [] ).length + 1;
const trueEndCol = trueOne - start.lastIndexOf('\n') - 1;
start = contract.instrumented.slice(0, falseZero);
const falseStartLine = ( start.match(/\n/g) || [] ).length + 1;
const falseStartCol = falseZero - start.lastIndexOf('\n') - 1;
start = contract.instrumented.slice(0, falseOne);
const falseEndLine = ( start.match(/\n/g) || [] ).length + 1;
const falseEndCol = falseOne - start.lastIndexOf('\n') - 1;
contract.branchId += 1;
contract.branchMap[contract.branchId] = {
line: trueStartLine,
type: 'if',
locations: [{
start: {
line: trueStartLine, column: trueStartCol,
},
end: {
line: trueEndLine, column: trueEndCol,
},
}, {
start: {
line: falseStartLine, column: falseStartCol,
},
end: {
line: falseEndLine, column: falseEndCol,
},
}],
};
}
/** /**
* Registers injections for branch measurements. Used by logicalOR registration method. * Registers injections for branch measurements. Used by logicalOR registration method.
* @param {Object} contract instrumentation target * @param {Object} contract instrumentation target
@ -241,6 +289,46 @@ class Registrar {
}; };
}; };
conditional(contract, expression){
this.addNewConditionalBranch(contract, expression);
// Double open parens
this._createInjectionPoint(
contract,
expression.condition.range[0],
{
type: 'injectOpenParen',
}
);
this._createInjectionPoint(
contract,
expression.condition.range[0],
{
type: 'injectOpenParen',
}
);
// False condition: (these get sorted in reverse order, so create in reversed order)
this._createInjectionPoint(
contract,
expression.condition.range[1] + 1,
{
type: 'injectOrFalse',
branchId: contract.branchId,
locationIdx: 1
}
);
// True condition
this._createInjectionPoint(
contract,
expression.condition.range[1] + 1,
{
type: 'injectAndTrue',
branchId: contract.branchId,
locationIdx: 0
}
);
}
/** /**
* Registers injections for logicalOR clause measurements (branches) * Registers injections for logicalOR clause measurements (branches)
* @param {Object} contract instrumentation target * @param {Object} contract instrumentation target
@ -262,7 +350,7 @@ class Registrar {
contract, contract,
expression.left.range[1] + 1, expression.left.range[1] + 1,
{ {
type: 'injectLogicalOR', type: 'injectAndTrue',
branchId: contract.branchId, branchId: contract.branchId,
locationIdx: 0 locationIdx: 0
} }
@ -280,7 +368,7 @@ class Registrar {
contract, contract,
expression.right.range[1] + 1, expression.right.range[1] + 1,
{ {
type: 'injectLogicalOR', type: 'injectAndTrue',
branchId: contract.branchId, branchId: contract.branchId,
locationIdx: 1 locationIdx: 1
} }

@ -1,185 +0,0 @@
/* eslint-env node, mocha */
/*const path = require('path');
const getInstrumentedVersion = require('./../lib/instrumentSolidity.js');
const util = require('./util/util.js');
const CoverageMap = require('./../lib/coverageMap');
const vm = require('./util/vm');
const assert = require('assert');
describe.skip('conditional statements', () => {
const filePath = path.resolve('./test.sol');
const pathPrefix = './';
it('should cover a conditional that reaches the consequent (same-line)', done => {
const contract = util.getCode('conditional/sameline-consequent.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [1, 0],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover a conditional that reaches the alternate (same-line)', done => {
const contract = util.getCode('conditional/sameline-alternate.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover a conditional that reaches the consequent (multi-line)', done => {
const contract = util.getCode('conditional/multiline-consequent.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [1, 0],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover a conditional that reaches the alternate (multi-line)', done => {
const contract = util.getCode('conditional/multiline-alternate.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover a DeclarativeExpression assignment by conditional that reaches the alternate', done => {
const contract = util.getCode('conditional/declarative-exp-assignment-alternate.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
// Runs bool z = (x) ? false : true;
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover an Identifier assignment by conditional that reaches the alternate', done => {
const contract = util.getCode('conditional/identifier-assignment-alternate.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
// Runs z = (x) ? false : true;
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1, 8: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1, 4: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover an assignment to a member expression (reaches the alternate)', done => {
const contract = util.getCode('conditional/mapping-assignment.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
11: 1, 12: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
});
*/

@ -1,121 +0,0 @@
/**
* This is logic to instrument ternary conditional assignment statements. Preserving
* here for the time being, because instrumentation of these became impossible in
* solc >= 0.5.0
*/
function instrumentAssignmentExpression(contract, expression) {
// This is suspended for 0.5.0 which tries to accomodate the new `emit` keyword.
// Solc is not allowing us to use the construction `emit SomeEvent()` within the parens :/
return;
// --------------------------------------------------------------------------------------------
// The only time we instrument an assignment expression is if there's a conditional expression on
// the right
/*if (expression.right.type === 'ConditionalExpression') {
if (expression.left.type === 'DeclarativeExpression' || expression.left.type === 'Identifier') {
// Then we need to go from bytes32 varname = (conditional expression)
// to bytes32 varname; (,varname) = (conditional expression)
createOrAppendInjectionPoint(contract, expression.left.range[1], {
type: 'literal', string: '; (,' + expression.left.name + ')',
});
instrumenter.instrumentConditionalExpression(contract, expression.right);
} else if (expression.left.type === 'MemberExpression') {
createOrAppendInjectionPoint(contract, expression.left.range[0], {
type: 'literal', string: '(,',
});
createOrAppendInjectionPoint(contract, expression.left.range[1], {
type: 'literal', string: ')',
});
instrumenter.instrumentConditionalExpression(contract, expression.right);
} else {
const err = 'Error instrumenting assignment expression @ solidity-coverage/lib/instrumenter.js';
console.log(err, contract, expression.left);
process.exit();
}
}*/
};
function instrumentConditionalExpression(contract, expression) {
// ----------------------------------------------------------------------------------------------
// This is suspended for 0.5.0 which tries to accomodate the new `emit` keyword.
// Solc is not allowing us to use the construction `emit SomeEvent()` within the parens :/
// Very sad, this is the coolest thing in here.
return;
// ----------------------------------------------------------------------------------------------
/*contract.branchId += 1;
const startline = (contract.instrumented.slice(0, expression.range[0]).match(/\n/g) || []).length + 1;
const startcol = expression.range[0] - contract.instrumented.slice(0, expression.range[0]).lastIndexOf('\n') - 1;
const consequentStartCol = startcol + (contract, expression.trueBody.range[0] - expression.range[0]);
const consequentEndCol = consequentStartCol + (contract, expression.trueBody.range[1] - expression.trueBody.range[0]);
const alternateStartCol = startcol + (contract, expression.falseBody.range[0] - expression.range[0]);
const alternateEndCol = alternateStartCol + (contract, expression.falseBody.range[1] - expression.falseBody.range[0]);
// NB locations for conditional branches in istanbul are length 1 and associated with the : and ?.
contract.branchMap[contract.branchId] = {
line: startline,
type: 'cond-expr',
locations: [{
start: {
line: startline, column: consequentStartCol,
},
end: {
line: startline, column: consequentEndCol,
},
}, {
start: {
line: startline, column: alternateStartCol,
},
end: {
line: startline, column: alternateEndCol,
},
}],
};
// Right, this could be being used just by itself or as an assignment. In the case of the latter, because
// the comma operator doesn't exist, we're going to have to get funky.
// if we're on a line by ourselves, this is easier
//
// Now if we've got to wrap the expression it's being set equal to, do that...
// Wrap the consequent
createOrAppendInjectionPoint(contract, expression.trueBody.range[0], {
type: 'openParen',
});
createOrAppendInjectionPoint(contract, expression.trueBody.range[0], {
type: 'callBranchEvent', comma: true, branchId: contract.branchId, locationIdx: 0,
});
createOrAppendInjectionPoint(contract, expression.trueBody.range[1], {
type: 'closeParen',
});
// Wrap the alternate
createOrAppendInjectionPoint(contract, expression.falseBody.range[0], {
type: 'openParen',
});
createOrAppendInjectionPoint(contract, expression.falseBody.range[0], {
type: 'callBranchEvent', comma: true, branchId: contract.branchId, locationIdx: 1,
});
createOrAppendInjectionPoint(contract, expression.falseBody.range[1], {
type: 'closeParen',
});*/
};
// Paren / Literal injectors
/*
injector.openParen = function injectOpenParen(contract, fileName, injectionPoint, injection) {
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + '(' + contract.instrumented.slice(injectionPoint);
};
injector.closeParen = function injectCloseParen(contract, fileName, injectionPoint, injection) {
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + ')' + contract.instrumented.slice(injectionPoint);
};
injector.literal = function injectLiteral(contract, fileName, injectionPoint, injection) {
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + injection.string + contract.instrumented.slice(injectionPoint);
};
*/

@ -4,5 +4,5 @@ const fn = (msg, config) => config.logger.log(msg);
module.exports = { module.exports = {
skipFiles: ['Migrations.sol'], skipFiles: ['Migrations.sol'],
silent: process.env.SILENT ? true : false, silent: process.env.SILENT ? true : false,
istanbulReporter: ['json-summary', 'text'], istanbulReporter: ['json-summary', 'text', 'html'],
} }

@ -37,4 +37,10 @@ contract Contract_OR {
x == 3 x == 3
); );
} }
function _if_neither(uint i) public {
if (i == 1 || i == 2){
/* ignore */
}
}
} }

@ -0,0 +1,54 @@
pragma solidity ^0.7.0;
contract Contract_ternary {
// Sameline consequent
function a() public {
bool x = true;
bool y = true;
x && y ? y = false : y = false;
}
// Multiline consequent
function b() public {
bool x = false;
bool y = false;
(x)
? y = false
: y = false;
}
// Sameline w/ logicalOR
function c() public {
bool x = false;
bool y = true;
(x || y) ? y = false : y = false;
}
// Multiline w/ logicalOR
function d() public {
bool x = false;
bool y = true;
(x || y)
? y = false
: y = false;
}
// Sameline alternate
function e() public {
bool x = false;
bool y = false;
(x) ? y = false : y = false;
}
// Multiline w/ logicalOR (both false)
function f() public {
bool x = false;
bool y = false;
(x || y)
? y = false
: y = false;
}
}

@ -30,4 +30,8 @@ contract("contract_or", function(accounts) {
await instance._require_multi_line(1); await instance._require_multi_line(1);
await instance._require_multi_line(3); await instance._require_multi_line(3);
}) })
it('_if_neither', async function(){
await instance._if_neither(3);
})
}); });

@ -0,0 +1,16 @@
const Contract_ternary = artifacts.require("Contract_ternary");
contract("contract_ternary", function(accounts) {
let instance;
before(async () => instance = await Contract_ternary.new())
it('misc ternary conditionals', async function(){
await instance.a();
await instance.b();
await instance.c();
await instance.d();
await instance.e();
await instance.f();
});
});

@ -0,0 +1,9 @@
pragma solidity ^0.7.0;
contract Test {
function a() public {
bool x = true;
bool y = true;
x && y ? y = false : y = false;
}
}

@ -0,0 +1,9 @@
pragma solidity ^0.7.0;
contract Test {
function a() public {
bool x = false;
bool y = false;
(x || y) ? y = false : y = false;
}
}

@ -0,0 +1,9 @@
pragma solidity ^0.7.0;
contract Test {
function a() public {
bool x = false;
bool y = true;
(x || y) ? y = false : y = false;
}
}

@ -0,0 +1,9 @@
pragma solidity ^0.7.0;
contract Test {
function a() public {
bool x = true;
bool y = false;
x ? y = false : y = false;
}
}

@ -0,0 +1,9 @@
pragma solidity ^0.7.0;
contract Test {
function a() public {
bool x = false;
bool y = true;
x || y ? y = false : y = false;
}
}

@ -0,0 +1,232 @@
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('ternary conditionals', () => {
let coverage;
let api;
before(async () => {
api = new Api({silent: true});
await api.ganache(client);
})
beforeEach(() => coverage = new Coverage());
after(async() => await api.finish());
async function setupAndRun(solidityFile){
const contract = await util.bootstrapCoverage(solidityFile, api);
coverage.addContract(contract.instrumented, util.filePath);
await contract.instance.a();
return coverage.generate(contract.data, util.pathPrefix);
}
it('should cover a conditional that reaches the consequent (same-line)', async function() {
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, {
1: [1, 0],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should cover an unbracketed conditional that reaches the consequent (same-line)', async function() {
const mapping = await setupAndRun('conditional/unbracketed-condition');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [1, 0],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should cover a multi-part conditional (&&) that reaches the consequent', async function() {
const mapping = await setupAndRun('conditional/and-condition');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [1, 0],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should cover a multi-part conditional (||) that reaches the consequent', async function() {
const mapping = await setupAndRun('conditional/or-condition');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [0, 1], 2: [1, 0],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should cover a multi-part unbracketed conditional (||) that reaches the consequent', async function() {
const mapping = await setupAndRun('conditional/unbracketed-or-condition');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [0, 1], 2: [1, 0],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should cover an always-false multi-part unbracketed conditional (||)', async function() {
const mapping = await setupAndRun('conditional/or-always-false-condition');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [0, 0], 2: [0, 1],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should cover a conditional that reaches the alternate (same-line)', async function() {
const mapping = await setupAndRun('conditional/sameline-alternate');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should cover a conditional that reaches the consequent (multi-line)', async function() {
const mapping = await setupAndRun('conditional/multiline-consequent');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [1, 0],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should cover a conditional that reaches the alternate (multi-line)', async function() {
const mapping = await setupAndRun('conditional/multiline-alternate');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
// Runs bool z = (x) ? false : true;
it('should cover a definition assignment by conditional that reaches the alternate', async function() {
const mapping = await setupAndRun('conditional/declarative-exp-assignment-alternate');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
// Runs z = (x) ? false : true;
it('should cover an identifier assignment by conditional that reaches the alternate', async function() {
const mapping = await setupAndRun('conditional/identifier-assignment-alternate');
assert.deepEqual(mapping[util.filePath].l, {
5: 1, 6: 1, 7: 1, 8: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1, 3: 1, 4: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
it('should cover an assignment to a member expression (reaches the alternate)', async function() {
const mapping = await setupAndRun('conditional/mapping-assignment');
assert.deepEqual(mapping[util.filePath].l, {
11: 1, 12: 1,
});
assert.deepEqual(mapping[util.filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[util.filePath].s, {
1: 1, 2: 1,
});
assert.deepEqual(mapping[util.filePath].f, {
1: 1,
});
});
});

@ -489,11 +489,8 @@ describe('Hardhat Plugin: standard use cases', function() {
verify.lineCoverage(expected); verify.lineCoverage(expected);
}) })
// This works on it's own but there's some kind of weird interaction with it('logicalOR & ternary conditionals', async function(){
// the solc 6 test which causes subsequent cov measurements to be zero. mock.installFullProject('ternary-and-logical-or');
// Have tried re-ordering etc...???. Truffle tests this & should be the same anyway...
it('logicalOR', async function(){
mock.installFullProject('logical-or');
mock.hardhatSetupEnv(this); mock.hardhatSetupEnv(this);
await this.env.run("coverage"); await this.env.run("coverage");
@ -501,8 +498,12 @@ describe('Hardhat Plugin: standard use cases', function() {
const expected = [ const expected = [
{ {
file: mock.pathToContract(hardhatConfig, 'Contract_OR.sol'), file: mock.pathToContract(hardhatConfig, 'Contract_OR.sol'),
pct: 59.09 pct: 53.85
} },
{
file: mock.pathToContract(hardhatConfig, 'Contract_ternary.sol'),
pct: 44.44
},
]; ];
verify.branchCoverage(expected); verify.branchCoverage(expected);

@ -499,15 +499,19 @@ describe('Truffle Plugin: standard use cases', function() {
verify.lineCoverage(expected); verify.lineCoverage(expected);
}); });
it('logicalOR', async function(){ it('logicalOR & ternary conditionals', async function(){
mock.installFullProject('logical-or'); mock.installFullProject('ternary-and-logical-or');
await plugin(truffleConfig); await plugin(truffleConfig);
const expected = [ const expected = [
{ {
file: mock.pathToContract(truffleConfig, 'Contract_OR.sol'), file: mock.pathToContract(truffleConfig, 'Contract_OR.sol'),
pct: 59.09 pct: 53.85
} },
{
file: mock.pathToContract(truffleConfig, 'Contract_ternary.sol'),
pct: 44.44
},
]; ];
verify.branchCoverage(expected); verify.branchCoverage(expected);

Loading…
Cancel
Save