feat: timeline and close checking (#55)

* feat: add test to validate timeline presence

* feat: add test and docs for close and timeline

* feat: add filter test

* docs: dont reference go, it's not relevant
This commit is contained in:
Jacob Heun 2019-09-19 13:34:25 +02:00 committed by GitHub
parent 02fe6d9040
commit 993ca1cb85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 113 additions and 25 deletions

View File

@ -17,8 +17,6 @@ Publishing a test suite as a module lets multiple modules all ensure compatibili
The purpose of this interface is not to reinvent any wheels when it comes to dialing and listening to transports. Instead, it tries to provide a uniform API for several transports through a shimmed interface.
The API is presented with both Node.js and Go primitives, however there are no actual limitations for it to be extended for any other language, pushing forward the cross compatibility and interop through diferent stacks.
## Lead Maintainer
[Jacob Heun](https://github.com/jacobheun/)
@ -93,6 +91,7 @@ A valid transport (one that follows the interface defined) must implement the fo
- type: `Transport`
- `new Transport({ upgrader, ...[options] })`
- `<Promise> transport.dial(multiaddr, [options])`
- `<Multiaddr[]> transport.filter(multiaddrs)`
- `transport.createListener([options], handlerFunction)`
- type: `transport.Listener`
- event: 'listening'
@ -122,12 +121,17 @@ The `Upgrader` methods take a [MultiaddrConnection](#multiaddrconnection) and wi
- `MultiaddrConnection`
- `sink<function(source)>`: A [streaming iterable sink](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#sink-it)
- `source<AsyncIterator>`: A [streaming iterable source](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#source-it)
- `close<function(Error)>`: A method for closing the connection
- `conn`: The raw connection of the transport, such as a TCP socket.
- `remoteAddr<Multiaddr>`: The remote `Multiaddr` of the connection.
- `[localAddr<Multiaddr>]`: An optional local `Multiaddr` of the connection.
- `timeline<object>`: A hash map of connection time events
- `open<number>`: The time in ticks the connection was opened
- `close<number>`: The time in ticks the connection was closed
### Creating a transport instance
- `JavaScript` - `const transport = new Transport({ upgrader, ...[options] })`
- `const transport = new Transport({ upgrader, ...[options] })`
Creates a new Transport instance. `options` is an JavaScript object that should include the necessary parameters for the transport instance. Options **MUST** include an `Upgrader` instance, as Transports will use this to return `interface-connection` instances from `transport.dial` and the listener `handlerFunction`.
@ -135,7 +139,7 @@ Creates a new Transport instance. `options` is an JavaScript object that should
### Dial to another peer
- `JavaScript` - `const connection = await transport.dial(multiaddr, [options])`
- `const connection = await transport.dial(multiaddr, [options])`
This method uses a transport to dial a Peer listening on `multiaddr`.
@ -172,9 +176,18 @@ try {
// ----
```
### Filtering Addresses
- `const supportedAddrs = await transport.filter(multiaddrs)`
When using a transport its important to be able to filter out `multiaddr`s that the transport doesn't support. A transport instance provides a filter method to return only the valid addresses it supports.
`multiaddrs` must be an array of type [`multiaddr`](https://www.npmjs.com/multiaddr).
Filter returns an array of `multiaddr`.
### Create a listener
- `JavaScript` - `const listener = transport.createListener([options], handlerFunction)`
- `const listener = transport.createListener([options], handlerFunction)`
This method creates a listener on the transport. Implementations **MUST** call `upgrader.upgradeInbound(multiaddrConnection)` and pass its results to the `handlerFunction` and any emitted `connection` events.
@ -191,7 +204,7 @@ The listener object created may emit the following events:
### Start a listener
- `JavaScript` - `await listener.listen(multiaddr)`
- `await listener.listen(multiaddr)`
This method puts the listener in `listening` mode, waiting for incoming connections.
@ -199,13 +212,13 @@ This method puts the listener in `listening` mode, waiting for incoming connecti
### Get listener addrs
- `JavaScript` - `listener.getAddrs()`
- `listener.getAddrs()`
This method returns the addresses on which this listener is listening. Useful when listening on port 0 or any interface (0.0.0.0).
### Stop a listener
- `JavaScript` - `await listener.close([options])`
- `await listener.close([options])`
This method closes the listener so that no more connections can be opened on this transport instance.

View File

@ -6,6 +6,7 @@ const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const { isValidTick } = require('./utils')
const goodbye = require('it-goodbye')
const { collect } = require('streaming-iterables')
const pipe = require('it-pipe')
@ -15,19 +16,18 @@ const sinon = require('sinon')
module.exports = (common) => {
const upgrader = {
upgradeOutbound (multiaddrConnection) {
['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => {
_upgrade (multiaddrConnection) {
['sink', 'source', 'remoteAddr', 'conn', 'timeline', 'close'].forEach(prop => {
expect(multiaddrConnection).to.have.property(prop)
})
return { sink: multiaddrConnection.sink, source: multiaddrConnection.source }
expect(isValidTick(multiaddrConnection.timeline.open)).to.equal(true)
return multiaddrConnection
},
upgradeOutbound (multiaddrConnection) {
return upgrader._upgrade(multiaddrConnection)
},
upgradeInbound (multiaddrConnection) {
['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => {
expect(multiaddrConnection).to.have.property(prop)
})
return { sink: multiaddrConnection.sink, source: multiaddrConnection.source }
return upgrader._upgrade(multiaddrConnection)
}
}
@ -67,6 +67,16 @@ module.exports = (common) => {
expect(result[0].toString()).to.equal('hey')
})
it('can close connections', async () => {
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
const conn = await transport.dial(addrs[0])
expect(upgradeSpy.callCount).to.equal(1)
expect(upgradeSpy.returned(conn)).to.equal(true)
await conn.close()
expect(isValidTick(conn.timeline.close)).to.equal(true)
})
it('to non existent listener', async () => {
const upgradeSpy = sinon.spy(upgrader, 'upgradeOutbound')
try {

37
src/filter-test.js Normal file
View File

@ -0,0 +1,37 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
module.exports = (common) => {
const upgrader = {
_upgrade (multiaddrConnection) {
return multiaddrConnection
},
upgradeOutbound (multiaddrConnection) {
return upgrader._upgrade(multiaddrConnection)
},
upgradeInbound (multiaddrConnection) {
return upgrader._upgrade(multiaddrConnection)
}
}
describe('filter', () => {
let addrs
let transport
before(async () => {
({ addrs, transport } = await common.setup({ upgrader }))
})
after(() => common.teardown && common.teardown())
it('filters addresses', () => {
const filteredAddrs = transport.filter(addrs)
expect(filteredAddrs).to.eql(addrs)
})
})
}

View File

@ -3,11 +3,13 @@
const dial = require('./dial-test')
const listen = require('./listen-test')
const filter = require('./filter-test')
module.exports = (common) => {
describe('interface-transport', () => {
dial(common)
listen(common)
filter(common)
})
}

View File

@ -9,22 +9,23 @@ chai.use(dirtyChai)
const sinon = require('sinon')
const pipe = require('it-pipe')
const { isValidTick } = require('./utils')
module.exports = (common) => {
const upgrader = {
upgradeOutbound (multiaddrConnection) {
['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => {
_upgrade (multiaddrConnection) {
['sink', 'source', 'remoteAddr', 'conn', 'timeline', 'close'].forEach(prop => {
expect(multiaddrConnection).to.have.property(prop)
})
expect(isValidTick(multiaddrConnection.timeline.open)).to.equal(true)
return { sink: multiaddrConnection.sink, source: multiaddrConnection.source }
return multiaddrConnection
},
upgradeOutbound (multiaddrConnection) {
return upgrader._upgrade(multiaddrConnection)
},
upgradeInbound (multiaddrConnection) {
['sink', 'source', 'remoteAddr', 'conn'].forEach(prop => {
expect(multiaddrConnection).to.have.property(prop)
})
return { sink: multiaddrConnection.sink, source: multiaddrConnection.source }
return upgrader._upgrade(multiaddrConnection)
}
}
@ -50,8 +51,10 @@ module.exports = (common) => {
it('close listener with connections, through timeout', async () => {
const upgradeSpy = sinon.spy(upgrader, 'upgradeInbound')
const listenerConns = []
const listener = transport.createListener((conn) => {
listenerConns.push(conn)
expect(upgradeSpy.returned(conn)).to.equal(true)
pipe(conn, conn)
})
@ -78,6 +81,13 @@ module.exports = (common) => {
listener.close()
])
await socket1.close()
expect(isValidTick(socket1.timeline.close)).to.equal(true)
listenerConns.forEach(conn => {
expect(isValidTick(conn.timeline.close)).to.equal(true)
})
// 2 dials = 2 connections upgraded
expect(upgradeSpy.callCount).to.equal(2)
})

16
src/utils/index.js Normal file
View File

@ -0,0 +1,16 @@
'use strict'
module.exports = {
/**
* A tick is considered valid if it happened between now
* and `ms` milliseconds ago
* @param {number} date Time in ticks
* @param {number} ms max milliseconds that should have expired
* @returns {boolean}
*/
isValidTick: function isValidTick (date, ms = 5000) {
const now = Date.now()
if (date > now - ms && date <= now) return true
return false
}
}