master
go 254 lines 9.71 KB
Raw
1 package cli
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/ipfs/kubo/test/cli/harness"
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13 )
14
15 func TestLsLongFormat(t *testing.T) {
16 t.Parallel()
17
18 t.Run("long format shows mode and mtime when preserved", func(t *testing.T) {
19 t.Parallel()
20 node := harness.NewT(t).NewNode().Init().StartDaemon()
21 defer node.StopDaemon()
22
23 // Create a test directory structure with known permissions
24 testDir := filepath.Join(node.Dir, "testdata")
25 require.NoError(t, os.MkdirAll(testDir, 0755))
26
27 // Create files with specific permissions
28 file1 := filepath.Join(testDir, "readable.txt")
29 require.NoError(t, os.WriteFile(file1, []byte("hello"), 0644))
30
31 file2 := filepath.Join(testDir, "executable.sh")
32 require.NoError(t, os.WriteFile(file2, []byte("#!/bin/sh\necho hi"), 0755))
33
34 // Set a known mtime in the past (to get year format, avoiding flaky time-based tests)
35 oldTime := time.Date(2020, time.June, 15, 10, 30, 0, 0, time.UTC)
36 require.NoError(t, os.Chtimes(file1, oldTime, oldTime))
37 require.NoError(t, os.Chtimes(file2, oldTime, oldTime))
38
39 // Add with preserved mode and mtime
40 addRes := node.IPFS("add", "-r", "--preserve-mode", "--preserve-mtime", "-Q", testDir)
41 dirCid := addRes.Stdout.Trimmed()
42
43 // Run ls with --long flag
44 lsRes := node.IPFS("ls", "--long", dirCid)
45 output := lsRes.Stdout.String()
46
47 // Verify format: Mode Hash Size ModTime Name
48 lines := strings.Split(strings.TrimSpace(output), "\n")
49 require.Len(t, lines, 2, "expected 2 files in output")
50
51 // Check executable.sh line (should be first alphabetically)
52 assert.Contains(t, lines[0], "-rwxr-xr-x", "executable should have 755 permissions")
53 assert.Contains(t, lines[0], "Jun 15 2020", "should show mtime with year format")
54 assert.Contains(t, lines[0], "executable.sh", "should show filename")
55
56 // Check readable.txt line
57 assert.Contains(t, lines[1], "-rw-r--r--", "readable file should have 644 permissions")
58 assert.Contains(t, lines[1], "Jun 15 2020", "should show mtime with year format")
59 assert.Contains(t, lines[1], "readable.txt", "should show filename")
60 })
61
62 t.Run("long format shows dash for files without preserved mode or mtime", func(t *testing.T) {
63 t.Parallel()
64 node := harness.NewT(t).NewNode().Init().StartDaemon()
65 defer node.StopDaemon()
66
67 // Create and add a file without --preserve-mode or --preserve-mtime
68 testFile := filepath.Join(node.Dir, "nopreserve.txt")
69 require.NoError(t, os.WriteFile(testFile, []byte("test content"), 0644))
70
71 addRes := node.IPFS("add", "-Q", testFile)
72 fileCid := addRes.Stdout.Trimmed()
73
74 // Create a wrapper directory to list
75 node.IPFS("files", "mkdir", "/testdir")
76 node.IPFS("files", "cp", "/ipfs/"+fileCid, "/testdir/file.txt")
77 statRes := node.IPFS("files", "stat", "--hash", "/testdir")
78 dirCid := statRes.Stdout.Trimmed()
79
80 // Run ls with --long flag
81 lsRes := node.IPFS("ls", "--long", dirCid)
82 output := lsRes.Stdout.String()
83
84 // Files without preserved mode or mtime should show "-" for both columns
85 // Format: "-" (mode) <CID> <size> "-" (mtime) <name>
86 assert.Regexp(t, `^-\s+\S+\s+\d+\s+-\s+`, output, "missing mode and mtime should both show dash")
87 })
88
89 t.Run("long format with headers shows correct column order", func(t *testing.T) {
90 t.Parallel()
91 node := harness.NewT(t).NewNode().Init().StartDaemon()
92 defer node.StopDaemon()
93
94 // Create a simple test file
95 testDir := filepath.Join(node.Dir, "headertest")
96 require.NoError(t, os.MkdirAll(testDir, 0755))
97 testFile := filepath.Join(testDir, "file.txt")
98 require.NoError(t, os.WriteFile(testFile, []byte("hello"), 0644))
99
100 oldTime := time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
101 require.NoError(t, os.Chtimes(testFile, oldTime, oldTime))
102
103 addRes := node.IPFS("add", "-r", "--preserve-mode", "--preserve-mtime", "-Q", testDir)
104 dirCid := addRes.Stdout.Trimmed()
105
106 // Run ls with --long and --headers (--size defaults to true)
107 lsRes := node.IPFS("ls", "--long", "--headers", dirCid)
108 output := lsRes.Stdout.String()
109 lines := strings.Split(strings.TrimSpace(output), "\n")
110
111 // First line should be headers in correct order: Mode Hash Size ModTime Name
112 require.GreaterOrEqual(t, len(lines), 2)
113 headerFields := strings.Fields(lines[0])
114 require.Len(t, headerFields, 5, "header should have 5 columns")
115 assert.Equal(t, "Mode", headerFields[0])
116 assert.Equal(t, "Hash", headerFields[1])
117 assert.Equal(t, "Size", headerFields[2])
118 assert.Equal(t, "ModTime", headerFields[3])
119 assert.Equal(t, "Name", headerFields[4])
120
121 // Data line should have matching columns
122 dataFields := strings.Fields(lines[1])
123 require.GreaterOrEqual(t, len(dataFields), 5)
124 assert.Regexp(t, `^-[rwx-]{9}$`, dataFields[0], "first field should be mode")
125 assert.Regexp(t, `^Qm`, dataFields[1], "second field should be CID")
126 assert.Regexp(t, `^\d+$`, dataFields[2], "third field should be size")
127 })
128
129 t.Run("long format with headers and size=false", func(t *testing.T) {
130 t.Parallel()
131 node := harness.NewT(t).NewNode().Init().StartDaemon()
132 defer node.StopDaemon()
133
134 testDir := filepath.Join(node.Dir, "headertest2")
135 require.NoError(t, os.MkdirAll(testDir, 0755))
136 testFile := filepath.Join(testDir, "file.txt")
137 require.NoError(t, os.WriteFile(testFile, []byte("hello"), 0644))
138
139 oldTime := time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
140 require.NoError(t, os.Chtimes(testFile, oldTime, oldTime))
141
142 addRes := node.IPFS("add", "-r", "--preserve-mode", "--preserve-mtime", "-Q", testDir)
143 dirCid := addRes.Stdout.Trimmed()
144
145 // Run ls with --long --headers --size=false
146 lsRes := node.IPFS("ls", "--long", "--headers", "--size=false", dirCid)
147 output := lsRes.Stdout.String()
148 lines := strings.Split(strings.TrimSpace(output), "\n")
149
150 // Header should be: Mode Hash ModTime Name (no Size)
151 require.GreaterOrEqual(t, len(lines), 2)
152 headerFields := strings.Fields(lines[0])
153 require.Len(t, headerFields, 4, "header should have 4 columns without size")
154 assert.Equal(t, "Mode", headerFields[0])
155 assert.Equal(t, "Hash", headerFields[1])
156 assert.Equal(t, "ModTime", headerFields[2])
157 assert.Equal(t, "Name", headerFields[3])
158 })
159
160 t.Run("long format for directories shows trailing slash", func(t *testing.T) {
161 t.Parallel()
162 node := harness.NewT(t).NewNode().Init().StartDaemon()
163 defer node.StopDaemon()
164
165 // Create nested directory structure
166 testDir := filepath.Join(node.Dir, "dirtest")
167 subDir := filepath.Join(testDir, "subdir")
168 require.NoError(t, os.MkdirAll(subDir, 0755))
169 require.NoError(t, os.WriteFile(filepath.Join(subDir, "file.txt"), []byte("hi"), 0644))
170
171 addRes := node.IPFS("add", "-r", "--preserve-mode", "-Q", testDir)
172 dirCid := addRes.Stdout.Trimmed()
173
174 // Run ls with --long flag
175 lsRes := node.IPFS("ls", "--long", dirCid)
176 output := lsRes.Stdout.String()
177
178 // Directory should end with /
179 assert.Contains(t, output, "subdir/", "directory should have trailing slash")
180 // Directory should show 'd' in mode
181 assert.Contains(t, output, "drwxr-xr-x", "directory should show directory mode")
182 })
183
184 t.Run("long format without size flag", func(t *testing.T) {
185 t.Parallel()
186 node := harness.NewT(t).NewNode().Init().StartDaemon()
187 defer node.StopDaemon()
188
189 testDir := filepath.Join(node.Dir, "nosizetest")
190 require.NoError(t, os.MkdirAll(testDir, 0755))
191 testFile := filepath.Join(testDir, "file.txt")
192 require.NoError(t, os.WriteFile(testFile, []byte("hello world"), 0644))
193
194 oldTime := time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
195 require.NoError(t, os.Chtimes(testFile, oldTime, oldTime))
196
197 addRes := node.IPFS("add", "-r", "--preserve-mode", "--preserve-mtime", "-Q", testDir)
198 dirCid := addRes.Stdout.Trimmed()
199
200 // Run ls with --long but --size=false
201 lsRes := node.IPFS("ls", "--long", "--size=false", dirCid)
202 output := lsRes.Stdout.String()
203
204 // Should still have mode and mtime, but format differs (no size column)
205 assert.Contains(t, output, "-rw-r--r--")
206 assert.Contains(t, output, "Jan 01 2020")
207 assert.Contains(t, output, "file.txt")
208 })
209
210 t.Run("long format output is stable", func(t *testing.T) {
211 // This test ensures the output format doesn't change due to refactors
212 t.Parallel()
213 node := harness.NewT(t).NewNode().Init().StartDaemon()
214 defer node.StopDaemon()
215
216 testDir := filepath.Join(node.Dir, "stabletest")
217 require.NoError(t, os.MkdirAll(testDir, 0755))
218 testFile := filepath.Join(testDir, "test.txt")
219 require.NoError(t, os.WriteFile(testFile, []byte("stable"), 0644))
220
221 // Use a fixed time for reproducibility
222 fixedTime := time.Date(2020, time.December, 25, 12, 0, 0, 0, time.UTC)
223 require.NoError(t, os.Chtimes(testFile, fixedTime, fixedTime))
224
225 addRes := node.IPFS("add", "-r", "--preserve-mode", "--preserve-mtime", "-Q", testDir)
226 dirCid := addRes.Stdout.Trimmed()
227
228 // The CID should be deterministic given same content, mode, and mtime
229 // This is the expected CID for this specific test data
230 lsRes := node.IPFS("ls", "--long", dirCid)
231 output := strings.TrimSpace(lsRes.Stdout.String())
232
233 // Verify the format: Mode<tab>Hash<tab>Size<tab>ModTime<tab>Name
234 fields := strings.Fields(output)
235 require.GreaterOrEqual(t, len(fields), 5, "output should have at least 5 fields")
236
237 // Field 0: mode (10 chars, starts with - for regular file)
238 assert.Regexp(t, `^-[rwx-]{9}$`, fields[0], "mode should be Unix permission format")
239
240 // Field 1: CID (starts with Qm or bafy)
241 assert.Regexp(t, `^(Qm|bafy)`, fields[1], "second field should be CID")
242
243 // Field 2: size (numeric)
244 assert.Regexp(t, `^\d+$`, fields[2], "third field should be numeric size")
245
246 // Fields 3-4: date (e.g., "Dec 25 2020" or "Dec 25 12:00")
247 // The date format is "Mon DD YYYY" for old files
248 assert.Equal(t, "Dec", fields[3])
249 assert.Equal(t, "25", fields[4])
250
251 // Last field: filename
252 assert.Equal(t, "test.txt", fields[len(fields)-1])
253 })
254 }