ignore_test.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package ignore
  7. import (
  8. "bytes"
  9. "io/ioutil"
  10. "os"
  11. "path/filepath"
  12. "runtime"
  13. "testing"
  14. )
  15. func TestIgnore(t *testing.T) {
  16. pats := New(true)
  17. err := pats.Load("testdata/.stignore")
  18. if err != nil {
  19. t.Fatal(err)
  20. }
  21. var tests = []struct {
  22. f string
  23. r bool
  24. }{
  25. {"afile", false},
  26. {"bfile", true},
  27. {"cfile", false},
  28. {"dfile", false},
  29. {"efile", true},
  30. {"ffile", true},
  31. {"dir1", false},
  32. {filepath.Join("dir1", "cfile"), true},
  33. {filepath.Join("dir1", "dfile"), false},
  34. {filepath.Join("dir1", "efile"), true},
  35. {filepath.Join("dir1", "ffile"), false},
  36. {"dir2", false},
  37. {filepath.Join("dir2", "cfile"), false},
  38. {filepath.Join("dir2", "dfile"), true},
  39. {filepath.Join("dir2", "efile"), true},
  40. {filepath.Join("dir2", "ffile"), false},
  41. {filepath.Join("dir3"), true},
  42. {filepath.Join("dir3", "afile"), true},
  43. {"lost+found", true},
  44. }
  45. for i, tc := range tests {
  46. if r := pats.Match(tc.f); r != tc.r {
  47. t.Errorf("Incorrect ignoreFile() #%d (%s); E: %v, A: %v", i, tc.f, tc.r, r)
  48. }
  49. }
  50. }
  51. func TestExcludes(t *testing.T) {
  52. stignore := `
  53. !iex2
  54. !ign1/ex
  55. ign1
  56. i*2
  57. !ign2
  58. `
  59. pats := New(true)
  60. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  61. if err != nil {
  62. t.Fatal(err)
  63. }
  64. var tests = []struct {
  65. f string
  66. r bool
  67. }{
  68. {"ign1", true},
  69. {"ign2", true},
  70. {"ibla2", true},
  71. {"iex2", false},
  72. {filepath.Join("ign1", "ign"), true},
  73. {filepath.Join("ign1", "ex"), false},
  74. {filepath.Join("ign1", "iex2"), false},
  75. {filepath.Join("iex2", "ign"), false},
  76. {filepath.Join("foo", "bar", "ign1"), true},
  77. {filepath.Join("foo", "bar", "ign2"), true},
  78. {filepath.Join("foo", "bar", "iex2"), false},
  79. }
  80. for _, tc := range tests {
  81. if r := pats.Match(tc.f); r != tc.r {
  82. t.Errorf("Incorrect match for %s: %v != %v", tc.f, r, tc.r)
  83. }
  84. }
  85. }
  86. func TestBadPatterns(t *testing.T) {
  87. var badPatterns = []string{
  88. "[",
  89. "/[",
  90. "**/[",
  91. "#include nonexistent",
  92. "#include .stignore",
  93. "!#include makesnosense",
  94. }
  95. for _, pat := range badPatterns {
  96. err := New(true).Parse(bytes.NewBufferString(pat), ".stignore")
  97. if err == nil {
  98. t.Errorf("No error for pattern %q", pat)
  99. }
  100. }
  101. }
  102. func TestCaseSensitivity(t *testing.T) {
  103. ign := New(true)
  104. err := ign.Parse(bytes.NewBufferString("test"), ".stignore")
  105. if err != nil {
  106. t.Error(err)
  107. }
  108. match := []string{"test"}
  109. dontMatch := []string{"foo"}
  110. switch runtime.GOOS {
  111. case "darwin", "windows":
  112. match = append(match, "TEST", "Test", "tESt")
  113. default:
  114. dontMatch = append(dontMatch, "TEST", "Test", "tESt")
  115. }
  116. for _, tc := range match {
  117. if !ign.Match(tc) {
  118. t.Errorf("Incorrect match for %q: should be matched", tc)
  119. }
  120. }
  121. for _, tc := range dontMatch {
  122. if ign.Match(tc) {
  123. t.Errorf("Incorrect match for %q: should not be matched", tc)
  124. }
  125. }
  126. }
  127. func TestCaching(t *testing.T) {
  128. fd1, err := ioutil.TempFile("", "")
  129. if err != nil {
  130. t.Fatal(err)
  131. }
  132. fd2, err := ioutil.TempFile("", "")
  133. if err != nil {
  134. t.Fatal(err)
  135. }
  136. defer fd1.Close()
  137. defer fd2.Close()
  138. defer os.Remove(fd1.Name())
  139. defer os.Remove(fd2.Name())
  140. _, err = fd1.WriteString("/x/\n#include " + filepath.Base(fd2.Name()) + "\n")
  141. if err != nil {
  142. t.Fatal(err)
  143. }
  144. fd2.WriteString("/y/\n")
  145. pats := New(true)
  146. err = pats.Load(fd1.Name())
  147. if err != nil {
  148. t.Fatal(err)
  149. }
  150. if pats.matches.len() != 0 {
  151. t.Fatal("Expected empty cache")
  152. }
  153. if len(pats.patterns) != 4 {
  154. t.Fatal("Incorrect number of patterns loaded", len(pats.patterns), "!=", 4)
  155. }
  156. // Cache some outcomes
  157. for _, letter := range []string{"a", "b", "x", "y"} {
  158. pats.Match(letter)
  159. }
  160. if pats.matches.len() != 4 {
  161. t.Fatal("Expected 4 cached results")
  162. }
  163. // Reload file, expect old outcomes to be preserved
  164. err = pats.Load(fd1.Name())
  165. if err != nil {
  166. t.Fatal(err)
  167. }
  168. if pats.matches.len() != 4 {
  169. t.Fatal("Expected 4 cached results")
  170. }
  171. // Modify the include file, expect empty cache
  172. fd2.WriteString("/z/\n")
  173. err = pats.Load(fd1.Name())
  174. if err != nil {
  175. t.Fatal(err)
  176. }
  177. if pats.matches.len() != 0 {
  178. t.Fatal("Expected 0 cached results")
  179. }
  180. // Cache some outcomes again
  181. for _, letter := range []string{"b", "x", "y"} {
  182. pats.Match(letter)
  183. }
  184. // Verify that outcomes preserved on next laod
  185. err = pats.Load(fd1.Name())
  186. if err != nil {
  187. t.Fatal(err)
  188. }
  189. if pats.matches.len() != 3 {
  190. t.Fatal("Expected 3 cached results")
  191. }
  192. // Modify the root file, expect cache to be invalidated
  193. fd1.WriteString("/a/\n")
  194. err = pats.Load(fd1.Name())
  195. if err != nil {
  196. t.Fatal(err)
  197. }
  198. if pats.matches.len() != 0 {
  199. t.Fatal("Expected cache invalidation")
  200. }
  201. // Cache some outcomes again
  202. for _, letter := range []string{"b", "x", "y"} {
  203. pats.Match(letter)
  204. }
  205. // Verify that outcomes provided on next laod
  206. err = pats.Load(fd1.Name())
  207. if err != nil {
  208. t.Fatal(err)
  209. }
  210. if pats.matches.len() != 3 {
  211. t.Fatal("Expected 3 cached results")
  212. }
  213. }
  214. func TestCommentsAndBlankLines(t *testing.T) {
  215. stignore := `
  216. // foo
  217. //bar
  218. //!baz
  219. //#dex
  220. // ips
  221. `
  222. pats := New(true)
  223. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  224. if err != nil {
  225. t.Error(err)
  226. }
  227. if len(pats.patterns) > 0 {
  228. t.Errorf("Expected no patterns")
  229. }
  230. }
  231. var result bool
  232. func BenchmarkMatch(b *testing.B) {
  233. stignore := `
  234. .frog
  235. .frog*
  236. .frogfox
  237. .whale
  238. .whale/*
  239. .dolphin
  240. .dolphin/*
  241. ~ferret~.*
  242. .ferret.*
  243. flamingo.*
  244. flamingo
  245. *.crow
  246. *.crow
  247. `
  248. pats := New(false)
  249. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  250. if err != nil {
  251. b.Error(err)
  252. }
  253. b.ResetTimer()
  254. for i := 0; i < b.N; i++ {
  255. result = pats.Match("filename")
  256. }
  257. }
  258. func BenchmarkMatchCached(b *testing.B) {
  259. stignore := `
  260. .frog
  261. .frog*
  262. .frogfox
  263. .whale
  264. .whale/*
  265. .dolphin
  266. .dolphin/*
  267. ~ferret~.*
  268. .ferret.*
  269. flamingo.*
  270. flamingo
  271. *.crow
  272. *.crow
  273. `
  274. // Caches per file, hence write the patterns to a file.
  275. fd, err := ioutil.TempFile("", "")
  276. if err != nil {
  277. b.Fatal(err)
  278. }
  279. _, err = fd.WriteString(stignore)
  280. defer fd.Close()
  281. defer os.Remove(fd.Name())
  282. if err != nil {
  283. b.Fatal(err)
  284. }
  285. // Load the patterns
  286. pats := New(true)
  287. err = pats.Load(fd.Name())
  288. if err != nil {
  289. b.Fatal(err)
  290. }
  291. // Cache the outcome for "filename"
  292. pats.Match("filename")
  293. // This load should now load the cached outcomes as the set of patterns
  294. // has not changed.
  295. err = pats.Load(fd.Name())
  296. if err != nil {
  297. b.Fatal(err)
  298. }
  299. b.ResetTimer()
  300. for i := 0; i < b.N; i++ {
  301. result = pats.Match("filename")
  302. }
  303. }
  304. func TestCacheReload(t *testing.T) {
  305. fd, err := ioutil.TempFile("", "")
  306. if err != nil {
  307. t.Fatal(err)
  308. }
  309. defer fd.Close()
  310. defer os.Remove(fd.Name())
  311. // Ignore file matches f1 and f2
  312. _, err = fd.WriteString("f1\nf2\n")
  313. if err != nil {
  314. t.Fatal(err)
  315. }
  316. pats := New(true)
  317. err = pats.Load(fd.Name())
  318. if err != nil {
  319. t.Fatal(err)
  320. }
  321. // Verify that both are ignored
  322. if !pats.Match("f1") {
  323. t.Error("Unexpected non-match for f1")
  324. }
  325. if !pats.Match("f2") {
  326. t.Error("Unexpected non-match for f2")
  327. }
  328. if pats.Match("f3") {
  329. t.Error("Unexpected match for f3")
  330. }
  331. // Rewrite file to match f1 and f3
  332. err = fd.Truncate(0)
  333. if err != nil {
  334. t.Fatal(err)
  335. }
  336. _, err = fd.Seek(0, os.SEEK_SET)
  337. if err != nil {
  338. t.Fatal(err)
  339. }
  340. _, err = fd.WriteString("f1\nf3\n")
  341. if err != nil {
  342. t.Fatal(err)
  343. }
  344. err = pats.Load(fd.Name())
  345. if err != nil {
  346. t.Fatal(err)
  347. }
  348. // Verify that the new patterns are in effect
  349. if !pats.Match("f1") {
  350. t.Error("Unexpected non-match for f1")
  351. }
  352. if pats.Match("f2") {
  353. t.Error("Unexpected match for f2")
  354. }
  355. if !pats.Match("f3") {
  356. t.Error("Unexpected non-match for f3")
  357. }
  358. }
  359. func TestHash(t *testing.T) {
  360. p1 := New(true)
  361. err := p1.Load("testdata/.stignore")
  362. if err != nil {
  363. t.Fatal(err)
  364. }
  365. // Same list of patterns as testdata/.stignore, after expansion
  366. stignore := `
  367. dir2/dfile
  368. dir3
  369. bfile
  370. dir1/cfile
  371. **/efile
  372. /ffile
  373. lost+found
  374. `
  375. p2 := New(true)
  376. err = p2.Parse(bytes.NewBufferString(stignore), ".stignore")
  377. if err != nil {
  378. t.Fatal(err)
  379. }
  380. // Not same list of patterns
  381. stignore = `
  382. dir2/dfile
  383. dir3
  384. bfile
  385. dir1/cfile
  386. /ffile
  387. lost+found
  388. `
  389. p3 := New(true)
  390. err = p3.Parse(bytes.NewBufferString(stignore), ".stignore")
  391. if err != nil {
  392. t.Fatal(err)
  393. }
  394. if p1.Hash() == "" {
  395. t.Error("p1 hash blank")
  396. }
  397. if p2.Hash() == "" {
  398. t.Error("p2 hash blank")
  399. }
  400. if p3.Hash() == "" {
  401. t.Error("p3 hash blank")
  402. }
  403. if p1.Hash() != p2.Hash() {
  404. t.Error("p1-p2 hashes differ")
  405. }
  406. if p1.Hash() == p3.Hash() {
  407. t.Error("p1-p3 hashes same")
  408. }
  409. }
  410. func TestHashOfEmpty(t *testing.T) {
  411. p1 := New(true)
  412. err := p1.Load("testdata/.stignore")
  413. if err != nil {
  414. t.Fatal(err)
  415. }
  416. firstHash := p1.Hash()
  417. // Reloading with a non-existent file should empty the patterns and
  418. // recalculate the hash. d41d8cd98f00b204e9800998ecf8427e is the md5 of
  419. // nothing.
  420. p1.Load("file/does/not/exist")
  421. secondHash := p1.Hash()
  422. if firstHash == secondHash {
  423. t.Error("hash did not change")
  424. }
  425. if secondHash != "d41d8cd98f00b204e9800998ecf8427e" {
  426. t.Error("second hash is not hash of empty string")
  427. }
  428. if len(p1.patterns) != 0 {
  429. t.Error("there are more than zero patterns")
  430. }
  431. }