diff --git a/remix-tests/bin/remix-tests b/remix-tests/bin/remix-tests index 3c14572035..ea2e0358b5 100755 --- a/remix-tests/bin/remix-tests +++ b/remix-tests/bin/remix-tests @@ -1,4 +1,4 @@ #!/usr/bin/env node -require('../src/run.js'); +require('../dist/run.js'); diff --git a/remix-tests/examples/simple_storage.sol b/remix-tests/examples/simple_storage.sol index bc74b55c00..278155d1f1 100644 --- a/remix-tests/examples/simple_storage.sol +++ b/remix-tests/examples/simple_storage.sol @@ -13,5 +13,4 @@ contract SimpleStorage { function get() public view returns (uint retVal) { return storedData; } - } diff --git a/remix-tests/package.json b/remix-tests/package.json index a78636e48f..4a2c245927 100644 --- a/remix-tests/package.json +++ b/remix-tests/package.json @@ -2,7 +2,8 @@ "name": "remix-tests", "version": "0.1.1", "description": "Tests for the Ethereum tool suite Remix", - "main": "./src/index.js", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "contributors": [ { "name": "Iuri Matias", @@ -17,8 +18,8 @@ "remix-tests": "./bin/remix-tests" }, "scripts": { - "lint": "standard", - "test": "standard && mocha tests/ -t 300000" + "build": "tsc", + "test": "tsc && mocha --require ts-node/register tests/*.ts -t 300000" }, "repository": { "type": "git", @@ -51,8 +52,17 @@ "yo-yoify": "latest" }, "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", - "standard": "^10.0.3" + "mocha": "^5.1.0", + "standard": "^10.0.3", + "ts-node": "^8.0.2", + "typescript": "^3.3.1" } } diff --git a/remix-tests/src/compiler.js b/remix-tests/src/compiler.js deleted file mode 100644 index 668fa7a7ee..0000000000 --- a/remix-tests/src/compiler.js +++ /dev/null @@ -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 -} diff --git a/remix-tests/src/compiler.ts b/remix-tests/src/compiler.ts new file mode 100644 index 0000000000..ceb1260515 --- /dev/null +++ b/remix-tests/src/compiler.ts @@ -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) + }) +} diff --git a/remix-tests/src/deployer.js b/remix-tests/src/deployer.js deleted file mode 100644 index d91438f697..0000000000 --- a/remix-tests/src/deployer.js +++ /dev/null @@ -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 -} diff --git a/remix-tests/src/deployer.ts b/remix-tests/src/deployer.ts new file mode 100644 index 0000000000..3a1eb0318a --- /dev/null +++ b/remix-tests/src/deployer.ts @@ -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) +} diff --git a/remix-tests/src/fileSystem.ts b/remix-tests/src/fileSystem.ts new file mode 100644 index 0000000000..43ada7cb1b --- /dev/null +++ b/remix-tests/src/fileSystem.ts @@ -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 diff --git a/remix-tests/src/fs.js b/remix-tests/src/fs.js deleted file mode 100644 index fa2dd4dfd7..0000000000 --- a/remix-tests/src/fs.js +++ /dev/null @@ -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 diff --git a/remix-tests/src/index.js b/remix-tests/src/index.js deleted file mode 100644 index b2b64a0430..0000000000 --- a/remix-tests/src/index.js +++ /dev/null @@ -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') -} diff --git a/remix-tests/src/index.ts b/remix-tests/src/index.ts new file mode 100644 index 0000000000..aad3269514 --- /dev/null +++ b/remix-tests/src/index.ts @@ -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' diff --git a/remix-tests/src/logger.js b/remix-tests/src/logger.js deleted file mode 100644 index 6dc6253bf1..0000000000 --- a/remix-tests/src/logger.js +++ /dev/null @@ -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 -} diff --git a/remix-tests/src/logger.ts b/remix-tests/src/logger.ts new file mode 100644 index 0000000000..a1ef9a784e --- /dev/null +++ b/remix-tests/src/logger.ts @@ -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 diff --git a/remix-tests/src/run.js b/remix-tests/src/run.js deleted file mode 100644 index e0aefb97f6..0000000000 --- a/remix-tests/src/run.js +++ /dev/null @@ -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 ', '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) diff --git a/remix-tests/src/run.ts b/remix-tests/src/run.ts new file mode 100644 index 0000000000..1364ad6855 --- /dev/null +++ b/remix-tests/src/run.ts @@ -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 ', '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) diff --git a/remix-tests/src/runTestFiles.js b/remix-tests/src/runTestFiles.js deleted file mode 100644 index a733b76efd..0000000000 --- a/remix-tests/src/runTestFiles.js +++ /dev/null @@ -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 diff --git a/remix-tests/src/runTestFiles.ts b/remix-tests/src/runTestFiles.ts new file mode 100644 index 0000000000..7fbace3b9b --- /dev/null +++ b/remix-tests/src/runTestFiles.ts @@ -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() + }) + } + ], () => { + }) +} diff --git a/remix-tests/src/runTestSources.js b/remix-tests/src/runTestSources.js deleted file mode 100644 index e91bd66a98..0000000000 --- a/remix-tests/src/runTestSources.js +++ /dev/null @@ -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 diff --git a/remix-tests/src/runTestSources.ts b/remix-tests/src/runTestSources.ts new file mode 100644 index 0000000000..c9e18fbff3 --- /dev/null +++ b/remix-tests/src/runTestSources.ts @@ -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) +} diff --git a/remix-tests/src/testRunner.js b/remix-tests/src/testRunner.js deleted file mode 100644 index ee2a385ea5..0000000000 --- a/remix-tests/src/testRunner.js +++ /dev/null @@ -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 -} diff --git a/remix-tests/src/testRunner.ts b/remix-tests/src/testRunner.ts new file mode 100644 index 0000000000..2b1791d1ce --- /dev/null +++ b/remix-tests/src/testRunner.ts @@ -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 + }) + }) +} diff --git a/remix-tests/src/types.ts b/remix-tests/src/types.ts new file mode 100644 index 0000000000..f33b3a32d2 --- /dev/null +++ b/remix-tests/src/types.ts @@ -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; +} diff --git a/remix-tests/tests/examples_1/simple_storage_test.sol b/remix-tests/tests/examples_1/simple_storage_test.sol index 35063a4179..a22ef9e504 100644 --- a/remix-tests/tests/examples_1/simple_storage_test.sol +++ b/remix-tests/tests/examples_1/simple_storage_test.sol @@ -26,5 +26,4 @@ contract MyTest { function shouldTriggerOnePass() public { Assert.equal(uint(1), uint(1), "the test 3 fails"); } - } diff --git a/remix-tests/tests/examples_2/simple_storage.sol b/remix-tests/tests/examples_2/simple_storage.sol index bc74b55c00..278155d1f1 100644 --- a/remix-tests/tests/examples_2/simple_storage.sol +++ b/remix-tests/tests/examples_2/simple_storage.sol @@ -13,5 +13,4 @@ contract SimpleStorage { function get() public view returns (uint retVal) { return storedData; } - } diff --git a/remix-tests/tests/examples_4/SafeMath.sol b/remix-tests/tests/examples_4/SafeMath.sol index 72a618504e..3fa42ea119 100644 --- a/remix-tests/tests/examples_4/SafeMath.sol +++ b/remix-tests/tests/examples_4/SafeMath.sol @@ -1,6 +1,6 @@ // Copyright (c) 2016 Smart Contract Solutions, Inc. -pragma solidity ^0.4.24; +pragma solidity >=0.4.22 <0.6.0; /** diff --git a/remix-tests/tests/examples_4/SafeMathProxy.sol b/remix-tests/tests/examples_4/SafeMathProxy.sol index 5b8828c0b6..96b88b8e9b 100644 --- a/remix-tests/tests/examples_4/SafeMathProxy.sol +++ b/remix-tests/tests/examples_4/SafeMathProxy.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.24; +pragma solidity >=0.4.22 <0.6.0; import "./SafeMath.sol"; /* @@ -9,23 +9,23 @@ https://truffleframework.com/tutorials/testing-for-throws-in-solidity-tests contract SafeMathProxy { 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); } - function mulProxy(uint256 a, uint256 b) returns (uint256) { + function mulProxy(uint256 a, uint256 b) public pure returns (uint256) { 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); } - function addProxy(uint256 a, uint256 b) returns (uint256) { + function addProxy(uint256 a, uint256 b) public pure returns (uint256) { 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); } } diff --git a/remix-tests/tests/examples_4/SafeMath_test.sol b/remix-tests/tests/examples_4/SafeMath_test.sol index bf4534c913..9953ee8f62 100644 --- a/remix-tests/tests/examples_4/SafeMath_test.sol +++ b/remix-tests/tests/examples_4/SafeMath_test.sol @@ -1,16 +1,16 @@ -pragma solidity ^0.4.24; -import "./remix_tests.sol"; +pragma solidity >=0.4.22 <0.6.0; +import "remix_tests.sol"; import "./SafeMath.sol"; import "./SafeMathProxy.sol"; contract SafeMathTest { SafeMathProxy safemathproxy; - function beforeAll() { + function beforeAll() public { safemathproxy = new SafeMathProxy(); } - function unsafeMultiplicationShouldOverflow() public view returns (bool) { + function unsafeMultiplicationShouldOverflow() public returns (bool) { uint256 a = 4; uint256 b = 2 ** 256 - 1; return Assert.equal( @@ -20,27 +20,29 @@ contract SafeMathTest { ); } - function safeMultiplicationShouldRevert() public view returns (bool) { + function safeMultiplicationShouldRevert() public returns (bool) { uint256 a = 4; 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( - address(safemathproxy).call.gas(40000).value(0)("mulProxy",[a, b]), + success, false, "safe multiplication did not revert" ); } - function safeDivisionByZeroShouldRevert() public view returns (bool) { + function safeDivisionByZeroShouldRevert() public returns (bool) { uint256 a = 4; uint256 b = 0; + (bool success, bytes memory data) = address(safemathproxy).call.gas(40000).value(0)(abi.encode("divProxy, [a, b]")); return Assert.equal( - address(safemathproxy).call.gas(40000).value(0)("divProxy", [a, b]), + success, false, "safe division did not revert" ); } - function unsafeSubtractShouldUnderflow() public view returns (bool) { + function unsafeSubtractShouldUnderflow() public returns (bool) { uint256 a = 0; uint256 b = a - 1; 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( - address(safemathproxy).call.gas(40000).value(0)("subProxy", [0, 1]), + success, false, "safe subtract should revert" ); } - function unsafeAdditionShouldOverflow() public constant returns (bool) { + function unsafeAdditionShouldOverflow() public returns (bool) { uint256 a = 1; uint256 b = 2 ** 256 - 1; 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 b = 2 ** 256 - 1; + (bool success, bytes memory data) = address(safemathproxy).call.gas(40000).value(0)(abi.encode("addProxy, [a, b]")); return Assert.equal( - address(safemathproxy).call.gas(40000).value(0)("addProxy", [a, b]), + success, false, "safe addition should revert" ); } - function safeModulusShouldRevert() public constant returns (bool) { + function safeModulusShouldRevert() public returns (bool) { uint256 a = 1; uint256 b = 0; + (bool success, bytes memory data) = address(safemathproxy).call.gas(40000).value(0)(abi.encode("modProxy, [a, b]")); return Assert.equal( - address(safemathproxy).call.gas(40000).value(0)("modProxy", [a, b]), + success, false, "safe modulus did not revert" ); diff --git a/remix-tests/tests/testRunner.js b/remix-tests/tests/testRunner.js deleted file mode 100644 index 092052fb7f..0000000000 --- a/remix-tests/tests/testRunner.js +++ /dev/null @@ -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) - }) - }) - }) -}) diff --git a/remix-tests/tests/testRunner.ts b/remix-tests/tests/testRunner.ts new file mode 100644 index 0000000000..3671f196f3 --- /dev/null +++ b/remix-tests/tests/testRunner.ts @@ -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) + }) + }) + }) +}) diff --git a/remix-tests/tsconfig.json b/remix-tests/tsconfig.json new file mode 100644 index 0000000000..7a57fc3cef --- /dev/null +++ b/remix-tests/tsconfig.json @@ -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. */ + } +}