@cryptotaxi247 / kubo / commits / d4ab5129e

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 '