diff --git a/dist/buidler.plugin.js b/dist/buidler.plugin.js index e69de29..7fe3934 100644 --- a/dist/buidler.plugin.js +++ b/dist/buidler.plugin.js @@ -0,0 +1,164 @@ +const API = require('./../lib/api'); +const utils = require('./plugin-assets/plugin.utils'); +const buidlerUtils = require('./plugin-assets/buidler.utils'); +const PluginUI = require('./plugin-assets/buidler.ui'); + +const pkg = require('./../package.json'); +const death = require('death'); +const path = require('path'); +const Web3 = require('web3'); +const ganache = require('ganache-cli'); + +const { task, internalTask, types } = require("@nomiclabs/buidler/config"); +const { ensurePluginLoadedWithUsePlugin } = require("@nomiclabs/buidler/plugins"); +const { BuidlerPluginError } = require("@nomiclabs/buidler/internal/core/errors"); +const { createProvider } = require("@nomiclabs/buidler/internal/core/providers/construction"); + +const { + TASK_TEST_RUN_MOCHA_TESTS, + TASK_TEST, + TASK_COMPILE, +} = require("@nomiclabs/buidler/builtin-tasks/task-names"); + +const util = require('util'); + +function plugin() { + let api; + let address; + let network; + let error; + let testsErrored = false; + + const ui = new PluginUI(); + + extendEnvironment(env => { + env.config.logger = {log: null}; + env.config = buidlerUtils.normalizeConfig(env.config); + + api = new API(utils.loadSolcoverJS(env.config)); + + const networkConfig = { + url: `http://${api.host}:${api.port}`, + gas: api.gasLimit, + gasPrice: api.gasPrice + } + + const provider = createProvider(api.defaultNetworkName, networkConfig); + + env.config.networks[api.defaultNetworkName] = networkConfig; + env.config.defaultNetwork = api.defaultNetworkName; + + env.network = { + name: api.defaultNetworkName, + config: networkConfig, + provider: provider, + } + + env.ethereum = provider; + + // Keep a reference so we can set the from account + network = env.network; + }) + + function myTimeout(){ + return new Promise(resolve => { + setTimeout(()=>{ + console.log('TIMEOUT') + }, 2000) + }) + } + + + task("timeout", 'desc').setAction(async function(taskArguments, { config }, runSuper){ + await myTimeout(); + }); + + task("coverage", "Generates a code coverage report for tests") + + .addOptionalParam("file", 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, { run, config }, runSuper){ + console.log(util.inspect()) + try { + death(buidlerUtils.finish.bind(null, config, api)); // Catch interrupt signals + config.logger = {log: null}; + config = buidlerUtils.normalizeConfig(config); + + api = new API(utils.loadSolcoverJS(config)); + + // Server launch + const address = await api.ganache(ganache); + const web3 = new Web3(address); + const accounts = await web3.eth.getAccounts(); + const nodeInfo = await web3.eth.getNodeInfo(); + const ganacheVersion = nodeInfo.split('/')[1]; + + // Set default account + network.from = accounts[0]; + + // Version Info + ui.report('versions', [ + ganacheVersion, + pkg.version + ]); + + // Network Info + ui.report('network', [ + api.defaultNetworkName, + api.port + ]); + + // Run post-launch server hook; + await api.onServerReady(config); + + // Instrument + const skipFiles = api.skipFiles || []; + + let { + targets, + skipped + } = utils.assembleFiles(config, skipFiles); + + targets = api.instrument(targets); + utils.reportSkipped(config, skipped); + + // Filesystem & Compiler Re-configuration + const { + tempArtifactsDir, + tempContractsDir + } = utils.getTempLocations(config); + + utils.save(targets, config.paths.sources, tempContractsDir); + utils.save(skipped, config.paths.sources, tempContractsDir); + + config.paths.sources = tempContractsDir; + config.paths.artifacts = tempArtifactsDir; + config.paths.cache = buidlerUtils.tempCacheDir(config); + console.log(config.paths.cache) + + config.solc.optimizer.enabled = false; + await run(TASK_COMPILE); + + await api.onCompileComplete(config); + + try { + failures = await run(TASK_TEST, {testFiles: []}) + } catch (e) { + error = e.stack; + console.log(e.message + error) + } + await api.onTestsComplete(config); + + // Run Istanbul + await api.report(); + await api.onIstanbulComplete(config); + } catch(e) { + error = e; + } + }) +} + +module.exports = plugin; + diff --git a/dist/plugin-assets/buidler.ui.js b/dist/plugin-assets/buidler.ui.js new file mode 100644 index 0000000..78c39a3 --- /dev/null +++ b/dist/plugin-assets/buidler.ui.js @@ -0,0 +1,83 @@ +const UI = require('./../../lib/ui').UI; + +/** + * Truffle Plugin logging + */ +class PluginUI extends UI { + constructor(log){ + super(log); + + this.flags = { + file: `Path (or glob) defining a subset of tests to run`, + + solcoverjs: `Relative path from working directory to config. ` + + `Useful for monorepo packages that share settings.`, + + temp: `Path to a disposable folder to store compilation artifacts in. ` + + `Useful when your test setup scripts include hard-coded paths to ` + + `a build directory.`, + + } + } + + /** + * Writes a formatted message via log + * @param {String} kind message selector + * @param {String[]} args info to inject into template + */ + report(kind, args=[]){ + const c = this.chalk; + const ct = c.bold.green('>'); + const ds = c.bold.yellow('>'); + const w = ":warning:"; + + const kinds = { + + 'instr-skip': `\n${c.bold('Coverage skipped for:')}` + + `\n${c.bold('=====================')}\n`, + + 'instr-skipped': `${ds} ${c.grey(args[0])}`, + + 'versions': `${ct} ${c.bold('ganache-core')}: ${args[0]}\n` + + `${ct} ${c.bold('solidity-coverage')}: v${args[1]}`, + + 'network': `\n${c.bold('Network Info')}` + + `\n${c.bold('============')}\n` + + `${ct} ${c.bold('port')}: ${args[1]}\n` + + `${ct} ${c.bold('network')}: ${args[0]}\n`, + } + + this._write(kinds[kind]); + } + + /** + * Returns a formatted message. Useful for error message. + * @param {String} kind message selector + * @param {String[]} args info to inject into template + * @return {String} message + */ + generate(kind, args=[]){ + const c = this.chalk; + const x = ":x:"; + + const kinds = { + + 'sources-fail': `${c.red('Cannot locate expected contract sources folder: ')} ${args[0]}`, + + 'solcoverjs-fail': `${c.red('Could not load .solcover.js config file. ')}` + + `${c.red('This can happen if it has a syntax error or ')}` + + `${c.red('the path you specified for it is wrong.')}`, + + '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. ')}`, + + } + + + return this._format(kinds[kind]) + } +} + +module.exports = PluginUI; \ No newline at end of file diff --git a/dist/plugin-assets/buidler.utils.js b/dist/plugin-assets/buidler.utils.js new file mode 100644 index 0000000..34382ae --- /dev/null +++ b/dist/plugin-assets/buidler.utils.js @@ -0,0 +1,45 @@ +const shell = require('shelljs'); +const pluginUtils = require("./plugin.utils"); +const path = require('path') +const util = require('util') + +function normalizeConfig(config){ + config.workingDir = config.paths.root; + config.contractsDir = config.paths.sources; + config.testDir = config.paths.tests; + config.artifactsDir = config.paths.artifacts; + + return config; +} + +function tempCacheDir(config){ + return path.join(config.paths.root, '.coverage_cache'); +} + +/** + * Silently removes temporary folders and calls api.finish to shut server down + * @param {buidlerConfig} config + * @param {SolidityCoverage} api + * @return {Promise} + */ +async function finish(config, api){ + const { + tempContractsDir, + tempArtifactsDir + } = pluginUtils.getTempLocations(config); + + shell.config.silent = true; + shell.rm('-Rf', tempContractsDir); + shell.rm('-Rf', tempArtifactsDir); + shell.rm('-Rf', path.join(config.paths.root, '.coverage_cache')); + shell.config.silent = false; + + if (api) await api.finish(); +} + +module.exports = { + normalizeConfig: normalizeConfig, + finish: finish, + tempCacheDir: tempCacheDir +} + diff --git a/dist/plugin-assets/plugin.utils.js b/dist/plugin-assets/plugin.utils.js index cabbf0c..d4162ee 100644 --- a/dist/plugin-assets/plugin.utils.js +++ b/dist/plugin-assets/plugin.utils.js @@ -11,6 +11,7 @@ const PluginUI = require('./truffle.ui'); const path = require('path'); const fs = require('fs-extra'); const shell = require('shelljs'); +const util = require('util') // === // UI @@ -193,7 +194,6 @@ function loadSolcoverJS(config){ let coverageConfig; let ui = new PluginUI(config.logger.log); - // Handle --solcoverjs flag (config.solcoverjs) ? solcoverjs = path.join(config.workingDir, config.solcoverjs) @@ -251,6 +251,7 @@ async function finish(config, api){ shell.config.silent = true; shell.rm('-Rf', tempContractsDir); shell.rm('-Rf', tempArtifactsDir); + shell.config.silent = false; if (api) await api.finish(); } diff --git a/lib/api.js b/lib/api.js index 14aa580..4ed9d4c 100644 --- a/lib/api.js +++ b/lib/api.js @@ -30,7 +30,6 @@ class API { this.testsErrored = false; this.cwd = config.cwd || process.cwd(); - this.originalContractsDir = config.originalContractsDir this.defaultHook = () => {}; this.onServerReady = config.onServerReady || this.defaultHook; @@ -41,12 +40,12 @@ class API { this.server = null; this.provider = null; this.defaultPort = 8555; + this.defaultNetworkName = 'soliditycoverage'; this.client = config.client; this.port = config.port || this.defaultPort; this.host = config.host || "127.0.0.1"; this.providerOptions = config.providerOptions || {}; - this.skipFiles = config.skipFiles || []; this.log = config.log || console.log; @@ -167,10 +166,7 @@ class API { return new Promise((resolve, reject) => { try { - this.coverage.generate( - this.instrumenter.instrumentationData, - this.originalContractsDir - ); + this.coverage.generate(this.instrumenter.instrumentationData); const mapping = this.makeKeysRelative(this.coverage.data, this.cwd); this.saveCoverage(mapping); @@ -230,7 +226,7 @@ class API { } // NB: EADDRINUSE errors are uncatch-able? - pify(this.server.listen)(this.port); + await pify(this.server.listen)(this.port); } assertHasBlockchain(provider){ diff --git a/lib/validator.js b/lib/validator.js index cdc5b62..1b22403 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -15,8 +15,6 @@ const configSchema = { cwd: {type: "string"}, host: {type: "string"}, - - originalContractsDir: {type: "string"}, port: {type: "number"}, providerOptions: {type: "object"}, silent: {type: "boolean"}, diff --git a/test/units/buidler/standard.js b/test/units/buidler/standard.js new file mode 100644 index 0000000..42d4818 --- /dev/null +++ b/test/units/buidler/standard.js @@ -0,0 +1,62 @@ +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'); + +const { + TASK_TEST +} = require("@nomiclabs/buidler/builtin-tasks/task-names"); + +// ======================= +// Standard Use-case Tests +// ======================= + +describe.only('Buidler Plugin: standard use cases', function() { + let buidlerConfig; + let solcoverConfig; + + beforeEach(() => { + mock.clean(); + + mock.loggerOutput.val = ''; + solcoverConfig = {}; + buidlerConfig = mock.getDefaultBuidlerConfig(); + verify.cleanInitialState(); + }) + + afterEach(() => { + mock.buidlerTearDownEnv(); + mock.clean(); + }); + + /*it.only('timeout', async function(){ + mock.buidlerSetupEnv(this); + this.env.run('timeout') + });*/ + + it('simple contract', async function(){ + mock.install('Simple', 'simple.js', solcoverConfig); + mock.buidlerSetupEnv(this); + + await this.env.run("coverage"); + + /*verify.coverageGenerated(buidlerConfig); + + const output = mock.getOutput(buidlerConfig); + 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"' + );*/ + }); +}) \ No newline at end of file diff --git a/test/units/validator.js b/test/units/validator.js index 14335fc..cc9aee4 100644 --- a/test/units/validator.js +++ b/test/units/validator.js @@ -22,7 +22,6 @@ describe('config validation', () => { const options = [ "cwd", "host", - "originalContractsDir", ] options.forEach(name => { diff --git a/test/util/integration.js b/test/util/integration.js index 340eab0..dcdaadb 100644 --- a/test/util/integration.js +++ b/test/util/integration.js @@ -8,8 +8,8 @@ const fs = require('fs'); const shell = require('shelljs'); const decache = require('decache'); -const PluginsTestHelpers = require("@nomiclabs/buidler/plugins-testing") const TruffleConfig = require('truffle-config'); +const { resetBuidlerContext } = require("@nomiclabs/buidler/plugins-testing") const temp = './sc_temp'; const truffleConfigName = 'truffle-config.js'; @@ -21,6 +21,7 @@ const migrationPath = `${temp}/migrations/2_deploy.js`; const templatePath = './test/integration/generic/*'; const projectPath = './test/integration/projects/' +let previousCWD; // ========================== // Misc Utils @@ -51,22 +52,20 @@ function getOutput(truffleConfig){ return JSON.parse(fs.readFileSync(jsonPath, 'utf8')); } -// Buidler env set up / tear down. -function useEnvironment(projectPath) { - let previousCWD; - - beforeEach("Loading buidler environment", function() { - previousCWD = process.cwd(); - process.chdir(projectPath); - process.env.BUIDLER_NETWORK = "develop"; - this.env = require("@nomiclabs/buidler"); - }); +// Buidler env set up +function buidlerSetupEnv(mocha) { + const mockwd = path.join(process.cwd(), temp); + previousCWD = process.cwd(); + process.chdir(mockwd); + mocha.env = require("@nomiclabs/buidler"); +}; - afterEach("Resetting buidler", function() { - PluginsTestHelpers.resetBuidlerContext(); +// Buidler env tear down +function buidlerTearDownEnv() { + resetBuidlerContext(); process.chdir(previousCWD); - }); -} +}; + // ========================== // Truffle Configuration @@ -144,10 +143,16 @@ function getDefaultBuidlerConfig() { } function getBuidlerConfigJS(config){ + const prefix =` + const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); + loadPluginFile(__dirname + "/../dist/buidler.plugin"); + usePlugin("@nomiclabs/buidler-truffle5"); + ` + if (config) { - return `module.exports = ${JSON.stringify(config, null, ' ')}` + return `${prefix}module.exports = ${JSON.stringify(config, null, ' ')}`; } else { - return `module.exports = ${JSON.stringify(getDefaultBuidlerConfig(), null, ' ')}` + return `${prefix}module.exports = ${JSON.stringify(getDefaultBuidlerConfig(), null, ' ')}`; } } @@ -297,6 +302,8 @@ module.exports = { installFullProject: installFullProject, clean: clean, pathToContract: pathToContract, - getOutput: getOutput + getOutput: getOutput, + buidlerSetupEnv: buidlerSetupEnv, + buidlerTearDownEnv: buidlerTearDownEnv }