@ -6,6 +6,7 @@ const istanbul = require('istanbul');
const util = require ( 'util' ) ;
const util = require ( 'util' ) ;
const assert = require ( 'assert' ) ;
const assert = require ( 'assert' ) ;
const detect = require ( 'detect-port' ) ;
const detect = require ( 'detect-port' ) ;
const _ = require ( 'lodash/lang' ) ;
const ConfigValidator = require ( './validator' ) ;
const ConfigValidator = require ( './validator' ) ;
const Instrumenter = require ( './instrumenter' ) ;
const Instrumenter = require ( './instrumenter' ) ;
@ -17,7 +18,7 @@ const AppUI = require('./ui').AppUI;
* Coverage Runner
* Coverage Runner
* /
* /
class API {
class API {
constructor ( config ) {
constructor ( config = { } ) {
this . coverage = new Coverage ( ) ;
this . coverage = new Coverage ( ) ;
this . instrumenter = new Instrumenter ( ) ;
this . instrumenter = new Instrumenter ( ) ;
this . validator = new ConfigValidator ( )
this . validator = new ConfigValidator ( )
@ -54,7 +55,8 @@ class API {
this . gasLimitString = "0xfffffffffff" ; // block gas limit for ganache (higher than "gas sent")
this . gasLimitString = "0xfffffffffff" ; // block gas limit for ganache (higher than "gas sent")
this . gasPrice = 0x01 ;
this . gasPrice = 0x01 ;
this . istanbulReporter = config . istanbulReporter || [ 'html' , 'lcov' , 'text' ] ;
this . istanbulFolder = config . istanbulFolder || false ;
this . istanbulReporter = config . istanbulReporter || [ 'html' , 'lcov' , 'text' , 'json' ] ;
this . setLoggingLevel ( config . silent ) ;
this . setLoggingLevel ( config . silent ) ;
this . ui = new AppUI ( this . log ) ;
this . ui = new AppUI ( this . log ) ;
@ -65,21 +67,12 @@ class API {
* Instruments a set of sources to prepare them for running under coverage
* Instruments a set of sources to prepare them for running under coverage
* @ param { Object [ ] } targets ( see below )
* @ param { Object [ ] } targets ( see below )
* @ return { Object [ ] } ( see below )
* @ return { Object [ ] } ( see below )
* @ example :
* @ example of input / output array :
*
* [ {
* targets :
* source : ( required ) < solidity - source > ,
* [ {
* canonicalPath : ( required ) < absolute path to source file >
* canonicalPath : < absolute - path >
* relativePath : ( optional ) < rel path to source file for logging >
* relativePath : < relative - path >
* } ]
* source : < source - file >
*
* } , ... ]
*
* outputs :
* [ {
* canonicalPath : < path >
* source : < instrumented - source - file >
* } ... ]
* /
* /
instrument ( targets = [ ] ) {
instrument ( targets = [ ] ) {
let currentFile ; // Keep track of filename in case we crash...
let currentFile ; // Keep track of filename in case we crash...
@ -95,7 +88,7 @@ class API {
this . ui . report ( 'instr-start' ) ;
this . ui . report ( 'instr-start' ) ;
}
}
this . ui . report ( 'instr-item' , [ target . relativePath ] ) ;
this . ui . report ( 'instr-item' , [ currentFile ] ) ;
const instrumented = this . instrumenter . instrument (
const instrumented = this . instrumenter . instrument (
target . source ,
target . source ,
@ -119,22 +112,35 @@ class API {
return outputs ;
return outputs ;
}
}
/ * *
* Returns a copy of the hit map created during instrumentation .
* Useful if you ' d like to delegate coverage collection to multiple processes .
* @ return { Object } instrumentationData
* /
getInstrumentationData ( ) {
return _ . cloneDeep ( this . instrumenter . instrumentationData )
}
/ * *
* Sets the hit map object generated during instrumentation . Useful if you ' d like
* to collect data for a pre - existing instrumentation .
* @ param { Object } data
* /
setInstrumentationData ( data = { } ) {
this . instrumenter . instrumentationData = _ . cloneDeep ( data ) ;
}
/ * *
/ * *
* Launches an in - process ethereum client server , hooking the DataCollector to its VM .
* Launches an in - process ethereum client server , hooking the DataCollector to its VM .
* @ param { Object } client ganache client
* @ param { Object } client ganache client
* @ return { String } address of server to connect to
* @ return { String } address of server to connect to
* /
* /
async ganache ( client ) {
async ganache ( client ) {
let retry = false ;
let address = ` http:// ${ this . host } : ${ this . port } ` ;
// Check for port-in-use
// Check for port-in-use
if ( await detect ( this . port ) !== this . port ) {
if ( await detect ( this . port ) !== this . port ) {
throw new Error ( this . ui . generate ( 'server-fail' , [ this . port ] ) )
throw new Error ( this . ui . generate ( 'server-fail' , [ this . port ] ) )
}
}
if ( ! this . client ) this . client = client ; // Prefer client from options
this . collector = new DataCollector ( this . instrumenter . instrumentationData ) ;
this . collector = new DataCollector ( this . instrumenter . instrumentationData ) ;
this . providerOptions . gasLimit = this . gasLimitString ;
this . providerOptions . gasLimit = this . gasLimitString ;
@ -143,16 +149,17 @@ class API {
// Launch server and attach to vm step of supplied client
// Launch server and attach to vm step of supplied client
try {
try {
if ( this . config . forceBackupServer ) throw new Error ( )
if ( this . config . forceBackupServer ) throw new Error ( )
await this . attachToVM ( )
await this . attachToVM ( client )
}
}
// Fallback to ganache-core-sc (eq: ganache-core 2.7.0 )
// Fallback to ganache-cli )
catch ( err ) {
catch ( err ) {
this . ui . report ( 'vm-fail' , [ ] ) ;
const _ganache = require ( 'ganache-cli' ) ;
this . client = require ( 'ganache-core-sc' ) ;
this . ui . report ( 'vm-fail' , [ _ganache . version ] ) ;
await this . attachToVM ( ) ;
await this . attachToVM ( _ganache ) ;
}
}
const address = ` http:// ${ this . host } : ${ this . port } ` ;
this . ui . report ( 'server' , [ address ] ) ;
this . ui . report ( 'server' , [ address ] ) ;
return address ;
return address ;
}
}
@ -162,7 +169,7 @@ class API {
* /
* /
async report ( ) {
async report ( ) {
const collector = new istanbul . Collector ( ) ;
const collector = new istanbul . Collector ( ) ;
const reporter = new istanbul . Reporter ( ) ;
const reporter = new istanbul . Reporter ( false , this . istanbulFolder ) ;
return new Promise ( ( resolve , reject ) => {
return new Promise ( ( resolve , reject ) => {
try {
try {
@ -177,7 +184,8 @@ class API {
// Pify doesn't like this one...
// Pify doesn't like this one...
reporter . write ( collector , true , ( err ) => {
reporter . write ( collector , true , ( err ) => {
if ( err ) throw err ;
if ( err ) return reject ( err ) ;
this . ui . report ( 'istanbul' ) ;
this . ui . report ( 'istanbul' ) ;
resolve ( ) ;
resolve ( ) ;
} ) ;
} ) ;
@ -204,9 +212,11 @@ class API {
// ========
// ========
// Provider
// Provider
// ========
// ========
async attachToVM ( ) {
async attachToVM ( client ) {
const self = this ;
const self = this ;
// Prefer client from options
if ( ! this . client ) this . client = client ;
this . server = this . client . server ( this . providerOptions ) ;
this . server = this . client . server ( this . providerOptions ) ;
this . assertHasBlockchain ( this . server . provider ) ;
this . assertHasBlockchain ( this . server . provider ) ;
@ -225,7 +235,6 @@ class API {
return vm ;
return vm ;
}
}
// NB: EADDRINUSE errors are uncatch-able?
await pify ( this . server . listen ) ( this . port ) ;
await pify ( this . server . listen ) ( this . port ) ;
}
}