Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 09d76e0

Browse files
authored
test: stub core in cli tests (#2798)
Our CLI tests test core functionality, most of which is duplicated by our interface tests, consequently our tests take unnecessarily long to run as the same code paths are being exercised by multiple tests during a test run. This PR refactors the tests to use sinon to stub core methods and refocusses them on making sure we pass the correct arguments to our core APIs. The exceptions to this are where CLI tests run code that is not part of the core interface API, such as `init` and starting/stopping daemons.
1 parent cb4f7ae commit 09d76e0

39 files changed

+2666
-1850
lines changed

packages/ipfs/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,21 +177,22 @@
177177
"clear-module": "^4.0.0",
178178
"cross-env": "^7.0.0",
179179
"delay": "^4.3.0",
180-
"dir-compare": "^1.7.3",
181180
"execa": "^3.0.0",
182181
"form-data": "^3.0.0",
183182
"go-ipfs-dep": "0.4.23-3",
184183
"hat": "0.0.3",
185184
"interface-ipfs-core": "^0.132.0",
186185
"ipfs-interop": "^1.0.0",
187186
"ipfsd-ctl": "^3.0.0",
187+
"it-first": "^1.0.1",
188188
"ncp": "^2.0.0",
189189
"p-event": "^4.1.0",
190190
"p-map": "^3.0.0",
191191
"qs": "^6.5.2",
192192
"rimraf": "^3.0.0",
193193
"sinon": "^8.0.4",
194194
"stream-to-promise": "^2.2.0",
195+
"string-argv": "^0.3.1",
195196
"temp-write": "^4.0.0"
196197
},
197198
"optionalDependencies": {

packages/ipfs/src/cli/bin.js

Lines changed: 57 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,86 +17,88 @@ process.once('unhandledRejection', (err) => {
1717

1818
const semver = require('semver')
1919
const pkg = require('../../package.json')
20+
2021
// Check for node version
2122
if (!semver.satisfies(process.versions.node, pkg.engines.node)) {
2223
console.error(`Please update your Node.js version to ${pkg.engines.node}`)
2324
process.exit(1)
2425
}
2526

2627
const updateNotifier = require('update-notifier')
27-
const debug = require('debug')('ipfs:cli')
2828
const { InvalidRepoVersionError } = require('ipfs-repo/src/errors/index')
2929
const { NotEnabledError } = require('../core/errors')
30-
const parser = require('./parser')
31-
32-
const commandAlias = require('./command-alias')
3330
const { print, getIpfs, getRepoPath } = require('./utils')
31+
const debug = require('debug')('ipfs:cli')
32+
const cli = require('./')
3433

3534
// Check if an update is available and notify
3635
const oneWeek = 1000 * 60 * 60 * 24 * 7
3736
updateNotifier({ pkg, updateCheckInterval: oneWeek }).notify()
3837

39-
// Apply command aliasing (eg `refs local` -> `refs-local`)
40-
const args = commandAlias(process.argv.slice(2))
41-
const repoPath = getRepoPath()
38+
async function main () {
39+
let exitCode = 0
40+
let ctx = {
41+
print,
42+
getStdin: () => process.stdin,
43+
repoPath: getRepoPath(),
44+
cleanup: () => {}
45+
}
4246

43-
let ctx = {
44-
print,
45-
repoPath,
46-
cleanup: () => {},
47-
getStdin: () => process.stdin
48-
}
49-
parser
50-
.middleware(async (argv) => {
51-
if (!['daemon', 'init'].includes(argv._[0])) {
52-
const { ipfs, isDaemon, cleanup } = await getIpfs(argv)
53-
ctx = {
54-
print,
55-
repoPath,
56-
ipfs,
57-
isDaemon,
58-
cleanup,
59-
getStdin: ctx.getStdin
47+
const command = process.argv.slice(2)
48+
49+
try {
50+
const data = await cli(command, async (argv) => {
51+
if (!['daemon', 'init'].includes(command[0])) {
52+
const { ipfs, isDaemon, cleanup } = await getIpfs(argv)
53+
54+
ctx = {
55+
...ctx,
56+
ipfs,
57+
isDaemon,
58+
cleanup
59+
}
6060
}
61-
}
6261

63-
argv.ctx = ctx
64-
return argv
65-
})
66-
.onFinishCommand(async (data) => {
62+
argv.ctx = ctx
63+
64+
return argv
65+
})
66+
6767
if (data) {
6868
print(data)
6969
}
70-
71-
await ctx.cleanup()
72-
})
73-
.fail(async (msg, err, yargs) => {
74-
// Handle yargs errors
75-
if (msg) {
76-
yargs.showHelp()
77-
print.error('\n')
78-
print.error(`Error: ${msg}`)
70+
} catch (err) {
71+
if (err.code === InvalidRepoVersionError.code) {
72+
err.message = 'Incompatible repo version. Migration needed. Pass --migrate for automatic migration'
7973
}
8074

81-
// Handle commands handler errors
82-
if (err) {
83-
if (err.code === InvalidRepoVersionError.code) {
84-
err.message = 'Incompatible repo version. Migration needed. Pass --migrate for automatic migration'
85-
}
86-
87-
if (err.code === NotEnabledError.code) {
88-
err.message = `no IPFS repo found in ${getRepoPath()}.\nplease run: 'ipfs init'`
89-
}
75+
if (err.code === NotEnabledError.code) {
76+
err.message = `no IPFS repo found in ${ctx.repoPath}.\nplease run: 'ipfs init'`
77+
}
9078

91-
if (debug.enabled) {
92-
debug(err)
93-
} else {
94-
print.error(err.message)
95-
}
79+
// Handle yargs errors
80+
if (err.code === 'ERR_YARGS') {
81+
err.yargs.showHelp()
82+
ctx.print.error('\n')
83+
ctx.print.error(`Error: ${err.message}`)
84+
} else if (debug.enabled) {
85+
// Handle commands handler errors
86+
debug(err)
87+
} else {
88+
ctx.print.error(err.message)
9689
}
9790

91+
exitCode = 1
92+
} finally {
9893
await ctx.cleanup()
94+
}
9995

100-
process.exit(1)
101-
})
102-
.parse(args)
96+
if (command[0] === 'daemon' && exitCode === 0) {
97+
// don't shut down the daemon process
98+
return
99+
}
100+
101+
process.exit(exitCode)
102+
}
103+
104+
main()

packages/ipfs/src/cli/commands/add.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ module.exports = {
187187
preload: argv.preload,
188188
nonatomic: argv.nonatomic,
189189
fileImportConcurrency: argv.fileImportConcurrency,
190-
blockWriteConcurrency: argv.blockWriteConcurrency
190+
blockWriteConcurrency: argv.blockWriteConcurrency,
191+
progress: () => {}
191192
}
192193

193194
if (options.enableShardingExperiment && isDaemon) {
@@ -203,9 +204,9 @@ module.exports = {
203204

204205
if (argv.progress && argv.file) {
205206
const totalBytes = await getTotalBytes(argv.file)
206-
bar = createProgressBar(totalBytes)
207+
bar = createProgressBar(totalBytes, print)
207208

208-
if (process.stdout.isTTY) {
209+
if (print.isTTY) {
209210
// bar.interrupt uses clearLine and cursorTo methods that are only on TTYs
210211
log = bar.interrupt.bind(bar)
211212
}

packages/ipfs/src/cli/commands/cat.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ module.exports = {
1919
},
2020

2121
async handler ({ ctx, ipfsPath, offset, length }) {
22-
const { ipfs } = ctx
22+
const { ipfs, print } = ctx
2323

2424
for await (const buf of ipfs.cat(ipfsPath, { offset, length })) {
25-
process.stdout.write(buf)
25+
print.write(buf)
2626
}
2727
}
2828
}

packages/ipfs/src/cli/commands/config/profile/apply.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ module.exports = {
1010
builder: {
1111
'dry-run': {
1212
type: 'boolean',
13-
describe: 'print difference between the current config and the config that would be generated.'
13+
describe: 'print difference between the current config and the config that would be generated.',
14+
default: false
1415
}
1516
},
1617

packages/ipfs/src/cli/commands/id.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,27 @@ module.exports = {
88
builder: {
99
format: {
1010
alias: 'f',
11-
type: 'string'
11+
type: 'string',
12+
describe: 'Print Node ID info in the given format. Allowed tokens: <id> <aver> <pver> <pubkey> <addrs>'
1213
}
1314
},
1415

15-
async handler ({ ctx }) {
16-
const { ipfs } = ctx
16+
async handler ({ ctx, format }) {
17+
const { ipfs, print } = ctx
1718
const id = await ipfs.id()
18-
return JSON.stringify(id, '', 2)
19+
20+
if (format) {
21+
print(format
22+
.replace('<id>', id.id)
23+
.replace('<aver>', id.agentVersion)
24+
.replace('<pver>', id.protocolVersion)
25+
.replace('<pubkey>', id.publicKey)
26+
.replace('<addrs>', (id.addresses || []).map(addr => addr.toString()).join('\n'))
27+
)
28+
29+
return
30+
}
31+
32+
print(JSON.stringify(id, '', 2))
1933
}
2034
}

packages/ipfs/src/cli/commands/name/publish.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ module.exports = {
3131
}
3232
},
3333

34-
async handler (argv) {
35-
const { ipfs, print } = argv.ctx
36-
const result = await ipfs.name.publish(argv.ipfsPath, argv)
34+
async handler ({ ctx, ipfsPath, resolve, lifetime, key, ttl }) {
35+
const { ipfs, print } = ctx
36+
const result = await ipfs.name.publish(ipfsPath, { resolve, lifetime, key, ttl })
3737
print(`Published to ${result.name}: ${result.value}`)
3838
}
3939
}

packages/ipfs/src/cli/index.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict'
2+
3+
const parser = require('./parser')
4+
const commandAlias = require('./command-alias')
5+
const errCode = require('err-code')
6+
7+
module.exports = (command, ctxMiddleware) => {
8+
// Apply command aliasing (eg `refs local` -> `refs-local`)
9+
command = commandAlias(command)
10+
11+
return new Promise((resolve, reject) => {
12+
try {
13+
parser
14+
.middleware(ctxMiddleware)
15+
.onFinishCommand((data) => {
16+
resolve(data)
17+
})
18+
.fail((msg, err, yargs) => {
19+
// Handle yargs errors
20+
if (msg) {
21+
return reject(errCode(new Error(msg), 'ERR_YARGS', { yargs }))
22+
}
23+
24+
reject(err)
25+
})
26+
.parse(command)
27+
} catch (err) {
28+
return reject(err)
29+
}
30+
})
31+
}

packages/ipfs/src/cli/parser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict'
22

3-
const yargs = require('yargs')
3+
const yargs = require('yargs/yargs')(process.argv.slice(2))
44
const mfs = require('ipfs-mfs/cli')
55
const utils = require('./utils')
66

packages/ipfs/src/cli/utils.js

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,34 +26,45 @@ const isDaemonOn = () => {
2626
let visible = true
2727
const disablePrinting = () => { visible = false }
2828

29-
const print = (msg, newline, isError = false) => {
30-
if (newline === undefined) {
31-
newline = true
32-
}
33-
29+
const print = (msg, includeNewline = true, isError = false) => {
3430
if (visible) {
3531
if (msg === undefined) {
3632
msg = ''
3733
}
38-
msg = newline ? msg + '\n' : msg
34+
msg = includeNewline ? msg + '\n' : msg
3935
const outStream = isError ? process.stderr : process.stdout
4036
outStream.write(msg)
4137
}
4238
}
4339

40+
print.clearLine = () => {
41+
return process.stdout.clearLine()
42+
}
43+
44+
print.cursorTo = (pos) => {
45+
process.stdout.cursorTo(pos)
46+
}
47+
48+
print.write = (data) => {
49+
process.stdout.write(data)
50+
}
51+
4452
print.error = (msg, newline) => {
4553
print(msg, newline, true)
4654
}
4755

48-
const createProgressBar = (totalBytes) => {
56+
// used by ipfs.add to interrupt the progress bar
57+
print.isTTY = process.stdout.isTTY
58+
59+
const createProgressBar = (totalBytes, output) => {
4960
const total = byteman(totalBytes, 2, 'MB')
5061
const barFormat = `:progress / ${total} [:bar] :percent :etas`
5162

5263
// 16 MB / 34 MB [=========== ] 48% 5.8s //
5364
return new Progress(barFormat, {
5465
incomplete: ' ',
5566
clear: true,
56-
stream: process.stdout,
67+
stream: output,
5768
total: totalBytes
5869
})
5970
}

0 commit comments

Comments
 (0)