Skip to content

Commit 5924706

Browse files
authored
Buidler plugin draft (#426)
* Plugin draft * Passing: standard test suite
1 parent 0bc67b8 commit 5924706

File tree

30 files changed

+843
-38
lines changed

30 files changed

+843
-38
lines changed

dist/buidler.plugin.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
const API = require('./../lib/api');
2+
const utils = require('./plugin-assets/plugin.utils');
3+
const buidlerUtils = require('./plugin-assets/buidler.utils');
4+
const PluginUI = require('./plugin-assets/buidler.ui');
5+
6+
const pkg = require('./../package.json');
7+
const death = require('death');
8+
const path = require('path');
9+
const Web3 = require('web3');
10+
const ganache = require('ganache-cli');
11+
12+
const { task, types } = require("@nomiclabs/buidler/config");
13+
const { ensurePluginLoadedWithUsePlugin } = require("@nomiclabs/buidler/plugins");
14+
15+
const {
16+
TASK_TEST,
17+
TASK_COMPILE,
18+
} = require("@nomiclabs/buidler/builtin-tasks/task-names");
19+
20+
function plugin() {
21+
22+
// UI for the task flags...
23+
const ui = new PluginUI();
24+
25+
task("coverage", "Generates a code coverage report for tests")
26+
27+
.addOptionalParam("file", ui.flags.file, null, types.string)
28+
.addOptionalParam("solcoverjs", ui.flags.solcoverjs, null, types.string)
29+
.addOptionalParam('temp', ui.flags.temp, null, types.string)
30+
31+
.setAction(async function(taskArguments, env){
32+
let error;
33+
let ui;
34+
let api;
35+
let config;
36+
37+
try {
38+
death(buidlerUtils.finish.bind(null, config, api)); // Catch interrupt signals
39+
40+
config = buidlerUtils.normalizeConfig(env.config);
41+
ui = new PluginUI(config.logger.log);
42+
api = new API(utils.loadSolcoverJS(config));
43+
44+
// ==============
45+
// Server launch
46+
// ==============
47+
48+
const network = buidlerUtils.setupNetwork(env, api);
49+
50+
const address = await api.ganache(ganache);
51+
const web3 = new Web3(address);
52+
const accounts = await web3.eth.getAccounts();
53+
const nodeInfo = await web3.eth.getNodeInfo();
54+
const ganacheVersion = nodeInfo.split('/')[1];
55+
56+
// Set default account
57+
network.from = accounts[0];
58+
59+
// Version Info
60+
ui.report('versions', [
61+
ganacheVersion,
62+
pkg.version
63+
]);
64+
65+
// Network Info
66+
ui.report('network', [
67+
api.defaultNetworkName,
68+
api.port
69+
]);
70+
71+
// Run post-launch server hook;
72+
await api.onServerReady(config);
73+
74+
// ================
75+
// Instrumentation
76+
// ================
77+
78+
const skipFiles = api.skipFiles || [];
79+
80+
let {
81+
targets,
82+
skipped
83+
} = utils.assembleFiles(config, skipFiles);
84+
85+
targets = api.instrument(targets);
86+
utils.reportSkipped(config, skipped);
87+
88+
// ==============
89+
// Compilation
90+
// ==============
91+
92+
const {
93+
tempArtifactsDir,
94+
tempContractsDir
95+
} = utils.getTempLocations(config);
96+
97+
utils.save(targets, config.paths.sources, tempContractsDir);
98+
utils.save(skipped, config.paths.sources, tempContractsDir);
99+
100+
config.paths.sources = tempContractsDir;
101+
config.paths.artifacts = tempArtifactsDir;
102+
config.paths.cache = buidlerUtils.tempCacheDir(config);
103+
config.solc.optimizer.enabled = false;
104+
105+
await env.run(TASK_COMPILE);
106+
107+
await api.onCompileComplete(config);
108+
109+
// ======
110+
// Tests
111+
// ======
112+
const testFiles = buidlerUtils.getTestFilePaths(config);
113+
114+
try {
115+
await env.run(TASK_TEST, {testFiles: testFiles})
116+
} catch (e) {
117+
error = e;
118+
}
119+
await api.onTestsComplete(config);
120+
121+
// ========
122+
// Istanbul
123+
// ========
124+
await api.report();
125+
await api.onIstanbulComplete(config);
126+
127+
} catch(e) {
128+
error = e;
129+
}
130+
131+
await utils.finish(config, api);
132+
133+
if (error !== undefined ) throw error;
134+
if (process.exitCode > 0) throw new Error(ui.generate('tests-fail', [process.exitCode]));
135+
})
136+
}
137+
138+
module.exports = plugin;
139+

dist/plugin-assets/buidler.ui.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
const UI = require('./../../lib/ui').UI;
2+
3+
/**
4+
* Truffle Plugin logging
5+
*/
6+
class PluginUI extends UI {
7+
constructor(log){
8+
super(log);
9+
10+
this.flags = {
11+
file: `Path (or glob) defining a subset of tests to run`,
12+
13+
solcoverjs: `Relative path from working directory to config. ` +
14+
`Useful for monorepo packages that share settings.`,
15+
16+
temp: `Path to a disposable folder to store compilation artifacts in. ` +
17+
`Useful when your test setup scripts include hard-coded paths to ` +
18+
`a build directory.`,
19+
20+
}
21+
}
22+
23+
/**
24+
* Writes a formatted message via log
25+
* @param {String} kind message selector
26+
* @param {String[]} args info to inject into template
27+
*/
28+
report(kind, args=[]){
29+
const c = this.chalk;
30+
const ct = c.bold.green('>');
31+
const ds = c.bold.yellow('>');
32+
const w = ":warning:";
33+
34+
const kinds = {
35+
36+
'instr-skip': `\n${c.bold('Coverage skipped for:')}` +
37+
`\n${c.bold('=====================')}\n`,
38+
39+
'instr-skipped': `${ds} ${c.grey(args[0])}`,
40+
41+
'versions': `${ct} ${c.bold('ganache-core')}: ${args[0]}\n` +
42+
`${ct} ${c.bold('solidity-coverage')}: v${args[1]}`,
43+
44+
'network': `\n${c.bold('Network Info')}` +
45+
`\n${c.bold('============')}\n` +
46+
`${ct} ${c.bold('port')}: ${args[1]}\n` +
47+
`${ct} ${c.bold('network')}: ${args[0]}\n`,
48+
}
49+
50+
this._write(kinds[kind]);
51+
}
52+
53+
/**
54+
* Returns a formatted message. Useful for error message.
55+
* @param {String} kind message selector
56+
* @param {String[]} args info to inject into template
57+
* @return {String} message
58+
*/
59+
generate(kind, args=[]){
60+
const c = this.chalk;
61+
const x = ":x:";
62+
63+
const kinds = {
64+
65+
'sources-fail': `${c.red('Cannot locate expected contract sources folder: ')} ${args[0]}`,
66+
67+
'solcoverjs-fail': `${c.red('Could not load .solcover.js config file. ')}` +
68+
`${c.red('This can happen if it has a syntax error or ')}` +
69+
`${c.red('the path you specified for it is wrong.')}`,
70+
71+
'tests-fail': `${x} ${c.bold(args[0])} ${c.red('test(s) failed under coverage.')}`,
72+
73+
'no-network': `${c.red('Network: ')} ${args[0]} ` +
74+
`${c.red(' is not defined in your truffle-config networks. ')}`,
75+
76+
}
77+
78+
79+
return this._format(kinds[kind])
80+
}
81+
}
82+
83+
module.exports = PluginUI;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
const shell = require('shelljs');
2+
const globby = require('globby');
3+
const pluginUtils = require("./plugin.utils");
4+
const path = require('path');
5+
const util = require('util');
6+
const { createProvider } = require("@nomiclabs/buidler/internal/core/providers/construction");
7+
8+
9+
// =============================
10+
// Buidler Specific Plugin Utils
11+
// =============================
12+
13+
/**
14+
* Returns a list of test files to pass to TASK_TEST.
15+
* @param {BuidlerConfig} config
16+
* @return {String[]} list of files to pass to mocha
17+
*/
18+
function getTestFilePaths(config){
19+
let target;
20+
21+
// Handle --file <path|glob> cli option (subset of tests)
22+
(typeof config.file === 'string')
23+
? target = globby.sync([config.file])
24+
: target = [];
25+
26+
// Return list of test files
27+
const testregex = /.*\.(js|ts|es|es6|jsx)$/;
28+
return target.filter(f => f.match(testregex) != null);
29+
}
30+
31+
/**
32+
* Normalizes buidler paths / logging for use by the plugin utilities and
33+
* attaches them to the config
34+
* @param {BuidlerConfig} config
35+
* @return {BuidlerConfig} updated config
36+
*/
37+
function normalizeConfig(config){
38+
config.workingDir = config.paths.root;
39+
config.contractsDir = config.paths.sources;
40+
config.testDir = config.paths.tests;
41+
config.artifactsDir = config.paths.artifacts;
42+
config.logger = config.logger ? config.logger : {log: null};
43+
44+
return config;
45+
}
46+
47+
function setupNetwork(env, api){
48+
const networkConfig = {
49+
url: `http://${api.host}:${api.port}`,
50+
gas: api.gasLimit,
51+
gasPrice: api.gasPrice
52+
}
53+
54+
const provider = createProvider(api.defaultNetworkName, networkConfig);
55+
56+
env.config.networks[api.defaultNetworkName] = networkConfig;
57+
env.config.defaultNetwork = api.defaultNetworkName;
58+
59+
env.network = {
60+
name: api.defaultNetworkName,
61+
config: networkConfig,
62+
provider: provider,
63+
}
64+
65+
env.ethereum = provider;
66+
67+
// Return a reference so we can set the from account
68+
return env.network;
69+
}
70+
71+
/**
72+
* Generates a path to a temporary compilation cache directory
73+
* @param {BuidlerConfig} config
74+
* @return {String} .../.coverage_cache
75+
*/
76+
function tempCacheDir(config){
77+
return path.join(config.paths.root, '.coverage_cache');
78+
}
79+
80+
/**
81+
* Silently removes temporary folders and calls api.finish to shut server down
82+
* @param {BuidlerConfig} config
83+
* @param {SolidityCoverage} api
84+
* @return {Promise}
85+
*/
86+
async function finish(config, api){
87+
const {
88+
tempContractsDir,
89+
tempArtifactsDir
90+
} = pluginUtils.getTempLocations(config);
91+
92+
shell.config.silent = true;
93+
shell.rm('-Rf', tempContractsDir);
94+
shell.rm('-Rf', tempArtifactsDir);
95+
shell.rm('-Rf', path.join(config.paths.root, '.coverage_cache'));
96+
shell.config.silent = false;
97+
98+
if (api) await api.finish();
99+
}
100+
101+
module.exports = {
102+
normalizeConfig: normalizeConfig,
103+
finish: finish,
104+
tempCacheDir: tempCacheDir,
105+
getTestFilePaths: getTestFilePaths,
106+
setupNetwork: setupNetwork
107+
}
108+

dist/plugin-assets/plugin.utils.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const PluginUI = require('./truffle.ui');
1111
const path = require('path');
1212
const fs = require('fs-extra');
1313
const shell = require('shelljs');
14+
const util = require('util')
1415

1516
// ===
1617
// UI
@@ -193,7 +194,6 @@ function loadSolcoverJS(config){
193194
let coverageConfig;
194195
let ui = new PluginUI(config.logger.log);
195196

196-
197197
// Handle --solcoverjs flag
198198
(config.solcoverjs)
199199
? solcoverjs = path.join(config.workingDir, config.solcoverjs)
@@ -251,6 +251,7 @@ async function finish(config, api){
251251
shell.config.silent = true;
252252
shell.rm('-Rf', tempContractsDir);
253253
shell.rm('-Rf', tempArtifactsDir);
254+
shell.config.silent = false;
254255

255256
if (api) await api.finish();
256257
}

0 commit comments

Comments
 (0)