|
|
|
@ -5,6 +5,7 @@ import { deployAll } from './deployer' |
|
|
|
|
import { runTest } from './testRunner' |
|
|
|
|
|
|
|
|
|
import Web3 from 'web3' |
|
|
|
|
import EventManager from './lib/eventManager' |
|
|
|
|
import { Provider, extend } from '@remix-project/remix-simulator' |
|
|
|
|
import { |
|
|
|
|
FinalResult, SrcIfc, compilationInterface, ASTInterface, Options, |
|
|
|
@ -12,126 +13,134 @@ import { |
|
|
|
|
} from './types' |
|
|
|
|
require('colors') |
|
|
|
|
|
|
|
|
|
const createWeb3Provider = async function () { |
|
|
|
|
const web3 = new Web3() |
|
|
|
|
const provider: any = new Provider() |
|
|
|
|
await provider.init() |
|
|
|
|
web3.setProvider(provider) |
|
|
|
|
extend(web3) |
|
|
|
|
return web3 |
|
|
|
|
} |
|
|
|
|
export class UnitTestRunner { |
|
|
|
|
event |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @dev Run tests from source of a test contract file (used for IDE) |
|
|
|
|
* @param contractSources Sources of contract |
|
|
|
|
* @param compilerConfig current compiler configuration |
|
|
|
|
* @param testCallback Test callback |
|
|
|
|
* @param resultCallback Result Callback |
|
|
|
|
* @param finalCallback Final Callback |
|
|
|
|
* @param importFileCb Import file callback |
|
|
|
|
* @param opts Options |
|
|
|
|
*/ |
|
|
|
|
export async function runTestSources (contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, finalCallback: any, importFileCb, opts: Options) { |
|
|
|
|
opts = opts || {} |
|
|
|
|
const sourceASTs: any = {} |
|
|
|
|
const web3 = opts.web3 || await createWeb3Provider() |
|
|
|
|
let accounts: string[] | null = opts.accounts || null |
|
|
|
|
async.waterfall([ |
|
|
|
|
function getAccountList (next) { |
|
|
|
|
if (accounts) return next() |
|
|
|
|
web3.eth.getAccounts((_err, _accounts) => { |
|
|
|
|
accounts = _accounts |
|
|
|
|
next() |
|
|
|
|
}) |
|
|
|
|
}, |
|
|
|
|
function compile (next) { |
|
|
|
|
compileContractSources(contractSources, compilerConfig, importFileCb, { accounts }, next) |
|
|
|
|
}, |
|
|
|
|
function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) { |
|
|
|
|
for (const filename in asts) { |
|
|
|
|
if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast } |
|
|
|
|
} |
|
|
|
|
deployAll(compilationResult, web3, false, (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, true, (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[] = [] |
|
|
|
|
constructor() { |
|
|
|
|
this.event = new EventManager() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async createWeb3Provider () { |
|
|
|
|
const web3 = new Web3() |
|
|
|
|
const provider: any = new Provider() |
|
|
|
|
await provider.init() |
|
|
|
|
web3.setProvider(provider) |
|
|
|
|
extend(web3) |
|
|
|
|
return web3 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (const filename in compilationResult) { |
|
|
|
|
if (!filename.endsWith('_test.sol')) { |
|
|
|
|
continue |
|
|
|
|
/** |
|
|
|
|
* @dev Run tests from source of a test contract file (used for IDE) |
|
|
|
|
* @param contractSources Sources of contract |
|
|
|
|
* @param compilerConfig current compiler configuration |
|
|
|
|
* @param testCallback Test callback |
|
|
|
|
* @param resultCallback Result Callback |
|
|
|
|
* @param finalCallback Final Callback |
|
|
|
|
* @param importFileCb Import file callback |
|
|
|
|
* @param opts Options |
|
|
|
|
*/ |
|
|
|
|
async runTestSources (contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, finalCallback: any, importFileCb, opts: Options) { |
|
|
|
|
opts = opts || {} |
|
|
|
|
const sourceASTs: any = {} |
|
|
|
|
const web3 = opts.web3 || await this.createWeb3Provider() |
|
|
|
|
let accounts: string[] | null = opts.accounts || null |
|
|
|
|
async.waterfall([ |
|
|
|
|
function getAccountList (next) { |
|
|
|
|
if (accounts) return next() |
|
|
|
|
web3.eth.getAccounts((_err, _accounts) => { |
|
|
|
|
accounts = _accounts |
|
|
|
|
next() |
|
|
|
|
}) |
|
|
|
|
}, |
|
|
|
|
(next) => { |
|
|
|
|
compileContractSources(contractSources, compilerConfig, importFileCb, { accounts, event: this.event}, next) |
|
|
|
|
}, |
|
|
|
|
function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) { |
|
|
|
|
for (const filename in asts) { |
|
|
|
|
if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast } |
|
|
|
|
} |
|
|
|
|
Object.keys(compilationResult[filename]).forEach(contractName => { |
|
|
|
|
contractsToTestDetails.push(compilationResult[filename][contractName]) |
|
|
|
|
contractsToTest.push(contractName) |
|
|
|
|
deployAll(compilationResult, web3, false, (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, true, (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) } |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
next(null, contractsToTest, contractsToTestDetails, contracts) |
|
|
|
|
}, |
|
|
|
|
function runTests (contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) { |
|
|
|
|
let totalPassing = 0 |
|
|
|
|
let totalFailing = 0 |
|
|
|
|
let totalTime = 0 |
|
|
|
|
const errors: any[] = [] |
|
|
|
|
// eslint-disable-next-line handle-callback-err
|
|
|
|
|
const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) { |
|
|
|
|
if (result.type === 'testFailure') { |
|
|
|
|
errors.push(result) |
|
|
|
|
}, |
|
|
|
|
function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) { |
|
|
|
|
const contractsToTest: string[] = [] |
|
|
|
|
const contractsToTestDetails: any[] = [] |
|
|
|
|
|
|
|
|
|
for (const filename in compilationResult) { |
|
|
|
|
if (!filename.endsWith('_test.sol')) { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
Object.keys(compilationResult[filename]).forEach(contractName => { |
|
|
|
|
contractsToTestDetails.push(compilationResult[filename][contractName]) |
|
|
|
|
contractsToTest.push(contractName) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
next(null, contractsToTest, contractsToTestDetails, contracts) |
|
|
|
|
}, |
|
|
|
|
function runTests (contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) { |
|
|
|
|
let totalPassing = 0 |
|
|
|
|
let totalFailing = 0 |
|
|
|
|
let totalTime = 0 |
|
|
|
|
const errors: any[] = [] |
|
|
|
|
// eslint-disable-next-line handle-callback-err
|
|
|
|
|
const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) { |
|
|
|
|
if (result.type === 'testFailure') { |
|
|
|
|
errors.push(result) |
|
|
|
|
} |
|
|
|
|
testCallback(result) |
|
|
|
|
} |
|
|
|
|
testCallback(result) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const _resultsCallback = function (_err, result, cb) { |
|
|
|
|
resultCallback(_err, result, () => {}) // eslint-disable-line @typescript-eslint/no-empty-function
|
|
|
|
|
totalPassing += result.passingNum |
|
|
|
|
totalFailing += result.failureNum |
|
|
|
|
totalTime += result.timePassed |
|
|
|
|
cb() |
|
|
|
|
} |
|
|
|
|
const _resultsCallback = function (_err, result, cb) { |
|
|
|
|
resultCallback(_err, result, () => {}) // eslint-disable-line @typescript-eslint/no-empty-function
|
|
|
|
|
totalPassing += result.passingNum |
|
|
|
|
totalFailing += result.failureNum |
|
|
|
|
totalTime += result.timePassed |
|
|
|
|
cb() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async.eachOfLimit(contractsToTest, 1, (contractName: string, index: string | number, cb: ErrorCallback) => { |
|
|
|
|
const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']] |
|
|
|
|
runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts, web3 }, _testCallback, (err, result) => { |
|
|
|
|
async.eachOfLimit(contractsToTest, 1, (contractName: string, index: string | number, cb: ErrorCallback) => { |
|
|
|
|
const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']] |
|
|
|
|
runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts, web3 }, _testCallback, (err, result) => { |
|
|
|
|
if (err) { |
|
|
|
|
return cb(err) |
|
|
|
|
} |
|
|
|
|
_resultsCallback(null, result, cb) |
|
|
|
|
}) |
|
|
|
|
}, function (err) { |
|
|
|
|
if (err) { |
|
|
|
|
return cb(err) |
|
|
|
|
return next(err) |
|
|
|
|
} |
|
|
|
|
_resultsCallback(null, result, cb) |
|
|
|
|
}) |
|
|
|
|
}, function (err) { |
|
|
|
|
if (err) { |
|
|
|
|
return next(err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const finalResults: FinalResult = { |
|
|
|
|
totalPassing: 0, |
|
|
|
|
totalFailing: 0, |
|
|
|
|
totalTime: 0, |
|
|
|
|
errors: [] |
|
|
|
|
} |
|
|
|
|
const finalResults: FinalResult = { |
|
|
|
|
totalPassing: 0, |
|
|
|
|
totalFailing: 0, |
|
|
|
|
totalTime: 0, |
|
|
|
|
errors: [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
finalResults.totalPassing = totalPassing || 0 |
|
|
|
|
finalResults.totalFailing = totalFailing || 0 |
|
|
|
|
finalResults.totalTime = totalTime || 0 |
|
|
|
|
finalResults.errors = [] |
|
|
|
|
finalResults.totalPassing = totalPassing || 0 |
|
|
|
|
finalResults.totalFailing = totalFailing || 0 |
|
|
|
|
finalResults.totalTime = totalTime || 0 |
|
|
|
|
finalResults.errors = [] |
|
|
|
|
|
|
|
|
|
errors.forEach((error, _index) => { |
|
|
|
|
finalResults.errors.push({ context: error.context, value: error.value, message: error.errMsg }) |
|
|
|
|
}) |
|
|
|
|
errors.forEach((error, _index) => { |
|
|
|
|
finalResults.errors.push({ context: error.context, value: error.value, message: error.errMsg }) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
next(null, finalResults) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
], finalCallback) |
|
|
|
|
next(null, finalResults) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
], finalCallback) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|