mirror of https://github.com/embarklabs/embark.git
feat(test/reporter): log tx functions during tests
Extract function log function to utils to use in the reporter to show txs in tests
This commit is contained in:
parent
89753c11e3
commit
87d92b6091
|
@ -4,4 +4,5 @@ export interface Contract {
|
|||
abiDefinition: ABIDefinition[];
|
||||
deployedAddress: string;
|
||||
className: string;
|
||||
silent?: boolean;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
@ -27,7 +28,7 @@ class ConsoleListener {
|
|||
this.contractsDeployed = true;
|
||||
|
||||
this._getContractsList((contractsList) => {
|
||||
this._updateContractList(contractsList);
|
||||
this.addressToContract = getAddressToContract(contractsList, this.addressToContract);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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', {
|
||||
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
@ -43,8 +44,19 @@ 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) {
|
||||
self.embarkEvents.request('contracts:contract', receipt.className, (contract) => {
|
||||
if (contract) {
|
||||
self.contracts.push(contract);
|
||||
self.addressToContract = getAddressToContract(self.contracts, self.addressToContract);
|
||||
}
|
||||
});
|
||||
|
||||
if (self.gasDetails) {
|
||||
const fmt = color('bright pass', ' ') +
|
||||
color('suite', ' %s') +
|
||||
color('light', ' deployed for ') +
|
||||
|
@ -53,18 +65,42 @@ class EmbarkSpec extends Base {
|
|||
|
||||
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;
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (self.gasDetails) {
|
||||
self.embarkEvents.on("deploy:contract:receipt", onContractReceipt);
|
||||
}
|
||||
self.embarkEvents.on("block:header", onBlockHeader);
|
||||
self.embarkEvents.setCommandHandler("reporter:toggleGasListener", () => {
|
||||
self.listenForGas = !self.listenForGas;
|
||||
|
@ -101,6 +137,7 @@ class EmbarkSpec extends Base {
|
|||
|
||||
runner.on('test', function () {
|
||||
self.stats.test.gasUsed = 0;
|
||||
self.contracts = [];
|
||||
});
|
||||
|
||||
runner.on('pass', function (test) {
|
||||
|
@ -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 () {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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');
|
||||
|
@ -99,8 +100,8 @@ function resetTest() {
|
|||
events,
|
||||
logger,
|
||||
fs: {
|
||||
existsSync: () => { return false },
|
||||
dappPath: () => { return "ok" }
|
||||
existsSync: () => { return false; },
|
||||
dappPath: () => { return "ok"; }
|
||||
},
|
||||
config: {
|
||||
contractsConfig: {}
|
||||
|
@ -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 = {
|
||||
|
@ -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));
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/*globals describe, it, beforeEach*/
|
||||
const {expect} = require('chai');
|
||||
const transactionUtils = require('../lib/utils/transactionUtils');
|
||||
require('colors');
|
||||
|
||||
let contractsList;
|
||||
|
||||
function resetTest() {
|
||||
contractsList = [
|
||||
{
|
||||
abiDefinition: [
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "storedData",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "x",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "set",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "get",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "retVal",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "initialValue",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
}
|
||||
],
|
||||
deployedAddress: "0x12345",
|
||||
className: "SimpleStorage",
|
||||
silent: true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
describe('Transaction Utils', () => {
|
||||
beforeEach(() => {
|
||||
resetTest();
|
||||
});
|
||||
|
||||
describe('#getAddressToContract', () => {
|
||||
it('should not update contracts list when no contracts', () => {
|
||||
contractsList = [];
|
||||
const result = transactionUtils.getAddressToContract(contractsList, {});
|
||||
|
||||
expect(result).to.deep.equal({});
|
||||
});
|
||||
|
||||
it('should not update contracts list when not deployed', () => {
|
||||
contractsList[0].deployedAddress = undefined;
|
||||
const result = transactionUtils.getAddressToContract(contractsList, {});
|
||||
|
||||
expect(result).to.deep.equal({});
|
||||
});
|
||||
|
||||
it('should update contracts list', () => {
|
||||
const result = transactionUtils.getAddressToContract(contractsList, {});
|
||||
|
||||
expect(result).to.deep.equal({
|
||||
"0x12345": {
|
||||
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
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getTransactionParams', () => {
|
||||
it('should return the param string and function name', () => {
|
||||
const result = transactionUtils.getAddressToContract(contractsList, {});
|
||||
const {functionName, paramString} = transactionUtils.getTransactionParams(result['0x12345'], '0x60fe47b100000000000000000000000099db99c77ad807f89829f5bda99527438f64a798');
|
||||
expect(functionName).to.equal('set');
|
||||
expect(paramString).to.equal('878372847193751743539905734564138820017777321880');
|
||||
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue