mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 18:37:45 +08:00
Some checks failed
CodeQL / codeql (push) Has been cancelled
Docker Check / lint (push) Has been cancelled
Docker Check / build (push) Has been cancelled
Gateway Conformance / gateway-conformance (push) Has been cancelled
Gateway Conformance / gateway-conformance-libp2p-experiment (push) Has been cancelled
Go Build / go-build (push) Has been cancelled
Go Check / go-check (push) Has been cancelled
Go Lint / go-lint (push) Has been cancelled
Go Test / go-test (push) Has been cancelled
Interop / interop-prep (push) Has been cancelled
Sharness / sharness-test (push) Has been cancelled
Spell Check / spellcheck (push) Has been cancelled
Interop / helia-interop (push) Has been cancelled
Interop / ipfs-webui (push) Has been cancelled
* fix(add): respect Provide config in fast-provide-root fast-provide-root should honor the same config settings as the regular provide system: - skip when Provide.Enabled is false - skip when Provide.DHT.Interval is 0 - respect Provide.Strategy (all/pinned/roots/mfs/combinations) This ensures fast-provide only runs when appropriate based on user configuration and the nature of the content being added (pinned vs unpinned, added to MFS or not). * feat(config): options to adjust global defaults Add Import.FastProvideRoot and Import.FastProvideWait configuration options to control default behavior of fast-provide-root and fast-provide-wait flags in ipfs add command. Users can now set global defaults in config while maintaining per-command flag overrides. - Add Import.FastProvideRoot (default: true) - Add Import.FastProvideWait (default: false) - Add ResolveBoolFromConfig helper for config resolution - Update docs with configuration details - Add log-based tests verifying actual behavior * refactor: extract fast-provide logic into reusable functions Extract fast-provide logic from add command into reusable components: - Add config.ShouldProvideForStrategy helper for strategy matching - Add ExecuteFastProvide function reusable across add and dag import commands - Move DefaultFastProvideTimeout constant to config/provide.go - Simplify add.go from 72 lines to 6 lines for fast-provide - Move fast-provide tests to dedicated TestAddFastProvide function Benefits: - cleaner API: callers only pass content characteristics - all strategy logic centralized in one place - better separation of concerns - easier to add fast-provide to other commands in future * feat(dag): add fast-provide support for dag import Adds --fast-provide-root and --fast-provide-wait flags to `ipfs dag import`, mirroring the fast-provide functionality available in `ipfs add`. Changes: - Add --fast-provide-root and --fast-provide-wait flags to dag import command - Implement fast-provide logic for all root CIDs in imported CAR files - Works even when --pin-roots=false (strategy checked internally) - Share ExecuteFastProvide implementation between add and dag import - Move ExecuteFastProvide to cmdenv package to avoid import cycles - Add logging when fast-provide is disabled - Conditional error handling: return error when wait=true, warn when wait=false - Update config docs to mention both ipfs add and ipfs dag import - Update changelog to use "provide" terminology and include dag import examples - Add comprehensive test coverage (TestDagImportFastProvide with 6 test cases) The fast-provide feature allows immediate DHT announcement of root CIDs for faster content discovery, bypassing the regular background queue. * docs: improve fast-provide documentation Refine documentation to better explain fast-provide and sweep provider working together, and highlight the performance improvement. Changelog: - add fast-provide to sweep provider features list - explain performance improvement: root CIDs discoverable in <1s vs 30+ seconds - note this uses optimistic DHT operations (faster with sweep provider) - simplify examples, point to --help for details Config docs: - fix: --fast-provide-roots should be --fast-provide-root (singular) - clarify Import.FastProvideRoot focuses on root CIDs while sweep handles all blocks - simplify Import.FastProvideWait description Command help: - ipfs add: explain sweep provider context upfront - ipfs dag import: add fast-provide explanation section - both explain the split: fast-provide for roots, sweep for all blocks * test: add tests for ShouldProvideForStrategy add tests covering all provide strategy combinations with focus on bitflag OR logic (the else-if bug fix). organized by behavior: - all strategy always provides - single strategies match only their flag - combined strategies use OR logic - zero strategy never provides * refactor: error cmd on error and wait=true change ExecuteFastProvide() to return error, enabling proper error propagation when --fast-provide-wait=true. in sync mode, provide failures now error the command as expected. in async mode (default), always returns nil with errors logged in background goroutine. also remove duplicate ExecuteFastProvide() from provide.go (75 lines), keeping single implementation in cmdenv/env.go for reuse across add and dag import commands. call sites simplified: - add.go: check and propagate error from ExecuteFastProvide - dag/import.go: return error from ForEach callback, remove confusing conditional error handling semantics: - precondition skips (DHT unavailable, etc): return nil (not failure) - async mode (wait=false): return nil, log errors in goroutine - sync mode (wait=true): return wrapped error on provide failure
192 lines
6.3 KiB
Go
192 lines
6.3 KiB
Go
package config
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestParseProvideStrategy(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expect ProvideStrategy
|
|
}{
|
|
{"all", ProvideStrategyAll},
|
|
{"pinned", ProvideStrategyPinned},
|
|
{"mfs", ProvideStrategyMFS},
|
|
{"pinned+mfs", ProvideStrategyPinned | ProvideStrategyMFS},
|
|
{"invalid", 0},
|
|
{"all+invalid", ProvideStrategyAll},
|
|
{"", ProvideStrategyAll},
|
|
{"flat", ProvideStrategyAll}, // deprecated, maps to "all"
|
|
{"flat+all", ProvideStrategyAll},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := ParseProvideStrategy(tt.input)
|
|
if result != tt.expect {
|
|
t.Errorf("ParseProvideStrategy(%q) = %d, want %d", tt.input, result, tt.expect)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateProvideConfig_Interval(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
interval time.Duration
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{"valid default (22h)", 22 * time.Hour, false, ""},
|
|
{"valid max (48h)", 48 * time.Hour, false, ""},
|
|
{"valid small (1h)", 1 * time.Hour, false, ""},
|
|
{"valid zero (disabled)", 0, false, ""},
|
|
{"invalid over limit (49h)", 49 * time.Hour, true, "must be less than or equal to DHT provider record validity"},
|
|
{"invalid over limit (72h)", 72 * time.Hour, true, "must be less than or equal to DHT provider record validity"},
|
|
{"invalid negative", -1 * time.Hour, true, "must be non-negative"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := &Provide{
|
|
DHT: ProvideDHT{
|
|
Interval: NewOptionalDuration(tt.interval),
|
|
},
|
|
}
|
|
|
|
err := ValidateProvideConfig(cfg)
|
|
|
|
if tt.wantErr {
|
|
require.Error(t, err, "expected error for interval=%v", tt.interval)
|
|
if tt.errMsg != "" {
|
|
assert.Contains(t, err.Error(), tt.errMsg, "error message mismatch")
|
|
}
|
|
} else {
|
|
require.NoError(t, err, "unexpected error for interval=%v", tt.interval)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateProvideConfig_MaxWorkers(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
maxWorkers int64
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{"valid default", 16, false, ""},
|
|
{"valid high", 100, false, ""},
|
|
{"valid low", 1, false, ""},
|
|
{"invalid zero", 0, true, "must be positive"},
|
|
{"invalid negative", -1, true, "must be positive"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := &Provide{
|
|
DHT: ProvideDHT{
|
|
MaxWorkers: NewOptionalInteger(tt.maxWorkers),
|
|
},
|
|
}
|
|
|
|
err := ValidateProvideConfig(cfg)
|
|
|
|
if tt.wantErr {
|
|
require.Error(t, err, "expected error for maxWorkers=%d", tt.maxWorkers)
|
|
if tt.errMsg != "" {
|
|
assert.Contains(t, err.Error(), tt.errMsg, "error message mismatch")
|
|
}
|
|
} else {
|
|
require.NoError(t, err, "unexpected error for maxWorkers=%d", tt.maxWorkers)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShouldProvideForStrategy(t *testing.T) {
|
|
t.Run("all strategy always provides", func(t *testing.T) {
|
|
// ProvideStrategyAll should return true regardless of flags
|
|
testCases := []struct{ pinned, pinnedRoot, mfs bool }{
|
|
{false, false, false},
|
|
{true, true, true},
|
|
{true, false, false},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
assert.True(t, ShouldProvideForStrategy(
|
|
ProvideStrategyAll, tc.pinned, tc.pinnedRoot, tc.mfs))
|
|
}
|
|
})
|
|
|
|
t.Run("single strategies match only their flag", func(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
strategy ProvideStrategy
|
|
pinned, pinnedRoot, mfs bool
|
|
want bool
|
|
}{
|
|
{"pinned: matches when pinned=true", ProvideStrategyPinned, true, false, false, true},
|
|
{"pinned: ignores other flags", ProvideStrategyPinned, false, true, true, false},
|
|
|
|
{"roots: matches when pinnedRoot=true", ProvideStrategyRoots, false, true, false, true},
|
|
{"roots: ignores other flags", ProvideStrategyRoots, true, false, true, false},
|
|
|
|
{"mfs: matches when mfs=true", ProvideStrategyMFS, false, false, true, true},
|
|
{"mfs: ignores other flags", ProvideStrategyMFS, true, true, false, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := ShouldProvideForStrategy(tt.strategy, tt.pinned, tt.pinnedRoot, tt.mfs)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("combined strategies use OR logic (else-if bug fix)", func(t *testing.T) {
|
|
// CRITICAL: Tests the fix where bitflag combinations (pinned+mfs) didn't work
|
|
// because of else-if instead of separate if statements
|
|
tests := []struct {
|
|
name string
|
|
strategy ProvideStrategy
|
|
pinned, pinnedRoot, mfs bool
|
|
want bool
|
|
}{
|
|
// pinned|mfs: provide if EITHER matches
|
|
{"pinned|mfs when pinned", ProvideStrategyPinned | ProvideStrategyMFS, true, false, false, true},
|
|
{"pinned|mfs when mfs", ProvideStrategyPinned | ProvideStrategyMFS, false, false, true, true},
|
|
{"pinned|mfs when both", ProvideStrategyPinned | ProvideStrategyMFS, true, false, true, true},
|
|
{"pinned|mfs when neither", ProvideStrategyPinned | ProvideStrategyMFS, false, false, false, false},
|
|
|
|
// roots|mfs
|
|
{"roots|mfs when root", ProvideStrategyRoots | ProvideStrategyMFS, false, true, false, true},
|
|
{"roots|mfs when mfs", ProvideStrategyRoots | ProvideStrategyMFS, false, false, true, true},
|
|
{"roots|mfs when neither", ProvideStrategyRoots | ProvideStrategyMFS, false, false, false, false},
|
|
|
|
// pinned|roots
|
|
{"pinned|roots when pinned", ProvideStrategyPinned | ProvideStrategyRoots, true, false, false, true},
|
|
{"pinned|roots when root", ProvideStrategyPinned | ProvideStrategyRoots, false, true, false, true},
|
|
{"pinned|roots when neither", ProvideStrategyPinned | ProvideStrategyRoots, false, false, false, false},
|
|
|
|
// triple combination
|
|
{"all-three when any matches", ProvideStrategyPinned | ProvideStrategyRoots | ProvideStrategyMFS, false, false, true, true},
|
|
{"all-three when none match", ProvideStrategyPinned | ProvideStrategyRoots | ProvideStrategyMFS, false, false, false, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := ShouldProvideForStrategy(tt.strategy, tt.pinned, tt.pinnedRoot, tt.mfs)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("zero strategy never provides", func(t *testing.T) {
|
|
assert.False(t, ShouldProvideForStrategy(ProvideStrategy(0), false, false, false))
|
|
assert.False(t, ShouldProvideForStrategy(ProvideStrategy(0), true, true, true))
|
|
})
|
|
}
|