ignore_test.go 9.5 KB

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