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

feat: modularise tests by command, add tools to skip and only #290

Merged
merged 41 commits into from
Jun 27, 2018

Conversation

alanshaw
Copy link
Contributor

@alanshaw alanshaw commented Jun 2, 2018

This PR modularises tests by command and provides tools for consumers of this library to use skip/only on a command basis.

  • Encourages more test isolation. Many tests were expecting values from previous tests to be available, this has largely been eradicated
  • Allows us to test partially implemented sub-systems e.g. currently repo.version and repo.stat are implemented but repo.gc is not. This allows us to import tests for repo.version and repo.stat without failing the suite but also import and skip tests for repo.gc for visibility until the implementation is complete
  • Removes the need for implementation specific checks to skip tests i.e. withGo goes away because we can just run the tests we want to run on any given implementation
  • Removes the need for platform specific checks to skip tests i.e. isNode goes away because we can just run the tests we want to run on any given platform
  • Consistently expresses the test titles more descriptively as "it should..." or "it should not..." to allow for easier understanding of test purpose
  • Consistently labels promised versions of tests with the same name as the non promised version but with the suffix "(promised)". Also moves these tests so they are adjacent in the suite file. This allows the developer to more easily see if the issue is because of the callback/promise interface for a command or a bug within the code for the command

This work is backwards compatible with the previous API so no changes to dependant code needs to be made. I wanted this to be backwards compatible, but quickly ran into an issue where the common object we pass to suites expects to only be used once. I started to pass it to multiple suites and the teardown function was trying to tear down nodes that were already stopped! So, unfortunately breaking change: common should now be a function that creates a common object for use with the tests. I'll submit PR's to js-ipfs (ipfs/js-ipfs#1389) and js-ipfs-api (ipfs-inactive/js-ipfs-http-client#785) to deal with this.

running tests by command

const tests = require('interface-ipfs-core')

tests.repo.version(common)

N.B. you can still run a whole sub-system suite like this:

const tests = require('interface-ipfs-core')
tests.repo(common)

skipping tests for a specific command

const tests = require('interface-ipfs-core')

tests.repo.version(common)
tests.repo.stat(common)
tests.repo.gc(common, { skip: true }) // pass an options object to skip these tests

// OR, at the subsystem level

tests.repo(common, { skip: ['gc'] })

skipping specific tests

const tests = require('interface-ipfs-core')

tests.repo.version(common)
tests.repo.stat(common)
tests.repo.gc(common, { skip: ['should do a thing'] }) // named test(s) to skip

// OR, at the subsystem level

tests.repo(common, { skip: ['should do a thing'] })

only

The idea here is that only can be used during development/bug fixing to run only problematic tests without having to npm link and find the test(s) to add .only to.

running only tests for a specific command

const tests = require('interface-ipfs-core')

tests.repo.version(common)
tests.repo.stat(common)
tests.repo.gc(common, { only: true }) // pass an options object to run only these tests

// OR, at the subsystem level

tests.repo(common, { only: ['gc'] })

running only specific tests

const tests = require('interface-ipfs-core')

tests.repo.version(common)
tests.repo.stat(common)
tests.repo.gc(common, { only: ['should do a thing'] }) // only run these named test(s)

// OR, at the subsystem level

tests.repo(common, { only: ['should do a thing'] })

@ghost ghost assigned alanshaw Jun 2, 2018
@ghost ghost added the in progress label Jun 2, 2018
@alanshaw alanshaw force-pushed the feat/modular-tests-skip-only branch from c0afce8 to c351f5e Compare June 4, 2018 14:57
@travisperson
Copy link
Contributor

This hits a lot of points that Victor and I wanted to try and look into this quarter. I really like the way this is laid out and the removal of the withGo and other opt out variables.

I would love to help take this through to competition.

alanshaw added a commit that referenced this pull request Jun 8, 2018
BREAKING CHANGE: Consumers of this test suite now have fine grained control over what tests are run. Tests can now be skipped and "onlyed" (run only specific tests). This can be done on a test, command and sub-system level. See the updated usage guide for instructions: https://github.com/ipfs/interface-ipfs-core/blob/master/README.md#usage.

This means that tests skips depending on implementation (e.g. go/js), environment (e.g. node/browser) or platform (e.g. macOS/linux/windows) that were previously present in this suite have been removed. Consumers of this library should add their own skips based on the implementation that's being tested and the environment/platform that the tests are running on.

The following other breaking changes have been made:

1. The common object passed to test suites has changed. It must now be a function that returns a common object (same shape and functions as before).
2. The `ipfs.ls` tests (not MFS `ipfs.files.ls`) is now a root level suite. You'll need to import it and use like `tests.ls(createCommon)` to have those tests run.
3. The `generic` suite (an alias to `miscellaneous`) has been removed.

See #290 for more details.

License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
@alanshaw
Copy link
Contributor Author

alanshaw commented Jun 8, 2018

I appreciate this is a massive PR to review, but a lot of it is mechanical changes. Please read the PR description above first (a lot of this is echoed in the changes to the README). Just to highlight how things have moved around:

Each sub-system (dag, files, swarm etc.) is now a folder with an index.js file. It defines all tests available in that subsystem - so for files, the commands are add, cat, get etc..

All the tests for files.add are grouped in src/files/add.js, so src/files/index.js creates an object that includes all the tests for each command and calls a function called createSuite.

createSuite turns that object into a function that runs all of the tests (it looks like function (createCommon, options) {}) and also adds each command as a property. What's fun is that the function that createSuite returns (what you get back from src/files/index.js) looks just like the function you get from src/files/add.js so you can use them interchangeably to run ALL tests for files (by calling tests.files()) or just tests for add (by calling tests.files.add()).


BREAKING CHANGE: Consumers of this test suite now have fine grained control over what tests are run. Tests can now be skipped and "onlyed" (run only specific tests). This can be done on a test, command and sub-system level. See the updated usage guide for instructions.

This means that tests skips depending on implementation (e.g. go/js), environment (e.g. node/browser) or platform (e.g. macOS/linux/windows) that were previously present in this suite have been removed. Consumers of this library should add their own skips based on the implementation that's being tested and the environment/platform that the tests are running on.

The following other breaking changes have been made:

  1. The common object passed to test suites has changed. It must now be a function that returns a common object (same shape and functions as before).
  2. The ipfs.ls tests (not MFS ipfs.files.ls) is now a root level suite. You'll need to import it and use like tests.ls(createCommon) to have those tests run.
  3. The generic suite (an alias to miscellaneous) has been removed.

@alanshaw alanshaw requested a review from daviddias June 8, 2018 14:57
@alanshaw alanshaw changed the title [WIP] feat: modularise tests by command, add tools to skip and only feat: modularise tests by command, add tools to skip and only Jun 8, 2018
@alanshaw alanshaw requested a review from olizilla June 8, 2018 15:01
Copy link
Contributor

@travisperson travisperson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a first long pass. I'll double back and take another look at a bit to refresh my eyes.

})

// This works because dag-cbor will just treat pbNode as a regular object
it.skip('should not put dag-pb node with wrong multicodec', (done) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this skip be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ipfs.dht.provide(new CID(res[0].hash), (err) => {
expect(err).to.not.exist()
done()
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe many of these tests will work anymore as there will never be any peers on the dht to provide too, and there isn't much of a reason to provide to self.

The dht for go-ipfs I know will error if no peers are connected when running provides [0]. Should this be valid, and an issue opened for go-ipfs?

[0] https://github.com/ipfs/go-ipfs/blob/08fb11fa896704d9e081bf64e66fac1f6d9d03dc/core/commands/dht.go#L280

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve run these tests against js-ipfs and js-ipfs-api (talks to go-ipfs) and they all pass, so the tests that could fail might already be skipped...I’ll check and get back to you

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DHT tests are completely commented out in js-ipfs currently 😟 https://github.com/ipfs/js-ipfs/blob/master/test/core/interface/dht.js

Some tests in js-ipfs-api were already skipped, but not any provide tests. I will investigate further...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are passing for me for js-ipfs-api:

screen shot 2018-06-25 at 11 31 32

Is it because they are providing to the bootstrap nodes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Historically there were only a couple of DHT tests. The suite was increased with #288 which led me to found some issues with the http-api.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weirdly, these tests just started failing for me. I'll fix.

const it = getIt(options)
const common = createCommon()

describe('.swarm', function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is .swarm.peers right?

expect(err).to.not.exist()
ipfs = nodes[0]
done()
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have an extra node

spawnNodes(2, factory, (err, nodes) => {
expect(err).to.not.exist()
ipfs = nodes[0]
done()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have an extra node?

@alanshaw
Copy link
Contributor Author

Sorry for the late adding of reviewers, I realised that this is a change that'll affect you all so I'd like you to all see it and know what is going on 🚀

@alanshaw alanshaw requested review from dryajov and vmx June 13, 2018 19:33
Copy link
Contributor

@jacobheun jacobheun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to this, it's a great step towards better tests.

const dirtyChai = require('dirty-chai')
const multihash = require('multihashes')
const CID = require('cids')
const Buffer = require('safe-buffer').Buffer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for safe-buffer anymore.

const { getDescribe, getIt } = require('../utils/mocha')

const expect = chai.expect
chai.use(dirtyChai)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are going to get describe and it through a local module, better to also pick expect from it, saving 4 lines from each module (and avoiding forgetting about it in the future).

const expect = chai.expect
chai.use(dirtyChai)

const invalidArg = 'this/Is/So/Invalid/'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move it inside the module.exports

module.exports = (createCommon, options) => {
const describe = getDescribe(options)
const it = getIt(options)
const common = createCommon()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does common need to created here, if there are no options for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to create a common object for each command now that they have been split into modules since it tracks nodes that are created so that they can be destroyed when teardown is called.

// Before:
tests.files(common)
// -> calls:
tests.files.add(common) // teardown destroys nodes created for files.add
tests.files.get(common) // teardown destroys nodes created for files.add AND files.get - ruh roh!
// etc.

// Now:
tests.files(createCommon)
// -> calls:
tests.files.add(createCommon) // teardown destroys nodes created for files.add
tests.files.get(createCommon) // teardown destroys nodes created for files.get

The alternative would be to make the common object smarter - to remove nodes from it's nodes array when teardown is called. However I was thinking that if we were to ever run these suites concurrently (please! one day!) then things would break.

From the PR description:

This work is backwards compatible with the previous API so no changes to dependent code needs to be made. I wanted this to be backwards compatible, but quickly ran into an issue where the common object we pass to suites expects to only be used once. I started to pass it to multiple suites and the teardown function was trying to tear down nodes that were already stopped! So, unfortunately breaking change: common should now be a function that creates a common object for use with the tests. I'll submit PR's to js-ipfs (ipfs/js-ipfs#1389) and js-ipfs-api (ipfs-inactive/js-ipfs-http-client#785) to deal with this.

}, done)
})

it('should not put dag-cbor node with wrong multicodec', function (done) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to use function if no special timeout needs to be added.


const chai = require('chai')
const dirtyChai = require('dirty-chai')
const { series, eachSeries } = require('async')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use require('async/series') as a good practice to avoid requiring the whole module into our codebase.

})
})

it('should fail to find other peer if peer does not exist', function (done) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no special timeout, please use a arrow func

it('should fail to find other peer if peer does not exist', function (done) {
nodeA.dht.findpeer('Qmd7qZS4T7xXtsNFdRoK1trfMs5zU94EpokQ9WFtxdPxsZ', (err, peer) => {
expect(err).to.not.exist()
expect(peer).to.be.equal(null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick s/to.be.equal(null)/to.not.exist()

const common = createCommon()

describe('.dht.findprovs', function () {
this.timeout(80 * 1000)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the global timeout in favor of by test timeout.

@vasco-santos
Copy link
Contributor

It seems a really better approach! Thanks @alanshaw

], done)
})

describe('.peers', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to drop this describe as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes! thanks :D

alanshaw added 2 commits June 26, 2018 23:37
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
alanshaw added 19 commits June 26, 2018 23:38
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
BREAKING CHANGE: Consumers of this test suite now have fine grained control over what tests are run. Tests can now be skipped and "onlyed" (run only specific tests). This can be done on a test, command and sub-system level. See the updated usage guide for instructions: https://github.com/ipfs/interface-ipfs-core/blob/master/README.md#usage.

This means that tests skips depending on implementation (e.g. go/js), environment (e.g. node/browser) or platform (e.g. macOS/linux/windows) that were previously present in this suite have been removed. Consumers of this library should add their own skips based on the implementation that's being tested and the environment/platform that the tests are running on.

The following other breaking changes have been made:

1. The common object passed to test suites has changed. It must now be a function that returns a common object (same shape and functions as before).
2. The `ipfs.ls` tests (not MFS `ipfs.files.ls`) is now a root level suite. You'll need to import it and use like `tests.ls(createCommon)` to have those tests run.
3. The `generic` suite (an alias to `miscellaneous`) has been removed.

See #290 for more details.

License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
Added originally in #308

License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
Originally from PR #267

License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
As per original PR #311

License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
@alanshaw alanshaw force-pushed the feat/modular-tests-skip-only branch from 0d50c67 to 7a9c4d0 Compare June 26, 2018 22:39
@alanshaw
Copy link
Contributor Author

I think I've addressed all the feedback now, if there's no further comments then I'll merge this in tomorrow. Thank you and much ❤️ for your time and thoughts.

Copy link
Contributor

@daviddias daviddias left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put it on a boat 🚢 \o/

Copy link
Contributor

@travisperson travisperson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like there is a lingering file js/src/bitswap.js that should be removed.

License: MIT
Signed-off-by: Alan Shaw <alan@tableflip.io>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants