From 0b75f99d752f7960ce4377d54b098be914600200 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Fri, 19 Oct 2018 17:37:34 +0200 Subject: [PATCH] feat: make libp2p a state machine (#257) * docs: add events to readme --- README.md | 49 ++- examples/discovery-mechanisms/1.js | 1 + examples/discovery-mechanisms/2.js | 1 + examples/libp2p-in-the-browser/1/src/index.js | 1 + examples/peer-and-content-routing/1.js | 1 + examples/peer-and-content-routing/2.js | 1 + examples/protocol-and-stream-muxing/3.js | 1 + examples/pubsub/1.js | 1 + examples/transports/1.js | 1 + examples/transports/2.js | 1 + examples/transports/3.js | 1 + package.json | 7 +- src/index.js | 295 ++++++++++++------ test/fsm.spec.js | 118 +++++++ test/node.js | 3 +- test/ping.node.js | 61 ++++ test/transports.browser.js | 37 +++ test/transports.node.js | 47 ++- test/turbolence.node.js | 1 + 19 files changed, 519 insertions(+), 109 deletions(-) create mode 100644 test/fsm.spec.js create mode 100644 test/ping.node.js diff --git a/README.md b/README.md index 3ba5808828..5c642d5c17 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ We've come a long way, but this project is still in Alpha, lots of development i - [Install](#install) - [Usage](#usage) - [API](#api) + - [Events](#events) - [Development](#development) - [Tests](#tests) - [Packages](#packages) @@ -206,22 +207,20 @@ Required keys in the `options` object: > Start the libp2p Node. -`callback` is a function with the following `function (err) {}` signature, where `err` is an Error in case starting the node fails. +`callback` following signature `function (err) {}`, where `err` is an Error in case starting the node fails. #### `libp2p.stop(callback)` > Stop the libp2p Node. -`callback` is a function with the following `function (err) {}` signature, where `err` is an Error in case stopping the node fails. +`callback` following signature `function (err) {}`, where `err` is an Error in case stopping the node fails. #### `libp2p.dial(peer, callback)` > Dials to another peer in the network, establishes the connection. - `peer`: can be an instance of [PeerInfo][], [PeerId][], [multiaddr][], or a multiaddr string -- `callback`: Function with signature `function (err, conn) {}` where `conn` is a [Connection](https://github.com/libp2p/interface-connection) object - -`callback` is a function with the following `function (err, conn) {}` signature, where `err` is an Error in of failure to dial the connection and `conn` is a [Connection][] instance in case of a protocol selected, if not it is undefined. +- `callback` following signature `function (err, conn) {}`, where `err` is an Error in of failure to dial the connection and `conn` is a [Connection][] instance in case of a protocol selected, if not it is undefined. #### `libp2p.dialProtocol(peer, protocol, callback)` @@ -229,9 +228,17 @@ Required keys in the `options` object: - `peer`: can be an instance of [PeerInfo][], [PeerId][], [multiaddr][], or a multiaddr string - `protocol`: String that defines the protocol (e.g '/ipfs/bitswap/1.1.0') -- `callback`: Function with signature `function (err, conn) {}` where `conn` is a [Connection](https://github.com/libp2p/interface-connection) object +- `callback`: Function with signature `function (err, conn) {}`, where `conn` is a [Connection](https://github.com/libp2p/interface-connection) object + +`callback` following signature `function (err, conn) {}`, where `err` is an Error in of failure to dial the connection and `conn` is a [Connection][] instance in case of a protocol selected, if not it is undefined. -`callback` is a function with the following `function (err, conn) {}` signature, where `err` is an Error in of failure to dial the connection and `conn` is a [Connection][] instance in case of a protocol selected, if not it is undefined. +#### `libp2p.dialFSM(peer, protocol, callback)` + +> Behaves like `.dial` and `.dialProtocol` but calls back with a Connection State Machine + +- `peer`: can be an instance of [PeerInfo][], [PeerId][], [multiaddr][], or a multiaddr string +- `protocol`: an optional String that defines the protocol (e.g '/ipfs/bitswap/1.1.0') +- `callback`: following signature `function (err, connFSM) {}`, where `connFSM` is a [Connection State Machine](https://github.com/libp2p/js-libp2p-switch#connection-state-machine) #### `libp2p.hangUp(peer, callback)` @@ -239,7 +246,7 @@ Required keys in the `options` object: - `peer`: can be an instance of [PeerInfo][], [PeerId][] or [multiaddr][] -`callback` is a function with the following `function (err) {}` signature, where `err` is an Error in case stopping the node fails. +`callback` following signature `function (err) {}`, where `err` is an Error in case stopping the node fails. #### `libp2p.peerRouting.findPeer(id, options, callback)` @@ -265,7 +272,7 @@ Required keys in the `options` object: > Handle new protocol - `protocol`: String that defines the protocol (e.g '/ipfs/bitswap/1.1.0') -- `handlerFunc`: Function with signature `function (protocol, conn) {}` where `conn` is a [Connection](https://github.com/libp2p/interface-connection) object +- `handlerFunc`: following signature `function (protocol, conn) {}`, where `conn` is a [Connection](https://github.com/libp2p/interface-connection) object - `matchFunc`: Function for matching on protocol (exact matching, semver, etc). Default to exact match. #### `libp2p.unhandle(protocol)` @@ -274,19 +281,35 @@ Required keys in the `options` object: - `protocol`: String that defines the protocol (e.g '/ipfs/bitswap/1.1.0') -#### `libp2p.on('peer:discovery', (peer) => {})` +#### Events + +##### `libp2p.on('start', () => {})` + +> Libp2p has started, along with all its services. + +##### `libp2p.on('stop', () => {})` + +> Libp2p has stopped, along with all its services. + +##### `libp2p.on('error', (err) => {})` + +> An error has occurred + +- `err`: instance of `Error` + +##### `libp2p.on('peer:discovery', (peer) => {})` > Peer has been discovered. - `peer`: instance of [PeerInfo][] -#### `libp2p.on('peer:connect', (peer) => {})` +##### `libp2p.on('peer:connect', (peer) => {})` > We connected to a new peer - `peer`: instance of [PeerInfo][] -#### `libp2p.on('peer:disconnect', (peer) => {})` +##### `libp2p.on('peer:disconnect', (peer) => {})` > We disconnected from Peer @@ -574,4 +597,4 @@ The libp2p implementation in JavaScript is a work in progress. As such, there ar ## License -[MIT](LICENSE) © David Dias +[MIT](LICENSE) © Protocol Labs diff --git a/examples/discovery-mechanisms/1.js b/examples/discovery-mechanisms/1.js index 444973db2c..13d389a0d0 100644 --- a/examples/discovery-mechanisms/1.js +++ b/examples/discovery-mechanisms/1.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict' const libp2p = require('../../') diff --git a/examples/discovery-mechanisms/2.js b/examples/discovery-mechanisms/2.js index 10bcf168e2..1711b3f0bd 100644 --- a/examples/discovery-mechanisms/2.js +++ b/examples/discovery-mechanisms/2.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict' const libp2p = require('../../') diff --git a/examples/libp2p-in-the-browser/1/src/index.js b/examples/libp2p-in-the-browser/1/src/index.js index 7100454b8a..95a98b544f 100644 --- a/examples/libp2p-in-the-browser/1/src/index.js +++ b/examples/libp2p-in-the-browser/1/src/index.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict' const domReady = require('detect-dom-ready') diff --git a/examples/peer-and-content-routing/1.js b/examples/peer-and-content-routing/1.js index bf121d7dea..d9a09df241 100644 --- a/examples/peer-and-content-routing/1.js +++ b/examples/peer-and-content-routing/1.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict' const libp2p = require('../../') diff --git a/examples/peer-and-content-routing/2.js b/examples/peer-and-content-routing/2.js index acd508d48e..f3f77d49bf 100644 --- a/examples/peer-and-content-routing/2.js +++ b/examples/peer-and-content-routing/2.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict' const libp2p = require('../../') diff --git a/examples/protocol-and-stream-muxing/3.js b/examples/protocol-and-stream-muxing/3.js index 730bf01ecc..453c035ade 100644 --- a/examples/protocol-and-stream-muxing/3.js +++ b/examples/protocol-and-stream-muxing/3.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict' const libp2p = require('../../') diff --git a/examples/pubsub/1.js b/examples/pubsub/1.js index 7331de180f..8adfd35dd1 100644 --- a/examples/pubsub/1.js +++ b/examples/pubsub/1.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict' const libp2p = require('../../') diff --git a/examples/transports/1.js b/examples/transports/1.js index 58b279cb01..8f334a1691 100644 --- a/examples/transports/1.js +++ b/examples/transports/1.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict' const libp2p = require('../../') diff --git a/examples/transports/2.js b/examples/transports/2.js index 508c01d628..64465c2206 100644 --- a/examples/transports/2.js +++ b/examples/transports/2.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict' const libp2p = require('../../') diff --git a/examples/transports/3.js b/examples/transports/3.js index 9bce138108..c117d648d2 100644 --- a/examples/transports/3.js +++ b/examples/transports/3.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ 'use strict' const libp2p = require('../../') diff --git a/package.json b/package.json index 4f8ee1f168..c8d71050dc 100644 --- a/package.json +++ b/package.json @@ -38,13 +38,15 @@ }, "dependencies": { "async": "^2.6.1", + "debug": "^4.0.1", "err-code": "^1.1.2", + "fsm-event": "^2.1.0", "joi": "^13.6.0", "joi-browser": "^13.4.0", "libp2p-connection-manager": "~0.0.2", "libp2p-floodsub": "~0.15.0", "libp2p-ping": "~0.8.0", - "libp2p-switch": "~0.40.8", + "libp2p-switch": "~0.41.0", "libp2p-websockets": "~0.12.0", "mafmt": "^6.0.2", "multiaddr": "^5.0.0", @@ -57,16 +59,17 @@ "@nodeutils/defaults-deep": "^1.1.0", "aegir": "^15.2.0", "chai": "^4.1.2", + "chai-checkmark": "^1.0.1", "cids": "~0.5.3", "dirty-chai": "^2.0.1", "electron-webrtc": "~0.3.0", + "libp2p-bootstrap": "~0.9.3", "libp2p-circuit": "~0.2.1", "libp2p-delegated-content-routing": "~0.2.2", "libp2p-delegated-peer-routing": "~0.2.2", "libp2p-kad-dht": "~0.10.5", "libp2p-mdns": "~0.12.0", "libp2p-mplex": "~0.8.2", - "libp2p-bootstrap": "~0.9.3", "libp2p-secio": "~0.10.0", "libp2p-spdy": "~0.12.1", "libp2p-tcp": "~0.13.0", diff --git a/src/index.js b/src/index.js index 424018fa76..9d54b85395 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,11 @@ 'use strict' +const FSM = require('fsm-event') const EventEmitter = require('events').EventEmitter const assert = require('assert') +const debug = require('debug') +const log = debug('libp2p') +log.error = debug('libp2p:error') const each = require('async/each') const series = require('async/series') @@ -22,6 +26,14 @@ const validateConfig = require('./config').validate const NOT_STARTED_ERROR_MESSAGE = 'The libp2p node is not started yet' +/** + * @fires Node#error Emitted when an error occurs + * @fires Node#peer:connect Emitted when a peer is connected to this node + * @fires Node#peer:disconnect Emitted when a peer disconnects from this node + * @fires Node#peer:discovery Emitted when a peer is discovered + * @fires Node#start Emitted when the node and its services has started + * @fires Node#stop Emitted when the node and its services has stopped + */ class Node extends EventEmitter { constructor (_options) { super() @@ -109,15 +121,181 @@ class Node extends EventEmitter { // Mount default protocols Ping.mount(this._switch) + + this.state = new FSM('STOPPED', { + STOPPED: { + start: 'STARTING', + stop: 'STOPPED' + }, + STARTING: { + done: 'STARTED', + abort: 'STOPPED', + stop: 'STOPPING' + }, + STARTED: { + stop: 'STOPPING', + start: 'STARTED' + }, + STOPPING: { + stop: 'STOPPING', + done: 'STOPPED' + } + }) + this.state.on('STARTING', () => { + log('libp2p is starting') + this._onStarting() + }) + this.state.on('STOPPING', () => { + log('libp2p is stopping') + this._onStopping() + }) + this.state.on('STARTED', () => { + log('libp2p has started') + this.emit('start') + }) + this.state.on('STOPPED', () => { + log('libp2p has stopped') + this.emit('stop') + }) + this.state.on('error', (err) => { + log.error(err) + this.emit('error', err) + }) + } + + /** + * Starts the libp2p node and all sub services + * + * @param {function(Error)} callback + * @returns {void} + */ + start (callback = () => {}) { + this.once('start', callback) + this.state('start') + } + + /** + * Stop the libp2p node by closing its listeners and open connections + * + * @param {function(Error)} callback + * @returns {void} + */ + stop (callback = () => {}) { + this.once('stop', callback) + this.state('stop') + } + + isStarted () { + return this.state ? this.state._state === 'STARTED' : false + } + + /** + * Dials to the provided peer. If successful, the `PeerInfo` of the + * peer will be added to the nodes `PeerBook` + * + * @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to dial + * @param {function(Error)} callback + * @returns {void} + */ + dial (peer, callback) { + assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE) + + this.dialProtocol(peer, null, callback) + } + + /** + * Dials to the provided peer and handshakes with the given protocol. + * If successful, the `PeerInfo` of the peer will be added to the nodes `PeerBook`, + * and the `Connection` will be sent in the callback + * + * @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to dial + * @param {string} protocol + * @param {function(Error, Connection)} callback + * @returns {void} + */ + dialProtocol (peer, protocol, callback) { + assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE) + + if (typeof protocol === 'function') { + callback = protocol + protocol = undefined + } + + this._getPeerInfo(peer, (err, peerInfo) => { + if (err) { return callback(err) } + + this._switch.dial(peerInfo, protocol, (err, conn) => { + if (err) { return callback(err) } + this.peerBook.put(peerInfo) + callback(null, conn) + }) + }) } - /* - * Start the libp2p node - * - create listeners on the multiaddrs the Peer wants to listen + /** + * Similar to `dial` and `dialProtocol`, but the callback will contain a + * Connection State Machine. + * + * @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to dial + * @param {string} protocol + * @param {function(Error, ConnectionFSM)} callback + * @returns {void} */ - start (callback) { + dialFSM (peer, protocol, callback) { + assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE) + + if (typeof protocol === 'function') { + callback = protocol + protocol = undefined + } + + this._getPeerInfo(peer, (err, peerInfo) => { + if (err) { return callback(err) } + + const connFSM = this._switch.dialFSM(peerInfo, protocol, (err) => { + if (!err) { + this.peerBook.put(peerInfo) + } + }) + + callback(null, connFSM) + }) + } + + hangUp (peer, callback) { + assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE) + + this._getPeerInfo(peer, (err, peerInfo) => { + if (err) { return callback(err) } + + this._switch.hangUp(peerInfo, callback) + }) + } + + ping (peer, callback) { + if (!this.isStarted()) { + return callback(new Error(NOT_STARTED_ERROR_MESSAGE)) + } + + this._getPeerInfo(peer, (err, peerInfo) => { + if (err) { return callback(err) } + + callback(null, new Ping(this._switch, peerInfo)) + }) + } + + handle (protocol, handlerFunc, matchFunc) { + this._switch.handle(protocol, handlerFunc, matchFunc) + } + + unhandle (protocol) { + this._switch.unhandle(protocol) + } + + _onStarting () { if (!this._modules.transport) { - return callback(new Error('no transports were present')) + this.emit('error', new Error('no transports were present')) + return this.state('abort') } let ws @@ -214,12 +392,10 @@ class Node extends EventEmitter { // TODO: chicken-and-egg problem #2: // have to set started here because FloodSub requires libp2p is already started if (this._floodSub) { - this._floodSub.start(cb) - } else { - cb() + return this._floodSub.start(cb) } + cb() }, - (cb) => { // detect which multiaddrs we don't have a transport for and remove them const multiaddrs = this.peerInfo.multiaddrs.toArray() @@ -231,18 +407,18 @@ class Node extends EventEmitter { } }) cb() - }, - (cb) => { - this.emit('start') - cb() } - ], callback) + ], (err) => { + if (err) { + log.error(err) + this.emit('error', err) + return this.state('stop') + } + this.state('done') + }) } - /* - * Stop the libp2p node by closing its listeners and open connections - */ - stop (callback) { + _onStopping () { series([ (cb) => { if (this._modules.peerDiscovery) { @@ -269,86 +445,21 @@ class Node extends EventEmitter { cb() }, (cb) => { - this.connectionManager.stop() - this._switch.stop(cb) + // Ensures idempotency for restarts + this._switch.transport.removeAll(cb) }, (cb) => { - this.emit('stop') - cb() + this.connectionManager.stop() + this._switch.stop(cb) } ], (err) => { - this._isStarted = false - callback(err) - }) - } - - isStarted () { - return this._isStarted - } - - dial (peer, callback) { - assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE) - - this._getPeerInfo(peer, (err, peerInfo) => { - if (err) { return callback(err) } - - this._switch.dial(peerInfo, (err) => { - if (err) { return callback(err) } - - this.peerBook.put(peerInfo) - callback() - }) - }) - } - - dialProtocol (peer, protocol, callback) { - assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE) - - if (typeof protocol === 'function') { - callback = protocol - protocol = undefined - } - - this._getPeerInfo(peer, (err, peerInfo) => { - if (err) { return callback(err) } - - this._switch.dial(peerInfo, protocol, (err, conn) => { - if (err) { return callback(err) } - this.peerBook.put(peerInfo) - callback(null, conn) - }) - }) - } - - hangUp (peer, callback) { - assert(this.isStarted(), NOT_STARTED_ERROR_MESSAGE) - - this._getPeerInfo(peer, (err, peerInfo) => { - if (err) { return callback(err) } - - this._switch.hangUp(peerInfo, callback) - }) - } - - ping (peer, callback) { - if (!this.isStarted()) { - return callback(new Error(NOT_STARTED_ERROR_MESSAGE)) - } - - this._getPeerInfo(peer, (err, peerInfo) => { - if (err) { return callback(err) } - - callback(null, new Ping(this._switch, peerInfo)) + if (err) { + log.error(err) + this.emit('error', err) + } + this.state('done') }) } - - handle (protocol, handlerFunc, matchFunc) { - this._switch.handle(protocol, handlerFunc, matchFunc) - } - - unhandle (protocol) { - this._switch.unhandle(protocol) - } } module.exports = Node diff --git a/test/fsm.spec.js b/test/fsm.spec.js new file mode 100644 index 0000000000..171fe62865 --- /dev/null +++ b/test/fsm.spec.js @@ -0,0 +1,118 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +chai.use(require('dirty-chai')) +chai.use(require('chai-checkmark')) +const expect = chai.expect +const sinon = require('sinon') +const series = require('async/series') +const createNode = require('./utils/create-node') + +describe('libp2p state machine (fsm)', () => { + describe('starting and stopping', () => { + let node + beforeEach((done) => { + createNode([], (err, _node) => { + node = _node + done(err) + }) + }) + afterEach(() => { + node.removeAllListeners() + }) + after((done) => { + node.stop(done) + node = null + }) + + it('should be able to start and stop several times', (done) => { + node.on('start', (err) => { + expect(err).to.not.exist().mark() + }) + node.on('stop', (err) => { + expect(err).to.not.exist().mark() + }) + + expect(4).checks(done) + + series([ + (cb) => node.start(cb), + (cb) => node.stop(cb), + (cb) => node.start(cb), + (cb) => node.stop(cb) + ], () => {}) + }) + + it('should noop when stopping a stopped node', (done) => { + node.once('start', node.stop) + node.once('stop', () => { + node.state.on('STOPPING', () => { + throw new Error('should not stop a stopped node') + }) + node.once('stop', done) + + // stop the stopped node + node.stop() + }) + node.start() + }) + + it('should noop when starting a started node', (done) => { + node.once('start', () => { + node.state.on('STARTING', () => { + throw new Error('should not start a started node') + }) + node.once('start', () => { + node.once('stop', done) + node.stop() + }) + + // start the started node + node.start() + }) + node.start() + }) + + it('should error on start with no transports', (done) => { + let transports = node._modules.transport + node._modules.transport = null + + node.on('stop', () => { + node._modules.transport = transports + expect(node._modules.transport).to.exist().mark() + }) + node.on('error', (err) => { + expect(err).to.exist().mark() + }) + node.on('start', () => { + throw new Error('should not start') + }) + + expect(2).checks(done) + + node.start() + }) + + it('should not start if the switch fails to start', (done) => { + const error = new Error('switch didnt start') + const stub = sinon.stub(node._switch, 'start') + .callsArgWith(0, error) + + node.on('stop', () => { + expect(stub.calledOnce).to.eql(true).mark() + stub.restore() + }) + node.on('error', (err) => { + expect(err).to.eql(error).mark() + }) + node.on('start', () => { + throw new Error('should not start') + }) + + expect(2).checks(done) + + node.start() + }) + }) +}) diff --git a/test/node.js b/test/node.js index ccfa009962..9e5bd89bd1 100644 --- a/test/node.js +++ b/test/node.js @@ -4,8 +4,9 @@ require('./pnet.node') require('./transports.node') require('./stream-muxing.node') require('./peer-discovery.node') -require('./pubsub.node') require('./peer-routing.node') +require('./ping.node') +require('./pubsub.node') require('./content-routing.node') require('./circuit-relay.node') require('./multiaddr-trim.node') diff --git a/test/ping.node.js b/test/ping.node.js new file mode 100644 index 0000000000..b677ab6a43 --- /dev/null +++ b/test/ping.node.js @@ -0,0 +1,61 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +chai.use(require('dirty-chai')) +const expect = chai.expect +const parallel = require('async/parallel') + +const createNode = require('./utils/create-node.js') +const echo = require('./utils/echo') + +describe('ping', () => { + let nodeA + let nodeB + + before((done) => { + parallel([ + (cb) => createNode('/ip4/0.0.0.0/tcp/0', (err, node) => { + expect(err).to.not.exist() + nodeA = node + node.handle('/echo/1.0.0', echo) + node.start(cb) + }), + (cb) => createNode('/ip4/0.0.0.0/tcp/0', (err, node) => { + expect(err).to.not.exist() + nodeB = node + node.handle('/echo/1.0.0', echo) + node.start(cb) + }) + ], done) + }) + + after((done) => { + parallel([ + (cb) => nodeA.stop(cb), + (cb) => nodeB.stop(cb) + ], done) + }) + + it('should be able to ping another node', (done) => { + nodeA.ping(nodeB.peerInfo, (err, ping) => { + expect(err).to.not.exist() + ping.once('ping', (time) => { + expect(time).to.exist() + ping.stop() + done() + }) + + ping.start() + }) + }) + + it('should be not be able to ping when stopped', (done) => { + nodeA.stop(() => { + nodeA.ping(nodeB.peerInfo, (err) => { + expect(err).to.exist() + done() + }) + }) + }) +}) diff --git a/test/transports.browser.js b/test/transports.browser.js index fcf749b653..92daa9e642 100644 --- a/test/transports.browser.js +++ b/test/transports.browser.js @@ -148,6 +148,42 @@ describe('transports', () => { }) }) + it('.dialFSM check conn and close', (done) => { + nodeA.dialFSM(peerB, (err, connFSM) => { + expect(err).to.not.exist() + + connFSM.once('muxed', () => { + expect(nodeA._switch.muxedConns).to.have.any.keys( + peerB.id.toB58String() + ) + + connFSM.once('error', done) + connFSM.once('close', () => { + // ensure the connection is closed + expect(nodeA._switch.muxedConns).to.not.have.any.keys([ + peerB.id.toB58String() + ]) + done() + }) + + connFSM.close() + }) + }) + }) + + it('.dialFSM with a protocol, do an echo and close', (done) => { + nodeA.dialFSM(peerB, '/echo/1.0.0', (err, connFSM) => { + expect(err).to.not.exist() + connFSM.once('connection', (conn) => { + tryEcho(conn, () => { + connFSM.close() + }) + }) + connFSM.once('error', done) + connFSM.once('close', done) + }) + }) + describe('stress', () => { it('one big write', (done) => { nodeA.dialProtocol(peerB, '/echo/1.0.0', (err, conn) => { @@ -194,6 +230,7 @@ describe('transports', () => { }) describe('webrtc-star', () => { + /* eslint-disable-next-line no-console */ if (!w.support) { return console.log('NO WEBRTC SUPPORT') } let peer1 diff --git a/test/transports.node.js b/test/transports.node.js index ac71c979df..4b5b5c121e 100644 --- a/test/transports.node.js +++ b/test/transports.node.js @@ -185,7 +185,7 @@ describe('transports', () => { }) it('nodeA.hangUp nodeB using PeerId (third)', (done) => { - nodeA.hangUp(nodeB.peerInfo.multiaddrs.toArray()[0], (err) => { + nodeA.hangUp(nodeB.peerInfo.id, (err) => { expect(err).to.not.exist() setTimeout(check, 500) @@ -207,6 +207,51 @@ describe('transports', () => { } }) }) + + it('.dialFSM check conn and close', (done) => { + nodeA.dialFSM(nodeB.peerInfo, (err, connFSM) => { + expect(err).to.not.exist() + + connFSM.once('muxed', () => { + expect(nodeA._switch.muxedConns).to.have.any.keys( + nodeB.peerInfo.id.toB58String() + ) + + connFSM.once('error', done) + connFSM.once('close', () => { + // ensure the connection is closed + expect(nodeA._switch.muxedConns).to.not.have.any.keys([ + nodeB.peerInfo.id.toB58String() + ]) + done() + }) + + connFSM.close() + }) + }) + }) + + it('.dialFSM with a protocol, do an echo and close', (done) => { + nodeA.dialFSM(nodeB.peerInfo, '/echo/1.0.0', (err, connFSM) => { + expect(err).to.not.exist() + connFSM.once('connection', (conn) => { + expect(nodeA._switch.muxedConns).to.have.all.keys([ + nodeB.peerInfo.id.toB58String() + ]) + tryEcho(conn, () => { + connFSM.close() + }) + }) + connFSM.once('error', done) + connFSM.once('close', () => { + // ensure the connection is closed + expect(nodeA._switch.muxedConns).to.not.have.any.keys([ + nodeB.peerInfo.id.toB58String() + ]) + done() + }) + }) + }) }) describe('TCP + WebSockets', () => { diff --git a/test/turbolence.node.js b/test/turbolence.node.js index 378033400c..1bc96c9dc4 100644 --- a/test/turbolence.node.js +++ b/test/turbolence.node.js @@ -42,6 +42,7 @@ describe('Turbolence tests', () => { } }) + /* eslint-disable-next-line no-console */ nodeSpawn.stderr.on('data', (data) => console.log(data.toString())) })