Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions dist/buidler.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
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, types } = require("@nomiclabs/buidler/config");
const { ensurePluginLoadedWithUsePlugin } = require("@nomiclabs/buidler/plugins");

const {
TASK_TEST,
TASK_COMPILE,
} = require("@nomiclabs/buidler/builtin-tasks/task-names");

function plugin() {

// UI for the task flags...
const ui = new PluginUI();

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, env){
let error;
let ui;
let api;
let config;

try {
death(buidlerUtils.finish.bind(null, config, api)); // Catch interrupt signals

config = buidlerUtils.normalizeConfig(env.config);
ui = new PluginUI(config.logger.log);
api = new API(utils.loadSolcoverJS(config));

// ==============
// Server launch
// ==============

const network = buidlerUtils.setupNetwork(env, api);

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);

// ================
// Instrumentation
// ================

const skipFiles = api.skipFiles || [];

let {
targets,
skipped
} = utils.assembleFiles(config, skipFiles);

targets = api.instrument(targets);
utils.reportSkipped(config, skipped);

// ==============
// Compilation
// ==============

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);
config.solc.optimizer.enabled = false;

await env.run(TASK_COMPILE);

await api.onCompileComplete(config);

// ======
// Tests
// ======
const testFiles = buidlerUtils.getTestFilePaths(config);

try {
await env.run(TASK_TEST, {testFiles: testFiles})
} catch (e) {
error = e;
}
await api.onTestsComplete(config);

// ========
// Istanbul
// ========
await api.report();
await api.onIstanbulComplete(config);

} catch(e) {
error = e;
}

await utils.finish(config, api);

if (error !== undefined ) throw error;
if (process.exitCode > 0) throw new Error(ui.generate('tests-fail', [process.exitCode]));
})
}

module.exports = plugin;

83 changes: 83 additions & 0 deletions dist/plugin-assets/buidler.ui.js
Original file line number Diff line number Diff line change
@@ -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;
108 changes: 108 additions & 0 deletions dist/plugin-assets/buidler.utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const shell = require('shelljs');
const globby = require('globby');
const pluginUtils = require("./plugin.utils");
const path = require('path');
const util = require('util');
const { createProvider } = require("@nomiclabs/buidler/internal/core/providers/construction");


// =============================
// 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 <path|glob> 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){
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};

return config;
}

function setupNetwork(env, api){
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;

// Return a reference so we can set the from account
return env.network;
}

/**
* Generates a path to a temporary compilation cache directory
* @param {BuidlerConfig} config
* @return {String} .../.coverage_cache
*/
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,
getTestFilePaths: getTestFilePaths,
setupNetwork: setupNetwork
}

3 changes: 2 additions & 1 deletion dist/plugin-assets/plugin.utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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();
}
Expand Down
Loading