Skip to content

Commit 7bce214

Browse files
committed
Route all errors through reporter (#399)
* Decouple app & plugin UIs. Make UI extensible class consumed by plugin. * Add flags to force loading truffle lib module from different sources (global, plugin) * Route all error output through UI Class * Add error checking for solcover.js loading * Add truffle lib bundle map
1 parent bad4fd0 commit 7bce214

File tree

17 files changed

+692
-1878
lines changed

17 files changed

+692
-1878
lines changed

.circleci/config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ jobs:
2626
name: Install yarn
2727
command: |
2828
npm install -g yarn
29+
- run:
30+
name: Install truffle (globally)
31+
command: |
32+
npm install -g truffle
2933
- run:
3034
name: Install dependencies
3135
command: |

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ truffle run coverage [options]
4747
|--------------|--------------------------------|-------------|
4848
| `--file` | `--file="test/registry/*.js"` | Filename or glob describing a subset of JS tests to run. (Globs must be enclosed by quotes.)|
4949
| `--solcoverjs` | `--solcoverjs ./../.solcover.js` | Relative path from working directory to config. Useful for monorepo packages that share settings. (Path must be "./" prefixed) |
50+
| `--useGlobalTruffle` | | Force use of truffle library module from globally installed truffle. (Default: false)|
51+
| `--usePluginTruffle` | | Force use of truffle library module from plugin provided copy (Truffle v5.0.31.) Requires you have locally installed Truffle V5 <= 5.0.30 (Default: false)|
5052
| `--version` | | Version info |
5153
| `--help` | | Usage notes |
5254
|<img width=250/>|<img width=500/> |<img width=100/>|
File renamed without changes.

dist/plugin-assets/truffle.library.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/plugin-assets/truffle.ui.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
11+
/**
12+
* Writes a formatted message via log
13+
* @param {String} kind message selector
14+
* @param {String[]} args info to inject into template
15+
*/
16+
report(kind, args=[]){
17+
const c = this.chalk;
18+
const ct = c.bold.green('>');
19+
const ds = c.bold.yellow('>');
20+
const w = ":warning:";
21+
22+
const kinds = {
23+
24+
'sol-tests': `\n${w} ${c.red("This plugin cannot run Truffle's native solidity tests: ")}`+
25+
`${args[0]} test(s) will be skipped.\n`,
26+
27+
28+
'lib-local': `\n${ct} ${c.grey('Using Truffle library from local node_modules.')}\n`,
29+
'lib-global': `\n${ct} ${c.grey('Using Truffle library from global node_modules.')}\n`,
30+
31+
'lib-warn': `${w} ${c.red('Unable to require Truffle library locally or globally.\n')}`+
32+
`${w} ${c.red('Using fallback Truffle library module instead (v5.0.31)')}\n` +
33+
`${w} ${c.red('Truffle V5 must be a local dependency for fallback to work.')}\n`,
34+
35+
36+
'help': `Usage: truffle run coverage [options]\n\n` +
37+
`Options:\n` +
38+
` --file: path (or glob) to subset of JS test files. (Quote your globs)\n` +
39+
` --solcoverjs: relative path to .solcover.js (ex: ./../.solcover.js)\n` +
40+
` --version: version info\n`,
41+
42+
43+
'truffle-version': `${ct} ${c.bold('truffle')}: v${args[0]}`,
44+
'ganache-version': `${ct} ${c.bold('ganache-core')}: ${args[0]}`,
45+
'coverage-version': `${ct} ${c.bold('solidity-coverage')}: v${args[0]}`,
46+
}
47+
48+
this._write(kinds[kind]);
49+
}
50+
51+
/**
52+
* Returns a formatted message. Useful for error message.
53+
* @param {String} kind message selector
54+
* @param {String[]} args info to inject into template
55+
* @return {String} message
56+
*/
57+
generate(kind, args=[]){
58+
const c = this.chalk;
59+
60+
const kinds = {
61+
'lib-fail': `${c.red('Unable to load plugin copy of Truffle library module. ')}` +
62+
`${c.red('Try installing Truffle >= v5.0.31 locally or globally.\n')}` +
63+
`Caught error message: ${args[0]}\n`,
64+
65+
'solcoverjs-fail': `${c.red('Could not load .solcover.js config file. ')}` +
66+
`${c.red('This can happen if it has a syntax error or ')}` +
67+
`${c.red('the path you specified for it is wrong.')}`,
68+
69+
}
70+
71+
72+
return this._format(kinds[kind])
73+
}
74+
}
75+
76+
module.exports = PluginUI;

dist/truffle.plugin.js

Lines changed: 106 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,6 @@
1-
/*
2-
TruffleConfig Paths
3-
===========================
4-
build_directory /users/myPath/to/someProject/build
5-
contracts_directory. /users/myPath/to/someProject/contracts
6-
working_directory /users/myPath/to/someProject
7-
contracts_build_directory /users/myPath/to/someProject/build/contracts
8-
9-
Compilation options override
10-
----------------------------
11-
build_directory /users/myPath/to/someProject/.coverageArtifacts
12-
contracts_directory /users/myPath/to/someProject/.coverageContracts
13-
14-
Test options override
15-
---------------------
16-
contracts_directory, /users/myPath/to/someProject/.coverageContracts
17-
contracts_build_directory, /users/myPath/to/someProject/.coverageArtifacts/contracts
18-
provider ganache.provider (async b/c vm must be resolved)
19-
logger add filter for unused variables...
20-
21-
Truffle Lib API
22-
===============
23-
load: const truffle = require("truffle") (or require("sc-truffle"))
24-
compilation: await truffle.contracts.compile(config)
25-
test: await truffle.test.run(config)
26-
*/
27-
281
const App = require('./../lib/app');
2+
const PluginUI = require('./plugin-assets/truffle.ui');
3+
294
const pkg = require('./../package.json');
305
const req = require('req-cwd');
316
const death = require('death');
@@ -34,79 +9,78 @@ const dir = require('node-dir');
349
const Web3 = require('web3');
3510
const util = require('util');
3611
const globby = require('globby');
12+
const shell = require('shelljs');
3713
const globalModules = require('global-modules');
3814

15+
/**
16+
* Truffle Plugin: `truffle run coverage [options]`
17+
* @param {Object} truffleConfig @truffle/config config
18+
* @return {Promise}
19+
*/
3920
async function plugin(truffleConfig){
21+
let ui;
4022
let app;
4123
let error;
4224
let truffle;
4325
let testsErrored = false;
44-
let coverageConfig;
45-
let solcoverjs;
4626

47-
// Load truffle lib, .solcover.js & launch app
27+
// This needs it's own try block because this logic
28+
// runs before app.cleanUp is defined.
4829
try {
49-
(truffleConfig.solcoverjs)
50-
? solcoverjs = path.join(truffleConfig.working_directory, truffleConfig.solcoverjs)
51-
: solcoverjs = path.join(truffleConfig.working_directory, '.solcover.js');
52-
53-
coverageConfig = req.silent(solcoverjs) || {};
54-
coverageConfig.cwd = truffleConfig.working_directory;
55-
coverageConfig.originalContractsDir = truffleConfig.contracts_directory;
56-
coverageConfig.log = coverageConfig.log || truffleConfig.logger.log;
30+
ui = new PluginUI(truffleConfig.logger.log);
5731

58-
app = new App(coverageConfig);
32+
if(truffleConfig.help) return ui.report('help'); // Bail if --help
5933

60-
if (truffleConfig.help){
61-
return app.ui.report('truffle-help')
62-
}
34+
truffle = loadTruffleLibrary(ui, truffleConfig);
35+
app = new App(loadSolcoverJS(ui, truffleConfig));
6336

64-
truffle = loadTruffleLibrary(app);
37+
} catch (err) { throw err }
6538

66-
} catch (err) {
67-
throw err;
68-
}
69-
70-
// Instrument and test..
7139
try {
72-
death(app.cleanUp); // This doesn't work...
40+
// Catch interrupt signals
41+
death(app.cleanUp);
7342

74-
// Launch in-process provider
43+
// Provider / Server launch
7544
const provider = await app.provider(truffle.ganache);
7645
const web3 = new Web3(provider);
7746
const accounts = await web3.eth.getAccounts();
7847
const nodeInfo = await web3.eth.getNodeInfo();
7948
const ganacheVersion = nodeInfo.split('/')[1];
8049

81-
app.ui.report('truffle-version', [truffle.version]);
82-
app.ui.report('ganache-version', [ganacheVersion]);
83-
app.ui.report('coverage-version',[pkg.version]);
50+
// Version Info
51+
ui.report('truffle-version', [truffle.version]);
52+
ui.report('ganache-version', [ganacheVersion]);
53+
ui.report('coverage-version',[pkg.version]);
8454

85-
// Bail early if user ran: --version
86-
if (truffleConfig.version) return;
55+
if (truffleConfig.version) return app.cleanUp(); // Bail if --version
8756

88-
// Write instrumented sources to temp folder
57+
// Instrument
58+
app.sanityCheckContext();
59+
app.generateStandardEnvironment();
8960
app.instrument();
9061

91-
// Ask truffle to use temp folders
62+
// Filesystem & Compiler Re-configuration
9263
truffleConfig.contracts_directory = app.contractsDir;
9364
truffleConfig.build_directory = app.artifactsDir;
94-
truffleConfig.contracts_build_directory = paths.artifacts(truffleConfig, app);
9565

96-
// Additional config
66+
truffleConfig.contracts_build_directory = path.join(
67+
app.artifactsDir,
68+
path.basename(truffleConfig.contracts_build_directory)
69+
);
70+
9771
truffleConfig.all = true;
98-
truffleConfig.test_files = tests(app, truffleConfig);
72+
truffleConfig.test_files = getTestFilePaths(ui, truffleConfig);
9973
truffleConfig.compilers.solc.settings.optimizer.enabled = false;
10074

101-
// Compile
75+
// Compile Instrumented Contracts
10276
await truffle.contracts.compile(truffleConfig);
10377

104-
// Launch in-process provider
78+
// Network Re-configuration
10579
const networkName = 'soliditycoverage';
10680
truffleConfig.network = networkName;
10781

108-
// Truffle alternately complains that fields are and
109-
// are not manually set
82+
// Truffle complains that these keys *are not* set when running plugin fn directly.
83+
// But throws saying they *cannot* be manually set when running as truffle command.
11084
try {
11185
truffleConfig.network_id = "*";
11286
truffleConfig.provider = provider;
@@ -133,6 +107,7 @@ async function plugin(truffleConfig){
133107
error = e;
134108
}
135109

110+
136111
// Finish
137112
await app.cleanUp();
138113

@@ -142,68 +117,112 @@ async function plugin(truffleConfig){
142117

143118
// -------------------------------------- Helpers --------------------------------------------------
144119

145-
function tests(app, truffle){
120+
/**
121+
* Returns a list of test files to pass to mocha.
122+
* @param {Object} ui reporter utility
123+
* @param {Object} truffle truffleConfig
124+
* @return {String[]} list of files to pass to mocha
125+
*/
126+
function getTestFilePaths(ui, truffle){
146127
let target;
147128

129+
// Handle --file <path|glob> cli option (subset of tests)
148130
(typeof truffle.file === 'string')
149131
? target = globby.sync([truffle.file])
150132
: target = dir.files(truffle.test_directory, { sync: true }) || [];
151133

134+
// Filter native solidity tests and warn that they're skipped
152135
const solregex = /.*\.(sol)$/;
153136
const hasSols = target.filter(f => f.match(solregex) != null);
154137

155-
if (hasSols.length > 0) app.ui.report('sol-tests', [hasSols.length]);
138+
if (hasSols.length > 0) ui.report('sol-tests', [hasSols.length]);
156139

140+
// Return list of test files
157141
const testregex = /.*\.(js|ts|es|es6|jsx)$/;
158142
return target.filter(f => f.match(testregex) != null);
159143
}
160144

161145

162-
function loadTruffleLibrary(app){
163146

164-
// Case: from local node_modules
147+
/**
148+
* Tries to load truffle module library and reports source. User can force use of
149+
* a non-local version using cli flags (see option). Load order is:
150+
*
151+
* 1. local node_modules
152+
* 2. global node_modules
153+
* 3. fail-safe (truffle lib v 5.0.31 at ./plugin-assets/truffle.library)
154+
*
155+
* @param {Object} ui reporter utility
156+
* @param {Object} truffleConfig config
157+
* @return {Module}
158+
*/
159+
function loadTruffleLibrary(ui, truffleConfig){
160+
161+
// Local
165162
try {
163+
if (truffleConfig.useGlobalTruffle || truffleConfig.usePluginTruffle) throw null;
164+
166165
const lib = require("truffle");
167-
app.ui.report('truffle-local');
166+
ui.report('lib-local');
168167
return lib;
169168

170169
} catch(err) {};
171170

172-
// Case: global
171+
// Global
173172
try {
173+
if (truffleConfig.usePluginTruffle) throw null;
174+
174175
const globalTruffle = path.join(globalModules, 'truffle');
175176
const lib = require(globalTruffle);
176-
app.ui.report('truffle-global');
177+
ui.report('lib-global');
177178
return lib;
178179

179180
} catch(err) {};
180181

181-
// Default: fallback
182+
// Plugin Copy @ v 5.0.31
182183
try {
184+
if (truffleConfig.forceLibFailure) throw null; // For err unit testing
183185

184-
app.ui.report('truffle-warn');
185-
return require("./truffle.library")}
186+
ui.report('lib-warn');
187+
return require("./plugin-assets/truffle.library")
186188

187-
catch(err) {
188-
const msg = app.ui.generate('truffle-fail', [err]);
189+
} catch(err) {
190+
const msg = ui.generate('lib-fail', [err]);
189191
throw new Error(msg);
190192
};
191193

192194
}
193195

194-
/**
195-
* Functions to generate substitute paths for instrumented contracts and artifacts.
196-
* @type {Object}
197-
*/
198-
const paths = {
196+
function loadSolcoverJS(ui, truffleConfig){
197+
let coverageConfig;
198+
let solcoverjs;
199199

200-
// "contracts_build_directory":
201-
artifacts: (truffle, app) => {
202-
return path.join(
203-
app.artifactsDir,
204-
path.basename(truffle.contracts_build_directory)
205-
)
200+
// Handle --solcoverjs flag
201+
(truffleConfig.solcoverjs)
202+
? solcoverjs = path.join(truffleConfig.working_directory, truffleConfig.solcoverjs)
203+
: solcoverjs = path.join(truffleConfig.working_directory, '.solcover.js');
204+
205+
// Catch solcoverjs syntax errors
206+
if (shell.test('-e', solcoverjs)){
207+
208+
try {
209+
coverageConfig = require(solcoverjs);
210+
} catch(error){
211+
error.message = ui.generate('solcoverjs-fail') + error.message;
212+
throw new Error(error)
213+
}
214+
215+
// Config is optional
216+
} else {
217+
coverageConfig = {};
206218
}
219+
220+
coverageConfig.log = truffleConfig.logger.log;
221+
coverageConfig.cwd = truffleConfig.working_directory;
222+
coverageConfig.originalContractsDir = truffleConfig.contracts_directory;
223+
224+
return coverageConfig;
207225
}
208226

227+
209228
module.exports = plugin;

0 commit comments

Comments
 (0)