master
go 165 lines 5.07 KB
Raw
1 // Excluded from plan9 (no fsnotify support).
2 //go:build !plan9
3
4 package cli
5
6 import (
7 "fmt"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "regexp"
12 "testing"
13 "time"
14
15 "github.com/ipfs/kubo/config"
16 "github.com/ipfs/kubo/test/cli/harness"
17 "github.com/stretchr/testify/require"
18 )
19
20 func TestIPFSWatch(t *testing.T) {
21 t.Parallel()
22
23 // Build ipfswatch binary once before running parallel subtests.
24 // This avoids race conditions and duplicate builds.
25 h := harness.NewT(t)
26 repoRoot := filepath.Dir(filepath.Dir(filepath.Dir(h.IPFSBin)))
27 ipfswatchBin := filepath.Join(repoRoot, "cmd", "ipfswatch", "ipfswatch")
28
29 if _, err := os.Stat(ipfswatchBin); os.IsNotExist(err) {
30 // -C changes to repo root so go.mod is found
31 cmd := exec.Command("go", "build", "-C", repoRoot, "-o", ipfswatchBin, "./cmd/ipfswatch")
32 out, err := cmd.CombinedOutput()
33 require.NoError(t, err, "failed to build ipfswatch: %s", string(out))
34 }
35
36 t.Run("ipfswatch adds watched files to IPFS", func(t *testing.T) {
37 t.Parallel()
38 h := harness.NewT(t)
39 node := h.NewNode().Init()
40
41 // Create a temp directory to watch
42 watchDir := filepath.Join(h.Dir, "watch")
43 err := os.MkdirAll(watchDir, 0o755)
44 require.NoError(t, err)
45
46 // Start ipfswatch in background
47 result := node.Runner.Run(harness.RunRequest{
48 Path: ipfswatchBin,
49 Args: []string{"--repo", node.Dir, "--path", watchDir},
50 RunFunc: harness.RunFuncStart,
51 })
52 require.NoError(t, result.Err, "ipfswatch should start without error")
53 defer func() {
54 if result.Cmd.Process != nil {
55 _ = result.Cmd.Process.Kill()
56 _, _ = result.Cmd.Process.Wait()
57 }
58 }()
59
60 // Wait for ipfswatch to initialize
61 time.Sleep(2 * time.Second)
62
63 // Check for startup errors
64 stderrStr := result.Stderr.String()
65 require.NotContains(t, stderrStr, "unknown datastore type", "ipfswatch should recognize datastore plugins")
66
67 // Create a test file with unique content based on timestamp
68 testContent := fmt.Sprintf("ipfswatch test content generated at %s", time.Now().Format(time.RFC3339Nano))
69 testFile := filepath.Join(watchDir, "test.txt")
70 err = os.WriteFile(testFile, []byte(testContent), 0o644)
71 require.NoError(t, err)
72
73 // Wait for ipfswatch to process the file and extract CID from log
74 // Log format: "added %s... key: %s"
75 cidPattern := regexp.MustCompile(`added .*/test\.txt\.\.\. key: (\S+)`)
76 var cid string
77 deadline := time.Now().Add(10 * time.Second)
78 for time.Now().Before(deadline) {
79 stderrStr = result.Stderr.String()
80 if matches := cidPattern.FindStringSubmatch(stderrStr); len(matches) > 1 {
81 cid = matches[1]
82 break
83 }
84 time.Sleep(100 * time.Millisecond)
85 }
86 require.NotEmpty(t, cid, "ipfswatch should have added test.txt and logged the CID, got stderr: %s", stderrStr)
87
88 // Kill ipfswatch to release the repo lock
89 if result.Cmd.Process != nil {
90 if err = result.Cmd.Process.Signal(os.Interrupt); err != nil {
91 _ = result.Cmd.Process.Kill()
92 }
93 _, _ = result.Cmd.Process.Wait()
94 }
95
96 // Verify the content matches by reading it back via ipfs cat
97 catRes := node.RunIPFS("cat", "--offline", cid)
98 require.Equal(t, 0, catRes.Cmd.ProcessState.ExitCode(),
99 "ipfs cat should succeed, cid=%s, stderr: %s", cid, catRes.Stderr.String())
100 require.Equal(t, testContent, catRes.Stdout.String(),
101 "content read from IPFS should match what was written")
102 })
103
104 t.Run("ipfswatch loads datastore plugins for pebbleds", func(t *testing.T) {
105 t.Parallel()
106 h := harness.NewT(t)
107 node := h.NewNode().Init()
108
109 // Configure pebbleds as the datastore
110 node.UpdateConfig(func(cfg *config.Config) {
111 cfg.Datastore.Spec = map[string]any{
112 "type": "mount",
113 "mounts": []any{
114 map[string]any{
115 "mountpoint": "/blocks",
116 "path": "blocks",
117 "prefix": "flatfs.datastore",
118 "shardFunc": "/repo/flatfs/shard/v1/next-to-last/2",
119 "sync": true,
120 "type": "flatfs",
121 },
122 map[string]any{
123 "mountpoint": "/",
124 "path": "datastore",
125 "prefix": "pebble.datastore",
126 "type": "pebbleds",
127 },
128 },
129 }
130 })
131
132 // Re-initialize datastore directory for pebbleds
133 // (the repo was initialized with levelds, need to remove it)
134 dsPath := filepath.Join(node.Dir, "datastore")
135 err := os.RemoveAll(dsPath)
136 require.NoError(t, err)
137 err = os.MkdirAll(dsPath, 0o755)
138 require.NoError(t, err)
139
140 // Create a temp directory to watch
141 watchDir := filepath.Join(h.Dir, "watch")
142 err = os.MkdirAll(watchDir, 0o755)
143 require.NoError(t, err)
144
145 // Start ipfswatch in background
146 result := node.Runner.Run(harness.RunRequest{
147 Path: ipfswatchBin,
148 Args: []string{"--repo", node.Dir, "--path", watchDir},
149 RunFunc: harness.RunFuncStart,
150 })
151 require.NoError(t, result.Err, "ipfswatch should start without error")
152 defer func() {
153 if result.Cmd.Process != nil {
154 _ = result.Cmd.Process.Kill()
155 _, _ = result.Cmd.Process.Wait()
156 }
157 }()
158
159 // Wait for ipfswatch to initialize and check for errors
160 time.Sleep(3 * time.Second)
161
162 stderrStr := result.Stderr.String()
163 require.NotContains(t, stderrStr, "unknown datastore type", "ipfswatch should recognize pebbleds datastore plugin")
164 })
165 }