const PluginUI = require('./truffle.ui'); const globalModules = require('global-modules'); const TruffleProvider = require('@truffle/provider'); const recursive = require('recursive-readdir'); const globby = require('globby'); const path = require('path'); // ============================= // Truffle Specific Plugin Utils // ============================== /** * Returns a list of test files to pass to mocha. * @param {Object} config truffleConfig * @return {String[]} list of files to pass to mocha */ async function getTestFilePaths(config){ let target; let ui = new PluginUI(config.logger.log); // Handle --file cli option (subset of tests) (typeof config.file === 'string') ? target = globby.sync([config.file]) : target = await recursive(config.testDir); // Filter native solidity tests and warn that they're skipped const solregex = /.*\.(sol)$/; const hasSols = target.filter(f => f.match(solregex) != null); if (hasSols.length > 0) ui.report('sol-tests', [hasSols.length]); // Return list of test files const testregex = /.*\.(js|ts|es|es6|jsx)$/; return target.filter(f => f.match(testregex) != null); } /** * Returns all Truffle artifacts. * @param {TruffleConfig} config * @return {Artifact[]} */ function getAllArtifacts(config){ const all = []; const artifactsGlob = path.join(config.artifactsDir, '/**/*.json'); const files = globby.sync([artifactsGlob]) for (const file of files){ const candidate = require(file); if (candidate.contractName && candidate.abi){ all.push(candidate); } } return all; } /** * Compiles project * Collects all artifacts from Truffle project, * Converts them to a format that can be consumed by api.abiUtils.diff * Saves them to `api.abiOutputPath` * @param {TruffleConfig} config * @param {TruffleAPI} truffle * @param {SolidityCoverageAPI} api */ async function generateHumanReadableAbiList(config, truffle, api){ await truffle.contracts.compile(config); const _artifacts = getAllArtifacts(config); const list = api.abiUtils.generateHumanReadableAbiList(_artifacts) api.saveHumanReadableAbis(list); } /** * Configures the network. Runs before the server is launched. * User can request a network from truffle-config with "--network ". * There are overlapiing options in solcoverjs (like port and providerOptions.network_id). * Where there are mismatches user is warned & the truffle network settings are preferred. * * Also generates a default config & sets the default gas high / gas price low. * * @param {TruffleConfig} config * @param {SolidityCoverage} api */ function setNetwork(config, api){ const ui = new PluginUI(config.logger.log); // --network if (config.network){ const network = config.networks[config.network]; // Check network: if (!network){ throw new Error(ui.generate('no-network', [config.network])); } // Check network id if (!isNaN(parseInt(network.network_id))){ // Warn: non-matching provider options id and network id if (api.providerOptions.network_id && api.providerOptions.network_id !== parseInt(network.network_id)){ ui.report('id-clash', [ parseInt(network.network_id) ]); } // Prefer network defined id. api.providerOptions.network_id = parseInt(network.network_id); } else { network.network_id = "*"; } // Check port: use solcoverjs || default if undefined if (!network.port) { ui.report('no-port', [api.port]); network.port = api.port; } // Warn: port conflicts if (api.port !== api.defaultPort && api.port !== network.port){ ui.report('port-clash', [ network.port ]) } // Prefer network port if defined; api.port = network.port; network.gas = api.gasLimit; network.gasPrice = api.gasPrice; setOuterConfigKeys(config, api, network.network_id); return; } // Default Network Configuration config.network = 'soliditycoverage'; setOuterConfigKeys(config, api, "*"); config.networks[config.network] = { network_id: "*", port: api.port, host: api.host, gas: api.gasLimit, gasPrice: api.gasPrice } } /** * Sets the default `from` account field in the truffle network that will be used. * This needs to be done after accounts are fetched from the launched client. * @param {TruffleConfig} config * @param {Array} accounts */ function setNetworkFrom(config, accounts){ if (!config.networks[config.network].from){ config.networks[config.network].from = accounts[0]; } } // Truffle complains that these outer keys *are not* set when running plugin fn directly. // But throws saying they *cannot* be manually set when running as truffle command. function setOuterConfigKeys(config, api, id){ try { config.network_id = id; config.port = api.port; config.host = api.host; config.provider = TruffleProvider.create(config); } catch (err){} } /** * Tries to load truffle module library and reports source. User can force use of * a non-local version using cli flags (see option). Load order is: * * 1. local node_modules * 2. global node_modules * * @param {Object} truffleConfig config * @return {Module} */ function loadLibrary(config){ const ui = new PluginUI(config.logger.log); // Local try { if (config.useGlobalTruffle) throw null; const lib = require("truffle"); ui.report('lib-local'); return lib; } catch(err) {}; // Global try { if (config.forceLibFailure) throw null; // For err unit testing const globalTruffle = path.join(globalModules, 'truffle'); const lib = require(globalTruffle); ui.report('lib-global'); return lib; } catch(err) { throw new Error(ui.generate('lib-fail', [err])); }; } /** * Maps truffle specific keys for the paths to things like sources to the generic * keys required by the plugin utils * @return {Object} truffle-config.js */ function normalizeConfig(config){ config.workingDir = config.working_directory; config.contractsDir = config.contracts_directory; config.testDir = config.test_directory; config.artifactsDir = config.build_directory; // eth-gas-reporter freezes the in-process client because it uses sync calls if (typeof config.mocha === "object" && config.mocha.reporter === 'eth-gas-reporter'){ config.mocha.reporter = 'spec'; delete config.mocha.reporterOptions; } // Truffle V4 style solc settings are honored over V5 settings. Apparently it's common // for both to be present in the same config (as an error). if (typeof config.solc === "object" ){ config.solc.optimizer = { enabled: false }; } return config; } /** * Configures mocha to generate a json object which maps which tests * hit which lines of code. */ function collectTestMatrixData(config, api){ if (config.matrix){ config.mocha = config.mocha || {}; config.mocha.reporter = api.matrixReporterPath; config.mocha.reporterOptions = { collectTestMatrixData: api.collectTestMatrixData.bind(api), saveMochaJsonOutput: api.saveMochaJsonOutput.bind(api), cwd: api.cwd } } } /** * Replacement logger which filters out compilation warnings triggered by injected trace * function definitions. * * @type {Object} */ const filteredLogger = { log: (val) => { const loggable = typeof val === 'string' && !val.includes('Warning:') && // solc msg grep !process.env.SILENT; // unit tests loggable && console.log(val); }, warn: console.warn, error: console.error } module.exports = { getTestFilePaths, setNetwork, setNetworkFrom, loadLibrary, normalizeConfig, filteredLogger, collectTestMatrixData, generateHumanReadableAbiList }