Skip to content

Commit

Permalink
feat(test/reporter): log tx functions during tests
Browse files Browse the repository at this point in the history
Extract function log function to utils to use in the reporter
  to show txs in tests
  • Loading branch information
jrainville committed Mar 1, 2019
1 parent 89753c1 commit 87d92b6
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 136 deletions.
1 change: 1 addition & 0 deletions packages/embark-typings/src/contract.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export interface Contract {
abiDefinition: ABIDefinition[];
deployedAddress: string;
className: string;
silent?: boolean;
}
50 changes: 4 additions & 46 deletions packages/embark/src/lib/modules/console_listener/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const async = require('async');
const utils = require('../../utils/utils.js');
const {getAddressToContract, getTransactionParams} = require('../../utils/transactionUtils');

class ConsoleListener {
constructor(embark, options) {
Expand Down Expand Up @@ -27,7 +28,7 @@ class ConsoleListener {
this.contractsDeployed = true;

this._getContractsList((contractsList) => {
this._updateContractList(contractsList);
this.addressToContract = getAddressToContract(contractsList, this.addressToContract);
});
});

Expand Down Expand Up @@ -57,37 +58,6 @@ class ConsoleListener {
});
}

_updateContractList(contractsList) {
if (!contractsList) return;
contractsList.forEach(contract => {
if (!contract.deployedAddress) return;

let address = contract.deployedAddress.toLowerCase();
if (!this.addressToContract[address]) {
let funcSignatures = {};
contract.abiDefinition
.filter(func => func.type === "function")
.map(func => {
const name = func.name +
'(' +
(func.inputs ? func.inputs.map(input => input.type).join(',') : '') +
')';
funcSignatures[utils.sha3(name).substring(0, 10)] = {
name,
abi: func,
functionName: func.name
};
});

this.addressToContract[address] = {
name: contract.className,
functions: funcSignatures,
silent: contract.silent
};
}
});
}

_listenForLogRequests() {
this.events.on('deploy:contract:receipt', receipt => {
this.events.emit('contracts:log', {
Expand Down Expand Up @@ -121,26 +91,14 @@ class ConsoleListener {
if (!contract) {
this.logger.info(`Contract log for unknown contract: ${JSON.stringify(request)}`);
return this._getContractsList((contractsList) => {
this._updateContractList(contractsList);
this.addressToContract = getAddressToContract(contractsList, this.addressToContract);
});
}
const {name, silent} = contract;
if (silent && !this.outputDone) {
return;
}

const func = contract.functions[data.substring(0, 10)];
const functionName = func.functionName;

const decodedParameters = utils.decodeParams(func.abi.inputs, data.substring(10));
let paramString = "";
if (func.abi.inputs) {
func.abi.inputs.forEach((input) => {
let quote = input.type.indexOf("int") === -1 ? '"' : '';
paramString += quote + decodedParameters[input.name] + quote + ", ";
});
paramString = paramString.substring(0, paramString.length - 2);
}
const {functionName, paramString} = getTransactionParams(contract, data);

gasUsed = utils.hexToNumber(gasUsed);
blockNumber = utils.hexToNumber(blockNumber);
Expand Down
61 changes: 51 additions & 10 deletions packages/embark/src/lib/modules/tests/reporter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const Base = require('mocha/lib/reporters/base');
const ms = require('mocha/lib/ms');
const color = Base.color;
const {getAddressToContract, getTransactionParams} = require('../../utils/transactionUtils');

class EmbarkApiSpec extends Base {
constructor(runner, options) {
Expand Down Expand Up @@ -43,28 +44,63 @@ class EmbarkSpec extends Base {
self.stats.totalGasCost = 0;
self.stats.test = {};
self.stats.test.gasUsed = 0;
self.contracts = [];
self.addressToContract = {};
self.txLogs = [];

function onContractReceipt(receipt) {
const fmt = color('bright pass', ' ') +
color('suite', ' %s') +
color('light', ' deployed for ') +
color(self.getGasColor(receipt.gasUsed), '%s') +
color('light', ' gas');
self.embarkEvents.request('contracts:contract', receipt.className, (contract) => {
if (contract) {
self.contracts.push(contract);
self.addressToContract = getAddressToContract(self.contracts, self.addressToContract);
}
});

console.log(fmt, receipt.className, receipt.gasUsed);
if (self.gasDetails) {
const fmt = color('bright pass', ' ') +
color('suite', ' %s') +
color('light', ' deployed for ') +
color(self.getGasColor(receipt.gasUsed), '%s') +
color('light', ' gas');

console.log(fmt, receipt.className, receipt.gasUsed);
}
}

function onBlockHeader(blockHeader) {
async function onBlockHeader(blockHeader) {
if(!self.listenForGas) {
return;
}
self.stats.totalGasCost += blockHeader.gasUsed;
self.stats.test.gasUsed += blockHeader.gasUsed;
}

if (self.gasDetails) {
self.embarkEvents.on("deploy:contract:receipt", onContractReceipt);
self.embarkEvents.request("blockchain:block:byNumber", blockHeader.number, (err, block) => {
if (err) {
return this.logger.error('Error getting block header', err.message || err);
}
// Don't know why, but sometimes we receive nothing
if (!block || !block.transactions) {
return;
}
block.transactions.forEach(transaction => {
self.contracts.find(contract => {
if (!contract.silent && contract.deployedAddress && transaction.to && contract.deployedAddress.toLowerCase() === transaction.to.toLowerCase()) {
const c = self.addressToContract[contract.deployedAddress.toLowerCase()];
if (!c) {
return;
}
const {functionName, paramString} = getTransactionParams(c, transaction.input);

self.txLogs.push(`\t\t- ${contract.className}.${functionName}(${paramString}) [${transaction.gas} gas]`);
return true;
}
return false;
});
});
});
}

self.embarkEvents.on("deploy:contract:receipt", onContractReceipt);
self.embarkEvents.on("block:header", onBlockHeader);
self.embarkEvents.setCommandHandler("reporter:toggleGasListener", () => {
self.listenForGas = !self.listenForGas;
Expand Down Expand Up @@ -101,6 +137,7 @@ class EmbarkSpec extends Base {

runner.on('test', function () {
self.stats.test.gasUsed = 0;
self.contracts = [];
});

runner.on('pass', function (test) {
Expand All @@ -112,11 +149,15 @@ class EmbarkSpec extends Base {
' - ' +
color(self.getGasColor(self.stats.test.gasUsed), '[%d gas]');
console.log(fmt, test.title, test.duration, self.stats.test.gasUsed);
self.txLogs.forEach(log => console.log(log));
self.txLogs = [];
});

runner.on('fail', function (test) {
console.log(indent() + color('fail', ' %d) %s') + ' - ' + color(self.getGasColor(self.stats.test.gasUsed), '[%d gas]'),
++n, test.title, self.stats.test.gasUsed);
self.txLogs.forEach(log => console.log(log));
self.txLogs = [];
});

runner.once('end', function () {
Expand Down
73 changes: 73 additions & 0 deletions packages/embark/src/lib/utils/transactionUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {Contract} from "embark";
import {ABIDefinition} from "web3/eth/abi";

const utils = require("./utils");

export interface AddressToContract {
name: string;
functions: { [functionName: string]: FunctionSignature; };
silent?: boolean;
}

export interface AddressToContractArray {
[address: string]: AddressToContract;
}

export interface FunctionSignature {
abi: ABIDefinition;
functionName?: string;
name: string;
}

export function getAddressToContract(contractsList: Contract[], addressToContract: AddressToContractArray): AddressToContractArray {
if (!contractsList) {
return addressToContract;
}
contractsList.forEach((contract: Contract) => {
if (!contract.deployedAddress) {
return;
}

const address = contract.deployedAddress.toLowerCase();
if (addressToContract[address]) {
return;
}
const funcSignatures: { [name: string]: FunctionSignature } = {};
contract.abiDefinition
.filter((func: ABIDefinition) => func.type === "function")
.map((func: ABIDefinition) => {
const name = `${func.name}(${func.inputs ? func.inputs.map((input) => input.type).join(",") : ""})`;
funcSignatures[utils.sha3(name).substring(0, 10)] = {
abi: func,
functionName: func.name,
name,
};
});

addressToContract[address] = {
functions: funcSignatures,
name: contract.className,
silent: contract.silent,
};
});
return addressToContract;
}

export function getTransactionParams(contract: AddressToContract, transactionInput: string): object {
const func = contract.functions[transactionInput.substring(0, 10)];
const functionName = func.functionName;

const decodedParameters = utils.decodeParams(func.abi.inputs, transactionInput.substring(10));
let paramString = "";
if (func.abi.inputs) {
func.abi.inputs.forEach((input) => {
const quote = input.type.indexOf("int") === -1 ? '"' : "";
paramString += quote + decodedParameters[input.name] + quote + ", ";
});
paramString = paramString.substring(0, paramString.length - 2);
}
return {
functionName,
paramString,
};
}
85 changes: 5 additions & 80 deletions packages/embark/src/test/modules/console_listener.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const {expect} = require('chai');
const sinon = require('sinon');
const Events = require('../../lib/core/events');
const Logger = require('../../lib/core/logger');
const transactionUtils = require('../../lib/utils/transactionUtils');
const ConsoleListener = require('../../lib/modules/console_listener');
const IPC = require('../../lib/core/ipc.js');
require('colors');
Expand Down Expand Up @@ -99,8 +100,8 @@ function resetTest() {
events,
logger,
fs: {
existsSync: () => { return false },
dappPath: () => { return "ok" }
existsSync: () => { return false; },
dappPath: () => { return "ok"; }
},
config: {
contractsConfig: {}
Expand Down Expand Up @@ -128,85 +129,9 @@ describe('Console Listener', function () {
done();
});

describe('#updateContractList', function () {
it('should not update contracts list', function (done) {
contractsList.deployedAddress = undefined;
consoleListener._updateContractList(contractsList);

expect(consoleListener.addressToContract.length).to.be.equal(0);
done();
});

it('should update contracts list', function (done) {
consoleListener._updateContractList(contractsList);

expect(consoleListener.addressToContract["0x12345"]).to.deep.equal({
name: "SimpleStorage",
functions: {
"0x2a1afcd9": {
"abi": {
"constant": true,
"inputs": [],
"name": "storedData",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
"functionName": "storedData",
"name": "storedData()"
},
"0x60fe47b1": {
"abi": {
"constant": false,
"inputs": [
{
"name": "x",
"type": "uint256"
}
],
"name": "set",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
"functionName": "set",
"name": "set(uint256)"
},
"0x6d4ce63c": {
"abi": {
"constant": true,
"inputs": [],
"name": "get",
"outputs": [
{
"name": "retVal",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
"functionName": "get",
"name": "get()"
}
},
silent: true
});
done();
});
});

describe('#listenForLogRequests', function () {
it('should emit the correct contracts logs', function (done) {
consoleListener._updateContractList(contractsList);
transactionUtils.getAddressToContract(contractsList, consoleListener.addressToContract);
consoleListener._onIpcLogRequest(ipcRequest);

const expectedContractLog = {
Expand Down Expand Up @@ -249,7 +174,7 @@ describe('Console Listener', function () {

it('should emit a log for a non-contract log', function (done) {
ipcRequest.type = 'something-other-than-contract-log';
consoleListener._updateContractList(contractsList);
transactionUtils.getAddressToContract(contractsList, consoleListener.addressToContract);
consoleListener._onIpcLogRequest(ipcRequest);

expect(loggerInfos[0]).to.be.equal(JSON.stringify(ipcRequest));
Expand Down
Loading

0 comments on commit 87d92b6

Please sign in to comment.