From 7f3a2d718332735b4f3a06a2da238bcccf2e7a79 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Mon, 10 Feb 2020 11:29:01 +0100 Subject: [PATCH] refactor: remove proxy api object and detect initialisaion state Ask the repo if it has been initialised, if so, allow the user to skip the `.init()` step and move on to `.start()` Removes the proxy api object in favour of vanilla functions because it was causing errors to be thrown if you even referenced properties that were from a different api state. E.g. with an unitialised repo: ```javascript const ipfs = await IPFS.create({ init: false, start: false }) // no invocation, just referencing the property causes an error to be thrown console.info(ipfs.start) ``` I'd looked at changing the proxy behaviour to return a function that throws if invoked, but at the time the proxy is called you don't know what the calling code is going to do with the return value so it's hard to know if it's accessing a function or a property - the return value is just put on the stack and interacted with so it seemed simpler to just pull it out and define the API up front. A nice future improvement might be to have `.init`, `.start` and `.stop` export functions that update the API - that way after `.stop` has been invoked, it could restore the API from the post-`.init` state, but this can come later. Also upgrades `ipfsd-ctl` to pass refs only during factory creation. Depends on: - [ ] https://github.com/ipfs/js-ipfsd-ctl/pull/457 - [ ] https://github.com/ipfs/js-ipfs-repo/pull/219 - [ ] https://github.com/ipfs/npm-go-ipfs-dep/pull/40 fix: do not allow parallel init, start or stop If the user is calling `.init`, `.start` or `.stop` from the code in multiple places simultaneously, they probably have a bug in their code and we should let them know instead of masking it. --- package.json | 3 +- src/core/api-manager.js | 127 +++++++++++++++++++++++++++++++--- src/core/components/init.js | 40 +++-------- src/core/components/start.js | 21 +++--- src/core/components/stop.js | 18 +++-- src/core/errors.js | 33 +++++++++ src/core/index.js | 35 +++++++--- test/core/bitswap.spec.js | 8 +-- test/core/create-node.spec.js | 4 ++ test/core/init.spec.js | 66 ++++++++++++++++++ test/core/start.spec.js | 113 ++++++++++++++++++++++++++++++ test/core/stop.spec.js | 100 ++++++++++++++++++++++++++ 12 files changed, 496 insertions(+), 72 deletions(-) create mode 100644 test/core/start.spec.js create mode 100644 test/core/stop.spec.js diff --git a/package.json b/package.json index ce0696fc34..87174bb503 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "ipfs-http-response": "^0.5.0", "ipfs-mfs": "^1.0.0", "ipfs-multipart": "^0.3.0", - "ipfs-repo": "^0.30.0", + "ipfs-repo": "^1.0.0", "ipfs-unixfs": "^0.3.0", "ipfs-unixfs-exporter": "^0.41.0", "ipfs-unixfs-importer": "^0.44.0", @@ -155,7 +155,6 @@ "multicodec": "^1.0.0", "multihashes": "~0.4.14", "multihashing-async": "^0.8.0", - "p-defer": "^3.0.0", "p-queue": "^6.1.0", "parse-duration": "^0.1.2", "peer-id": "^0.13.5", diff --git a/src/core/api-manager.js b/src/core/api-manager.js index 5ccc055e98..1fb8542121 100644 --- a/src/core/api-manager.js +++ b/src/core/api-manager.js @@ -1,23 +1,128 @@ 'use strict' +const noop = () => {} +const defaultApi = (onUndef = noop) => ({ + add: onUndef, + bitswap: { + stat: onUndef, + unwant: onUndef, + wantlist: onUndef + }, + block: { + get: onUndef, + put: onUndef, + rm: onUndef, + stat: onUndef + }, + bootstrap: { + add: onUndef, + list: onUndef, + rm: onUndef + }, + cat: onUndef, + config: onUndef, + dag: { + get: onUndef, + put: onUndef, + resolve: onUndef, + tree: onUndef + }, + dns: onUndef, + files: { + chmod: onUndef, + cp: onUndef, + flush: onUndef, + ls: onUndef, + mkdir: onUndef, + mv: onUndef, + read: onUndef, + rm: onUndef, + stat: onUndef, + touch: onUndef, + write: onUndef + }, + get: onUndef, + id: onUndef, + init: onUndef, + isOnline: onUndef, + key: { + export: onUndef, + gen: onUndef, + import: onUndef, + info: onUndef, + list: onUndef, + rename: onUndef, + rm: onUndef + }, + ls: onUndef, + name: { + publish: onUndef, + pubsub: { + cancel: onUndef, + state: onUndef, + subs: onUndef + } + }, + object: { + data: onUndef, + get: onUndef, + links: onUndef, + new: onUndef, + patch: { + addLink: onUndef, + appendData: onUndef, + rmLink: onUndef, + setData: onUndef + }, + put: onUndef, + stat: onUndef + }, + pin: onUndef, + ping: onUndef, + pubsub: { + subscribe: onUndef, + unsubscribe: onUndef, + publish: onUndef, + ls: onUndef, + peers: onUndef + }, + refs: onUndef, + repo: { + gc: onUndef, + stat: onUndef, + version: onUndef + }, + resolve: onUndef, + start: onUndef, + stats: { + bitswap: onUndef, + bw: onUndef, + repo: onUndef + }, + stop: onUndef, + swarm: { + addrs: onUndef, + connect: onUndef, + disconnect: onUndef, + localAddrs: onUndef, + peers: onUndef + }, + version: onUndef +}) + module.exports = class ApiManager { constructor () { - this._api = {} - this._onUndef = () => undefined - this.api = new Proxy(this._api, { - get: (_, prop) => { - if (prop === 'then') return undefined // Not a promise! - return this._api[prop] === undefined ? this._onUndef(prop) : this._api[prop] - } - }) + this.api = { + ...defaultApi() + } } update (nextApi, onUndef) { const prevApi = { ...this._api } const prevUndef = this._onUndef - Object.keys(this._api).forEach(k => { delete this._api[k] }) - Object.assign(this._api, nextApi) - if (onUndef) this._onUndef = onUndef + Object.keys(this.api).forEach(k => { delete this.api[k] }) + Object.assign(this.api, defaultApi(onUndef), nextApi) + this._onUndef = onUndef || noop return { cancel: () => this.update(prevApi, prevUndef), api: this.api } } } diff --git a/src/core/components/init.js b/src/core/components/init.js index 7e2f0ce94d..5b4b587e44 100644 --- a/src/core/components/init.js +++ b/src/core/components/init.js @@ -5,7 +5,6 @@ const PeerId = require('peer-id') const PeerInfo = require('peer-info') const mergeOptions = require('merge-options') const getDefaultConfig = require('../runtime/config-nodejs.js') -const createRepo = require('../runtime/repo-nodejs') const Keychain = require('libp2p-keychain') const NoKeychain = require('./no-keychain') const mortice = require('mortice') @@ -13,16 +12,14 @@ const { DAGNode } = require('ipld-dag-pb') const UnixFs = require('ipfs-unixfs') const multicodec = require('multicodec') const { - AlreadyInitializingError, AlreadyInitializedError, - NotStartedError, - NotEnabledError + AlreadyInitializingError, + NotStartedError } = require('../errors') const BlockService = require('ipfs-block-service') const Ipld = require('ipld') const getDefaultIpldOptions = require('../runtime/ipld-nodejs') const createPreloader = require('../preload') -const { ERR_REPO_NOT_INITIALIZED } = require('ipfs-repo').errors const IPNS = require('../ipns') const OfflineDatastore = require('../ipns/routing/offline-datastore') const initAssets = require('../runtime/init-assets-nodejs') @@ -32,9 +29,14 @@ const Components = require('./') module.exports = ({ apiManager, print, - options: constructorOptions + options: constructorOptions, + repo }) => async function init (options) { - const { cancel } = apiManager.update({ init: () => { throw new AlreadyInitializingError() } }) + const { cancel } = apiManager.update({ + init: () => { + throw new AlreadyInitializingError() + } + }) try { options = options || {} @@ -49,30 +51,9 @@ module.exports = ({ options.config = mergeOptions(options.config, constructorOptions.config) } - options.repo = options.repo || constructorOptions.repo options.repoAutoMigrate = options.repoAutoMigrate || constructorOptions.repoAutoMigrate - const repo = typeof options.repo === 'string' || options.repo == null - ? createRepo({ path: options.repo, autoMigrate: options.repoAutoMigrate }) - : options.repo - - let isInitialized = true - - if (repo.closed) { - try { - await repo.open() - } catch (err) { - if (err.code === ERR_REPO_NOT_INITIALIZED) { - isInitialized = false - } else { - throw err - } - } - } - - if (!isInitialized && options.allowNew === false) { - throw new NotEnabledError('new repo initialization is not enabled') - } + const isInitialized = await repo.isInitialized() const { peerId, keychain } = isInitialized ? await initExistingRepo(repo, options) @@ -212,6 +193,7 @@ async function initNewRepo (repo, { privateKey, emptyRepo, bits, profiles, confi } async function initExistingRepo (repo, { config: newConfig, profiles, pass }) { + await repo.open() let config = await repo.config.get() if (newConfig || profiles) { diff --git a/src/core/components/start.js b/src/core/components/start.js index ec771cb09b..1edade5eae 100644 --- a/src/core/components/start.js +++ b/src/core/components/start.js @@ -3,10 +3,14 @@ const Bitswap = require('ipfs-bitswap') const multiaddr = require('multiaddr') const get = require('dlv') -const defer = require('p-defer') const IPNS = require('../ipns') const routingConfig = require('../ipns/routing/config') -const { AlreadyInitializedError, NotEnabledError } = require('../errors') +const { + AlreadyInitializedError, + AlreadyStartingError, + AlreadyStartedError, + NotEnabledError +} = require('../errors') const Components = require('./') const createMfsPreload = require('../mfs-preload') @@ -24,8 +28,11 @@ module.exports = ({ print, repo }) => async function start () { - const startPromise = defer() - const { cancel } = apiManager.update({ start: () => startPromise.promise }) + const { cancel } = apiManager.update({ + start: () => { + throw new AlreadyStartingError() + } + }) try { // The repo may be closed if previously stopped @@ -97,14 +104,12 @@ module.exports = ({ repo }) - apiManager.update(api, () => undefined) + apiManager.update(api) } catch (err) { cancel() - startPromise.reject(err) throw err } - startPromise.resolve(apiManager.api) return apiManager.api } @@ -234,7 +239,7 @@ function createApi ({ version: Components.repo.version({ repo }) }, resolve, - start: () => apiManager.api, + start: async () => { throw new AlreadyStartedError() }, // eslint-disable-line require-await stats: { bitswap: Components.bitswap.stat({ bitswap }), bw: libp2p.metrics diff --git a/src/core/components/stop.js b/src/core/components/stop.js index f8ff775199..67a52ac439 100644 --- a/src/core/components/stop.js +++ b/src/core/components/stop.js @@ -1,7 +1,10 @@ 'use strict' -const defer = require('p-defer') -const { NotStartedError, AlreadyInitializedError } = require('../errors') +const { + NotStartedError, + AlreadyInitializedError, + AlreadyStoppingError +} = require('../errors') const Components = require('./') module.exports = ({ @@ -22,8 +25,11 @@ module.exports = ({ print, repo }) => async function stop () { - const stopPromise = defer() - const { cancel } = apiManager.update({ stop: () => stopPromise.promise }) + const { cancel } = apiManager.update({ + stop: () => { + throw new AlreadyStoppingError() + } + }) try { blockService.unsetExchange() @@ -58,11 +64,9 @@ module.exports = ({ apiManager.update(api, () => { throw new NotStartedError() }) } catch (err) { cancel() - stopPromise.reject(err) throw err } - stopPromise.resolve(apiManager.api) return apiManager.api } @@ -182,7 +186,7 @@ function createApi ({ bw: notStarted, repo: Components.repo.stat({ repo }) }, - stop: () => apiManager.api, + stop: notStarted, swarm: { addrs: notStarted, connect: notStarted, diff --git a/src/core/errors.js b/src/core/errors.js index 465576b322..d1272fb6d3 100644 --- a/src/core/errors.js +++ b/src/core/errors.js @@ -44,6 +44,39 @@ class NotStartedError extends Error { NotStartedError.code = 'ERR_NOT_STARTED' exports.NotStartedError = NotStartedError +class AlreadyStartingError extends Error { + constructor (message = 'already starting') { + super(message) + this.name = 'AlreadyStartingError' + this.code = AlreadyStartingError.code + } +} + +AlreadyStartingError.code = 'ERR_ALREADY_STARTING' +exports.AlreadyStartingError = AlreadyStartingError + +class AlreadyStartedError extends Error { + constructor (message = 'already started') { + super(message) + this.name = 'AlreadyStartedError' + this.code = AlreadyStartedError.code + } +} + +AlreadyStartedError.code = 'ERR_ALREADY_STARTED' +exports.AlreadyStartedError = AlreadyStartedError + +class AlreadyStoppingError extends Error { + constructor (message = 'already started') { + super(message) + this.name = 'AlreadyStoppingError' + this.code = AlreadyStartedError.code + } +} + +AlreadyStoppingError.code = 'ERR_ALREADY_STOPPING' +exports.AlreadyStoppingError = AlreadyStoppingError + class NotEnabledError extends Error { constructor (message = 'not enabled') { super(message) diff --git a/src/core/index.js b/src/core/index.js index 4e748d7ed8..348b63f6da 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -16,9 +16,10 @@ const multibase = require('multibase') const multicodec = require('multicodec') const multihashing = require('multihashing-async') const CID = require('cids') -const { NotInitializedError } = require('./errors') +const { NotInitializedError, NotEnabledError } = require('./errors') const Components = require('./components') const ApiManager = require('./api-manager') +const createRepo = require('./runtime/repo-nodejs') const getDefaultOptions = () => ({ init: true, @@ -30,7 +31,8 @@ const getDefaultOptions = () => ({ '/dns4/node0.preload.ipfs.io/https', '/dns4/node1.preload.ipfs.io/https' ] - } + }, + repoAutoMigrate: false }) async function create (options) { @@ -38,26 +40,39 @@ async function create (options) { // eslint-disable-next-line no-console const print = options.silent ? log : console.log + const repo = typeof options.repo === 'string' || options.repo == null + ? createRepo({ path: options.repo, autoMigrate: options.repoAutoMigrate }) + : options.repo const apiManager = new ApiManager() - const { api } = apiManager.update({ - init: Components.init({ apiManager, print, options }), + init: Components.init({ + apiManager, + print, + options, + repo + }), dns: Components.dns(), isOnline: Components.isOnline({}) }, async () => { throw new NotInitializedError() }) // eslint-disable-line require-await - if (!options.init) { - return api + if (await repo.isInitialized()) { + // FIXME: the repo is already initialised so we are only calling init + // here for the side effect of it updating the available api operations + await api.init() + } else if (options.init.allowNew === false) { + throw new NotEnabledError('new repo initialization is not enabled') } - await api.init() + if (options.init && !(await repo.isInitialized())) { + await api.init() + } - if (!options.start) { - return api + if (options.start) { + return api.start() } - return api.start() + return api } module.exports = { diff --git a/test/core/bitswap.spec.js b/test/core/bitswap.spec.js index d772eb8dff..0c8cd9cbc6 100644 --- a/test/core/bitswap.spec.js +++ b/test/core/bitswap.spec.js @@ -22,6 +22,8 @@ describe('bitswap', function () { this.timeout(60 * 1000) const df = factory() + afterEach(() => df.clean()) + describe('transfer a block between', () => { it('2 peers', async function () { const remote = (await df.spawn({ type: 'js' })).api @@ -33,7 +35,6 @@ describe('bitswap', function () { const b = await remote.block.get(block.cid) expect(b.data).to.eql(block.data) - await df.clean() }) it('3 peers', async () => { @@ -57,7 +58,6 @@ describe('bitswap', function () { expect(await remote2.block.get(block.cid)).to.eql(block) expect(await proc.block.get(block.cid)).to.eql(block) }, { concurrency: 3 }) - await df.clean() }) }) @@ -66,6 +66,7 @@ describe('bitswap', function () { // TODO make this test more interesting (10Mb file) // TODO remove randomness from the test const file = Buffer.from(`I love IPFS <3 ${hat()}`) + const remote = (await df.spawn({ type: 'js' })).api const proc = (await df.spawn({ type: 'proc' })).api await proc.swarm.connect(remote.peerId.addresses[0]) @@ -73,7 +74,6 @@ describe('bitswap', function () { const files = await all(remote.add([{ path: 'awesome.txt', content: file }])) const data = await concat(proc.cat(files[0].cid)) expect(data.slice()).to.eql(file) - await df.clean() }) }) @@ -85,8 +85,6 @@ describe('bitswap', function () { } catch (err) { expect(err).to.exist() expect(err.code).to.equal('ERR_INVALID_CID') - } finally { - await df.clean() } }) }) diff --git a/test/core/create-node.spec.js b/test/core/create-node.spec.js index 8b63ee1ae0..8e8ac47e62 100644 --- a/test/core/create-node.spec.js +++ b/test/core/create-node.spec.js @@ -168,6 +168,10 @@ describe('create node', function () { repo: tempRepo, init: { bits: 512 }, config: { + Addresses: { + Swarm: [] + }, + Bootstrap: [], Pubsub: { Enabled: false } diff --git a/test/core/init.spec.js b/test/core/init.spec.js index 2089e4a02f..bcdc807453 100644 --- a/test/core/init.spec.js +++ b/test/core/init.spec.js @@ -6,6 +6,10 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const { isNode } = require('ipfs-utils/src/env') const hat = require('hat') const IPFS = require('../../src/core') +const { + AlreadyInitializedError, + AlreadyInitializingError +} = require('../../src/core/errors') const privateKey = 'CAASqAkwggSkAgEAAoIBAQChVmiObYo6pkKrMSd3OzW1cTL+RDmX1rkETYGKWV9TPXMNgElFTYoYHqT9QZomj5RI8iUmHccjzqr4J0mV+E0NpvHHOLlmDZ82lAw2Zx7saUkeQWvC0S9Z0o3aTx2sSubZV53rSomkZgQH4fYTs4RERejV4ltzLFdzQQBwWrBvlagpPHUCxKDUCnE5oIzdbD26ltWViPBWr7TfotzC8Lyi/tceqCpHMUJGMbsVgypnlgpey07MBvs71dVh5LcRen/ztsQO6Yju4D3QgWoyD0SIUdJFvBzEwL9bSiA3QjUc/fkGd7EcdN5bebYOqAi4ZIiAMLp3i4+B8Tzq/acull43AgMBAAECggEBAIDgZE75o4SsEO9tKWht7L5OeXxxBUyMImkUfJkGQUZd/MzZIC5y/Q+9UvBW+gs5gCsw+onTGaM50Iq/32Ej4nE4XURVxIuH8BmJ86N1hlc010qK2cjajqeCsPulXT+m6XbOLYCpnv+q2idt0cL1EH/1FEPeOEztK8ION4qIdw36SoykfTx/RqtkKHtS01AwN82EOPbWk7huyQT5R5MsCZmRJXBFkpNtiL+8619BH2aVlghHO4NouF9wQjdz/ysVuyYg+3rX2cpGjuHDTZ6hVQiJD1lF6D+dua7UPyHYAG2iRQiKZmCjitt9ywzPxiRaYF/aZ02FEMWckZulR09axskCgYEAzjl6ER8WwxYHn4tHse+CrIIF2z5cscdrh7KSwd3Rse9hIIBDJ/0KkvoYd1IcWrS8ywLrRfSLIjEU9u7IN1m+IRVWJ61fXNqOHm9clAu6qNhCN6W2+JfxDkUygTwmsq0v3huO+qkiMQz+a4nAXJe8Utd36ywgPhVGxFa/7x1v1N0CgYEAyEdiYRFf1aQZcO7+B2FH+tkGJsB30VIBhcpG9EukuQUUulLHhScc/KRj+EFAACLdkTqlVI0xVYIWaaCXwoQCWKixjZ5mYPC+bBLgn4IoDS6XTdHtR7Vn3UUvGTKsM0/z4e8/0eSzGNCHoYez9IoBlPNic0sQuST4jzgS2RYnFCMCgYASWSzSLyjwTJp7CIJlg4Dl5l+tBRxsOOkJVssV8q2AnmLO6HqRKUNylkvs+eJJ88DEc0sJm1txvFo4KkCoJBT1jpduyk8szMlOTew3w99kvHEP0G+6KJKrCV8X/okW5q/WnC8ZgEjpglV0rfnugxWfbUpfIzrvKydzuqAzHzRfBQKBgQDANtKSeoxRjEbmfljLWHAure8bbgkQmfXgI7xpZdfXwqqcECpw/pLxXgycDHOSLeQcJ/7Y4RGCEXHVOk2sX+mokW6mjmmPjD4VlyCBtfcef6KzC1EBS3c9g9KqCln+fTOBmY7UsPu6SxiAzK7HeVP/Un8gS+Dm8DalrZlZQ8uJpQKBgF6mL/Xo/XUOiz2jAD18l8Y6s49bA9H2CoLpBGTV1LfY5yTFxRy4R3qnX/IzsKy567sbtkEFKJxplc/RzCQfrgbdj7k26SbKtHR3yERaFGRYq8UeAHeYC1/N19LF5BMQL4y5R4PJ1SFPeJCL/wXiMqs1maTqvKqtc4bbegNdwlxn' @@ -25,6 +29,7 @@ describe('init', function () { repo, init: false, start: false, + silent: true, preload: { enabled: false } }) }) @@ -89,4 +94,65 @@ describe('init', function () { expect(config.Bootstrap).to.be.empty() expect(config.Discovery.MDNS.Enabled).to.be.true() }) + + it('should be initialised if initialised previously', async () => { + // init and start a node + ipfs = await IPFS.create({ + repo, + init: true, + start: true, + silent: true, + preload: { enabled: false } + }) + // now stop it + await ipfs.stop() + + // create a new instance with the same repo + ipfs = await IPFS.create({ + repo, + init: false, + start: false, + silent: true, + preload: { enabled: false } + }) + + // should be able to start the node and not have to init it first + await ipfs.start() + await ipfs.stop() + }) + + it('should explode if double init in parallel', async () => { + ipfs = await IPFS.create({ + repo, + init: false, + start: false, + silent: true, + preload: { enabled: false } + }) + + let promise + + await expect(() => { + promise = ipfs.init() + ipfs.init() + }).to.throw(AlreadyInitializingError) + + // wait for the first init promise to resolve - this is because the second + // will cause the test to exit and teardown of the repo to commence which + // pulls the rug out from under the init operation + await promise + }) + + it('should explode if double init in series', async () => { + ipfs = await IPFS.create({ + repo, + init: false, + start: false, + silent: true, + preload: { enabled: false } + }) + + await ipfs.init() + expect(ipfs.init()).to.eventually.be.rejectedWith(AlreadyInitializedError) + }) }) diff --git a/test/core/start.spec.js b/test/core/start.spec.js new file mode 100644 index 0000000000..9d7e5b88d8 --- /dev/null +++ b/test/core/start.spec.js @@ -0,0 +1,113 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const { expect } = require('interface-ipfs-core/src/utils/mocha') +const { isNode } = require('ipfs-utils/src/env') +const IPFS = require('../../src/core') +const { + AlreadyStartingError, + AlreadyStartedError, + NotInitializedError +} = require('../../src/core/errors') + +// This gets replaced by `create-repo-browser.js` in the browser +const createTempRepo = require('../utils/create-repo-nodejs.js') + +describe('start', function () { + if (!isNode) return + + let ipfs + let repo + + beforeEach(() => { + repo = createTempRepo() + }) + + afterEach(async () => { + if (ipfs && ipfs.isOnline()) { + await ipfs.stop() + } + + await repo.teardown() + }) + + it('should start successfully', async () => { + ipfs = await IPFS.create({ + repo, + init: true, + start: false, + silent: true, + preload: { enabled: false } + }) + + expect(ipfs.isOnline()).to.be.false() + await ipfs.start() + expect(ipfs.isOnline()).to.be.true() + }) + + it('should start and stop and start successfully', async () => { + ipfs = await IPFS.create({ + repo, + init: true, + start: false, + silent: true, + preload: { enabled: false } + }) + + expect(ipfs.isOnline()).to.be.false() + await ipfs.start() + expect(ipfs.isOnline()).to.be.true() + await ipfs.stop() + expect(ipfs.isOnline()).to.be.false() + await ipfs.start() + expect(ipfs.isOnline()).to.be.true() + }) + + it('should explode when starting a node twice in parallel', async () => { + ipfs = await IPFS.create({ + repo, + init: true, + start: false, + silent: true, + preload: { enabled: false } + }) + + let promise + + await expect(() => { + promise = ipfs.start() + ipfs.start() + }).to.throw(AlreadyStartingError) + + // wait for the first start promise to resolve - this is because the second + // will cause the test to exit before `ipfs.isOnline` returns true, so the + // node will not be stopped which messes up the rest of the tests + await promise + }) + + it('should explode when starting a node twice in series', async () => { + ipfs = await IPFS.create({ + repo, + init: true, + start: false, + silent: true, + preload: { enabled: false } + }) + + await ipfs.start() + await expect(ipfs.start()).to.eventually.be.rejectedWith(AlreadyStartedError) + }) + + it('should not start an uninitialised node', async () => { + ipfs = await IPFS.create({ + repo, + init: false, + start: false, + silent: true, + preload: { enabled: false } + }) + + await expect(ipfs.start()).to.eventually.be.rejectedWith(NotInitializedError) + }) +}) diff --git a/test/core/stop.spec.js b/test/core/stop.spec.js new file mode 100644 index 0000000000..d6e5e59992 --- /dev/null +++ b/test/core/stop.spec.js @@ -0,0 +1,100 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const { expect } = require('interface-ipfs-core/src/utils/mocha') +const { isNode } = require('ipfs-utils/src/env') +const IPFS = require('../../src/core') +const { + AlreadyStoppingError, + NotStartedError +} = require('../../src/core/errors') + +// This gets replaced by `create-repo-browser.js` in the browser +const createTempRepo = require('../utils/create-repo-nodejs.js') + +describe('stop', function () { + if (!isNode) return + + let ipfs + let repo + + beforeEach(() => { + repo = createTempRepo() + }) + + afterEach(async () => { + if (ipfs && ipfs.isOnline()) { + await ipfs.stop() + } + + await repo.teardown() + }) + + it('should stop successfully', async () => { + ipfs = await IPFS.create({ + repo, + init: true, + start: true, + silent: true, + preload: { enabled: false } + }) + + expect(ipfs.isOnline()).to.be.true() + await ipfs.stop() + expect(ipfs.isOnline()).to.be.false() + }) + + it('should start and stop and start and stop successfully', async () => { + ipfs = await IPFS.create({ + repo, + init: true, + start: true, + silent: true, + preload: { enabled: false } + }) + + expect(ipfs.isOnline()).to.be.true() + await ipfs.stop() + expect(ipfs.isOnline()).to.be.false() + await ipfs.start() + expect(ipfs.isOnline()).to.be.true() + await ipfs.stop() + expect(ipfs.isOnline()).to.be.false() + }) + + it('should explode when stopping a node twice in parallel', async () => { + ipfs = await IPFS.create({ + repo, + init: true, + start: true, + silent: true, + preload: { enabled: false } + }) + + let promise + + await expect(() => { + promise = ipfs.stop() + ipfs.stop() + }).to.throw(AlreadyStoppingError) + + // wait for the first start promise to resolve - this is because the second + // will cause the test to exit before `ipfs.isOnline` returns true, so the + // node will not be stopped which messes up the rest of the tests + await promise + }) + + it('should explode when starting a node twice in series', async () => { + ipfs = await IPFS.create({ + repo, + init: true, + start: true, + silent: true, + preload: { enabled: false } + }) + + await ipfs.stop() + await expect(ipfs.stop()).to.eventually.be.rejectedWith(NotStartedError) + }) +})