diff --git a/remix-simulator/src/methods/accounts.js b/remix-simulator/src/methods/accounts.js index c85493aade..d23f572229 100644 --- a/remix-simulator/src/methods/accounts.js +++ b/remix-simulator/src/methods/accounts.js @@ -3,10 +3,15 @@ var Web3 = require('web3') var Accounts = function () { this.web3 = new Web3() // TODO: make it random and/or use remix-libs - this.accounts = [this.web3.eth.accounts.create(['abcd'])] + this.accounts = [this.web3.eth.accounts.create(['abcd']), this.web3.eth.accounts.create(['ef12']), this.web3.eth.accounts.create(['ef34'])] this.accounts[this.accounts[0].address.toLowerCase()] = this.accounts[0] + this.accounts[this.accounts[1].address.toLowerCase()] = this.accounts[1] + this.accounts[this.accounts[2].address.toLowerCase()] = this.accounts[2] + this.accounts[this.accounts[0].address.toLowerCase()].privateKey = Buffer.from(this.accounts[this.accounts[0].address.toLowerCase()].privateKey.slice(2), 'hex') + this.accounts[this.accounts[1].address.toLowerCase()].privateKey = Buffer.from(this.accounts[this.accounts[1].address.toLowerCase()].privateKey.slice(2), 'hex') + this.accounts[this.accounts[2].address.toLowerCase()].privateKey = Buffer.from(this.accounts[this.accounts[2].address.toLowerCase()].privateKey.slice(2), 'hex') } Accounts.prototype.methods = function () { diff --git a/remix-tests/sol/tests_accounts.sol.js b/remix-tests/sol/tests_accounts.sol.js new file mode 100644 index 0000000000..2a3cb04024 --- /dev/null +++ b/remix-tests/sol/tests_accounts.sol.js @@ -0,0 +1,9 @@ +module.exports = `pragma solidity ^0.4.7; + +library TestsAccounts { + function getAccount(uint index) returns (address) { + >accounts< + return accounts[index]; + } +} +` diff --git a/remix-tests/src/compiler.js b/remix-tests/src/compiler.js index f43a9cab65..f25709fd8a 100644 --- a/remix-tests/src/compiler.js +++ b/remix-tests/src/compiler.js @@ -9,18 +9,28 @@ String.prototype.regexIndexOf = function (regex, startpos) { 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 { + body += '= [' + accounts.map((value) => { return `address(${value})` }).join(',') + '];' + } + 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, cb) { +function compileFileOrFiles (filename, isDirectory, opts, cb) { let compiler, filepath - + let accounts = opts.accounts || [] const sources = { 'tests.sol': { content: require('../sol/tests.sol.js') }, - 'remix_tests.sol': { content: require('../sol/tests.sol.js') } + 'remix_tests.sol': { content: require('../sol/tests.sol.js') }, + 'remix_accounts.sol': { content: writeTestAccountsContract(accounts) } } - // TODO: for now assumes filepath dir contains all tests, later all this // should be replaced with remix's & browser solidity compiler code filepath = (isDirectory ? filename : path.dirname(filename)) @@ -61,12 +71,13 @@ function compileFileOrFiles (filename, isDirectory, cb) { }) } -function compileContractSources (sources, importFileCb, cb) { +function compileContractSources (sources, importFileCb, cb, opts) { 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 for (let file in sources) { diff --git a/remix-tests/src/index.js b/remix-tests/src/index.js index 3dfd689ff4..baf9c8d674 100644 --- a/remix-tests/src/index.js +++ b/remix-tests/src/index.js @@ -16,13 +16,22 @@ var createWeb3Provider = function () { return web3 } -var runTestSources = function (contractSources, testCallback, resultCallback, finalCallback, importFileCb) { +var 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) { - let web3 = createWeb3Provider() Deployer.deployAll(compilationResult, web3, function (err, contracts) { if (err) { next(err) @@ -33,19 +42,21 @@ var runTestSources = function (contractSources, testCallback, resultCallback, fi }, 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, contracts) + next(null, contractsToTest, contractsToTestDetails, contracts) }, - function runTests (contractsToTest, contracts, next) { + function runTests (contractsToTest, contractsToTestDetails, contracts, next) { let totalPassing = 0 let totalFailing = 0 let totalTime = 0 @@ -67,7 +78,7 @@ var runTestSources = function (contractSources, testCallback, resultCallback, fi } async.eachOfLimit(contractsToTest, 1, (contractName, index, cb) => { - TestRunner.runTest(contractName, contracts[contractName], _testCallback, (err, result) => { + TestRunner.runTest(contractName, contracts[contractName], contractsToTestDetails[index], { accounts }, _testCallback, (err, result) => { if (err) { return cb(err) } @@ -95,7 +106,8 @@ var runTestSources = function (contractSources, testCallback, resultCallback, fi ], finalCallback) } -var runTestFiles = function (filepath, isDirectory, web3) { +var runTestFiles = function (filepath, isDirectory, web3, opts) { + opts = opts || {} const { Signale } = require('signale') // signale configuration const options = { @@ -118,37 +130,49 @@ var runTestFiles = function (filepath, isDirectory, web3) { } } 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, 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 = [] + var 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.readdirSync(filepath).forEach(filename => { - if (filename.indexOf('_test.sol') < 0) { - return - } - Object.keys(compilationResult[path.basename(filename)]).forEach(contractName => { - contractsToTest.push(contractName) - }) + gatherContractsFrom(filename) }) } else { - contractsToTest = Object.keys(compilationResult[path.basename(filepath)]) + gatherContractsFrom(filepath) } - - next(null, contractsToTest, contracts) + next(null, contractsToTest, contractsToTestDetails, contracts) }, - function runTests (contractsToTest, contracts, next) { + function runTests (contractsToTest, contractsToTestDetails, contracts, next) { let totalPassing = 0 let totalFailing = 0 let totalTime = 0 @@ -172,7 +196,7 @@ var runTestFiles = function (filepath, isDirectory, web3) { } async.eachOfLimit(contractsToTest, 1, (contractName, index, cb) => { - TestRunner.runTest(contractName, contracts[contractName], testCallback, (err, result) => { + TestRunner.runTest(contractName, contracts[contractName], contractsToTestDetails[index], { accounts }, testCallback, (err, result) => { if (err) { return cb(err) } diff --git a/remix-tests/src/testRunner.js b/remix-tests/src/testRunner.js index 89493e944b..c22dba71e2 100644 --- a/remix-tests/src/testRunner.js +++ b/remix-tests/src/testRunner.js @@ -2,6 +2,20 @@ 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) + return fullName && userdoc.methods[fullName] ? userdoc.methods[fullName].notice : null +} + function getAvailableFunctions (jsonInterface) { return jsonInterface.reverse().filter((x) => x.type === 'function').map((x) => x.name) } @@ -24,7 +38,7 @@ function createRunList (jsonInterface) { if (availableFunctions.indexOf('beforeEach') >= 0) { runList.push({name: 'beforeEach', type: 'internal', constant: false}) } - runList.push({name: func.name, type: 'test', constant: func.constant}) + 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}) } @@ -37,7 +51,7 @@ function createRunList (jsonInterface) { return runList } -function runTest (testName, testObject, testCallback, resultsCallback) { +function runTest (testName, testObject, contractDetails, opts, testCallback, resultsCallback) { let runList = createRunList(testObject._jsonInterface) let passingNum = 0 @@ -45,12 +59,32 @@ function runTest (testName, testObject, testCallback, resultsCallback) { 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().then((result) => { + 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}) @@ -63,7 +97,7 @@ function runTest (testName, testObject, testCallback, resultsCallback) { next() }) } else { - method.send().on('receipt', function (receipt) { + 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)') @@ -95,6 +129,7 @@ function runTest (testName, testObject, testCallback, resultsCallback) { return next(err) } }).on('error', function (err) { + console.error(err) next(err) }) } diff --git a/remix-tests/tests/testRunner.js b/remix-tests/tests/testRunner.js index ccdd0c84b7..1c6d00f335 100644 --- a/remix-tests/tests/testRunner.js +++ b/remix-tests/tests/testRunner.js @@ -10,18 +10,27 @@ 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, next) + Compiler.compileFileOrFiles(filename, false, {accounts}, next) }, function deployAllContracts (compilationResult, next) { + compilationData = compilationResult Deployer.deployAll(compilationResult, web3, next) } ], function (_err, contracts) { - callback(null, contracts) + callback(null, compilationData, contracts, accounts) }) -} +} + describe('testRunner', function () { describe('#runTest', function() { @@ -30,7 +39,7 @@ describe('testRunner', function () { let tests = [], results = {} before(function (done) { - compileAndDeploy(filename, function (_err, contracts) { + compileAndDeploy(filename, function (_err, compilationData, contracts, accounts) { var testCallback = function (test) { tests.push(test) } @@ -38,7 +47,7 @@ describe('testRunner', function () { results = _results done() } - TestRunner.runTest('MyTest', contracts.MyTest, testCallback, resultsCallback) + TestRunner.runTest('MyTest', contracts.MyTest, compilationData['simple_storage_test.sol']['MyTest'], { accounts }, testCallback, resultsCallback) }) }) @@ -66,7 +75,7 @@ describe('testRunner', function () { let tests = [], results = {} before(function (done) { - compileAndDeploy(filename, function (_err, contracts) { + compileAndDeploy(filename, function (_err, compilationData, contracts, accounts) { var testCallback = function (test) { tests.push(test) } @@ -74,7 +83,7 @@ describe('testRunner', function () { results = _results done() } - TestRunner.runTest('MyTest', contracts.MyTest, testCallback, resultsCallback) + TestRunner.runTest('MyTest', contracts.MyTest, compilationData['simple_storage_test.sol']['MyTest'], { accounts }, testCallback, resultsCallback) }) }) @@ -101,7 +110,7 @@ describe('testRunner', function () { let tests = [], results = {} before(function (done) { - compileAndDeploy(filename, function (_err, contracts) { + compileAndDeploy(filename, function (_err, compilationData, contracts, accounts) { var testCallback = function (test) { tests.push(test) } @@ -109,8 +118,8 @@ describe('testRunner', function () { results = _results done() } - TestRunner.runTest('StringTest', contracts.StringTest, testCallback, resultsCallback) - TestRunner.runTest('StringTest2', contracts.StringTest2, testCallback, resultsCallback) + TestRunner.runTest('StringTest', contracts.StringTest, compilationData['simple_string_test.sol']['StringTest'], { accounts }, testCallback, resultsCallback) + TestRunner.runTest('StringTest2', contracts.StringTest2, compilationData['simple_string_test.sol']['StringTest2'], { accounts }, testCallback, resultsCallback) }) }) @@ -138,7 +147,7 @@ describe('testRunner', function () { let tests = [], results = {} before(function (done) { - compileAndDeploy(filename, function (_err, contracts) { + compileAndDeploy(filename, function (_err, compilationData, contracts, accounts) { var testCallback = function (test) { tests.push(test) } @@ -146,7 +155,7 @@ describe('testRunner', function () { results = _results done() } - TestRunner.runTest('IntegerTest', contracts.IntegerTest, testCallback, resultsCallback) + TestRunner.runTest('IntegerTest', contracts.IntegerTest, compilationData['number_test.sol']['IntegerTest'], { accounts }, testCallback, resultsCallback) }) }) @@ -157,5 +166,33 @@ describe('testRunner', 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['sender_test.sol']['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/various_sender/sender_test.sol b/remix-tests/tests/various_sender/sender_test.sol new file mode 100644 index 0000000000..896c145447 --- /dev/null +++ b/remix-tests/tests/various_sender/sender_test.sol @@ -0,0 +1,28 @@ +pragma solidity ^0.4.7; +import "remix_tests.sol"; // this import is automatically injected by Remix. +import "remix_accounts.sol"; + +contract SenderTest { + + function beforeAll () {} + + /// 1 + function checkSenderIs1 () public { + Assert.equal(msg.sender, TestsAccounts.getAccount(1), "wrong sender in checkSenderIs1"); + } + + /// 0 + function checkSenderIs0 () public { + Assert.equal(msg.sender, TestsAccounts.getAccount(0), "wrong sender in checkSenderIs0"); + } + + /// 1 + function checkSenderIsNt0 () public { + Assert.notEqual(msg.sender, TestsAccounts.getAccount(0), "wrong sender in checkSenderIsNot0"); + } + + /// 2 + function checkSenderIsnt2 () public { + Assert.notEqual(msg.sender, TestsAccounts.getAccount(1), "wrong sender in checkSenderIsnt2"); + } +}