fix(cmd): exit 1 on error (#10903)
* Fix command stream result handling Do not close the stream prematurely in a deferred function. Also, avoid possible shadowing errors. Closes #9007 * test(cli): test/cli/cid_test.go includes regression tests for https://github.com/ipfs/kubo/issues/9007 and better coverage than the old test/sharness/t0290-cid.sh (identified bug in basemoji) * fix: base256emoji in cid bases --prefix changed the character display logic from ASCII range check to unicode.IsPrint() to properly handle Unicode characters including emojis. * docs: changelog --------- Co-authored-by: Marcin Rataj <lidel@lidel.org>
Andrew Gillis committed
Aug 8, 2025 at 17:25 UTC
d4ab5129e92fd681a42db8982b454e03003f9291
6 files changed
+677
-10
core/commands/cid.go
+1
-1
@@ -293,7 +293,7 @@ var basesCmd = &cmds.Command{
293
multibaseSorter{val}.Sort()
294
for _, v := range val {
295
code := v.Code
296
- if code < 32 || code >= 127 {
296
+ if !unicode.IsPrint(rune(code)) {
297
// don't display non-printable prefixes
298
code = ' '
299
}
core/commands/commands.go
+6
-6
@@ -233,12 +233,11 @@ type nonFatalError string
233
// contain non-fatal errors. The helper function is allowed to panic
234
// on internal errors.
235
func streamResult(procVal func(interface{}, io.Writer) nonFatalError) func(cmds.Response, cmds.ResponseEmitter) error {
236
- return func(res cmds.Response, re cmds.ResponseEmitter) (err error) {
236
+ return func(res cmds.Response, re cmds.ResponseEmitter) (rerr error) {
237
defer func() {
238
if r := recover(); r != nil {
239
- err = fmt.Errorf("internal error: %v", r)
239
+ rerr = fmt.Errorf("internal error: %v", r)
240
}
241
- re.Close()
241
}()
242
243
var errors bool
@@ -248,7 +247,8 @@ func streamResult(procVal func(interface{}, io.Writer) nonFatalError) func(cmds.
247
if err == io.EOF {
248
break
249
}
251
- return err
250
+ rerr = err
251
+ return
252
}
253
254
errorMsg := procVal(v, os.Stdout)
@@ -260,8 +260,8 @@ func streamResult(procVal func(interface{}, io.Writer) nonFatalError) func(cmds.
260
}
261
262
if errors {
263
- return fmt.Errorf("errors while displaying some entries")
263
+ rerr = fmt.Errorf("errors while displaying some entries")
264
}
265
- return nil
265
+ return
266
}
267
}
docs/changelogs/v0.37.md
+6
@@ -71,6 +71,12 @@ Kubo has been cleaned up by removing unnecessary dependencies and packages:
71
72
These changes reduce the dependency footprint while improving code maintainability and following Go best practices.
73
74
+#### Improved `ipfs cid`
75
+
76
+Certain `ipfs cid` commands can now be run without a daemon or repository, and return correct exit code 1 on error, making it easier to perform CID conversion in scripts and CI/CD pipelines.
77
+
78
+While at it, we also fixed unicode support in `ipfs cid bases --prefix` to correctly show `base256emoji` 🚀 :-)
79
+
80
#### Deprecated `ipfs stats reprovide`
81
82
The `ipfs stats reprovide` command has moved to `ipfs provide stat`. This was done to organize provider commands in one location.
test/cli/cid_test.go
new
+609
@@ -0,0 +1,609 @@
1
+package cli
2
+
3
+import (
4
+ "fmt"
5
+ "strings"
6
+ "testing"
7
+
8
+ "github.com/ipfs/kubo/test/cli/harness"
9
+ "github.com/stretchr/testify/assert"
10
+)
11
+
12
+func TestCidCommands(t *testing.T) {
13
+ t.Parallel()
14
+
15
+ t.Run("base32", testCidBase32)
16
+ t.Run("format", testCidFormat)
17
+ t.Run("bases", testCidBases)
18
+ t.Run("codecs", testCidCodecs)
19
+ t.Run("hashes", testCidHashes)
20
+}
21
+
22
+// testCidBase32 tests 'ipfs cid base32' subcommand
23
+// Includes regression tests for https://github.com/ipfs/kubo/issues/9007
24
+func testCidBase32(t *testing.T) {
25
+ t.Parallel()
26
+ node := harness.NewT(t).NewNode()
27
+
28
+ t.Run("converts valid CIDs to base32", func(t *testing.T) {
29
+ t.Run("CIDv0 to base32", func(t *testing.T) {
30
+ res := node.RunIPFS("cid", "base32", "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo")
31
+ assert.Equal(t, 0, res.ExitCode())
32
+ assert.Equal(t, "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa\n", res.Stdout.String())
33
+ })
34
+
35
+ t.Run("CIDv1 base58 to base32", func(t *testing.T) {
36
+ res := node.RunIPFS("cid", "base32", "zdj7WgefqQm5HogBQ2bckZuTYYDarRTUZi51GYCnerHD2G86j")
37
+ assert.Equal(t, 0, res.ExitCode())
38
+ assert.Equal(t, "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa\n", res.Stdout.String())
39
+ })
40
+
41
+ t.Run("already base32 CID remains unchanged", func(t *testing.T) {
42
+ res := node.RunIPFS("cid", "base32", "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
43
+ assert.Equal(t, 0, res.ExitCode())
44
+ assert.Equal(t, "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa\n", res.Stdout.String())
45
+ })
46
+
47
+ t.Run("multiple valid CIDs", func(t *testing.T) {
48
+ res := node.RunIPFS("cid", "base32",
49
+ "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo",
50
+ "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
51
+ assert.Equal(t, 0, res.ExitCode())
52
+ assert.Empty(t, res.Stderr.String())
53
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
54
+ assert.Equal(t, 2, len(lines))
55
+ assert.Equal(t, "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa", lines[0])
56
+ assert.Equal(t, "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa", lines[1])
57
+ })
58
+ })
59
+
60
+ t.Run("error handling", func(t *testing.T) {
61
+ // Regression tests for https://github.com/ipfs/kubo/issues/9007
62
+ t.Run("returns error code 1 for single invalid CID", func(t *testing.T) {
63
+ res := node.RunIPFS("cid", "base32", "invalid-cid")
64
+ assert.Equal(t, 1, res.ExitCode())
65
+ assert.Contains(t, res.Stderr.String(), "invalid-cid: invalid cid")
66
+ assert.Contains(t, res.Stderr.String(), "Error: errors while displaying some entries")
67
+ })
68
+
69
+ t.Run("returns error code 1 for mixed valid and invalid CIDs", func(t *testing.T) {
70
+ res := node.RunIPFS("cid", "base32", "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo", "invalid-cid")
71
+ assert.Equal(t, 1, res.ExitCode())
72
+ // Valid CID should be converted and printed to stdout
73
+ assert.Contains(t, res.Stdout.String(), "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
74
+ // Invalid CID error should be printed to stderr
75
+ assert.Contains(t, res.Stderr.String(), "invalid-cid: invalid cid")
76
+ assert.Contains(t, res.Stderr.String(), "Error: errors while displaying some entries")
77
+ })
78
+
79
+ t.Run("returns error code 1 for stdin with invalid CIDs", func(t *testing.T) {
80
+ input := "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo\nbad-cid\nbafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa"
81
+ res := node.RunPipeToIPFS(strings.NewReader(input), "cid", "base32")
82
+ assert.Equal(t, 1, res.ExitCode())
83
+ // Valid CIDs should be converted
84
+ assert.Contains(t, res.Stdout.String(), "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
85
+ // Invalid CID error should be in stderr
86
+ assert.Contains(t, res.Stderr.String(), "bad-cid: invalid cid")
87
+ })
88
+ })
89
+}
90
+
91
+// testCidFormat tests 'ipfs cid format' subcommand
92
+// Includes regression tests for https://github.com/ipfs/kubo/issues/9007
93
+func testCidFormat(t *testing.T) {
94
+ t.Parallel()
95
+ node := harness.NewT(t).NewNode()
96
+
97
+ t.Run("formats CIDs with various options", func(t *testing.T) {
98
+ t.Run("default format preserves CID", func(t *testing.T) {
99
+ res := node.RunIPFS("cid", "format", "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo")
100
+ assert.Equal(t, 0, res.ExitCode())
101
+ assert.Equal(t, "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo\n", res.Stdout.String())
102
+ })
103
+
104
+ t.Run("convert to CIDv1 with base58btc", func(t *testing.T) {
105
+ res := node.RunIPFS("cid", "format", "-v", "1", "-b", "base58btc",
106
+ "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo")
107
+ assert.Equal(t, 0, res.ExitCode())
108
+ assert.Equal(t, "zdj7WgefqQm5HogBQ2bckZuTYYDarRTUZi51GYCnerHD2G86j\n", res.Stdout.String())
109
+ })
110
+
111
+ t.Run("convert to CIDv0", func(t *testing.T) {
112
+ res := node.RunIPFS("cid", "format", "-v", "0",
113
+ "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
114
+ assert.Equal(t, 0, res.ExitCode())
115
+ assert.Equal(t, "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo\n", res.Stdout.String())
116
+ })
117
+
118
+ t.Run("change codec to raw", func(t *testing.T) {
119
+ res := node.RunIPFS("cid", "format", "--mc", "raw", "-b", "base32",
120
+ "bafybeievd6mwe6vcwnkwo3eizs3h7w3a34opszbyfxziqdxguhjw7imdve")
121
+ assert.Equal(t, 0, res.ExitCode())
122
+ assert.Equal(t, "bafkreievd6mwe6vcwnkwo3eizs3h7w3a34opszbyfxziqdxguhjw7imdve\n", res.Stdout.String())
123
+ })
124
+
125
+ t.Run("multiple valid CIDs with format options", func(t *testing.T) {
126
+ res := node.RunIPFS("cid", "format", "-v", "1", "-b", "base58btc",
127
+ "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo",
128
+ "bafybeifgwyq5gs4l2mru5klgwjfmftjvkmbyyjurbupuz2bst7mhmg2hwa")
129
+ assert.Equal(t, 0, res.ExitCode())
130
+ assert.Empty(t, res.Stderr.String())
131
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
132
+ assert.Equal(t, 2, len(lines))
133
+ assert.Equal(t, "zdj7WgefqQm5HogBQ2bckZuTYYDarRTUZi51GYCnerHD2G86j", lines[0])
134
+ assert.Equal(t, "zdj7WgefqQm5HogBQ2bckZuTYYDarRTUZi51GYCnerHD2G86j", lines[1])
135
+ })
136
+ })
137
+
138
+ t.Run("error handling", func(t *testing.T) {
139
+ // Regression tests for https://github.com/ipfs/kubo/issues/9007
140
+ t.Run("returns error code 1 for single invalid CID", func(t *testing.T) {
141
+ res := node.RunIPFS("cid", "format", "not-a-cid")
142
+ assert.Equal(t, 1, res.ExitCode())
143
+ assert.Contains(t, res.Stderr.String(), "not-a-cid: invalid cid")
144
+ assert.Contains(t, res.Stderr.String(), "Error: errors while displaying some entries")
145
+ })
146
+
147
+ t.Run("returns error code 1 for mixed valid and invalid CIDs", func(t *testing.T) {
148
+ res := node.RunIPFS("cid", "format", "not-a-cid", "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo")
149
+ assert.Equal(t, 1, res.ExitCode())
150
+ // Valid CID should be printed to stdout
151
+ assert.Contains(t, res.Stdout.String(), "QmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo")
152
+ // Invalid CID error should be printed to stderr
153
+ assert.Contains(t, res.Stderr.String(), "not-a-cid: invalid cid")
154
+ assert.Contains(t, res.Stderr.String(), "Error: errors while displaying some entries")
155
+ })
156
+
157
+ t.Run("returns error code 1 for stdin with invalid CIDs", func(t *testing.T) {
158
+ input := "invalid\nQmZZRTyhDpL5Jgift1cHbAhexeE1m2Hw8x8g7rTcPahDvo"
159
+ res := node.RunPipeToIPFS(strings.NewReader(input), "cid", "format", "-v", "1", "-b", "base58btc")
160
+ assert.Equal(t, 1, res.ExitCode())
161
+ // Valid CID should be converted
162
+ assert.Contains(t, res.Stdout.String(), "zdj7WgefqQm5HogBQ2bckZuTYYDarRTUZi51GYCnerHD2G86j")
163
+ // Invalid CID error should be in stderr
164
+ assert.Contains(t, res.Stderr.String(), "invalid: invalid cid")
165
+ })
166
+ })
167
+}
168
+
169
+// testCidBases tests 'ipfs cid bases' subcommand
170
+func testCidBases(t *testing.T) {
171
+ t.Parallel()
172
+ node := harness.NewT(t).NewNode()
173
+
174
+ t.Run("lists available bases", func(t *testing.T) {
175
+ // This is a regression test to ensure we don't accidentally add or remove support
176
+ // for multibase encodings. If a new base is intentionally added or removed,
177
+ // this test should be updated accordingly.
178
+ expectedBases := []string{
179
+ "identity",
180
+ "base2",
181
+ "base16",
182
+ "base16upper",
183
+ "base32",
184
+ "base32upper",
185
+ "base32pad",
186
+ "base32padupper",
187
+ "base32hex",
188
+ "base32hexupper",
189
+ "base32hexpad",
190
+ "base32hexpadupper",
191
+ "base36",
192
+ "base36upper",
193
+ "base58btc",
194
+ "base58flickr",
195
+ "base64",
196
+ "base64pad",
197
+ "base64url",
198
+ "base64urlpad",
199
+ "base256emoji",
200
+ }
201
+
202
+ res := node.RunIPFS("cid", "bases")
203
+ assert.Equal(t, 0, res.ExitCode())
204
+
205
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
206
+ assertExactSet(t, "bases", expectedBases, lines)
207
+ })
208
+
209
+ t.Run("with --prefix flag shows single letter prefixes", func(t *testing.T) {
210
+ // Regression test to catch any changes to the output format or supported bases
211
+ expectedLines := []string{
212
+ "identity",
213
+ "0 base2",
214
+ "b base32",
215
+ "B base32upper",
216
+ "c base32pad",
217
+ "C base32padupper",
218
+ "f base16",
219
+ "F base16upper",
220
+ "k base36",
221
+ "K base36upper",
222
+ "m base64",
223
+ "M base64pad",
224
+ "t base32hexpad",
225
+ "T base32hexpadupper",
226
+ "u base64url",
227
+ "U base64urlpad",
228
+ "v base32hex",
229
+ "V base32hexupper",
230
+ "z base58btc",
231
+ "Z base58flickr",
232
+ "🚀 base256emoji",
233
+ }
234
+
235
+ res := node.RunIPFS("cid", "bases", "--prefix")
236
+ assert.Equal(t, 0, res.ExitCode())
237
+
238
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
239
+ assertExactSet(t, "bases --prefix output", expectedLines, lines)
240
+ })
241
+
242
+ t.Run("with --numeric flag shows numeric codes", func(t *testing.T) {
243
+ // Regression test to catch any changes to the output format or supported bases
244
+ expectedLines := []string{
245
+ "0 identity",
246
+ "48 base2",
247
+ "98 base32",
248
+ "66 base32upper",
249
+ "99 base32pad",
250
+ "67 base32padupper",
251
+ "102 base16",
252
+ "70 base16upper",
253
+ "107 base36",
254
+ "75 base36upper",
255
+ "109 base64",
256
+ "77 base64pad",
257
+ "116 base32hexpad",
258
+ "84 base32hexpadupper",
259
+ "117 base64url",
260
+ "85 base64urlpad",
261
+ "118 base32hex",
262
+ "86 base32hexupper",
263
+ "122 base58btc",
264
+ "90 base58flickr",
265
+ "128640 base256emoji",
266
+ }
267
+
268
+ res := node.RunIPFS("cid", "bases", "--numeric")
269
+ assert.Equal(t, 0, res.ExitCode())
270
+
271
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
272
+ assertExactSet(t, "bases --numeric output", expectedLines, lines)
273
+ })
274
+
275
+ t.Run("with both --prefix and --numeric flags", func(t *testing.T) {
276
+ // Regression test to catch any changes to the output format or supported bases
277
+ expectedLines := []string{
278
+ "0 identity",
279
+ "0 48 base2",
280
+ "b 98 base32",
281
+ "B 66 base32upper",
282
+ "c 99 base32pad",
283
+ "C 67 base32padupper",
284
+ "f 102 base16",
285
+ "F 70 base16upper",
286
+ "k 107 base36",
287
+ "K 75 base36upper",
288
+ "m 109 base64",
289
+ "M 77 base64pad",
290
+ "t 116 base32hexpad",
291
+ "T 84 base32hexpadupper",
292
+ "u 117 base64url",
293
+ "U 85 base64urlpad",
294
+ "v 118 base32hex",
295
+ "V 86 base32hexupper",
296
+ "z 122 base58btc",
297
+ "Z 90 base58flickr",
298
+ "🚀 128640 base256emoji",
299
+ }
300
+
301
+ res := node.RunIPFS("cid", "bases", "--prefix", "--numeric")
302
+ assert.Equal(t, 0, res.ExitCode())
303
+
304
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
305
+ assertExactSet(t, "bases --prefix --numeric output", expectedLines, lines)
306
+ })
307
+}
308
+
309
+// testCidCodecs tests 'ipfs cid codecs' subcommand
310
+func testCidCodecs(t *testing.T) {
311
+ t.Parallel()
312
+ node := harness.NewT(t).NewNode()
313
+
314
+ t.Run("lists available codecs", func(t *testing.T) {
315
+ // This is a regression test to ensure we don't accidentally add or remove
316
+ // IPLD codecs. If a codec is intentionally added or removed,
317
+ // this test should be updated accordingly.
318
+ expectedCodecs := []string{
319
+ "cbor",
320
+ "raw",
321
+ "dag-pb",
322
+ "dag-cbor",
323
+ "libp2p-key",
324
+ "git-raw",
325
+ "torrent-info",
326
+ "torrent-file",
327
+ "blake3-hashseq",
328
+ "leofcoin-block",
329
+ "leofcoin-tx",
330
+ "leofcoin-pr",
331
+ "dag-jose",
332
+ "dag-cose",
333
+ "eth-block",
334
+ "eth-block-list",
335
+ "eth-tx-trie",
336
+ "eth-tx",
337
+ "eth-tx-receipt-trie",
338
+ "eth-tx-receipt",
339
+ "eth-state-trie",
340
+ "eth-account-snapshot",
341
+ "eth-storage-trie",
342
+ "eth-receipt-log-trie",
343
+ "eth-receipt-log",
344
+ "bitcoin-block",
345
+ "bitcoin-tx",
346
+ "bitcoin-witness-commitment",
347
+ "zcash-block",
348
+ "zcash-tx",
349
+ "stellar-block",
350
+ "stellar-tx",
351
+ "decred-block",
352
+ "decred-tx",
353
+ "dash-block",
354
+ "dash-tx",
355
+ "swarm-manifest",
356
+ "swarm-feed",
357
+ "beeson",
358
+ "dag-json",
359
+ "swhid-1-snp",
360
+ "json",
361
+ "rdfc-1",
362
+ "json-jcs",
363
+ }
364
+
365
+ res := node.RunIPFS("cid", "codecs")
366
+ assert.Equal(t, 0, res.ExitCode())
367
+
368
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
369
+ assertExactSet(t, "codecs", expectedCodecs, lines)
370
+ })
371
+
372
+ t.Run("with --numeric flag shows codec numbers", func(t *testing.T) {
373
+ // This is a regression test to ensure we don't accidentally add or remove
374
+ // IPLD codecs. If a codec is intentionally added or removed,
375
+ // this test should be updated accordingly.
376
+ expectedLines := []string{
377
+ "81 cbor",
378
+ "85 raw",
379
+ "112 dag-pb",
380
+ "113 dag-cbor",
381
+ "114 libp2p-key",
382
+ "120 git-raw",
383
+ "123 torrent-info",
384
+ "124 torrent-file",
385
+ "128 blake3-hashseq",
386
+ "129 leofcoin-block",
387
+ "130 leofcoin-tx",
388
+ "131 leofcoin-pr",
389
+ "133 dag-jose",
390
+ "134 dag-cose",
391
+ "144 eth-block",
392
+ "145 eth-block-list",
393
+ "146 eth-tx-trie",
394
+ "147 eth-tx",
395
+ "148 eth-tx-receipt-trie",
396
+ "149 eth-tx-receipt",
397
+ "150 eth-state-trie",
398
+ "151 eth-account-snapshot",
399
+ "152 eth-storage-trie",
400
+ "153 eth-receipt-log-trie",
401
+ "154 eth-receipt-log",
402
+ "176 bitcoin-block",
403
+ "177 bitcoin-tx",
404
+ "178 bitcoin-witness-commitment",
405
+ "192 zcash-block",
406
+ "193 zcash-tx",
407
+ "208 stellar-block",
408
+ "209 stellar-tx",
409
+ "224 decred-block",
410
+ "225 decred-tx",
411
+ "240 dash-block",
412
+ "241 dash-tx",
413
+ "250 swarm-manifest",
414
+ "251 swarm-feed",
415
+ "252 beeson",
416
+ "297 dag-json",
417
+ "496 swhid-1-snp",
418
+ "512 json",
419
+ "46083 rdfc-1",
420
+ "46593 json-jcs",
421
+ }
422
+
423
+ res := node.RunIPFS("cid", "codecs", "--numeric")
424
+ assert.Equal(t, 0, res.ExitCode())
425
+
426
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
427
+ assertExactSet(t, "codecs --numeric output", expectedLines, lines)
428
+ })
429
+
430
+ t.Run("with --supported flag lists only supported codecs", func(t *testing.T) {
431
+ // This is a regression test to ensure we don't accidentally change the list
432
+ // of supported codecs. If a codec is intentionally added or removed from
433
+ // support, this test should be updated accordingly.
434
+ expectedSupportedCodecs := []string{
435
+ "cbor",
436
+ "dag-cbor",
437
+ "dag-jose",
438
+ "dag-json",
439
+ "dag-pb",
440
+ "git-raw",
441
+ "json",
442
+ "libp2p-key",
443
+ "raw",
444
+ }
445
+
446
+ res := node.RunIPFS("cid", "codecs", "--supported")
447
+ assert.Equal(t, 0, res.ExitCode())
448
+
449
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
450
+ assertExactSet(t, "supported codecs", expectedSupportedCodecs, lines)
451
+ })
452
+
453
+ t.Run("with both --supported and --numeric flags", func(t *testing.T) {
454
+ // Regression test to catch any changes to supported codecs or output format
455
+ expectedLines := []string{
456
+ "81 cbor",
457
+ "85 raw",
458
+ "112 dag-pb",
459
+ "113 dag-cbor",
460
+ "114 libp2p-key",
461
+ "120 git-raw",
462
+ "133 dag-jose",
463
+ "297 dag-json",
464
+ "512 json",
465
+ }
466
+
467
+ res := node.RunIPFS("cid", "codecs", "--supported", "--numeric")
468
+ assert.Equal(t, 0, res.ExitCode())
469
+
470
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
471
+ assertExactSet(t, "codecs --supported --numeric output", expectedLines, lines)
472
+ })
473
+}
474
+
475
+// testCidHashes tests 'ipfs cid hashes' subcommand
476
+func testCidHashes(t *testing.T) {
477
+ t.Parallel()
478
+ node := harness.NewT(t).NewNode()
479
+
480
+ t.Run("lists available hashes", func(t *testing.T) {
481
+ // This is a regression test to ensure we don't accidentally add or remove
482
+ // support for hash functions. If a hash function is intentionally added
483
+ // or removed, this test should be updated accordingly.
484
+ expectedHashes := []string{
485
+ "identity",
486
+ "sha1",
487
+ "sha2-256",
488
+ "sha2-512",
489
+ "sha3-512",
490
+ "sha3-384",
491
+ "sha3-256",
492
+ "sha3-224",
493
+ "shake-256",
494
+ "keccak-224",
495
+ "keccak-256",
496
+ "keccak-384",
497
+ "keccak-512",
498
+ "blake3",
499
+ "dbl-sha2-256",
500
+ }
501
+
502
+ // Also expect all blake2b variants (160-512 in steps of 8)
503
+ for i := 160; i <= 512; i += 8 {
504
+ expectedHashes = append(expectedHashes, fmt.Sprintf("blake2b-%d", i))
505
+ }
506
+
507
+ // Also expect all blake2s variants (160-256 in steps of 8)
508
+ for i := 160; i <= 256; i += 8 {
509
+ expectedHashes = append(expectedHashes, fmt.Sprintf("blake2s-%d", i))
510
+ }
511
+
512
+ res := node.RunIPFS("cid", "hashes")
513
+ assert.Equal(t, 0, res.ExitCode())
514
+
515
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
516
+ assertExactSet(t, "hash functions", expectedHashes, lines)
517
+ })
518
+
519
+ t.Run("with --numeric flag shows hash function codes", func(t *testing.T) {
520
+ // This is a regression test to ensure we don't accidentally add or remove
521
+ // support for hash functions. If a hash function is intentionally added
522
+ // or removed, this test should be updated accordingly.
523
+ expectedLines := []string{
524
+ "0 identity",
525
+ "17 sha1",
526
+ "18 sha2-256",
527
+ "19 sha2-512",
528
+ "20 sha3-512",
529
+ "21 sha3-384",
530
+ "22 sha3-256",
531
+ "23 sha3-224",
532
+ "25 shake-256",
533
+ "26 keccak-224",
534
+ "27 keccak-256",
535
+ "28 keccak-384",
536
+ "29 keccak-512",
537
+ "30 blake3",
538
+ "86 dbl-sha2-256",
539
+ }
540
+
541
+ // Add all blake2b variants (160-512 in steps of 8)
542
+ for i := 160; i <= 512; i += 8 {
543
+ expectedLines = append(expectedLines, fmt.Sprintf("%d blake2b-%d", 45568+i/8, i))
544
+ }
545
+
546
+ // Add all blake2s variants (160-256 in steps of 8)
547
+ for i := 160; i <= 256; i += 8 {
548
+ expectedLines = append(expectedLines, fmt.Sprintf("%d blake2s-%d", 45632+i/8, i))
549
+ }
550
+
551
+ res := node.RunIPFS("cid", "hashes", "--numeric")
552
+ assert.Equal(t, 0, res.ExitCode())
553
+
554
+ lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n")
555
+ assertExactSet(t, "hashes --numeric output", expectedLines, lines)
556
+ })
557
+}
558
+
559
+// assertExactSet compares expected vs actual items and reports clear errors for any differences.
560
+// This is used as a regression test to ensure we don't accidentally add or remove support.
561
+// Both expected and actual strings are trimmed of whitespace before comparison for maintainability.
562
+func assertExactSet(t *testing.T, itemType string, expected []string, actual []string) {
563
+ t.Helper()
564
+
565
+ // Normalize by trimming whitespace
566
+ normalizedExpected := make([]string, len(expected))
567
+ for i, item := range expected {
568
+ normalizedExpected[i] = strings.TrimSpace(item)
569
+ }
570
+
571
+ normalizedActual := make([]string, len(actual))
572
+ for i, item := range actual {
573
+ normalizedActual[i] = strings.TrimSpace(item)
574
+ }
575
+
576
+ expectedSet := make(map[string]bool)
577
+ for _, item := range normalizedExpected {
578
+ expectedSet[item] = true
579
+ }
580
+
581
+ actualSet := make(map[string]bool)
582
+ for _, item := range normalizedActual {
583
+ actualSet[item] = true
584
+ }
585
+
586
+ var missing []string
587
+ for _, item := range normalizedExpected {
588
+ if !actualSet[item] {
589
+ missing = append(missing, item)
590
+ }
591
+ }
592
+
593
+ var unexpected []string
594
+ for _, item := range normalizedActual {
595
+ if !expectedSet[item] {
596
+ unexpected = append(unexpected, item)
597
+ }
598
+ }
599
+
600
+ if len(missing) > 0 {
601
+ t.Errorf("Missing expected %s: %q", itemType, missing)
602
+ }
603
+ if len(unexpected) > 0 {
604
+ t.Errorf("Unexpected %s found: %q", itemType, unexpected)
605
+ }
606
+
607
+ assert.Equal(t, len(expected), len(actual),
608
+ "Expected %d %s but got %d", len(expected), itemType, len(actual))
609
+}
test/sharness/t0018-indent.sh
+3
@@ -5,6 +5,9 @@ test_description="Test sharness test indent"
5
. lib/test-lib.sh
6
7
for file in $(find .. -name 't*.sh' -type f); do
8
+ if [ "$(basename "$file")" = "t0290-cid.sh" ]; then
9
+ continue
10
+ fi
11
test_expect_success "indent in $file is not using tabs" '
12
test_must_fail grep -P "^ *\t" $file
13
'
test/sharness/t0290-cid.sh
+52
-3
@@ -4,6 +4,11 @@ test_description="Test cid commands"
4
5
. lib/test-lib.sh
6
7
+# NOTE: Primary tests for "ipfs cid" commands are in test/cli/cid_test.go
8
+# These sharness tests are kept for backward compatibility but new tests
9
+# should be added to test/cli/cid_test.go instead. If any of these tests
10
+# break, consider removing them and updating only the test/cli version.
11
+
12
# note: all "ipfs cid" commands should work without requiring a repo
13
14
CIDv0="QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv"
@@ -101,7 +106,7 @@ v 118 base32hex
106
V 86 base32hexupper
107
z 122 base58btc
108
Z 90 base58flickr
104
- 128640 base256emoji
109
+🚀 128640 base256emoji
110
EOF
111
112
cat <<EOF > codecs_expect
@@ -240,13 +245,57 @@ cat <<EOF > hashes_expect
245
EOF
246
247
test_expect_success "cid bases" '
243
- cut -c 12- bases_expect > expect &&
248
+ cat <<-EOF > expect
249
+ identity
250
+ base2
251
+ base32
252
+ base32upper
253
+ base32pad
254
+ base32padupper
255
+ base16
256
+ base16upper
257
+ base36
258
+ base36upper
259
+ base64
260
+ base64pad
261
+ base32hexpad
262
+ base32hexpadupper
263
+ base64url
264
+ base64urlpad
265
+ base32hex
266
+ base32hexupper
267
+ base58btc
268
+ base58flickr
269
+ base256emoji
270
+ EOF
271
ipfs cid bases > actual &&
272
test_cmp expect actual
273
'
274
275
test_expect_success "cid bases --prefix" '
249
- cut -c 1-3,12- bases_expect > expect &&
276
+ cat <<-EOF > expect
277
+ identity
278
+ 0 base2
279
+ b base32
280
+ B base32upper
281
+ c base32pad
282
+ C base32padupper
283
+ f base16
284
+ F base16upper
285
+ k base36
286
+ K base36upper
287
+ m base64
288
+ M base64pad
289
+ t base32hexpad
290
+ T base32hexpadupper
291
+ u base64url
292
+ U base64urlpad
293
+ v base32hex
294
+ V base32hexupper
295
+ z base58btc
296
+ Z base58flickr
297
+ 🚀 base256emoji
298
+ EOF
299
ipfs cid bases --prefix > actual &&
300
test_cmp expect actual
301
'