Skip to content
This repository has been archived by the owner on Dec 10, 2020. It is now read-only.

Commit

Permalink
Unit tests for all components
Browse files Browse the repository at this point in the history
  • Loading branch information
vpulim committed Nov 17, 2018
1 parent 6b4446c commit 261d3bb
Show file tree
Hide file tree
Showing 18 changed files with 754 additions and 300 deletions.
2 changes: 0 additions & 2 deletions lib/handler/ethhandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ class EthHandler extends Handler {
const { block, max, skip, reverse } = message.data
const headers = await this.chain.getHeaders(block, max, skip, reverse)
peer.eth.send('BlockHeaders', headers)
} else if (message.name === 'GetBlockBodies') {
// TO DO
}
} catch (error) {
this.emit('error', error)
Expand Down
150 changes: 150 additions & 0 deletions lib/net/protocol/boundprotocol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
'use strict'

const EventEmitter = require('events')

/**
* Binds a protocol implementation to the specified peer
* @memberof module:net/protocol
*/
class BoundProtocol extends EventEmitter {
/**
* Create bound protocol
* @param {Object} options constructor parameters
* @param {Protocol} options.protocol protocol to bind
* @param {Peer} options.peer peer that protocol is bound to
* @param {Sender} options.sender message sender
*/
constructor (options) {
super()

this.protocol = options.protocol
this.peer = options.peer
this.sender = options.sender
this.name = this.protocol.name
this.versions = this.protocol.versions
this.timeout = this.protocol.timeout
this.logger = this.protocol.logger
this._status = {}
this.resolvers = new Map()
this.sender.on('message', message => {
try {
this.handle(message)
} catch (error) {
this.emit('error', error)
}
})
this.sender.on('error', error => this.emit('error', error))
this.addMethods()
}

get status () {
return this._status
}

set status (status) {
Object.assign(this._status, status)
}

async handshake (sender) {
this._status = await this.protocol.handshake(sender)
}

/**
* Handle incoming message
* @private
* @param {Object} message message object
* @emits message
*/
handle (incoming) {
const messages = this.protocol.messages
const message = messages.find(m => m.code === incoming.code)
if (!message) {
return
}

let data
let error
try {
data = this.protocol.decode(message, incoming.payload)
} catch (e) {
error = new Error(`Could not decode message ${message.name}: ${e}`)
}
const resolver = this.resolvers.get(incoming.code)
if (resolver) {
clearTimeout(resolver.timeout)
this.resolvers.delete(incoming.code)
if (error) {
resolver.reject(error)
} else {
resolver.resolve(data)
}
} else {
if (error) {
this.emit('error', error)
} else {
this.emit('message', { name: message.name, data: data })
}
}
}

/**
* Send message with name and the specified args
* @param {string} name message name
* @param {object} args message arguments
*/
send (name, args) {
const messages = this.protocol.messages
const message = messages.find(m => m.name === name)
if (message) {
const encoded = this.protocol.encode(message, args)
this.sender.sendMessage(message.code, encoded)
return message
} else {
throw new Error(`Unknown message: ${name}`)
}
}

/**
* Returns a promise that resolves with the message payload when a response
* to the specified message is received
* @param {string} name message to wait for
* @param {object} args message arguments
* @return {Promise}
*/
async request (name, args) {
const message = this.send(name, args)
const resolver = {
timeout: null,
resolve: null,
reject: null
}
if (this.resolvers.get(message.response)) {
throw new Error(`Only one active request allowed per message type (${name})`)
}
this.resolvers.set(message.response, resolver)
return new Promise((resolve, reject) => {
resolver.timeout = setTimeout(() => {
resolver.timeout = null
this.resolvers.delete(message.response)
reject(new Error(`Request timed out after ${this.timeout}ms`))
}, this.timeout)
resolver.resolve = resolve
resolver.reject = reject
})
}

/**
* Add a methods to the bound protocol for each protocol message that has a
* corresponding response message
*/
addMethods () {
const messages = this.protocol.messages.filter(m => m.response)
for (let message of messages) {
const name = message.name
const camel = name[0].toLowerCase() + name.slice(1)
this[name] = this[camel] = async (args) => this.request(name, args)
}
}
}

module.exports = BoundProtocol
146 changes: 1 addition & 145 deletions lib/net/protocol/protocol.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const BoundProtocol = require('./boundprotocol')
const EventEmitter = require('events')
const { defaultLogger } = require('../../logging')

Expand All @@ -19,151 +20,6 @@ const defaultOptions = {
* @property {function(*): *} decode decodes message payload
*/

/**
* Binds a protocol implementation to the specified peer
* @extends EventEmitter
*/
class BoundProtocol extends EventEmitter {
/**
* Create bound protocol
* @param {Object} options constructor parameters
* @param {Protocol} options.protocol protocol to bind
* @param {Peer} options.peer peer that protocol is bound to
* @param {Sender} options.sender message sender
*/
constructor (options) {
super()

this.protocol = options.protocol
this.peer = options.peer
this.sender = options.sender
this.name = this.protocol.name
this.versions = this.protocol.versions
this.timeout = this.protocol.timeout
this.logger = this.protocol.logger
this._status = null
this.resolvers = new Map()
this.sender.on('message', message => {
try {
this.handle(message)
} catch (error) {
this.emit('error', error)
}
})
this.sender.on('error', error => this.emit('error', error))
this.addMethods()
}

get status () {
return this._status
}

set status (status) {
Object.assign(this._status, status)
}

async handshake (sender) {
this._status = await this.protocol.handshake(sender)
}

/**
* Handle incoming message
* @private
* @param {Object} message message object
* @emits message
*/
handle (incoming) {
const messages = this.protocol.messages
const message = messages.find(m => m.code === incoming.code)
if (!message) {
return
}

let data
let error
try {
data = this.protocol.decode(message, incoming.payload)
} catch (e) {
error = new Error(`Could not decode message ${message.name}: ${e}`)
}
const resolver = this.resolvers.get(incoming.code)
if (resolver) {
clearTimeout(resolver.timeout)
this.resolvers.delete(incoming.code)
if (error) {
resolver.reject(error)
} else {
resolver.resolve(data)
}
} else {
if (error) {
this.emit('error', error)
} else {
this.emit('message', { name: message.name, data: data })
}
}
}

/**
* Send message with name and the specified args
* @param {string} name message name
* @param {object} args message arguments
*/
send (name, args) {
const messages = this.protocol.messages
const message = messages.find(m => m.name === name)
if (message) {
const encoded = this.protocol.encode(message, args)
this.sender.sendMessage(message.code, encoded)
return message
} else {
throw new Error(`Unknown message: ${name}`)
}
}

/**
* Returns a promise that resolves with the message payload when a response
* to the specified message is received
* @param {string} name message to wait for
* @param {object} args message arguments
* @return {Promise}
*/
async request (name, args) {
const message = this.send(name, args)
const resolver = {
timeout: null,
resolve: null,
reject: null
}
if (this.resolvers.get(message.response)) {
throw new Error(`Only one active request allowed per message type (${name})`)
}
this.resolvers.set(message.response, resolver)
return new Promise((resolve, reject) => {
resolver.timeout = setTimeout(() => {
resolver.timeout = null
this.resolvers.delete(message.response)
reject(new Error(`Request timed out after ${this.timeout}ms`))
}, this.timeout)
resolver.resolve = resolve
resolver.reject = reject
})
}

/**
* Add a methods to the bound protocol for each protocol message that has a
* corresponding response message
*/
addMethods () {
const messages = this.protocol.messages.filter(m => m.response)
for (let message of messages) {
const name = message.name
const camel = name[0].toLowerCase() + name.slice(1)
this[name] = this[camel] = async (args) => this.request(name, args)
}
}
}

/**
* Base class for all wire protocols
* @memberof module:net/protocol
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,12 @@
"nyc": "~11.6.0",
"pino": "^5.8.0",
"pino-pretty": "^2.2.2",
"sinon": "^6.0.0",
"standard": "~11.0.1",
"supertest": "^3.1.0",
"tape": "~4.9.0",
"tape-catch": "~1.0.6",
"testdouble": "^3.8.2",
"testdouble-timers": "^0.1.1",
"tmp": "~0.0.33"
}
}
30 changes: 15 additions & 15 deletions test/blockpool.js → test/blockchain/blockpool.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const tape = require('tape')
const Block = require('ethereumjs-block')
const util = require('ethereumjs-util')
const { Chain, BlockPool } = require('../lib/blockchain')
const { defaultLogger } = require('../lib/logging')
const { Chain, BlockPool } = require('../../lib/blockchain')
const { defaultLogger } = require('../../lib/logging')
defaultLogger.silent = true

tape('[BlockPool]: functions', t => {
t.test('should add block segment to chain', async (st) => {
tape('[BlockPool]', t => {
t.test('should add block segment to chain', async (t) => {
const chain = new Chain() // eslint-disable-line no-new
const pool = new BlockPool({ chain })
await pool.open()
Expand All @@ -24,13 +24,13 @@ tape('[BlockPool]: functions', t => {
// add blocks out of order to make sure they are inserted in order
await pool.add(block2)
await pool.add(block1)
st.equal(chain.blocks.td.toString(16), '433333333', 'get chain.blocks.td')
st.equal(chain.blocks.height.toString(10), '2', 'get chain.blocks.height')
t.equal(chain.blocks.td.toString(16), '433333333', 'get chain.blocks.td')
t.equal(chain.blocks.height.toString(10), '2', 'get chain.blocks.height')
chain.close()
st.end()
t.end()
})

t.test('should get pool size', async (st) => {
t.test('should get pool size', async (t) => {
const chain = new Chain() // eslint-disable-line no-new
const pool = new BlockPool({ chain })
await pool.open()
Expand All @@ -46,19 +46,19 @@ tape('[BlockPool]: functions', t => {
block2.header.parentHash = block1.hash()

await pool.add(block2)
st.equal(pool.size, 1, 'pool contains out of order block')
t.equal(pool.size, 1, 'pool contains out of order block')
await pool.add(block1)
st.equal(pool.size, 0, 'pool should be empty')
t.equal(pool.size, 0, 'pool should be empty')
chain.close()
st.end()
t.end()
})

t.test('should check opened state', async (st) => {
t.test('should check opened state', async (t) => {
const chain = new Chain() // eslint-disable-line no-new
const pool = new BlockPool({ chain })
st.equal(await pool.add([]), false, 'not opened')
t.equal(await pool.add([]), false, 'not opened')
await pool.open()
st.equal(await pool.open(), false, 'already opened')
st.end()
t.equal(await pool.open(), false, 'already opened')
t.end()
})
})
Loading

0 comments on commit 261d3bb

Please sign in to comment.