From c500b9df7a2975be3ad231c2894e46e94bc650a9 Mon Sep 17 00:00:00 2001 From: Alex Rea Date: Tue, 27 Jun 2017 11:24:01 +0100 Subject: [PATCH] Allow files to be skipped during coverage While ordinarily we shouldn't want to do these, it is possible to construct valid contracts using assembly that break when the coverage events are injected. --- README.md | 3 ++- bin/exec.js | 14 +++++++++----- test/cli.js | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2d6b397..e6203fc 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ can be useful if you are using a different vm like the [sc-forks version of pyet directory. `dir` allows you to define a relative path from the root directory to those assets. `dir: "./"` would tell solidity-coverage to look for `.//contracts/` and `.//test/` + **copyNodeModules**: *{ Boolean }* : When true, will copy `node_modules` into the coverage environment. False by default, and may significantly increase the time for coverage to complete if enabled. Only enable if required. ++ **skipFiles**: *{ Array }* : An array of contracts (with paths expressed relative to the `contracts` directory) that should be skipped when doing instrumentation. `Migrations.sol` is skipped by default, and does not need to be added to this configuration option if it is used. **Example .solcover.js config file** ```javascript @@ -97,7 +98,7 @@ the extra events. If this is the case, then the coverage may be incomplete. To a **Using `require` in `migrations.js` files**: Truffle overloads Node's `require` function but implements a simplified search algorithm for node_modules packages -([see Truffle issue #383](https://github.com/trufflesuite/truffle/issues/383)). +([see Truffle issue #383](https://github.com/trufflesuite/truffle/issues/383)). Because solidity-coverage copies an instrumented version of your project into a temporary folder, `require` statements handled by Truffle internally won't resolve correctly. diff --git a/bin/exec.js b/bin/exec.js index 328b521..0392722 100644 --- a/bin/exec.js +++ b/bin/exec.js @@ -62,6 +62,7 @@ const workingDir = config.dir || '.'; // Relative path to contracts folder let port = config.port || 8555; // Port testrpc listens on const accounts = config.accounts || 35; // Number of accounts to testrpc launches with const copyNodeModules = config.copyNodeModules || false; // Whether we copy node_modules when making coverage environment +let skipFiles = config.skipFiles || []; // Which files should be skipped during instrumentation // Silence shell and script logging (for solcover's unit tests / CI) if (config.silent) { @@ -89,7 +90,7 @@ try { // Coverage network opts specified: use port if declared if (truffleConfig && truffleConfig.networks && truffleConfig.networks.coverage) { port = truffleConfig.networks.coverage.port || port; - + // Coverage network opts NOT specified: default to the development network w/ modified // port, gasLimit, gasPrice. Export the config object only. } else { @@ -114,18 +115,19 @@ try { cleanUp(msg + err); } -// For each contract except migrations.sol: +// For each contract except migrations.sol (or those in skipFiles): // 1. Generate file path reference for coverage report // 2. Load contract as string // 3. Instrument contract // 4. Save instrumented contract in the coverage environment folder where covered tests will run // 5. Add instrumentation info to the coverage map +skipFiles = skipFiles.map(contract => `${coverageDir}/contracts/` + contract); +skipFiles.push(`${coverageDir}/contracts/Migrations.sol`); + let currentFile; try { shell.ls(`${coverageDir}/contracts/**/*.sol`).forEach(file => { - const migrations = `${coverageDir}/contracts/Migrations.sol`; - - if (file !== migrations) { + if (!skipFiles.includes(file)) { log('Instrumenting ', file); currentFile = file; @@ -135,6 +137,8 @@ try { const instrumentedContractInfo = getInstrumentedVersion(contract, canonicalPath); fs.writeFileSync(contractPath, instrumentedContractInfo.contract); coverage.addContract(instrumentedContractInfo, canonicalPath); + } else { + log('Skipping instrumentation of ', file); } }); } catch (err) { diff --git a/test/cli.js b/test/cli.js index 186a02f..c8c394e 100644 --- a/test/cli.js +++ b/test/cli.js @@ -242,6 +242,28 @@ describe('cli', () => { collectGarbage(); }); + it('contracts are skipped: should generate coverage, cleanup & exit(0)', () => { + // Skip instrumentation of some contracts + assert(pathExists('./coverage') === false, 'should start without: coverage'); + assert(pathExists('./coverage.json') === false, 'should start without: coverage.json'); + const testConfig = Object.assign({}, config); + + testConfig.skipFiles = ['Owned.sol']; + mock.installInheritanceTest(testConfig); + + shell.exec(script); + assert(shell.error() === null, 'script should not error'); + + assert(pathExists('./coverage') === true, 'script should gen coverage folder'); + assert(pathExists('./coverage.json') === true, 'script should gen coverage.json'); + + const produced = JSON.parse(fs.readFileSync('./coverage.json', 'utf8')); + const firstKey = Object.keys(produced)[0]; + assert(Object.keys(produced).length === 1, 'coverage.json should only contain instrumentation for one contract'); + assert(firstKey.substr(firstKey.length - 9) === 'Proxy.sol', 'coverage.json should only contain instrumentation for Proxy.sol'); + collectGarbage(); + }); + it('truffle tests failing: should generate coverage, cleanup & exit(1)', () => { assert(pathExists('./coverage') === false, 'should start without: coverage'); assert(pathExists('./coverage.json') === false, 'should start without: coverage.json');