Add modifier-invocation-as-branch coverage (#588)
parent
7403f3f0e1
commit
bf77884218
@ -0,0 +1,8 @@ |
||||
// Testing hooks
|
||||
const fn = (msg, config) => config.logger.log(msg); |
||||
|
||||
module.exports = { |
||||
skipFiles: ['Migrations.sol'], |
||||
silent: process.env.SILENT ? true : false, |
||||
istanbulReporter: ['json-summary', 'text'], |
||||
} |
@ -0,0 +1,45 @@ |
||||
pragma solidity ^0.6.0; |
||||
|
||||
import "./ModifiersB.sol"; |
||||
|
||||
/** |
||||
* New syntaxes in solc 0.6.x |
||||
*/ |
||||
contract ModifiersA is ModifiersB { |
||||
uint counter; |
||||
bool flag = true; |
||||
|
||||
modifier flippable { |
||||
require(flag); |
||||
_; |
||||
} |
||||
|
||||
modifier overridden() override { |
||||
require(true); |
||||
_; |
||||
} |
||||
|
||||
function flip() public { |
||||
flag = !flag; |
||||
} |
||||
|
||||
function simpleSet(uint i) |
||||
public |
||||
override(ModifiersB) |
||||
{ |
||||
counter = counter + i; |
||||
} |
||||
|
||||
function simpleView(uint i) |
||||
view |
||||
overridden |
||||
external |
||||
returns (uint, bool) |
||||
{ |
||||
return (counter + i, true); |
||||
} |
||||
|
||||
function simpleSetFlip(uint i) flippable public { |
||||
counter = counter + i; |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
pragma solidity ^0.6.0; |
||||
|
||||
|
||||
contract ModifiersB { |
||||
uint value; |
||||
uint b; |
||||
|
||||
constructor() public { |
||||
} |
||||
|
||||
modifier overridden() virtual { |
||||
require(true); |
||||
_; |
||||
} |
||||
|
||||
function simpleSet(uint i) public virtual { |
||||
value = 5; |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
pragma solidity ^0.6.0; |
||||
|
||||
import "./ModifiersB.sol"; |
||||
|
||||
/** |
||||
* New syntaxes in solc 0.6.x |
||||
*/ |
||||
contract ModifiersC { |
||||
uint counter; |
||||
address owner; |
||||
bool flag = true; |
||||
|
||||
constructor() public { |
||||
owner = msg.sender; |
||||
} |
||||
|
||||
modifier flippable { |
||||
require(flag); |
||||
_; |
||||
} |
||||
|
||||
function flip() public { |
||||
flag = !flag; |
||||
} |
||||
|
||||
function simpleSetFlip(uint i) flippable public { |
||||
counter = counter + i; |
||||
} |
||||
|
||||
modifier onlyOwner { |
||||
require(msg.sender == owner); |
||||
_; |
||||
} |
||||
|
||||
function set(uint i) |
||||
onlyOwner |
||||
public |
||||
payable |
||||
virtual |
||||
{ |
||||
counter = counter + i; |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
require("@nomiclabs/hardhat-truffle5"); |
||||
require(__dirname + "/../plugins/nomiclabs.plugin"); |
||||
|
||||
module.exports = { |
||||
solidity: { |
||||
version: "0.6.7" |
||||
}, |
||||
logger: process.env.SILENT ? { log: () => {} } : console, |
||||
}; |
@ -0,0 +1,40 @@ |
||||
const ModifiersA = artifacts.require("ModifiersA"); |
||||
const ModifiersC = artifacts.require("ModifiersC"); |
||||
|
||||
contract("Modifiers", function(accounts) { |
||||
let instance; |
||||
|
||||
before(async () => { |
||||
A = await ModifiersA.new(); |
||||
C = await ModifiersC.new(); |
||||
}) |
||||
|
||||
it('simpleSet (overridden method)', async function(){ |
||||
await A.simpleSet(5); |
||||
}); |
||||
|
||||
it('simpleView (overridden modifier)', async function(){ |
||||
await A.simpleView(5); |
||||
}); |
||||
|
||||
it('simpleSetFlip (both branches)', async function(){ |
||||
await A.simpleSetFlip(5); |
||||
await A.flip(); |
||||
|
||||
try { |
||||
await A.simpleSetFlip(5); |
||||
} catch (e) { |
||||
/* ignore */ |
||||
} |
||||
}); |
||||
|
||||
it('simpleSetFlip (false branch + other file)', async function(){ |
||||
await C.flip(); |
||||
|
||||
try { |
||||
await C.simpleSetFlip(5); |
||||
} catch (e) { |
||||
/* ignore */ |
||||
} |
||||
}); |
||||
}); |
@ -0,0 +1,9 @@ |
||||
module.exports = { |
||||
networks: {}, |
||||
mocha: {}, |
||||
compilers: { |
||||
solc: { |
||||
version: "0.6.2" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
const Modified = artifacts.require('Modified'); |
||||
|
||||
contract('Modified', () => { |
||||
it('should set counter', async function(){ |
||||
const m = await Modified.new() |
||||
await m.set(5); |
||||
}); |
||||
}); |
@ -0,0 +1,20 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Modified { |
||||
uint counter; |
||||
|
||||
modifier m { |
||||
_; |
||||
} |
||||
|
||||
// When modifier coverage is on, branch cov should be 50% |
||||
// When off: 100% |
||||
function set(uint i) |
||||
m |
||||
public |
||||
payable |
||||
virtual |
||||
{ |
||||
counter = counter + i; |
||||
} |
||||
} |
@ -0,0 +1,18 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Test { |
||||
bool flag = true; |
||||
|
||||
modifier m { |
||||
require(flag); |
||||
_; |
||||
} |
||||
|
||||
function flip() public { |
||||
flag = !flag; |
||||
} |
||||
|
||||
function a() m public { |
||||
uint x = 5; |
||||
} |
||||
} |
@ -0,0 +1,21 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Test { |
||||
modifier mmm { |
||||
require(true); |
||||
_; |
||||
} |
||||
|
||||
modifier nnn { |
||||
require(true); |
||||
_; |
||||
} |
||||
|
||||
function a() |
||||
mmm |
||||
nnn |
||||
public |
||||
{ |
||||
uint x = 5; |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Test { |
||||
modifier mmm { |
||||
require(true); |
||||
_; |
||||
} |
||||
|
||||
function a() mmm public { |
||||
uint x = 5; |
||||
} |
||||
|
||||
function b() mmm public { |
||||
uint x = 5; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Test { |
||||
modifier mmm { |
||||
require(true); |
||||
_; |
||||
} |
||||
|
||||
modifier nnn { |
||||
require(true); |
||||
_; |
||||
} |
||||
|
||||
function a() mmm nnn public { |
||||
uint x = 5; |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
abstract contract IM { |
||||
function a() payable virtual public; |
||||
} |
||||
|
||||
contract Test is IM { |
||||
modifier m { |
||||
require(true); |
||||
_; |
||||
} |
||||
|
||||
function a() payable m public override { |
||||
uint x = 5; |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Test { |
||||
modifier m { |
||||
require(false); |
||||
_; |
||||
} |
||||
|
||||
function a() m public { |
||||
uint x = 5; |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
pragma solidity ^0.7.0; |
||||
|
||||
contract Test { |
||||
modifier m { |
||||
require(true); |
||||
_; |
||||
} |
||||
|
||||
function a() m public { |
||||
uint x = 5; |
||||
} |
||||
} |
@ -0,0 +1,163 @@ |
||||
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('modifiers', () => { |
||||
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); |
||||
|
||||
/* some modifiers intentionally fail */ |
||||
try { |
||||
await contract.instance.a(); |
||||
} catch(e){} |
||||
|
||||
return coverage.generate(contract.data, util.pathPrefix); |
||||
} |
||||
|
||||
it('should cover a modifier branch which always succeeds', async function() { |
||||
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, { |
||||
1: [1, 0], 2: [1, 0] |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].s, { |
||||
1: 1, 2: 1, |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].f, { |
||||
1: 1, 2: 1 |
||||
}); |
||||
}); |
||||
|
||||
// NB: Failures are replayed by truffle-contract
|
||||
it('should cover a modifier branch which never succeeds', async function() { |
||||
const mapping = await setupAndRun('modifiers/same-contract-fail'); |
||||
|
||||
assert.deepEqual(mapping[util.filePath].l, { |
||||
5: 2, 6: 0, 10: 0, |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].b, { |
||||
1: [0, 2], 2: [0, 2] |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].s, { |
||||
1: 2, 2: 0, |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].f, { |
||||
1: 2, 2: 0 |
||||
}); |
||||
}); |
||||
|
||||
it('should cover a modifier on an overridden function', async function() { |
||||
const mapping = await setupAndRun('modifiers/override-function'); |
||||
|
||||
assert.deepEqual(mapping[util.filePath].l, { |
||||
9: 1, 10: 1, 14: 1, |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].b, { |
||||
1: [1, 0], 2: [1, 0] |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].s, { |
||||
1: 1, 2: 1 |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].f, { |
||||
1: 1, 2: 1 |
||||
}); |
||||
}); |
||||
|
||||
it('should cover multiple modifiers on the same function', async function() { |
||||
const mapping = await setupAndRun('modifiers/multiple-mods-same-fn'); |
||||
|
||||
assert.deepEqual(mapping[util.filePath].l, { |
||||
5: 1, 6: 1, 10: 1, 11: 1, 15: 1 |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].b, { |
||||
1: [1, 0], 2: [1, 0], 3: [1, 0], 4: [1, 0] |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].s, { |
||||
1: 1, 2: 1, 3: 1 |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].f, { |
||||
1: 1, 2: 1, 3: 1 |
||||
}); |
||||
}); |
||||
|
||||
it('should cover multiple functions which use the same modifier', async function() { |
||||
const contract = await util.bootstrapCoverage('modifiers/multiple-fns-same-mod', api); |
||||
coverage.addContract(contract.instrumented, util.filePath); |
||||
await contract.instance.a(); |
||||
await contract.instance.b(); |
||||
const mapping = coverage.generate(contract.data, util.pathPrefix); |
||||
|
||||
assert.deepEqual(mapping[util.filePath].l, { |
||||
5: 2, 6: 2, 10: 1, 14: 1 |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].b, { |
||||
1: [2, 0], 2: [1, 0], 3: [1, 0] |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].s, { |
||||
1: 2, 2: 1, 3: 1, |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].f, { |
||||
1: 2, 2: 1, 3: 1 |
||||
}); |
||||
}); |
||||
|
||||
it('should cover when both modifier branches are hit', async function() { |
||||
const contract = await util.bootstrapCoverage('modifiers/both-branches', api); |
||||
coverage.addContract(contract.instrumented, util.filePath); |
||||
await contract.instance.a(); |
||||
await contract.instance.flip(); |
||||
|
||||
try { |
||||
await contract.instance.a(); |
||||
} catch(e) { /*ignore*/ } |
||||
|
||||
const mapping = coverage.generate(contract.data, util.pathPrefix); |
||||
|
||||
assert.deepEqual(mapping[util.filePath].l, { |
||||
7: 3, 8: 1, 12: 1, 16: 1 |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].b, { |
||||
1: [1, 2], 2: [1, 2], |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].s, { |
||||
1: 3, 2: 1, 3: 1, |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].f, { |
||||
1: 3, 2: 1, 3: 1 |
||||
}); |
||||
}); |
||||
|
||||
it('should cover when modifiers are listed with newlines', async function() { |
||||
const mapping = await setupAndRun('modifiers/listed-modifiers'); |
||||
|
||||
assert.deepEqual(mapping[util.filePath].l, { |
||||
5: 1, 6: 1, 10: 1, 11: 1, 19: 1 |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].b, { |
||||
1: [1, 0], 2: [1, 0], 3: [1, 0], 4: [1, 0] |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].s, { |
||||
1: 1, 2: 1, 3: 1, |
||||
}); |
||||
assert.deepEqual(mapping[util.filePath].f, { |
||||
1: 1, 2: 1, 3: 1 |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue