feat(@embark/test-runner): make vm default node (#1846)

* feat: make vm default node

* feat(@embark/tests): enable switching node between tests
This commit is contained in:
Jonathan Rainville 2019-09-12 17:30:28 -04:00 committed by Iuri Matias
parent 2f9d5e6085
commit f54fbf0b3d
22 changed files with 368 additions and 163 deletions

View File

@ -5,7 +5,7 @@ let accounts;
// For documentation please see https://embark.status.im/docs/contracts_testing.html // For documentation please see https://embark.status.im/docs/contracts_testing.html
config({ config({
//deployment: { //blockchain: {
// accounts: [ // accounts: [
// // you can configure custom accounts with a custom balance // // you can configure custom accounts with a custom balance
// // see https://embark.status.im/docs/contracts_testing.html#Configuring-accounts // // see https://embark.status.im/docs/contracts_testing.html#Configuring-accounts
@ -31,7 +31,7 @@ contract("SimpleStorage", function () {
}); });
it("set storage value", async 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(); let result = await SimpleStorage.methods.get().call();
assert.strictEqual(parseInt(result, 10), 150); assert.strictEqual(parseInt(result, 10), 150);
}); });

View File

@ -35,6 +35,7 @@
"webpackDone": "webpackDone" "webpackDone": "webpackDone"
}, },
"blockchain": { "blockchain": {
"vm": "vm",
"call": "eth_call", "call": "eth_call",
"clients": { "clients": {
"geth": "geth", "geth": "geth",

View File

@ -708,9 +708,7 @@ simulator(_options) {
const Engine = require('../lib/core/engine.js'); const Engine = require('../lib/core/engine.js');
const engine = new Engine({ const engine = new Engine({
// TODO: this should not be necessary env: options.env,
env: "development",
//env: options.env,
client: options.client, client: options.client,
locale: options.locale, locale: options.locale,
version: this.version, version: this.version,
@ -743,23 +741,6 @@ simulator(_options) {
engine.registerModuleGroup("pipeline"); engine.registerModuleGroup("pipeline");
engine.registerModuleGroup("tests", options); 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); engine.startEngine(next);
}, },
function setupTestEnvironment(next) { function setupTestEnvironment(next) {

View File

@ -22,7 +22,7 @@ export function getBlockchainDefaults(env) {
wsHost: "localhost", wsHost: "localhost",
wsPort: 8546, wsPort: 8546,
networkType: "custom", networkType: "custom",
miningMode: 'dev', isDev: true,
nodiscover: true, nodiscover: true,
maxpeers: 0, maxpeers: 0,
targetGasLimit: 8000000, targetGasLimit: 8000000,

View File

@ -211,6 +211,7 @@ class Engine {
contractsComponents(_options) { contractsComponents(_options) {
this.registerModule('ethereum-blockchain-client'); this.registerModule('ethereum-blockchain-client');
this.registerModule('ganache');
this.registerModulePackage('embark-web3'); this.registerModulePackage('embark-web3');
this.registerModulePackage('embark-accounts-manager'); this.registerModulePackage('embark-accounts-manager');
this.registerModulePackage('embark-specialconfigs', {plugins: this.plugins}); this.registerModulePackage('embark-specialconfigs', {plugins: this.plugins});

View File

@ -1,5 +1,6 @@
import async from 'async'; import async from 'async';
const {__} = require('embark-i18n'); const {__} = require('embark-i18n');
const constants = require('embark-core/constants');
const Web3RequestManager = require('web3-core-requestmanager'); const Web3RequestManager = require('web3-core-requestmanager');
import BlockchainAPI from "./api"; import BlockchainAPI from "./api";
@ -12,7 +13,7 @@ class Blockchain {
this.blockchainConfig = embark.config.blockchainConfig; this.blockchainConfig = embark.config.blockchainConfig;
this.contractConfig = embark.config.contractConfig; this.contractConfig = embark.config.contractConfig;
this.blockchainApi = new BlockchainAPI(embark); this.blockchainApi = new BlockchainAPI(embark);
this.startedClient = null;
embark.registerActionForEvent("pipeline:generateAll:before", this.addArtifactFile.bind(this)); embark.registerActionForEvent("pipeline:generateAll:before", this.addArtifactFile.bind(this));
@ -22,6 +23,16 @@ class Blockchain {
}); });
this.events.setCommandHandler("blockchain:node:start", async (blockchainConfig, cb) => { 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 requestManager = new Web3RequestManager.Manager(blockchainConfig.endpoint);
const ogConsoleError = console.error; const ogConsoleError = console.error;
@ -37,21 +48,52 @@ class Blockchain {
console.error = ogConsoleError; console.error = ogConsoleError;
if (!err) { if (!err) {
// Node is already started // Node is already started
this.events.emit("blockchain:started"); started();
return cb(null, true); return cb(null, true);
} }
const clientName = blockchainConfig.client; const clientFunctions = this.blockchainNodes[clientName];
const client = this.blockchainNodes[clientName]; if (!clientFunctions) {
if (!client) return cb("client " + clientName + " not found"); return cb(__("Client %s not found", clientName));
}
let onStart = () => { let onStart = () => {
this.events.emit("blockchain:started", clientName); started();
cb(); 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.registerAPIs("ethereum");
this.blockchainApi.registerRequests("ethereum"); this.blockchainApi.registerRequests("ethereum");
} }

View File

@ -29,6 +29,10 @@ class EthereumBlockchainClient {
this.events.request("blockchain:client:register", "ethereum", this.getClient.bind(this)); this.events.request("blockchain:client:register", "ethereum", this.getClient.bind(this));
this.events.request("deployment:deployer:register", "ethereum", this.deployer.bind(this)); this.events.request("deployment:deployer:register", "ethereum", this.deployer.bind(this));
this.events.on("blockchain:started", () => {
this._web3 = null;
});
this.registerAPIRequests(); this.registerAPIRequests();
} }
@ -54,23 +58,18 @@ class EthereumBlockchainClient {
async deployer(contract, done) { async deployer(contract, done) {
const web3 = await this.web3; const web3 = await this.web3;
// var web3 = new Web3("ws://localhost:8556") const [account] = await web3.eth.getAccounts();
// web3.eth.getAccounts().then((accounts) => { const contractObj = new web3.eth.Contract(contract.abiDefinition, contract.address);
let accounts = await web3.eth.getAccounts(); const contractObject = contractObj.deploy({arguments: (contract.args || []), data: ("0x" + contract.code)});
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)});
if (contract.gas === 'auto' || !contract.gas) { if (contract.gas === 'auto' || !contract.gas) {
let gasValue = await contractObject.estimateGas(); const gasValue = await contractObject.estimateGas();
let increase_per = 1 + (Math.random() / 10.0); const increase_per = 1 + (Math.random() / 10.0);
contract.gas = Math.floor(gasValue * increase_per); contract.gas = Math.floor(gasValue * increase_per);
} }
if (!contract.gasPrice) { if (!contract.gasPrice) {
let gasPrice = await web3.eth.getGasPrice(); const gasPrice = await web3.eth.getGasPrice();
contract.gasPrice = contract.gasPrice || gasPrice; contract.gasPrice = contract.gasPrice || gasPrice;
} }
@ -198,8 +197,7 @@ class EthereumBlockchainClient {
} }
async determineAccounts(params, callback) { async determineAccounts(params, callback) {
let provider = await this.events.request2("blockchain:client:provider", "ethereum"); const web3 = await this.web3;
let web3 = new Web3(provider);
let accounts = await web3.eth.getAccounts(); let accounts = await web3.eth.getAccounts();
let deploymentAccount = accounts[0]; let deploymentAccount = accounts[0];
let contract = params.contract; let contract = params.contract;

View File

@ -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;

View File

@ -6,7 +6,6 @@ import {ws, rpc} from './check.js';
const constants = require('embark-core/constants'); const constants = require('embark-core/constants');
class Geth { class Geth {
constructor(embark, options) { constructor(embark, options) {
this.embark = embark; this.embark = embark;
this.embarkConfig = embark.config.embarkConfig; this.embarkConfig = embark.config.embarkConfig;
@ -23,7 +22,8 @@ class Geth {
return; return;
} }
this.events.request("blockchain:node:register", constants.blockchain.clients.geth, (readyCb) => { this.events.request("blockchain:node:register", constants.blockchain.clients.geth, {
launchFn: (readyCb) => {
this.events.request('processes:register', 'blockchain', { this.events.request('processes:register', 'blockchain', {
launchFn: (cb) => { launchFn: (cb) => {
this.startBlockchainNode(cb); this.startBlockchainNode(cb);
@ -39,6 +39,11 @@ class Geth {
readyCb(); readyCb();
}); });
this.registerServiceCheck(); this.registerServiceCheck();
},
stopFn: async (cb) => {
await this.events.request("processes:stop", "blockchain");
cb();
}
}); });
this.events.request("whisper:node:register", constants.blockchain.clients.geth, readyCb => { this.events.request("whisper:node:register", constants.blockchain.clients.geth, readyCb => {

View File

@ -34,6 +34,10 @@ export default class AccountsManager {
this.embark.registerActionForEvent("blockchain:proxy:request", this.checkBlockchainRequest.bind(this)); this.embark.registerActionForEvent("blockchain:proxy:request", this.checkBlockchainRequest.bind(this));
this.embark.registerActionForEvent("blockchain:proxy:response", this.checkBlockchainResponse.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. // 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. // 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 // Update the nonce and sign it

View File

@ -9,6 +9,10 @@ export default class DeploymentChecks {
this.events = events; this.events = events;
this.logger = logger; this.logger = logger;
this._web3 = null; this._web3 = null;
this.events.on("blockchain:started", () => {
this._web3 = null;
});
} }
get web3() { get web3() {

View File

@ -17,6 +17,10 @@ export default class TrackingFunctions {
this._block = null; this._block = null;
this.ensureChainTrackerFile(); this.ensureChainTrackerFile();
this.events.on("blockchain:started", () => {
this._web3 = null;
});
} }
get web3() { get web3() {

View File

@ -84,6 +84,10 @@ class ENS {
setImmediate(cb, this.isENSName(name)); setImmediate(cb, this.isENSName(name));
}); });
this.events.on("blockchain:started", () => {
this._web3 = null;
});
this.init(() => {}); this.init(() => {});
} }

View File

@ -47,6 +47,14 @@
}, },
"extends": "../../../.eslintrc.json" "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": { "devDependencies": {
"@babel/cli": "7.2.3", "@babel/cli": "7.2.3",
"@babel/core": "7.2.2", "@babel/core": "7.2.2",
@ -60,12 +68,7 @@
"tslint": "5.16.0", "tslint": "5.16.0",
"typescript": "3.4.5" "typescript": "3.4.5"
}, },
"dependencies": {
"async": "2.6.1",
"embarkjs": "^4.1.1", "embarkjs": "^4.1.1",
"mocha": "6.2.0",
"web3": "1.2.1"
},
"engines": { "engines": {
"node": ">=8.12.0 <12.0.0", "node": ">=8.12.0 <12.0.0",
"npm": ">=6.4.1", "npm": ">=6.4.1",

View File

@ -1,3 +1,5 @@
import {__} from 'embark-i18n';
const assert = require('assert').strict; const assert = require('assert').strict;
const async = require('async'); const async = require('async');
const EmbarkJS = require('embarkjs'); const EmbarkJS = require('embarkjs');
@ -14,8 +16,12 @@ class MochaTestRunner {
this.embark = embark; this.embark = embark;
this.events = embark.events; this.events = embark.events;
this.plugins = options.plugins; this.plugins = options.plugins;
this.logger = embark.logger;
this.fs = embark.fs;
this.files = []; this.files = [];
this.options = {};
this.web3 = null;
this.events.request('tests:runner:register', this.events.request('tests:runner:register',
'JavaScript (Mocha)', 'JavaScript (Mocha)',
@ -37,20 +43,31 @@ class MochaTestRunner {
return JAVASCRIPT_TEST_MATCH.test(path); return JAVASCRIPT_TEST_MATCH.test(path);
} }
run(options, cb) { async run(options, cb) {
const {events, plugins} = this; const {events} = this.embark;
const {reporter} = options; const {reporter} = options;
this.options = options;
const Module = require("module"); const Module = require("module");
const originalRequire = require("module").prototype.require; const originalRequire = require("module").prototype.require;
let accounts = []; let accounts = [];
let compiledContracts = {}; let compiledContracts = {};
let web3;
const config = (cfg, acctCb) => { const config = (cfg, acctCb) => {
global.before((done) => { global.before((done) => {
async.waterfall([ 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) => { (next) => {
events.request("contracts:build", cfg, compiledContracts, next); events.request("contracts:build", cfg, compiledContracts, next);
}, },
@ -86,6 +103,12 @@ class MochaTestRunner {
} }
next(); next();
},
(next) => {
this.web3.eth.getAccounts((err, accts) => {
accounts = accts;
next(err);
});
} }
], (err) => { ], (err) => {
// Reset the gas accumulator so that we don't show deployment gas on the // Reset the gas accumulator so that we don't show deployment gas on the
@ -102,32 +125,37 @@ 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([ async.waterfall([
(next) => { // request provider (next) => {
events.request("blockchain:client:provider", "ethereum", next); this.plugins.emitAndRunActionsForEvent('tests:contracts:compile:before', contractFiles, next);
}, },
(provider, next) => { // set provider and fetch account list (_contractFiles, next) => {
web3 = new Web3(provider); contractFiles = _contractFiles;
web3.eth.getAccounts(next); events.request("compiler:contracts:compile", _contractFiles, next);
}, },
(accts, next) => { // reset contracts as we might have state leakage from other plugins (_compiledContracts, next) => {
accounts = accts; this.plugins.emitAndRunActionsForEvent('tests:contracts:compile:after', _compiledContracts, next);
events.request("contracts:reset", next);
}, },
(next) => { // get contract files (_compiledContracts, next) => {
events.request("config:contractsFiles", next); compiledContracts = _compiledContracts;
}, const fns = this.files.map((file) => {
(cf, next) => { return (seriesCb) => {
plugins.emitAndRunActionsForEvent('tests:contracts:compile:before', cf, next);
}, this.fs.readFile(file, (err, data) => {
(cf, next) => { // compile contracts if (err) {
events.request("compiler:contracts:compile", cf, next); self.logger.error(__('Error reading file %s', file));
}, self.logger.error(err);
(cc, next) => { seriesCb(null, 1);
plugins.emitAndRunActionsForEvent('tests:contracts:compile:after', cc, next); }
}, if (data.toString().search(/contract\(|describe\(/) === -1) {
(cc, next) => { // override require return seriesCb(null, 0);
compiledContracts = cc; }
Module.prototype.require = function(req) { Module.prototype.require = function(req) {
const prefix = "Embark/contracts/"; const prefix = "Embark/contracts/";
@ -141,15 +169,11 @@ class MochaTestRunner {
if (!instance) { if (!instance) {
throw new Error(`Cannot find module '${req}'`); throw new Error(`Cannot find module '${req}'`);
} }
return instance; return instance;
}; };
next();
},
(next) => { // initialize Mocha
const mocha = new Mocha();
mocha.reporter(Reporter, { reporter: reporter }); const mocha = new Mocha();
mocha.reporter(Reporter, {reporter: options.reporter});
const describeWithAccounts = (scenario, cb) => { const describeWithAccounts = (scenario, cb) => {
Mocha.describe(scenario, cb.bind(mocha, accounts)); Mocha.describe(scenario, cb.bind(mocha, accounts));
}; };
@ -161,14 +185,19 @@ class MochaTestRunner {
global.config = config; global.config = config;
}); });
mocha.suite.timeout(TEST_TIMEOUT);
for(const file of this.files) {
mocha.addFile(file);
}
mocha.run((_failures) => { mocha.suite.timeout(TEST_TIMEOUT);
next(); mocha.addFile(file);
mocha.run((failures) => {
Module.prototype.require = originalRequire;
seriesCb(null, failures);
}); });
});
};
});
async.series(fns, next);
} }
], (err) => { ], (err) => {
events.emit('tests:finished'); events.emit('tests:finished');

View File

@ -10,6 +10,9 @@ class TransactionTracker {
this._web3 = null; this._web3 = null;
embark.events.on("block:header", this.onBlockHeader.bind(this)); embark.events.on("block:header", this.onBlockHeader.bind(this));
this.events.on("blockchain:started", () => {
this._web3 = null;
});
this.registerAPICalls(); this.registerAPICalls();
this.subscribeToPendingTransactions(); this.subscribeToPendingTransactions();
} }

View File

@ -40,11 +40,6 @@ class EmbarkWeb3 {
} }
async registerWeb3Object() { 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 provider = await this.events.request2("blockchain:client:provider", "ethereum");
const web3 = new Web3(provider); const web3 = new Web3(provider);
await this.events.request2("runcode:register", 'web3', web3); await this.events.request2("runcode:register", 'web3', web3);

View File

@ -44,6 +44,10 @@ class ContractsManager {
cb(null, this.contracts[contract.className]); cb(null, this.contracts[contract.className]);
}); });
this.events.on("blockchain:started", () => {
this._web3 = null;
});
this.registerCommands(); this.registerCommands();
this.registerAPIs(); this.registerAPIs();
} }
@ -262,6 +266,9 @@ class ContractsManager {
function prepareContractsFromConfig(callback) { function prepareContractsFromConfig(callback) {
self.events.emit("status", __("Building...")); self.events.emit("status", __("Building..."));
if (contractsConfig.contracts.deploy) {
contractsConfig.contracts = contractsConfig.contracts.deploy;
}
async.eachOf(contractsConfig.contracts, (contract, className, eachCb) => { async.eachOf(contractsConfig.contracts, (contract, className, eachCb) => {
contract = new Contract(self.logger, contract); contract = new Contract(self.logger, contract);
if (!contract.artifact) { if (!contract.artifact) {

View File

@ -13,7 +13,6 @@ class ContractDeployer {
} }
deployContract(contract, callback) { deployContract(contract, callback) {
async.waterfall([ async.waterfall([
(next) => { (next) => {
this.plugins.emitAndRunActionsForEvent('deployment:contract:beforeDeploy', {contract: contract}, (err, _params) => { this.plugins.emitAndRunActionsForEvent('deployment:contract:beforeDeploy', {contract: contract}, (err, _params) => {
@ -22,7 +21,6 @@ class ContractDeployer {
}); });
}, },
(next) => { (next) => {
// self.plugins.emitAndRunActionsForEvent('deployment:contract:arguments', {contract: contract}, (_params) => {
this.plugins.emitAndRunActionsForEvent('deployment:contract:shouldDeploy', {contract: contract, shouldDeploy: true}, (err, params) => { this.plugins.emitAndRunActionsForEvent('deployment:contract:shouldDeploy', {contract: contract, shouldDeploy: true}, (err, params) => {
next(err, params); next(err, params);
}); });

View File

@ -11,26 +11,29 @@ export default class ProxyManager {
private proxy: any; private proxy: any;
private plugins: any; private plugins: any;
private readonly host: string; private readonly host: string;
private rpcPort: number; private rpcPort = 0;
private wsPort: number; private wsPort = 0;
private ready: boolean; private ready = false;
private isWs = false; private isWs = false;
private vms: any[];
constructor(private embark: Embark, options: any) { constructor(private embark: Embark, options: any) {
this.logger = embark.logger; this.logger = embark.logger;
this.events = embark.events; this.events = embark.events;
this.plugins = options.plugins; this.plugins = options.plugins;
this.ready = false; this.vms = [];
this.rpcPort = 0;
this.wsPort = 0;
this.host = "localhost"; this.host = "localhost";
this.events.once("blockchain:started", async () => { this.events.on("blockchain:started", async (clientName: string) => {
await this.setupProxy(); await this.setupProxy(clientName);
this.ready = true; this.ready = true;
this.events.emit("proxy:ready"); 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) { if (!this.embark.config.blockchainConfig.proxy) {
this.logger.warn(__("The proxy has been disabled -- some Embark features will not work.")); 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")); cb(null, buildUrl("http", this.host, this.rpcPort, "rpc"));
}); });
this.events.setCommandHandler("proxy:vm:register", (handler: any) => {
this.vms.push(handler);
});
} }
public onReady() { public onReady() {
@ -61,23 +68,31 @@ export default class ProxyManager {
}); });
} }
private async setupProxy() { private async setupProxy(clientName: string) {
if (!this.embark.config.blockchainConfig.proxy) { if (!this.embark.config.blockchainConfig.proxy) {
return; return;
} }
if (this.proxy) {
throw new Error("Proxy is already started");
}
const port = await findNextPort(this.embark.config.blockchainConfig.rpcPort + constants.blockchain.servicePortOnProxy); const port = await findNextPort(this.embark.config.blockchainConfig.rpcPort + constants.blockchain.servicePortOnProxy);
this.rpcPort = port; this.rpcPort = port;
this.wsPort = port + 1; 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}) this.proxy = await new Proxy({events: this.events, plugins: this.plugins, logger: this.logger, vms: this.vms});
.serve(
this.embark.config.blockchainConfig.endpoint, await this.proxy.serve(
clientName === constants.blockchain.vm ? constants.blockchain.vm : this.embark.config.blockchainConfig.endpoint,
this.host, this.host,
this.isWs ? this.wsPort : this.rpcPort, this.isWs ? this.wsPort : this.rpcPort,
this.isWs, this.isWs,
); );
return; }
private stopProxy() {
this.proxy.stop();
this.proxy = null;
} }
} }

View File

@ -4,6 +4,7 @@ import express from 'express';
import expressWs from 'express-ws'; import expressWs from 'express-ws';
import cors from 'cors'; import cors from 'cors';
const Web3RequestManager = require('web3-core-requestmanager'); const Web3RequestManager = require('web3-core-requestmanager');
const constants = require("embark-core/constants");
const ACTION_TIMEOUT = 5000; const ACTION_TIMEOUT = 5000;
@ -15,9 +16,15 @@ export class Proxy {
this.timeouts = {}; this.timeouts = {};
this.plugins = options.plugins; this.plugins = options.plugins;
this.logger = options.logger; this.logger = options.logger;
this.vms = options.vms;
this.app = null;
this.server = null;
} }
async serve(endpoint, localHost, localPort, ws) { async serve(endpoint, localHost, localPort, ws) {
if (endpoint === constants.blockchain.vm) {
endpoint = this.vms[this.vms.length - 1]();
}
const requestManager = new Web3RequestManager.Manager(endpoint); const requestManager = new Web3RequestManager.Manager(endpoint);
try { try {
@ -26,17 +33,17 @@ export class Proxy {
throw new Error(__('Unable to connect to the blockchain endpoint')); throw new Error(__('Unable to connect to the blockchain endpoint'));
} }
const app = express(); this.app = express();
if (ws) { if (ws) {
expressWs(app); expressWs(this.app);
} }
app.use(cors()); this.app.use(cors());
app.use(express.json()); this.app.use(express.json());
app.use(express.urlencoded({extended: true})); this.app.use(express.urlencoded({extended: true}));
if (ws) { if (ws) {
app.ws('/', (ws, _wsReq) => { this.app.ws('/', (ws, _wsReq) => {
ws.on('message', (msg) => { ws.on('message', (msg) => {
let jsonMsg; let jsonMsg;
try { try {
@ -50,7 +57,7 @@ export class Proxy {
// Send the possibly modified request to the Node // Send the possibly modified request to the Node
requestManager.send(resp.reqData, (err, result) => { requestManager.send(resp.reqData, (err, result) => {
if (err) { 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) => { this.emitActionsForResponse(resp.reqData, {jsonrpc: "2.0", id: resp.reqData.id, result}, (_err, resp) => {
// Send back to the caller (web3) // Send back to the caller (web3)
@ -62,7 +69,7 @@ export class Proxy {
}); });
} else { } else {
// HTTP // HTTP
app.use((req, res) => { this.app.use((req, res) => {
// Modify request // Modify request
this.emitActionsForRequest(req.body, (_err, resp) => { this.emitActionsForRequest(req.body, (_err, resp) => {
// Send the possibly modified request to the Node // Send the possibly modified request to the Node
@ -80,9 +87,9 @@ export class Proxy {
} }
return new Promise(resolve => { 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; calledBack = true;
}); });
} }
stop() {
if (!this.server) {
return;
}
this.server.close();
this.server = null;
this.app = null;
this.commList = {};
this.receipts = {};
this.transactions = {};
this.timeouts = {};
}
} }

View File

@ -1,9 +1,12 @@
import { __ } from 'embark-i18n'; import { __ } from 'embark-i18n';
import {buildUrl, deconstructUrl, recursiveMerge} from "embark-utils";
const async = require('async'); const async = require('async');
const chalk = require('chalk'); const chalk = require('chalk');
const path = require('path'); const path = require('path');
const { dappPath } = require('embark-utils'); const { dappPath } = require('embark-utils');
import cloneDeep from "lodash.clonedeep";
import { COVERAGE_GAS_LIMIT, GAS_LIMIT } from './constants'; import { COVERAGE_GAS_LIMIT, GAS_LIMIT } from './constants';
const constants = require('embark-core/constants');
const coverage = require('istanbul-lib-coverage'); const coverage = require('istanbul-lib-coverage');
const reporter = require('istanbul-lib-report'); const reporter = require('istanbul-lib-report');
@ -22,6 +25,10 @@ class TestRunner {
this.gasLimit = options.coverage ? COVERAGE_GAS_LIMIT : GAS_LIMIT; this.gasLimit = options.coverage ? COVERAGE_GAS_LIMIT : GAS_LIMIT;
this.files = []; this.files = [];
this.configObj = embark.config;
this.originalConfigObj = cloneDeep(embark.config);
this.simOptions = {};
this.events.setCommandHandler('tests:run', (options, callback) => { this.events.setCommandHandler('tests:run', (options, callback) => {
this.run(options, callback); this.run(options, callback);
}); });
@ -32,6 +39,9 @@ class TestRunner {
// like Jest tests and such. // like Jest tests and such.
this.runners.unshift({pluginName, matchFn, addFn, runFn}); 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) { run(options, cb) {
@ -65,7 +75,7 @@ class TestRunner {
}); });
async.series(runnerFns, next); async.series(runnerFns, next);
}, }
], (err) => { ], (err) => {
reporter.footer(); reporter.footer();
@ -85,16 +95,16 @@ class TestRunner {
open(dappPath('coverage/index.html')).then(() => { open(dappPath('coverage/index.html')).then(() => {
cb(err, reporter.passes, reporter.fails); cb(err, reporter.passes, reporter.fails);
}); });
} catch(err) { } catch(e) {
process.stdout.write(chalk`{red Coverage report could not be created:}\n{white ${err.message}}\n`); process.stdout.write(chalk`{red Coverage report could not be created:}\n{white ${e.message}}\n`);
cb(err, reporter.passes, reporter.fails); cb(e, reporter.passes, reporter.fails);
} }
}); });
} }
generateCoverageReport() { generateCoverageReport() {
const coveragePath = dappPath(".embark", "coverage.json"); 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 map = coverage.createCoverageMap(coverageMap);
const tree = reporter.summarizers.nested(map); const tree = reporter.summarizers.nested(map);
@ -131,6 +141,77 @@ class TestRunner {
cb(null, [filePath]); 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; module.exports = TestRunner;