fileutil_test.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package fsext
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestGlobWithDoubleStar(t *testing.T) {
  11. t.Run("finds files matching pattern", func(t *testing.T) {
  12. testDir := t.TempDir()
  13. mainGo := filepath.Join(testDir, "src", "main.go")
  14. utilsGo := filepath.Join(testDir, "src", "utils.go")
  15. helperGo := filepath.Join(testDir, "pkg", "helper.go")
  16. readmeMd := filepath.Join(testDir, "README.md")
  17. for _, file := range []string{mainGo, utilsGo, helperGo, readmeMd} {
  18. require.NoError(t, os.MkdirAll(filepath.Dir(file), 0o755))
  19. require.NoError(t, os.WriteFile(file, []byte("test content"), 0o644))
  20. }
  21. matches, truncated, err := GlobWithDoubleStar("**/main.go", testDir, 0)
  22. require.NoError(t, err)
  23. require.False(t, truncated)
  24. require.Equal(t, matches, []string{mainGo})
  25. })
  26. t.Run("finds directories matching pattern", func(t *testing.T) {
  27. testDir := t.TempDir()
  28. srcDir := filepath.Join(testDir, "src")
  29. pkgDir := filepath.Join(testDir, "pkg")
  30. internalDir := filepath.Join(testDir, "internal")
  31. cmdDir := filepath.Join(testDir, "cmd")
  32. pkgFile := filepath.Join(testDir, "pkg.txt")
  33. for _, dir := range []string{srcDir, pkgDir, internalDir, cmdDir} {
  34. require.NoError(t, os.MkdirAll(dir, 0o755))
  35. }
  36. require.NoError(t, os.WriteFile(filepath.Join(srcDir, "main.go"), []byte("package main"), 0o644))
  37. require.NoError(t, os.WriteFile(pkgFile, []byte("test"), 0o644))
  38. matches, truncated, err := GlobWithDoubleStar("pkg", testDir, 0)
  39. require.NoError(t, err)
  40. require.False(t, truncated)
  41. require.Equal(t, matches, []string{pkgDir})
  42. })
  43. t.Run("finds nested directories with wildcard patterns", func(t *testing.T) {
  44. testDir := t.TempDir()
  45. srcPkgDir := filepath.Join(testDir, "src", "pkg")
  46. libPkgDir := filepath.Join(testDir, "lib", "pkg")
  47. mainPkgDir := filepath.Join(testDir, "pkg")
  48. otherDir := filepath.Join(testDir, "other")
  49. for _, dir := range []string{srcPkgDir, libPkgDir, mainPkgDir, otherDir} {
  50. require.NoError(t, os.MkdirAll(dir, 0o755))
  51. }
  52. matches, truncated, err := GlobWithDoubleStar("**/pkg", testDir, 0)
  53. require.NoError(t, err)
  54. require.False(t, truncated)
  55. var relativeMatches []string
  56. for _, match := range matches {
  57. rel, err := filepath.Rel(testDir, match)
  58. require.NoError(t, err)
  59. relativeMatches = append(relativeMatches, filepath.ToSlash(rel))
  60. }
  61. require.ElementsMatch(t, relativeMatches, []string{"pkg", "src/pkg", "lib/pkg"})
  62. })
  63. t.Run("finds directory contents with recursive patterns", func(t *testing.T) {
  64. testDir := t.TempDir()
  65. pkgDir := filepath.Join(testDir, "pkg")
  66. pkgFile1 := filepath.Join(pkgDir, "main.go")
  67. pkgFile2 := filepath.Join(pkgDir, "utils.go")
  68. pkgSubdir := filepath.Join(pkgDir, "internal")
  69. pkgSubfile := filepath.Join(pkgSubdir, "helper.go")
  70. require.NoError(t, os.MkdirAll(pkgSubdir, 0o755))
  71. for _, file := range []string{pkgFile1, pkgFile2, pkgSubfile} {
  72. require.NoError(t, os.WriteFile(file, []byte("package main"), 0o644))
  73. }
  74. matches, truncated, err := GlobWithDoubleStar("pkg/**", testDir, 0)
  75. require.NoError(t, err)
  76. require.False(t, truncated)
  77. var relativeMatches []string
  78. for _, match := range matches {
  79. rel, err := filepath.Rel(testDir, match)
  80. require.NoError(t, err)
  81. relativeMatches = append(relativeMatches, filepath.ToSlash(rel))
  82. }
  83. require.ElementsMatch(t, relativeMatches, []string{
  84. "pkg",
  85. "pkg/main.go",
  86. "pkg/utils.go",
  87. "pkg/internal",
  88. "pkg/internal/helper.go",
  89. })
  90. })
  91. t.Run("respects limit parameter", func(t *testing.T) {
  92. testDir := t.TempDir()
  93. for i := range 10 {
  94. file := filepath.Join(testDir, "file", fmt.Sprintf("test%d.txt", i))
  95. require.NoError(t, os.MkdirAll(filepath.Dir(file), 0o755))
  96. require.NoError(t, os.WriteFile(file, []byte("test"), 0o644))
  97. }
  98. matches, truncated, err := GlobWithDoubleStar("**/*.txt", testDir, 5)
  99. require.NoError(t, err)
  100. require.True(t, truncated, "Expected truncation with limit")
  101. require.Len(t, matches, 5, "Expected exactly 5 matches with limit")
  102. })
  103. t.Run("handles nested directory patterns", func(t *testing.T) {
  104. testDir := t.TempDir()
  105. file1 := filepath.Join(testDir, "a", "b", "c", "file1.txt")
  106. file2 := filepath.Join(testDir, "a", "b", "file2.txt")
  107. file3 := filepath.Join(testDir, "a", "file3.txt")
  108. file4 := filepath.Join(testDir, "file4.txt")
  109. for _, file := range []string{file1, file2, file3, file4} {
  110. require.NoError(t, os.MkdirAll(filepath.Dir(file), 0o755))
  111. require.NoError(t, os.WriteFile(file, []byte("test"), 0o644))
  112. }
  113. matches, truncated, err := GlobWithDoubleStar("a/b/c/file1.txt", testDir, 0)
  114. require.NoError(t, err)
  115. require.False(t, truncated)
  116. require.Equal(t, []string{file1}, matches)
  117. })
  118. t.Run("returns results sorted by modification time (newest first)", func(t *testing.T) {
  119. testDir := t.TempDir()
  120. file1 := filepath.Join(testDir, "file1.txt")
  121. require.NoError(t, os.WriteFile(file1, []byte("first"), 0o644))
  122. file2 := filepath.Join(testDir, "file2.txt")
  123. require.NoError(t, os.WriteFile(file2, []byte("second"), 0o644))
  124. file3 := filepath.Join(testDir, "file3.txt")
  125. require.NoError(t, os.WriteFile(file3, []byte("third"), 0o644))
  126. base := time.Now()
  127. m1 := base
  128. m2 := base.Add(10 * time.Hour)
  129. m3 := base.Add(20 * time.Hour)
  130. require.NoError(t, os.Chtimes(file1, m1, m1))
  131. require.NoError(t, os.Chtimes(file2, m2, m2))
  132. require.NoError(t, os.Chtimes(file3, m3, m3))
  133. matches, truncated, err := GlobWithDoubleStar("*.txt", testDir, 0)
  134. require.NoError(t, err)
  135. require.False(t, truncated)
  136. require.Equal(t, []string{file3, file2, file1}, matches)
  137. })
  138. t.Run("handles empty directory", func(t *testing.T) {
  139. testDir := t.TempDir()
  140. matches, truncated, err := GlobWithDoubleStar("**", testDir, 0)
  141. require.NoError(t, err)
  142. require.False(t, truncated)
  143. // Even empty directories should return the directory itself
  144. require.Equal(t, []string{testDir}, matches)
  145. })
  146. t.Run("handles non-existent search path", func(t *testing.T) {
  147. nonExistentDir := filepath.Join(t.TempDir(), "does", "not", "exist")
  148. matches, truncated, err := GlobWithDoubleStar("**", nonExistentDir, 0)
  149. require.Error(t, err, "Should return error for non-existent search path")
  150. require.False(t, truncated)
  151. require.Empty(t, matches)
  152. })
  153. t.Run("respects basic ignore patterns", func(t *testing.T) {
  154. testDir := t.TempDir()
  155. rootIgnore := filepath.Join(testDir, ".crushignore")
  156. require.NoError(t, os.WriteFile(rootIgnore, []byte("*.tmp\nbackup/\n"), 0o644))
  157. goodFile := filepath.Join(testDir, "good.txt")
  158. require.NoError(t, os.WriteFile(goodFile, []byte("content"), 0o644))
  159. badFile := filepath.Join(testDir, "bad.tmp")
  160. require.NoError(t, os.WriteFile(badFile, []byte("temp content"), 0o644))
  161. goodDir := filepath.Join(testDir, "src")
  162. require.NoError(t, os.MkdirAll(goodDir, 0o755))
  163. ignoredDir := filepath.Join(testDir, "backup")
  164. require.NoError(t, os.MkdirAll(ignoredDir, 0o755))
  165. ignoredFileInDir := filepath.Join(testDir, "backup", "old.txt")
  166. require.NoError(t, os.WriteFile(ignoredFileInDir, []byte("old content"), 0o644))
  167. matches, truncated, err := GlobWithDoubleStar("*.tmp", testDir, 0)
  168. require.NoError(t, err)
  169. require.False(t, truncated)
  170. require.Empty(t, matches, "Expected no matches for '*.tmp' pattern (should be ignored)")
  171. matches, truncated, err = GlobWithDoubleStar("backup", testDir, 0)
  172. require.NoError(t, err)
  173. require.False(t, truncated)
  174. require.Empty(t, matches, "Expected no matches for 'backup' pattern (should be ignored)")
  175. matches, truncated, err = GlobWithDoubleStar("*.txt", testDir, 0)
  176. require.NoError(t, err)
  177. require.False(t, truncated)
  178. require.Equal(t, []string{goodFile}, matches)
  179. })
  180. t.Run("handles mixed file and directory matching with sorting", func(t *testing.T) {
  181. testDir := t.TempDir()
  182. oldestFile := filepath.Join(testDir, "old.rs")
  183. require.NoError(t, os.WriteFile(oldestFile, []byte("old"), 0o644))
  184. middleDir := filepath.Join(testDir, "mid.rs")
  185. require.NoError(t, os.MkdirAll(middleDir, 0o755))
  186. newestFile := filepath.Join(testDir, "new.rs")
  187. require.NoError(t, os.WriteFile(newestFile, []byte("new"), 0o644))
  188. base := time.Now()
  189. tOldest := base
  190. tMiddle := base.Add(10 * time.Hour)
  191. tNewest := base.Add(20 * time.Hour)
  192. // Reverse the expected order
  193. require.NoError(t, os.Chtimes(newestFile, tOldest, tOldest))
  194. require.NoError(t, os.Chtimes(middleDir, tMiddle, tMiddle))
  195. require.NoError(t, os.Chtimes(oldestFile, tNewest, tNewest))
  196. matches, truncated, err := GlobWithDoubleStar("*.rs", testDir, 0)
  197. require.NoError(t, err)
  198. require.False(t, truncated)
  199. require.Len(t, matches, 3)
  200. // Results should be sorted by mod time, but we set the oldestFile
  201. // to have the most recent mod time
  202. require.Equal(t, []string{oldestFile, middleDir, newestFile}, matches)
  203. })
  204. }