-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Expand file tree
/
Copy pathdag_test.go
More file actions
325 lines (280 loc) · 11.2 KB
/
dag_test.go
File metadata and controls
325 lines (280 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
package cli
import (
"encoding/json"
"io"
"os"
"testing"
"time"
"github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/test/cli/harness"
"github.com/ipfs/kubo/test/cli/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
fixtureFile = "./fixtures/TestDagStat.car"
textOutputPath = "./fixtures/TestDagStatExpectedOutput.txt"
node1Cid = "bafyreibmdfd7c5db4kls4ty57zljfhqv36gi43l6txl44pi423wwmeskwy"
node2Cid = "bafyreie3njilzdi4ixumru4nzgecsnjtu7fzfcwhg7e6s4s5i7cnbslvn4"
fixtureCid = "bafyreifrm6uf5o4dsaacuszf35zhibyojlqclabzrms7iak67pf62jygaq"
)
type DagStat struct {
Cid string `json:"Cid"`
Size int `json:"Size"`
NumBlocks int `json:"NumBlocks"`
}
type Data struct {
UniqueBlocks int `json:"UniqueBlocks"`
TotalSize int `json:"TotalSize"`
SharedSize int `json:"SharedSize"`
Ratio float64 `json:"Ratio"`
DagStats []DagStat `json:"DagStats"`
}
// The Fixture file represents a dag where 2 nodes of size = 46B each, have a common child of 7B
// when traversing the DAG from the root's children (node1 and node2) we count (46 + 7)x2 bytes (counting redundant bytes) = 106
// since both nodes share a common child of 7 bytes we actually had to read (46)x2 + 7 = 99 bytes
// we should get a dedup ratio of 106/99 that results in approximately 1.0707071
func TestDag(t *testing.T) {
t.Parallel()
t.Run("ipfs dag stat --enc=json", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init().StartDaemon()
// Import fixture
r, err := os.Open(fixtureFile)
assert.Nil(t, err)
defer r.Close()
err = node.IPFSDagImport(r, fixtureCid)
assert.NoError(t, err)
stat := node.RunIPFS("dag", "stat", "--progress=false", "--enc=json", node1Cid, node2Cid)
var data Data
err = json.Unmarshal(stat.Stdout.Bytes(), &data)
assert.NoError(t, err)
expectedUniqueBlocks := 3
expectedSharedSize := 7
expectedTotalSize := 99
expectedRatio := float64(expectedSharedSize+expectedTotalSize) / float64(expectedTotalSize)
expectedDagStatsLength := 2
// Validate UniqueBlocks
assert.Equal(t, expectedUniqueBlocks, data.UniqueBlocks)
assert.Equal(t, expectedSharedSize, data.SharedSize)
assert.Equal(t, expectedTotalSize, data.TotalSize)
assert.Equal(t, testutils.FloatTruncate(expectedRatio, 4), testutils.FloatTruncate(data.Ratio, 4))
// Validate DagStats
assert.Equal(t, expectedDagStatsLength, len(data.DagStats))
node1Output := data.DagStats[0]
node2Output := data.DagStats[1]
assert.Equal(t, node1Output.Cid, node1Cid)
assert.Equal(t, node2Output.Cid, node2Cid)
expectedNode1Size := (expectedTotalSize + expectedSharedSize) / 2
expectedNode2Size := (expectedTotalSize + expectedSharedSize) / 2
assert.Equal(t, expectedNode1Size, node1Output.Size)
assert.Equal(t, expectedNode2Size, node2Output.Size)
expectedNode1Blocks := 2
expectedNode2Blocks := 2
assert.Equal(t, expectedNode1Blocks, node1Output.NumBlocks)
assert.Equal(t, expectedNode2Blocks, node2Output.NumBlocks)
})
t.Run("ipfs dag stat", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init().StartDaemon()
r, err := os.Open(fixtureFile)
assert.NoError(t, err)
defer r.Close()
f, err := os.Open(textOutputPath)
assert.NoError(t, err)
defer f.Close()
content, err := io.ReadAll(f)
assert.NoError(t, err)
err = node.IPFSDagImport(r, fixtureCid)
assert.NoError(t, err)
stat := node.RunIPFS("dag", "stat", "--progress=false", node1Cid, node2Cid)
assert.Equal(t, content, stat.Stdout.Bytes())
})
t.Run("dag stat deduplicates by multihash", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init().StartDaemon()
// Add content and get CIDv0 with dag-pb (not raw leaves)
cidV0 := node.IPFSAddStr("hello world", "--cid-version=0", "--raw-leaves=false")
// Convert to CIDv1 (same multihash, different CID)
cidV1 := node.IPFS("cid", "format", "-v", "1", "-b", "base32", cidV0).Stdout.Trimmed()
// Run dag stat with both CIDs - should deduplicate by multihash
stat := node.RunIPFS("dag", "stat", "--progress=false", "--enc=json", cidV0, cidV1)
var data Data
err := json.Unmarshal(stat.Stdout.Bytes(), &data)
require.NoError(t, err)
// Same block referenced via CIDv0 and CIDv1 should be counted once
assert.Equal(t, 1, data.UniqueBlocks, "same data via different CIDs should be 1 unique block")
assert.Equal(t, 2.0, data.Ratio, "ratio should be 2.0 (2 refs to 1 block)")
})
}
func TestDagImportFastProvide(t *testing.T) {
t.Parallel()
t.Run("fast-provide-root disabled via config: verify skipped in logs", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init()
node.UpdateConfig(func(cfg *config.Config) {
cfg.Import.FastProvideRoot = config.False
})
// Start daemon with debug logging
node.StartDaemonWithReq(harness.RunRequest{
CmdOpts: []harness.CmdOpt{
harness.RunWithEnv(map[string]string{
"GOLOG_LOG_LEVEL": "error,core/commands=debug,core/commands/cmdenv=debug",
}),
},
}, "")
defer node.StopDaemon()
// Import CAR file
r, err := os.Open(fixtureFile)
require.NoError(t, err)
defer r.Close()
err = node.IPFSDagImport(r, fixtureCid)
require.NoError(t, err)
// Verify fast-provide-root was disabled
daemonLog := node.Daemon.Stderr.String()
require.Contains(t, daemonLog, "fast-provide-root: skipped")
})
t.Run("fast-provide-root enabled with wait=false: verify async provide", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init()
// Use default config (FastProvideRoot=true, FastProvideWait=false)
node.StartDaemonWithReq(harness.RunRequest{
CmdOpts: []harness.CmdOpt{
harness.RunWithEnv(map[string]string{
"GOLOG_LOG_LEVEL": "error,core/commands=debug,core/commands/cmdenv=debug",
}),
},
}, "")
defer node.StopDaemon()
// Import CAR file
r, err := os.Open(fixtureFile)
require.NoError(t, err)
defer r.Close()
err = node.IPFSDagImport(r, fixtureCid)
require.NoError(t, err)
daemonLog := node.Daemon.Stderr
// Should see async mode started
require.Contains(t, daemonLog.String(), "fast-provide-root: enabled")
require.Contains(t, daemonLog.String(), "fast-provide-root: providing asynchronously")
require.Contains(t, daemonLog.String(), fixtureCid) // Should log the specific CID being provided
// Wait for async completion or failure (slightly more than DefaultFastProvideTimeout)
// In test environment with no DHT peers, this will fail with "failed to find any peer in table"
timeout := config.DefaultFastProvideTimeout + time.Second
completedOrFailed := waitForLogMessage(daemonLog, "async provide completed", timeout) ||
waitForLogMessage(daemonLog, "async provide failed", timeout)
require.True(t, completedOrFailed, "async provide should complete or fail within timeout")
})
t.Run("fast-provide-root enabled with wait=true: verify sync provide", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init()
node.UpdateConfig(func(cfg *config.Config) {
cfg.Import.FastProvideWait = config.True
})
node.StartDaemonWithReq(harness.RunRequest{
CmdOpts: []harness.CmdOpt{
harness.RunWithEnv(map[string]string{
"GOLOG_LOG_LEVEL": "error,core/commands=debug,core/commands/cmdenv=debug",
}),
},
}, "")
defer node.StopDaemon()
// Import CAR file - use Run instead of IPFSDagImport to handle expected error
r, err := os.Open(fixtureFile)
require.NoError(t, err)
defer r.Close()
res := node.Runner.Run(harness.RunRequest{
Path: node.IPFSBin,
Args: []string{"dag", "import", "--pin-roots=false"},
CmdOpts: []harness.CmdOpt{
harness.RunWithStdin(r),
},
})
// In sync mode (wait=true), provide errors propagate and fail the command.
// Test environment uses 'test' profile with no bootstrappers, and CI has
// insufficient peers for proper DHT puts, so we expect this to fail with
// "failed to find any peer in table" error from the DHT.
require.Equal(t, 1, res.ExitCode())
require.Contains(t, res.Stderr.String(), "Error: fast-provide: failed to find any peer in table")
daemonLog := node.Daemon.Stderr.String()
// Should see sync mode started
require.Contains(t, daemonLog, "fast-provide-root: enabled")
require.Contains(t, daemonLog, "fast-provide-root: providing synchronously")
require.Contains(t, daemonLog, fixtureCid) // Should log the specific CID being provided
require.Contains(t, daemonLog, "sync provide failed") // Verify the failure was logged
})
t.Run("fast-provide-wait ignored when root disabled", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init()
node.UpdateConfig(func(cfg *config.Config) {
cfg.Import.FastProvideRoot = config.False
cfg.Import.FastProvideWait = config.True
})
node.StartDaemonWithReq(harness.RunRequest{
CmdOpts: []harness.CmdOpt{
harness.RunWithEnv(map[string]string{
"GOLOG_LOG_LEVEL": "error,core/commands=debug,core/commands/cmdenv=debug",
}),
},
}, "")
defer node.StopDaemon()
// Import CAR file
r, err := os.Open(fixtureFile)
require.NoError(t, err)
defer r.Close()
err = node.IPFSDagImport(r, fixtureCid)
require.NoError(t, err)
daemonLog := node.Daemon.Stderr.String()
require.Contains(t, daemonLog, "fast-provide-root: skipped")
// Note: dag import doesn't log wait-flag-ignored like add does
})
t.Run("CLI flag overrides config: flag=true overrides config=false", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init()
node.UpdateConfig(func(cfg *config.Config) {
cfg.Import.FastProvideRoot = config.False
})
node.StartDaemonWithReq(harness.RunRequest{
CmdOpts: []harness.CmdOpt{
harness.RunWithEnv(map[string]string{
"GOLOG_LOG_LEVEL": "error,core/commands=debug,core/commands/cmdenv=debug",
}),
},
}, "")
defer node.StopDaemon()
// Import CAR file with flag override
r, err := os.Open(fixtureFile)
require.NoError(t, err)
defer r.Close()
err = node.IPFSDagImport(r, fixtureCid, "--fast-provide-root=true")
require.NoError(t, err)
daemonLog := node.Daemon.Stderr
// Flag should enable it despite config saying false
require.Contains(t, daemonLog.String(), "fast-provide-root: enabled")
require.Contains(t, daemonLog.String(), "fast-provide-root: providing asynchronously")
require.Contains(t, daemonLog.String(), fixtureCid) // Should log the specific CID being provided
})
t.Run("CLI flag overrides config: flag=false overrides config=true", func(t *testing.T) {
t.Parallel()
node := harness.NewT(t).NewNode().Init()
node.UpdateConfig(func(cfg *config.Config) {
cfg.Import.FastProvideRoot = config.True
})
node.StartDaemonWithReq(harness.RunRequest{
CmdOpts: []harness.CmdOpt{
harness.RunWithEnv(map[string]string{
"GOLOG_LOG_LEVEL": "error,core/commands=debug,core/commands/cmdenv=debug",
}),
},
}, "")
defer node.StopDaemon()
// Import CAR file with flag override
r, err := os.Open(fixtureFile)
require.NoError(t, err)
defer r.Close()
err = node.IPFSDagImport(r, fixtureCid, "--fast-provide-root=false")
require.NoError(t, err)
daemonLog := node.Daemon.Stderr.String()
// Flag should disable it despite config saying true
require.Contains(t, daemonLog, "fast-provide-root: skipped")
})
}