Skip to content

Commit 87d92b6

Browse files
committed
feat(test/reporter): log tx functions during tests
Extract function log function to utils to use in the reporter to show txs in tests
1 parent 89753c1 commit 87d92b6

File tree

6 files changed

+304
-136
lines changed

6 files changed

+304
-136
lines changed

packages/embark-typings/src/contract.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export interface Contract {
44
abiDefinition: ABIDefinition[];
55
deployedAddress: string;
66
className: string;
7+
silent?: boolean;
78
}

packages/embark/src/lib/modules/console_listener/index.js

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const async = require('async');
22
const utils = require('../../utils/utils.js');
3+
const {getAddressToContract, getTransactionParams} = require('../../utils/transactionUtils');
34

45
class ConsoleListener {
56
constructor(embark, options) {
@@ -27,7 +28,7 @@ class ConsoleListener {
2728
this.contractsDeployed = true;
2829

2930
this._getContractsList((contractsList) => {
30-
this._updateContractList(contractsList);
31+
this.addressToContract = getAddressToContract(contractsList, this.addressToContract);
3132
});
3233
});
3334

@@ -57,37 +58,6 @@ class ConsoleListener {
5758
});
5859
}
5960

60-
_updateContractList(contractsList) {
61-
if (!contractsList) return;
62-
contractsList.forEach(contract => {
63-
if (!contract.deployedAddress) return;
64-
65-
let address = contract.deployedAddress.toLowerCase();
66-
if (!this.addressToContract[address]) {
67-
let funcSignatures = {};
68-
contract.abiDefinition
69-
.filter(func => func.type === "function")
70-
.map(func => {
71-
const name = func.name +
72-
'(' +
73-
(func.inputs ? func.inputs.map(input => input.type).join(',') : '') +
74-
')';
75-
funcSignatures[utils.sha3(name).substring(0, 10)] = {
76-
name,
77-
abi: func,
78-
functionName: func.name
79-
};
80-
});
81-
82-
this.addressToContract[address] = {
83-
name: contract.className,
84-
functions: funcSignatures,
85-
silent: contract.silent
86-
};
87-
}
88-
});
89-
}
90-
9161
_listenForLogRequests() {
9262
this.events.on('deploy:contract:receipt', receipt => {
9363
this.events.emit('contracts:log', {
@@ -121,26 +91,14 @@ class ConsoleListener {
12191
if (!contract) {
12292
this.logger.info(`Contract log for unknown contract: ${JSON.stringify(request)}`);
12393
return this._getContractsList((contractsList) => {
124-
this._updateContractList(contractsList);
94+
this.addressToContract = getAddressToContract(contractsList, this.addressToContract);
12595
});
12696
}
12797
const {name, silent} = contract;
12898
if (silent && !this.outputDone) {
12999
return;
130100
}
131-
132-
const func = contract.functions[data.substring(0, 10)];
133-
const functionName = func.functionName;
134-
135-
const decodedParameters = utils.decodeParams(func.abi.inputs, data.substring(10));
136-
let paramString = "";
137-
if (func.abi.inputs) {
138-
func.abi.inputs.forEach((input) => {
139-
let quote = input.type.indexOf("int") === -1 ? '"' : '';
140-
paramString += quote + decodedParameters[input.name] + quote + ", ";
141-
});
142-
paramString = paramString.substring(0, paramString.length - 2);
143-
}
101+
const {functionName, paramString} = getTransactionParams(contract, data);
144102

145103
gasUsed = utils.hexToNumber(gasUsed);
146104
blockNumber = utils.hexToNumber(blockNumber);

packages/embark/src/lib/modules/tests/reporter.js

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const Base = require('mocha/lib/reporters/base');
22
const ms = require('mocha/lib/ms');
33
const color = Base.color;
4+
const {getAddressToContract, getTransactionParams} = require('../../utils/transactionUtils');
45

56
class EmbarkApiSpec extends Base {
67
constructor(runner, options) {
@@ -43,28 +44,63 @@ class EmbarkSpec extends Base {
4344
self.stats.totalGasCost = 0;
4445
self.stats.test = {};
4546
self.stats.test.gasUsed = 0;
47+
self.contracts = [];
48+
self.addressToContract = {};
49+
self.txLogs = [];
4650

4751
function onContractReceipt(receipt) {
48-
const fmt = color('bright pass', ' ') +
49-
color('suite', ' %s') +
50-
color('light', ' deployed for ') +
51-
color(self.getGasColor(receipt.gasUsed), '%s') +
52-
color('light', ' gas');
52+
self.embarkEvents.request('contracts:contract', receipt.className, (contract) => {
53+
if (contract) {
54+
self.contracts.push(contract);
55+
self.addressToContract = getAddressToContract(self.contracts, self.addressToContract);
56+
}
57+
});
5358

54-
console.log(fmt, receipt.className, receipt.gasUsed);
59+
if (self.gasDetails) {
60+
const fmt = color('bright pass', ' ') +
61+
color('suite', ' %s') +
62+
color('light', ' deployed for ') +
63+
color(self.getGasColor(receipt.gasUsed), '%s') +
64+
color('light', ' gas');
65+
66+
console.log(fmt, receipt.className, receipt.gasUsed);
67+
}
5568
}
5669

57-
function onBlockHeader(blockHeader) {
70+
async function onBlockHeader(blockHeader) {
5871
if(!self.listenForGas) {
5972
return;
6073
}
6174
self.stats.totalGasCost += blockHeader.gasUsed;
6275
self.stats.test.gasUsed += blockHeader.gasUsed;
63-
}
6476

65-
if (self.gasDetails) {
66-
self.embarkEvents.on("deploy:contract:receipt", onContractReceipt);
77+
self.embarkEvents.request("blockchain:block:byNumber", blockHeader.number, (err, block) => {
78+
if (err) {
79+
return this.logger.error('Error getting block header', err.message || err);
80+
}
81+
// Don't know why, but sometimes we receive nothing
82+
if (!block || !block.transactions) {
83+
return;
84+
}
85+
block.transactions.forEach(transaction => {
86+
self.contracts.find(contract => {
87+
if (!contract.silent && contract.deployedAddress && transaction.to && contract.deployedAddress.toLowerCase() === transaction.to.toLowerCase()) {
88+
const c = self.addressToContract[contract.deployedAddress.toLowerCase()];
89+
if (!c) {
90+
return;
91+
}
92+
const {functionName, paramString} = getTransactionParams(c, transaction.input);
93+
94+
self.txLogs.push(`\t\t- ${contract.className}.${functionName}(${paramString}) [${transaction.gas} gas]`);
95+
return true;
96+
}
97+
return false;
98+
});
99+
});
100+
});
67101
}
102+
103+
self.embarkEvents.on("deploy:contract:receipt", onContractReceipt);
68104
self.embarkEvents.on("block:header", onBlockHeader);
69105
self.embarkEvents.setCommandHandler("reporter:toggleGasListener", () => {
70106
self.listenForGas = !self.listenForGas;
@@ -101,6 +137,7 @@ class EmbarkSpec extends Base {
101137

102138
runner.on('test', function () {
103139
self.stats.test.gasUsed = 0;
140+
self.contracts = [];
104141
});
105142

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

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

122163
runner.once('end', function () {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {Contract} from "embark";
2+
import {ABIDefinition} from "web3/eth/abi";
3+
4+
const utils = require("./utils");
5+
6+
export interface AddressToContract {
7+
name: string;
8+
functions: { [functionName: string]: FunctionSignature; };
9+
silent?: boolean;
10+
}
11+
12+
export interface AddressToContractArray {
13+
[address: string]: AddressToContract;
14+
}
15+
16+
export interface FunctionSignature {
17+
abi: ABIDefinition;
18+
functionName?: string;
19+
name: string;
20+
}
21+
22+
export function getAddressToContract(contractsList: Contract[], addressToContract: AddressToContractArray): AddressToContractArray {
23+
if (!contractsList) {
24+
return addressToContract;
25+
}
26+
contractsList.forEach((contract: Contract) => {
27+
if (!contract.deployedAddress) {
28+
return;
29+
}
30+
31+
const address = contract.deployedAddress.toLowerCase();
32+
if (addressToContract[address]) {
33+
return;
34+
}
35+
const funcSignatures: { [name: string]: FunctionSignature } = {};
36+
contract.abiDefinition
37+
.filter((func: ABIDefinition) => func.type === "function")
38+
.map((func: ABIDefinition) => {
39+
const name = `${func.name}(${func.inputs ? func.inputs.map((input) => input.type).join(",") : ""})`;
40+
funcSignatures[utils.sha3(name).substring(0, 10)] = {
41+
abi: func,
42+
functionName: func.name,
43+
name,
44+
};
45+
});
46+
47+
addressToContract[address] = {
48+
functions: funcSignatures,
49+
name: contract.className,
50+
silent: contract.silent,
51+
};
52+
});
53+
return addressToContract;
54+
}
55+
56+
export function getTransactionParams(contract: AddressToContract, transactionInput: string): object {
57+
const func = contract.functions[transactionInput.substring(0, 10)];
58+
const functionName = func.functionName;
59+
60+
const decodedParameters = utils.decodeParams(func.abi.inputs, transactionInput.substring(10));
61+
let paramString = "";
62+
if (func.abi.inputs) {
63+
func.abi.inputs.forEach((input) => {
64+
const quote = input.type.indexOf("int") === -1 ? '"' : "";
65+
paramString += quote + decodedParameters[input.name] + quote + ", ";
66+
});
67+
paramString = paramString.substring(0, paramString.length - 2);
68+
}
69+
return {
70+
functionName,
71+
paramString,
72+
};
73+
}

packages/embark/src/test/modules/console_listener.js

Lines changed: 5 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const {expect} = require('chai');
33
const sinon = require('sinon');
44
const Events = require('../../lib/core/events');
55
const Logger = require('../../lib/core/logger');
6+
const transactionUtils = require('../../lib/utils/transactionUtils');
67
const ConsoleListener = require('../../lib/modules/console_listener');
78
const IPC = require('../../lib/core/ipc.js');
89
require('colors');
@@ -99,8 +100,8 @@ function resetTest() {
99100
events,
100101
logger,
101102
fs: {
102-
existsSync: () => { return false },
103-
dappPath: () => { return "ok" }
103+
existsSync: () => { return false; },
104+
dappPath: () => { return "ok"; }
104105
},
105106
config: {
106107
contractsConfig: {}
@@ -128,85 +129,9 @@ describe('Console Listener', function () {
128129
done();
129130
});
130131

131-
describe('#updateContractList', function () {
132-
it('should not update contracts list', function (done) {
133-
contractsList.deployedAddress = undefined;
134-
consoleListener._updateContractList(contractsList);
135-
136-
expect(consoleListener.addressToContract.length).to.be.equal(0);
137-
done();
138-
});
139-
140-
it('should update contracts list', function (done) {
141-
consoleListener._updateContractList(contractsList);
142-
143-
expect(consoleListener.addressToContract["0x12345"]).to.deep.equal({
144-
name: "SimpleStorage",
145-
functions: {
146-
"0x2a1afcd9": {
147-
"abi": {
148-
"constant": true,
149-
"inputs": [],
150-
"name": "storedData",
151-
"outputs": [
152-
{
153-
"name": "",
154-
"type": "uint256"
155-
}
156-
],
157-
"payable": false,
158-
"stateMutability": "view",
159-
"type": "function"
160-
},
161-
"functionName": "storedData",
162-
"name": "storedData()"
163-
},
164-
"0x60fe47b1": {
165-
"abi": {
166-
"constant": false,
167-
"inputs": [
168-
{
169-
"name": "x",
170-
"type": "uint256"
171-
}
172-
],
173-
"name": "set",
174-
"outputs": [],
175-
"payable": false,
176-
"stateMutability": "nonpayable",
177-
"type": "function"
178-
},
179-
"functionName": "set",
180-
"name": "set(uint256)"
181-
},
182-
"0x6d4ce63c": {
183-
"abi": {
184-
"constant": true,
185-
"inputs": [],
186-
"name": "get",
187-
"outputs": [
188-
{
189-
"name": "retVal",
190-
"type": "uint256"
191-
}
192-
],
193-
"payable": false,
194-
"stateMutability": "view",
195-
"type": "function"
196-
},
197-
"functionName": "get",
198-
"name": "get()"
199-
}
200-
},
201-
silent: true
202-
});
203-
done();
204-
});
205-
});
206-
207132
describe('#listenForLogRequests', function () {
208133
it('should emit the correct contracts logs', function (done) {
209-
consoleListener._updateContractList(contractsList);
134+
transactionUtils.getAddressToContract(contractsList, consoleListener.addressToContract);
210135
consoleListener._onIpcLogRequest(ipcRequest);
211136

212137
const expectedContractLog = {
@@ -249,7 +174,7 @@ describe('Console Listener', function () {
249174

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

255180
expect(loggerInfos[0]).to.be.equal(JSON.stringify(ipcRequest));

0 commit comments

Comments
 (0)