Use ganache server (instead of provider) (#402)

+ Support --network <name> cli flag
pull/403/head
cgewecke 5 years ago committed by GitHub
parent 77628b02cb
commit 0b8c303fac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .circleci/config.yml
  2. 22
      dist/plugin-assets/truffle.ui.js
  3. 141
      dist/truffle.plugin.js
  4. 76
      lib/app.js
  5. 48
      lib/collector.js
  6. 8
      lib/ui.js
  7. 1
      package.json
  8. 14
      scripts/run-zeppelin.sh
  9. 30
      test/units/truffle/errors.js
  10. 96
      test/units/truffle/flags.js
  11. 39
      test/units/truffle/standard.js
  12. 24
      yarn.lock

@ -78,8 +78,7 @@ workflows:
build: build:
jobs: jobs:
- unit-test - unit-test
# TODO: re-enable when server logic is added - e2e-zeppelin
#- e2e-zeppelin
- e2e-metacoin - e2e-metacoin
# TODO: re-enable. # TODO: re-enable.
# At the moment we're using forks so this is pointless # At the moment we're using forks so this is pointless

@ -21,9 +21,17 @@ class PluginUI extends UI {
const kinds = { const kinds = {
'sol-tests': `\n${w} ${c.red("This plugin cannot run Truffle's native solidity tests: ")}`+ 'sol-tests': `${w} ${c.red("This plugin cannot run Truffle's native solidity tests: ")}`+
`${args[0]} test(s) will be skipped.\n`, `${args[0]} test(s) will be skipped.\n`,
'id-clash': `${w} ${c.red("The 'network_id' values in your truffle network ")}` +
`${c.red("and .solcover.js are different. Using truffle's: ")} ${c.bold(args[0])}.\n`,
'port-clash': `${w} ${c.red("The 'port' values in your truffle network ")}` +
`${c.red("and .solcover.js are different. Using truffle's: ")} ${c.bold(args[0])}.\n`,
'no-port': `${w} ${c.red("No 'port' was declared in your truffle network. ")}` +
`${c.red("Using solidity-coverage's: ")} ${c.bold(args[0])}.\n`,
'lib-local': `\n${ct} ${c.grey('Using Truffle library from local node_modules.')}\n`, 'lib-local': `\n${ct} ${c.grey('Using Truffle library from local node_modules.')}\n`,
'lib-global': `\n${ct} ${c.grey('Using Truffle library from global node_modules.')}\n`, 'lib-global': `\n${ct} ${c.grey('Using Truffle library from global node_modules.')}\n`,
@ -43,6 +51,15 @@ class PluginUI extends UI {
'truffle-version': `${ct} ${c.bold('truffle')}: v${args[0]}`, 'truffle-version': `${ct} ${c.bold('truffle')}: v${args[0]}`,
'ganache-version': `${ct} ${c.bold('ganache-core')}: ${args[0]}`, 'ganache-version': `${ct} ${c.bold('ganache-core')}: ${args[0]}`,
'coverage-version': `${ct} ${c.bold('solidity-coverage')}: v${args[0]}`, 'coverage-version': `${ct} ${c.bold('solidity-coverage')}: v${args[0]}`,
'network': `\n${c.bold('Network Info')}` +
`\n${c.bold('============')}\n` +
`${ct} ${c.bold('id')}: ${args[1]}\n` +
`${ct} ${c.bold('port')}: ${args[2]}\n` +
`${ct} ${c.bold('network')}: ${args[0]}\n`,
} }
this._write(kinds[kind]); this._write(kinds[kind]);
@ -66,6 +83,9 @@ class PluginUI extends UI {
`${c.red('This can happen if it has a syntax error or ')}` + `${c.red('This can happen if it has a syntax error or ')}` +
`${c.red('the path you specified for it is wrong.')}`, `${c.red('the path you specified for it is wrong.')}`,
'no-network': `${c.red('Network: ')} ${args[0]} ` +
`${c.red(' is not defined in your truffle-config networks. ')}`,
} }

@ -11,6 +11,7 @@ const util = require('util');
const globby = require('globby'); const globby = require('globby');
const shell = require('shelljs'); const shell = require('shelljs');
const globalModules = require('global-modules'); const globalModules = require('global-modules');
const TruffleProvider = require('@truffle/provider');
/** /**
* Truffle Plugin: `truffle run coverage [options]` * Truffle Plugin: `truffle run coverage [options]`
@ -25,12 +26,12 @@ async function plugin(truffleConfig){
let solcoverjs; let solcoverjs;
let testsErrored = false; let testsErrored = false;
// This needs it's own try block because this logic // Separate try block because this logic
// runs before app.cleanUp is defined. // runs before app.cleanUp is defined.
try { try {
ui = new PluginUI(truffleConfig.logger.log); ui = new PluginUI(truffleConfig.logger.log);
if(truffleConfig.help) return ui.report('help'); // Bail if --help if(truffleConfig.help) return ui.report('help'); // Exit if --help
truffle = loadTruffleLibrary(ui, truffleConfig); truffle = loadTruffleLibrary(ui, truffleConfig);
app = new App(loadSolcoverJS(ui, truffleConfig)); app = new App(loadSolcoverJS(ui, truffleConfig));
@ -41,19 +42,30 @@ async function plugin(truffleConfig){
// Catch interrupt signals // Catch interrupt signals
death(app.cleanUp); death(app.cleanUp);
setNetwork(ui, app, truffleConfig);
// Provider / Server launch // Provider / Server launch
const provider = await app.provider(truffle.ganache); const address = await app.ganache(truffle.ganache);
const web3 = new Web3(provider);
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);
// Version Info // Version Info
ui.report('truffle-version', [truffle.version]); ui.report('truffle-version', [truffle.version]);
ui.report('ganache-version', [ganacheVersion]); ui.report('ganache-version', [ganacheVersion]);
ui.report('coverage-version',[pkg.version]); ui.report('coverage-version',[pkg.version]);
if (truffleConfig.version) return app.cleanUp(); // Bail if --version if (truffleConfig.version) return app.cleanUp(); // Exit if --version
ui.report('network', [
truffleConfig.network,
truffleConfig.networks[truffleConfig.network].network_id,
truffleConfig.networks[truffleConfig.network].port
]);
// Instrument // Instrument
app.sanityCheckContext(); app.sanityCheckContext();
@ -76,25 +88,6 @@ async function plugin(truffleConfig){
// Compile Instrumented Contracts // Compile Instrumented Contracts
await truffle.contracts.compile(truffleConfig); await truffle.contracts.compile(truffleConfig);
// Network Re-configuration
const networkName = 'soliditycoverage';
truffleConfig.network = networkName;
// Truffle complains that these keys *are not* set when running plugin fn directly.
// But throws saying they *cannot* be manually set when running as truffle command.
try {
truffleConfig.network_id = "*";
truffleConfig.provider = provider;
} catch (err){}
truffleConfig.networks[networkName] = {
network_id: "*",
provider: provider,
gas: app.gasLimit,
gasPrice: app.gasPrice,
from: accounts[0]
}
// Run tests // Run tests
try { try {
failures = await truffle.test.run(truffleConfig) failures = await truffle.test.run(truffleConfig)
@ -143,7 +136,101 @@ function getTestFilePaths(ui, truffle){
return target.filter(f => f.match(testregex) != null); 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 * Tries to load truffle module library and reports source. User can force use of
@ -188,8 +275,7 @@ function loadTruffleLibrary(ui, truffleConfig){
return require("./plugin-assets/truffle.library") return require("./plugin-assets/truffle.library")
} catch(err) { } catch(err) {
const msg = ui.generate('lib-fail', [err]); throw new Error(ui.generate('lib-fail', [err]));
throw new Error(msg);
}; };
} }
@ -218,11 +304,12 @@ function loadSolcoverJS(ui, truffleConfig){
coverageConfig = {}; coverageConfig = {};
} }
// Truffle writes to coverage config
coverageConfig.log = truffleConfig.logger.log; coverageConfig.log = truffleConfig.logger.log;
coverageConfig.cwd = truffleConfig.working_directory; coverageConfig.cwd = truffleConfig.working_directory;
coverageConfig.originalContractsDir = truffleConfig.contracts_directory; coverageConfig.originalContractsDir = truffleConfig.contracts_directory;
//Merge solcoverjs mocha opts into truffle's // Solidity-Coverage writes to Truffle config
truffleConfig.mocha = truffleConfig.mocha || {}; truffleConfig.mocha = truffleConfig.mocha || {};
if (coverageConfig.mocha && typeof coverageConfig.mocha === 'object'){ if (coverageConfig.mocha && typeof coverageConfig.mocha === 'object'){

@ -26,7 +26,7 @@ class App {
this.testsErrored = false; this.testsErrored = false;
this.instrumentToFile = (config.instrumentToFile === false) ? false : true; this.instrumentToFile = (config.instrumentToFile === false) ? false : true;
this.cwd = config.cwd; this.cwd = config.cwd || process.cwd();
this.contractsDirName = '.coverage_contracts'; this.contractsDirName = '.coverage_contracts';
this.artifactsDirName = '.coverage_artifacts'; this.artifactsDirName = '.coverage_artifacts';
this.contractsDir = path.join(this.cwd, this.contractsDirName); this.contractsDir = path.join(this.cwd, this.contractsDirName);
@ -34,7 +34,12 @@ class App {
this.originalContractsDir = config.originalContractsDir this.originalContractsDir = config.originalContractsDir
this.server = null;
this.provider = null;
this.defaultPort = 8555;
this.client = config.client; this.client = config.client;
this.port = config.port || this.defaultPort;
this.host = config.host || "127.0.0.1";
this.providerOptions = config.providerOptions || {}; this.providerOptions = config.providerOptions || {};
this.skippedFolders = []; this.skippedFolders = [];
@ -56,6 +61,21 @@ class App {
// -------------------------------------- Methods ----------------------------------------------- // -------------------------------------- Methods -----------------------------------------------
/** /**
* Setup temp folder, write instrumented contracts to it and register them as coverage targets * Setup temp folder, write instrumented contracts to it and register them as coverage targets
*
* TODO: This function should be completely rewritten so that file loading, skip-filters and
* saving are done by the plugin API.
*
* Input should be array of these...
* {
* canonicalPath: <path>
* source: <source-file>
* }
*
* Output should be array of these...:
* {
* canonicalPath: <path>
* source: <instrumented-source-file>
* }
*/ */
instrument(targetFiles=[]) { instrument(targetFiles=[]) {
let targets; let targets;
@ -114,14 +134,15 @@ class App {
} }
/** /**
* Launch an in-process ethereum provider and hook up the DataCollector to its VM. * Launch an in-process ethereum client and hook up the DataCollector to its VM.
* @param {Object} client ethereum client * @param {Object} client ethereum client
* @return {Object} provider * @return {Object} provider
* *
* TODO: generalize provider options setting for non-ganache clients.. * TODO: generalize provider options setting for non-ganache clients..
*/ */
async provider(client){ async ganache(client){
let retry = false; let retry = false;
let address = `http://${this.host}:${this.port}`;
if(!this.client) this.client = client; // Prefer client from options if(!this.client) this.client = client; // Prefer client from options
@ -130,23 +151,27 @@ class App {
this.providerOptions.gasLimit = this.gasLimitString; this.providerOptions.gasLimit = this.gasLimitString;
this.providerOptions.allowUnlimitedContractSize = true; this.providerOptions.allowUnlimitedContractSize = true;
// Try to launch provider and attach to vm step of // Launch server and attach to vm step of supplied client
// either plugin's ganache or a provider passed via options
try { try {
this.provider = await this.attachToVM(); if (this.config.forceBackupServer) throw new Error()
} catch(err){ await this.attachToVM()
retry = true;
this.ui.report('vm-fail', [])
} }
// Fallback to ganache-core-sc (eq: ganache-core 2.7.0) // Fallback to ganache-core-sc (eq: ganache-core 2.7.0)
if (retry){ catch(err) {
this.providerOptions.logger = { log: this.collector.step.bind(this.collector) }; this.ui.report('vm-fail', []);
this.client = require('ganache-core-sc'); this.client = require('ganache-core-sc');
this.provider = this.client.provider(this.providerOptions);
try { await this.attachToVM() }
catch(err) {
err.message = `${this.ui.generate('server-fail', [address])} ${err.message}`;
throw err;
}
} }
return this.provider; this.ui.report('server', [address]);
return address;
} }
/** /**
@ -232,9 +257,9 @@ class App {
shell.rm('-Rf', this.contractsDir); shell.rm('-Rf', this.contractsDir);
shell.rm('-Rf', this.artifactsDir); shell.rm('-Rf', this.artifactsDir);
if (this.provider && this.provider.close){ if (this.server && this.server.close){
this.ui.report('cleanup'); this.ui.report('cleanup');
await pify(self.provider.close)(); await pify(self.server.close)();
} }
} }
// ------------------------------------------ Utils ---------------------------------------------- // ------------------------------------------ Utils ----------------------------------------------
@ -244,27 +269,26 @@ class App {
// ======== // ========
async attachToVM(){ async attachToVM(){
const self = this; const self = this;
const provider = this.client.provider(this.providerOptions);
this.assertHasBlockchain(provider); this.server = this.client.server(this.providerOptions);
await this.vmIsResolved(provider); this.assertHasBlockchain(this.server.provider);
await this.vmIsResolved(this.server.provider);
const blockchain = provider.engine.manager.state.blockchain; const blockchain = this.server.provider.engine.manager.state.blockchain;
const createVM = blockchain.createVMFromStateTrie; const createVM = blockchain.createVMFromStateTrie;
// Attach to VM which ganache has already instantiated // Attach to VM which ganache has already created for transactions
// and which it uses to execute eth_send
blockchain.vm.on('step', self.collector.step.bind(self.collector)); blockchain.vm.on('step', self.collector.step.bind(self.collector));
// Attach/hijack createVM method which ganache uses to run eth_calls // Hijack createVM method which ganache runs for each `eth_call`
blockchain.createVMFromStateTrie = function(state, activatePrecompiles) { blockchain.createVMFromStateTrie = function(state, activatePrecompiles) {
const vm = createVM.apply(blockchain, arguments); const vm = createVM.apply(blockchain, arguments);
vm.on('step', self.collector.step.bind(self.collector)); vm.on('step', self.collector.step.bind(self.collector));
return vm; return vm;
} }
return provider; await pify(this.server.listen)(this.port);
} }
assertHasBlockchain(provider){ assertHasBlockchain(provider){
@ -310,7 +334,11 @@ class App {
*/ */
makeKeysRelative(map, wd) { makeKeysRelative(map, wd) {
const newCoverage = {}; const newCoverage = {};
Object.keys(map).forEach(pathKey => newCoverage[path.relative(wd, pathKey)] = map[pathKey]);
Object
.keys(map)
.forEach(pathKey => newCoverage[path.relative(wd, pathKey)] = map[pathKey]);
return newCoverage; return newCoverage;
} }

@ -1,37 +1,61 @@
const web3Utils = require('web3-utils') const web3Utils = require('web3-utils')
/**
* Writes data from the VM step to the in-memory
* coverage map constructed by the Instrumenter.
*/
class DataCollector { class DataCollector {
constructor(instrumentationData={}){ constructor(instrumentationData={}){
this.instrumentationData = instrumentationData; this.instrumentationData = instrumentationData;
this.validOpcodes = {
"PUSH1": true,
}
} }
/**
* VM step event handler. Detects instrumentation hashes when they are pushed to the
* top of the stack. This runs millions of times - trying to keep it fast.
* @param {Object} info vm step info
*/
step(info){ step(info){
const self = this; try {
if (typeof info !== 'object' || !info.opcode ) return; if (this.validOpcodes[info.opcode.name] && info.stack.length > 0){
if (info.opcode.name.includes("PUSH1") && info.stack.length > 0){
const idx = info.stack.length - 1; const idx = info.stack.length - 1;
let hash = web3Utils.toHex(info.stack[idx]).toString(); let hash = web3Utils.toHex(info.stack[idx]).toString();
hash = self._normalizeHash(hash); hash = this._normalizeHash(hash);
if(self.instrumentationData[hash]){ if(this.instrumentationData[hash]){
self.instrumentationData[hash].hits++; this.instrumentationData[hash].hits++;
} }
} }
} catch (err) { /*Ignore*/ };
} }
_setInstrumentationData(data){ /**
this.instrumentationData = data; * Left-pads zero prefixed bytes 32 hashes to length 66. The '59' in the
} * comparison below is arbitrary. It provides a margin for recurring zeros
* but prevents left-padding shorter irrelevant hashes (like fn sigs)
*
* @param {String} hash data hash from evm stack.
* @return {String} 0x prefixed hash of length 66.
*/
_normalizeHash(hash){ _normalizeHash(hash){
if (hash.length < 66 && hash.length > 52){ if (hash.length < 66 && hash.length > 59){
hash = hash.slice(2); hash = hash.slice(2);
while(hash.length < 64) hash = '0' + hash; while(hash.length < 64) hash = '0' + hash;
hash = '0x' + hash hash = '0x' + hash
} }
return hash; return hash;
} }
/**
* Unit test helper
* @param {Object} data Instrumenter.instrumentationData
*/
_setInstrumentationData(data){
this.instrumentationData = data;
}
} }
module.exports = DataCollector; module.exports = DataCollector;

@ -73,7 +73,9 @@ class AppUI extends UI {
'istanbul': `${ct} ${c.grey('Istanbul reports written to')} ./coverage/ ` + 'istanbul': `${ct} ${c.grey('Istanbul reports written to')} ./coverage/ ` +
`${c.grey('and')} ./coverage.json`, `${c.grey('and')} ./coverage.json`,
'cleanup': `${ct} ${c.grey('solidity-coverage cleaning up, shutting down ganache-core')}`, 'cleanup': `${ct} ${c.grey('solidity-coverage cleaning up, shutting down ganache server')}`,
'server': `${ct} ${c.bold('server: ')} ${c.grey(args[0])}`,
} }
this._write(kinds[kind]); this._write(kinds[kind]);
@ -95,6 +97,10 @@ class AppUI extends UI {
'istanbul-fail': `${c.red('Istanbul coverage reports could not be generated. ')}`, 'istanbul-fail': `${c.red('Istanbul coverage reports could not be generated. ')}`,
'sources-fail': `${c.red('Cannot locate expected contract sources folder: ')} ${args[0]}`, 'sources-fail': `${c.red('Cannot locate expected contract sources folder: ')} ${args[0]}`,
'server-fail': `${c.red('Could not launch ganache server. Is ')}` +
`${args[0]} ${c.red('already in use? ')}` +
`${c.red('Run "lsof -i" in your terminal to check.\n')}`,
} }
return this._format(kinds[kind]) return this._format(kinds[kind])

@ -21,6 +21,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@truffle/provider": "^0.1.17",
"chalk": "^2.4.2", "chalk": "^2.4.2",
"death": "^1.1.0", "death": "^1.1.0",
"ganache-core-sc": "2.7.0-sc.0", "ganache-core-sc": "2.7.0-sc.0",

@ -20,6 +20,8 @@ fi
echo "PR_PATH >>>>> $PR_PATH" echo "PR_PATH >>>>> $PR_PATH"
npm install -g yarn;
# Install sc-forks Zeppelin fork (temporarily). It's setup to # Install sc-forks Zeppelin fork (temporarily). It's setup to
# consume the plugin and skips a small set of GSN tests that rely on # consume the plugin and skips a small set of GSN tests that rely on
# the client being stand-alone. (See OZ issue #1918 for discussion) # the client being stand-alone. (See OZ issue #1918 for discussion)
@ -30,14 +32,14 @@ echo ">>>>> checkout provider-benchmarks branch"
git checkout provider-benchmarks git checkout provider-benchmarks
# Swap installed coverage for PR branch version # Swap installed coverage for PR branch version
echo ">>>>> npm install" echo ">>>>> yarn install"
npm install yarn install
echo ">>>>> npm uninstall --save-dev solidity-coverage" echo ">>>>> yarn remove --dev solidity-coverage"
npm uninstall --save-dev solidity-coverage yarn remove solidity-coverage --dev
echo ">>>>> npm install --save-dev PR_PATH" echo ">>>>> yarn add -dev $PR_PATH"
npm install --save-dev "$PR_PATH" yarn add "$PR_PATH" --dev
# Track perf # Track perf
time npx truffle run coverage time npx truffle run coverage

@ -64,6 +64,8 @@ describe('Truffle Plugin: error cases', function() {
verify.coverageNotGenerated(truffleConfig); verify.coverageNotGenerated(truffleConfig);
}) })
it('lib module load failure', async function(){ it('lib module load failure', async function(){
verify.cleanInitialState(); verify.cleanInitialState();
truffleConfig.usePluginTruffle = true; truffleConfig.usePluginTruffle = true;
@ -82,26 +84,28 @@ describe('Truffle Plugin: error cases', function() {
} }
}); });
// Simple.sol with a failing assertion in a truffle test it('--network <target> is not declared in truffle-config.js', async function(){
it('truffle tests failing', async function() {
verify.cleanInitialState(); verify.cleanInitialState();
mock.install('Simple', 'truffle-test-fail.js', solcoverConfig); truffleConfig.network = 'does-not-exist';
mock.install('Simple', 'simple.js', solcoverConfig);
try { try {
await plugin(truffleConfig); await plugin(truffleConfig);
assert.fail() assert.fail()
} catch (err) { } catch (err) {
assert(err.message.includes('failed under coverage'));
}
verify.coverageGenerated(truffleConfig);
const output = mock.getOutput(truffleConfig); assert(
const path = Object.keys(output)[0]; err.message.includes('is not defined'),
`Should notify network 'is not defined': ${err.message}`
);
assert(output[path].fnMap['1'].name === 'test', 'cov missing "test"'); assert(
assert(output[path].fnMap['2'].name === 'getX', 'cov missing "getX"'); err.message.includes('does-not-exist'),
`Should name missing network: 'does-not-exist': ${err.message}`
);
}
}); });
// Truffle test contains syntax error // Truffle test contains syntax error
@ -127,7 +131,7 @@ describe('Truffle Plugin: error cases', function() {
await plugin(truffleConfig); await plugin(truffleConfig);
assert.fail() assert.fail()
} catch(err){ } catch(err){
assert(err.toString().includes('Compilation failed')); assert(err.message.includes('Compilation failed'));
} }
verify.coverageNotGenerated(truffleConfig); verify.coverageNotGenerated(truffleConfig);
@ -143,7 +147,7 @@ describe('Truffle Plugin: error cases', function() {
assert.fail() assert.fail()
} catch(err){ } catch(err){
assert( assert(
err.toString().includes('/Unparseable.sol.'), err.message.includes('/Unparseable.sol.'),
`Should throw instrumentation errors with file name: ${err.toString()}` `Should throw instrumentation errors with file name: ${err.toString()}`
); );

@ -25,7 +25,7 @@ describe('Truffle Plugin: command line options', function() {
afterEach(() => mock.clean()); afterEach(() => mock.clean());
it('truffle run coverage --file test/<fileName>', async function() { it('--file test/<fileName>', async function() {
verify.cleanInitialState(); verify.cleanInitialState();
truffleConfig.file = path.join( truffleConfig.file = path.join(
@ -54,7 +54,7 @@ describe('Truffle Plugin: command line options', function() {
verify.lineCoverage(expected); verify.lineCoverage(expected);
}); });
it('truffle run coverage --file test/<glob*>', async function() { it('--file test/<glob*>', async function() {
verify.cleanInitialState(); verify.cleanInitialState();
truffleConfig.file = path.join( truffleConfig.file = path.join(
@ -83,7 +83,7 @@ describe('Truffle Plugin: command line options', function() {
verify.lineCoverage(expected); verify.lineCoverage(expected);
}); });
it('truffle run coverage --file test/gl{o,b}*.js', async function() { it('--file test/gl{o,b}*.js', async function() {
verify.cleanInitialState(); verify.cleanInitialState();
truffleConfig.file = path.join( truffleConfig.file = path.join(
@ -112,7 +112,7 @@ describe('Truffle Plugin: command line options', function() {
verify.lineCoverage(expected); verify.lineCoverage(expected);
}); });
it('truffle run coverage --config ../.solcover.js', async function() { it('--config ../.solcover.js', async function() {
verify.cleanInitialState(); verify.cleanInitialState();
solcoverConfig = { solcoverConfig = {
@ -142,7 +142,7 @@ describe('Truffle Plugin: command line options', function() {
shell.rm('.solcover.js'); shell.rm('.solcover.js');
}); });
it('truffle run coverage --help', async function(){ it('--help', async function(){
verify.cleanInitialState(); verify.cleanInitialState();
truffleConfig.help = "true"; truffleConfig.help = "true";
@ -157,7 +157,7 @@ describe('Truffle Plugin: command line options', function() {
); );
}) })
it('truffle run coverage --version', async function(){ it('--version', async function(){
verify.cleanInitialState(); verify.cleanInitialState();
truffleConfig.version = "true"; truffleConfig.version = "true";
@ -183,7 +183,7 @@ describe('Truffle Plugin: command line options', function() {
}) })
it('truffle run coverage --useGlobalTruffle', async function(){ it('--useGlobalTruffle', async function(){
verify.cleanInitialState(); verify.cleanInitialState();
truffleConfig.useGlobalTruffle = true; truffleConfig.useGlobalTruffle = true;
@ -198,7 +198,7 @@ describe('Truffle Plugin: command line options', function() {
); );
}); });
it('truffle run coverage --usePluginTruffle', async function(){ it('--usePluginTruffle', async function(){
verify.cleanInitialState(); verify.cleanInitialState();
truffleConfig.usePluginTruffle = true; truffleConfig.usePluginTruffle = true;
@ -212,5 +212,85 @@ describe('Truffle Plugin: command line options', function() {
`Should notify it's using plugin truffle lib copy: ${mock.loggerOutput.val}` `Should notify it's using plugin truffle lib copy: ${mock.loggerOutput.val}`
); );
}); });
it('--usePluginTruffle', async function(){
verify.cleanInitialState();
truffleConfig.usePluginTruffle = true;
truffleConfig.logger = mock.testLogger;
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
assert(
mock.loggerOutput.val.includes('fallback Truffle library module'),
`Should notify it's using plugin truffle lib copy: ${mock.loggerOutput.val}`
);
});
it('--network (network_id mismatch in configs)', async function(){
verify.cleanInitialState();
truffleConfig.logger = mock.testLogger;
solcoverConfig = { providerOptions: { network_id: 5 }}
truffleConfig.network = 'development';
truffleConfig.networks['development'].network_id = 7;
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
assert(
mock.loggerOutput.val.includes("'network_id' values"),
`Should notify about network_id values: ${mock.loggerOutput.val}`
);
});
it('--network (truffle config missing port)', async function(){
verify.cleanInitialState();
truffleConfig.logger = mock.testLogger;
truffleConfig.network = 'development';
truffleConfig.networks['development'].port = undefined;
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
assert(
mock.loggerOutput.val.includes("No 'port' was declared"),
`Should notify about missing port: ${mock.loggerOutput.val}`
);
assert(
mock.loggerOutput.val.includes("8555"),
`Should have used default coverage port 8555: ${mock.loggerOutput.val}`
);
});
it('--network (declared port mismatches)', async function(){
verify.cleanInitialState();
truffleConfig.logger = mock.testLogger;
truffleConfig.network = 'development'; // 8545
solcoverConfig = { port: 8222 }
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
assert(
mock.loggerOutput.val.includes("The 'port' values"),
`Should notify about mismatched port values: ${mock.loggerOutput.val}`
);
assert(
mock.loggerOutput.val.includes("8545"),
`Should have used default coverage port 8545: ${mock.loggerOutput.val}`
);
});
}); });

@ -218,4 +218,43 @@ describe('Truffle Plugin: standard use cases', function() {
mock.install('Expensive', 'block-gas-limit.js', solcoverConfig); mock.install('Expensive', 'block-gas-limit.js', solcoverConfig);
await plugin(truffleConfig); await plugin(truffleConfig);
}); });
// Simple.sol with a failing assertion in a truffle test
it('truffle tests failing', async function() {
verify.cleanInitialState();
mock.install('Simple', 'truffle-test-fail.js', solcoverConfig);
try {
await plugin(truffleConfig);
assert.fail()
} catch(err){
assert(err.message.includes('failed under coverage'));
}
verify.coverageGenerated(truffleConfig);
const output = mock.getOutput(truffleConfig);
const path = Object.keys(output)[0];
assert(output[path].fnMap['1'].name === 'test', 'cov missing "test"');
assert(output[path].fnMap['2'].name === 'getX', 'cov missing "getX"');
});
it('uses the fallback server', async function(){
verify.cleanInitialState();
truffleConfig.logger = mock.testLogger;
solcoverConfig = { forceBackupServer: true }
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
assert(
mock.loggerOutput.val.includes("Using ganache-core-sc"),
`Should notify about backup server module: ${mock.loggerOutput.val}`
);
});
}) })

@ -166,6 +166,30 @@
dependencies: dependencies:
defer-to-connect "^1.0.1" defer-to-connect "^1.0.1"
"@truffle/error@^0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@truffle/error/-/error-0.0.7.tgz#e9db39885575647ef08bf624b0c13fe46d41a209"
integrity sha512-UIfVKsXSXocKnn5+RNklUXNoGd/JVj7V8KmC48TQzmjU33HQI86PX0JDS7SpHMHasI3w9X//1q7Lu7nZtj3Zzg==
"@truffle/interface-adapter@^0.2.6":
version "0.2.6"
resolved "https://registry.yarnpkg.com/@truffle/interface-adapter/-/interface-adapter-0.2.6.tgz#41bdf5a1a120e8b76c441fa1a746bdc3c6d3fad2"
integrity sha512-8ByMwqC9hWaHQDlwCEMoIhEewvkZXeeywu46/7yM7BHE1XoSDjiQzWjagg6nLJwmFRwB1ZHYC3RXiDyFJLWd0g==
dependencies:
bn.js "^4.11.8"
ethers "^4.0.32"
lodash "^4.17.13"
web3 "1.2.1"
"@truffle/provider@^0.1.17":
version "0.1.17"
resolved "https://registry.yarnpkg.com/@truffle/provider/-/provider-0.1.17.tgz#9b72a76a7ce18a054eabb3ebd21a55a33dc7d7b6"
integrity sha512-V8k2jErxotpMt/R6qS7GL0stM1TWsuJYnm6iRcYDGwp0D33pZDLrnmwsuIbtXIfbAuaJ26kQHu2LGCUxuFh0LA==
dependencies:
"@truffle/error" "^0.0.7"
"@truffle/interface-adapter" "^0.2.6"
web3 "1.2.1"
"@types/bn.js@^4.11.2": "@types/bn.js@^4.11.2":
version "4.11.5" version "4.11.5"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.5.tgz#40e36197433f78f807524ec623afcf0169ac81dc" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.5.tgz#40e36197433f78f807524ec623afcf0169ac81dc"

Loading…
Cancel
Save