From f54fbf0b3dc65d1f0f19744ca18af33037538cb9 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Thu, 12 Sep 2019 17:30:28 -0400 Subject: [PATCH] feat(@embark/test-runner): make vm default node (#1846) * feat: make vm default node * feat(@embark/tests): enable switching node between tests --- .../demo/test/simple_storage_spec.js | 4 +- packages/core/core/constants.json | 1 + packages/embark/src/cmd/cmd_controller.js | 21 +-- .../embark/src/lib/core/configDefaults.js | 2 +- packages/embark/src/lib/core/engine.js | 1 + .../src/lib/modules/blockchain/index.js | 56 ++++++- .../ethereum-blockchain-client/index.js | 24 ++- .../embark/src/lib/modules/ganache/index.js | 10 ++ packages/embark/src/lib/modules/geth/index.js | 39 +++-- .../plugins/accounts-manager/src/index.ts | 4 + .../deploy-tracker/src/deploymentChecks.js | 4 + .../deploy-tracker/src/trackingFunctions.js | 4 + packages/plugins/ens/src/index.js | 4 + packages/plugins/mocha-tests/package.json | 13 +- packages/plugins/mocha-tests/src/lib/index.js | 145 +++++++++++------- .../plugins/transaction-tracker/src/index.js | 3 + packages/plugins/web3/src/index.js | 5 - packages/stack/contracts-manager/src/index.js | 7 + .../stack/deployment/src/contract_deployer.js | 2 - packages/stack/proxy/src/index.ts | 51 +++--- packages/stack/proxy/src/proxy.js | 40 +++-- packages/stack/test-runner/src/lib/index.js | 91 ++++++++++- 22 files changed, 368 insertions(+), 163 deletions(-) create mode 100644 packages/embark/src/lib/modules/ganache/index.js diff --git a/dapps/templates/demo/test/simple_storage_spec.js b/dapps/templates/demo/test/simple_storage_spec.js index 2344c723b..dc8a84d9c 100644 --- a/dapps/templates/demo/test/simple_storage_spec.js +++ b/dapps/templates/demo/test/simple_storage_spec.js @@ -5,7 +5,7 @@ let accounts; // For documentation please see https://embark.status.im/docs/contracts_testing.html config({ - //deployment: { + //blockchain: { // accounts: [ // // you can configure custom accounts with a custom balance // // see https://embark.status.im/docs/contracts_testing.html#Configuring-accounts @@ -31,7 +31,7 @@ contract("SimpleStorage", function () { }); it("set storage value", async function () { - await SimpleStorage.methods.set(150).send(); + await SimpleStorage.methods.set(150).send({from: web3.eth.defaultAccount}); let result = await SimpleStorage.methods.get().call(); assert.strictEqual(parseInt(result, 10), 150); }); diff --git a/packages/core/core/constants.json b/packages/core/core/constants.json index 31754a9f1..fcb9a659a 100644 --- a/packages/core/core/constants.json +++ b/packages/core/core/constants.json @@ -35,6 +35,7 @@ "webpackDone": "webpackDone" }, "blockchain": { + "vm": "vm", "call": "eth_call", "clients": { "geth": "geth", diff --git a/packages/embark/src/cmd/cmd_controller.js b/packages/embark/src/cmd/cmd_controller.js index 7f935eb8c..bb6878a72 100644 --- a/packages/embark/src/cmd/cmd_controller.js +++ b/packages/embark/src/cmd/cmd_controller.js @@ -708,9 +708,7 @@ simulator(_options) { const Engine = require('../lib/core/engine.js'); const engine = new Engine({ - // TODO: this should not be necessary - env: "development", - //env: options.env, + env: options.env, client: options.client, locale: options.locale, version: this.version, @@ -743,23 +741,6 @@ simulator(_options) { engine.registerModuleGroup("pipeline"); engine.registerModuleGroup("tests", options); - let plugin = engine.plugins.createPlugin('cmdcontrollerplugin', {}); - plugin.registerActionForEvent("embark:engine:started", async (_params, cb) => { - try { - await engine.events.request2("blockchain:node:start", engine.config.blockchainConfig); - - await Promise.all([ - engine.events.request2("storage:node:start", engine.config.storageConfig), - engine.events.request2("communication:node:start", engine.config.communicationConfig), - engine.events.request2("namesystem:node:start", engine.config.namesystemConfig) - ]); - } catch (e) { - return cb(e); - } - - cb(); - }); - engine.startEngine(next); }, function setupTestEnvironment(next) { diff --git a/packages/embark/src/lib/core/configDefaults.js b/packages/embark/src/lib/core/configDefaults.js index 008e2657a..ed2ee6645 100644 --- a/packages/embark/src/lib/core/configDefaults.js +++ b/packages/embark/src/lib/core/configDefaults.js @@ -22,7 +22,7 @@ export function getBlockchainDefaults(env) { wsHost: "localhost", wsPort: 8546, networkType: "custom", - miningMode: 'dev', + isDev: true, nodiscover: true, maxpeers: 0, targetGasLimit: 8000000, diff --git a/packages/embark/src/lib/core/engine.js b/packages/embark/src/lib/core/engine.js index c115ce866..e65067ea3 100644 --- a/packages/embark/src/lib/core/engine.js +++ b/packages/embark/src/lib/core/engine.js @@ -211,6 +211,7 @@ class Engine { contractsComponents(_options) { this.registerModule('ethereum-blockchain-client'); + this.registerModule('ganache'); this.registerModulePackage('embark-web3'); this.registerModulePackage('embark-accounts-manager'); this.registerModulePackage('embark-specialconfigs', {plugins: this.plugins}); diff --git a/packages/embark/src/lib/modules/blockchain/index.js b/packages/embark/src/lib/modules/blockchain/index.js index 0b9757c8b..79e25a601 100644 --- a/packages/embark/src/lib/modules/blockchain/index.js +++ b/packages/embark/src/lib/modules/blockchain/index.js @@ -1,5 +1,6 @@ import async from 'async'; const {__} = require('embark-i18n'); +const constants = require('embark-core/constants'); const Web3RequestManager = require('web3-core-requestmanager'); import BlockchainAPI from "./api"; @@ -12,7 +13,7 @@ class Blockchain { this.blockchainConfig = embark.config.blockchainConfig; this.contractConfig = embark.config.contractConfig; this.blockchainApi = new BlockchainAPI(embark); - + this.startedClient = null; embark.registerActionForEvent("pipeline:generateAll:before", this.addArtifactFile.bind(this)); @@ -22,6 +23,16 @@ class Blockchain { }); this.events.setCommandHandler("blockchain:node:start", async (blockchainConfig, cb) => { + const self = this; + const clientName = blockchainConfig.client; + function started() { + self.startedClient = clientName; + self.events.emit("blockchain:started", clientName); + } + if (clientName === constants.blockchain.vm) { + started(); + return cb(); + } const requestManager = new Web3RequestManager.Manager(blockchainConfig.endpoint); const ogConsoleError = console.error; @@ -37,21 +48,52 @@ class Blockchain { console.error = ogConsoleError; if (!err) { // Node is already started - this.events.emit("blockchain:started"); + started(); return cb(null, true); } - const clientName = blockchainConfig.client; - const client = this.blockchainNodes[clientName]; - if (!client) return cb("client " + clientName + " not found"); + const clientFunctions = this.blockchainNodes[clientName]; + if (!clientFunctions) { + return cb(__("Client %s not found", clientName)); + } let onStart = () => { - this.events.emit("blockchain:started", clientName); + started(); cb(); }; - client.apply(client, [onStart]); + this.startedClient = clientName; + clientFunctions.launchFn.apply(clientFunctions, [onStart]); }); }); + + this.events.setCommandHandler("blockchain:node:stop", (clientName, cb) => { + if (typeof clientName === 'function') { + if (!this.startedClient) { + return cb(__('No blockchain client is currently started')); + } + cb = clientName; + clientName = this.startedClient; + } + + if (clientName === constants.blockchain.vm) { + this.startedClient = null; + this.events.emit("blockchain:stopped", clientName); + return cb(); + } + + const clientFunctions = this.blockchainNodes[clientName]; + if (!clientFunctions) { + return cb(__("Client %s not found", clientName)); + } + + clientFunctions.stopFn.apply(clientFunctions, [ + () => { + this.events.emit("blockchain:stopped", clientName); + cb(); + } + ]); + this.startedClient = null; + }); this.blockchainApi.registerAPIs("ethereum"); this.blockchainApi.registerRequests("ethereum"); } diff --git a/packages/embark/src/lib/modules/ethereum-blockchain-client/index.js b/packages/embark/src/lib/modules/ethereum-blockchain-client/index.js index 54bfb4701..9fddd85b4 100644 --- a/packages/embark/src/lib/modules/ethereum-blockchain-client/index.js +++ b/packages/embark/src/lib/modules/ethereum-blockchain-client/index.js @@ -29,6 +29,10 @@ class EthereumBlockchainClient { this.events.request("blockchain:client:register", "ethereum", this.getClient.bind(this)); this.events.request("deployment:deployer:register", "ethereum", this.deployer.bind(this)); + this.events.on("blockchain:started", () => { + this._web3 = null; + }); + this.registerAPIRequests(); } @@ -54,23 +58,18 @@ class EthereumBlockchainClient { async deployer(contract, done) { const web3 = await this.web3; - // var web3 = new Web3("ws://localhost:8556") - // web3.eth.getAccounts().then((accounts) => { - let accounts = await web3.eth.getAccounts(); - let account = accounts[0]; - // let contractObject = this.blockchain.ContractObject({abi: contract.abiDefinition}); - let contractObj = new web3.eth.Contract(contract.abiDefinition, contract.address); - // let deployObject = this.blockchain.deployContractObject(contractObject, {arguments: contractParams, data: dataCode}); - let contractObject = contractObj.deploy({arguments: (contract.args || []), data: ("0x" + contract.code)}); + const [account] = await web3.eth.getAccounts(); + const contractObj = new web3.eth.Contract(contract.abiDefinition, contract.address); + const contractObject = contractObj.deploy({arguments: (contract.args || []), data: ("0x" + contract.code)}); if (contract.gas === 'auto' || !contract.gas) { - let gasValue = await contractObject.estimateGas(); - let increase_per = 1 + (Math.random() / 10.0); + const gasValue = await contractObject.estimateGas(); + const increase_per = 1 + (Math.random() / 10.0); contract.gas = Math.floor(gasValue * increase_per); } if (!contract.gasPrice) { - let gasPrice = await web3.eth.getGasPrice(); + const gasPrice = await web3.eth.getGasPrice(); contract.gasPrice = contract.gasPrice || gasPrice; } @@ -198,8 +197,7 @@ class EthereumBlockchainClient { } async determineAccounts(params, callback) { - let provider = await this.events.request2("blockchain:client:provider", "ethereum"); - let web3 = new Web3(provider); + const web3 = await this.web3; let accounts = await web3.eth.getAccounts(); let deploymentAccount = accounts[0]; let contract = params.contract; diff --git a/packages/embark/src/lib/modules/ganache/index.js b/packages/embark/src/lib/modules/ganache/index.js new file mode 100644 index 000000000..7653a580f --- /dev/null +++ b/packages/embark/src/lib/modules/ganache/index.js @@ -0,0 +1,10 @@ +class Ganache { + constructor(embark) { + embark.events.request('proxy:vm:register', () => { + const ganache = require('ganache-cli'); + return ganache.provider(); + }); + } +} + +module.exports = Ganache; diff --git a/packages/embark/src/lib/modules/geth/index.js b/packages/embark/src/lib/modules/geth/index.js index 4d7db631f..fdd5b300f 100644 --- a/packages/embark/src/lib/modules/geth/index.js +++ b/packages/embark/src/lib/modules/geth/index.js @@ -6,7 +6,6 @@ import {ws, rpc} from './check.js'; const constants = require('embark-core/constants'); class Geth { - constructor(embark, options) { this.embark = embark; this.embarkConfig = embark.config.embarkConfig; @@ -23,22 +22,28 @@ class Geth { return; } - this.events.request("blockchain:node:register", constants.blockchain.clients.geth, (readyCb) => { - this.events.request('processes:register', 'blockchain', { - launchFn: (cb) => { - this.startBlockchainNode(cb); - }, - stopFn: (cb) => { - this.stopBlockchainNode(cb); - } - }); - this.events.request("processes:launch", "blockchain", (err) => { - if (err) { - this.logger.error(`Error launching blockchain process: ${err.message || err}`); - } - readyCb(); - }); - this.registerServiceCheck(); + this.events.request("blockchain:node:register", constants.blockchain.clients.geth, { + launchFn: (readyCb) => { + this.events.request('processes:register', 'blockchain', { + launchFn: (cb) => { + this.startBlockchainNode(cb); + }, + stopFn: (cb) => { + this.stopBlockchainNode(cb); + } + }); + this.events.request("processes:launch", "blockchain", (err) => { + if (err) { + this.logger.error(`Error launching blockchain process: ${err.message || err}`); + } + readyCb(); + }); + this.registerServiceCheck(); + }, + stopFn: async (cb) => { + await this.events.request("processes:stop", "blockchain"); + cb(); + } }); this.events.request("whisper:node:register", constants.blockchain.clients.geth, readyCb => { diff --git a/packages/plugins/accounts-manager/src/index.ts b/packages/plugins/accounts-manager/src/index.ts index e4aacaaf4..661cfcc83 100644 --- a/packages/plugins/accounts-manager/src/index.ts +++ b/packages/plugins/accounts-manager/src/index.ts @@ -34,6 +34,10 @@ export default class AccountsManager { this.embark.registerActionForEvent("blockchain:proxy:request", this.checkBlockchainRequest.bind(this)); this.embark.registerActionForEvent("blockchain:proxy:response", this.checkBlockchainResponse.bind(this)); + this.events.on("blockchain:started", () => { + this._web3 = null; + }); + // Allow to run transaction in parallel by resolving the nonce manually. // For each transaction, resolve the nonce by taking the max of current transaction count and the cache we keep locally. // Update the nonce and sign it diff --git a/packages/plugins/deploy-tracker/src/deploymentChecks.js b/packages/plugins/deploy-tracker/src/deploymentChecks.js index 8eec52718..3a590e32a 100644 --- a/packages/plugins/deploy-tracker/src/deploymentChecks.js +++ b/packages/plugins/deploy-tracker/src/deploymentChecks.js @@ -9,6 +9,10 @@ export default class DeploymentChecks { this.events = events; this.logger = logger; this._web3 = null; + + this.events.on("blockchain:started", () => { + this._web3 = null; + }); } get web3() { diff --git a/packages/plugins/deploy-tracker/src/trackingFunctions.js b/packages/plugins/deploy-tracker/src/trackingFunctions.js index eabc56dfd..fc5ab80d3 100644 --- a/packages/plugins/deploy-tracker/src/trackingFunctions.js +++ b/packages/plugins/deploy-tracker/src/trackingFunctions.js @@ -17,6 +17,10 @@ export default class TrackingFunctions { this._block = null; this.ensureChainTrackerFile(); + + this.events.on("blockchain:started", () => { + this._web3 = null; + }); } get web3() { diff --git a/packages/plugins/ens/src/index.js b/packages/plugins/ens/src/index.js index 202cc6af9..6efc8d6f6 100644 --- a/packages/plugins/ens/src/index.js +++ b/packages/plugins/ens/src/index.js @@ -84,6 +84,10 @@ class ENS { setImmediate(cb, this.isENSName(name)); }); + this.events.on("blockchain:started", () => { + this._web3 = null; + }); + this.init(() => {}); } diff --git a/packages/plugins/mocha-tests/package.json b/packages/plugins/mocha-tests/package.json index fa12f0be5..0930fc01f 100644 --- a/packages/plugins/mocha-tests/package.json +++ b/packages/plugins/mocha-tests/package.json @@ -47,6 +47,14 @@ }, "extends": "../../../.eslintrc.json" }, + "dependencies": { + "@babel/runtime-corejs2": "7.3.1", + "async": "3.1.0", + "embark-i18n": "^4.1.1", + "embark-utils": "^4.1.1", + "mocha": "6.2.0", + "web3": "1.2.1" + }, "devDependencies": { "@babel/cli": "7.2.3", "@babel/core": "7.2.2", @@ -60,12 +68,7 @@ "tslint": "5.16.0", "typescript": "3.4.5" }, - "dependencies": { - "async": "2.6.1", "embarkjs": "^4.1.1", - "mocha": "6.2.0", - "web3": "1.2.1" - }, "engines": { "node": ">=8.12.0 <12.0.0", "npm": ">=6.4.1", diff --git a/packages/plugins/mocha-tests/src/lib/index.js b/packages/plugins/mocha-tests/src/lib/index.js index 6d0a2cbcb..93b48fe2e 100644 --- a/packages/plugins/mocha-tests/src/lib/index.js +++ b/packages/plugins/mocha-tests/src/lib/index.js @@ -1,3 +1,5 @@ +import {__} from 'embark-i18n'; + const assert = require('assert').strict; const async = require('async'); const EmbarkJS = require('embarkjs'); @@ -14,8 +16,12 @@ class MochaTestRunner { this.embark = embark; this.events = embark.events; this.plugins = options.plugins; + this.logger = embark.logger; + this.fs = embark.fs; this.files = []; + this.options = {}; + this.web3 = null; this.events.request('tests:runner:register', 'JavaScript (Mocha)', @@ -37,20 +43,31 @@ class MochaTestRunner { return JAVASCRIPT_TEST_MATCH.test(path); } - run(options, cb) { - const {events, plugins} = this; + async run(options, cb) { + const {events} = this.embark; const {reporter} = options; + this.options = options; const Module = require("module"); const originalRequire = require("module").prototype.require; let accounts = []; let compiledContracts = {}; - let web3; const config = (cfg, acctCb) => { global.before((done) => { async.waterfall([ + (next) => { + events.request("tests:deployment:check", cfg, this.options, (err, provider) => { + if (err) { + return next(err); + } + if (provider) { + this.web3.setProvider(provider); + } + next(); + }); + }, (next) => { events.request("contracts:build", cfg, compiledContracts, next); }, @@ -86,6 +103,12 @@ class MochaTestRunner { } next(); + }, + (next) => { + this.web3.eth.getAccounts((err, accts) => { + accounts = accts; + next(err); + }); } ], (err) => { // Reset the gas accumulator so that we don't show deployment gas on the @@ -102,73 +125,79 @@ class MochaTestRunner { }); }; + const provider = await this.events.request2("tests:blockchain:start", this.options); + this.web3 = new Web3(provider); + accounts = await this.web3.eth.getAccounts(); + await events.request2("contracts:reset"); + let contractFiles = await events.request2("config:contractsFiles"); + async.waterfall([ - (next) => { // request provider - events.request("blockchain:client:provider", "ethereum", next); + (next) => { + this.plugins.emitAndRunActionsForEvent('tests:contracts:compile:before', contractFiles, next); }, - (provider, next) => { // set provider and fetch account list - web3 = new Web3(provider); - web3.eth.getAccounts(next); + (_contractFiles, next) => { + contractFiles = _contractFiles; + events.request("compiler:contracts:compile", _contractFiles, next); }, - (accts, next) => { // reset contracts as we might have state leakage from other plugins - accounts = accts; - events.request("contracts:reset", next); + (_compiledContracts, next) => { + this.plugins.emitAndRunActionsForEvent('tests:contracts:compile:after', _compiledContracts, next); }, - (next) => { // get contract files - events.request("config:contractsFiles", next); - }, - (cf, next) => { - plugins.emitAndRunActionsForEvent('tests:contracts:compile:before', cf, next); - }, - (cf, next) => { // compile contracts - events.request("compiler:contracts:compile", cf, next); - }, - (cc, next) => { - plugins.emitAndRunActionsForEvent('tests:contracts:compile:after', cc, next); - }, - (cc, next) => { // override require - compiledContracts = cc; + (_compiledContracts, next) => { + compiledContracts = _compiledContracts; + const fns = this.files.map((file) => { + return (seriesCb) => { - Module.prototype.require = function(req) { - const prefix = "Embark/contracts/"; - if (!req.startsWith(prefix)) { - return originalRequire.apply(this, arguments); - } + this.fs.readFile(file, (err, data) => { + if (err) { + self.logger.error(__('Error reading file %s', file)); + self.logger.error(err); + seriesCb(null, 1); + } + if (data.toString().search(/contract\(|describe\(/) === -1) { + return seriesCb(null, 0); + } - const contractClass = req.replace(prefix, ""); - const instance = compiledContracts[contractClass]; + Module.prototype.require = function(req) { + const prefix = "Embark/contracts/"; + if (!req.startsWith(prefix)) { + return originalRequire.apply(this, arguments); + } - if (!instance) { - throw new Error(`Cannot find module '${req}'`); - } + const contractClass = req.replace(prefix, ""); + const instance = compiledContracts[contractClass]; - return instance; - }; - next(); - }, - (next) => { // initialize Mocha - const mocha = new Mocha(); + if (!instance) { + throw new Error(`Cannot find module '${req}'`); + } + return instance; + }; - mocha.reporter(Reporter, { reporter: reporter }); - const describeWithAccounts = (scenario, cb) => { - Mocha.describe(scenario, cb.bind(mocha, accounts)); - }; + const mocha = new Mocha(); + mocha.reporter(Reporter, {reporter: options.reporter}); + const describeWithAccounts = (scenario, cb) => { + Mocha.describe(scenario, cb.bind(mocha, accounts)); + }; - mocha.suite.on('pre-require', () => { - global.describe = describeWithAccounts; - global.contract = describeWithAccounts; - global.assert = assert; - global.config = config; + mocha.suite.on('pre-require', () => { + global.describe = describeWithAccounts; + global.contract = describeWithAccounts; + global.assert = assert; + global.config = config; + }); + + + mocha.suite.timeout(TEST_TIMEOUT); + mocha.addFile(file); + + mocha.run((failures) => { + Module.prototype.require = originalRequire; + seriesCb(null, failures); + }); + }); + }; }); - mocha.suite.timeout(TEST_TIMEOUT); - for(const file of this.files) { - mocha.addFile(file); - } - - mocha.run((_failures) => { - next(); - }); + async.series(fns, next); } ], (err) => { events.emit('tests:finished'); diff --git a/packages/plugins/transaction-tracker/src/index.js b/packages/plugins/transaction-tracker/src/index.js index b98256947..5f8b9c360 100644 --- a/packages/plugins/transaction-tracker/src/index.js +++ b/packages/plugins/transaction-tracker/src/index.js @@ -10,6 +10,9 @@ class TransactionTracker { this._web3 = null; embark.events.on("block:header", this.onBlockHeader.bind(this)); + this.events.on("blockchain:started", () => { + this._web3 = null; + }); this.registerAPICalls(); this.subscribeToPendingTransactions(); } diff --git a/packages/plugins/web3/src/index.js b/packages/plugins/web3/src/index.js index 8bee1bb80..9677c9066 100644 --- a/packages/plugins/web3/src/index.js +++ b/packages/plugins/web3/src/index.js @@ -40,11 +40,6 @@ class EmbarkWeb3 { } async registerWeb3Object() { - const checkWeb3 = `return (typeof web3 === 'undefined');`; - const web3NotDefined = await this.events.request2('runcode:eval', checkWeb3); - - if (!web3NotDefined) return; - const provider = await this.events.request2("blockchain:client:provider", "ethereum"); const web3 = new Web3(provider); await this.events.request2("runcode:register", 'web3', web3); diff --git a/packages/stack/contracts-manager/src/index.js b/packages/stack/contracts-manager/src/index.js index 4c8de9953..14414623f 100644 --- a/packages/stack/contracts-manager/src/index.js +++ b/packages/stack/contracts-manager/src/index.js @@ -44,6 +44,10 @@ class ContractsManager { cb(null, this.contracts[contract.className]); }); + this.events.on("blockchain:started", () => { + this._web3 = null; + }); + this.registerCommands(); this.registerAPIs(); } @@ -262,6 +266,9 @@ class ContractsManager { function prepareContractsFromConfig(callback) { self.events.emit("status", __("Building...")); + if (contractsConfig.contracts.deploy) { + contractsConfig.contracts = contractsConfig.contracts.deploy; + } async.eachOf(contractsConfig.contracts, (contract, className, eachCb) => { contract = new Contract(self.logger, contract); if (!contract.artifact) { diff --git a/packages/stack/deployment/src/contract_deployer.js b/packages/stack/deployment/src/contract_deployer.js index 37bec9d41..6aea0e747 100644 --- a/packages/stack/deployment/src/contract_deployer.js +++ b/packages/stack/deployment/src/contract_deployer.js @@ -13,7 +13,6 @@ class ContractDeployer { } deployContract(contract, callback) { - async.waterfall([ (next) => { this.plugins.emitAndRunActionsForEvent('deployment:contract:beforeDeploy', {contract: contract}, (err, _params) => { @@ -22,7 +21,6 @@ class ContractDeployer { }); }, (next) => { - // self.plugins.emitAndRunActionsForEvent('deployment:contract:arguments', {contract: contract}, (_params) => { this.plugins.emitAndRunActionsForEvent('deployment:contract:shouldDeploy', {contract: contract, shouldDeploy: true}, (err, params) => { next(err, params); }); diff --git a/packages/stack/proxy/src/index.ts b/packages/stack/proxy/src/index.ts index 6b0635e66..b9bc668c0 100644 --- a/packages/stack/proxy/src/index.ts +++ b/packages/stack/proxy/src/index.ts @@ -11,26 +11,29 @@ export default class ProxyManager { private proxy: any; private plugins: any; private readonly host: string; - private rpcPort: number; - private wsPort: number; - private ready: boolean; + private rpcPort = 0; + private wsPort = 0; + private ready = false; private isWs = false; + private vms: any[]; constructor(private embark: Embark, options: any) { this.logger = embark.logger; this.events = embark.events; this.plugins = options.plugins; - this.ready = false; - this.rpcPort = 0; - this.wsPort = 0; + this.vms = []; this.host = "localhost"; - this.events.once("blockchain:started", async () => { - await this.setupProxy(); + this.events.on("blockchain:started", async (clientName: string) => { + await this.setupProxy(clientName); this.ready = true; this.events.emit("proxy:ready"); }); + this.events.on("blockchain:stopped", async (clientName: string, node?: string) => { + this.ready = false; + await this.stopProxy(); + }); if (!this.embark.config.blockchainConfig.proxy) { this.logger.warn(__("The proxy has been disabled -- some Embark features will not work.")); @@ -48,6 +51,10 @@ export default class ProxyManager { } cb(null, buildUrl("http", this.host, this.rpcPort, "rpc")); }); + + this.events.setCommandHandler("proxy:vm:register", (handler: any) => { + this.vms.push(handler); + }); } public onReady() { @@ -61,23 +68,31 @@ export default class ProxyManager { }); } - private async setupProxy() { + private async setupProxy(clientName: string) { if (!this.embark.config.blockchainConfig.proxy) { return; } + if (this.proxy) { + throw new Error("Proxy is already started"); + } const port = await findNextPort(this.embark.config.blockchainConfig.rpcPort + constants.blockchain.servicePortOnProxy); this.rpcPort = port; this.wsPort = port + 1; - this.isWs = (/wss?/).test(this.embark.config.blockchainConfig.endpoint); + this.isWs = clientName === constants.blockchain.vm || (/wss?/).test(this.embark.config.blockchainConfig.endpoint); - this.proxy = await new Proxy({events: this.events, plugins: this.plugins, logger: this.logger}) - .serve( - this.embark.config.blockchainConfig.endpoint, - this.host, - this.isWs ? this.wsPort : this.rpcPort, - this.isWs, - ); - return; + this.proxy = await new Proxy({events: this.events, plugins: this.plugins, logger: this.logger, vms: this.vms}); + + await this.proxy.serve( + clientName === constants.blockchain.vm ? constants.blockchain.vm : this.embark.config.blockchainConfig.endpoint, + this.host, + this.isWs ? this.wsPort : this.rpcPort, + this.isWs, + ); + } + + private stopProxy() { + this.proxy.stop(); + this.proxy = null; } } diff --git a/packages/stack/proxy/src/proxy.js b/packages/stack/proxy/src/proxy.js index c1576ae91..91ebda924 100644 --- a/packages/stack/proxy/src/proxy.js +++ b/packages/stack/proxy/src/proxy.js @@ -4,6 +4,7 @@ import express from 'express'; import expressWs from 'express-ws'; import cors from 'cors'; const Web3RequestManager = require('web3-core-requestmanager'); +const constants = require("embark-core/constants"); const ACTION_TIMEOUT = 5000; @@ -15,9 +16,15 @@ export class Proxy { this.timeouts = {}; this.plugins = options.plugins; this.logger = options.logger; + this.vms = options.vms; + this.app = null; + this.server = null; } async serve(endpoint, localHost, localPort, ws) { + if (endpoint === constants.blockchain.vm) { + endpoint = this.vms[this.vms.length - 1](); + } const requestManager = new Web3RequestManager.Manager(endpoint); try { @@ -26,17 +33,17 @@ export class Proxy { throw new Error(__('Unable to connect to the blockchain endpoint')); } - const app = express(); + this.app = express(); if (ws) { - expressWs(app); + expressWs(this.app); } - app.use(cors()); - app.use(express.json()); - app.use(express.urlencoded({extended: true})); + this.app.use(cors()); + this.app.use(express.json()); + this.app.use(express.urlencoded({extended: true})); if (ws) { - app.ws('/', (ws, _wsReq) => { + this.app.ws('/', (ws, _wsReq) => { ws.on('message', (msg) => { let jsonMsg; try { @@ -50,7 +57,7 @@ export class Proxy { // Send the possibly modified request to the Node requestManager.send(resp.reqData, (err, result) => { if (err) { - return this.logger.error(__('Error executing the request on the Node'), JSON.stringify(err)); + return this.logger.error(__('Error executing the request on the Node'), err.message || err); } this.emitActionsForResponse(resp.reqData, {jsonrpc: "2.0", id: resp.reqData.id, result}, (_err, resp) => { // Send back to the caller (web3) @@ -62,7 +69,7 @@ export class Proxy { }); } else { // HTTP - app.use((req, res) => { + this.app.use((req, res) => { // Modify request this.emitActionsForRequest(req.body, (_err, resp) => { // Send the possibly modified request to the Node @@ -80,9 +87,9 @@ export class Proxy { } return new Promise(resolve => { - app.listen(localPort, localHost, null, + this.server = this.app.listen(localPort, localHost, null, () => { - resolve(app); + resolve(this.app); }); }); } @@ -147,4 +154,17 @@ export class Proxy { calledBack = true; }); } + + stop() { + if (!this.server) { + return; + } + this.server.close(); + this.server = null; + this.app = null; + this.commList = {}; + this.receipts = {}; + this.transactions = {}; + this.timeouts = {}; + } } diff --git a/packages/stack/test-runner/src/lib/index.js b/packages/stack/test-runner/src/lib/index.js index 62918664d..685c8ebd6 100644 --- a/packages/stack/test-runner/src/lib/index.js +++ b/packages/stack/test-runner/src/lib/index.js @@ -1,9 +1,12 @@ import { __ } from 'embark-i18n'; +import {buildUrl, deconstructUrl, recursiveMerge} from "embark-utils"; const async = require('async'); const chalk = require('chalk'); const path = require('path'); const { dappPath } = require('embark-utils'); +import cloneDeep from "lodash.clonedeep"; import { COVERAGE_GAS_LIMIT, GAS_LIMIT } from './constants'; +const constants = require('embark-core/constants'); const coverage = require('istanbul-lib-coverage'); const reporter = require('istanbul-lib-report'); @@ -22,6 +25,10 @@ class TestRunner { this.gasLimit = options.coverage ? COVERAGE_GAS_LIMIT : GAS_LIMIT; this.files = []; + this.configObj = embark.config; + this.originalConfigObj = cloneDeep(embark.config); + this.simOptions = {}; + this.events.setCommandHandler('tests:run', (options, callback) => { this.run(options, callback); }); @@ -32,6 +39,9 @@ class TestRunner { // like Jest tests and such. this.runners.unshift({pluginName, matchFn, addFn, runFn}); }); + + this.events.setCommandHandler('tests:deployment:check', this.checkDeploymentOptions.bind(this)); + this.events.setCommandHandler('tests:blockchain:start', this.startBlockchainNode.bind(this)); } run(options, cb) { @@ -65,7 +75,7 @@ class TestRunner { }); async.series(runnerFns, next); - }, + } ], (err) => { reporter.footer(); @@ -85,16 +95,16 @@ class TestRunner { open(dappPath('coverage/index.html')).then(() => { cb(err, reporter.passes, reporter.fails); }); - } catch(err) { - process.stdout.write(chalk`{red Coverage report could not be created:}\n{white ${err.message}}\n`); - cb(err, reporter.passes, reporter.fails); + } catch(e) { + process.stdout.write(chalk`{red Coverage report could not be created:}\n{white ${e.message}}\n`); + cb(e, reporter.passes, reporter.fails); } }); } generateCoverageReport() { const coveragePath = dappPath(".embark", "coverage.json"); - const coverageMap = JSON.parse(fs.readFileSync(coveragePath)); + const coverageMap = JSON.parse(this.fs.readFileSync(coveragePath)); const map = coverage.createCoverageMap(coverageMap); const tree = reporter.summarizers.nested(map); @@ -131,6 +141,77 @@ class TestRunner { cb(null, [filePath]); }); } + + async checkDeploymentOptions(config, options, cb = () => {}) { + let resetServices = false; + const blockchainConfig = config.blockchain || {}; + let {host, port, type, protocol} = blockchainConfig.endpoint ? deconstructUrl(blockchainConfig.endpoint) : {}; + const accounts = blockchainConfig.accounts; + + if (host && port && !['rpc', 'ws'].includes(type)) { + return cb(__("contracts config error: unknown deployment type %s", type)); + } + + if (!type) { + type = constants.blockchain.vm; + } + + if (accounts || port !== this.simOptions.port || type !== this.simOptions.type || host !== this.simOptions.host) { + resetServices = true; + } + + Object.assign(this.simOptions, {host, port, type, protocol, accounts, client: config.blockchain && config.blockchain.client}); + + if (!resetServices) { + return cb(); + } + + const provider = await this.startBlockchainNode(options); + cb(null, provider); + return provider; + } + + async startBlockchainNode(options, cb = () => {}) { + let node = options.node; + if (!this.simOptions.host && (node && node === constants.blockchain.vm)) { + this.simOptions.type = constants.blockchain.vm; + this.simOptions.client = constants.blockchain.vm; + } else if (this.simOptions.host || (node && node !== constants.blockchain.vm)) { + let options = this.simOptions; + if (node && node !== constants.blockchain.vm) { + options = deconstructUrl(node); + } + + if (!options.protocol) { + options.protocol = (options.type === "rpc") ? 'http' : 'ws'; + } + Object.assign(this.simOptions, options); + node = null; + } + + this.configObj.blockchainConfig = recursiveMerge({}, this.originalConfigObj.blockchainConfig, { + endpoint: this.simOptions.host ? buildUrl(this.simOptions.protocol, this.simOptions.host, this.simOptions.port, this.simOptions.type) : null, + type: this.simOptions.type, + accounts: this.simOptions.accounts, + coverage: options.coverage + }); + if (this.simOptions.client) { + this.configObj.blockchainConfig.client = this.simOptions.client; + } + this.logger.trace('Setting blockchain configs:', this.configObj.blockchainConfig); + await this.events.request2('config:blockchainConfig:set', this.configObj.blockchainConfig); + + try { + await this.events.request2("blockchain:node:stop"); + } catch (e) { + // Nothing to do here, the node probably wasn't even started + } + + await this.events.request2("blockchain:node:start", this.configObj.blockchainConfig); + const provider = await this.events.request2("blockchain:client:provider", "ethereum"); + cb(null, provider); + return provider; + } } module.exports = TestRunner;