diff --git a/lib/api.js b/lib/api.js index 7425bbf..dec6710 100644 --- a/lib/api.js +++ b/lib/api.js @@ -150,13 +150,7 @@ class API { catch(err) { this.ui.report('vm-fail', []); this.client = require('ganache-core-sc'); - - try { await this.attachToVM() } - - catch(err) { - err.message = `${this.ui.generate('server-fail', [address])} ${err.message}`; - throw err; - } + await this.attachToVM(); } this.ui.report('server', [address]); @@ -283,10 +277,6 @@ class API { return newCoverage; } - toRelativePath(absolutePath, parentDir){ - return absolutePath.split(`/${parentDir}`)[1] - } - // ======= // Logging // ======= diff --git a/lib/parse.js b/lib/parse.js index c2089d7..146e184 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -9,7 +9,6 @@ const register = new Registrar(); const parse = {}; parse.AssignmentExpression = function(contract, expression) { - register.prePosition(expression); register.statement(contract, expression); }; @@ -41,9 +40,10 @@ parse.FunctionCall = function(contract, expression) { } }; -parse.ConditionalExpression = function(contract, expression) { +parse.Conditional = function(contract, expression) { register.statement(contract, expression); - register.conditionalExpression(contract, expression); + // TODO: Investigate node structure + // There are potential substatements here we aren't measuring }; parse.ContractDefinition = function(contract, expression) { @@ -108,18 +108,11 @@ parse.IfStatement = function(contract, expression) { } }; -parse.InterfaceStatement = function(contract, expression) { - parse.ContractOrLibraryStatement(contract, expression); -}; - -parse.LibraryStatement = function(contract, expression) { - parse.ContractOrLibraryStatement(contract, expression); -}; - -parse.MemberExpression = function(contract, expression) { +// TODO: Investigate Node structure +/*parse.MemberAccess = function(contract, expression) { parse[expression.object.type] && parse[expression.object.type](contract, expression.object); -}; +};*/ parse.Modifiers = function(contract, modifiers) { if (modifiers) { @@ -151,10 +144,11 @@ parse.ReturnStatement = function(contract, expression) { register.statement(contract, expression); }; -parse.UnaryExpression = function(contract, expression) { - parse[expression.argument.type] && - parse[expression.argument.type](contract, expression.argument); -}; +// TODO:Investigate node structure +/*parse.UnaryOperation = function(contract, expression) { + parse[subExpression.argument.type] && + parse[subExpression.argument.type](contract, expression.argument); +};*/ parse.UsingStatement = function (contract, expression) { parse[expression.for.type] && @@ -165,12 +159,6 @@ parse.VariableDeclarationStatement = function (contract, expression) { register.statement(contract, expression); }; -parse.VariableDeclarationTuple = function (contract, expression) { - register.statement(contract, expression); - parse[expression.declarations[0].id.type] && - parse[expression.declarations[0].id.type](contract, expression.declarations[0].id); -}; - parse.WhileStatement = function (contract, expression) { register.statement(contract, expression); parse[expression.body.type] && diff --git a/lib/registrar.js b/lib/registrar.js index e4bc5bd..608fe28 100644 --- a/lib/registrar.js +++ b/lib/registrar.js @@ -19,19 +19,6 @@ class Registrar { : contract.injectionPoints[key] = [value]; } - /** - * TODO - idk what this is anymore - * @param {Object} expression AST node - */ - prePosition(expression) { - if ( - expression.right.type === 'ConditionalExpression' && - expression.left.type === 'MemberExpression' - ) { - expression.range[0] -= 2; - } - } - /** * Registers injections for statement measurements * @param {Object} contract instrumentation target diff --git a/test/sources/solidity/contracts/app/CLibrary.sol b/test/integration/projects/libraries/contracts/CLibrary.sol similarity index 100% rename from test/sources/solidity/contracts/app/CLibrary.sol rename to test/integration/projects/libraries/contracts/CLibrary.sol diff --git a/test/integration/projects/libraries/contracts/Migrations.sol b/test/integration/projects/libraries/contracts/Migrations.sol new file mode 100644 index 0000000..c378ffb --- /dev/null +++ b/test/integration/projects/libraries/contracts/Migrations.sol @@ -0,0 +1,23 @@ +pragma solidity >=0.4.21 <0.6.0; + +contract Migrations { + address public owner; + uint public last_completed_migration; + + constructor() public { + owner = msg.sender; + } + + modifier restricted() { + if (msg.sender == owner) _; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/test/sources/solidity/contracts/app/PureView.sol b/test/integration/projects/libraries/contracts/PureView.sol similarity index 100% rename from test/sources/solidity/contracts/app/PureView.sol rename to test/integration/projects/libraries/contracts/PureView.sol diff --git a/test/sources/solidity/contracts/app/TotallyPure.sol b/test/integration/projects/libraries/contracts/UsesPure.sol similarity index 83% rename from test/sources/solidity/contracts/app/TotallyPure.sol rename to test/integration/projects/libraries/contracts/UsesPure.sol index 5c6c826..b14d5f5 100644 --- a/test/sources/solidity/contracts/app/TotallyPure.sol +++ b/test/integration/projects/libraries/contracts/UsesPure.sol @@ -1,10 +1,10 @@ pragma solidity ^0.5.0; -import "./../../externalSources/Face.sol"; -import "./../../externalSources/PureView.sol"; -import "./../../externalSources/CLibrary.sol"; +import "./_Interface.sol"; +import "./PureView.sol"; +import "./CLibrary.sol"; -contract TotallyPure is PureView, Face { +contract UsesPure is PureView, _Interface { uint onehundred = 99; function usesThem() public view { diff --git a/test/sources/solidity/contracts/app/Face.sol b/test/integration/projects/libraries/contracts/_Interface.sol similarity index 79% rename from test/sources/solidity/contracts/app/Face.sol rename to test/integration/projects/libraries/contracts/_Interface.sol index 2e9d2a4..ee608f4 100644 --- a/test/sources/solidity/contracts/app/Face.sol +++ b/test/integration/projects/libraries/contracts/_Interface.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.0; -interface Face { +interface _Interface { function stare(uint a, uint b) external; function cry() external; } \ No newline at end of file diff --git a/test/integration/projects/libraries/migrations/1_initial_migration.js b/test/integration/projects/libraries/migrations/1_initial_migration.js new file mode 100644 index 0000000..ee2135d --- /dev/null +++ b/test/integration/projects/libraries/migrations/1_initial_migration.js @@ -0,0 +1,5 @@ +const Migrations = artifacts.require("Migrations"); + +module.exports = function(deployer) { + deployer.deploy(Migrations); +}; diff --git a/test/integration/projects/libraries/migrations/2_contracta.js b/test/integration/projects/libraries/migrations/2_contracta.js new file mode 100644 index 0000000..80eda3a --- /dev/null +++ b/test/integration/projects/libraries/migrations/2_contracta.js @@ -0,0 +1,8 @@ +const UsesPure = artifacts.require("UsesPure"); +const CLibrary = artifacts.require("CLibrary"); + +module.exports = function(deployer) { + deployer.deploy(CLibrary); + deployer.link(CLibrary, UsesPure); + deployer.deploy(UsesPure); +}; \ No newline at end of file diff --git a/test/integration/projects/libraries/test/libraries.js b/test/integration/projects/libraries/test/libraries.js new file mode 100644 index 0000000..e41ff19 --- /dev/null +++ b/test/integration/projects/libraries/test/libraries.js @@ -0,0 +1,64 @@ +const UsesPure = artifacts.require('UsesPure'); + +contract('UsesPure', accounts => { + it('calls imported, inherited pure/view functions within its own function', async () => { + const instance = await UsesPure.deployed(); + await instance.usesThem(); + }); + + it('calls a library method', async() => { + const instance = await UsesPure.deployed(); + const value = await instance.usesLibrary(); + assert.equal(value.toNumber(), 1); + }); + + it('calls an imported, inherited pure function', async () => { + const instance = await UsesPure.deployed(); + const value = await instance.isPure(4, 5); + assert.equal(value.toNumber(), 20); + }); + + it('calls an imported, inherited view function', async () => { + const instance = await UsesPure.deployed(); + const value = await instance.isView(); + assert.equal(value.toNumber(), 5); + }); + + it('overrides an imported, inherited abstract pure function', async () => { + const instance = await UsesPure.deployed(); + const value = await instance.bePure(4, 5); + assert.equal(value.toNumber(), 9); + }); + + it('overrides an imported, inherited abstract view function', async () => { + const instance = await UsesPure.deployed(); + const value = await instance.beView(); + assert.equal(value.toNumber(), 99); + }); + + it('calls a pure method implemented in an inherited class', async() => { + const instance = await UsesPure.deployed(); + const value = await instance.inheritedPure(4, 5); + assert.equal(value.toNumber(), 9); + }); + + it('calls a view method implemented in an inherited class', async () => { + const instance = await UsesPure.deployed(); + const value = await instance.inheritedView(); + assert.equal(value.toNumber(), 5); + }); + + it('calls a view method whose modifiers span lines', async () => { + const instance = await UsesPure.deployed(); + const value = await instance.multiline(5, 7) + assert.equal(value.toNumber(), 99); + }); + + it('calls a method who signature is defined by an interface', async () => { + const instance = await UsesPure.deployed(); + await instance.cry(); + }); + + + +}); \ No newline at end of file diff --git a/test/integration/projects/libraries/truffle-config.js b/test/integration/projects/libraries/truffle-config.js new file mode 100644 index 0000000..b398b07 --- /dev/null +++ b/test/integration/projects/libraries/truffle-config.js @@ -0,0 +1,7 @@ +module.exports = { + networks: {}, + mocha: {}, + compilers: { + solc: {} + } +} diff --git a/test/sources/js/totallyPure.js b/test/sources/js/totallyPure.js index b665267..7688a3f 100644 --- a/test/sources/js/totallyPure.js +++ b/test/sources/js/totallyPure.js @@ -1,58 +1,55 @@ -/* eslint-env node, mocha */ -/* global artifacts, contract, assert */ +const UsesPure = artifacts.require('UsesPure'); -const TotallyPure = artifacts.require('./TotallyPure.sol'); - -contract('TotallyPure', accounts => { +contract('UsesPure', accounts => { it('calls imported, inherited pure/view functions within its own function', async () => { - const instance = await TotallyPure.deployed(); + const instance = await UsesPure.deployed(); await instance.usesThem(); }); it('calls an imported, inherited pure function', async () => { - const instance = await TotallyPure.deployed(); + const instance = await UsesPure.deployed(); const value = await instance.isPure(4, 5); assert.equal(value.toNumber(), 20); }); it('calls an imported, inherited view function', async () => { - const instance = await TotallyPure.deployed(); + const instance = await UsesPure.deployed(); const value = await instance.isView(); assert.equal(value.toNumber(), 5); }); it('overrides an imported, inherited abstract pure function', async () => { - const instance = await TotallyPure.deployed(); + const instance = await UsesPure.deployed(); const value = await instance.bePure(4, 5); assert.equal(value.toNumber(), 9); }); it('overrides an imported, inherited abstract view function', async () => { - const instance = await TotallyPure.deployed(); + const instance = await UsesPure.deployed(); const value = await instance.beView(); assert.equal(value.toNumber(), 99); }); it('calls a pure method implemented in an inherited class', async() => { - const instance = await TotallyPure.deployed(); + const instance = await UsesPure.deployed(); const value = await instance.inheritedPure(4, 5); assert.equal(value.toNumber(), 9); }); it('calls a view method implemented in an inherited class', async () => { - const instance = await TotallyPure.deployed(); + const instance = await UsesPure.deployed(); const value = await instance.inheritedView(); assert.equal(value.toNumber(), 5); }); it('calls a view method whose modifiers span lines', async () => { - const instance = await TotallyPure.deployed(); + const instance = await UsesPure.deployed(); const value = await instance.multiline(5, 7) assert.equal(value.toNumber(), 99); }); it('calls a method who signature is defined by an interface', async () => { - const instance = await TotallyPure.deployed(); + const instance = await UsesPure.deployed(); await instance.cry(); }); diff --git a/test/sources/solidity/contracts/statements/unary.sol b/test/sources/solidity/contracts/statements/unary.sol new file mode 100644 index 0000000..f91ac75 --- /dev/null +++ b/test/sources/solidity/contracts/statements/unary.sol @@ -0,0 +1,8 @@ +pragma solidity ^0.5.0; + +contract Test { + function a() public { + uint x = 1; + x++; + } +} diff --git a/test/units/statements.js b/test/units/statements.js index ef3ee0d..cd0fbc7 100644 --- a/test/units/statements.js +++ b/test/units/statements.js @@ -147,6 +147,15 @@ describe('generic statements', () => { }); }); + it.skip('should cover a unary statement', async function(){ + const contract = await util.bootstrapCoverage('statements/unary', provider, collector); + coverage.addContract(contract.instrumented, util.filePath); + await contract.instance.a(); + const mapping = coverage.generate(contract.data, util.pathPrefix); + + // TODO: obtain both statements in unary.sol + }) + it('should cover an empty bodied contract statement', async function() { const contract = await util.bootstrapCoverage('statements/empty-contract-body', provider, collector); coverage.addContract(contract.instrumented, util.filePath); diff --git a/test/units/truffle/errors.js b/test/units/truffle/errors.js index 2af13f7..b39a00f 100644 --- a/test/units/truffle/errors.js +++ b/test/units/truffle/errors.js @@ -23,12 +23,12 @@ describe('Truffle Plugin: error cases', function() { mock.loggerOutput.val = ''; solcoverConfig = {}; truffleConfig = mock.getDefaultTruffleConfig(); + verify.cleanInitialState(); }) afterEach(() => mock.clean()); it('project contains no contract sources folder', async function() { - verify.cleanInitialState(); mock.installFullProject('no-sources'); try { @@ -50,9 +50,8 @@ describe('Truffle Plugin: error cases', function() { }); it('.solcover.js has syntax error', async function(){ - verify.cleanInitialState(); - mock.installFullProject('bad-solcoverjs'); + try { await plugin(truffleConfig); assert.fail() @@ -67,7 +66,6 @@ describe('Truffle Plugin: error cases', function() { }) it('.solcover.js has incorrectly formatted option', async function(){ - verify.cleanInitialState(); solcoverConfig.port = "Antwerpen"; mock.install('Simple', 'simple.js', solcoverConfig); @@ -84,7 +82,6 @@ describe('Truffle Plugin: error cases', function() { }); it('lib module load failure', async function(){ - verify.cleanInitialState(); truffleConfig.usePluginTruffle = true; truffleConfig.forceLibFailure = true; @@ -102,8 +99,6 @@ describe('Truffle Plugin: error cases', function() { }); it('--network is not declared in truffle-config.js', async function(){ - verify.cleanInitialState(); - truffleConfig.network = 'does-not-exist'; mock.install('Simple', 'simple.js', solcoverConfig); @@ -127,7 +122,6 @@ describe('Truffle Plugin: error cases', function() { // This case *does* throw an error, but it's uncatch-able; it('tries to launch with a port already in use', async function(){ - verify.cleanInitialState(); const server = ganache.server(); truffleConfig.network = 'development'; @@ -149,11 +143,31 @@ describe('Truffle Plugin: error cases', function() { await pify(server.close)(); }); + it('uses an invalid istanbul reporter', async function() { + solcoverConfig = { + silent: process.env.SILENT ? true : false, + istanbulReporter: ['does-not-exist'] + }; + + mock.install('Simple', 'simple.js', solcoverConfig); + + try { + await plugin(truffleConfig); + assert.fail(); + } catch(err){ + assert( + err.message.includes('does-not-exist') && + err.message.includes('coverage reports could not be generated'), + `Should error on invalid reporter: ${err.message}` + ) + } + + }); + // Truffle test contains syntax error it('truffle crashes', async function() { - verify.cleanInitialState(); - mock.install('Simple', 'truffle-crash.js', solcoverConfig); + try { await plugin(truffleConfig); assert.fail() @@ -164,8 +178,6 @@ describe('Truffle Plugin: error cases', function() { // Solidity syntax errors it('compilation failure', async function(){ - verify.cleanInitialState(); - mock.install('SimpleError', 'simple.js', solcoverConfig); try { @@ -179,8 +191,6 @@ describe('Truffle Plugin: error cases', function() { }); it('instrumentation failure', async function(){ - verify.cleanInitialState(); - mock.install('Unparseable', 'simple.js', solcoverConfig); try { diff --git a/test/units/truffle/flags.js b/test/units/truffle/flags.js index 82a0674..af1cadd 100644 --- a/test/units/truffle/flags.js +++ b/test/units/truffle/flags.js @@ -21,13 +21,12 @@ describe('Truffle Plugin: command line options', function() { mock.loggerOutput.val = ''; solcoverConfig = {}; truffleConfig = mock.getDefaultTruffleConfig(); + verify.cleanInitialState(); }) afterEach(() => mock.clean()); it('--file test/', async function() { - verify.cleanInitialState(); - truffleConfig.file = path.join( truffleConfig.working_directory, 'test/specific_a.js' @@ -55,8 +54,6 @@ describe('Truffle Plugin: command line options', function() { }); it('--file test/', async function() { - verify.cleanInitialState(); - truffleConfig.file = path.join( truffleConfig.working_directory, 'test/globby*' @@ -84,8 +81,6 @@ describe('Truffle Plugin: command line options', function() { }); it('--file test/gl{o,b}*.js', async function() { - verify.cleanInitialState(); - truffleConfig.file = path.join( truffleConfig.working_directory, 'test/gl{o,b}*.js' @@ -113,8 +108,6 @@ describe('Truffle Plugin: command line options', function() { }); it('--config ../.solcover.js', async function() { - verify.cleanInitialState(); - solcoverConfig = { silent: process.env.SILENT ? true : false, istanbulReporter: ['json-summary', 'text'] @@ -143,8 +136,6 @@ describe('Truffle Plugin: command line options', function() { }); it('--help', async function(){ - verify.cleanInitialState(); - truffleConfig.help = "true"; truffleConfig.logger = mock.testLogger; @@ -158,8 +149,6 @@ describe('Truffle Plugin: command line options', function() { }) it('--version', async function(){ - verify.cleanInitialState(); - truffleConfig.version = "true"; truffleConfig.logger = mock.testLogger; @@ -184,8 +173,6 @@ describe('Truffle Plugin: command line options', function() { }) it('--useGlobalTruffle', async function(){ - verify.cleanInitialState(); - truffleConfig.useGlobalTruffle = true; truffleConfig.logger = mock.testLogger; @@ -199,8 +186,6 @@ describe('Truffle Plugin: command line options', function() { }); it('--usePluginTruffle', async function(){ - verify.cleanInitialState(); - truffleConfig.usePluginTruffle = true; truffleConfig.logger = mock.testLogger; @@ -214,8 +199,6 @@ describe('Truffle Plugin: command line options', function() { }); it('--usePluginTruffle', async function(){ - verify.cleanInitialState(); - truffleConfig.usePluginTruffle = true; truffleConfig.logger = mock.testLogger; @@ -229,8 +212,6 @@ describe('Truffle Plugin: command line options', function() { }); it('--network (network_id mismatch in configs)', async function(){ - verify.cleanInitialState(); - truffleConfig.logger = mock.testLogger; solcoverConfig = { providerOptions: { network_id: 5 }} @@ -248,8 +229,6 @@ describe('Truffle Plugin: command line options', function() { }); it('--network (truffle config missing port)', async function(){ - verify.cleanInitialState(); - truffleConfig.logger = mock.testLogger; truffleConfig.network = 'development'; @@ -267,12 +246,9 @@ describe('Truffle Plugin: command line options', function() { mock.loggerOutput.val.includes("8555"), `Should have used default coverage port 8555: ${mock.loggerOutput.val}` ); - }); it('--network (declared port mismatches)', async function(){ - verify.cleanInitialState(); - truffleConfig.logger = mock.testLogger; truffleConfig.network = 'development'; // 8545 @@ -290,7 +266,6 @@ describe('Truffle Plugin: command line options', function() { mock.loggerOutput.val.includes("8545"), `Should have used default coverage port 8545: ${mock.loggerOutput.val}` ); - }); }); diff --git a/test/units/truffle/standard.js b/test/units/truffle/standard.js index 7b5cbaa..c1cd2ca 100644 --- a/test/units/truffle/standard.js +++ b/test/units/truffle/standard.js @@ -21,13 +21,12 @@ describe('Truffle Plugin: standard use cases', function() { mock.loggerOutput.val = ''; solcoverConfig = {}; truffleConfig = mock.getDefaultTruffleConfig(); + verify.cleanInitialState(); }) afterEach(() => mock.clean()); it('simple contract', async function(){ - verify.cleanInitialState(); - mock.install('Simple', 'simple.js', solcoverConfig); await plugin(truffleConfig); @@ -36,21 +35,18 @@ describe('Truffle Plugin: standard use cases', function() { const output = mock.getOutput(truffleConfig); const path = Object.keys(output)[0]; - assert(output[path].fnMap['1'].name === 'test', 'coverage.json missing "test"'); - assert(output[path].fnMap['2'].name === 'getX', 'coverage.json missing "getX"'); - }); - - // Truffle test asserts balance is 777 ether - it('config with providerOptions', async function() { - solcoverConfig.providerOptions = { default_balance_ether: 777 } + assert( + output[path].fnMap['1'].name === 'test', + 'coverage.json missing "test"' + ); - mock.install('Simple', 'testrpc-options.js', solcoverConfig); - await plugin(truffleConfig); + assert( + output[path].fnMap['2'].name === 'getX', + 'coverage.json missing "getX"' + ); }); - it('large contract with many unbracketed statements (time check)', async function() { - verify.cleanInitialState(); - + it('with many unbracketed statements (time check)', async function() { truffleConfig.compilers.solc.version = "0.4.24"; mock.install('Oraclize', 'oraclize.js', solcoverConfig, truffleConfig, true); @@ -59,8 +55,7 @@ describe('Truffle Plugin: standard use cases', function() { // This project has three contract suites and uses .deployed() instances which // depend on truffle's migratons and the inter-test evm_revert / evm_snapshot mechanism. - it('project evm_reverts repeatedly', async function() { - verify.cleanInitialState(); + it('with multiple migrations (evm_reverts repeatedly)', async function() { mock.installFullProject('multiple-migrations'); await plugin(truffleConfig); @@ -82,84 +77,17 @@ describe('Truffle Plugin: standard use cases', function() { verify.lineCoverage(expected); }); - // This project has [ @skipForCoverage ] tags in the test descriptions - // at selected 'contract' and 'it' blocks. - it('uses solcoverjs mocha options', async function() { - verify.cleanInitialState(); - - solcoverConfig.mocha = { - grep: '@skipForCoverage', - invert: true, - }; - - solcoverConfig.silent = process.env.SILENT ? true : false, - solcoverConfig.istanbulReporter = ['json-summary', 'text'] - - mock.installFullProject('multiple-migrations', solcoverConfig); - await plugin(truffleConfig); - - const expected = [ - { - file: mock.pathToContract(truffleConfig, 'ContractA.sol'), - pct: 0 - }, - { - file: mock.pathToContract(truffleConfig, 'ContractB.sol'), - pct: 0, - }, - { - file: mock.pathToContract(truffleConfig, 'ContractC.sol'), - pct: 100, - }, - ]; - - verify.lineCoverage(expected); - }); - - it('skips a folder', async function() { - verify.cleanInitialState(); - mock.installFullProject('skipping'); - await plugin(truffleConfig); - - const expected = [{ - file: mock.pathToContract(truffleConfig, 'ContractA.sol'), - pct: 100 - }]; - - const missing = [{ - file: mock.pathToContract(truffleConfig, 'skipped-folder/ContractB.sol'), - }]; - - verify.lineCoverage(expected); - verify.coverageMissing(missing); - }); - - it('uses "onServerReady", "onTestsComplete", "onIstanbulComplete"', async function() { - verify.cleanInitialState(); - - truffleConfig.logger = mock.testLogger; - mock.installFullProject('test-files'); - + it('with relative path solidity imports', async function() { + mock.installFullProject('import-paths'); await plugin(truffleConfig); - - assert( - mock.loggerOutput.val.includes('running onServerReady') && - mock.loggerOutput.val.includes('running onTestsComplete') && - mock.loggerOutput.val.includes('running onIstanbulComplete'), - - `Should run "on" hooks : ${mock.loggerOutput.val}` - ); }); - it('project with relative path solidity imports', async function() { - verify.cleanInitialState(); - mock.installFullProject('import-paths'); + it('uses libraries', async function() { + mock.installFullProject('libraries'); await plugin(truffleConfig); }); - it('project contains native solidity tests', async function(){ - verify.cleanInitialState(); - + it('uses native solidity tests', async function(){ mock.install('Simple', 'TestSimple.sol', solcoverConfig); truffleConfig.logger = mock.testLogger; @@ -171,63 +99,58 @@ describe('Truffle Plugin: standard use cases', function() { ); }); - it('contract only uses ".call"', async function(){ - verify.cleanInitialState(); + it('uses inheritance', async function() { + mock.installDouble( + ['Proxy', 'Owned'], + 'inheritance.js', + solcoverConfig + ); - mock.install('OnlyCall', 'only-call.js', solcoverConfig); await plugin(truffleConfig); verify.coverageGenerated(truffleConfig); const output = mock.getOutput(truffleConfig); - const path = Object.keys(output)[0]; - assert(output[path].fnMap['1'].name === 'addTwo', 'cov should map "addTwo"'); - }); - - it('contract sends / transfers to instrumented fallback', async function(){ - verify.cleanInitialState(); - - mock.install('Wallet', 'wallet.js', solcoverConfig); - await plugin(truffleConfig); + const ownedPath = Object.keys(output)[0]; + const proxyPath = Object.keys(output)[1]; - verify.coverageGenerated(truffleConfig); + assert( + output[ownedPath].fnMap['1'].name === 'constructor', + '"constructor" not covered' + ); - const output = mock.getOutput(truffleConfig); - const path = Object.keys(output)[0]; - assert(output[path].fnMap['1'].name === 'transferPayment', 'cov should map "transferPayment"'); + assert( + output[proxyPath].fnMap['1'].name === 'isOwner', + '"isOwner" not covered' + ); }); - it('contracts are skipped', async function() { - verify.cleanInitialState(); - - solcoverConfig.skipFiles = ['Owned.sol']; - - mock.installDouble(['Proxy', 'Owned'], 'inheritance.js', solcoverConfig); + it('only uses ".call"', async function(){ + mock.install('OnlyCall', 'only-call.js', solcoverConfig); await plugin(truffleConfig); verify.coverageGenerated(truffleConfig); const output = mock.getOutput(truffleConfig); - const firstKey = Object.keys(output)[0]; - - assert(Object.keys(output).length === 1, 'Wrong # of contracts covered'); - assert(firstKey.substr(firstKey.length - 9) === 'Proxy.sol', 'Wrong contract covered'); + const path = Object.keys(output)[0]; + assert( + output[path].fnMap['1'].name === 'addTwo', + 'cov should map "addTwo"' + ); }); - it('contract uses inheritance', async function() { - verify.cleanInitialState(); - - mock.installDouble(['Proxy', 'Owned'], 'inheritance.js', solcoverConfig); + it('sends / transfers to instrumented fallback', async function(){ + mock.install('Wallet', 'wallet.js', solcoverConfig); await plugin(truffleConfig); verify.coverageGenerated(truffleConfig); const output = mock.getOutput(truffleConfig); - const ownedPath = Object.keys(output)[0]; - const proxyPath = Object.keys(output)[1]; - - assert(output[ownedPath].fnMap['1'].name === 'constructor', '"constructor" not covered'); - assert(output[proxyPath].fnMap['1'].name === 'isOwner', '"isOwner" not covered'); + const path = Object.keys(output)[0]; + assert( + output[path].fnMap['1'].name === 'transferPayment', + 'cov should map "transferPayment"' + ); }); // Truffle test asserts deployment cost is greater than 20,000,000 gas @@ -237,9 +160,7 @@ describe('Truffle Plugin: standard use cases', function() { }); // Simple.sol with a failing assertion in a truffle test - it('truffle tests failing', async function() { - verify.cleanInitialState(); - + it('unit tests failing', async function() { mock.install('Simple', 'truffle-test-fail.js', solcoverConfig); try { @@ -259,11 +180,8 @@ describe('Truffle Plugin: standard use cases', function() { }); it('uses the fallback server', async function(){ - verify.cleanInitialState(); - truffleConfig.logger = mock.testLogger; - - solcoverConfig = { forceBackupServer: true } + solcoverConfig.forceBackupServer = true; mock.install('Simple', 'simple.js', solcoverConfig); await plugin(truffleConfig); @@ -272,6 +190,104 @@ describe('Truffle Plugin: standard use cases', function() { mock.loggerOutput.val.includes("Using ganache-core-sc"), `Should notify about backup server module: ${mock.loggerOutput.val}` ); + }); + + // This project has [ @skipForCoverage ] tags in the test descriptions + // at selected 'contract' and 'it' blocks. + it('config: mocha options', async function() { + solcoverConfig.mocha = { + grep: '@skipForCoverage', + invert: true, + }; + + solcoverConfig.silent = process.env.SILENT ? true : false, + solcoverConfig.istanbulReporter = ['json-summary', 'text'] + + mock.installFullProject('multiple-migrations', solcoverConfig); + await plugin(truffleConfig); + + const expected = [ + { + file: mock.pathToContract(truffleConfig, 'ContractA.sol'), + pct: 0 + }, + { + file: mock.pathToContract(truffleConfig, 'ContractB.sol'), + pct: 0, + }, + { + file: mock.pathToContract(truffleConfig, 'ContractC.sol'), + pct: 100, + }, + ]; + + verify.lineCoverage(expected); + }); + + // Truffle test asserts balance is 777 ether + it('config: providerOptions', async function() { + solcoverConfig.providerOptions = { default_balance_ether: 777 } + + mock.install('Simple', 'testrpc-options.js', solcoverConfig); + await plugin(truffleConfig); + }); + + it('config: skipped file', async function() { + solcoverConfig.skipFiles = ['Owned.sol']; + + mock.installDouble( + ['Proxy', 'Owned'], + 'inheritance.js', + solcoverConfig + ); + await plugin(truffleConfig); + + verify.coverageGenerated(truffleConfig); + + const output = mock.getOutput(truffleConfig); + const firstKey = Object.keys(output)[0]; + + assert( + Object.keys(output).length === 1, + 'Wrong # of contracts covered' + ); + + assert( + firstKey.substr(firstKey.length - 9) === 'Proxy.sol', + 'Wrong contract covered' + ); + }); + + it('config: skipped folder', async function() { + mock.installFullProject('skipping'); + await plugin(truffleConfig); + + const expected = [{ + file: mock.pathToContract(truffleConfig, 'ContractA.sol'), + pct: 100 + }]; + + const missing = [{ + file: mock.pathToContract(truffleConfig, 'skipped-folder/ContractB.sol'), + }]; + + verify.lineCoverage(expected); + verify.coverageMissing(missing); + }); + + it('config: "onServerReady", "onTestsComplete", ...', async function() { + truffleConfig.logger = mock.testLogger; + mock.installFullProject('test-files'); + + await plugin(truffleConfig); + + assert( + mock.loggerOutput.val.includes('running onServerReady') && + mock.loggerOutput.val.includes('running onTestsComplete') && + mock.loggerOutput.val.includes('running onIstanbulComplete'), + + `Should run "on" hooks : ${mock.loggerOutput.val}` + ); }); })