Merge pull request #1128 from 0mkara/remix-tests-ts

Remix Tests Typescript implementation
pull/7/head
yann300 6 years ago committed by GitHub
commit af9aae696e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      remix-tests/bin/remix-tests
  2. 1
      remix-tests/examples/simple_storage.sol
  3. 20
      remix-tests/package.json
  4. 129
      remix-tests/src/compiler.js
  5. 126
      remix-tests/src/compiler.ts
  6. 109
      remix-tests/src/deployer.js
  7. 110
      remix-tests/src/deployer.ts
  8. 20
      remix-tests/src/fileSystem.ts
  9. 20
      remix-tests/src/fs.js
  10. 10
      remix-tests/src/index.js
  11. 5
      remix-tests/src/index.ts
  12. 58
      remix-tests/src/logger.js
  13. 57
      remix-tests/src/logger.ts
  14. 65
      remix-tests/src/run.js
  15. 65
      remix-tests/src/run.ts
  16. 133
      remix-tests/src/runTestFiles.js
  17. 142
      remix-tests/src/runTestFiles.ts
  18. 107
      remix-tests/src/runTestSources.js
  19. 110
      remix-tests/src/runTestSources.ts
  20. 149
      remix-tests/src/testRunner.js
  21. 174
      remix-tests/src/testRunner.ts
  22. 39
      remix-tests/src/types.ts
  23. 1
      remix-tests/tests/examples_1/simple_storage_test.sol
  24. 1
      remix-tests/tests/examples_2/simple_storage.sol
  25. 2
      remix-tests/tests/examples_4/SafeMath.sol
  26. 12
      remix-tests/tests/examples_4/SafeMathProxy.sol
  27. 37
      remix-tests/tests/examples_4/SafeMath_test.sol
  28. 198
      remix-tests/tests/testRunner.js
  29. 214
      remix-tests/tests/testRunner.ts
  30. 24
      remix-tests/tsconfig.json

@ -1,4 +1,4 @@
#!/usr/bin/env node #!/usr/bin/env node
require('../src/run.js'); require('../dist/run.js');

@ -13,5 +13,4 @@ contract SimpleStorage {
function get() public view returns (uint retVal) { function get() public view returns (uint retVal) {
return storedData; return storedData;
} }
} }

@ -2,7 +2,8 @@
"name": "remix-tests", "name": "remix-tests",
"version": "0.1.1", "version": "0.1.1",
"description": "Tests for the Ethereum tool suite Remix", "description": "Tests for the Ethereum tool suite Remix",
"main": "./src/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts",
"contributors": [ "contributors": [
{ {
"name": "Iuri Matias", "name": "Iuri Matias",
@ -17,8 +18,8 @@
"remix-tests": "./bin/remix-tests" "remix-tests": "./bin/remix-tests"
}, },
"scripts": { "scripts": {
"lint": "standard", "build": "tsc",
"test": "standard && mocha tests/ -t 300000" "test": "tsc && mocha --require ts-node/register tests/*.ts -t 300000"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -51,8 +52,17 @@
"yo-yoify": "latest" "yo-yoify": "latest"
}, },
"devDependencies": { "devDependencies": {
"mocha": "^5.1.0", "@types/async": "^2.4.0",
"@types/colors": "^1.2.1",
"@types/web3": "^1.0.18",
"@types/color-support": "^1.1.0",
"@types/commander": "^2.12.2",
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.21",
"babel-preset-es2017": "^6.24.1", "babel-preset-es2017": "^6.24.1",
"standard": "^10.0.3" "mocha": "^5.1.0",
"standard": "^10.0.3",
"ts-node": "^8.0.2",
"typescript": "^3.3.1"
} }
} }

@ -1,129 +0,0 @@
/* eslint no-extend-native: "warn" */
let fs = require('./fs')
var async = require('async')
var path = require('path')
let RemixCompiler = require('remix-solidity').Compiler
String.prototype.regexIndexOf = function (regex, startpos) { // eslint-disable-line
var indexOf = this.substring(startpos || 0).search(regex)
return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf
}
function writeTestAccountsContract (accounts) {
var testAccountContract = require('../sol/tests_accounts.sol.js')
var body = 'address[' + accounts.length + '] memory accounts;'
if (!accounts.length) body += ';'
else {
accounts.map((address, index) => {
body += `\naccounts[${index}] = ${address};\n`
})
}
return testAccountContract.replace('>accounts<', body)
}
var userAgent = (typeof (navigator) !== 'undefined') && navigator.userAgent ? navigator.userAgent.toLowerCase() : '-'
var isBrowser = !(typeof (window) === 'undefined' || userAgent.indexOf(' electron/') > -1)
// TODO: replace this with remix's own compiler code
function compileFileOrFiles (filename, isDirectory, opts, cb) {
let compiler
let accounts = opts.accounts || []
const sources = {
'tests.sol': { content: require('../sol/tests.sol.js') },
'remix_tests.sol': { content: require('../sol/tests.sol.js') },
'remix_accounts.sol': { content: writeTestAccountsContract(accounts) }
}
const filepath = (isDirectory ? filename : path.dirname(filename))
// TODO: for now assumes filepath dir contains all tests, later all this
// should be replaced with remix's & browser solidity compiler code
// This logic is wrong
// We should only look into current file if a full file name with path is given
// We should only walk through directory if a directory name is passed
try {
// walkSync only if it is a directory
fs.walkSync(filepath, foundpath => {
// only process .sol files
if (foundpath.split('.').pop() === 'sol') {
let c = fs.readFileSync(foundpath).toString()
const s = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm
let includeTestLibs = '\nimport \'remix_tests.sol\';\n'
if (foundpath.indexOf('_test.sol') > 0 && c.regexIndexOf(s) < 0) {
c = includeTestLibs.concat(c)
}
sources[foundpath] = { content: c }
}
})
} catch (e) {
throw e
} finally {
async.waterfall([
function loadCompiler (next) {
compiler = new RemixCompiler()
compiler.onInternalCompilerLoaded()
// compiler.event.register('compilerLoaded', this, function (version) {
next()
// });
},
function doCompilation (next) {
compiler.event.register('compilationFinished', this, function (success, data, source) {
next(null, data)
})
compiler.compile(sources, filepath)
}
], function (err, result) {
let errors = (result.errors || []).filter((e) => e.type === 'Error' || e.severity === 'error')
if (errors.length > 0) {
if (!isBrowser) require('signale').fatal(errors)
return cb(new Error('errors compiling'))
}
cb(err, result.contracts)
})
}
}
function compileContractSources (sources, importFileCb, opts, cb) {
let compiler, filepath
let accounts = opts.accounts || []
// Iterate over sources keys. Inject test libraries. Inject test library import statements.
if (!('remix_tests.sol' in sources) && !('tests.sol' in sources)) {
sources['remix_tests.sol'] = { content: require('../sol/tests.sol.js') }
sources['remix_accounts.sol'] = { content: writeTestAccountsContract(accounts) }
}
const s = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm
let includeTestLibs = '\nimport \'remix_tests.sol\';\n'
for (let file in sources) {
const c = sources[file].content
if (file.indexOf('_test.sol') > 0 && c && c.regexIndexOf(s) < 0) {
sources[file].content = includeTestLibs.concat(c)
}
}
async.waterfall([
function loadCompiler (next) {
compiler = new RemixCompiler(importFileCb)
compiler.onInternalCompilerLoaded()
// compiler.event.register('compilerLoaded', this, function (version) {
next()
// });
},
function doCompilation (next) {
compiler.event.register('compilationFinished', this, function (success, data, source) {
next(null, data)
})
compiler.compile(sources, filepath)
}
], function (err, result) {
let errors = (result.errors || []).filter((e) => e.type === 'Error' || e.severity === 'error')
if (errors.length > 0) {
if (!isBrowser) require('signale').fatal(errors)
return cb(new Error('errors compiling'))
}
cb(err, result.contracts)
})
}
module.exports = {
compileFileOrFiles: compileFileOrFiles,
compileContractSources: compileContractSources
}

@ -0,0 +1,126 @@
import fs from './fileSystem'
import async from 'async'
import path from 'path'
let RemixCompiler = require('remix-solidity').Compiler
import { SrcIfc } from './types'
function regexIndexOf (inputString: string, regex: RegExp, startpos: number = 0) {
const indexOf = inputString.substring(startpos).search(regex)
return (indexOf >= 0) ? (indexOf + (startpos)) : indexOf
}
function writeTestAccountsContract (accounts: string[]) {
const testAccountContract = require('../sol/tests_accounts.sol.js')
let body = `address[${accounts.length}] memory accounts;`
if (!accounts.length) body += ';'
else {
accounts.map((address, index) => {
body += `\naccounts[${index}] = ${address};\n`
})
}
return testAccountContract.replace('>accounts<', body)
}
const userAgent = (typeof (navigator) !== 'undefined') && navigator.userAgent ? navigator.userAgent.toLowerCase() : '-'
const isBrowser = !(typeof (window) === 'undefined' || userAgent.indexOf(' electron/') > -1)
// TODO: replace this with remix's own compiler code
export function compileFileOrFiles(filename: string, isDirectory: boolean, opts: any, cb: Function) {
let compiler: any
let accounts = opts.accounts || []
const sources = {
'tests.sol': { content: require('../sol/tests.sol.js') },
'remix_tests.sol': { content: require('../sol/tests.sol.js') },
'remix_accounts.sol': { content: writeTestAccountsContract(accounts) }
}
const filepath = (isDirectory ? filename : path.dirname(filename))
// TODO: for now assumes filepath dir contains all tests, later all this
// should be replaced with remix's & browser solidity compiler code
// This logic is wrong
// We should only look into current file if a full file name with path is given
// We should only walk through directory if a directory name is passed
try {
// walkSync only if it is a directory
fs.walkSync(filepath, (foundpath: string) => {
// only process .sol files
if (foundpath.split('.').pop() === 'sol') {
let c = fs.readFileSync(foundpath).toString()
const s = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm
let includeTestLibs = '\nimport \'remix_tests.sol\';\n'
if (foundpath.indexOf('_test.sol') > 0 && regexIndexOf(c, s) < 0) {
c = includeTestLibs.concat(c)
}
sources[foundpath] = { content: c }
}
})
} catch (e) {
throw e
} finally {
async.waterfall([
function loadCompiler(next: Function) {
compiler = new RemixCompiler()
compiler.onInternalCompilerLoaded()
// compiler.event.register('compilerLoaded', this, function (version) {
next()
// });
},
function doCompilation(next: Function) {
// @ts-ignore
compiler.event.register('compilationFinished', this, (success, data, source) => {
next(null, data)
})
compiler.compile(sources, filepath)
}
], function (err: Error | null | undefined, result: any) {
let errors = (result.errors || []).filter((e) => e.type === 'Error' || e.severity === 'error')
if (errors.length > 0) {
if (!isBrowser) require('signale').fatal(errors)
return cb(new Error('errors compiling'))
}
cb(err, result.contracts)
})
}
}
export function compileContractSources(sources: SrcIfc, importFileCb: any, opts: any, cb: Function) {
let compiler, filepath: string
let accounts = opts.accounts || []
// Iterate over sources keys. Inject test libraries. Inject test library import statements.
if (!('remix_tests.sol' in sources) && !('tests.sol' in sources)) {
sources['remix_tests.sol'] = { content: require('../sol/tests.sol.js') }
sources['remix_accounts.sol'] = { content: writeTestAccountsContract(accounts) }
}
const s = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm
let includeTestLibs = '\nimport \'remix_tests.sol\';\n'
for (let file in sources) {
const c = sources[file].content
if (file.indexOf('_test.sol') > 0 && c && regexIndexOf(c, s) < 0) {
sources[file].content = includeTestLibs.concat(c)
}
}
async.waterfall([
function loadCompiler (next: Function) {
compiler = new RemixCompiler(importFileCb)
compiler.onInternalCompilerLoaded()
// compiler.event.register('compilerLoaded', this, function (version) {
next()
// });
},
function doCompilation (next: Function) {
// @ts-ignore
compiler.event.register('compilationFinished', this, (success, data, source) => {
next(null, data)
})
compiler.compile(sources, filepath)
}
], function (err: Error | null | undefined , result: any) {
let errors = (result.errors || []).filter((e) => e.type === 'Error' || e.severity === 'error')
if (errors.length > 0) {
if (!isBrowser) require('signale').fatal(errors)
return cb(new Error('errors compiling'))
}
cb(err, result.contracts)
})
}

@ -1,109 +0,0 @@
var async = require('async')
var remixLib = require('remix-lib')
function deployAll (compileResult, web3, callback) {
let compiledObject = {}
let contracts = {}
let accounts = []
async.waterfall([
function getAccountList (next) {
web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts
next()
})
},
function getContractData (next) {
for (let contractFile in compileResult) {
for (let contractName in compileResult[contractFile]) {
let contract = compileResult[contractFile][contractName]
const className = contractName
const filename = contractFile
let abi = contract.abi
let code = contract.evm.bytecode.object
compiledObject[className] = {}
compiledObject[className].abi = abi
compiledObject[className].code = code
compiledObject[className].filename = filename
compiledObject[className].className = className
compiledObject[className].raw = contract
if (contractFile.indexOf('_test.sol') >= 0) {
compiledObject[className].isTest = true
}
}
}
next()
},
function determineContractsToDeploy (next) {
let contractsToDeploy = ['Assert']
let allContracts = Object.keys(compiledObject)
for (let contractName of allContracts) {
if (contractName === 'Assert') {
continue
}
if (compiledObject[contractName].isTest) {
contractsToDeploy.push(contractName)
}
}
next(null, contractsToDeploy)
},
function deployContracts (contractsToDeploy, next) {
var deployRunner = (deployObject, contractObject, contractName, filename, callback) => {
deployObject.estimateGas().then((gasValue) => {
deployObject.send({
from: accounts[0],
gas: Math.ceil(gasValue * 1.2)
}).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
contracts[contractName].filename = filename
callback(null, { result: { createdAddress: receipt.contractAddress } }) // TODO this will only work with JavaScriptV VM
}).on('error', function (err) {
console.dir(err)
callback(err)
})
})
}
async.eachOfLimit(contractsToDeploy, 1, function (contractName, index, nextEach) {
let contract = compiledObject[contractName]
let encodeDataFinalCallback = (error, contractDeployData) => {
if (error) return nextEach(error)
let contractObject = new web3.eth.Contract(contract.abi)
let deployObject = contractObject.deploy({arguments: [], data: '0x' + contractDeployData.dataHex})
deployRunner(deployObject, contractObject, contractName, contract.filename, (error) => { nextEach(error) })
}
let encodeDataStepCallback = (msg) => { console.dir(msg) }
let encodeDataDeployLibraryCallback = (libData, callback) => {
let abi = compiledObject[libData.data.contractName].abi
let code = compiledObject[libData.data.contractName].code
let libraryObject = new web3.eth.Contract(abi)
let deployObject = libraryObject.deploy({arguments: [], data: '0x' + code})
deployRunner(deployObject, libraryObject, libData.data.contractName, contract.filename, callback)
}
let funAbi = null // no need to set the abi for encoding the constructor
let params = '' // we suppose that the test contract does not have any param in the constructor
remixLib.execution.txFormat.encodeConstructorCallAndDeployLibraries(contractName, contract.raw, compileResult, params, funAbi, encodeDataFinalCallback, encodeDataStepCallback, encodeDataDeployLibraryCallback)
}, function () {
next(null, contracts)
})
}
], callback)
}
module.exports = {
deployAll: deployAll
}

@ -0,0 +1,110 @@
import async from 'async'
var remixLib = require('remix-lib')
import Web3 from 'web3'
export function deployAll(compileResult: object, web3: Web3, callback) {
let compiledObject = {}
let contracts = {}
let accounts: string[] = []
async.waterfall([
function getAccountList(next: Function) {
web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts
next()
})
},
function getContractData(next: Function) {
for (let contractFile in compileResult) {
for (let contractName in compileResult[contractFile]) {
let contract = compileResult[contractFile][contractName]
const className = contractName
const filename = contractFile
let abi = contract.abi
let code = contract.evm.bytecode.object
compiledObject[className] = {}
compiledObject[className].abi = abi
compiledObject[className].code = code
compiledObject[className].filename = filename
compiledObject[className].className = className
compiledObject[className].raw = contract
if (contractFile.indexOf('_test.sol') >= 0) {
compiledObject[className].isTest = true
}
}
}
next()
},
function determineContractsToDeploy(next: Function) {
let contractsToDeploy: string[] = ['Assert']
let allContracts = Object.keys(compiledObject)
for (let contractName of allContracts) {
if (contractName === 'Assert') {
continue
}
if (compiledObject[contractName].isTest) {
contractsToDeploy.push(contractName)
}
}
next(null, contractsToDeploy)
},
function deployContracts(contractsToDeploy: string[], next: Function) {
const deployRunner = (deployObject, contractObject, contractName, filename, callback) => {
deployObject.estimateGas().then((gasValue) => {
deployObject.send({
from: accounts[0],
gas: Math.ceil(gasValue * 1.2)
}).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
contracts[contractName].filename = filename
callback(null, { result: { createdAddress: receipt.contractAddress } }) // TODO this will only work with JavaScriptV VM
}).on('error', function (err) {
console.error(err)
callback(err)
})
})
}
async.eachOfLimit(contractsToDeploy, 1, function (contractName, index, nextEach) {
let contract = compiledObject[contractName]
let encodeDataFinalCallback = (error, contractDeployData) => {
if (error) return nextEach(error)
try {
let contractObject = new web3.eth.Contract(contract.abi)
let deployObject = contractObject.deploy({arguments: [], data: '0x' + contractDeployData.dataHex})
deployRunner(deployObject, contractObject, contractName, contract.filename, (error) => { nextEach(error) })
} catch (e) {
throw e
}
}
let encodeDataStepCallback = (msg) => { console.dir(msg) }
let encodeDataDeployLibraryCallback = (libData, callback) => {
let abi = compiledObject[libData.data.contractName].abi
let code = compiledObject[libData.data.contractName].code
let libraryObject = new web3.eth.Contract(abi)
let deployObject = libraryObject.deploy({arguments: [], data: '0x' + code})
deployRunner(deployObject, libraryObject, libData.data.contractName, contract.filename, callback)
}
let funAbi = null // no need to set the abi for encoding the constructor
let params = '' // we suppose that the test contract does not have any param in the constructor
remixLib.execution.txFormat.encodeConstructorCallAndDeployLibraries(contractName, contract.raw, compileResult, params, funAbi, encodeDataFinalCallback, encodeDataStepCallback, encodeDataDeployLibraryCallback)
}, function () {
next(null, contracts)
})
}
], callback)
}

@ -0,0 +1,20 @@
// Extend fs
let fs: any = require('fs')
import path from 'path'
// https://github.com/mikeal/node-utils/blob/master/file/lib/main.js
fs.walkSync = function (start: string, callback: Function) {
fs.readdirSync(start).forEach((name: string) => {
if (name === 'node_modules') {
return // hack
}
const abspath = path.join(start, name)
if (fs.statSync(abspath).isDirectory()) {
fs.walkSync(abspath, callback)
} else {
callback(abspath)
}
})
}
export = fs

@ -1,20 +0,0 @@
// Extend fs
var fs = require('fs')
const path = require('path')
// https://github.com/mikeal/node-utils/blob/master/file/lib/main.js
fs.walkSync = function (start, callback) {
fs.readdirSync(start).forEach(name => {
if (name === 'node_modules') {
return // hack
}
var abspath = path.join(start, name)
if (fs.statSync(abspath).isDirectory()) {
fs.walkSync(abspath, callback)
} else {
callback(abspath)
}
})
}
module.exports = fs

@ -1,10 +0,0 @@
const runTestFiles = require('./runTestFiles.js')
const runTestSources = require('./runTestSources.js')
const TestRunner = require('./testRunner.js')
module.exports = {
runTestFiles: runTestFiles,
runTestSources: runTestSources,
runTest: TestRunner.runTest,
assertLibCode: require('../sol/tests.sol.js')
}

@ -0,0 +1,5 @@
export { runTestFiles } from './runTestFiles'
export { runTestSources } from './runTestSources'
export { runTest } from './testRunner'
export * from './types'
export { assertLibCode } from '../sol/tests.sol.js'

@ -1,58 +0,0 @@
var gray = require('ansi-gray')
const winston = require('winston')
var timestamp = require('time-stamp')
var supportsColor = require('color-support')
function hasFlag (flag) {
return ((typeof (process) !== 'undefined') && (process.argv.indexOf('--' + flag) !== -1))
}
function addColor (str) {
if (hasFlag('no-color')) {
return str
}
if (hasFlag('color')) {
return gray(str)
}
if (supportsColor()) {
return gray(str)
}
return str
}
function getTimestamp () {
return '[' + addColor(timestamp('HH:mm:ss')) + ']'
}
// create winston logger format
const logFmt = winston.format.printf((info) => {
return `${getTimestamp()} ${info.level}: ${info.message}`
})
class Log {
constructor () {
this.logger = winston.createLogger({
level: 'error',
transports: [new winston.transports.Console()],
format: winston.format.combine(
winston.format.colorize({ all: true }),
logFmt
)
})
}
setVerbosity (v) {
this.logger.configure({
level: v,
transports: [new winston.transports.Console()],
format: winston.format.combine(
winston.format.colorize({ all: true }),
logFmt
)
})
}
}
module.exports = {
Log
}

@ -0,0 +1,57 @@
import colors from 'colors'
import winston, { Logger, LoggerOptions } from 'winston'
import timestamp from 'time-stamp';
import supportsColor from 'color-support'
function hasFlag (flag: string) {
return ((typeof (process) !== 'undefined') && (process.argv.indexOf('--' + flag) !== -1))
}
function addColor (str: string) {
if (hasFlag('no-color')) {
return str
}
if (hasFlag('color')) {
return colors.gray(str)
}
if (supportsColor()) {
return colors.gray(str)
}
return str
}
function getTimestamp () {
return '[' + addColor(timestamp('HH:mm:ss')) + ']'
}
// create winston logger format
const logFmt = winston.format.printf((info) => {
return `${getTimestamp()} ${info.level}: ${info.message}`
})
class Log {
logger: Logger;
constructor () {
this.logger = winston.createLogger({
level: 'error',
transports: [new winston.transports.Console()],
format: winston.format.combine(
winston.format.colorize({ all: true }),
logFmt
)
})
}
setVerbosity (v: LoggerOptions["level"]) {
this.logger.configure({
level: v,
transports: [new winston.transports.Console()],
format: winston.format.combine(
winston.format.colorize({ all: true }),
logFmt
)
})
}
}
export = Log

@ -1,65 +0,0 @@
const commander = require('commander')
const Web3 = require('web3')
const RemixTests = require('./index.js')
const fs = require('fs')
const Provider = require('remix-simulator').Provider
const { Log } = require('./logger.js')
const logger = new Log()
const log = logger.logger
require('colors')
// parse verbosity
function mapVerbosity (v) {
const levels = {
0: 'error',
1: 'warn',
2: 'info',
3: 'verbose',
4: 'debug',
5: 'silly'
}
return levels[v]
}
const version = require('../package.json').version
commander.version(version)
commander.command('version').description('output the version number').action(function () {
console.log(version)
})
commander.command('help').description('output usage information').action(function () {
commander.help()
})
// get current version
commander
.option('-v, --verbose <level>', 'run with verbosity', mapVerbosity)
.action(function (filename) {
// Console message
console.log(('\n\t👁 :: Running remix-tests - Unit testing for solidity :: 👁\t\n').white)
// set logger verbosity
if (commander.verbose) {
logger.setVerbosity(commander.verbose)
log.info('verbosity level set to ' + commander.verbose.blue)
}
let web3 = new Web3()
// web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545'))
web3.setProvider(new Provider())
// web3.setProvider(new web3.providers.WebsocketProvider('ws://localhost:8546'))
if (!fs.existsSync(filename)) {
console.error(filename + ' not found')
process.exit(1)
}
let isDirectory = fs.lstatSync(filename).isDirectory()
RemixTests.runTestFiles(filename, isDirectory, web3)
})
if (!process.argv.slice(2).length) {
log.error('Please specify a filename')
process.exit()
}
commander.parse(process.argv)

@ -0,0 +1,65 @@
import commander from 'commander'
import Web3 from 'web3'
import { runTestFiles } from './runTestFiles'
import fs from './fileSystem'
import { Provider } from 'remix-simulator'
import Log from './logger'
const logger = new Log()
const log = logger.logger
import colors from 'colors'
// parse verbosity
function mapVerbosity (v: number) {
const levels = {
0: 'error',
1: 'warn',
2: 'info',
3: 'verbose',
4: 'debug',
5: 'silly'
}
return levels[v]
}
const version = require('../package.json').version
commander.version(version)
commander.command('version').description('output the version number').action(function () {
console.log(version)
})
commander.command('help').description('output usage information').action(function () {
commander.help()
})
// get current version
commander
.option('-v, --verbose <level>', 'run with verbosity', mapVerbosity)
.action((filename) => {
// Console message
console.log(colors.white('\n\t👁\t:: Running remix-tests - Unit testing for solidity ::\t👁\n'))
// set logger verbosity
if (commander.verbose) {
logger.setVerbosity(commander.verbose)
log.info('verbosity level set to ' + commander.verbose.blue)
}
let web3 = new Web3()
// web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545'))
web3.setProvider(new Provider())
// web3.setProvider(new web3.providers.WebsocketProvider('ws://localhost:8546'))
if (!fs.existsSync(filename)) {
console.error(filename + ' not found')
process.exit(1)
}
let isDirectory = fs.lstatSync(filename).isDirectory()
runTestFiles(filename, isDirectory, web3)
})
if (!process.argv.slice(2).length) {
log.error('Please specify a filename')
process.exit()
}
commander.parse(process.argv)

@ -1,133 +0,0 @@
const async = require('async')
const path = require('path')
const fs = require('./fs')
const TestRunner = require('./testRunner.js')
require('colors')
let Compiler = require('./compiler.js')
let Deployer = require('./deployer.js')
const runTestFiles = function (filepath, isDirectory, web3, opts) {
opts = opts || {}
const { Signale } = require('signale')
// signale configuration
const options = {
types: {
result: {
badge: '\t✓',
label: '',
color: 'greenBright'
},
name: {
badge: '\n\t◼',
label: '',
color: 'white'
},
error: {
badge: '\t✘',
label: '',
color: 'redBright'
}
}
}
const signale = new Signale(options)
let accounts = opts.accounts || null
async.waterfall([
function getAccountList (next) {
if (accounts) return next(null)
web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts
next(null)
})
},
function compile (next) {
Compiler.compileFileOrFiles(filepath, isDirectory, { accounts }, next)
},
function deployAllContracts (compilationResult, next) {
Deployer.deployAll(compilationResult, web3, function (err, contracts) {
if (err) {
next(err)
}
next(null, compilationResult, contracts)
})
},
function determineTestContractsToRun (compilationResult, contracts, next) {
let contractsToTest = []
let contractsToTestDetails = []
const gatherContractsFrom = (filename) => {
if (filename.indexOf('_test.sol') < 0) {
return
}
Object.keys(compilationResult[path.basename(filename)]).forEach(contractName => {
contractsToTest.push(contractName)
contractsToTestDetails.push(compilationResult[path.basename(filename)][contractName])
})
}
if (isDirectory) {
fs.walkSync(filepath, foundpath => {
gatherContractsFrom(foundpath)
})
} else {
gatherContractsFrom(filepath)
}
next(null, contractsToTest, contractsToTestDetails, contracts)
},
function runTests (contractsToTest, contractsToTestDetails, contracts, next) {
let totalPassing = 0
let totalFailing = 0
let totalTime = 0
let errors = []
var testCallback = function (result) {
if (result.type === 'contract') {
signale.name(result.value.white)
} else if (result.type === 'testPass') {
signale.result(result.value)
} else if (result.type === 'testFailure') {
signale.result(result.value.red)
errors.push(result)
}
}
var resultsCallback = function (_err, result, cb) {
totalPassing += result.passingNum
totalFailing += result.failureNum
totalTime += result.timePassed
cb()
}
async.eachOfLimit(contractsToTest, 1, (contractName, index, cb) => {
TestRunner.runTest(contractName, contracts[contractName], contractsToTestDetails[index], { accounts }, testCallback, (err, result) => {
if (err) {
return cb(err)
}
resultsCallback(null, result, cb)
})
}, function (err, _results) {
if (err) {
return next(err)
}
console.log('\n')
if (totalPassing > 0) {
console.log((' ' + totalPassing + ' passing ').green + ('(' + totalTime + 's)').grey)
}
if (totalFailing > 0) {
console.log((' ' + totalFailing + ' failing').red)
}
console.log('')
errors.forEach((error, index) => {
console.log(' ' + (index + 1) + ') ' + error.context + ' ' + error.value)
console.log('')
console.log(('\t error: ' + error.errMsg).red)
})
console.log('')
next()
})
}
], function () {
})
}
module.exports = runTestFiles

@ -0,0 +1,142 @@
import async from 'async'
import fs from './fileSystem'
import { runTest } from './testRunner'
import { TestResultInterface, ResultsInterface } from './types'
import colors from 'colors'
import Web3 from 'web3'
import { compileFileOrFiles } from './compiler'
import { deployAll } from './deployer'
export function runTestFiles(filepath: string, isDirectory: boolean, web3: Web3, opts?: object) {
opts = opts || {}
const { Signale } = require('signale')
// signale configuration
const options = {
types: {
result: {
badge: '\t✓',
label: '',
color: 'greenBright'
},
name: {
badge: '\n\t◼',
label: '',
color: 'white'
},
error: {
badge: '\t✘',
label: '',
color: 'redBright'
}
}
}
const signale = new Signale(options)
let accounts = opts['accounts'] || null
async.waterfall([
function getAccountList (next: Function) {
if (accounts) return next(null)
web3.eth.getAccounts((_err: Error | null | undefined, _accounts) => {
accounts = _accounts
next(null)
})
},
function compile(next: Function) {
compileFileOrFiles(filepath, isDirectory, { accounts }, next)
},
function deployAllContracts (compilationResult, next: Function) {
deployAll(compilationResult, web3, (err, contracts) => {
if (err) {
next(err)
}
next(null, compilationResult, contracts)
})
},
function determineTestContractsToRun (compilationResult, contracts, next: Function) {
let contractsToTest: any[] = []
let contractsToTestDetails: any[] = []
const gatherContractsFrom = function(filename: string) {
if (filename.indexOf('_test.sol') < 0) {
return
}
try {
Object.keys(compilationResult[filename]).forEach(contractName => {
contractsToTest.push(contractName)
contractsToTestDetails.push(compilationResult[filename][contractName])
})
} catch (e) {
console.error(e)
}
}
if (isDirectory) {
fs.walkSync(filepath, (foundpath: string) => {
gatherContractsFrom(foundpath)
})
} else {
gatherContractsFrom(filepath)
}
next(null, contractsToTest, contractsToTestDetails, contracts)
},
function runTests(contractsToTest, contractsToTestDetails, contracts, next: Function) {
let totalPassing: number = 0
let totalFailing: number = 0
let totalTime: number = 0
let errors: any[] = []
const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) {
if(err) throw err;
if (result.type === 'contract') {
signale.name(result.value.white)
} else if (result.type === 'testPass') {
signale.result(result.value)
} else if (result.type === 'testFailure') {
signale.result(result.value.red)
errors.push(result)
}
}
const _resultsCallback = (_err: Error | null | undefined, result: ResultsInterface, cb) => {
totalPassing += result.passingNum
totalFailing += result.failureNum
totalTime += result.timePassed
cb()
}
async.eachOfLimit(contractsToTest, 1, (contractName: string, index, cb) => {
try {
runTest(contractName, contracts[contractName], contractsToTestDetails[index], { accounts }, _testCallback, (err, result) => {
if (err) {
console.log(err)
return cb(err)
}
_resultsCallback(null, result, cb)
})
} catch(e) {
console.error(e)
}
}, function (err) {
if (err) {
return next(err)
}
console.log('\n')
if (totalPassing > 0) {
console.log(colors.green(totalPassing + ' passing ') + colors.grey('(' + totalTime + 's)'))
}
if (totalFailing > 0) {
console.log(colors.red(totalFailing + ' failing'))
}
console.log('')
errors.forEach((error, index) => {
console.log(' ' + (index + 1) + ') ' + error.context + ' ' + error.value)
console.log('')
console.log(colors.red('\t error: ' + error.errMsg))
})
console.log('')
next()
})
}
], () => {
})
}

@ -1,107 +0,0 @@
const async = require('async')
require('colors')
let Compiler = require('./compiler.js')
let Deployer = require('./deployer.js')
let TestRunner = require('./testRunner.js')
const Web3 = require('web3')
const Provider = require('remix-simulator').Provider
var createWeb3Provider = function () {
let web3 = new Web3()
web3.setProvider(new Provider())
return web3
}
const runTestSources = function (contractSources, testCallback, resultCallback, finalCallback, importFileCb, opts) {
opts = opts || {}
let web3 = opts.web3 || createWeb3Provider()
let accounts = opts.accounts || null
async.waterfall([
function getAccountList (next) {
if (accounts) return next()
web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts
next()
})
},
function compile (next) {
Compiler.compileContractSources(contractSources, importFileCb, next)
},
function deployAllContracts (compilationResult, next) {
Deployer.deployAll(compilationResult, web3, function (err, contracts) {
if (err) {
next(err)
}
next(null, compilationResult, contracts)
})
},
function determineTestContractsToRun (compilationResult, contracts, next) {
let contractsToTest = []
let contractsToTestDetails = []
for (let filename in compilationResult) {
if (filename.indexOf('_test.sol') < 0) {
continue
}
Object.keys(compilationResult[filename]).forEach(contractName => {
contractsToTestDetails.push(compilationResult[filename][contractName])
contractsToTest.push(contractName)
})
}
next(null, contractsToTest, contractsToTestDetails, contracts)
},
function runTests (contractsToTest, contractsToTestDetails, contracts, next) {
let totalPassing = 0
let totalFailing = 0
let totalTime = 0
let errors = []
var _testCallback = function (result) {
if (result.type === 'testFailure') {
errors.push(result)
}
testCallback(result)
}
var _resultsCallback = function (_err, result, cb) {
resultCallback(_err, result, () => {})
totalPassing += result.passingNum
totalFailing += result.failureNum
totalTime += result.timePassed
cb()
}
async.eachOfLimit(contractsToTest, 1, (contractName, index, cb) => {
TestRunner.runTest(contractName, contracts[contractName], contractsToTestDetails[index], { accounts }, _testCallback, (err, result) => {
if (err) {
return cb(err)
}
_resultsCallback(null, result, cb)
})
}, function (err, _results) {
if (err) {
return next(err)
}
let finalResults = {}
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})
})
next(null, finalResults)
})
}
], finalCallback)
}
module.exports = runTestSources

@ -0,0 +1,110 @@
import async from 'async'
require('colors')
import { compileContractSources } from './compiler'
import { deployAll } from './deployer'
import { runTest } from './testRunner'
import Web3 = require('web3')
import Provider from 'remix-simulator'
import { FinalResult } from './types'
const createWeb3Provider = function () {
let web3 = new Web3()
web3.setProvider(new Provider())
return web3
}
export function runTestSources(contractSources, testCallback, resultCallback, finalCallback, importFileCb, opts) {
opts = opts || {}
let web3 = opts.web3 || createWeb3Provider()
let accounts = 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, importFileCb, opts, next)
},
function deployAllContracts (compilationResult, next) {
deployAll(compilationResult, web3, (err, contracts) => {
if (err) {
next(err)
}
next(null, compilationResult, contracts)
})
},
function determineTestContractsToRun (compilationResult, contracts, next) {
let contractsToTest: any[] = []
let contractsToTestDetails: any[] = []
for (let filename in compilationResult) {
if (filename.indexOf('_test.sol') < 0) {
continue
}
Object.keys(compilationResult[filename]).forEach(contractName => {
contractsToTestDetails.push(compilationResult[filename][contractName])
contractsToTest.push(contractName)
})
}
next(null, contractsToTest, contractsToTestDetails, contracts)
},
function runTests(contractsToTest, contractsToTestDetails, contracts, next) {
let totalPassing = 0
let totalFailing = 0
let totalTime = 0
let errors: any[] = []
const _testCallback = function (result) {
if (result.type === 'testFailure') {
errors.push(result)
}
testCallback(result)
}
const _resultsCallback = function (_err, result, cb) {
resultCallback(_err, result, () => {})
totalPassing += result.passingNum
totalFailing += result.failureNum
totalTime += result.timePassed
cb()
}
async.eachOfLimit(contractsToTest, 1, (contractName, index, cb) => {
runTest(contractName, contracts(contractName), contractsToTestDetails[index], { accounts }, _testCallback, (err, result) => {
if (err) {
return cb(err)
}
_resultsCallback(null, result, cb)
})
}, function (err) {
if (err) {
return next(err)
}
let finalResults: FinalResult = {
totalPassing: 0,
totalFailing: 0,
totalTime: 0,
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})
})
next(null, finalResults)
})
}
], finalCallback)
}

@ -1,149 +0,0 @@
var async = require('async')
var changeCase = require('change-case')
var Web3 = require('web3')
function getFunctionFullName (signature, methodIdentifiers) {
for (var method in methodIdentifiers) {
if (signature.replace('0x', '') === methodIdentifiers[method].replace('0x', '')) {
return method
}
}
return null
}
function getOverridedSender (userdoc, signature, methodIdentifiers) {
let fullName = getFunctionFullName(signature, methodIdentifiers)
let match = /sender: account-+(\d)/g
let accountIndex = userdoc.methods[fullName] ? match.exec(userdoc.methods[fullName].notice) : null
return fullName && accountIndex ? accountIndex[1] : null
}
function getAvailableFunctions (jsonInterface) {
return jsonInterface.reverse().filter((x) => x.type === 'function').map((x) => x.name)
}
function getTestFunctions (jsonInterface) {
let specialFunctions = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach']
return jsonInterface.filter((x) => specialFunctions.indexOf(x.name) < 0 && x.type === 'function')
}
function createRunList (jsonInterface) {
let availableFunctions = getAvailableFunctions(jsonInterface)
let testFunctions = getTestFunctions(jsonInterface)
let runList = []
if (availableFunctions.indexOf('beforeAll') >= 0) {
runList.push({name: 'beforeAll', type: 'internal', constant: false})
}
for (let func of testFunctions) {
if (availableFunctions.indexOf('beforeEach') >= 0) {
runList.push({name: 'beforeEach', type: 'internal', constant: false})
}
runList.push({name: func.name, signature: func.signature, type: 'test', constant: func.constant})
if (availableFunctions.indexOf('afterEach') >= 0) {
runList.push({name: 'afterEach', type: 'internal', constant: false})
}
}
if (availableFunctions.indexOf('afterAll') >= 0) {
runList.push({name: 'afterAll', type: 'internal', constant: false})
}
return runList
}
function runTest (testName, testObject, contractDetails, opts, testCallback, resultsCallback) {
let runList = createRunList(testObject._jsonInterface)
let passingNum = 0
let failureNum = 0
let timePassed = 0
let web3 = new Web3()
var userAgent = (typeof (navigator) !== 'undefined') && navigator.userAgent ? navigator.userAgent.toLowerCase() : '-'
var isBrowser = !(typeof (window) === 'undefined' || userAgent.indexOf(' electron/') > -1)
if (!isBrowser) {
let signale = require('signale')
signale.warn('DO NOT TRY TO ACCESS (IN YOUR SOLIDITY TEST) AN ACCOUNT GREATER THAN THE LENGTH OF THE FOLLOWING ARRAY (' + opts.accounts.length + ') :')
signale.warn(opts.accounts)
signale.warn('e.g: the following code won\'t work in the current context:')
signale.warn('TestsAccounts.getAccount(' + opts.accounts.length + ')')
}
testCallback({type: 'contract', value: testName, filename: testObject.filename})
async.eachOfLimit(runList, 1, function (func, index, next) {
let sender
if (func.signature) {
sender = getOverridedSender(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers)
if (opts.accounts) {
sender = opts.accounts[sender]
}
}
let sendParams
if (sender) sendParams = { from: sender }
let method = testObject.methods[func.name].apply(testObject.methods[func.name], [])
let startTime = Date.now()
if (func.constant) {
method.call(sendParams).then((result) => {
let time = Math.ceil((Date.now() - startTime) / 1000.0)
if (result) {
testCallback({type: 'testPass', value: changeCase.sentenceCase(func.name), time: time, context: testName})
passingNum += 1
timePassed += time
} else {
testCallback({type: 'testFailure', value: changeCase.sentenceCase(func.name), time: time, errMsg: 'function returned false', context: testName})
failureNum += 1
}
next()
})
} else {
method.send(sendParams).on('receipt', function (receipt) {
try {
let time = Math.ceil((Date.now() - startTime) / 1000.0)
let topic = Web3.utils.sha3('AssertionEvent(bool,string)')
let testPassed = false
for (let i in receipt.events) {
let event = receipt.events[i]
if (event.raw.topics.indexOf(topic) >= 0) {
var testEvent = web3.eth.abi.decodeParameters(['bool', 'string'], event.raw.data)
if (!testEvent[0]) {
testCallback({type: 'testFailure', value: changeCase.sentenceCase(func.name), time: time, errMsg: testEvent[1], context: testName})
failureNum += 1
return next()
}
testPassed = true
}
}
if (testPassed) {
testCallback({type: 'testPass', value: changeCase.sentenceCase(func.name), time: time, context: testName})
passingNum += 1
}
return next()
} catch (err) {
console.log('error!')
console.dir(err)
return next(err)
}
}).on('error', function (err) {
console.error(err)
next(err)
})
}
}, function (error) {
resultsCallback(error, {
passingNum: passingNum,
failureNum: failureNum,
timePassed: timePassed
})
})
}
module.exports = {
runTest: runTest
}

@ -0,0 +1,174 @@
import async from 'async'
import * as changeCase from 'change-case'
import Web3 from 'web3'
import { RunListInterface, TestCbInterface, TestResultInterface, ResultCbInterface } from './types'
function getFunctionFullName (signature: string, methodIdentifiers) {
for (const method in methodIdentifiers) {
if (signature.replace('0x', '') === methodIdentifiers[method].replace('0x', '')) {
return method
}
}
return null
}
function getOverridedSender (userdoc, signature: string, methodIdentifiers) {
let fullName: any = getFunctionFullName(signature, methodIdentifiers)
let match = /sender: account-+(\d)/g
let accountIndex = userdoc.methods[fullName] ? match.exec(userdoc.methods[fullName].notice) : null
return fullName && accountIndex ? accountIndex[1] : null
}
function getAvailableFunctions (jsonInterface) {
return jsonInterface.reverse().filter((x) => x.type === 'function').map((x) => x.name)
}
function getTestFunctions (jsonInterface) {
let specialFunctions = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach']
return jsonInterface.filter((x) => specialFunctions.indexOf(x.name) < 0 && x.type === 'function')
}
function createRunList (jsonInterface): RunListInterface[] {
let availableFunctions = getAvailableFunctions(jsonInterface)
let testFunctions = getTestFunctions(jsonInterface)
let runList: RunListInterface[] = []
if (availableFunctions.indexOf('beforeAll') >= 0) {
runList.push({ name: 'beforeAll', type: 'internal', constant: false })
}
for (let func of testFunctions) {
if (availableFunctions.indexOf('beforeEach') >= 0) {
runList.push({ name: 'beforeEach', type: 'internal', constant: false })
}
runList.push({ name: func.name, signature: func.signature, type: 'test', constant: func.constant })
if (availableFunctions.indexOf('afterEach') >= 0) {
runList.push({ name: 'afterEach', type: 'internal', constant: false })
}
}
if (availableFunctions.indexOf('afterAll') >= 0) {
runList.push({ name: 'afterAll', type: 'internal', constant: false })
}
return runList
}
export function runTest (testName, testObject: any, contractDetails: any, opts: any, testCallback: TestCbInterface, resultsCallback: ResultCbInterface) {
let runList = createRunList(testObject._jsonInterface)
let passingNum: number = 0
let failureNum: number = 0
let timePassed: number = 0
let web3 = new Web3()
const userAgent = (typeof (navigator) !== 'undefined') && navigator.userAgent ? navigator.userAgent.toLowerCase() : '-'
const isBrowser = !(typeof (window) === 'undefined' || userAgent.indexOf(' electron/') > -1)
if (!isBrowser) {
let signale = require('signale')
signale.warn('DO NOT TRY TO ACCESS (IN YOUR SOLIDITY TEST) AN ACCOUNT GREATER THAN THE LENGTH OF THE FOLLOWING ARRAY (' + opts.accounts.length + ') :')
signale.warn(opts.accounts)
signale.warn('e.g: the following code won\'t work in the current context:')
signale.warn('TestsAccounts.getAccount(' + opts.accounts.length + ')')
}
const resp: TestResultInterface = {
type: 'contract',
value: testName,
filename: testObject.filename
}
testCallback(undefined, resp)
async.eachOfLimit(runList, 1, function (func, index, next) {
let sender
if (func.signature) {
sender = getOverridedSender(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers)
if (opts.accounts) {
sender = opts.accounts[sender]
}
}
let sendParams
if (sender) sendParams = { from: sender }
let method = testObject.methods[func.name].apply(testObject.methods[func.name], [])
let startTime = Date.now()
if (func.constant) {
method.call(sendParams).then((result) => {
let time = Math.ceil((Date.now() - startTime) / 1000.0)
if (result) {
const resp: TestResultInterface = {
type: 'testPass',
value: changeCase.sentenceCase(func.name),
time: time,
context: testName
}
testCallback(undefined, resp)
passingNum += 1
timePassed += time
} else {
const resp: TestResultInterface = {
type: 'testFailure',
value: changeCase.sentenceCase(func.name),
time: time,
errMsg: 'function returned false',
context: testName
}
testCallback(undefined, resp)
failureNum += 1
}
next()
})
} else {
method.send(sendParams).on('receipt', (receipt) => {
try {
let time: number = Math.ceil((Date.now() - startTime) / 1000.0)
let topic = Web3.utils.sha3('AssertionEvent(bool,string)')
let testPassed: boolean = false
for (let i in receipt.events) {
let event = receipt.events[i]
if (event.raw.topics.indexOf(topic) >= 0) {
const testEvent = web3.eth.abi.decodeParameters(['bool', 'string'], event.raw.data)
if (!testEvent[0]) {
const resp: TestResultInterface = {
type: 'testFailure',
value: changeCase.sentenceCase(func.name),
time: time,
errMsg: testEvent[1],
context: testName
};
testCallback(undefined, resp)
failureNum += 1
return next()
}
testPassed = true
}
}
if (testPassed) {
const resp: TestResultInterface = {
type: 'testPass',
value: changeCase.sentenceCase(func.name),
time: time,
context: testName
}
testCallback(undefined, resp)
passingNum += 1
}
return next()
} catch (err) {
console.error(err)
return next(err)
}
}).on('error', function (err: Error | null | undefined) {
console.error(err)
next(err)
})
}
}, function(error) {
resultsCallback(error, {
passingNum: passingNum,
failureNum: failureNum,
timePassed: timePassed
})
})
}

@ -0,0 +1,39 @@
/** sources object with name of the file and content **/
export interface SrcIfc {
[key: string]: {
content: string
}
}
/** An object with final results of test **/
export interface FinalResult {
totalPassing: number,
totalFailing: number,
totalTime: number,
errors: any[],
}
/** List of tests to run **/
export interface RunListInterface {
name: string,
type: string,
constant: boolean,
signature?: any
}
export interface ResultsInterface {
passingNum: number,
failureNum: number,
timePassed: number
}
export interface TestResultInterface {
type: string,
value: any,
time?: number,
context?: string,
errMsg?: string
filename?: string
}
export interface TestCbInterface {
(error: Error | null | undefined, result: TestResultInterface) : void;
}
export interface ResultCbInterface {
(error: Error | null | undefined, result: ResultsInterface) : void;
}

@ -26,5 +26,4 @@ contract MyTest {
function shouldTriggerOnePass() public { function shouldTriggerOnePass() public {
Assert.equal(uint(1), uint(1), "the test 3 fails"); Assert.equal(uint(1), uint(1), "the test 3 fails");
} }
} }

@ -13,5 +13,4 @@ contract SimpleStorage {
function get() public view returns (uint retVal) { function get() public view returns (uint retVal) {
return storedData; return storedData;
} }
} }

@ -1,6 +1,6 @@
// Copyright (c) 2016 Smart Contract Solutions, Inc. // Copyright (c) 2016 Smart Contract Solutions, Inc.
pragma solidity ^0.4.24; pragma solidity >=0.4.22 <0.6.0;
/** /**

@ -1,4 +1,4 @@
pragma solidity ^0.4.24; pragma solidity >=0.4.22 <0.6.0;
import "./SafeMath.sol"; import "./SafeMath.sol";
/* /*
@ -9,23 +9,23 @@ https://truffleframework.com/tutorials/testing-for-throws-in-solidity-tests
contract SafeMathProxy { contract SafeMathProxy {
using SafeMath for uint; using SafeMath for uint;
function divProxy(uint256 a, uint256 b) returns (uint256) { function divProxy(uint256 a, uint256 b) public pure returns (uint256) {
return a.div(b); return a.div(b);
} }
function mulProxy(uint256 a, uint256 b) returns (uint256) { function mulProxy(uint256 a, uint256 b) public pure returns (uint256) {
return a.mul(b); return a.mul(b);
} }
function subProxy(uint256 a, uint256 b) returns (uint256) { function subProxy(uint256 a, uint256 b) public pure returns (uint256) {
return a.sub(b); return a.sub(b);
} }
function addProxy(uint256 a, uint256 b) returns (uint256) { function addProxy(uint256 a, uint256 b) public pure returns (uint256) {
return a.add(b); return a.add(b);
} }
function modProxy(uint256 a, uint256 b) returns (uint256) { function modProxy(uint256 a, uint256 b) public pure returns (uint256) {
return a.mod(b); return a.mod(b);
} }
} }

@ -1,16 +1,16 @@
pragma solidity ^0.4.24; pragma solidity >=0.4.22 <0.6.0;
import "./remix_tests.sol"; import "remix_tests.sol";
import "./SafeMath.sol"; import "./SafeMath.sol";
import "./SafeMathProxy.sol"; import "./SafeMathProxy.sol";
contract SafeMathTest { contract SafeMathTest {
SafeMathProxy safemathproxy; SafeMathProxy safemathproxy;
function beforeAll() { function beforeAll() public {
safemathproxy = new SafeMathProxy(); safemathproxy = new SafeMathProxy();
} }
function unsafeMultiplicationShouldOverflow() public view returns (bool) { function unsafeMultiplicationShouldOverflow() public returns (bool) {
uint256 a = 4; uint256 a = 4;
uint256 b = 2 ** 256 - 1; uint256 b = 2 ** 256 - 1;
return Assert.equal( return Assert.equal(
@ -20,27 +20,29 @@ contract SafeMathTest {
); );
} }
function safeMultiplicationShouldRevert() public view returns (bool) { function safeMultiplicationShouldRevert() public returns (bool) {
uint256 a = 4; uint256 a = 4;
uint256 b = 2 ** 256 - 1; uint256 b = 2 ** 256 - 1;
(bool success, bytes memory data) = address(safemathproxy).call.gas(40000).value(0)(abi.encode("mulProxy, [a, b]"));
return Assert.equal( return Assert.equal(
address(safemathproxy).call.gas(40000).value(0)("mulProxy",[a, b]), success,
false, false,
"safe multiplication did not revert" "safe multiplication did not revert"
); );
} }
function safeDivisionByZeroShouldRevert() public view returns (bool) { function safeDivisionByZeroShouldRevert() public returns (bool) {
uint256 a = 4; uint256 a = 4;
uint256 b = 0; uint256 b = 0;
(bool success, bytes memory data) = address(safemathproxy).call.gas(40000).value(0)(abi.encode("divProxy, [a, b]"));
return Assert.equal( return Assert.equal(
address(safemathproxy).call.gas(40000).value(0)("divProxy", [a, b]), success,
false, false,
"safe division did not revert" "safe division did not revert"
); );
} }
function unsafeSubtractShouldUnderflow() public view returns (bool) { function unsafeSubtractShouldUnderflow() public returns (bool) {
uint256 a = 0; uint256 a = 0;
uint256 b = a - 1; uint256 b = a - 1;
return Assert.equal( return Assert.equal(
@ -50,35 +52,38 @@ contract SafeMathTest {
); );
} }
function safeSubtractShouldRevert() public constant returns (bool) { function safeSubtractShouldRevert() public returns (bool) {
(bool success, bytes memory data) = address(safemathproxy).call.gas(40000).value(0)(abi.encode("subProxy, [0, 1]"));
return Assert.equal( return Assert.equal(
address(safemathproxy).call.gas(40000).value(0)("subProxy", [0, 1]), success,
false, false,
"safe subtract should revert" "safe subtract should revert"
); );
} }
function unsafeAdditionShouldOverflow() public constant returns (bool) { function unsafeAdditionShouldOverflow() public returns (bool) {
uint256 a = 1; uint256 a = 1;
uint256 b = 2 ** 256 - 1; uint256 b = 2 ** 256 - 1;
return Assert.equal(a + b, 0, "unsafe addition did not overflow"); return Assert.equal(a + b, 0, "unsafe addition did not overflow");
} }
function safeAdditionShouldRevert() public constant returns (bool) { function safeAdditionShouldRevert() public returns (bool) {
uint256 a = 1; uint256 a = 1;
uint256 b = 2 ** 256 - 1; uint256 b = 2 ** 256 - 1;
(bool success, bytes memory data) = address(safemathproxy).call.gas(40000).value(0)(abi.encode("addProxy, [a, b]"));
return Assert.equal( return Assert.equal(
address(safemathproxy).call.gas(40000).value(0)("addProxy", [a, b]), success,
false, false,
"safe addition should revert" "safe addition should revert"
); );
} }
function safeModulusShouldRevert() public constant returns (bool) { function safeModulusShouldRevert() public returns (bool) {
uint256 a = 1; uint256 a = 1;
uint256 b = 0; uint256 b = 0;
(bool success, bytes memory data) = address(safemathproxy).call.gas(40000).value(0)(abi.encode("modProxy, [a, b]"));
return Assert.equal( return Assert.equal(
address(safemathproxy).call.gas(40000).value(0)("modProxy", [a, b]), success,
false, false,
"safe modulus did not revert" "safe modulus did not revert"
); );

@ -1,198 +0,0 @@
const async = require('async')
const Web3 = require('web3')
const assert = require('assert')
let Compiler = require('../src/compiler.js')
let Deployer = require('../src/deployer.js')
let TestRunner = require('../src/testRunner.js')
const Provider = require('remix-simulator').Provider
function compileAndDeploy (filename, callback) {
let web3 = new Web3()
web3.setProvider(new Provider())
let compilationData
let accounts
async.waterfall([
function getAccountList (next) {
web3.eth.getAccounts((_err, _accounts) => {
accounts = _accounts
next(_err)
})
},
function compile (next) {
Compiler.compileFileOrFiles(filename, false, {accounts}, next)
},
function deployAllContracts (compilationResult, next) {
compilationData = compilationResult
Deployer.deployAll(compilationResult, web3, next)
}
], function (_err, contracts) {
callback(null, compilationData, contracts, accounts)
})
}
describe('testRunner', function () {
describe('#runTest', function() {
describe('test with beforeAll', function () {
let filename = 'tests/examples_1/simple_storage_test.sol'
let tests = [], results = {}
before(function (done) {
compileAndDeploy(filename, function (_err, compilationData, contracts, accounts) {
var testCallback = function (test) {
tests.push(test)
}
var resultsCallback = function (_err, _results) {
results = _results
done()
}
TestRunner.runTest('MyTest', contracts.MyTest, compilationData[filename]['MyTest'], { accounts }, testCallback, resultsCallback)
})
})
it('should 1 passing test', function () {
assert.equal(results.passingNum, 2)
})
it('should 1 failing test', function () {
assert.equal(results.failureNum, 2)
})
it('should returns 5 messages', function () {
assert.deepEqual(tests, [
{ type: 'contract', value: 'MyTest', filename: 'tests/examples_1/simple_storage_test.sol' },
{ type: 'testFailure', value: 'Should trigger one fail', time: 1, context: 'MyTest', errMsg: 'the test 1 fails' },
{ type: 'testPass', value: 'Should trigger one pass', time: 1, context: 'MyTest'},
{ type: 'testPass', value: 'Initial value should be100', time: 1, context: 'MyTest' },
{ type: 'testFailure', value: 'Initial value should be200', time: 1, context: 'MyTest', errMsg: 'function returned false' }
])
})
})
describe('test with beforeEach', function () {
let filename = 'tests/examples_2/simple_storage_test.sol'
let tests = [], results = {}
before(function (done) {
compileAndDeploy(filename, function (_err, compilationData, contracts, accounts) {
var testCallback = function (test) {
tests.push(test)
}
var resultsCallback = function (_err, _results) {
results = _results
done()
}
TestRunner.runTest('MyTest', contracts.MyTest, compilationData[filename]['MyTest'], { accounts }, testCallback, resultsCallback)
})
})
it('should 2 passing tests', function () {
assert.equal(results.passingNum, 2)
})
it('should 0 failing tests', function () {
assert.equal(results.failureNum, 0)
})
it('should returns 3 messages', function () {
assert.deepEqual(tests, [
{ type: 'contract', value: 'MyTest', filename: 'tests/examples_2/simple_storage_test.sol' },
{ type: 'testPass', value: 'Initial value should be100', time: 1, context: 'MyTest' },
{ type: 'testPass', value: 'Initial value should be200', time: 1, context: 'MyTest' }
])
})
})
// Test string equality
describe('test string equality', function () {
let filename = 'tests/examples_3/simple_string_test.sol'
let tests = [], results = {}
before(function (done) {
compileAndDeploy(filename, function (_err, compilationData, contracts, accounts) {
var testCallback = function (test) {
tests.push(test)
}
var resultsCallback = function (_err, _results) {
results = _results
done()
}
TestRunner.runTest('StringTest', contracts.StringTest, compilationData[filename]['StringTest'], { accounts }, testCallback, resultsCallback)
TestRunner.runTest('StringTest2', contracts.StringTest2, compilationData[filename]['StringTest2'], { accounts }, testCallback, resultsCallback)
})
})
it('should 2 passing tests', function () {
assert.equal(results.passingNum, 2)
})
it('should 1 failing tests', function () {
assert.equal(results.failureNum, 1)
})
it('should returns 3 messages', function () {
assert.deepEqual(tests, [
{ type: 'contract', value: 'StringTest', filename: 'tests/examples_3/simple_string_test.sol' },
{ type: 'testFailure', value: 'Value should be hello world', time: 1, context: 'StringTest', "errMsg": "initial value is not correct" },
{ type: 'testPass', value: 'Value should not be hello wordl', time: 1, context: 'StringTest' },
{ type: 'testPass', value: 'Initial value should be hello', time: 1, context: 'StringTest' },
])
})
})
// Test signed/unsigned integer weight
describe('test number weight', function () {
let filename = 'tests/number/number_test.sol'
let tests = [], results = {}
before(function (done) {
compileAndDeploy(filename, function (_err, compilationData, contracts, accounts) {
var testCallback = function (test) {
tests.push(test)
}
var resultsCallback = function (_err, _results) {
results = _results
done()
}
TestRunner.runTest('IntegerTest', contracts.IntegerTest, compilationData[filename]['IntegerTest'], { accounts }, testCallback, resultsCallback)
})
})
it('should have 6 passing tests', function () {
assert.equal(results.passingNum, 6)
})
it('should have 2 failing tests', function () {
assert.equal(results.failureNum, 2)
})
})
// Test Transaction with different sender
describe('various sender', function () {
let filename = 'tests/various_sender/sender_test.sol'
let tests = [], results = {}
before(function (done) {
compileAndDeploy(filename, function (_err, compilationData, contracts, accounts) {
var testCallback = function (test) {
tests.push(test)
}
var resultsCallback = function (_err, _results) {
results = _results
done()
}
TestRunner.runTest('SenderTest', contracts.SenderTest, compilationData[filename]['SenderTest'], { accounts }, testCallback, resultsCallback)
})
})
it('should have 4 passing tests', function () {
assert.equal(results.passingNum, 4)
})
it('should have 1 failing tests', function () {
assert.equal(results.failureNum, 0)
})
})
})
})

@ -0,0 +1,214 @@
import 'mocha'
import * as async from 'async'
import Web3 from 'web3'
import * as assert from 'assert'
import { Provider } from 'remix-simulator'
import { compileFileOrFiles } from '../dist/compiler'
import { deployAll } from '../dist/deployer'
import { runTest } from '../dist/index'
import { ResultsInterface, TestCbInterface, ResultCbInterface } from '../dist/index'
function compileAndDeploy(filename: string, callback: Function) {
let web3: Web3 = new Web3()
web3.setProvider(new Provider())
let compilationData: object
let accounts: string[]
async.waterfall([
function getAccountList(next: Function): void {
web3.eth.getAccounts((_err: Error | null | undefined, _accounts: string[]) => {
accounts = _accounts
next(_err)
})
},
function compile(next: Function): void {
compileFileOrFiles(filename, false, { accounts }, next)
},
function deployAllContracts(compilationResult: object, next: Function): void {
try {
compilationData = compilationResult
deployAll(compilationResult, web3, next)
} catch (e) {
throw e
}
}
], function (_err: Error | null | undefined, contracts: any): void {
callback(null, compilationData, contracts, accounts)
})
}
describe('testRunner', () => {
describe('#runTest', () => {
describe('test with beforeAll', () => {
let filename: string = 'tests/examples_1/simple_storage_test.sol'
let tests: any[] = [], results: ResultsInterface;
before((done) => {
compileAndDeploy(filename, (_err: Error | null | undefined, compilationData: object, contracts: any, accounts: object) => {
const testCallback: TestCbInterface = (err, test) => {
if (err) { throw err }
tests.push(test)
}
const resultsCallback: ResultCbInterface = (err, _results) => {
if (err) { throw err }
results = _results
done()
}
runTest('MyTest', contracts.MyTest, compilationData[filename]['MyTest'], { accounts }, testCallback, resultsCallback)
})
})
it('should 1 passing test', function () {
assert.equal(results.passingNum, 2)
})
it('should 1 failing test', function () {
assert.equal(results.failureNum, 2)
})
it('should returns 5 messages', function () {
assert.deepEqual(tests, [
{ type: 'contract', value: 'MyTest', filename: 'tests/examples_1/simple_storage_test.sol' },
{ type: 'testFailure', value: 'Should trigger one fail', time: 1, context: 'MyTest', errMsg: 'the test 1 fails' },
{ type: 'testPass', value: 'Should trigger one pass', time: 1, context: 'MyTest' },
{ type: 'testPass', value: 'Initial value should be100', time: 1, context: 'MyTest' },
{ type: 'testFailure', value: 'Initial value should be200', time: 1, context: 'MyTest', errMsg: 'function returned false' }
])
})
})
describe('test with beforeEach', function () {
let filename = 'tests/examples_2/simple_storage_test.sol'
let tests: any[] = [], results: ResultsInterface;
before(function (done) {
compileAndDeploy(filename, function (_err: Error | null | undefined, compilationData: object, contracts: any, accounts: object) {
const testCallback: TestCbInterface = (err, test) => {
if (err) { throw err }
tests.push(test)
}
const resultsCallback: ResultCbInterface = (err, _results) => {
if (err) { throw err }
results = _results
done()
}
runTest('MyTest', contracts.MyTest, compilationData[filename]['MyTest'], { accounts }, testCallback, resultsCallback)
})
})
it('should 2 passing tests', function () {
assert.equal(results.passingNum, 2)
})
it('should 0 failing tests', function () {
assert.equal(results.failureNum, 0)
})
it('should returns 3 messages', function () {
assert.deepEqual(tests, [
{ type: 'contract', value: 'MyTest', filename: 'tests/examples_2/simple_storage_test.sol' },
{ type: 'testPass', value: 'Initial value should be100', time: 1, context: 'MyTest' },
{ type: 'testPass', value: 'Initial value should be200', time: 1, context: 'MyTest' }
])
})
})
// Test string equality
describe('test string equality', function () {
let filename = 'tests/examples_3/simple_string_test.sol'
let tests: any[] = [], results: ResultsInterface;
before(function (done) {
compileAndDeploy(filename, (_err, compilationData, contracts, accounts) => {
const testCallback: TestCbInterface = (err, test) => {
if (err) { throw err }
tests.push(test)
}
const resultsCallback: ResultCbInterface = (err, _results) => {
if (err) { throw err }
results = _results
done()
}
runTest('StringTest', contracts.StringTest, compilationData[filename]['StringTest'], { accounts }, testCallback, resultsCallback)
runTest('StringTest2', contracts.StringTest2, compilationData[filename]['StringTest2'], { accounts }, testCallback, resultsCallback)
})
})
it('should 2 passing tests', function () {
assert.equal(results.passingNum, 2)
})
it('should 1 failing tests', function () {
assert.equal(results.failureNum, 1)
})
it('should returns 3 messages', function () {
assert.deepEqual(tests, [
{ type: 'contract', value: 'StringTest', filename: 'tests/examples_3/simple_string_test.sol' },
{ type: 'testFailure', value: 'Value should be hello world', time: 1, context: 'StringTest', "errMsg": "initial value is not correct" },
{ type: 'testPass', value: 'Value should not be hello wordl', time: 1, context: 'StringTest' },
{ type: 'testPass', value: 'Initial value should be hello', time: 1, context: 'StringTest' },
])
})
})
//Test signed/unsigned integer weight
describe('test number weight', function () {
let filename = 'tests/number/number_test.sol'
let tests: any[] = [], results: ResultsInterface;
before(function (done) {
compileAndDeploy(filename, (_err, compilationData, contracts, accounts) => {
const testCallback: TestCbInterface = (err, test) => {
if (err) { throw err }
tests.push(test)
}
const resultsCallback: ResultCbInterface = (err, _results) => {
if (err) { throw err }
results = _results
done()
}
runTest('IntegerTest', contracts.IntegerTest, compilationData[filename]['IntegerTest'], { accounts }, testCallback, resultsCallback)
})
})
it('should have 6 passing tests', function () {
assert.equal(results.passingNum, 6)
})
it('should have 2 failing tests', function () {
assert.equal(results.failureNum, 2)
})
})
// Test Transaction with different sender
describe('various sender', function () {
let filename = 'tests/various_sender/sender_test.sol'
let tests: any[] = [], results: ResultsInterface;
before(function (done) {
compileAndDeploy(filename, (_err, compilationData, contracts, accounts) => {
const testCallback: TestCbInterface = (err, test) => {
if (err) { throw err }
tests.push(test)
}
const resultsCallback: ResultCbInterface = (err, _results) => {
if (err) { throw err }
results = _results
done()
}
runTest('SenderTest', contracts.SenderTest, compilationData[filename]['SenderTest'], { accounts }, testCallback, resultsCallback)
})
})
it('should have 4 passing tests', function () {
assert.equal(results.passingNum, 4)
})
it('should have 1 failing tests', function () {
assert.equal(results.failureNum, 0)
})
})
})
})

@ -0,0 +1,24 @@
{
"include": ["src"],
"compilerOptions": {
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": ["dom", "es2018"], /* Specify library files to be included in the compilation. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
/* Module Resolution Options */
"baseUrl": "./src", /* Base directory to resolve non-absolute module names. */
"paths": { "remix-tests": ["./"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"typeRoots": [
"./@types",
"./node_modules/@types"
],
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
}
}
Loading…
Cancel
Save