|
|
|
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((_err: Error | null | undefined, _accounts) => {
|
|
|
|
accounts = _accounts
|
|
|
|
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.message, severity: 'error' }]) // IDE expects errors in array
|
|
|
|
else next(null, compilationResult, contracts)
|
|
|
|
})
|
|
|
|
} else { next([{ message: 'contract deployment failed: ' + 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)
|
|
|
|
}
|