import async from 'async'
import fs from './fileSystem'
import { runTest } from './testRunner'
import { TestResultInterface , ResultsInterface , CompilerConfiguration , compilationInterface , ASTInterface , Options , AstNode } from './types'
import colors from 'colors'
import { Web3 } from 'web3'
import { format } from 'util'
import { compileFileOrFiles } from './compiler'
import { deployAll } from './deployer'
/ * *
* @dev run test contract files ( used for CLI )
* @param filepath Path of file
* @param isDirectory True , if path is a directory
* @param web3 Web3
* @param finalCallback optional callback to run finally
* @param opts Options
* /
// eslint-disable-next-line @typescript-eslint/no-empty-function
export function runTestFiles ( filepath : string , isDirectory : boolean , web3 : Web3 , compilerConfig : CompilerConfiguration , finalCallback : any = ( ) = > { } , opts? : Options ) {
opts = opts || { }
compilerConfig = compilerConfig || { } as CompilerConfiguration
const sourceASTs : any = { }
const printLog = ( log : string [ ] ) = > {
let formattedLog
if ( typeof log [ 0 ] === 'string' && ( log [ 0 ] . includes ( '%s' ) || log [ 0 ] . includes ( '%d' ) ) ) {
formattedLog = format ( log [ 0 ] , . . . log . slice ( 1 ) )
} else {
formattedLog = log . join ( ' ' )
}
signale . log ( formattedLog )
}
const { Signale } = require ( 'signale' ) // eslint-disable-line
// signale configuration
const options = {
types : {
result : {
badge : '\t✓' ,
label : '' ,
color : 'greenBright'
} ,
name : {
badge : '\n\t◼' ,
label : '' ,
color : 'whiteBright'
} ,
log : {
badge : '\t' ,
label : '' ,
color : 'white'
} ,
error : {
badge : '\t✘' ,
label : '' ,
color : 'redBright'
}
}
}
const signale = new Signale ( options )
let accounts = opts [ 'accounts' ] || null
async . waterfall ( [
function getAccountList ( next ) {
if ( accounts ) return next ( null )
web3 . eth . getAccounts ( )
. then ( _accounts = > {
accounts = _accounts
next ( null )
} )
. catch ( ( _err : Error | null | undefined ) = > next ( null ) )
} ,
function compile ( next ) {
compileFileOrFiles ( filepath , isDirectory , { accounts } , compilerConfig , next )
} ,
function deployAllContracts ( compilationResult : compilationInterface , asts : ASTInterface , next ) {
// Extract AST of test contract file source
for ( const filename in asts ) {
if ( filename . endsWith ( '_test.sol' ) ) { sourceASTs [ filename ] = asts [ filename ] . ast }
}
deployAll ( compilationResult , web3 , accounts , false , null , ( err , contracts ) = > {
if ( err ) {
// If contract deployment fails because of 'Out of Gas' error, try again with double gas
// This is temporary, should be removed when remix-tests will have a dedicated UI to
// accept deployment params from UI
if ( err . message . includes ( 'The contract code couldn\'t be stored, please check your gas limit' ) ) {
deployAll ( compilationResult , web3 , accounts , true , null , ( error , contracts ) = > {
if ( error ) next ( [ { message : 'contract deployment failed after trying twice: ' + error . innerError || error . message , severity : 'error' } ] ) // IDE expects errors in array
else next ( null , compilationResult , contracts )
} )
} else { next ( [ { message : 'contract deployment failed: ' + err . innerError || err . message , severity : 'error' } ] ) } // IDE expects errors in array
} else { next ( null , compilationResult , contracts ) }
} )
} ,
function determineTestContractsToRun ( compilationResult : compilationInterface , contracts : any , next ) {
const contractsToTest : string [ ] = [ ]
const contractsToTestDetails : any [ ] = [ ]
const gatherContractsFrom = function ( filename : string ) {
if ( ! filename . endsWith ( '_test.sol' ) ) {
return
}
try {
Object . keys ( compilationResult [ filename ] ) . forEach ( contractName = > {
contractsToTest . push ( contractName )
contractsToTestDetails . push ( compilationResult [ filename ] [ contractName ] )
} )
} catch ( e ) {
console . error ( e )
}
}
if ( isDirectory ) {
fs . walkSync ( filepath , ( foundpath : string ) = > {
gatherContractsFrom ( foundpath )
} )
} else {
gatherContractsFrom ( filepath )
}
next ( null , contractsToTest , contractsToTestDetails , contracts )
} ,
function runTests ( contractsToTest : string [ ] , contractsToTestDetails : any [ ] , contracts : any , next ) {
let totalPassing = 0
let totalFailing = 0
let totalTime = 0
const _testCallback = function ( err : Error | null | undefined , result : TestResultInterface ) {
if ( err ) throw err
if ( result . type === 'contract' ) {
signale . name ( result . value )
console . log ( '\n' )
} else if ( result . type === 'testPass' ) {
if ( result ? . hhLogs ? . length ) result . hhLogs . forEach ( printLog )
signale . result ( result . value . white )
} else if ( result . type === 'testFailure' ) {
if ( result ? . hhLogs ? . length ) result . hhLogs . forEach ( printLog )
signale . error ( result . value . white )
if ( result . assertMethod ) {
console . log ( colors . green ( '\t Expected value should be ' + result . assertMethod + ' to: ' + result . expected ) )
console . log ( colors . red ( '\t Received: ' + result . returned ) )
}
console . log ( colors . red ( '\t Message: ' + result . errMsg ) )
console . log ( '\n' )
}
}
const _resultsCallback = ( _err : Error | null | undefined , result : ResultsInterface , cb ) = > {
totalPassing += result . passingNum
totalFailing += result . failureNum
totalTime += result . timePassed
cb ( )
}
async . eachOfLimit ( contractsToTest , 1 , ( contractName : string , index , cb ) = > {
try {
const fileAST : AstNode = sourceASTs [ contracts [ contractName ] [ 'filename' ] ]
runTest ( contractName , contracts [ contractName ] , contractsToTestDetails [ index ] , fileAST , { accounts , web3 } , _testCallback , ( err , result ) = > {
if ( err ) {
console . log ( err )
return cb ( err )
}
_resultsCallback ( null , result , cb )
} )
} catch ( e ) {
console . error ( e )
}
} , function ( err ) {
if ( err ) {
return next ( err )
}
console . log ( '\n' )
console . log ( colors . bold . underline ( 'Tests Summary: ' ) )
if ( totalPassing >= 0 ) {
console . log ( colors . green ( 'Passed: ' + totalPassing ) )
}
if ( totalFailing >= 0 ) {
console . log ( colors . red ( 'Failed: ' + totalFailing ) )
}
console . log ( colors . white ( 'Time Taken: ' + totalTime + 's' ) )
console . log ( '' )
next ( null , totalPassing , totalFailing )
} )
}
] , finalCallback )
}