Commit 28773548 authored by aniket-engg's avatar aniket-engg

linting for remix-tests fixed

parent 84d0bac2
{ {
"extends": "../../.eslintrc", "extends": "../../.eslintrc",
"rules": { "rules": {
"@typescript-eslint/no-explicit-any": "off", "dot-notation": "off",
"@typescript-eslint/no-var-requires": "off", "no-unused-vars": "off"
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-ts-comment": "off"
}, },
"env": { "env": {
"browser": true, "browser": true,
......
const assertionEvents = [ const assertionEvents = [
{ {
name: 'AssertionEvent', name: 'AssertionEvent',
params: ['bool', 'string', 'string'] params: ['bool', 'string', 'string']
}, },
{ {
name: 'AssertionEventUint', name: 'AssertionEventUint',
params: ['bool', 'string', 'string', 'uint256', 'uint256'] params: ['bool', 'string', 'string', 'uint256', 'uint256']
}, },
{ {
name: 'AssertionEventInt', name: 'AssertionEventInt',
params: ['bool', 'string', 'string', 'int256', 'int256'] params: ['bool', 'string', 'string', 'int256', 'int256']
}, },
{ {
name: 'AssertionEventBool', name: 'AssertionEventBool',
params: ['bool', 'string', 'string', 'bool', 'bool'] params: ['bool', 'string', 'string', 'bool', 'bool']
}, },
{ {
name: 'AssertionEventAddress', name: 'AssertionEventAddress',
params: ['bool', 'string', 'string', 'address', 'address'] params: ['bool', 'string', 'string', 'address', 'address']
}, },
{ {
name: 'AssertionEventBytes32', name: 'AssertionEventBytes32',
params: ['bool', 'string', 'string', 'bytes32', 'bytes32'] params: ['bool', 'string', 'string', 'bytes32', 'bytes32']
}, },
{ {
name: 'AssertionEventString', name: 'AssertionEventString',
params: ['bool', 'string', 'string', 'string', 'string'] params: ['bool', 'string', 'string', 'string', 'string']
}, },
{ {
name: 'AssertionEventUintInt', name: 'AssertionEventUintInt',
params: ['bool', 'string', 'string', 'uint256', 'int256'] params: ['bool', 'string', 'string', 'uint256', 'int256']
}, },
{ {
name: 'AssertionEventIntUint', name: 'AssertionEventIntUint',
params: ['bool', 'string', 'string', 'int256', 'uint256'] params: ['bool', 'string', 'string', 'int256', 'uint256']
} }
] ]
export default assertionEvents export default assertionEvents
\ No newline at end of file
...@@ -2,26 +2,26 @@ import fs from './fileSystem' ...@@ -2,26 +2,26 @@ import fs from './fileSystem'
import async from 'async' import async from 'async'
import path from 'path' import path from 'path'
import Log from './logger' import Log from './logger'
const logger = new Log()
const log = logger.logger
import { Compiler as RemixCompiler } from '@remix-project/remix-solidity' import { Compiler as RemixCompiler } from '@remix-project/remix-solidity'
import { SrcIfc, CompilerConfiguration, CompilationErrors } from './types' import { SrcIfc, CompilerConfiguration, CompilationErrors } from './types'
const logger = new Log()
const log = logger.logger
function regexIndexOf (inputString: string, regex: RegExp, startpos = 0) { function regexIndexOf (inputString: string, regex: RegExp, startpos = 0) {
const indexOf = inputString.substring(startpos).search(regex) const indexOf = inputString.substring(startpos).search(regex)
return (indexOf >= 0) ? (indexOf + (startpos)) : indexOf return (indexOf >= 0) ? (indexOf + (startpos)) : indexOf
} }
function writeTestAccountsContract (accounts: string[]) { function writeTestAccountsContract (accounts: string[]) {
const testAccountContract = require('../sol/tests_accounts.sol') const testAccountContract = require('../sol/tests_accounts.sol')
let body = `address[${accounts.length}] memory accounts;` let body = `address[${accounts.length}] memory accounts;`
if (!accounts.length) body += ';' if (!accounts.length) body += ';'
else { else {
accounts.map((address, index) => { accounts.map((address, index) => {
body += `\naccounts[${index}] = ${address};\n` body += `\naccounts[${index}] = ${address};\n`
}) })
} }
return testAccountContract.replace('>accounts<', body) return testAccountContract.replace('>accounts<', body)
} }
/** /**
...@@ -29,47 +29,46 @@ function writeTestAccountsContract (accounts: string[]) { ...@@ -29,47 +29,46 @@ function writeTestAccountsContract (accounts: string[]) {
* @param path file path to check * @param path file path to check
*/ */
function isRemixTestFile(path: string) { function isRemixTestFile (path: string) {
return ['tests.sol', 'remix_tests.sol', 'remix_accounts.sol'].some(name => path.includes(name)) return ['tests.sol', 'remix_tests.sol', 'remix_accounts.sol'].some(name => path.includes(name))
} }
/** /**
* @dev Process file to prepare sources object to be passed in solc compiler input * @dev Process file to prepare sources object to be passed in solc compiler input
* *
* See: https://solidity.readthedocs.io/en/latest/using-the-compiler.html#input-description * See: https://solidity.readthedocs.io/en/latest/using-the-compiler.html#input-description
* *
* @param filePath path of file to process * @param filePath path of file to process
* @param sources existing 'sources' object in which keys are the "global" names of the source files and * @param sources existing 'sources' object in which keys are the "global" names of the source files and
* value is object containing content of corresponding file with under key 'content' * value is object containing content of corresponding file with under key 'content'
* @param isRoot True, If file is a root test contract file which is getting processed, not an imported file * @param isRoot True, If file is a root test contract file which is getting processed, not an imported file
*/ */
function processFile(filePath: string, sources: SrcIfc, isRoot = false) { function processFile (filePath: string, sources: SrcIfc, isRoot = false) {
const importRegEx = /import ['"](.+?)['"];/g const importRegEx = /import ['"](.+?)['"];/g
let group: RegExpExecArray| null = null let group: RegExpExecArray| null = null
const isFileAlreadyInSources: boolean = Object.keys(sources).includes(filePath) const isFileAlreadyInSources: boolean = Object.keys(sources).includes(filePath)
// Return if file is a remix test file or already processed // Return if file is a remix test file or already processed
if(isRemixTestFile(filePath) || isFileAlreadyInSources) if (isRemixTestFile(filePath) || isFileAlreadyInSources) { return }
return
let content: string = fs.readFileSync(filePath, { encoding: 'utf-8' }) let content: string = fs.readFileSync(filePath, { encoding: 'utf-8' })
const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm
// import 'remix_tests.sol', if file is a root test contract file and doesn't already have it // import 'remix_tests.sol', if file is a root test contract file and doesn't already have it
if (isRoot && filePath.endsWith('_test.sol') && regexIndexOf(content, testFileImportRegEx) < 0) { if (isRoot && filePath.endsWith('_test.sol') && regexIndexOf(content, testFileImportRegEx) < 0) {
const includeTestLibs = '\nimport \'remix_tests.sol\';\n' const includeTestLibs = '\nimport \'remix_tests.sol\';\n'
content = includeTestLibs.concat(content) content = includeTestLibs.concat(content)
} }
sources[filePath] = {content} sources[filePath] = { content }
importRegEx.exec('') // Resetting state of RegEx importRegEx.exec('') // Resetting state of RegEx
// Process each 'import' in file content // Process each 'import' in file content
while ((group = importRegEx.exec(content))) { while ((group = importRegEx.exec(content))) {
const importedFile: string = group[1] const importedFile: string = group[1]
const importedFilePath: string = path.join(path.dirname(filePath), importedFile) const importedFilePath: string = path.join(path.dirname(filePath), importedFile)
processFile(importedFilePath, sources) processFile(importedFilePath, sources)
} }
} }
const userAgent = (typeof (navigator) !== 'undefined') && navigator.userAgent ? navigator.userAgent.toLowerCase() : '-' const userAgent = (typeof (navigator) !== 'undefined') && navigator.userAgent ? navigator.userAgent.toLowerCase() : '-'
...@@ -81,88 +80,86 @@ const isBrowser = !(typeof (window) === 'undefined' || userAgent.indexOf(' elect ...@@ -81,88 +80,86 @@ const isBrowser = !(typeof (window) === 'undefined' || userAgent.indexOf(' elect
* @param isDirectory True, if path is a directory * @param isDirectory True, if path is a directory
* @param opts Options * @param opts Options
* @param cb Callback * @param cb Callback
* *
* TODO: replace this with remix's own compiler code * TODO: replace this with remix's own compiler code
*/ */
export function compileFileOrFiles(filename: string, isDirectory: boolean, opts: any, compilerConfig: CompilerConfiguration, cb): void { export function compileFileOrFiles (filename: string, isDirectory: boolean, opts: any, compilerConfig: CompilerConfiguration, cb): void {
let compiler: any let compiler: any
const accounts: string[] = opts.accounts || [] const accounts: string[] = opts.accounts || []
const sources: SrcIfc = { const sources: SrcIfc = {
'tests.sol': { content: require('../sol/tests.sol') }, 'tests.sol': { content: require('../sol/tests.sol') },
'remix_tests.sol': { content: require('../sol/tests.sol') }, 'remix_tests.sol': { content: require('../sol/tests.sol') },
'remix_accounts.sol': { content: writeTestAccountsContract(accounts) } 'remix_accounts.sol': { content: writeTestAccountsContract(accounts) }
}
const filepath: string = (isDirectory ? filename : path.dirname(filename))
try {
if (!isDirectory && fs.existsSync(filename)) {
if (filename.split('.').pop() === 'sol') {
processFile(filename, sources, true)
} else {
throw new Error('Not a solidity file')
}
} else {
// walkSync only if it is a directory
let testFileCount = 0
fs.walkSync(filepath, (foundpath: string) => {
// only process .sol files
if (foundpath.split('.').pop() === 'sol' && foundpath.endsWith('_test.sol')) {
testFileCount++
processFile(foundpath, sources, true)
}
})
if (testFileCount > 0) {
log.info(`${testFileCount} Solidity test file${testFileCount === 1 ? '' : 's'} found`)
} else {
log.error('No Solidity test file found. Make sure your test file ends with \'_test.sol\'')
process.exit()
}
} }
const filepath: string = (isDirectory ? filename : path.dirname(filename)) } catch (e) { // eslint-disable-line no-useless-catch
try { throw e
if(!isDirectory && fs.existsSync(filename)) { } finally {
if (filename.split('.').pop() === 'sol') { async.waterfall([
processFile(filename, sources, true) function loadCompiler (next) {
} else { compiler = new RemixCompiler()
throw new Error('Not a solidity file') if (compilerConfig) {
} const { currentCompilerUrl, evmVersion, optimize, runs } = compilerConfig
} else { if (evmVersion) compiler.set('evmVersion', evmVersion)
// walkSync only if it is a directory if (optimize) compiler.set('optimize', optimize)
let testFileCount = 0 if (runs) compiler.set('runs', runs)
fs.walkSync(filepath, (foundpath: string) => { if (currentCompilerUrl) {
// only process .sol files compiler.loadRemoteVersion(currentCompilerUrl)
if (foundpath.split('.').pop() === 'sol' && foundpath.endsWith('_test.sol')) { compiler.event.register('compilerLoaded', this, function (version) {
testFileCount++ next()
processFile(foundpath, sources, true)
}
}) })
if(testFileCount > 0) { } else {
log.info(`${testFileCount} Solidity test file${testFileCount===1?'':'s'} found`) compiler.onInternalCompilerLoaded()
} next()
else { }
log.error(`No Solidity test file found. Make sure your test file ends with '_test.sol'`) } else {
process.exit() compiler.onInternalCompilerLoaded()
} next()
} }
},
} catch (e) { // eslint-disable-line no-useless-catch function doCompilation (next) {
throw e // @ts-ignore
} finally { compiler.event.register('compilationFinished', this, (success, data, source) => {
async.waterfall([ next(null, data)
function loadCompiler(next) {
compiler = new RemixCompiler()
if(compilerConfig) {
const {currentCompilerUrl, evmVersion, optimize, runs} = compilerConfig
evmVersion ? compiler.set('evmVersion', evmVersion) : null
optimize ? compiler.set('optimize', optimize) : null
runs ? compiler.set('runs', runs) : null
if(currentCompilerUrl) {
compiler.loadRemoteVersion(currentCompilerUrl)
compiler.event.register('compilerLoaded', this, function (version) {
next()
})
} else {
compiler.onInternalCompilerLoaded()
next()
}
} else {
compiler.onInternalCompilerLoaded()
next()
}
},
function doCompilation(next) {
// @ts-ignore
compiler.event.register('compilationFinished', this, (success, data, source) => {
next(null, data)
})
compiler.compile(sources, filepath)
}
], function (err: Error | null | undefined, result: any) {
const error: Error[] = []
if (result.error) error.push(result.error)
const errors = (result.errors || error).filter((e) => e.type === 'Error' || e.severity === 'error')
if (errors.length > 0) {
if (!isBrowser) require('signale').fatal(errors)
return cb(new CompilationErrors(errors))
}
cb(err, result.contracts, result.sources) //return callback with contract details & ASTs
}) })
} compiler.compile(sources, filepath)
}
], function (err: Error | null | undefined, result: any) {
const error: Error[] = []
if (result.error) error.push(result.error)
const errors = (result.errors || error).filter((e) => e.type === 'Error' || e.severity === 'error')
if (errors.length > 0) {
if (!isBrowser) require('signale').fatal(errors)
return cb(new CompilationErrors(errors))
}
cb(err, result.contracts, result.sources) // return callback with contract details & ASTs
})
}
} }
/** /**
...@@ -173,53 +170,53 @@ export function compileFileOrFiles(filename: string, isDirectory: boolean, opts: ...@@ -173,53 +170,53 @@ export function compileFileOrFiles(filename: string, isDirectory: boolean, opts:
* @param opts Options * @param opts Options
* @param cb Callback * @param cb Callback
*/ */
export function compileContractSources(sources: SrcIfc, compilerConfig: CompilerConfiguration, importFileCb: any, opts: any, cb): void { export function compileContractSources (sources: SrcIfc, compilerConfig: CompilerConfiguration, importFileCb: any, opts: any, cb): void {
let compiler, filepath: string let compiler, filepath: string
const accounts: string[] = opts.accounts || [] const accounts: string[] = opts.accounts || []
// Iterate over sources keys. Inject test libraries. Inject test library import statements. // Iterate over sources keys. Inject test libraries. Inject test library import statements.
if (!('remix_tests.sol' in sources) && !('tests.sol' in sources)) { if (!('remix_tests.sol' in sources) && !('tests.sol' in sources)) {
sources['tests.sol'] = { content: require('../sol/tests.sol.js') } sources['tests.sol'] = { content: require('../sol/tests.sol.js') }
sources['remix_tests.sol'] = { content: require('../sol/tests.sol.js') } sources['remix_tests.sol'] = { content: require('../sol/tests.sol.js') }
sources['remix_accounts.sol'] = { content: writeTestAccountsContract(accounts) } sources['remix_accounts.sol'] = { content: writeTestAccountsContract(accounts) }
}
const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm
const includeTestLibs = '\nimport \'remix_tests.sol\';\n'
for (const file in sources) {
const c: string = sources[file].content
if (file.endsWith('_test.sol') && c && regexIndexOf(c, testFileImportRegEx) < 0) {
sources[file].content = includeTestLibs.concat(c)
} }
const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm }
const includeTestLibs = '\nimport \'remix_tests.sol\';\n' async.waterfall([
for (const file in sources) { function loadCompiler (next) {
const c: string = sources[file].content const { currentCompilerUrl, evmVersion, optimize, runs, usingWorker } = compilerConfig
if (file.endsWith('_test.sol') && c && regexIndexOf(c, testFileImportRegEx) < 0) { compiler = new RemixCompiler(importFileCb)
sources[file].content = includeTestLibs.concat(c) compiler.set('evmVersion', evmVersion)
} compiler.set('optimize', optimize)
compiler.set('runs', runs)
compiler.loadVersion(usingWorker, currentCompilerUrl)
// @ts-ignore
compiler.event.register('compilerLoaded', this, (version) => {
next()
})
},
function doCompilation (next) {
// @ts-ignore
compiler.event.register('compilationFinished', this, (success, data, source) => {
next(null, data)
})
compiler.compile(sources, filepath)
} }
], function (err: Error | null | undefined, result: any) {
async.waterfall([ const error: Error[] = []
function loadCompiler (next) { if (result.error) error.push(result.error)
const {currentCompilerUrl, evmVersion, optimize, runs, usingWorker} = compilerConfig const errors = (result.errors || error).filter((e) => e.type === 'Error' || e.severity === 'error')
compiler = new RemixCompiler(importFileCb) if (errors.length > 0) {
compiler.set('evmVersion', evmVersion) if (!isBrowser) require('signale').fatal(errors)
compiler.set('optimize', optimize) return cb(new CompilationErrors(errors))
compiler.set('runs', runs) }
compiler.loadVersion(usingWorker, currentCompilerUrl) cb(err, result.contracts, result.sources) // return callback with contract details & ASTs
// @ts-ignore })
compiler.event.register('compilerLoaded', this, (version) => {
next()
})
},
function doCompilation (next) {
// @ts-ignore
compiler.event.register('compilationFinished', this, (success, data, source) => {
next(null, data)
})
compiler.compile(sources, filepath)
}
], function (err: Error | null | undefined , result: any) {
const error: Error[] = []
if (result.error) error.push(result.error)
const errors = (result.errors || error).filter((e) => e.type === 'Error' || e.severity === 'error')
if (errors.length > 0) {
if (!isBrowser) require('signale').fatal(errors)
return cb(new CompilationErrors(errors))
}
cb(err, result.contracts, result.sources) // return callback with contract details & ASTs
})
} }
...@@ -11,108 +11,108 @@ import { compilationInterface } from './types' ...@@ -11,108 +11,108 @@ import { compilationInterface } from './types'
* @param callback Callback * @param callback Callback
*/ */
export function deployAll(compileResult: compilationInterface, web3: Web3, withDoubleGas: boolean, callback) { export function deployAll (compileResult: compilationInterface, web3: Web3, withDoubleGas: boolean, callback) {
const compiledObject = {} const compiledObject = {}
const contracts = {} const contracts = {}
let accounts: string[] = [] let accounts: string[] = []
async.waterfall([ async.waterfall([
function getAccountList(next) { function getAccountList (next) {
web3.eth.getAccounts((_err, _accounts) => { web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts accounts = _accounts
next() next()
}) })
}, },
function getContractData(next) { function getContractData (next) {
for (const contractFile in compileResult) { for (const contractFile in compileResult) {
for (const contractName in compileResult[contractFile]) { for (const contractName in compileResult[contractFile]) {
const contract = compileResult[contractFile][contractName] const contract = compileResult[contractFile][contractName]
const className = contractName const className = contractName
const filename = contractFile const filename = contractFile
const abi = contract.abi const abi = contract.abi
const code = contract.evm.bytecode.object const code = contract.evm.bytecode.object
compiledObject[className] = {} compiledObject[className] = {}
compiledObject[className].abi = abi compiledObject[className].abi = abi
compiledObject[className].code = code compiledObject[className].code = code
compiledObject[className].filename = filename compiledObject[className].filename = filename
compiledObject[className].className = className compiledObject[className].className = className
compiledObject[className].raw = contract compiledObject[className].raw = contract
if (contractFile.endsWith('_test.sol')) { if (contractFile.endsWith('_test.sol')) {
compiledObject[className].isTest = true compiledObject[className].isTest = true
} }
} }
} }
next() next()
}, },
function determineContractsToDeploy(next) { function determineContractsToDeploy (next) {
const contractsToDeploy: string[] = ['Assert'] const contractsToDeploy: string[] = ['Assert']
const allContracts = Object.keys(compiledObject) const allContracts = Object.keys(compiledObject)
for (const contractName of allContracts) {
if (contractName === 'Assert') {
continue
}
if (compiledObject[contractName].isTest) {
contractsToDeploy.push(contractName)
}
}
next(null, contractsToDeploy)
},
function deployContracts(contractsToDeploy: string[], next) {
const deployRunner = (deployObject, contractObject, contractName, filename, callback) => {
deployObject.estimateGas().then((gasValue) => {
const gasBase = Math.ceil(gasValue * 1.2)
const gas = withDoubleGas ? gasBase * 2 : gasBase
deployObject.send({
from: accounts[0],
gas: gas
}).on('receipt', function (receipt) {
contractObject.options.address = receipt.contractAddress
contractObject.options.from = accounts[0]
contractObject.options.gas = 5000 * 1000
compiledObject[contractName].deployedAddress = receipt.contractAddress
contracts[contractName] = contractObject for (const contractName of allContracts) {
contracts[contractName].filename = filename if (contractName === 'Assert') {
continue
}
if (compiledObject[contractName].isTest) {
contractsToDeploy.push(contractName)
}
}
next(null, contractsToDeploy)
},
function deployContracts (contractsToDeploy: string[], next) {
const deployRunner = (deployObject, contractObject, contractName, filename, callback) => {
deployObject.estimateGas().then((gasValue) => {
const gasBase = Math.ceil(gasValue * 1.2)
const gas = withDoubleGas ? gasBase * 2 : gasBase
deployObject.send({
from: accounts[0],
gas: gas
}).on('receipt', function (receipt) {
contractObject.options.address = receipt.contractAddress
contractObject.options.from = accounts[0]
contractObject.options.gas = 5000 * 1000
compiledObject[contractName].deployedAddress = receipt.contractAddress
callback(null, { result: { createdAddress: receipt.contractAddress } }) // TODO this will only work with JavaScriptV VM contracts[contractName] = contractObject
}).on('error', function (err) { contracts[contractName].filename = filename
console.error(err)
callback(err)
})
})
}
async.eachOfLimit(contractsToDeploy, 1, function (contractName, index, nextEach) { callback(null, { result: { createdAddress: receipt.contractAddress } }) // TODO this will only work with JavaScriptV VM
const contract = compiledObject[contractName] }).on('error', function (err) {
const encodeDataFinalCallback = (error, contractDeployData) => { console.error(err)
if (error) return nextEach(error) callback(err)
const contractObject = new web3.eth.Contract(contract.abi) })
const deployObject = contractObject.deploy({arguments: [], data: '0x' + contractDeployData.dataHex}) })
deployRunner(deployObject, contractObject, contractName, contract.filename, (error) => { nextEach(error) }) }
}
const encodeDataStepCallback = (msg) => { console.dir(msg) } async.eachOfLimit(contractsToDeploy, 1, function (contractName, index, nextEach) {
const contract = compiledObject[contractName]
const encodeDataFinalCallback = (error, contractDeployData) => {
if (error) return nextEach(error)
const contractObject = new web3.eth.Contract(contract.abi)
const deployObject = contractObject.deploy({ arguments: [], data: '0x' + contractDeployData.dataHex })
deployRunner(deployObject, contractObject, contractName, contract.filename, (error) => { nextEach(error) })
}
const encodeDataDeployLibraryCallback = (libData, callback) => { const encodeDataStepCallback = (msg) => { console.dir(msg) }
const abi = compiledObject[libData.data.contractName].abi
const code = compiledObject[libData.data.contractName].code
const libraryObject = new web3.eth.Contract(abi)
const deployObject = libraryObject.deploy({arguments: [], data: '0x' + code})
deployRunner(deployObject, libraryObject, libData.data.contractName, contract.filename, callback)
}
const funAbi = null // no need to set the abi for encoding the constructor const encodeDataDeployLibraryCallback = (libData, callback) => {
const params = '' // we suppose that the test contract does not have any param in the constructor const abi = compiledObject[libData.data.contractName].abi
execution.txFormat.encodeConstructorCallAndDeployLibraries(contractName, contract.raw, compileResult, params, funAbi, encodeDataFinalCallback, encodeDataStepCallback, encodeDataDeployLibraryCallback) const code = compiledObject[libData.data.contractName].code
}, function (err) { const libraryObject = new web3.eth.Contract(abi)
if(err) next(err) const deployObject = libraryObject.deploy({ arguments: [], data: '0x' + code })
next(null, contracts) deployRunner(deployObject, libraryObject, libData.data.contractName, contract.filename, callback)
})
} }
], callback)
const funAbi = null // no need to set the abi for encoding the constructor
const params = '' // we suppose that the test contract does not have any param in the constructor
execution.txFormat.encodeConstructorCallAndDeployLibraries(contractName, contract.raw, compileResult, params, funAbi, encodeDataFinalCallback, encodeDataStepCallback, encodeDataDeployLibraryCallback)
}, function (err) {
if (err) next(err)
next(null, contracts)
})
}
], callback)
} }
// Extend fs // Extend fs
const fs: any = require('fs')
import path from 'path' import path from 'path'
const fs: any = require('fs')
// https://github.com/mikeal/node-utils/blob/master/file/lib/main.js // https://github.com/mikeal/node-utils/blob/master/file/lib/main.js
fs.walkSync = function (start: string, callback) { fs.walkSync = function (start: string, callback) {
fs.readdirSync(start).forEach((name: string) => { fs.readdirSync(start).forEach((name: string) => {
if (name === 'node_modules') { if (name === 'node_modules') {
return // hack return // hack
} }
const abspath = path.join(start, name) const abspath = path.join(start, name)
if (fs.statSync(abspath).isDirectory()) { if (fs.statSync(abspath).isDirectory()) {
fs.walkSync(abspath, callback) fs.walkSync(abspath, callback)
} else { } else {
callback(abspath) callback(abspath)
} }
}) })
} }
export = fs export = fs
import colors from 'colors' import colors from 'colors'
import winston, { Logger, LoggerOptions } from 'winston' import winston, { Logger, LoggerOptions } from 'winston'
import timestamp from 'time-stamp'; import timestamp from 'time-stamp'
import supportsColor from 'color-support' import supportsColor from 'color-support'
function hasFlag (flag: string) { function hasFlag (flag: string) {
return ((typeof (process) !== 'undefined') && (process.argv.indexOf('--' + flag) !== -1)) return ((typeof (process) !== 'undefined') && (process.argv.indexOf('--' + flag) !== -1))
} }
function addColor (str: string) { function addColor (str: string) {
if (hasFlag('no-color')) { if (hasFlag('no-color')) {
return str return str
} }
if (hasFlag('color')) { if (hasFlag('color')) {
return colors.gray(str) return colors.gray(str)
} }
if (supportsColor()) { if (supportsColor()) {
return colors.gray(str) return colors.gray(str)
} }
return str return str
} }
function getTimestamp () { function getTimestamp () {
return '[' + addColor(timestamp('HH:mm:ss')) + ']' return '[' + addColor(timestamp('HH:mm:ss')) + ']'
} }
// create winston logger format // create winston logger format
const logFmt = winston.format.printf((info) => { const logFmt = winston.format.printf((info) => {
return `${getTimestamp()} ${info.level}: ${info.message}` return `${getTimestamp()} ${info.level}: ${info.message}`
}) })
class Log { class Log {
logger: Logger; logger: Logger;
constructor () { constructor () {
this.logger = winston.createLogger({ this.logger = winston.createLogger({
level: 'info', level: 'info',
transports: [new winston.transports.Console()], transports: [new winston.transports.Console()],
format: winston.format.combine( format: winston.format.combine(
winston.format.colorize({ all: true }), winston.format.colorize({ all: true }),
logFmt logFmt
) )
}) })
} }
setVerbosity (v: LoggerOptions["level"]): void {
this.logger.configure({ setVerbosity (v: LoggerOptions['level']): void {
level: v, this.logger.configure({
transports: [new winston.transports.Console()], level: v,
format: winston.format.combine( transports: [new winston.transports.Console()],
winston.format.colorize({ all: true }), format: winston.format.combine(
logFmt winston.format.colorize({ all: true }),
) logFmt
}) )
})
} }
} }
......
...@@ -7,29 +7,29 @@ import fs from './fileSystem' ...@@ -7,29 +7,29 @@ import fs from './fileSystem'
import { Provider } from '@remix-project/remix-simulator' import { Provider } from '@remix-project/remix-simulator'
import { CompilerConfiguration } from './types' import { CompilerConfiguration } from './types'
import Log from './logger' import Log from './logger'
import colors from 'colors'
const logger = new Log() const logger = new Log()
const log = logger.logger const log = logger.logger
import colors from 'colors'
// parse verbosity // parse verbosity
function mapVerbosity (v: number) { function mapVerbosity (v: number) {
const levels = { const levels = {
0: 'error', 0: 'error',
1: 'warn', 1: 'warn',
2: 'info', 2: 'info',
3: 'verbose', 3: 'verbose',
4: 'debug', 4: 'debug',
5: 'silly' 5: 'silly'
} }
return levels[v] return levels[v]
} }
function mapOptimize (v: string) { function mapOptimize (v: string) {
const optimize = { const optimize = {
'true': true, true: true,
'false': false false: false
} }
return optimize[v]; return optimize[v]
} }
const version = require('../package.json').version const version = require('../package.json').version
...@@ -37,92 +37,91 @@ const version = require('../package.json').version ...@@ -37,92 +37,91 @@ const version = require('../package.json').version
commander.version(version) commander.version(version)
commander.command('version').description('output the version number').action(function () { commander.command('version').description('output the version number').action(function () {
console.log(version) console.log(version)
}) })
commander.command('help').description('output usage information').action(function () { commander.command('help').description('output usage information').action(function () {
commander.help() commander.help()
}) })
// get current version // get current version
commander commander
.option('-c, --compiler <string>', 'set compiler version (e.g: 0.6.1, 0.7.1 etc)') .option('-c, --compiler <string>', 'set compiler version (e.g: 0.6.1, 0.7.1 etc)')
.option('-e, --evm <string>', 'set EVM version (e.g: petersburg, istanbul etc)') .option('-e, --evm <string>', 'set EVM version (e.g: petersburg, istanbul etc)')
.option('-o, --optimize <bool>', 'enable/disable optimization', mapOptimize) .option('-o, --optimize <bool>', 'enable/disable optimization', mapOptimize)
.option('-r, --runs <number>', 'set runs (e.g: 150, 250 etc)') .option('-r, --runs <number>', 'set runs (e.g: 150, 250 etc)')
.option('-v, --verbose <level>', 'set verbosity level (0 to 5)', mapVerbosity) .option('-v, --verbose <level>', 'set verbosity level (0 to 5)', mapVerbosity)
.action(async (testsPath) => { .action(async (testsPath) => {
// Check if path exists
// Check if path exists if (!fs.existsSync(testsPath)) {
if (!fs.existsSync(testsPath)) { log.error(testsPath + ' not found')
log.error(testsPath + ' not found') process.exit(1)
process.exit(1) }
}
// Check if path is for a directory
// Check if path is for a directory const isDirectory = fs.lstatSync(testsPath).isDirectory()
const isDirectory = fs.lstatSync(testsPath).isDirectory()
// If path is for a file, file name must have `_test.sol` suffix
// If path is for a file, file name must have `_test.sol` suffix if (!isDirectory && !testsPath.endsWith('_test.sol')) {
if(!isDirectory && !testsPath.endsWith('_test.sol')) { log.error('Test filename should end with "_test.sol"')
log.error('Test filename should end with "_test.sol"') process.exit()
process.exit() }
}
// Console message
// Console message console.log(colors.white('\n\t👁\t:: Running remix-tests - Unit testing for solidity ::\t👁\n'))
console.log(colors.white('\n\t👁\t:: Running remix-tests - Unit testing for solidity ::\t👁\n'))
// Set logger verbosity
// Set logger verbosity if (commander.verbose) {
if (commander.verbose) { logger.setVerbosity(commander.verbose)
logger.setVerbosity(commander.verbose) log.info('verbosity level set to ' + commander.verbose.blue)
log.info('verbosity level set to ' + commander.verbose.blue) }
}
const compilerConfig = {} as CompilerConfiguration
let compilerConfig = {} as CompilerConfiguration if (commander.compiler) {
if (commander.compiler) { const compVersion = commander.compiler
const compVersion = commander.compiler const baseURL = 'https://binaries.soliditylang.org/wasm/'
const baseURL = 'https://binaries.soliditylang.org/wasm/' const response: AxiosResponse = await axios.get(baseURL + 'list.json')
const response: AxiosResponse = await axios.get(baseURL + 'list.json') const { releases, latestRelease } = response.data
const { releases, latestRelease } = response.data const compString = releases[compVersion]
const compString = releases[compVersion] if (!compString) {
if(!compString) { log.error(`No compiler found in releases with version ${compVersion}`)
log.error(`No compiler found in releases with version ${compVersion}`) process.exit()
process.exit() } else {
} else { compilerConfig.currentCompilerUrl = compString.replace('soljson-', '').replace('.js', '')
compilerConfig.currentCompilerUrl = compString.replace('soljson-', '').replace('.js', '') log.info(`Compiler version set to ${compVersion}. Latest version is ${latestRelease}`)
log.info(`Compiler version set to ${compVersion}. Latest version is ${latestRelease}`) }
} }
}
if (commander.evm) {
if (commander.evm) { compilerConfig.evmVersion = commander.evm
compilerConfig.evmVersion = commander.evm log.info(`EVM set to ${compilerConfig.evmVersion}`)
log.info(`EVM set to ${compilerConfig.evmVersion}`) }
}
if (commander.optimize) {
if (commander.optimize) { compilerConfig.optimize = commander.optimize
compilerConfig.optimize = commander.optimize log.info(`Optimization is ${compilerConfig.optimize ? 'enabled' : 'disabled'}`)
log.info(`Optimization is ${compilerConfig.optimize ? 'enabled' : 'disabled'}`) }
}
if (commander.runs) {
if (commander.runs) { if (!commander.optimize) {
if(!commander.optimize) { log.error('Optimization should be enabled for runs')
log.error(`Optimization should be enabled for runs`) process.exit()
process.exit() }
} compilerConfig.runs = commander.runs
compilerConfig.runs = commander.runs log.info(`Runs set to ${compilerConfig.runs}`)
log.info(`Runs set to ${compilerConfig.runs}`) }
}
const web3 = new Web3()
const web3 = new Web3() const provider: any = new Provider()
const provider: any = new Provider() await provider.init()
await provider.init() web3.setProvider(provider)
web3.setProvider(provider)
runTestFiles(path.resolve(testsPath), isDirectory, web3, compilerConfig)
runTestFiles(path.resolve(testsPath), isDirectory, web3, compilerConfig) })
})
if (!process.argv.slice(2).length) { if (!process.argv.slice(2).length) {
log.error('Please specify a file or directory path') log.error('Please specify a file or directory path')
process.exit() process.exit()
} }
commander.parse(process.argv) commander.parse(process.argv)
...@@ -3,7 +3,7 @@ import fs from './fileSystem' ...@@ -3,7 +3,7 @@ import fs from './fileSystem'
import { runTest } from './testRunner' import { runTest } from './testRunner'
import { TestResultInterface, ResultsInterface, CompilerConfiguration, compilationInterface, ASTInterface, Options, AstNode } from './types' import { TestResultInterface, ResultsInterface, CompilerConfiguration, compilationInterface, ASTInterface, Options, AstNode } from './types'
import colors from 'colors' import colors from 'colors'
import Web3 from 'web3'; import Web3 from 'web3'
import { compileFileOrFiles } from './compiler' import { compileFileOrFiles } from './compiler'
import { deployAll } from './deployer' import { deployAll } from './deployer'
...@@ -18,144 +18,143 @@ import { deployAll } from './deployer' ...@@ -18,144 +18,143 @@ import { deployAll } from './deployer'
*/ */
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
export function runTestFiles(filepath: string, isDirectory: boolean, web3: Web3, compilerConfig: CompilerConfiguration, finalCallback: any = () => {}, opts?: Options) { export function runTestFiles (filepath: string, isDirectory: boolean, web3: Web3, compilerConfig: CompilerConfiguration, finalCallback: any = () => {}, opts?: Options) {
opts = opts || {} opts = opts || {}
compilerConfig = compilerConfig || {} as CompilerConfiguration compilerConfig = compilerConfig || {} as CompilerConfiguration
const sourceASTs: any = {} const sourceASTs: any = {}
const { Signale } = require('signale') const { Signale } = require('signale')
// signale configuration // signale configuration
const options = { const options = {
types: { types: {
result: { result: {
badge: '\t✓', badge: '\t✓',
label: '', label: '',
color: 'greenBright' color: 'greenBright'
}, },
name: { name: {
badge: '\n\t◼', badge: '\n\t◼',
label: '', label: '',
color: 'white' color: 'white'
}, },
error: { error: {
badge: '\t✘', badge: '\t✘',
label: '', label: '',
color: 'redBright' color: 'redBright'
} }
}
} }
const signale = new Signale(options) }
let accounts = opts['accounts'] || null const signale = new Signale(options)
async.waterfall([ let accounts = opts['accounts'] || null
function getAccountList (next) { async.waterfall([
if (accounts) return next(null) function getAccountList (next) {
web3.eth.getAccounts((_err: Error | null | undefined, _accounts) => { if (accounts) return next(null)
accounts = _accounts web3.eth.getAccounts((_err: Error | null | undefined, _accounts) => {
next(null) accounts = _accounts
}) next(null)
}, })
function compile(next) { },
compileFileOrFiles(filepath, isDirectory, { accounts }, compilerConfig, next) function compile (next) {
}, compileFileOrFiles(filepath, isDirectory, { accounts }, compilerConfig, next)
function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) { },
// Extract AST of test contract file source function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) {
for(const filename in asts) { // Extract AST of test contract file source
if(filename.endsWith('_test.sol')) for (const filename in asts) {
sourceASTs[filename] = asts[filename].ast if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast }
} }
deployAll(compilationResult, web3, false, (err, contracts) => { deployAll(compilationResult, web3, false, (err, contracts) => {
if (err) { if (err) {
next(err) next(err)
} }
next(null, compilationResult, contracts) next(null, compilationResult, contracts)
}) })
}, },
function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) { function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) {
const contractsToTest: string[] = [] const contractsToTest: string[] = []
const contractsToTestDetails: any[] = [] const contractsToTestDetails: any[] = []
const gatherContractsFrom = function(filename: string) { const gatherContractsFrom = function (filename: string) {
if (!filename.endsWith('_test.sol')) { if (!filename.endsWith('_test.sol')) {
return return
} }
try { try {
Object.keys(compilationResult[filename]).forEach(contractName => { Object.keys(compilationResult[filename]).forEach(contractName => {
contractsToTest.push(contractName) contractsToTest.push(contractName)
contractsToTestDetails.push(compilationResult[filename][contractName]) contractsToTestDetails.push(compilationResult[filename][contractName])
}) })
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
} }
if (isDirectory) { if (isDirectory) {
fs.walkSync(filepath, (foundpath: string) => { fs.walkSync(filepath, (foundpath: string) => {
gatherContractsFrom(foundpath) gatherContractsFrom(foundpath)
}) })
} else { } else {
gatherContractsFrom(filepath) gatherContractsFrom(filepath)
} }
next(null, contractsToTest, contractsToTestDetails, contracts) next(null, contractsToTest, contractsToTestDetails, contracts)
}, },
function runTests(contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) { function runTests (contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) {
let totalPassing = 0 let totalPassing = 0
let totalFailing = 0 let totalFailing = 0
let totalTime = 0 let totalTime = 0
const errors: any[] = [] const errors: any[] = []
const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) { const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) {
if(err) throw err; if (err) throw err
if (result.type === 'contract') { if (result.type === 'contract') {
signale.name(result.value.white) signale.name(result.value.white)
} else if (result.type === 'testPass') { } else if (result.type === 'testPass') {
signale.result(result.value) signale.result(result.value)
} else if (result.type === 'testFailure') { } else if (result.type === 'testFailure') {
signale.error(result.value.red) signale.error(result.value.red)
errors.push(result) errors.push(result)
} }
} }
const _resultsCallback = (_err: Error | null | undefined, result: ResultsInterface, cb) => { const _resultsCallback = (_err: Error | null | undefined, result: ResultsInterface, cb) => {
totalPassing += result.passingNum totalPassing += result.passingNum
totalFailing += result.failureNum totalFailing += result.failureNum
totalTime += result.timePassed totalTime += result.timePassed
cb() cb()
} }
async.eachOfLimit(contractsToTest, 1, (contractName: string, index, cb) => { async.eachOfLimit(contractsToTest, 1, (contractName: string, index, cb) => {
try { try {
const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']] const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']]
runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts }, _testCallback, (err, result) => { runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts }, _testCallback, (err, result) => {
if (err) { if (err) {
console.log(err) console.log(err)
return cb(err) return cb(err)
} }
_resultsCallback(null, result, cb) _resultsCallback(null, result, cb)
}) })
} catch(e) { } catch (e) {
console.error(e) console.error(e)
} }
}, function (err) { }, function (err) {
if (err) { if (err) {
return next(err) return next(err)
} }
console.log('\n') console.log('\n')
if (totalPassing > 0) { if (totalPassing > 0) {
console.log(colors.green(totalPassing + ' passing ') + colors.grey('(' + totalTime + 's)')) console.log(colors.green(totalPassing + ' passing ') + colors.grey('(' + totalTime + 's)'))
} }
if (totalFailing > 0) { if (totalFailing > 0) {
console.log(colors.red(totalFailing + ' failing')) console.log(colors.red(totalFailing + ' failing'))
} }
console.log('') console.log('')
errors.forEach((error, index) => { errors.forEach((error, index) => {
console.log(' ' + (index + 1) + ') ' + colors.bold(error.context + ': ') + error.value) console.log(' ' + (index + 1) + ') ' + colors.bold(error.context + ': ') + error.value)
console.log('') console.log('')
console.log(colors.red('\t error: ' + error.errMsg)) console.log(colors.red('\t error: ' + error.errMsg))
console.log(colors.green('\t expected value to be '+ error.assertMethod + ' to: ' + error.expected)) console.log(colors.green('\t expected value to be ' + error.assertMethod + ' to: ' + error.expected))
console.log(colors.red('\t returned: ' + error.returned)) console.log(colors.red('\t returned: ' + error.returned))
}) })
console.log('') console.log('')
next() next()
}) })
} }
], finalCallback) ], finalCallback)
} }
import async, { ErrorCallback } from 'async' import async, { ErrorCallback } from 'async'
require('colors')
import { compileContractSources } from './compiler' import { compileContractSources } from './compiler'
import { deployAll } from './deployer' import { deployAll } from './deployer'
import { runTest } from './testRunner' import { runTest } from './testRunner'
import Web3 from 'web3'; import Web3 from 'web3'
import { Provider } from '@remix-project/remix-simulator' import { Provider } from '@remix-project/remix-simulator'
import { FinalResult, SrcIfc, compilationInterface, ASTInterface, Options, import {
TestResultInterface, AstNode, CompilerConfiguration } from './types' FinalResult, SrcIfc, compilationInterface, ASTInterface, Options,
TestResultInterface, AstNode, CompilerConfiguration
} from './types'
require('colors')
const createWeb3Provider = async function () { const createWeb3Provider = async function () {
const web3 = new Web3() const web3 = new Web3()
const provider: any = new Provider() const provider: any = new Provider()
await provider.init() await provider.init()
web3.setProvider(provider) web3.setProvider(provider)
return web3 return web3
} }
/** /**
...@@ -28,110 +30,107 @@ const createWeb3Provider = async function () { ...@@ -28,110 +30,107 @@ const createWeb3Provider = async function () {
* @param importFileCb Import file callback * @param importFileCb Import file callback
* @param opts Options * @param opts Options
*/ */
export async function runTestSources(contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, finalCallback: any, importFileCb, opts: Options) { export async function runTestSources (contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, finalCallback: any, importFileCb, opts: Options) {
opts = opts || {} opts = opts || {}
const sourceASTs: any = {} const sourceASTs: any = {}
const web3 = opts.web3 || await createWeb3Provider() const web3 = opts.web3 || await createWeb3Provider()
let accounts: string[] | null = opts.accounts || null let accounts: string[] | null = opts.accounts || null
async.waterfall([ async.waterfall([
function getAccountList (next) { function getAccountList (next) {
if (accounts) return next() if (accounts) return next()
web3.eth.getAccounts((_err, _accounts) => { web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts accounts = _accounts
next() next()
}) })
}, },
function compile (next) { function compile (next) {
compileContractSources(contractSources, compilerConfig, importFileCb, { accounts }, next) compileContractSources(contractSources, compilerConfig, importFileCb, { accounts }, next)
}, },
function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) { function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) {
for(const filename in asts) { for (const filename in asts) {
if(filename.endsWith('_test.sol')) if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast }
sourceASTs[filename] = asts[filename].ast }
} deployAll(compilationResult, web3, false, (err, contracts) => {
deployAll(compilationResult, web3, false, (err, contracts) => { if (err) {
if (err) { // If contract deployment fails because of 'Out of Gas' error, try again with double gas
// 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
// This is temporary, should be removed when remix-tests will have a dedicated UI to // accept deployment params from UI
// accept deployment params from UI if (err.message.includes('The contract code couldn\'t be stored, please check your gas limit')) {
if(err.message.includes('The contract code couldn\'t be stored, please check your gas limit')) { deployAll(compilationResult, web3, true, (error, contracts) => {
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
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(null, compilationResult, contracts)
})
} else
next([{message: 'contract deployment failed: ' + err.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
function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) { } else { next(null, compilationResult, contracts) }
const contractsToTest: string[] = [] })
const contractsToTestDetails: any[] = [] },
function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) {
const contractsToTest: string[] = []
const contractsToTestDetails: any[] = []
for (const filename in compilationResult) { for (const filename in compilationResult) {
if (!filename.endsWith('_test.sol')) { if (!filename.endsWith('_test.sol')) {
continue continue
} }
Object.keys(compilationResult[filename]).forEach(contractName => { Object.keys(compilationResult[filename]).forEach(contractName => {
contractsToTestDetails.push(compilationResult[filename][contractName]) contractsToTestDetails.push(compilationResult[filename][contractName])
contractsToTest.push(contractName) contractsToTest.push(contractName)
}) })
} }
next(null, contractsToTest, contractsToTestDetails, contracts) next(null, contractsToTest, contractsToTestDetails, contracts)
}, },
function runTests(contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) { function runTests (contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) {
let totalPassing = 0 let totalPassing = 0
let totalFailing = 0 let totalFailing = 0
let totalTime = 0 let totalTime = 0
const errors: any[] = [] const errors: any[] = []
// eslint-disable-next-line handle-callback-err
const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) { const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) {
if (result.type === 'testFailure') { if (result.type === 'testFailure') {
errors.push(result) errors.push(result)
} }
testCallback(result) testCallback(result)
} }
const _resultsCallback = function (_err, result, cb) { const _resultsCallback = function (_err, result, cb) {
resultCallback(_err, result, () => {}) //eslint-disable-line @typescript-eslint/no-empty-function resultCallback(_err, result, () => {}) // eslint-disable-line @typescript-eslint/no-empty-function
totalPassing += result.passingNum totalPassing += result.passingNum
totalFailing += result.failureNum totalFailing += result.failureNum
totalTime += result.timePassed totalTime += result.timePassed
cb() cb()
} }
async.eachOfLimit(contractsToTest, 1, (contractName: string, index: string | number, cb: ErrorCallback) => { async.eachOfLimit(contractsToTest, 1, (contractName: string, index: string | number, cb: ErrorCallback) => {
const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']] const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']]
runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts }, _testCallback, (err, result) => { runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts }, _testCallback, (err, result) => {
if (err) { if (err) {
return cb(err) return cb(err)
} }
_resultsCallback(null, result, cb) _resultsCallback(null, result, cb)
}) })
}, function (err) { }, function (err) {
if (err) { if (err) {
return next(err) return next(err)
} }
const finalResults: FinalResult = { const finalResults: FinalResult = {
totalPassing: 0, totalPassing: 0,
totalFailing: 0, totalFailing: 0,
totalTime: 0, totalTime: 0,
errors: [], errors: []
} }
finalResults.totalPassing = totalPassing || 0 finalResults.totalPassing = totalPassing || 0
finalResults.totalFailing = totalFailing || 0 finalResults.totalFailing = totalFailing || 0
finalResults.totalTime = totalTime || 0 finalResults.totalTime = totalTime || 0
finalResults.errors = [] finalResults.errors = []
errors.forEach((error, _index) => { errors.forEach((error, _index) => {
finalResults.errors.push({context: error.context, value: error.value, message: error.errMsg}) finalResults.errors.push({ context: error.context, value: error.value, message: error.errMsg })
}) })
next(null, finalResults) next(null, finalResults)
}) })
} }
], finalCallback) ], finalCallback)
} }
import async from 'async' import async from 'async'
import * as changeCase from 'change-case' import * as changeCase from 'change-case'
import Web3 from 'web3'; import Web3 from 'web3'
import assertionEvents from './assertionEvents' import assertionEvents from './assertionEvents'
import { RunListInterface, TestCbInterface, TestResultInterface, ResultCbInterface, import {
CompiledContract, AstNode, Options, FunctionDescription, UserDocumentation } from './types' RunListInterface, TestCbInterface, TestResultInterface, ResultCbInterface,
CompiledContract, AstNode, Options, FunctionDescription, UserDocumentation
} from './types'
/** /**
* @dev Get function name using method signature * @dev Get function name using method signature
...@@ -12,12 +14,12 @@ import { RunListInterface, TestCbInterface, TestResultInterface, ResultCbInterfa ...@@ -12,12 +14,12 @@ import { RunListInterface, TestCbInterface, TestResultInterface, ResultCbInterfa
*/ */
function getFunctionFullName (signature: string, methodIdentifiers: Record <string, string>): string | null { function getFunctionFullName (signature: string, methodIdentifiers: Record <string, string>): string | null {
for (const method in methodIdentifiers) { for (const method in methodIdentifiers) {
if (signature.replace('0x', '') === methodIdentifiers[method].replace('0x', '')) { if (signature.replace('0x', '') === methodIdentifiers[method].replace('0x', '')) {
return method return method
}
} }
return null }
return null
} }
/** /**
...@@ -25,8 +27,8 @@ function getFunctionFullName (signature: string, methodIdentifiers: Record <stri ...@@ -25,8 +27,8 @@ function getFunctionFullName (signature: string, methodIdentifiers: Record <stri
* @param funcABI function ABI * @param funcABI function ABI
*/ */
function isConstant(funcABI: FunctionDescription): boolean { function isConstant (funcABI: FunctionDescription): boolean {
return (funcABI.constant || funcABI.stateMutability === 'view' || funcABI.stateMutability === 'pure') return (funcABI.constant || funcABI.stateMutability === 'view' || funcABI.stateMutability === 'pure')
} }
/** /**
...@@ -34,8 +36,8 @@ function isConstant(funcABI: FunctionDescription): boolean { ...@@ -34,8 +36,8 @@ function isConstant(funcABI: FunctionDescription): boolean {
* @param funcABI function ABI * @param funcABI function ABI
*/ */
function isPayable(funcABI: FunctionDescription): boolean { function isPayable (funcABI: FunctionDescription): boolean {
return (funcABI.payable || funcABI.stateMutability === 'payable') return (funcABI.payable || funcABI.stateMutability === 'payable')
} }
/** /**
...@@ -44,8 +46,8 @@ function isPayable(funcABI: FunctionDescription): boolean { ...@@ -44,8 +46,8 @@ function isPayable(funcABI: FunctionDescription): boolean {
* @param name name * @param name name
*/ */
function isNodeName(node: AstNode, name: string): boolean { function isNodeName (node: AstNode, name: string): boolean {
return node.name === name return node.name === name
} }
/** /**
...@@ -54,8 +56,8 @@ function isNodeName(node: AstNode, name: string): boolean { ...@@ -54,8 +56,8 @@ function isNodeName(node: AstNode, name: string): boolean {
* @param type type * @param type type
*/ */
function isNodeType(node: AstNode, type: string): boolean { function isNodeType (node: AstNode, type: string): boolean {
return node.nodeType === type return node.nodeType === type
} }
/** /**
...@@ -64,8 +66,8 @@ function isNodeType(node: AstNode, type: string): boolean { ...@@ -64,8 +66,8 @@ function isNodeType(node: AstNode, type: string): boolean {
* @param typesList list of types * @param typesList list of types
*/ */
function isNodeTypeIn(node: AstNode, typesList: string[]): boolean { function isNodeTypeIn (node: AstNode, typesList: string[]): boolean {
return typesList.includes(node.nodeType) return typesList.includes(node.nodeType)
} }
/** /**
...@@ -76,10 +78,10 @@ function isNodeTypeIn(node: AstNode, typesList: string[]): boolean { ...@@ -76,10 +78,10 @@ function isNodeTypeIn(node: AstNode, typesList: string[]): boolean {
*/ */
function getOverridedSender (userdoc: UserDocumentation, signature: string, methodIdentifiers: Record <string, string>): string | null { function getOverridedSender (userdoc: UserDocumentation, signature: string, methodIdentifiers: Record <string, string>): string | null {
const fullName: string | null = getFunctionFullName(signature, methodIdentifiers) const fullName: string | null = getFunctionFullName(signature, methodIdentifiers)
const senderRegex = /#sender: account-+(\d)/g const senderRegex = /#sender: account-+(\d)/g
const accountIndex: RegExpExecArray | null = fullName && userdoc.methods[fullName] ? senderRegex.exec(userdoc.methods[fullName].notice) : null const accountIndex: RegExpExecArray | null = fullName && userdoc.methods[fullName] ? senderRegex.exec(userdoc.methods[fullName].notice) : null
return fullName && accountIndex ? accountIndex[1] : null return fullName && accountIndex ? accountIndex[1] : null
} }
/** /**
...@@ -90,10 +92,10 @@ function getOverridedSender (userdoc: UserDocumentation, signature: string, meth ...@@ -90,10 +92,10 @@ function getOverridedSender (userdoc: UserDocumentation, signature: string, meth
*/ */
function getProvidedValue (userdoc: UserDocumentation, signature: string, methodIdentifiers: Record <string, string>): string | null { function getProvidedValue (userdoc: UserDocumentation, signature: string, methodIdentifiers: Record <string, string>): string | null {
const fullName: string | null = getFunctionFullName(signature, methodIdentifiers) const fullName: string | null = getFunctionFullName(signature, methodIdentifiers)
const valueRegex = /#value: (\d+)/g const valueRegex = /#value: (\d+)/g
const value: RegExpExecArray | null = fullName && userdoc.methods[fullName] ? valueRegex.exec(userdoc.methods[fullName].notice) : null const value: RegExpExecArray | null = fullName && userdoc.methods[fullName] ? valueRegex.exec(userdoc.methods[fullName].notice) : null
return fullName && value ? value[1] : null return fullName && value ? value[1] : null
} }
/** /**
...@@ -103,36 +105,36 @@ function getProvidedValue (userdoc: UserDocumentation, signature: string, method ...@@ -103,36 +105,36 @@ function getProvidedValue (userdoc: UserDocumentation, signature: string, method
*/ */
function getAvailableFunctions (fileAST: AstNode, testContractName: string): string[] { function getAvailableFunctions (fileAST: AstNode, testContractName: string): string[] {
let funcList: string[] = [] let funcList: string[] = []
if(fileAST.nodes && fileAST.nodes.length > 0) { if (fileAST.nodes && fileAST.nodes.length > 0) {
const contractAST: AstNode[] = fileAST.nodes.filter(node => isNodeName(node, testContractName) && isNodeType(node, 'ContractDefinition')) const contractAST: AstNode[] = fileAST.nodes.filter(node => isNodeName(node, testContractName) && isNodeType(node, 'ContractDefinition'))
if(contractAST.length > 0 && contractAST[0].nodes) { if (contractAST.length > 0 && contractAST[0].nodes) {
const funcNodes: AstNode[] = contractAST[0].nodes.filter(node => ((node.kind === "function" && isNodeType(node, 'FunctionDefinition')) || isNodeType(node, 'FunctionDefinition'))) const funcNodes: AstNode[] = contractAST[0].nodes.filter(node => ((node.kind === 'function' && isNodeType(node, 'FunctionDefinition')) || isNodeType(node, 'FunctionDefinition')))
funcList = funcNodes.map(node => node.name) funcList = funcNodes.map(node => node.name)
}
} }
return funcList; }
return funcList
} }
function getAssertMethodLocation (fileAST: AstNode, testContractName: string, functionName: string, assertMethod: string): string { function getAssertMethodLocation (fileAST: AstNode, testContractName: string, functionName: string, assertMethod: string): string {
if(fileAST.nodes?.length) { if (fileAST.nodes?.length) {
const contractAST: AstNode = fileAST.nodes.find(node => isNodeName(node, testContractName) && isNodeType(node, 'ContractDefinition')) const contractAST: AstNode = fileAST.nodes.find(node => isNodeName(node, testContractName) && isNodeType(node, 'ContractDefinition'))
if(contractAST?.nodes?.length) { if (contractAST?.nodes?.length) {
const funcNode: AstNode = contractAST.nodes.find(node => isNodeName(node, functionName) && isNodeType(node, 'FunctionDefinition')) const funcNode: AstNode = contractAST.nodes.find(node => isNodeName(node, functionName) && isNodeType(node, 'FunctionDefinition'))
// Check if statement nodeType is 'ExpressionStatement' or 'Return', for examples: // Check if statement nodeType is 'ExpressionStatement' or 'Return', for examples:
// Assert.equal(foo.get(), 100, "initial value is not correct"); // Assert.equal(foo.get(), 100, "initial value is not correct");
// return Assert.equal(foo.get(), 100, "initial value is not correct"); // return Assert.equal(foo.get(), 100, "initial value is not correct");
const expressions = funcNode.body.statements.filter(s => const expressions = funcNode.body.statements.filter(s =>
isNodeTypeIn(s, ['ExpressionStatement', 'Return']) isNodeTypeIn(s, ['ExpressionStatement', 'Return']) &&
&& isNodeType(s.expression, 'FunctionCall')) isNodeType(s.expression, 'FunctionCall'))
const assetExpression = expressions.find(e => e.expression.expression const assetExpression = expressions.find(e => e.expression.expression &&
&& isNodeType(e.expression.expression, 'MemberAccess') isNodeType(e.expression.expression, 'MemberAccess') &&
&& e.expression.expression.memberName === assertMethod e.expression.expression.memberName === assertMethod &&
&& isNodeName(e.expression.expression.expression, 'Assert') isNodeName(e.expression.expression.expression, 'Assert')
) )
return assetExpression?.expression?.src return assetExpression?.expression?.src
}
} }
}
} }
/** /**
...@@ -142,15 +144,15 @@ function getAssertMethodLocation (fileAST: AstNode, testContractName: string, fu ...@@ -142,15 +144,15 @@ function getAssertMethodLocation (fileAST: AstNode, testContractName: string, fu
*/ */
function getTestFunctionsInterface (jsonInterface: FunctionDescription[], funcList: string[]): FunctionDescription[] { function getTestFunctionsInterface (jsonInterface: FunctionDescription[], funcList: string[]): FunctionDescription[] {
const functionsInterface: FunctionDescription[] = [] const functionsInterface: FunctionDescription[] = []
const specialFunctions: string[] = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'] const specialFunctions: string[] = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach']
for(const func of funcList){ for (const func of funcList) {
if(!specialFunctions.includes(func)) { if (!specialFunctions.includes(func)) {
const funcInterface: FunctionDescription | undefined = jsonInterface.find(node => node.type === 'function' && node.name === func) const funcInterface: FunctionDescription | undefined = jsonInterface.find(node => node.type === 'function' && node.name === func)
if(funcInterface) functionsInterface.push(funcInterface) if (funcInterface) functionsInterface.push(funcInterface)
}
} }
return functionsInterface }
return functionsInterface
} }
/** /**
...@@ -159,15 +161,15 @@ function getTestFunctionsInterface (jsonInterface: FunctionDescription[], funcLi ...@@ -159,15 +161,15 @@ function getTestFunctionsInterface (jsonInterface: FunctionDescription[], funcLi
*/ */
function getSpecialFunctionsInterface (jsonInterface: FunctionDescription[]): Record<string, FunctionDescription> { function getSpecialFunctionsInterface (jsonInterface: FunctionDescription[]): Record<string, FunctionDescription> {
const specialFunctionsInterface: Record<string, FunctionDescription> = {} const specialFunctionsInterface: Record<string, FunctionDescription> = {}
const funcList: string[] = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'] const funcList: string[] = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach']
for(const func of funcList){ for (const func of funcList) {
const funcInterface: FunctionDescription | undefined = jsonInterface.find(node => node.type === 'function' && node.name === func) const funcInterface: FunctionDescription | undefined = jsonInterface.find(node => node.type === 'function' && node.name === func)
if(funcInterface) { if (funcInterface) {
specialFunctionsInterface[func] = funcInterface specialFunctionsInterface[func] = funcInterface
}
} }
return specialFunctionsInterface }
return specialFunctionsInterface
} }
/** /**
...@@ -178,184 +180,182 @@ function getSpecialFunctionsInterface (jsonInterface: FunctionDescription[]): Re ...@@ -178,184 +180,182 @@ function getSpecialFunctionsInterface (jsonInterface: FunctionDescription[]): Re
*/ */
function createRunList (jsonInterface: FunctionDescription[], fileAST: AstNode, testContractName: string): RunListInterface[] { function createRunList (jsonInterface: FunctionDescription[], fileAST: AstNode, testContractName: string): RunListInterface[] {
const availableFunctions: string[] = getAvailableFunctions(fileAST, testContractName) const availableFunctions: string[] = getAvailableFunctions(fileAST, testContractName)
const testFunctionsInterface: FunctionDescription[] = getTestFunctionsInterface(jsonInterface, availableFunctions) const testFunctionsInterface: FunctionDescription[] = getTestFunctionsInterface(jsonInterface, availableFunctions)
const specialFunctionsInterface: Record<string, FunctionDescription> = getSpecialFunctionsInterface(jsonInterface) const specialFunctionsInterface: Record<string, FunctionDescription> = getSpecialFunctionsInterface(jsonInterface)
const runList: RunListInterface[] = [] const runList: RunListInterface[] = []
if (availableFunctions.includes('beforeAll')) { if (availableFunctions.includes('beforeAll')) {
const func = specialFunctionsInterface['beforeAll'] const func = specialFunctionsInterface['beforeAll']
runList.push({ name: 'beforeAll', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) }) runList.push({ name: 'beforeAll', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) })
}
for (const func of testFunctionsInterface) {
if (availableFunctions.includes('beforeEach')) {
const func = specialFunctionsInterface['beforeEach']
runList.push({ name: 'beforeEach', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) })
} }
if (func.name && func.inputs) runList.push({ name: func.name, inputs: func.inputs, signature: func.signature, type: 'test', constant: isConstant(func), payable: isPayable(func) })
for (const func of testFunctionsInterface) { if (availableFunctions.indexOf('afterEach') >= 0) {
if (availableFunctions.includes('beforeEach')) { const func = specialFunctionsInterface['afterEach']
const func = specialFunctionsInterface['beforeEach'] runList.push({ name: 'afterEach', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) })
runList.push({ name: 'beforeEach', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) })
}
if(func.name && func.inputs) runList.push({ name: func.name, inputs: func.inputs, signature: func.signature, type: 'test', constant: isConstant(func), payable: isPayable(func) })
if (availableFunctions.indexOf('afterEach') >= 0) {
const func = specialFunctionsInterface['afterEach']
runList.push({ name: 'afterEach', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) })
}
} }
}
if (availableFunctions.indexOf('afterAll') >= 0) { if (availableFunctions.indexOf('afterAll') >= 0) {
const func = specialFunctionsInterface['afterAll'] const func = specialFunctionsInterface['afterAll']
runList.push({ name: 'afterAll', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) }) runList.push({ name: 'afterAll', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) })
} }
return runList return runList
} }
export function runTest (testName: string, testObject: any, contractDetails: CompiledContract, fileAST: AstNode, opts: Options, testCallback: TestCbInterface, resultsCallback: ResultCbInterface): void { export function runTest (testName: string, testObject: any, contractDetails: CompiledContract, fileAST: AstNode, opts: Options, testCallback: TestCbInterface, resultsCallback: ResultCbInterface): void {
let passingNum = 0 let passingNum = 0
let failureNum = 0 let failureNum = 0
let timePassed = 0 let timePassed = 0
const isJSONInterfaceAvailable = testObject && testObject.options && testObject.options.jsonInterface const isJSONInterfaceAvailable = testObject && testObject.options && testObject.options.jsonInterface
if(!isJSONInterfaceAvailable) if (!isJSONInterfaceAvailable) { return resultsCallback(new Error('Contract interface not available'), { passingNum, failureNum, timePassed }) }
return resultsCallback(new Error('Contract interface not available'), { passingNum, failureNum, timePassed }) const runList: RunListInterface[] = createRunList(testObject.options.jsonInterface, fileAST, testName)
const runList: RunListInterface[] = createRunList(testObject.options.jsonInterface, fileAST, testName) const web3 = new Web3()
const web3 = new Web3() const accts: TestResultInterface = {
const accts: TestResultInterface = { type: 'accountList',
type: 'accountList', value: opts.accounts
value: opts.accounts }
} testCallback(undefined, accts)
testCallback(undefined, accts)
const resp: TestResultInterface = {
const resp: TestResultInterface = { type: 'contract',
type: 'contract', value: testName,
value: testName, filename: testObject.filename
filename: testObject.filename }
testCallback(undefined, resp)
async.eachOfLimit(runList, 1, function (func, index, next) {
let sender: string | null = null
if (func.signature) {
sender = getOverridedSender(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers)
if (opts.accounts && sender) {
sender = opts.accounts[sender]
}
} }
testCallback(undefined, resp) let sendParams: Record<string, string> | null = null
async.eachOfLimit(runList, 1, function (func, index, next) { if (sender) sendParams = { from: sender }
let sender: string | null = null if (func.inputs && func.inputs.length > 0) { return resultsCallback(new Error(`Method '${func.name}' can not have parameters inside a test contract`), { passingNum, failureNum, timePassed }) }
if (func.signature) { const method = testObject.methods[func.name].apply(testObject.methods[func.name], [])
sender = getOverridedSender(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers) const startTime = Date.now()
if (opts.accounts && sender) { if (func.constant) {
sender = opts.accounts[sender] method.call(sendParams).then((result) => {
} const time = (Date.now() - startTime) / 1000.0
} if (result) {
let sendParams: Record<string, string> | null = null const resp: TestResultInterface = {
if (sender) sendParams = { from: sender } type: 'testPass',
if(func.inputs && func.inputs.length > 0) value: changeCase.sentenceCase(func.name),
return resultsCallback(new Error(`Method '${func.name}' can not have parameters inside a test contract`), { passingNum, failureNum, timePassed }) filename: testObject.filename,
const method = testObject.methods[func.name].apply(testObject.methods[func.name], []) time: time,
const startTime = Date.now() context: testName
if (func.constant) { }
method.call(sendParams).then((result) => { testCallback(undefined, resp)
const time = (Date.now() - startTime) / 1000.0 passingNum += 1
if (result) { timePassed += time
const resp: TestResultInterface = {
type: 'testPass',
value: changeCase.sentenceCase(func.name),
filename: testObject.filename,
time: time,
context: testName
}
testCallback(undefined, resp)
passingNum += 1
timePassed += time
} else {
const resp: TestResultInterface = {
type: 'testFailure',
value: changeCase.sentenceCase(func.name),
filename: testObject.filename,
time: time,
errMsg: 'function returned false',
context: testName
}
testCallback(undefined, resp)
failureNum += 1
timePassed += time
}
next()
})
} else { } else {
if(func.payable) { const resp: TestResultInterface = {
const value = getProvidedValue(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers) type: 'testFailure',
if(value) { value: changeCase.sentenceCase(func.name),
if(sendParams) sendParams.value = value filename: testObject.filename,
else sendParams = { value } time: time,
} errMsg: 'function returned false',
} context: testName
method.send(sendParams).on('receipt', (receipt) => { }
try { testCallback(undefined, resp)
const time: number = (Date.now() - startTime) / 1000.0 failureNum += 1
const assertionEventHashes = assertionEvents.map(e => Web3.utils.sha3(e.name + '(' + e.params.join() + ')') ) timePassed += time
let testPassed = false }
for (const i in receipt.events) { next()
let events = receipt.events[i] })
if (!Array.isArray(events)) events = [events] } else {
for (const event of events) { if (func.payable) {
const eIndex = assertionEventHashes.indexOf(event.raw.topics[0]) // event name topic will always be at index 0 const value = getProvidedValue(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers)
if (eIndex >= 0) { if (value) {
const testEvent = web3.eth.abi.decodeParameters(assertionEvents[eIndex].params, event.raw.data) if (sendParams) sendParams.value = value
if (!testEvent[0]) { else sendParams = { value }
const assertMethod = testEvent[2] }
if (assertMethod === 'ok') { // for 'Assert.ok' method }
testEvent[3] = 'false' method.send(sendParams).on('receipt', (receipt) => {
testEvent[4] = 'true' try {
} const time: number = (Date.now() - startTime) / 1000.0
const location = getAssertMethodLocation(fileAST, testName, func.name, assertMethod) const assertionEventHashes = assertionEvents.map(e => Web3.utils.sha3(e.name + '(' + e.params.join() + ')'))
const resp: TestResultInterface = { let testPassed = false
type: 'testFailure', for (const i in receipt.events) {
value: changeCase.sentenceCase(func.name), let events = receipt.events[i]
filename: testObject.filename, if (!Array.isArray(events)) events = [events]
time: time, for (const event of events) {
errMsg: testEvent[1], const eIndex = assertionEventHashes.indexOf(event.raw.topics[0]) // event name topic will always be at index 0
context: testName, if (eIndex >= 0) {
assertMethod, const testEvent = web3.eth.abi.decodeParameters(assertionEvents[eIndex].params, event.raw.data)
returned: testEvent[3], if (!testEvent[0]) {
expected: testEvent[4], const assertMethod = testEvent[2]
location if (assertMethod === 'ok') { // for 'Assert.ok' method
}; testEvent[3] = 'false'
testCallback(undefined, resp) testEvent[4] = 'true'
failureNum += 1 }
timePassed += time const location = getAssertMethodLocation(fileAST, testName, func.name, assertMethod)
return next() const resp: TestResultInterface = {
}
testPassed = true
}
}
}
if (testPassed) {
const resp: TestResultInterface = {
type: 'testPass',
value: changeCase.sentenceCase(func.name),
filename: testObject.filename,
time: time,
context: testName
}
testCallback(undefined, resp)
passingNum += 1
timePassed += time
}
return next()
} catch (err) {
console.error(err)
return next(err)
}
}).on('error', function (err: Error) {
const time: number = (Date.now() - startTime) / 1000.0
const resp: TestResultInterface = {
type: 'testFailure', type: 'testFailure',
value: changeCase.sentenceCase(func.name), value: changeCase.sentenceCase(func.name),
filename: testObject.filename, filename: testObject.filename,
time: time, time: time,
errMsg: err.message, errMsg: testEvent[1],
context: testName context: testName,
}; assertMethod,
returned: testEvent[3],
expected: testEvent[4],
location
}
testCallback(undefined, resp) testCallback(undefined, resp)
failureNum += 1 failureNum += 1
timePassed += time timePassed += time
return next() return next()
}) }
testPassed = true
}
}
}
if (testPassed) {
const resp: TestResultInterface = {
type: 'testPass',
value: changeCase.sentenceCase(func.name),
filename: testObject.filename,
time: time,
context: testName
}
testCallback(undefined, resp)
passingNum += 1
timePassed += time
}
return next()
} catch (err) {
console.error(err)
return next(err)
} }
}, function(error) { }).on('error', function (err: Error) {
resultsCallback(error, { passingNum, failureNum, timePassed }) const time: number = (Date.now() - startTime) / 1000.0
}) const resp: TestResultInterface = {
type: 'testFailure',
value: changeCase.sentenceCase(func.name),
filename: testObject.filename,
time: time,
errMsg: err.message,
context: testName
}
testCallback(undefined, resp)
failureNum += 1
timePassed += time
return next()
})
}
}, function (error) {
resultsCallback(error, { passingNum, failureNum, timePassed })
})
} }
...@@ -51,8 +51,8 @@ export interface Options { ...@@ -51,8 +51,8 @@ export interface Options {
export interface CompilerConfiguration { export interface CompilerConfiguration {
currentCompilerUrl: string, currentCompilerUrl: string,
evmVersion: string, evmVersion: string,
optimize: boolean, optimize: boolean,
usingWorker?: boolean, usingWorker?: boolean,
runs: number runs: number
} }
...@@ -64,7 +64,7 @@ export interface CompilationErrors { ...@@ -64,7 +64,7 @@ export interface CompilationErrors {
} }
export class CompilationErrors extends Error { export class CompilationErrors extends Error {
constructor(errors: Array<any>) { constructor (errors: Array<any>) {
const mapError = errors.map((e) => { return e.formattedMessage || e.message }) const mapError = errors.map((e) => { return e.formattedMessage || e.message })
super(mapError.join('\n')) super(mapError.join('\n'))
this.errors = errors this.errors = errors
...@@ -74,9 +74,9 @@ export class CompilationErrors extends Error { ...@@ -74,9 +74,9 @@ export class CompilationErrors extends Error {
/** sources object with name of the file and content **/ /** sources object with name of the file and content **/
//////////// /// /////////
// SOURCE // // SOURCE //
//////////// /// /////////
export interface CompilationSource { export interface CompilationSource {
/** Identifier of the source (used in source maps) */ /** Identifier of the source (used in source maps) */
id: number id: number
...@@ -162,9 +162,9 @@ export interface CompiledContract { ...@@ -162,9 +162,9 @@ export interface CompiledContract {
} }
} }
///////// /// //////
// ABI // // ABI //
///////// /// //////
export type ABIDescription = FunctionDescription | EventDescription export type ABIDescription = FunctionDescription | EventDescription
export interface FunctionDescription { export interface FunctionDescription {
...@@ -227,9 +227,9 @@ export type ABITypeParameter = ...@@ -227,9 +227,9 @@ export type ABITypeParameter =
| 'tuple[]' | 'tuple[]'
| string // Fallback | string // Fallback
/////////////////////////// /// ////////////////////////
// NATURAL SPECIFICATION // // NATURAL SPECIFICATION //
/////////////////////////// /// ////////////////////////
// Userdoc // Userdoc
export interface UserDocumentation { export interface UserDocumentation {
...@@ -267,9 +267,9 @@ export interface DevMethodDoc { ...@@ -267,9 +267,9 @@ export interface DevMethodDoc {
} }
} }
////////////// /// ///////////
// BYTECODE // // BYTECODE //
////////////// /// ///////////
export interface BytecodeObject { export interface BytecodeObject {
/** The bytecode as a hex string. */ /** The bytecode as a hex string. */
object: string object: string
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
"workspace-schematic": "nx workspace-schematic", "workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph", "dep-graph": "nx dep-graph",
"help": "nx help", "help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui", "lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs & lerna publish --skip-git & npm run bumpVersion:libs", "publish:libs": "npm run build:libs & lerna publish --skip-git & npm run bumpVersion:libs",
......
...@@ -347,7 +347,7 @@ ...@@ -347,7 +347,7 @@
"linter": "eslint", "linter": "eslint",
"config": "libs/remix-tests/.eslintrc", "config": "libs/remix-tests/.eslintrc",
"tsConfig": ["libs/remix-tests/tsconfig.lib.json"], "tsConfig": ["libs/remix-tests/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "libs/remix-tests/tests/**/*"] "exclude": ["**/node_modules/**", "libs/remix-tests/tests/**/*", "**/dist/**"]
} }
}, },
"test": { "test": {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment