Refactor instrument method (#406)
* Move all filesystem & filtering logic to plugins * Move plugin helpers to own filepull/407/head
parent
37b54ccf12
commit
7e9be6c3c2
@ -0,0 +1,460 @@ |
|||||||
|
/** |
||||||
|
* A collection of utilities for common tasks plugins will need in the course |
||||||
|
* of composing a workflow using the solidity-coverage API |
||||||
|
* |
||||||
|
* TODO: Sweep back through here and make all `config.truffle_variable` plugin |
||||||
|
* platform neutral... |
||||||
|
*/ |
||||||
|
|
||||||
|
const PluginUI = require('./truffle.ui'); |
||||||
|
|
||||||
|
const path = require('path'); |
||||||
|
const fs = require('fs-extra'); |
||||||
|
const dir = require('node-dir'); |
||||||
|
const globby = require('globby'); |
||||||
|
const shell = require('shelljs'); |
||||||
|
const globalModules = require('global-modules'); |
||||||
|
const TruffleProvider = require('@truffle/provider'); |
||||||
|
|
||||||
|
// ===
|
||||||
|
// UI
|
||||||
|
// ===
|
||||||
|
|
||||||
|
/** |
||||||
|
* Displays a list of skipped contracts |
||||||
|
* @param {TruffleConfig} config |
||||||
|
* @return {Object[]} skipped array of objects generated by `assembleTargets` method |
||||||
|
*/ |
||||||
|
function reportSkipped(config, skipped=[]){ |
||||||
|
let started = false; |
||||||
|
const ui = new PluginUI(config.logger.log); |
||||||
|
|
||||||
|
for (let item of skipped){ |
||||||
|
if (!started) { |
||||||
|
ui.report('instr-skip', []); |
||||||
|
started = true; |
||||||
|
} |
||||||
|
ui.report('instr-skipped', [item.relativePath]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ========
|
||||||
|
// File I/O
|
||||||
|
// ========
|
||||||
|
|
||||||
|
/** |
||||||
|
* Loads source |
||||||
|
* @param {String} _path absolute path |
||||||
|
* @return {String} source file |
||||||
|
*/ |
||||||
|
function loadSource(_path){ |
||||||
|
return fs.readFileSync(_path).toString(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Save a set of instrumented files to a temporary directory. |
||||||
|
* @param {Object[]} targets array of targets generated by `assembleTargets` |
||||||
|
* @param {[type]} originalDir absolute path to parent directory of original source |
||||||
|
* @param {[type]} tempDir absolute path to temp parent directory |
||||||
|
*/ |
||||||
|
function save(targets, originalDir, tempDir){ |
||||||
|
let _path; |
||||||
|
for (target of targets) { |
||||||
|
_path = target.canonicalPath.replace(originalDir, tempDir); |
||||||
|
fs.outputFileSync(_path, target.source); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Relativizes an absolute file path, given an absolute parent path |
||||||
|
* @param {String} pathToFile |
||||||
|
* @param {String} pathToParent |
||||||
|
* @return {String} relative path |
||||||
|
*/ |
||||||
|
function toRelativePath(pathToFile, pathToParent){ |
||||||
|
return pathToFile.replace(`${pathToParent}${path.sep}`, ''); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a pair of canonically named temporary directory paths for contracts |
||||||
|
* and artifacts. Instrumented assets can be written & compiled to these. |
||||||
|
* Then the unit tests can be run, consuming them as sources. |
||||||
|
* @param {TruffleConfig} config |
||||||
|
* @return {Object} temp paths |
||||||
|
*/ |
||||||
|
function getTempLocations(config){ |
||||||
|
const cwd = config.working_directory; |
||||||
|
const contractsDirName = '.coverage_contracts'; |
||||||
|
const artifactsDirName = '.coverage_artifacts'; |
||||||
|
|
||||||
|
return { |
||||||
|
tempContractsDir: path.join(cwd, contractsDirName), |
||||||
|
tempArtifactsDir: path.join(cwd, artifactsDirName) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks for existence of contract sources, and sweeps away debris |
||||||
|
* left over from an uncontrolled crash. |
||||||
|
*/ |
||||||
|
function checkContext(config, tempContractsDir, tempArtifactsDir){ |
||||||
|
const ui = new PluginUI(config.logger.log); |
||||||
|
|
||||||
|
if (!shell.test('-e', config.contracts_directory)){ |
||||||
|
|
||||||
|
const msg = ui.generate('sources-fail', [config.contracts_directory]) |
||||||
|
throw new Error(msg); |
||||||
|
} |
||||||
|
|
||||||
|
if (shell.test('-e', tempContractsDir)){ |
||||||
|
shell.rm('-Rf', tempContractsDir); |
||||||
|
} |
||||||
|
|
||||||
|
if (shell.test('-e', tempArtifactsDir)){ |
||||||
|
shell.rm('-Rf', tempArtifactsDir); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// Instrumentation Set Assembly
|
||||||
|
// =============================
|
||||||
|
|
||||||
|
function assembleFiles(config, skipFiles=[]){ |
||||||
|
let targets; |
||||||
|
let skipFolders; |
||||||
|
let skipped = []; |
||||||
|
|
||||||
|
const { |
||||||
|
tempContractsDir, |
||||||
|
tempArtifactsDir |
||||||
|
} = getTempLocations(config); |
||||||
|
|
||||||
|
checkContext(config, tempContractsDir, tempArtifactsDir); |
||||||
|
|
||||||
|
shell.mkdir(tempContractsDir); |
||||||
|
shell.mkdir(tempArtifactsDir); |
||||||
|
|
||||||
|
targets = shell.ls(`${config.contracts_directory}/**/*.sol`); |
||||||
|
|
||||||
|
skipFiles = assembleSkipped(config, targets, skipFiles); |
||||||
|
|
||||||
|
return assembleTargets(config, targets, skipFiles) |
||||||
|
} |
||||||
|
|
||||||
|
function assembleTargets(config, targets=[], skipFiles=[]){ |
||||||
|
const skipped = []; |
||||||
|
const filtered = []; |
||||||
|
const cd = config.contracts_directory; |
||||||
|
|
||||||
|
for (let target of targets){ |
||||||
|
if (skipFiles.includes(target)){ |
||||||
|
|
||||||
|
skipped.push({ |
||||||
|
canonicalPath: target, |
||||||
|
relativePath: toRelativePath(target, cd), |
||||||
|
source: loadSource(target) |
||||||
|
}) |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
filtered.push({ |
||||||
|
canonicalPath: target, |
||||||
|
relativePath: toRelativePath(target, cd), |
||||||
|
source: loadSource(target) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
skipped: skipped, |
||||||
|
targets: filtered |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses the skipFiles option (which also accepts folders) |
||||||
|
*/ |
||||||
|
function assembleSkipped(config, targets, skipFiles=[]){ |
||||||
|
const cd = config.contracts_directory; |
||||||
|
|
||||||
|
// Make paths absolute
|
||||||
|
skipFiles = skipFiles.map(contract => `${cd}/${contract}`); |
||||||
|
skipFiles.push(`${cd}/Migrations.sol`); |
||||||
|
|
||||||
|
// Enumerate files in skipped folders
|
||||||
|
const skipFolders = skipFiles.filter(item => path.extname(item) !== '.sol') |
||||||
|
|
||||||
|
for (let folder of skipFolders){ |
||||||
|
for (let target of targets ) { |
||||||
|
if (target.indexOf(folder) === 0) |
||||||
|
skipFiles.push(target); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return skipFiles; |
||||||
|
} |
||||||
|
|
||||||
|
// ========
|
||||||
|
// Truffle
|
||||||
|
// ========
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a list of test files to pass to mocha. |
||||||
|
* @param {Object} config truffleConfig |
||||||
|
* @return {String[]} list of files to pass to mocha |
||||||
|
*/ |
||||||
|
function getTestFilePaths(config){ |
||||||
|
let target; |
||||||
|
let ui = new PluginUI(config.logger.log); |
||||||
|
|
||||||
|
|
||||||
|
// Handle --file <path|glob> cli option (subset of tests)
|
||||||
|
(typeof config.file === 'string') |
||||||
|
? target = globby.sync([config.file]) |
||||||
|
: target = dir.files(config.test_directory, { sync: true }) || []; |
||||||
|
|
||||||
|
// 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); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Configures the network. Runs before the server is launched. |
||||||
|
* User can request a network from truffle-config with "--network <name>". |
||||||
|
* 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 <network-name>
|
||||||
|
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). It's necessary to maintain |
||||||
|
* a fail-safe lib because feature was only introduced in 5.0.30. Load order is: |
||||||
|
* |
||||||
|
* 1. local node_modules |
||||||
|
* 2. global node_modules |
||||||
|
* 3. fail-safe (truffle lib v 5.0.31 at ./plugin-assets/truffle.library) |
||||||
|
* |
||||||
|
* @param {Object} truffleConfig config |
||||||
|
* @return {Module} |
||||||
|
*/ |
||||||
|
function loadTruffleLibrary(config){ |
||||||
|
const ui = new PluginUI(config.logger.log); |
||||||
|
|
||||||
|
// Local
|
||||||
|
try { |
||||||
|
if (config.useGlobalTruffle || config.usePluginTruffle) throw null; |
||||||
|
|
||||||
|
const lib = require("truffle"); |
||||||
|
ui.report('lib-local'); |
||||||
|
return lib; |
||||||
|
|
||||||
|
} catch(err) {}; |
||||||
|
|
||||||
|
// Global
|
||||||
|
try { |
||||||
|
if (config.usePluginTruffle) throw null; |
||||||
|
|
||||||
|
const globalTruffle = path.join(globalModules, 'truffle'); |
||||||
|
const lib = require(globalTruffle); |
||||||
|
ui.report('lib-global'); |
||||||
|
return lib; |
||||||
|
|
||||||
|
} catch(err) {}; |
||||||
|
|
||||||
|
// Plugin Copy @ v 5.0.31
|
||||||
|
try { |
||||||
|
if (config.forceLibFailure) throw null; // For err unit testing
|
||||||
|
|
||||||
|
ui.report('lib-warn'); |
||||||
|
return require("./truffle.library") |
||||||
|
|
||||||
|
} catch(err) { |
||||||
|
throw new Error(ui.generate('lib-fail', [err])); |
||||||
|
}; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function loadSolcoverJS(config){ |
||||||
|
let solcoverjs; |
||||||
|
let coverageConfig; |
||||||
|
let ui = new PluginUI(config.logger.log); |
||||||
|
|
||||||
|
|
||||||
|
// Handle --solcoverjs flag
|
||||||
|
(config.solcoverjs) |
||||||
|
? solcoverjs = path.join(config.working_directory, config.solcoverjs) |
||||||
|
: solcoverjs = path.join(config.working_directory, '.solcover.js'); |
||||||
|
|
||||||
|
// Catch solcoverjs syntax errors
|
||||||
|
if (shell.test('-e', solcoverjs)){ |
||||||
|
|
||||||
|
try { |
||||||
|
coverageConfig = require(solcoverjs); |
||||||
|
} catch(error){ |
||||||
|
error.message = ui.generate('solcoverjs-fail') + error.message; |
||||||
|
throw new Error(error) |
||||||
|
} |
||||||
|
|
||||||
|
// Config is optional
|
||||||
|
} else { |
||||||
|
coverageConfig = {}; |
||||||
|
} |
||||||
|
|
||||||
|
// Truffle writes to coverage config
|
||||||
|
coverageConfig.log = config.logger.log; |
||||||
|
coverageConfig.cwd = config.working_directory; |
||||||
|
coverageConfig.originalContractsDir = config.contracts_directory; |
||||||
|
|
||||||
|
// Solidity-Coverage writes to Truffle config
|
||||||
|
config.mocha = config.mocha || {}; |
||||||
|
|
||||||
|
if (coverageConfig.mocha && typeof coverageConfig.mocha === 'object'){ |
||||||
|
config.mocha = Object.assign( |
||||||
|
config.mocha, |
||||||
|
coverageConfig.mocha |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return coverageConfig; |
||||||
|
} |
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Finishing / Cleanup
|
||||||
|
// ==========================
|
||||||
|
|
||||||
|
/** |
||||||
|
* Silently removes temporary folders and calls api.finish to shut server down |
||||||
|
* @param {TruffleConfig} config |
||||||
|
* @param {SolidityCoverage} api |
||||||
|
* @return {Promise} |
||||||
|
*/ |
||||||
|
async function finish(config, api){ |
||||||
|
const { |
||||||
|
tempContractsDir, |
||||||
|
tempArtifactsDir |
||||||
|
} = getTempLocations(config); |
||||||
|
|
||||||
|
shell.config.silent = true; |
||||||
|
shell.rm('-Rf', tempContractsDir); |
||||||
|
shell.rm('-Rf', tempArtifactsDir); |
||||||
|
|
||||||
|
if (api) await api.finish(); |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
assembleFiles: assembleFiles, |
||||||
|
assembleSkipped: assembleSkipped, |
||||||
|
assembleTargets: assembleTargets, |
||||||
|
checkContext: checkContext, |
||||||
|
finish: finish, |
||||||
|
getTempLocations: getTempLocations, |
||||||
|
getTestFilePaths: getTestFilePaths, |
||||||
|
loadSource: loadSource, |
||||||
|
loadSolcoverJS: loadSolcoverJS, |
||||||
|
loadTruffleLibrary: loadTruffleLibrary, |
||||||
|
reportSkipped: reportSkipped, |
||||||
|
save: save, |
||||||
|
setNetwork: setNetwork, |
||||||
|
setNetworkFrom: setNetworkFrom, |
||||||
|
setOuterConfigKeys: setOuterConfigKeys, |
||||||
|
checkContext: checkContext, |
||||||
|
toRelativePath: toRelativePath |
||||||
|
} |
@ -1,326 +1,114 @@ |
|||||||
const App = require('./../lib/app'); |
const API = require('./../lib/api'); |
||||||
|
const utils = require('./plugin-assets/plugin.utils'); |
||||||
const PluginUI = require('./plugin-assets/truffle.ui'); |
const PluginUI = require('./plugin-assets/truffle.ui'); |
||||||
|
|
||||||
const pkg = require('./../package.json'); |
const pkg = require('./../package.json'); |
||||||
const req = require('req-cwd'); |
|
||||||
const death = require('death'); |
const death = require('death'); |
||||||
const path = require('path'); |
const path = require('path'); |
||||||
const dir = require('node-dir'); |
|
||||||
const Web3 = require('web3'); |
const Web3 = require('web3'); |
||||||
const util = require('util'); |
|
||||||
const globby = require('globby'); |
|
||||||
const shell = require('shelljs'); |
|
||||||
const globalModules = require('global-modules'); |
|
||||||
const TruffleProvider = require('@truffle/provider'); |
|
||||||
|
|
||||||
/** |
/** |
||||||
* Truffle Plugin: `truffle run coverage [options]` |
* Truffle Plugin: `truffle run coverage [options]` |
||||||
* @param {Object} truffleConfig @truffle/config config |
* @param {Object} config @truffle/config config |
||||||
* @return {Promise} |
* @return {Promise} |
||||||
*/ |
*/ |
||||||
async function plugin(truffleConfig){ |
async function plugin(config){ |
||||||
let ui; |
let ui; |
||||||
let app; |
let api; |
||||||
let error; |
let error; |
||||||
let truffle; |
let truffle; |
||||||
let solcoverjs; |
|
||||||
let testsErrored = false; |
let testsErrored = false; |
||||||
|
|
||||||
// Separate try block because this logic
|
|
||||||
// runs before app.cleanUp is defined.
|
|
||||||
try { |
try { |
||||||
ui = new PluginUI(truffleConfig.logger.log); |
death(utils.finish.bind(null, config, api)); // Catch interrupt signals
|
||||||
|
|
||||||
if(truffleConfig.help) return ui.report('help'); // Exit if --help
|
|
||||||
|
|
||||||
truffle = loadTruffleLibrary(ui, truffleConfig); |
ui = new PluginUI(config.logger.log); |
||||||
app = new App(loadSolcoverJS(ui, truffleConfig)); |
|
||||||
|
|
||||||
} catch (err) { throw err } |
if(config.help) return ui.report('help'); // Exit if --help
|
||||||
|
|
||||||
try { |
truffle = utils.loadTruffleLibrary(config); |
||||||
// Catch interrupt signals
|
api = new API(utils.loadSolcoverJS(config)); |
||||||
death(app.cleanUp); |
|
||||||
|
|
||||||
setNetwork(ui, app, truffleConfig); |
utils.setNetwork(config, api); |
||||||
|
|
||||||
// Provider / Server launch
|
// Server launch
|
||||||
const address = await app.ganache(truffle.ganache); |
const address = await api.ganache(truffle.ganache); |
||||||
|
|
||||||
const web3 = new Web3(address); |
const web3 = new Web3(address); |
||||||
const accounts = await web3.eth.getAccounts(); |
const accounts = await web3.eth.getAccounts(); |
||||||
const nodeInfo = await web3.eth.getNodeInfo(); |
const nodeInfo = await web3.eth.getNodeInfo(); |
||||||
const ganacheVersion = nodeInfo.split('/')[1]; |
const ganacheVersion = nodeInfo.split('/')[1]; |
||||||
|
|
||||||
setNetworkFrom(truffleConfig, accounts); |
utils.setNetworkFrom(config, accounts); |
||||||
|
|
||||||
// Version Info
|
// Version Info
|
||||||
ui.report('truffle-version', [truffle.version]); |
ui.report('versions', [ |
||||||
ui.report('ganache-version', [ganacheVersion]); |
truffle.version, |
||||||
ui.report('coverage-version',[pkg.version]); |
ganacheVersion, |
||||||
|
pkg.version |
||||||
|
]); |
||||||
|
|
||||||
if (truffleConfig.version) return app.cleanUp(); // Exit if --version
|
// Exit if --version
|
||||||
|
if (config.version) return await utils.finish(config, api); |
||||||
|
|
||||||
ui.report('network', [ |
ui.report('network', [ |
||||||
truffleConfig.network, |
config.network, |
||||||
truffleConfig.networks[truffleConfig.network].network_id, |
config.networks[config.network].network_id, |
||||||
truffleConfig.networks[truffleConfig.network].port |
config.networks[config.network].port |
||||||
]); |
]); |
||||||
|
|
||||||
// Instrument
|
// Instrument
|
||||||
app.sanityCheckContext(); |
let { |
||||||
app.generateStandardEnvironment(); |
targets, |
||||||
app.instrument(); |
skipped |
||||||
|
} = utils.assembleFiles(config, api.skipFiles); |
||||||
|
|
||||||
|
targets = api.instrument(targets); |
||||||
|
utils.reportSkipped(config, skipped); |
||||||
|
|
||||||
// Filesystem & Compiler Re-configuration
|
// Filesystem & Compiler Re-configuration
|
||||||
truffleConfig.contracts_directory = app.contractsDir; |
const { |
||||||
truffleConfig.build_directory = app.artifactsDir; |
tempArtifactsDir, |
||||||
|
tempContractsDir |
||||||
|
} = utils.getTempLocations(config); |
||||||
|
|
||||||
|
utils.save(targets, config.contracts_directory, tempContractsDir); |
||||||
|
utils.save(skipped, config.contracts_directory, tempContractsDir); |
||||||
|
|
||||||
truffleConfig.contracts_build_directory = path.join( |
config.contracts_directory = tempContractsDir; |
||||||
app.artifactsDir, |
config.build_directory = tempArtifactsDir; |
||||||
path.basename(truffleConfig.contracts_build_directory) |
|
||||||
|
config.contracts_build_directory = path.join( |
||||||
|
tempArtifactsDir, |
||||||
|
path.basename(config.contracts_build_directory) |
||||||
); |
); |
||||||
|
|
||||||
truffleConfig.all = true; |
config.all = true; |
||||||
truffleConfig.test_files = getTestFilePaths(ui, truffleConfig); |
config.test_files = utils.getTestFilePaths(config); |
||||||
truffleConfig.compilers.solc.settings.optimizer.enabled = false; |
config.compilers.solc.settings.optimizer.enabled = false; |
||||||
|
|
||||||
// Compile Instrumented Contracts
|
// Compile Instrumented Contracts
|
||||||
await truffle.contracts.compile(truffleConfig); |
await truffle.contracts.compile(config); |
||||||
|
|
||||||
// Run tests
|
// Run tests
|
||||||
try { |
try { |
||||||
failures = await truffle.test.run(truffleConfig) |
failures = await truffle.test.run(config) |
||||||
} catch (e) { |
} catch (e) { |
||||||
error = e.stack; |
error = e.stack; |
||||||
} |
} |
||||||
// Run Istanbul
|
// Run Istanbul
|
||||||
await app.report(); |
await api.report(); |
||||||
|
|
||||||
} catch(e){ |
} catch(e){ |
||||||
error = e; |
error = e; |
||||||
} |
} |
||||||
|
|
||||||
|
|
||||||
// Finish
|
// Finish
|
||||||
await app.cleanUp(); |
await utils.finish(config, api); |
||||||
|
|
||||||
if (error !== undefined) throw error; |
if (error !== undefined) throw error; |
||||||
if (failures > 0) throw new Error(`${failures} test(s) failed under coverage.`) |
if (failures > 0) throw new Error(`${failures} test(s) failed under coverage.`) |
||||||
} |
} |
||||||
|
|
||||||
// -------------------------------------- Helpers --------------------------------------------------
|
|
||||||
|
|
||||||
/** |
|
||||||
* Returns a list of test files to pass to mocha. |
|
||||||
* @param {Object} ui reporter utility |
|
||||||
* @param {Object} truffle truffleConfig |
|
||||||
* @return {String[]} list of files to pass to mocha |
|
||||||
*/ |
|
||||||
function getTestFilePaths(ui, truffle){ |
|
||||||
let target; |
|
||||||
|
|
||||||
// Handle --file <path|glob> cli option (subset of tests)
|
|
||||||
(typeof truffle.file === 'string') |
|
||||||
? target = globby.sync([truffle.file]) |
|
||||||
: target = dir.files(truffle.test_directory, { sync: true }) || []; |
|
||||||
|
|
||||||
// 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); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Configures the network. Runs before the server is launched. |
|
||||||
* User can request a network from truffle-config with "--network <name>". |
|
||||||
* There are overlapping 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 {SolidityCoverage} app |
|
||||||
* @param {TruffleConfig} config |
|
||||||
*/ |
|
||||||
function setNetwork(ui, app, config){ |
|
||||||
|
|
||||||
// --network <network-name>
|
|
||||||
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 (app.providerOptions.network_id && |
|
||||||
app.providerOptions.network_id !== parseInt(network.network_id)){ |
|
||||||
|
|
||||||
ui.report('id-clash', [ parseInt(network.network_id) ]); |
|
||||||
} |
|
||||||
|
|
||||||
// Prefer network defined id.
|
|
||||||
app.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', [app.port]); |
|
||||||
network.port = app.port; |
|
||||||
} |
|
||||||
|
|
||||||
// Warn: port conflicts
|
|
||||||
if (app.port !== app.defaultPort && app.port !== network.port){ |
|
||||||
ui.report('port-clash', [ network.port ]) |
|
||||||
} |
|
||||||
|
|
||||||
// Prefer network port if defined;
|
|
||||||
app.port = network.port; |
|
||||||
|
|
||||||
network.gas = app.gasLimit; |
|
||||||
network.gasPrice = app.gasPrice; |
|
||||||
|
|
||||||
setOuterConfigKeys(config, app, network.network_id); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Default Network Configuration
|
|
||||||
config.network = 'soliditycoverage'; |
|
||||||
setOuterConfigKeys(config, app, "*"); |
|
||||||
|
|
||||||
config.networks[config.network] = { |
|
||||||
network_id: "*", |
|
||||||
port: app.port, |
|
||||||
host: app.host, |
|
||||||
gas: app.gasLimit, |
|
||||||
gasPrice: app.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, app, id){ |
|
||||||
try { |
|
||||||
config.network_id = id; |
|
||||||
config.port = app.port; |
|
||||||
config.host = app.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 |
|
||||||
* 3. fail-safe (truffle lib v 5.0.31 at ./plugin-assets/truffle.library) |
|
||||||
* |
|
||||||
* @param {Object} ui reporter utility |
|
||||||
* @param {Object} truffleConfig config |
|
||||||
* @return {Module} |
|
||||||
*/ |
|
||||||
function loadTruffleLibrary(ui, truffleConfig){ |
|
||||||
|
|
||||||
// Local
|
|
||||||
try { |
|
||||||
if (truffleConfig.useGlobalTruffle || truffleConfig.usePluginTruffle) throw null; |
|
||||||
|
|
||||||
const lib = require("truffle"); |
|
||||||
ui.report('lib-local'); |
|
||||||
return lib; |
|
||||||
|
|
||||||
} catch(err) {}; |
|
||||||
|
|
||||||
// Global
|
|
||||||
try { |
|
||||||
if (truffleConfig.usePluginTruffle) throw null; |
|
||||||
|
|
||||||
const globalTruffle = path.join(globalModules, 'truffle'); |
|
||||||
const lib = require(globalTruffle); |
|
||||||
ui.report('lib-global'); |
|
||||||
return lib; |
|
||||||
|
|
||||||
} catch(err) {}; |
|
||||||
|
|
||||||
// Plugin Copy @ v 5.0.31
|
|
||||||
try { |
|
||||||
if (truffleConfig.forceLibFailure) throw null; // For err unit testing
|
|
||||||
|
|
||||||
ui.report('lib-warn'); |
|
||||||
return require("./plugin-assets/truffle.library") |
|
||||||
|
|
||||||
} catch(err) { |
|
||||||
throw new Error(ui.generate('lib-fail', [err])); |
|
||||||
}; |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
function loadSolcoverJS(ui, truffleConfig){ |
|
||||||
let coverageConfig; |
|
||||||
let solcoverjs; |
|
||||||
|
|
||||||
// Handle --solcoverjs flag
|
|
||||||
(truffleConfig.solcoverjs) |
|
||||||
? solcoverjs = path.join(truffleConfig.working_directory, truffleConfig.solcoverjs) |
|
||||||
: solcoverjs = path.join(truffleConfig.working_directory, '.solcover.js'); |
|
||||||
|
|
||||||
// Catch solcoverjs syntax errors
|
|
||||||
if (shell.test('-e', solcoverjs)){ |
|
||||||
|
|
||||||
try { |
|
||||||
coverageConfig = require(solcoverjs); |
|
||||||
} catch(error){ |
|
||||||
error.message = ui.generate('solcoverjs-fail') + error.message; |
|
||||||
throw new Error(error) |
|
||||||
} |
|
||||||
|
|
||||||
// Config is optional
|
|
||||||
} else { |
|
||||||
coverageConfig = {}; |
|
||||||
} |
|
||||||
|
|
||||||
// Truffle writes to coverage config
|
|
||||||
coverageConfig.log = truffleConfig.logger.log; |
|
||||||
coverageConfig.cwd = truffleConfig.working_directory; |
|
||||||
coverageConfig.originalContractsDir = truffleConfig.contracts_directory; |
|
||||||
|
|
||||||
// Solidity-Coverage writes to Truffle config
|
|
||||||
truffleConfig.mocha = truffleConfig.mocha || {}; |
|
||||||
|
|
||||||
if (coverageConfig.mocha && typeof coverageConfig.mocha === 'object'){ |
|
||||||
truffleConfig.mocha = Object.assign( |
|
||||||
truffleConfig.mocha, |
|
||||||
coverageConfig.mocha |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
return coverageConfig; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
module.exports = plugin; |
module.exports = plugin; |
||||||
|
Loading…
Reference in new issue