From ce2e6c3dfb9ae043ae28f23d7f69a0c0a2c91c42 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Thu, 21 Nov 2019 22:51:50 -0800 Subject: [PATCH] Add tests for buidler cli options (#435) --- README.md | 9 +- dist/buidler.plugin.js | 22 +++-- dist/plugin-assets/buidler.ui.js | 6 +- dist/plugin-assets/buidler.utils.js | 46 ++++----- dist/plugin-assets/plugin.utils.js | 26 ++--- dist/truffle.plugin.js | 1 + package.json | 2 +- test/units/buidler/errors.js | 2 +- test/units/buidler/flags.js | 146 ++++++++++++++++++++++++++++ test/units/truffle/flags.js | 15 +-- test/units/truffle/standard.js | 2 +- test/util/integration.js | 18 +++- 12 files changed, 223 insertions(+), 72 deletions(-) create mode 100644 test/units/buidler/flags.js diff --git a/README.md b/README.md index 6024ad5..67bb69b 100644 --- a/README.md +++ b/README.md @@ -36,17 +36,16 @@ truffle run coverage [command-options] + Coverage launches its own in-process ganache server. + You can set [ganache options][1] using the `providerOptions` key in your `.solcover.js` [config][15]. + Coverage [distorts gas consumption][13]. Tests that check exact gas consumption should be [skipped][24]. -+ :warning: Contracts are compiled **without optimization**. Please report unexpected compilation faults to [issue 417][25] ++ :warning: Contracts are compiled **without optimization**. Please report unexpected compilation faults to [issue 417][25] ## Command Options | Option | Example | Description | |--------------|------------------------------------|--------------------------------| -| file | `--file="test/registry/*.js"` | Filename or glob describing a subset of JS tests to run. (Globs must be enclosed by quotes.)| +| file (Truffle) | `--file="test/registry/*.js"` | Filename or glob describing a subset of JS tests to run. (Globs must be enclosed by quotes.)| +| testFiles (Buidler) | `--testFiles test/file.js` | JS test file(s) to run.| | solcoverjs | `--solcoverjs ./../.solcover.js` | Relative path from working directory to config. Useful for monorepo packages that share settings. (Path must be "./" prefixed) | -| network | `--network development` | Use network settings defined in the Truffle config | +| network | `--network development` | Use network settings defined in the Truffle or Buidler config | | temp[*][14] | `--temp build` | :warning: **Caution** :warning: Path to a *disposable* folder to store compilation artifacts in. Useful when your test setup scripts include hard-coded paths to a build directory. [More...][14] | -| version | | Version info | -| help | | Usage notes | [* Advanced use][14] diff --git a/dist/buidler.plugin.js b/dist/buidler.plugin.js index dba89c4..8c005da 100644 --- a/dist/buidler.plugin.js +++ b/dist/buidler.plugin.js @@ -17,6 +17,8 @@ const { TASK_COMPILE, } = require("@nomiclabs/buidler/builtin-tasks/task-names"); +ensurePluginLoadedWithUsePlugin(); + function plugin() { // UI for the task flags... @@ -24,11 +26,11 @@ function plugin() { task("coverage", "Generates a code coverage report for tests") - .addOptionalParam("file", ui.flags.file, null, types.string) + .addOptionalParam("testFiles", ui.flags.file, null, types.string) .addOptionalParam("solcoverjs", ui.flags.solcoverjs, null, types.string) .addOptionalParam('temp', ui.flags.temp, null, types.string) - .setAction(async function(taskArguments, env){ + .setAction(async function(args, env){ let error; let ui; let api; @@ -37,15 +39,19 @@ function plugin() { try { death(buidlerUtils.finish.bind(null, config, api)); // Catch interrupt signals - config = buidlerUtils.normalizeConfig(env.config); + config = buidlerUtils.normalizeConfig(env.config, args); ui = new PluginUI(config.logger.log); api = new API(utils.loadSolcoverJS(config)); // ============== // Server launch // ============== - - const network = buidlerUtils.setupNetwork(env, api); + const network = buidlerUtils.setupNetwork( + env, + api, + args, + ui + ); const address = await api.ganache(ganache); const web3 = new Web3(address); @@ -88,12 +94,14 @@ function plugin() { // ============== // Compilation // ============== + config.temp = args.temp; const { tempArtifactsDir, tempContractsDir } = utils.getTempLocations(config); + utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir) utils.save(targets, config.paths.sources, tempContractsDir); utils.save(skipped, config.paths.sources, tempContractsDir); @@ -109,7 +117,7 @@ function plugin() { // ====== // Tests // ====== - const testFiles = buidlerUtils.getTestFilePaths(config); + const testFiles = args.testFiles ? [args.testFiles] : []; try { await env.run(TASK_TEST, {testFiles: testFiles}) @@ -128,7 +136,7 @@ function plugin() { error = e; } - await utils.finish(config, api); + await buidlerUtils.finish(config, api); if (error !== undefined ) throw error; if (process.exitCode > 0) throw new Error(ui.generate('tests-fail', [process.exitCode])); diff --git a/dist/plugin-assets/buidler.ui.js b/dist/plugin-assets/buidler.ui.js index 78c39a3..35f35ff 100644 --- a/dist/plugin-assets/buidler.ui.js +++ b/dist/plugin-assets/buidler.ui.js @@ -45,6 +45,10 @@ class PluginUI extends UI { `\n${c.bold('============')}\n` + `${ct} ${c.bold('port')}: ${args[1]}\n` + `${ct} ${c.bold('network')}: ${args[0]}\n`, + + 'port-clash': `${w} ${c.red("The 'port' values in your Buidler url ")}` + + `${c.red("and .solcover.js are different. Using Buidler's: ")} ${c.bold(args[0])}.\n`, + } this._write(kinds[kind]); @@ -70,8 +74,6 @@ class PluginUI extends UI { 'tests-fail': `${x} ${c.bold(args[0])} ${c.red('test(s) failed under coverage.')}`, - 'no-network': `${c.red('Network: ')} ${args[0]} ` + - `${c.red(' is not defined in your truffle-config networks. ')}`, } diff --git a/dist/plugin-assets/buidler.utils.js b/dist/plugin-assets/buidler.utils.js index 23fff0c..9ef5dff 100644 --- a/dist/plugin-assets/buidler.utils.js +++ b/dist/plugin-assets/buidler.utils.js @@ -10,47 +10,44 @@ const { createProvider } = require("@nomiclabs/buidler/internal/core/providers/c // Buidler Specific Plugin Utils // ============================= -/** - * Returns a list of test files to pass to TASK_TEST. - * @param {BuidlerConfig} config - * @return {String[]} list of files to pass to mocha - */ -function getTestFilePaths(config){ - let target; - - // Handle --file cli option (subset of tests) - (typeof config.file === 'string') - ? target = globby.sync([config.file]) - : target = []; - - // Return list of test files - const testregex = /.*\.(js|ts|es|es6|jsx)$/; - return target.filter(f => f.match(testregex) != null); -} - /** * Normalizes buidler paths / logging for use by the plugin utilities and * attaches them to the config * @param {BuidlerConfig} config * @return {BuidlerConfig} updated config */ -function normalizeConfig(config){ +function normalizeConfig(config, args){ config.workingDir = config.paths.root; config.contractsDir = config.paths.sources; config.testDir = config.paths.tests; config.artifactsDir = config.paths.artifacts; config.logger = config.logger ? config.logger : {log: null}; + config.solcoverjs = args.solcoverjs return config; } -function setupNetwork(env, api){ - const networkConfig = { - url: `http://${api.host}:${api.port}`, - gas: api.gasLimit, - gasPrice: api.gasPrice +function setupNetwork(env, api, taskArgs, ui){ + let networkConfig = {}; + + if (taskArgs.network){ + networkConfig = env.config.networks[taskArgs.network]; + + const configPort = networkConfig.url.split(':')[2]; + + // Warn: port conflicts + if (api.port !== api.defaultPort && api.port !== configPort){ + ui.report('port-clash', [ configPort ]) + } + + // Prefer network port + api.port = parseInt(configPort); } + networkConfig.url = `http://${api.host}:${api.port}`; + networkConfig.gas = api.gasLimit; + networkConfig.gasPrice = api.gasPrice; + const provider = createProvider(api.defaultNetworkName, networkConfig); env.config.networks[api.defaultNetworkName] = networkConfig; @@ -102,7 +99,6 @@ module.exports = { normalizeConfig: normalizeConfig, finish: finish, tempCacheDir: tempCacheDir, - getTestFilePaths: getTestFilePaths, setupNetwork: setupNetwork } diff --git a/dist/plugin-assets/plugin.utils.js b/dist/plugin-assets/plugin.utils.js index d4162ee..1dc50a9 100644 --- a/dist/plugin-assets/plugin.utils.js +++ b/dist/plugin-assets/plugin.utils.js @@ -48,6 +48,19 @@ function loadSource(_path){ return fs.readFileSync(_path).toString(); } +/** + * Sets up temporary folders for instrumented contracts and their compilation artifacts + * @param {PlatformConfig} config + * @param {String} tempContractsDir + * @param {String} tempArtifactsDir + */ +function setupTempFolders(config, tempContractsDir, tempArtifactsDir){ + checkContext(config, tempContractsDir, tempArtifactsDir); + + shell.mkdir(tempContractsDir); + shell.mkdir(tempArtifactsDir); +} + /** * Save a set of instrumented files to a temporary directory. * @param {Object[]} targets array of targets generated by `assembleTargets` @@ -122,16 +135,6 @@ function assembleFiles(config, skipFiles=[]){ let skipFolders; let skipped = []; - const { - tempContractsDir, - tempArtifactsDir - } = getTempLocations(config); - - checkContext(config, tempContractsDir, tempArtifactsDir); - - shell.mkdir(tempContractsDir); - shell.mkdir(tempArtifactsDir); - targets = shell.ls(`${config.contractsDir}/**/*.sol`); skipFiles = assembleSkipped(config, targets, skipFiles); @@ -268,5 +271,6 @@ module.exports = { reportSkipped: reportSkipped, save: save, checkContext: checkContext, - toRelativePath: toRelativePath + toRelativePath: toRelativePath, + setupTempFolders: setupTempFolders } diff --git a/dist/truffle.plugin.js b/dist/truffle.plugin.js index 62c4ad3..57445cd 100644 --- a/dist/truffle.plugin.js +++ b/dist/truffle.plugin.js @@ -82,6 +82,7 @@ async function plugin(config){ tempContractsDir } = utils.getTempLocations(config); + utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir) utils.save(targets, config.contracts_directory, tempContractsDir); utils.save(skipped, config.contracts_directory, tempContractsDir); diff --git a/package.json b/package.json index bed3e2b..9f41191 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "scripts": { "nyc": "SILENT=true nyc --exclude '**/sc_temp/**' --exclude '**/test/**'", "test": "npm run nyc -- mocha test/units/* --timeout 100000 --no-warnings --exit", - "test:ci": "SILENT=true nyc --reporter=lcov --exclude '**/sc_temp/**' --exclude '**/test/**/' -- mocha test/units/* --timeout 100000 --no-warnings --exit", + "test:ci": "SILENT=true node --max-old-space-size=3072 ./node_modules/.bin/nyc --reporter=lcov --exclude '**/sc_temp/**' --exclude '**/test/**/' -- mocha test/units/* --timeout 100000 --no-warnings --exit", "test:debug": "mocha test/units/* --timeout 100000 --no-warnings --exit" }, "homepage": "https://github.com/sc-forks/solidity-coverage", diff --git a/test/units/buidler/errors.js b/test/units/buidler/errors.js index c39c990..9ab3e48 100644 --- a/test/units/buidler/errors.js +++ b/test/units/buidler/errors.js @@ -3,7 +3,7 @@ const fs = require('fs'); const path = require('path') const pify = require('pify') const shell = require('shelljs'); -const ganache = require('ganache-core-sc'); +const ganache = require('ganache-cli') const verify = require('../../util/verifiers') const mock = require('../../util/integration'); diff --git a/test/units/buidler/flags.js b/test/units/buidler/flags.js new file mode 100644 index 0000000..435e297 --- /dev/null +++ b/test/units/buidler/flags.js @@ -0,0 +1,146 @@ +const assert = require('assert'); +const fs = require('fs'); +const path = require('path') +const shell = require('shelljs'); + +const verify = require('../../util/verifiers') +const mock = require('../../util/integration'); +const plugin = require('../../../dist/buidler.plugin'); + +// ======================= +// CLI Options / Flags +// ======================= +async function delay(){ + return new Promise(res => setTimeout(() => res(), 1000)) +} + +describe('Buidler Plugin: command line options', function() { + let buidlerConfig; + let solcoverConfig; + + beforeEach(function(){ + mock.clean(); + + mock.loggerOutput.val = ''; + solcoverConfig = { + skipFiles: ['Migrations.sol'], + silent: process.env.SILENT ? true : false, + istanbulReporter: ['json-summary', 'text'] + }; + buidlerConfig = mock.getDefaultBuidlerConfig(); + verify.cleanInitialState(); + }) + + afterEach(async function (){ + mock.buidlerTearDownEnv(); + mock.clean(); + }); + + + it('--temp', async function(){ + const taskArgs = { + temp: 'special_folder' + } + + mock.install('Simple', 'simple.js', solcoverConfig); + mock.buidlerSetupEnv(this); + + await this.env.run("coverage", taskArgs); + + const expected = [{ + file: mock.pathToContract(buidlerConfig, 'Simple.sol'), + pct: 100 + }]; + + verify.lineCoverage(expected); + }); + + it('--network (declared port mismatches)', async function(){ + const taskArgs = { + network: 'development' // 8545 + } + + solcoverConfig.port = 8222; + + mock.install('Simple', 'simple.js', solcoverConfig); + mock.buidlerSetupEnv(this); + + await this.env.run("coverage", taskArgs); + + assert( + mock.loggerOutput.val.includes("The 'port' values"), + `Should notify about mismatched port values: ${mock.loggerOutput.val}` + ); + + assert( + mock.loggerOutput.val.includes("8545"), + `Should have used default coverage port 8545: ${mock.loggerOutput.val}` + ); + + const expected = [{ + file: mock.pathToContract(buidlerConfig, 'Simple.sol'), + pct: 100 + }]; + + verify.lineCoverage(expected); + }); + + it('--testFiles test/', async function() { + const taskArgs = { + testFiles: path.join( + buidlerConfig.paths.root, + 'test/specific_a.js' + ) + }; + + mock.installFullProject('test-files'); + mock.buidlerSetupEnv(this); + + await this.env.run("coverage", taskArgs); + + const expected = [ + { + file: mock.pathToContract(buidlerConfig, 'ContractA.sol'), + pct: 100 + }, + { + file: mock.pathToContract(buidlerConfig, 'ContractB.sol'), + pct: 0, + }, + { + file: mock.pathToContract(buidlerConfig, 'ContractC.sol'), + pct: 0, + }, + ]; + + verify.lineCoverage(expected); + }); + + it('--config ../.solcover.js', async function() { + // Write solcoverjs to parent dir of sc_temp (where the test project is installed) + fs.writeFileSync( + '.solcover.js', + `module.exports=${JSON.stringify(solcoverConfig)}` + ); + + // This relative path has to be ./ prefixed (it's path.joined to buidler's paths.root) + const taskArgs = { + solcoverjs: './../.solcover.js' + }; + + mock.install('Simple', 'simple.js'); + mock.buidlerSetupEnv(this); + + await this.env.run("coverage", taskArgs); + + // The relative solcoverjs uses the json-summary reporter + const expected = [{ + file: mock.pathToContract(buidlerConfig, 'Simple.sol'), + pct: 100 + }]; + + verify.lineCoverage(expected); + shell.rm('.solcover.js'); + }); +}); + diff --git a/test/units/truffle/flags.js b/test/units/truffle/flags.js index 92757d5..5cef07c 100644 --- a/test/units/truffle/flags.js +++ b/test/units/truffle/flags.js @@ -198,20 +198,7 @@ describe('Truffle Plugin: command line options', function() { ); }); - it('--usePluginTruffle', async function(){ - truffleConfig.usePluginTruffle = true; - truffleConfig.logger = mock.testLogger; - - mock.install('Simple', 'simple.js', solcoverConfig); - await plugin(truffleConfig); - - assert( - mock.loggerOutput.val.includes('fallback Truffle library module'), - `Should notify it's using plugin truffle lib copy: ${mock.loggerOutput.val}` - ); - }); - - it('--coverageArtifacts', async function(){ + it('--temp', async function(){ truffleConfig.logger = mock.testLogger; truffleConfig.temp = 'special_location'; diff --git a/test/units/truffle/standard.js b/test/units/truffle/standard.js index 4b767bc..1597a38 100644 --- a/test/units/truffle/standard.js +++ b/test/units/truffle/standard.js @@ -156,7 +156,7 @@ describe('Truffle Plugin: standard use cases', function() { }); // Truffle test asserts deployment cost is greater than 20,000,000 gas - it('deployment cost > block gasLimit', async function() { + it.skip('deployment cost > block gasLimit', async function() { mock.install('Expensive', 'block-gas-limit.js', solcoverConfig); await plugin(truffleConfig); }); diff --git a/test/util/integration.js b/test/util/integration.js index a5313d8..8f37e5e 100644 --- a/test/util/integration.js +++ b/test/util/integration.js @@ -27,9 +27,17 @@ let previousCWD; // Misc Utils // ========================== function decacheConfigs(){ - decache(`${process.cwd()}/${temp}/.solcover.js`); - decache(`${process.cwd()}/${temp}/${truffleConfigName}`); - decache(`${process.cwd()}/${temp}/${buidlerConfigName}`); + const paths = [ + `${process.cwd()}/${temp}/.solcover.js`, + `${process.cwd()}/${temp}/${truffleConfigName}`, + `${process.cwd()}/${temp}/${buidlerConfigName}`, + `${process.cwd()}/${temp}/contracts/Simple.sol`, + `${process.cwd()}/${temp}/test/simple.js` + ]; + + paths.forEach(pth => { + try { decache(pth) } catch (e){} + }); } function clean() { @@ -65,8 +73,8 @@ function buidlerSetupEnv(mocha) { // Buidler env tear down function buidlerTearDownEnv() { - resetBuidlerContext(); - process.chdir(previousCWD); + resetBuidlerContext(); + process.chdir(previousCWD); };