From 19e9351e5c06c4b0f2e68caf063bd5532f2e11ef Mon Sep 17 00:00:00 2001 From: Sabyasachi Patra Date: Sun, 3 Feb 2019 18:16:23 +0530 Subject: [PATCH] migrated to ts [WIP] --- remix-tests/src/{index.js => _index.js} | 0 .../src/{runTestFiles.js => _runTestFiles.js} | 0 .../{runTestSources.js => _runTestSources.js} | 0 remix-tests/src/_testRunner.js | 149 ++++++++++ remix-tests/src/index.ts | 8 +- remix-tests/src/runTestFiles.ts | 133 +++++++++ remix-tests/src/runTestSources.ts | 118 ++++++++ remix-tests/src/testRunner.js | 260 +++++++++--------- remix-tests/src/testRunner.ts | 147 ++++++++++ remix-tests/tsconfig.json | 2 +- 10 files changed, 675 insertions(+), 142 deletions(-) rename remix-tests/src/{index.js => _index.js} (100%) rename remix-tests/src/{runTestFiles.js => _runTestFiles.js} (100%) rename remix-tests/src/{runTestSources.js => _runTestSources.js} (100%) create mode 100644 remix-tests/src/_testRunner.js diff --git a/remix-tests/src/index.js b/remix-tests/src/_index.js similarity index 100% rename from remix-tests/src/index.js rename to remix-tests/src/_index.js diff --git a/remix-tests/src/runTestFiles.js b/remix-tests/src/_runTestFiles.js similarity index 100% rename from remix-tests/src/runTestFiles.js rename to remix-tests/src/_runTestFiles.js diff --git a/remix-tests/src/runTestSources.js b/remix-tests/src/_runTestSources.js similarity index 100% rename from remix-tests/src/runTestSources.js rename to remix-tests/src/_runTestSources.js diff --git a/remix-tests/src/_testRunner.js b/remix-tests/src/_testRunner.js new file mode 100644 index 0000000000..ee2a385ea5 --- /dev/null +++ b/remix-tests/src/_testRunner.js @@ -0,0 +1,149 @@ +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/index.ts b/remix-tests/src/index.ts index 9b79dfdff6..03aec74397 100644 --- a/remix-tests/src/index.ts +++ b/remix-tests/src/index.ts @@ -1,10 +1,10 @@ -import runTestFiles from './runTestFiles.ts' -import runTestSources from './runTestSources.ts' -import TestRunner from './testRunner.ts' +import runTestFiles from './runTestFiles' +import runTestSources from './runTestSources' +import runTest from './testRunner' module.exports = { runTestFiles: runTestFiles, runTestSources: runTestSources, - runTest: TestRunner.runTest, + runTest: runTest, assertLibCode: require('../sol/tests.sol.js') } diff --git a/remix-tests/src/runTestFiles.ts b/remix-tests/src/runTestFiles.ts index e69de29bb2..9000900abd 100644 --- a/remix-tests/src/runTestFiles.ts +++ b/remix-tests/src/runTestFiles.ts @@ -0,0 +1,133 @@ +import async = require('async') +import path = require('path') +import fs = require('./fs') +import runTest = require('./testRunner.js') +require('colors') + +import Compiler = require('./compiler.js') +import Deployer = require('./deployer.js') + +function runTestFiles(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: any[] = [] + let contractsToTestDetails: any[] = [] + 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: any[] = [] + + 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) => { + 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) + } + + console.log('\n') + if (totalPassing > 0) { + console.log(('%c ' + totalPassing + ' passing ') + ('%c(' + totalTime + 's)'),'color: green','color: grey') + } + if (totalFailing > 0) { + console.log(('%c ' + totalFailing + ' failing'),'color: red') + } + console.log('') + + errors.forEach((error, index) => { + console.log(' ' + (index + 1) + ') ' + error.context + ' ' + error.value) + console.log('') + console.log(('%c\t error: ' + error.errMsg),'color: red') + }) + console.log('') + + next() + }) + } + ], function () { + }) +} + +export = runTestFiles; \ No newline at end of file diff --git a/remix-tests/src/runTestSources.ts b/remix-tests/src/runTestSources.ts index e69de29bb2..1b38e1da66 100644 --- a/remix-tests/src/runTestSources.ts +++ b/remix-tests/src/runTestSources.ts @@ -0,0 +1,118 @@ +import async = require('async') +require('colors') + +import Compiler = require('./compiler.js') +import Deployer = require('./deployer.js') +import runTest = require('./testRunner.js') + +import Web3 = require('web3') +import Provider from 'remix-simulator' + +interface FinalResult { + totalPassing: number, + totalFailing: number, + totalTime: number, + errors: any[], +} + +var createWeb3Provider = function () { + let web3 = new Web3() + web3.setProvider(new Provider()) + return web3 +} + +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) { + 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: 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[] = [] + + 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) => { + 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) +} +export = runTestSources; \ No newline at end of file diff --git a/remix-tests/src/testRunner.js b/remix-tests/src/testRunner.js index ee2a385ea5..4f70ecba1c 100644 --- a/remix-tests/src/testRunner.js +++ b/remix-tests/src/testRunner.js @@ -1,149 +1,135 @@ -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 +"use strict"; +exports.__esModule = true; +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 + 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 getOverridedSender(userdoc, signature, methodIdentifiers) { + var fullName = getFunctionFullName(signature, methodIdentifiers); + var match = /sender: account-+(\d)/g; + var 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 getAvailableFunctions(jsonInterface) { + return jsonInterface.reverse().filter(function (x) { return x.type === 'function'; }).map(function (x) { return x.name; }); } - -function getTestFunctions (jsonInterface) { - let specialFunctions = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'] - return jsonInterface.filter((x) => specialFunctions.indexOf(x.name) < 0 && x.type === 'function') +function getTestFunctions(jsonInterface) { + var specialFunctions = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach']; + return jsonInterface.filter(function (x) { return 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}) +function createRunList(jsonInterface) { + var availableFunctions = getAvailableFunctions(jsonInterface); + var testFunctions = getTestFunctions(jsonInterface); + var runList = []; + if (availableFunctions.indexOf('beforeAll') >= 0) { + runList.push({ name: 'beforeAll', 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}) + for (var _i = 0, testFunctions_1 = testFunctions; _i < testFunctions_1.length; _i++) { + var func = testFunctions_1[_i]; + 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 + 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] - } +function runTest(testName, testObject, contractDetails, opts, testCallback, resultsCallback) { + var runList = createRunList(testObject._jsonInterface); + var passingNum = 0; + var failureNum = 0; + var timePassed = 0; + var web3 = new Web3(); + var userAgent = (typeof (navigator) !== 'undefined') && navigator.userAgent ? navigator.userAgent.toLowerCase() : '-'; + var isBrowser = !(typeof (window) === 'undefined' || userAgent.indexOf(' electron/') > -1); + if (!isBrowser) { + var 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 + ')'); } - 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 + testCallback({ type: 'contract', value: testName, filename: testObject.filename }); + async.eachOfLimit(runList, 1, function (func, index, next) { + var sender; + if (func.signature) { + sender = getOverridedSender(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers); + if (opts.accounts) { + sender = opts.accounts[sender]; } - } - - 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 + var sendParams; + if (sender) + sendParams = { from: sender }; + var method = testObject.methods[func.name].apply(testObject.methods[func.name], []); + var startTime = Date.now(); + if (func.constant) { + method.call(sendParams).then(function (result) { + var 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 { + var time = Math.ceil((Date.now() - startTime) / 1000.0); + var topic = Web3.utils.sha3('AssertionEvent(bool,string)'); + var testPassed = false; + for (var i in receipt.events) { + var event_1 = receipt.events[i]; + if (event_1.raw.topics.indexOf(topic) >= 0) { + var testEvent = web3.eth.abi.decodeParameters(['bool', 'string'], event_1.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 + }); + }); } +exports.runTest = runTest; diff --git a/remix-tests/src/testRunner.ts b/remix-tests/src/testRunner.ts index e69de29bb2..ef32e6c17f 100644 --- a/remix-tests/src/testRunner.ts +++ b/remix-tests/src/testRunner.ts @@ -0,0 +1,147 @@ +import async = require('async'); +import changeCase = require('change-case'); +import 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: 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) { + let availableFunctions = getAvailableFunctions(jsonInterface) + let testFunctions = getTestFunctions(jsonInterface) + let runList: any[] = [] + + 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 + }) + }) +} + +export = runTest \ No newline at end of file diff --git a/remix-tests/tsconfig.json b/remix-tests/tsconfig.json index bf2cf3a40d..64f82972b0 100644 --- a/remix-tests/tsconfig.json +++ b/remix-tests/tsconfig.json @@ -15,7 +15,7 @@ "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": ["node_modules/@types"], /* List of folders to include type definitions from. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "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. */