diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18ddbab --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +**/node_modules/ +**/*.log +test/repo-tests* +**/bundle.js +docs +# Logs +logs +*.log + +coverage + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +build + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +lib +dist +test/test-data/go-ipfs-repo/LOCK +test/test-data/go-ipfs-repo/LOG +test/test-data/go-ipfs-repo/LOG.old + +# while testing npm5 +package-lock.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..93c315b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +language: node_js +cache: npm +stages: + - check + - test + - cov + +node_js: + - '10' + +os: + - linux + - osx + - windows + +matrix: + fast_finish: true + allow_failures: + - os: windows + +script: npx nyc -s npm run test:node -- --bail +after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov + +jobs: + include: + - stage: check + script: + - npx aegir commitlint --travis + - npx aegir dep-check + - npm run lint + +notifications: + email: false diff --git a/README.md b/README.md index 89db6c4..da59d8a 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,40 @@ -# Interoperability Compliance Test Stories for libp2p +# Interoperability Tests for libp2p - - - - +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai/) +[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p) -> In this repo you will find the [PDD (Protocol Driven Development)](https://github.com/ipfs/pdd) stories that enable you to test a libp2p implementation against other implementations. +> Interoperability tests for libp2p Implementations -## ❗️ Important +This repository will be used for interop tests. -**The setup for these tests is still in flux, please make sure to read the latest update at: https://github.com/libp2p/interop/issues/1** +## Lead Maintainer -## PDD Compliance Test Stories +[Vasco Santos](https://github.com/vasco-santos) -Each Test Story has actors and a dramatization. +## Usage -- **Qualitative** - Test Stories that check feature interoperability and compliance. - - [Transports](./PDD-TRANSPORTS.md) - - [Protocol and Stream Multiplexing](./PDD-PROTOCOL-AND-STREAM-MULTIPLEXING.md) - - [Identify](./PDD-IDENTIFY.md) - - [Encrypted Communications](./PDD-ENCRYPTED-COMMUNICATIONS.md) - - [Peer Routing](./PDD-PEER-ROUTING.md) - - [Content Routing](./PDD-CONTENT-ROUTING.md) - - [PubSub](./PDD-PUBSUB.md) - - [Circuit Relay](./PDD-CIRCUIT-RELAY.md) - - [The IPFS bundle](./PDD-THE-IPFS-BUNDLE.md) -- **Quantitative** - Test Stories focused on stress testing the implementations against each other. - - [Thousands of Muxed Streams](./PDD-THOUSANDS-OF-MUXED-STREAMS.md) - - [Hundreds of Peer Connections](./PDD-HUNDREDS-OF-PEER-CONNECTIONS.md) +### Install + +``` +> git clone git@github.com:libp2p/interop.git +> cd interop +> npm install +``` + +### Run the tests + +``` +> npm test +``` + +### Test with a non yet released version of js-ipfs + +TODO + +### Test with a non yet released version of go-ipfs + +TODO ## Contribute diff --git a/package.json b/package.json index 657d88e..1ff36fe 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,44 @@ { - "name": "libp2p-interop", + "name": "interop-libp2p", "version": "0.0.0", - "description": "", - "main": " ", + "description": "Interoperability Tests for libp2p", + "leadMaintainer": "Vasco Santos ", + "main": "", + "engines": { + "node": ">=10.0.0", + "npm": ">6.0.0" + }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "lint": "aegir lint", + "test": "aegir test -t node -f test/node.js", + "test:node": "aegir test -t node -f test/node.js", + "test:browser": "aegir test -t browser --no-cors -f test/browser.js" }, "repository": { "type": "git", "url": "git+https://github.com/libp2p/interop.git" }, - "author": "David Dias ", + "keywords": [ + "libp2p" + ], "license": "MIT", "bugs": { "url": "https://github.com/libp2p/interop/issues" }, - "homepage": "https://github.com/libp2p/interop#readme" + "homepage": "https://github.com/libp2p/interop#readme", + "devDependencies": { + "aegir": "^18.1.0", + "chai": "^4.2.0", + "chai-checkmark": "^1.0.1", + "cross-env": "^5.2.0", + "dirty-chai": "^2.0.1", + "go-libp2p-dep": "^6.0.30", + "libp2p-daemon": "~0.1.2", + "libp2p-daemon-client": "~0.0.3", + "rimraf": "^2.6.3" + }, + "contributors": [], + "dependencies": { + "execa": "^1.0.0" + } } diff --git a/PDD-CIRCUIT-RELAY.md b/pdd/PDD-CIRCUIT-RELAY.md similarity index 100% rename from PDD-CIRCUIT-RELAY.md rename to pdd/PDD-CIRCUIT-RELAY.md diff --git a/PDD-CONTENT-ROUTING.md b/pdd/PDD-CONTENT-ROUTING.md similarity index 100% rename from PDD-CONTENT-ROUTING.md rename to pdd/PDD-CONTENT-ROUTING.md diff --git a/PDD-ENCRYPTED-COMMUNICATIONS.md b/pdd/PDD-ENCRYPTED-COMMUNICATIONS.md similarity index 100% rename from PDD-ENCRYPTED-COMMUNICATIONS.md rename to pdd/PDD-ENCRYPTED-COMMUNICATIONS.md diff --git a/PDD-HUNDREDS-OF-PEER-CONNECTIONS.md b/pdd/PDD-HUNDREDS-OF-PEER-CONNECTIONS.md similarity index 100% rename from PDD-HUNDREDS-OF-PEER-CONNECTIONS.md rename to pdd/PDD-HUNDREDS-OF-PEER-CONNECTIONS.md diff --git a/PDD-IDENTIFY.md b/pdd/PDD-IDENTIFY.md similarity index 100% rename from PDD-IDENTIFY.md rename to pdd/PDD-IDENTIFY.md diff --git a/PDD-PEER-ROUTING.md b/pdd/PDD-PEER-ROUTING.md similarity index 100% rename from PDD-PEER-ROUTING.md rename to pdd/PDD-PEER-ROUTING.md diff --git a/PDD-PROTOCOL-AND-STREAM-MULTIPLEXING.md b/pdd/PDD-PROTOCOL-AND-STREAM-MULTIPLEXING.md similarity index 100% rename from PDD-PROTOCOL-AND-STREAM-MULTIPLEXING.md rename to pdd/PDD-PROTOCOL-AND-STREAM-MULTIPLEXING.md diff --git a/PDD-PUBSUB.md b/pdd/PDD-PUBSUB.md similarity index 100% rename from PDD-PUBSUB.md rename to pdd/PDD-PUBSUB.md diff --git a/PDD-THE-IPFS-BUNDLE.md b/pdd/PDD-THE-IPFS-BUNDLE.md similarity index 100% rename from PDD-THE-IPFS-BUNDLE.md rename to pdd/PDD-THE-IPFS-BUNDLE.md diff --git a/PDD-THOUSANDS-OF-MUXED-STREAMS.md b/pdd/PDD-THOUSANDS-OF-MUXED-STREAMS.md similarity index 100% rename from PDD-THOUSANDS-OF-MUXED-STREAMS.md rename to pdd/PDD-THOUSANDS-OF-MUXED-STREAMS.md diff --git a/PDD-TRANSPORT.md b/pdd/PDD-TRANSPORT.md similarity index 100% rename from PDD-TRANSPORT.md rename to pdd/PDD-TRANSPORT.md diff --git a/peer-a.json b/pdd/peer-a.json similarity index 100% rename from peer-a.json rename to pdd/peer-a.json diff --git a/peer-b.json b/pdd/peer-b.json similarity index 100% rename from peer-b.json rename to pdd/peer-b.json diff --git a/peer-c.json b/pdd/peer-c.json similarity index 100% rename from peer-c.json rename to pdd/peer-c.json diff --git a/peer-d.json b/pdd/peer-d.json similarity index 100% rename from peer-d.json rename to pdd/peer-d.json diff --git a/peer-e.json b/pdd/peer-e.json similarity index 100% rename from peer-e.json rename to pdd/peer-e.json diff --git a/src/daemon.js b/src/daemon.js new file mode 100644 index 0000000..408c008 --- /dev/null +++ b/src/daemon.js @@ -0,0 +1,142 @@ +'use strict' + +const assert = require('assert') +const execa = require('execa') +const fs = require('fs') +const path = require('path') +const rimraf = require('rimraf') + +const Client = require('libp2p-daemon-client') +const { getSockPath, isWindows } = require('./utils') + +// process path +const processPath = process.cwd() + +// go-libp2p defaults +const goDaemon = { + defaultSock: getSockPath('/tmp/p2pd-go.sock'), + bin: path.join('go-libp2p-dep', 'go-libp2p', isWindows ? 'p2pd.exe' : 'p2pd') +} + +// js-libp2p defaults +const jsDaemon = { + defaultSock: getSockPath('/tmp/p2pd-js.sock'), + bin: path.join('libp2p-daemon', 'src', 'cli', 'bin.js') +} + +class Daemon { + /** + * @constructor + * @param {String} type daemon implementation type ("go" or "js") + * @param {String} sock unix socket path + */ + constructor (type, sock) { + assert(type === 'go' || type === 'js', 'invalid type received. Should be "go" or "js"') + + this._client = undefined + this._type = type + this._binPath = this._getBinPath(type) + this._sock = sock && getSockPath(sock) + + if (!this._sock) { + this._sock = type === 'go' ? goDaemon.defaultSock : jsDaemon.defaultSock + } + } + + /** + * Get binary path according to implementation and OS + * @private + * @param {String} type daemon implementation type ("go" or "js") + * @returns {String} + */ + _getBinPath (type) { + const depPath = type === 'go' ? goDaemon.bin : jsDaemon.bin + let npmPath = path.join(processPath, '../', depPath) + + if (fs.existsSync(npmPath)) { + return npmPath + } + + npmPath = path.join(processPath, 'node_modules', depPath) + + if (fs.existsSync(npmPath)) { + return npmPath + } + + throw new Error('Cannot find the libp2p executable') + } + + /** + * @async + * Starts a daemon and a client associated with it. + * @returns {void} + */ + async start () { + if (this._client) { + throw new Error('Daemon has already started') + } + + // start daemon + await this._startDaemon() + + // start client + this._client = new Client(this._sock) + + await this._client.attach() + } + + /** + * Starts the specifiec daemon and wait for its start. + * @private + * @returns {Promise} + */ + _startDaemon () { + return new Promise((resolve, reject) => { + const options = this._type === 'go' ? ['-listen', `/unix${this._sock}`] : ['--sock', this._sock] + const daemon = execa(this._binPath, options) + + daemon.stdout.once('data', () => { + return resolve() + }) + daemon.stderr.once('data', (data) => { + return reject(data.toString()) + }) + }) + } + + /** + * @async + * Stops the daemon client and cleans the unix socket. + * @returns {void} + */ + async stop () { + await this._client && this._client.close() + await this._cleanUnixSocket() + } + + /** + * Cleans the unix socket. + * @private + * @returns {Promise} + */ + _cleanUnixSocket () { + return new Promise((resolve, reject) => { + rimraf(this._sock, (err) => { + if (err) { + return reject(err) + } + resolve() + }) + }) + } + + /** + * libp2p client instance + * @type {Client} + */ + get client () { + return this._client + } +} + +exports = module.exports = Daemon diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..c8d7a72 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,11 @@ +'use strict' + +const os = require('os') +const path = require('path') +const isWindows = os.platform() === 'win32' + +exports.isWindows = isWindows + +exports.getSockPath = (sockPath) => isWindows + ? path.join('\\\\?\\pipe', sockPath) + : path.resolve(os.tmpdir(), sockPath) diff --git a/test/connect/README.md b/test/connect/README.md new file mode 100644 index 0000000..ae14148 --- /dev/null +++ b/test/connect/README.md @@ -0,0 +1,3 @@ +# Connect + +In this set of tests, we intend to guarantee that nodes implemented in a specific language are able to connect with other nodes, regardless of their implementation language. \ No newline at end of file diff --git a/test/connect/go2go.js b/test/connect/go2go.js new file mode 100644 index 0000000..1a9cacf --- /dev/null +++ b/test/connect/go2go.js @@ -0,0 +1,61 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +chai.use(require('dirty-chai')) +chai.use(require('chai-checkmark')) +const expect = chai.expect + +const Daemon = require('../../src/daemon') + +describe('connect', () => { + let goDaemon1 + let goDaemon2 + + // Start Daemons + before(async function () { + this.timeout(20 * 1000) + + goDaemon1 = new Daemon('go') + goDaemon2 = new Daemon('go', '/tmp/p2pd-go2.sock') + + await Promise.all([ + goDaemon1.start(), + goDaemon2.start() + ]) + }) + + // Stop daemons + after(async function () { + await Promise.all([ + goDaemon1.stop(), + goDaemon2.stop() + ]) + }) + + it('go peer to go peer', async function () { + this.timeout(10 * 1000) + + const identify1 = await goDaemon1.client.identify() + const identify2 = await goDaemon2.client.identify() + + // verify connected peers + const knownPeersBeforeConnect1 = await goDaemon1.client.listPeers() + expect(knownPeersBeforeConnect1).to.have.lengthOf(0) + + const knownPeersBeforeConnect2 = await goDaemon2.client.listPeers() + expect(knownPeersBeforeConnect2).to.have.lengthOf(0) + + // connect peers + await goDaemon2.client.connect(identify1.peerId, identify1.addrs) + + // verify connected peers + const knownPeersAfterConnect1 = await goDaemon1.client.listPeers() + expect(knownPeersAfterConnect1).to.have.lengthOf(1) + expect(knownPeersAfterConnect1[0].toB58String()).to.equal(identify2.peerId.toB58String()) + + const knownPeersAfterConnect2 = await goDaemon2.client.listPeers() + expect(knownPeersAfterConnect2).to.have.lengthOf(1) + expect(knownPeersAfterConnect2[0].toB58String()).to.equal(identify1.peerId.toB58String()) + }) +}) diff --git a/test/connect/go2js.js b/test/connect/go2js.js new file mode 100644 index 0000000..f60fde6 --- /dev/null +++ b/test/connect/go2js.js @@ -0,0 +1,61 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +chai.use(require('dirty-chai')) +chai.use(require('chai-checkmark')) +const expect = chai.expect + +const Daemon = require('../../src/daemon') + +describe('connect', () => { + let goDaemon + let jsDaemon + + // Start Daemons + before(async function () { + this.timeout(20 * 1000) + + goDaemon = new Daemon('go') + jsDaemon = new Daemon('js') + + await Promise.all([ + goDaemon.start(), + jsDaemon.start() + ]) + }) + + // Stop daemons + after(async function () { + await Promise.all([ + goDaemon.stop(), + jsDaemon.stop() + ]) + }) + + it('go peer to js peer', async function () { + this.timeout(10 * 1000) + + const identifyJs = await jsDaemon.client.identify() + const identifyGo = await goDaemon.client.identify() + + // verify connected peers + const knownPeersBeforeConnectJs = await jsDaemon.client.listPeers() + expect(knownPeersBeforeConnectJs).to.have.lengthOf(0) + + const knownPeersBeforeConnectGo = await goDaemon.client.listPeers() + expect(knownPeersBeforeConnectGo).to.have.lengthOf(0) + + // connect peers + await goDaemon.client.connect(identifyJs.peerId, identifyJs.addrs) + + // verify connected peers + const knownPeersAfterConnectGo = await goDaemon.client.listPeers() + expect(knownPeersAfterConnectGo).to.have.lengthOf(1) + expect(knownPeersAfterConnectGo[0].toB58String()).to.equal(identifyJs.peerId.toB58String()) + + const knownPeersAfterConnectJs = await jsDaemon.client.listPeers() + expect(knownPeersAfterConnectJs).to.have.lengthOf(1) + expect(knownPeersAfterConnectJs[0].toB58String()).to.equal(identifyGo.peerId.toB58String()) + }) +}) diff --git a/test/connect/index.js b/test/connect/index.js new file mode 100644 index 0000000..dbe06c2 --- /dev/null +++ b/test/connect/index.js @@ -0,0 +1,6 @@ +'use strict' + +require('./go2go') +require('./go2js') +require('./js2go') +require('./js2js') diff --git a/test/connect/js2go.js b/test/connect/js2go.js new file mode 100644 index 0000000..a4a885a --- /dev/null +++ b/test/connect/js2go.js @@ -0,0 +1,61 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +chai.use(require('dirty-chai')) +chai.use(require('chai-checkmark')) +const expect = chai.expect + +const Daemon = require('../../src/daemon') + +describe('connect', () => { + let jsDaemon + let goDaemon + + // Start Daemons + before(async function () { + this.timeout(20 * 1000) + + jsDaemon = new Daemon('js') + goDaemon = new Daemon('go') + + await Promise.all([ + jsDaemon.start(), + goDaemon.start() + ]) + }) + + // Stop daemons + after(async function () { + await Promise.all([ + jsDaemon.stop(), + goDaemon.stop() + ]) + }) + + it('js peer to go peer', async function () { + this.timeout(10 * 1000) + + const identifyJs = await jsDaemon.client.identify() + const identifyGo = await goDaemon.client.identify() + + // verify connected peers + const knownPeersBeforeConnectJs = await jsDaemon.client.listPeers() + expect(knownPeersBeforeConnectJs).to.have.lengthOf(0) + + const knownPeersBeforeConnectGo = await goDaemon.client.listPeers() + expect(knownPeersBeforeConnectGo).to.have.lengthOf(0) + + // connect peers + await jsDaemon.client.connect(identifyGo.peerId, identifyGo.addrs) + + // verify connected peers + const knownPeersAfterConnectJs = await jsDaemon.client.listPeers() + expect(knownPeersAfterConnectJs).to.have.lengthOf(1) + expect(knownPeersAfterConnectJs[0].toB58String()).to.equal(identifyGo.peerId.toB58String()) + + const knownPeersAfterConnectGo = await goDaemon.client.listPeers() + expect(knownPeersAfterConnectGo).to.have.lengthOf(1) + expect(knownPeersAfterConnectGo[0].toB58String()).to.equal(identifyJs.peerId.toB58String()) + }) +}) diff --git a/test/connect/js2js.js b/test/connect/js2js.js new file mode 100644 index 0000000..0eca213 --- /dev/null +++ b/test/connect/js2js.js @@ -0,0 +1,64 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +chai.use(require('dirty-chai')) +chai.use(require('chai-checkmark')) +const expect = chai.expect + +const Daemon = require('../../src/daemon') + +describe('connect', () => { + let jsDaemon1 + let jsDaemon2 + + // Start Daemons + before(async function () { + this.timeout(20 * 1000) + + jsDaemon1 = new Daemon('js') + jsDaemon2 = new Daemon('js', '/tmp/p2pd-js2.sock') + + await Promise.all([ + jsDaemon1.start(), + jsDaemon2.start() + ]) + }) + + // Stop daemons + after(async function () { + await Promise.all([ + jsDaemon1.stop(), + jsDaemon2.stop() + ]) + }) + + it('js peer to js peer', async function () { + this.timeout(10 * 1000) + + const identify1 = await jsDaemon1.client.identify() + const identify2 = await jsDaemon2.client.identify() + + // verify connected peers + const knownPeersBeforeConnect1 = await jsDaemon1.client.listPeers() + expect(knownPeersBeforeConnect1).to.have.lengthOf(0) + + const knownPeersBeforeConnect2 = await jsDaemon2.client.listPeers() + expect(knownPeersBeforeConnect2).to.have.lengthOf(0) + + // connect peers + await jsDaemon2.client.connect(identify1.peerId, identify1.addrs) + + // jsDaemon1 will take some time to get the peers + await new Promise(resolve => setTimeout(resolve, 1000)) + + // verify connected peers + const knownPeersAfterConnect1 = await jsDaemon1.client.listPeers() + expect(knownPeersAfterConnect1).to.have.lengthOf(1) + expect(knownPeersAfterConnect1[0].toB58String()).to.equal(identify2.peerId.toB58String()) + + const knownPeersAfterConnect2 = await jsDaemon2.client.listPeers() + expect(knownPeersAfterConnect2).to.have.lengthOf(1) + expect(knownPeersAfterConnect2[0].toB58String()).to.equal(identify1.peerId.toB58String()) + }) +}) diff --git a/test/node.js b/test/node.js new file mode 100644 index 0000000..6d036c4 --- /dev/null +++ b/test/node.js @@ -0,0 +1,3 @@ +'use strict' + +require('./connect')