ignore_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  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. "fmt"
  10. "io/ioutil"
  11. "os"
  12. "path/filepath"
  13. "runtime"
  14. "testing"
  15. )
  16. func TestIgnore(t *testing.T) {
  17. pats := New(true)
  18. err := pats.Load("testdata/.stignore")
  19. if err != nil {
  20. t.Fatal(err)
  21. }
  22. var tests = []struct {
  23. f string
  24. r bool
  25. }{
  26. {"afile", false},
  27. {"bfile", true},
  28. {"cfile", false},
  29. {"dfile", false},
  30. {"efile", true},
  31. {"ffile", true},
  32. {"dir1", false},
  33. {filepath.Join("dir1", "cfile"), true},
  34. {filepath.Join("dir1", "dfile"), false},
  35. {filepath.Join("dir1", "efile"), true},
  36. {filepath.Join("dir1", "ffile"), false},
  37. {"dir2", false},
  38. {filepath.Join("dir2", "cfile"), false},
  39. {filepath.Join("dir2", "dfile"), true},
  40. {filepath.Join("dir2", "efile"), true},
  41. {filepath.Join("dir2", "ffile"), false},
  42. {filepath.Join("dir3"), true},
  43. {filepath.Join("dir3", "afile"), true},
  44. {"lost+found", true},
  45. }
  46. for i, tc := range tests {
  47. if r := pats.Match(tc.f); r.IsIgnored() != tc.r {
  48. t.Errorf("Incorrect ignoreFile() #%d (%s); E: %v, A: %v", i, tc.f, tc.r, r)
  49. }
  50. }
  51. }
  52. func TestExcludes(t *testing.T) {
  53. stignore := `
  54. !iex2
  55. !ign1/ex
  56. ign1
  57. i*2
  58. !ign2
  59. `
  60. pats := New(true)
  61. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  62. if err != nil {
  63. t.Fatal(err)
  64. }
  65. var tests = []struct {
  66. f string
  67. r bool
  68. }{
  69. {"ign1", true},
  70. {"ign2", true},
  71. {"ibla2", true},
  72. {"iex2", false},
  73. {filepath.Join("ign1", "ign"), true},
  74. {filepath.Join("ign1", "ex"), false},
  75. {filepath.Join("ign1", "iex2"), false},
  76. {filepath.Join("iex2", "ign"), false},
  77. {filepath.Join("foo", "bar", "ign1"), true},
  78. {filepath.Join("foo", "bar", "ign2"), true},
  79. {filepath.Join("foo", "bar", "iex2"), false},
  80. }
  81. for _, tc := range tests {
  82. if r := pats.Match(tc.f); r.IsIgnored() != tc.r {
  83. t.Errorf("Incorrect match for %s: %v != %v", tc.f, r, tc.r)
  84. }
  85. }
  86. }
  87. func TestFlagOrder(t *testing.T) {
  88. stignore := `
  89. ## Ok cases
  90. (?i)(?d)!ign1
  91. (?d)(?i)!ign2
  92. (?i)!(?d)ign3
  93. (?d)!(?i)ign4
  94. !(?i)(?d)ign5
  95. !(?d)(?i)ign6
  96. ## Bad cases
  97. !!(?i)(?d)ign7
  98. (?i)(?i)(?d)ign8
  99. (?i)(?d)(?d)!ign9
  100. (?d)(?d)!ign10
  101. `
  102. pats := New(true)
  103. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  104. if err != nil {
  105. t.Fatal(err)
  106. }
  107. for i := 1; i < 7; i++ {
  108. pat := fmt.Sprintf("ign%d", i)
  109. if r := pats.Match(pat); r.IsIgnored() || r.IsDeletable() {
  110. t.Errorf("incorrect %s", pat)
  111. }
  112. }
  113. for i := 7; i < 10; i++ {
  114. pat := fmt.Sprintf("ign%d", i)
  115. if r := pats.Match(pat); r.IsDeletable() {
  116. t.Errorf("incorrect %s", pat)
  117. }
  118. }
  119. if r := pats.Match("(?d)!ign10"); !r.IsIgnored() {
  120. t.Errorf("incorrect")
  121. }
  122. }
  123. func TestDeletables(t *testing.T) {
  124. stignore := `
  125. (?d)ign1
  126. (?d)(?i)ign2
  127. (?i)(?d)ign3
  128. !(?d)ign4
  129. !ign5
  130. !(?i)(?d)ign6
  131. ign7
  132. (?i)ign8
  133. `
  134. pats := New(true)
  135. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  136. if err != nil {
  137. t.Fatal(err)
  138. }
  139. var tests = []struct {
  140. f string
  141. i bool
  142. d bool
  143. }{
  144. {"ign1", true, true},
  145. {"ign2", true, true},
  146. {"ign3", true, true},
  147. {"ign4", false, false},
  148. {"ign5", false, false},
  149. {"ign6", false, false},
  150. {"ign7", true, false},
  151. {"ign8", true, false},
  152. }
  153. for _, tc := range tests {
  154. if r := pats.Match(tc.f); r.IsIgnored() != tc.i || r.IsDeletable() != tc.d {
  155. t.Errorf("Incorrect match for %s: %v != Result{%t, %t}", tc.f, r, tc.i, tc.d)
  156. }
  157. }
  158. }
  159. func TestBadPatterns(t *testing.T) {
  160. var badPatterns = []string{
  161. "[",
  162. "/[",
  163. "**/[",
  164. "#include nonexistent",
  165. "#include .stignore",
  166. "!#include makesnosense",
  167. }
  168. for _, pat := range badPatterns {
  169. err := New(true).Parse(bytes.NewBufferString(pat), ".stignore")
  170. if err == nil {
  171. t.Errorf("No error for pattern %q", pat)
  172. }
  173. }
  174. }
  175. func TestCaseSensitivity(t *testing.T) {
  176. ign := New(true)
  177. err := ign.Parse(bytes.NewBufferString("test"), ".stignore")
  178. if err != nil {
  179. t.Error(err)
  180. }
  181. match := []string{"test"}
  182. dontMatch := []string{"foo"}
  183. switch runtime.GOOS {
  184. case "darwin", "windows":
  185. match = append(match, "TEST", "Test", "tESt")
  186. default:
  187. dontMatch = append(dontMatch, "TEST", "Test", "tESt")
  188. }
  189. for _, tc := range match {
  190. if !ign.Match(tc).IsIgnored() {
  191. t.Errorf("Incorrect match for %q: should be matched", tc)
  192. }
  193. }
  194. for _, tc := range dontMatch {
  195. if ign.Match(tc).IsIgnored() {
  196. t.Errorf("Incorrect match for %q: should not be matched", tc)
  197. }
  198. }
  199. }
  200. func TestCaching(t *testing.T) {
  201. fd1, err := ioutil.TempFile("", "")
  202. if err != nil {
  203. t.Fatal(err)
  204. }
  205. fd2, err := ioutil.TempFile("", "")
  206. if err != nil {
  207. t.Fatal(err)
  208. }
  209. defer fd1.Close()
  210. defer fd2.Close()
  211. defer os.Remove(fd1.Name())
  212. defer os.Remove(fd2.Name())
  213. _, err = fd1.WriteString("/x/\n#include " + filepath.Base(fd2.Name()) + "\n")
  214. if err != nil {
  215. t.Fatal(err)
  216. }
  217. fd2.WriteString("/y/\n")
  218. pats := New(true)
  219. err = pats.Load(fd1.Name())
  220. if err != nil {
  221. t.Fatal(err)
  222. }
  223. if pats.matches.len() != 0 {
  224. t.Fatal("Expected empty cache")
  225. }
  226. // Cache some outcomes
  227. for _, letter := range []string{"a", "b", "x", "y"} {
  228. pats.Match(letter)
  229. }
  230. if pats.matches.len() != 4 {
  231. t.Fatal("Expected 4 cached results")
  232. }
  233. // Reload file, expect old outcomes to be preserved
  234. err = pats.Load(fd1.Name())
  235. if err != nil {
  236. t.Fatal(err)
  237. }
  238. if pats.matches.len() != 4 {
  239. t.Fatal("Expected 4 cached results")
  240. }
  241. // Modify the include file, expect empty cache
  242. fd2.WriteString("/z/\n")
  243. err = pats.Load(fd1.Name())
  244. if err != nil {
  245. t.Fatal(err)
  246. }
  247. if pats.matches.len() != 0 {
  248. t.Fatal("Expected 0 cached results")
  249. }
  250. // Cache some outcomes again
  251. for _, letter := range []string{"b", "x", "y"} {
  252. pats.Match(letter)
  253. }
  254. // Verify that outcomes preserved on next load
  255. err = pats.Load(fd1.Name())
  256. if err != nil {
  257. t.Fatal(err)
  258. }
  259. if pats.matches.len() != 3 {
  260. t.Fatal("Expected 3 cached results")
  261. }
  262. // Modify the root file, expect cache to be invalidated
  263. fd1.WriteString("/a/\n")
  264. err = pats.Load(fd1.Name())
  265. if err != nil {
  266. t.Fatal(err)
  267. }
  268. if pats.matches.len() != 0 {
  269. t.Fatal("Expected cache invalidation")
  270. }
  271. // Cache some outcomes again
  272. for _, letter := range []string{"b", "x", "y"} {
  273. pats.Match(letter)
  274. }
  275. // Verify that outcomes provided on next load
  276. err = pats.Load(fd1.Name())
  277. if err != nil {
  278. t.Fatal(err)
  279. }
  280. if pats.matches.len() != 3 {
  281. t.Fatal("Expected 3 cached results")
  282. }
  283. }
  284. func TestCommentsAndBlankLines(t *testing.T) {
  285. stignore := `
  286. // foo
  287. //bar
  288. //!baz
  289. //#dex
  290. // ips
  291. `
  292. pats := New(true)
  293. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  294. if err != nil {
  295. t.Error(err)
  296. }
  297. if len(pats.patterns) > 0 {
  298. t.Errorf("Expected no patterns")
  299. }
  300. }
  301. var result Result
  302. func BenchmarkMatch(b *testing.B) {
  303. stignore := `
  304. .frog
  305. .frog*
  306. .frogfox
  307. .whale
  308. .whale/*
  309. .dolphin
  310. .dolphin/*
  311. ~ferret~.*
  312. .ferret.*
  313. flamingo.*
  314. flamingo
  315. *.crow
  316. *.crow
  317. `
  318. pats := New(false)
  319. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  320. if err != nil {
  321. b.Error(err)
  322. }
  323. b.ResetTimer()
  324. for i := 0; i < b.N; i++ {
  325. result = pats.Match("filename")
  326. }
  327. }
  328. func BenchmarkMatchCached(b *testing.B) {
  329. stignore := `
  330. .frog
  331. .frog*
  332. .frogfox
  333. .whale
  334. .whale/*
  335. .dolphin
  336. .dolphin/*
  337. ~ferret~.*
  338. .ferret.*
  339. flamingo.*
  340. flamingo
  341. *.crow
  342. *.crow
  343. `
  344. // Caches per file, hence write the patterns to a file.
  345. fd, err := ioutil.TempFile("", "")
  346. if err != nil {
  347. b.Fatal(err)
  348. }
  349. _, err = fd.WriteString(stignore)
  350. defer fd.Close()
  351. defer os.Remove(fd.Name())
  352. if err != nil {
  353. b.Fatal(err)
  354. }
  355. // Load the patterns
  356. pats := New(true)
  357. err = pats.Load(fd.Name())
  358. if err != nil {
  359. b.Fatal(err)
  360. }
  361. // Cache the outcome for "filename"
  362. pats.Match("filename")
  363. // This load should now load the cached outcomes as the set of patterns
  364. // has not changed.
  365. err = pats.Load(fd.Name())
  366. if err != nil {
  367. b.Fatal(err)
  368. }
  369. b.ResetTimer()
  370. for i := 0; i < b.N; i++ {
  371. result = pats.Match("filename")
  372. }
  373. }
  374. func TestCacheReload(t *testing.T) {
  375. fd, err := ioutil.TempFile("", "")
  376. if err != nil {
  377. t.Fatal(err)
  378. }
  379. defer fd.Close()
  380. defer os.Remove(fd.Name())
  381. // Ignore file matches f1 and f2
  382. _, err = fd.WriteString("f1\nf2\n")
  383. if err != nil {
  384. t.Fatal(err)
  385. }
  386. pats := New(true)
  387. err = pats.Load(fd.Name())
  388. if err != nil {
  389. t.Fatal(err)
  390. }
  391. // Verify that both are ignored
  392. if !pats.Match("f1").IsIgnored() {
  393. t.Error("Unexpected non-match for f1")
  394. }
  395. if !pats.Match("f2").IsIgnored() {
  396. t.Error("Unexpected non-match for f2")
  397. }
  398. if pats.Match("f3").IsIgnored() {
  399. t.Error("Unexpected match for f3")
  400. }
  401. // Rewrite file to match f1 and f3
  402. err = fd.Truncate(0)
  403. if err != nil {
  404. t.Fatal(err)
  405. }
  406. _, err = fd.Seek(0, os.SEEK_SET)
  407. if err != nil {
  408. t.Fatal(err)
  409. }
  410. _, err = fd.WriteString("f1\nf3\n")
  411. if err != nil {
  412. t.Fatal(err)
  413. }
  414. err = pats.Load(fd.Name())
  415. if err != nil {
  416. t.Fatal(err)
  417. }
  418. // Verify that the new patterns are in effect
  419. if !pats.Match("f1").IsIgnored() {
  420. t.Error("Unexpected non-match for f1")
  421. }
  422. if pats.Match("f2").IsIgnored() {
  423. t.Error("Unexpected match for f2")
  424. }
  425. if !pats.Match("f3").IsIgnored() {
  426. t.Error("Unexpected non-match for f3")
  427. }
  428. }
  429. func TestHash(t *testing.T) {
  430. p1 := New(true)
  431. err := p1.Load("testdata/.stignore")
  432. if err != nil {
  433. t.Fatal(err)
  434. }
  435. // Same list of patterns as testdata/.stignore, after expansion
  436. stignore := `
  437. dir2/dfile
  438. dir3
  439. bfile
  440. dir1/cfile
  441. **/efile
  442. /ffile
  443. lost+found
  444. `
  445. p2 := New(true)
  446. err = p2.Parse(bytes.NewBufferString(stignore), ".stignore")
  447. if err != nil {
  448. t.Fatal(err)
  449. }
  450. // Not same list of patterns
  451. stignore = `
  452. dir2/dfile
  453. dir3
  454. bfile
  455. dir1/cfile
  456. /ffile
  457. lost+found
  458. `
  459. p3 := New(true)
  460. err = p3.Parse(bytes.NewBufferString(stignore), ".stignore")
  461. if err != nil {
  462. t.Fatal(err)
  463. }
  464. if p1.Hash() == "" {
  465. t.Error("p1 hash blank")
  466. }
  467. if p2.Hash() == "" {
  468. t.Error("p2 hash blank")
  469. }
  470. if p3.Hash() == "" {
  471. t.Error("p3 hash blank")
  472. }
  473. if p1.Hash() != p2.Hash() {
  474. t.Error("p1-p2 hashes differ")
  475. }
  476. if p1.Hash() == p3.Hash() {
  477. t.Error("p1-p3 hashes same")
  478. }
  479. }
  480. func TestHashOfEmpty(t *testing.T) {
  481. p1 := New(true)
  482. err := p1.Load("testdata/.stignore")
  483. if err != nil {
  484. t.Fatal(err)
  485. }
  486. firstHash := p1.Hash()
  487. // Reloading with a non-existent file should empty the patterns and
  488. // recalculate the hash. d41d8cd98f00b204e9800998ecf8427e is the md5 of
  489. // nothing.
  490. p1.Load("file/does/not/exist")
  491. secondHash := p1.Hash()
  492. if firstHash == secondHash {
  493. t.Error("hash did not change")
  494. }
  495. if secondHash != "d41d8cd98f00b204e9800998ecf8427e" {
  496. t.Error("second hash is not hash of empty string")
  497. }
  498. if len(p1.patterns) != 0 {
  499. t.Error("there are more than zero patterns")
  500. }
  501. }
  502. func TestWindowsPatterns(t *testing.T) {
  503. // We should accept patterns as both a/b and a\b and match that against
  504. // both kinds of slash as well.
  505. if runtime.GOOS != "windows" {
  506. t.Skip("Windows specific test")
  507. return
  508. }
  509. stignore := `
  510. a/b
  511. c\d
  512. `
  513. pats := New(true)
  514. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  515. if err != nil {
  516. t.Fatal(err)
  517. }
  518. tests := []string{`a\b`, `c\d`}
  519. for _, pat := range tests {
  520. if !pats.Match(pat).IsIgnored() {
  521. t.Errorf("Should match %s", pat)
  522. }
  523. }
  524. }
  525. func TestAutomaticCaseInsensitivity(t *testing.T) {
  526. // We should do case insensitive matching by default on some platforms.
  527. if runtime.GOOS != "windows" && runtime.GOOS != "darwin" {
  528. t.Skip("Windows/Mac specific test")
  529. return
  530. }
  531. stignore := `
  532. A/B
  533. c/d
  534. `
  535. pats := New(true)
  536. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  537. if err != nil {
  538. t.Fatal(err)
  539. }
  540. tests := []string{`a/B`, `C/d`}
  541. for _, pat := range tests {
  542. if !pats.Match(pat).IsIgnored() {
  543. t.Errorf("Should match %s", pat)
  544. }
  545. }
  546. }
  547. func TestCommas(t *testing.T) {
  548. stignore := `
  549. foo,bar.txt
  550. {baz,quux}.txt
  551. `
  552. pats := New(true)
  553. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  554. if err != nil {
  555. t.Fatal(err)
  556. }
  557. tests := []struct {
  558. name string
  559. match bool
  560. }{
  561. {"foo.txt", false},
  562. {"bar.txt", false},
  563. {"foo,bar.txt", true},
  564. {"baz.txt", true},
  565. {"quux.txt", true},
  566. {"baz,quux.txt", false},
  567. }
  568. for _, tc := range tests {
  569. if pats.Match(tc.name).IsIgnored() != tc.match {
  570. t.Errorf("Match of %s was %v, should be %v", tc.name, !tc.match, tc.match)
  571. }
  572. }
  573. }