ignore_test.go 21 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055
  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 https://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. "time"
  16. "github.com/syncthing/syncthing/lib/fs"
  17. "github.com/syncthing/syncthing/lib/osutil"
  18. )
  19. func TestIgnore(t *testing.T) {
  20. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"), WithCache(true))
  21. err := pats.Load(".stignore")
  22. if err != nil {
  23. t.Fatal(err)
  24. }
  25. var tests = []struct {
  26. f string
  27. r bool
  28. }{
  29. {"afile", false},
  30. {"bfile", true},
  31. {"cfile", false},
  32. {"dfile", false},
  33. {"efile", true},
  34. {"ffile", true},
  35. {"dir1", false},
  36. {filepath.Join("dir1", "cfile"), true},
  37. {filepath.Join("dir1", "dfile"), false},
  38. {filepath.Join("dir1", "efile"), true},
  39. {filepath.Join("dir1", "ffile"), false},
  40. {"dir2", false},
  41. {filepath.Join("dir2", "cfile"), false},
  42. {filepath.Join("dir2", "dfile"), true},
  43. {filepath.Join("dir2", "efile"), true},
  44. {filepath.Join("dir2", "ffile"), false},
  45. {filepath.Join("dir3"), true},
  46. {filepath.Join("dir3", "afile"), true},
  47. {"lost+found", true},
  48. }
  49. for i, tc := range tests {
  50. if r := pats.Match(tc.f); r.IsIgnored() != tc.r {
  51. t.Errorf("Incorrect ignoreFile() #%d (%s); E: %v, A: %v", i, tc.f, tc.r, r)
  52. }
  53. }
  54. }
  55. func TestExcludes(t *testing.T) {
  56. stignore := `
  57. !iex2
  58. !ign1/ex
  59. ign1
  60. i*2
  61. !ign2
  62. `
  63. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  64. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  65. if err != nil {
  66. t.Fatal(err)
  67. }
  68. var tests = []struct {
  69. f string
  70. r bool
  71. }{
  72. {"ign1", true},
  73. {"ign2", true},
  74. {"ibla2", true},
  75. {"iex2", false},
  76. {filepath.Join("ign1", "ign"), true},
  77. {filepath.Join("ign1", "ex"), false},
  78. {filepath.Join("ign1", "iex2"), false},
  79. {filepath.Join("iex2", "ign"), false},
  80. {filepath.Join("foo", "bar", "ign1"), true},
  81. {filepath.Join("foo", "bar", "ign2"), true},
  82. {filepath.Join("foo", "bar", "iex2"), false},
  83. }
  84. for _, tc := range tests {
  85. if r := pats.Match(tc.f); r.IsIgnored() != tc.r {
  86. t.Errorf("Incorrect match for %s: %v != %v", tc.f, r, tc.r)
  87. }
  88. }
  89. }
  90. func TestFlagOrder(t *testing.T) {
  91. stignore := `
  92. ## Ok cases
  93. (?i)(?d)!ign1
  94. (?d)(?i)!ign2
  95. (?i)!(?d)ign3
  96. (?d)!(?i)ign4
  97. !(?i)(?d)ign5
  98. !(?d)(?i)ign6
  99. ## Bad cases
  100. !!(?i)(?d)ign7
  101. (?i)(?i)(?d)ign8
  102. (?i)(?d)(?d)!ign9
  103. (?d)(?d)!ign10
  104. `
  105. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  106. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  107. if err != nil {
  108. t.Fatal(err)
  109. }
  110. for i := 1; i < 7; i++ {
  111. pat := fmt.Sprintf("ign%d", i)
  112. if r := pats.Match(pat); r.IsIgnored() || r.IsDeletable() {
  113. t.Errorf("incorrect %s", pat)
  114. }
  115. }
  116. for i := 7; i < 10; i++ {
  117. pat := fmt.Sprintf("ign%d", i)
  118. if r := pats.Match(pat); r.IsDeletable() {
  119. t.Errorf("incorrect %s", pat)
  120. }
  121. }
  122. if r := pats.Match("(?d)!ign10"); !r.IsIgnored() {
  123. t.Errorf("incorrect")
  124. }
  125. }
  126. func TestDeletables(t *testing.T) {
  127. stignore := `
  128. (?d)ign1
  129. (?d)(?i)ign2
  130. (?i)(?d)ign3
  131. !(?d)ign4
  132. !ign5
  133. !(?i)(?d)ign6
  134. ign7
  135. (?i)ign8
  136. `
  137. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  138. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  139. if err != nil {
  140. t.Fatal(err)
  141. }
  142. var tests = []struct {
  143. f string
  144. i bool
  145. d bool
  146. }{
  147. {"ign1", true, true},
  148. {"ign2", true, true},
  149. {"ign3", true, true},
  150. {"ign4", false, false},
  151. {"ign5", false, false},
  152. {"ign6", false, false},
  153. {"ign7", true, false},
  154. {"ign8", true, false},
  155. }
  156. for _, tc := range tests {
  157. if r := pats.Match(tc.f); r.IsIgnored() != tc.i || r.IsDeletable() != tc.d {
  158. t.Errorf("Incorrect match for %s: %v != Result{%t, %t}", tc.f, r, tc.i, tc.d)
  159. }
  160. }
  161. }
  162. func TestBadPatterns(t *testing.T) {
  163. var badPatterns = []string{
  164. "[",
  165. "/[",
  166. "**/[",
  167. "#include nonexistent",
  168. "#include .stignore",
  169. }
  170. for _, pat := range badPatterns {
  171. err := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true)).Parse(bytes.NewBufferString(pat), ".stignore")
  172. if err == nil {
  173. t.Errorf("No error for pattern %q", pat)
  174. }
  175. }
  176. }
  177. func TestCaseSensitivity(t *testing.T) {
  178. ign := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  179. err := ign.Parse(bytes.NewBufferString("test"), ".stignore")
  180. if err != nil {
  181. t.Error(err)
  182. }
  183. match := []string{"test"}
  184. dontMatch := []string{"foo"}
  185. switch runtime.GOOS {
  186. case "darwin", "windows":
  187. match = append(match, "TEST", "Test", "tESt")
  188. default:
  189. dontMatch = append(dontMatch, "TEST", "Test", "tESt")
  190. }
  191. for _, tc := range match {
  192. if !ign.Match(tc).IsIgnored() {
  193. t.Errorf("Incorrect match for %q: should be matched", tc)
  194. }
  195. }
  196. for _, tc := range dontMatch {
  197. if ign.Match(tc).IsIgnored() {
  198. t.Errorf("Incorrect match for %q: should not be matched", tc)
  199. }
  200. }
  201. }
  202. func TestCaching(t *testing.T) {
  203. dir, err := ioutil.TempDir("", "")
  204. if err != nil {
  205. t.Fatal(err)
  206. }
  207. fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
  208. fd1, err := osutil.TempFile(fs, "", "")
  209. if err != nil {
  210. t.Fatal(err)
  211. }
  212. fd2, err := osutil.TempFile(fs, "", "")
  213. if err != nil {
  214. t.Fatal(err)
  215. }
  216. defer fd1.Close()
  217. defer fd2.Close()
  218. defer fs.Remove(fd1.Name())
  219. defer fs.Remove(fd2.Name())
  220. _, err = fd1.Write([]byte("/x/\n#include " + filepath.Base(fd2.Name()) + "\n"))
  221. if err != nil {
  222. t.Fatal(err)
  223. }
  224. fd2.Write([]byte("/y/\n"))
  225. pats := New(fs, WithCache(true))
  226. err = pats.Load(fd1.Name())
  227. if err != nil {
  228. t.Fatal(err)
  229. }
  230. if pats.matches.len() != 0 {
  231. t.Fatal("Expected empty cache")
  232. }
  233. // Cache some outcomes
  234. for _, letter := range []string{"a", "b", "x", "y"} {
  235. pats.Match(letter)
  236. }
  237. if pats.matches.len() != 4 {
  238. t.Fatal("Expected 4 cached results")
  239. }
  240. // Reload file, expect old outcomes to be preserved
  241. err = pats.Load(fd1.Name())
  242. if err != nil {
  243. t.Fatal(err)
  244. }
  245. if pats.matches.len() != 4 {
  246. t.Fatal("Expected 4 cached results")
  247. }
  248. // Modify the include file, expect empty cache. Ensure the timestamp on
  249. // the file changes.
  250. fd2.Write([]byte("/z/\n"))
  251. fd2.Sync()
  252. fakeTime := time.Now().Add(5 * time.Second)
  253. fs.Chtimes(fd2.Name(), fakeTime, fakeTime)
  254. err = pats.Load(fd1.Name())
  255. if err != nil {
  256. t.Fatal(err)
  257. }
  258. if pats.matches.len() != 0 {
  259. t.Fatal("Expected 0 cached results")
  260. }
  261. // Cache some outcomes again
  262. for _, letter := range []string{"b", "x", "y"} {
  263. pats.Match(letter)
  264. }
  265. // Verify that outcomes preserved on next load
  266. err = pats.Load(fd1.Name())
  267. if err != nil {
  268. t.Fatal(err)
  269. }
  270. if pats.matches.len() != 3 {
  271. t.Fatal("Expected 3 cached results")
  272. }
  273. // Modify the root file, expect cache to be invalidated
  274. fd1.Write([]byte("/a/\n"))
  275. fd1.Sync()
  276. fakeTime = time.Now().Add(5 * time.Second)
  277. fs.Chtimes(fd1.Name(), fakeTime, fakeTime)
  278. err = pats.Load(fd1.Name())
  279. if err != nil {
  280. t.Fatal(err)
  281. }
  282. if pats.matches.len() != 0 {
  283. t.Fatal("Expected cache invalidation")
  284. }
  285. // Cache some outcomes again
  286. for _, letter := range []string{"b", "x", "y"} {
  287. pats.Match(letter)
  288. }
  289. // Verify that outcomes provided on next load
  290. err = pats.Load(fd1.Name())
  291. if err != nil {
  292. t.Fatal(err)
  293. }
  294. if pats.matches.len() != 3 {
  295. t.Fatal("Expected 3 cached results")
  296. }
  297. }
  298. func TestCommentsAndBlankLines(t *testing.T) {
  299. stignore := `
  300. // foo
  301. //bar
  302. //!baz
  303. //#dex
  304. // ips
  305. `
  306. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  307. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  308. if err != nil {
  309. t.Error(err)
  310. }
  311. if len(pats.patterns) > 0 {
  312. t.Errorf("Expected no patterns")
  313. }
  314. }
  315. var result Result
  316. func BenchmarkMatch(b *testing.B) {
  317. stignore := `
  318. .frog
  319. .frog*
  320. .frogfox
  321. .whale
  322. .whale/*
  323. .dolphin
  324. .dolphin/*
  325. ~ferret~.*
  326. .ferret.*
  327. flamingo.*
  328. flamingo
  329. *.crow
  330. *.crow
  331. `
  332. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."))
  333. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  334. if err != nil {
  335. b.Error(err)
  336. }
  337. b.ResetTimer()
  338. for i := 0; i < b.N; i++ {
  339. result = pats.Match("filename")
  340. }
  341. }
  342. func BenchmarkMatchCached(b *testing.B) {
  343. stignore := `
  344. .frog
  345. .frog*
  346. .frogfox
  347. .whale
  348. .whale/*
  349. .dolphin
  350. .dolphin/*
  351. ~ferret~.*
  352. .ferret.*
  353. flamingo.*
  354. flamingo
  355. *.crow
  356. *.crow
  357. `
  358. // Caches per file, hence write the patterns to a file.
  359. dir, err := ioutil.TempDir("", "")
  360. if err != nil {
  361. b.Fatal(err)
  362. }
  363. fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
  364. fd, err := osutil.TempFile(fs, "", "")
  365. if err != nil {
  366. b.Fatal(err)
  367. }
  368. _, err = fd.Write([]byte(stignore))
  369. defer fd.Close()
  370. defer fs.Remove(fd.Name())
  371. if err != nil {
  372. b.Fatal(err)
  373. }
  374. // Load the patterns
  375. pats := New(fs, WithCache(true))
  376. err = pats.Load(fd.Name())
  377. if err != nil {
  378. b.Fatal(err)
  379. }
  380. // Cache the outcome for "filename"
  381. pats.Match("filename")
  382. // This load should now load the cached outcomes as the set of patterns
  383. // has not changed.
  384. err = pats.Load(fd.Name())
  385. if err != nil {
  386. b.Fatal(err)
  387. }
  388. b.ResetTimer()
  389. for i := 0; i < b.N; i++ {
  390. result = pats.Match("filename")
  391. }
  392. }
  393. func TestCacheReload(t *testing.T) {
  394. dir, err := ioutil.TempDir("", "")
  395. if err != nil {
  396. t.Fatal(err)
  397. }
  398. fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
  399. fd, err := osutil.TempFile(fs, "", "")
  400. if err != nil {
  401. t.Fatal(err)
  402. }
  403. defer fd.Close()
  404. defer fs.Remove(fd.Name())
  405. // Ignore file matches f1 and f2
  406. _, err = fd.Write([]byte("f1\nf2\n"))
  407. if err != nil {
  408. t.Fatal(err)
  409. }
  410. pats := New(fs, WithCache(true))
  411. err = pats.Load(fd.Name())
  412. if err != nil {
  413. t.Fatal(err)
  414. }
  415. // Verify that both are ignored
  416. if !pats.Match("f1").IsIgnored() {
  417. t.Error("Unexpected non-match for f1")
  418. }
  419. if !pats.Match("f2").IsIgnored() {
  420. t.Error("Unexpected non-match for f2")
  421. }
  422. if pats.Match("f3").IsIgnored() {
  423. t.Error("Unexpected match for f3")
  424. }
  425. // Rewrite file to match f1 and f3
  426. err = fd.Truncate(0)
  427. if err != nil {
  428. t.Fatal(err)
  429. }
  430. _, err = fd.Seek(0, os.SEEK_SET)
  431. if err != nil {
  432. t.Fatal(err)
  433. }
  434. _, err = fd.Write([]byte("f1\nf3\n"))
  435. if err != nil {
  436. t.Fatal(err)
  437. }
  438. fd.Sync()
  439. fakeTime := time.Now().Add(5 * time.Second)
  440. fs.Chtimes(fd.Name(), fakeTime, fakeTime)
  441. err = pats.Load(fd.Name())
  442. if err != nil {
  443. t.Fatal(err)
  444. }
  445. // Verify that the new patterns are in effect
  446. if !pats.Match("f1").IsIgnored() {
  447. t.Error("Unexpected non-match for f1")
  448. }
  449. if pats.Match("f2").IsIgnored() {
  450. t.Error("Unexpected match for f2")
  451. }
  452. if !pats.Match("f3").IsIgnored() {
  453. t.Error("Unexpected non-match for f3")
  454. }
  455. }
  456. func TestHash(t *testing.T) {
  457. p1 := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  458. err := p1.Load("testdata/.stignore")
  459. if err != nil {
  460. t.Fatal(err)
  461. }
  462. // Same list of patterns as testdata/.stignore, after expansion
  463. stignore := `
  464. dir2/dfile
  465. dir3
  466. bfile
  467. dir1/cfile
  468. **/efile
  469. /ffile
  470. lost+found
  471. `
  472. p2 := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  473. err = p2.Parse(bytes.NewBufferString(stignore), ".stignore")
  474. if err != nil {
  475. t.Fatal(err)
  476. }
  477. // Not same list of patterns
  478. stignore = `
  479. dir2/dfile
  480. dir3
  481. bfile
  482. dir1/cfile
  483. /ffile
  484. lost+found
  485. `
  486. p3 := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  487. err = p3.Parse(bytes.NewBufferString(stignore), ".stignore")
  488. if err != nil {
  489. t.Fatal(err)
  490. }
  491. if p1.Hash() == "" {
  492. t.Error("p1 hash blank")
  493. }
  494. if p2.Hash() == "" {
  495. t.Error("p2 hash blank")
  496. }
  497. if p3.Hash() == "" {
  498. t.Error("p3 hash blank")
  499. }
  500. if p1.Hash() != p2.Hash() {
  501. t.Error("p1-p2 hashes differ")
  502. }
  503. if p1.Hash() == p3.Hash() {
  504. t.Error("p1-p3 hashes same")
  505. }
  506. }
  507. func TestHashOfEmpty(t *testing.T) {
  508. p1 := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  509. err := p1.Load("testdata/.stignore")
  510. if err != nil {
  511. t.Fatal(err)
  512. }
  513. firstHash := p1.Hash()
  514. // Reloading with a non-existent file should empty the patterns and
  515. // recalculate the hash. d41d8cd98f00b204e9800998ecf8427e is the md5 of
  516. // nothing.
  517. p1.Load("file/does/not/exist")
  518. secondHash := p1.Hash()
  519. if firstHash == secondHash {
  520. t.Error("hash did not change")
  521. }
  522. if secondHash != "d41d8cd98f00b204e9800998ecf8427e" {
  523. t.Error("second hash is not hash of empty string")
  524. }
  525. if len(p1.patterns) != 0 {
  526. t.Error("there are more than zero patterns")
  527. }
  528. }
  529. func TestWindowsPatterns(t *testing.T) {
  530. // We should accept patterns as both a/b and a\b and match that against
  531. // both kinds of slash as well.
  532. if runtime.GOOS != "windows" {
  533. t.Skip("Windows specific test")
  534. return
  535. }
  536. stignore := `
  537. a/b
  538. c\d
  539. `
  540. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  541. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  542. if err != nil {
  543. t.Fatal(err)
  544. }
  545. tests := []string{`a\b`, `c\d`}
  546. for _, pat := range tests {
  547. if !pats.Match(pat).IsIgnored() {
  548. t.Errorf("Should match %s", pat)
  549. }
  550. }
  551. }
  552. func TestAutomaticCaseInsensitivity(t *testing.T) {
  553. // We should do case insensitive matching by default on some platforms.
  554. if runtime.GOOS != "windows" && runtime.GOOS != "darwin" {
  555. t.Skip("Windows/Mac specific test")
  556. return
  557. }
  558. stignore := `
  559. A/B
  560. c/d
  561. `
  562. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  563. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  564. if err != nil {
  565. t.Fatal(err)
  566. }
  567. tests := []string{`a/B`, `C/d`}
  568. for _, pat := range tests {
  569. if !pats.Match(pat).IsIgnored() {
  570. t.Errorf("Should match %s", pat)
  571. }
  572. }
  573. }
  574. func TestCommas(t *testing.T) {
  575. stignore := `
  576. foo,bar.txt
  577. {baz,quux}.txt
  578. `
  579. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  580. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  581. if err != nil {
  582. t.Fatal(err)
  583. }
  584. tests := []struct {
  585. name string
  586. match bool
  587. }{
  588. {"foo.txt", false},
  589. {"bar.txt", false},
  590. {"foo,bar.txt", true},
  591. {"baz.txt", true},
  592. {"quux.txt", true},
  593. {"baz,quux.txt", false},
  594. }
  595. for _, tc := range tests {
  596. if pats.Match(tc.name).IsIgnored() != tc.match {
  597. t.Errorf("Match of %s was %v, should be %v", tc.name, !tc.match, tc.match)
  598. }
  599. }
  600. }
  601. func TestIssue3164(t *testing.T) {
  602. stignore := `
  603. (?d)(?i)*.part
  604. (?d)(?i)/foo
  605. (?d)(?i)**/bar
  606. `
  607. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  608. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  609. if err != nil {
  610. t.Fatal(err)
  611. }
  612. expanded := pats.Patterns()
  613. t.Log(expanded)
  614. expected := []string{
  615. "(?d)(?i)*.part",
  616. "(?d)(?i)**/*.part",
  617. "(?d)(?i)*.part/**",
  618. "(?d)(?i)**/*.part/**",
  619. "(?d)(?i)/foo",
  620. "(?d)(?i)/foo/**",
  621. "(?d)(?i)**/bar",
  622. "(?d)(?i)bar",
  623. "(?d)(?i)**/bar/**",
  624. "(?d)(?i)bar/**",
  625. }
  626. if len(expanded) != len(expected) {
  627. t.Errorf("Unmatched count: %d != %d", len(expanded), len(expected))
  628. }
  629. for i := range expanded {
  630. if expanded[i] != expected[i] {
  631. t.Errorf("Pattern %d does not match: %s != %s", i, expanded[i], expected[i])
  632. }
  633. }
  634. }
  635. func TestIssue3174(t *testing.T) {
  636. stignore := `
  637. *ä*
  638. `
  639. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  640. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  641. if err != nil {
  642. t.Fatal(err)
  643. }
  644. if !pats.Match("åäö").IsIgnored() {
  645. t.Error("Should match")
  646. }
  647. }
  648. func TestIssue3639(t *testing.T) {
  649. stignore := `
  650. foo/
  651. `
  652. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  653. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  654. if err != nil {
  655. t.Fatal(err)
  656. }
  657. if !pats.Match("foo/bar").IsIgnored() {
  658. t.Error("Should match 'foo/bar'")
  659. }
  660. if pats.Match("foo").IsIgnored() {
  661. t.Error("Should not match 'foo'")
  662. }
  663. }
  664. func TestIssue3674(t *testing.T) {
  665. stignore := `
  666. a*b
  667. a**c
  668. `
  669. testcases := []struct {
  670. file string
  671. matches bool
  672. }{
  673. {"ab", true},
  674. {"asdfb", true},
  675. {"ac", true},
  676. {"asdfc", true},
  677. {"as/db", false},
  678. {"as/dc", true},
  679. }
  680. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  681. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  682. if err != nil {
  683. t.Fatal(err)
  684. }
  685. for _, tc := range testcases {
  686. res := pats.Match(tc.file).IsIgnored()
  687. if res != tc.matches {
  688. t.Errorf("Matches(%q) == %v, expected %v", tc.file, res, tc.matches)
  689. }
  690. }
  691. }
  692. func TestGobwasGlobIssue18(t *testing.T) {
  693. stignore := `
  694. a?b
  695. bb?
  696. `
  697. testcases := []struct {
  698. file string
  699. matches bool
  700. }{
  701. {"ab", false},
  702. {"acb", true},
  703. {"asdb", false},
  704. {"bb", false},
  705. {"bba", true},
  706. {"bbaa", false},
  707. }
  708. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  709. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  710. if err != nil {
  711. t.Fatal(err)
  712. }
  713. for _, tc := range testcases {
  714. res := pats.Match(tc.file).IsIgnored()
  715. if res != tc.matches {
  716. t.Errorf("Matches(%q) == %v, expected %v", tc.file, res, tc.matches)
  717. }
  718. }
  719. }
  720. func TestRoot(t *testing.T) {
  721. stignore := `
  722. !/a
  723. /*
  724. `
  725. testcases := []struct {
  726. file string
  727. matches bool
  728. }{
  729. {".", false},
  730. {"a", false},
  731. {"b", true},
  732. }
  733. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  734. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  735. if err != nil {
  736. t.Fatal(err)
  737. }
  738. for _, tc := range testcases {
  739. res := pats.Match(tc.file).IsIgnored()
  740. if res != tc.matches {
  741. t.Errorf("Matches(%q) == %v, expected %v", tc.file, res, tc.matches)
  742. }
  743. }
  744. }
  745. func TestLines(t *testing.T) {
  746. stignore := `
  747. #include testdata/excludes
  748. !/a
  749. /*
  750. !/a
  751. `
  752. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  753. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  754. if err != nil {
  755. t.Fatal(err)
  756. }
  757. expectedLines := []string{
  758. "",
  759. "#include testdata/excludes",
  760. "",
  761. "!/a",
  762. "/*",
  763. "!/a",
  764. "",
  765. }
  766. lines := pats.Lines()
  767. if len(lines) != len(expectedLines) {
  768. t.Fatalf("len(Lines()) == %d, expected %d", len(lines), len(expectedLines))
  769. }
  770. for i := range lines {
  771. if lines[i] != expectedLines[i] {
  772. t.Fatalf("Lines()[%d] == %s, expected %s", i, lines[i], expectedLines[i])
  773. }
  774. }
  775. }
  776. func TestDuplicateLines(t *testing.T) {
  777. stignore := `
  778. !/a
  779. /*
  780. !/a
  781. `
  782. stignoreFiltered := `
  783. !/a
  784. /*
  785. `
  786. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"), WithCache(true))
  787. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  788. if err != nil {
  789. t.Fatal(err)
  790. }
  791. patsLen := len(pats.patterns)
  792. err = pats.Parse(bytes.NewBufferString(stignoreFiltered), ".stignore")
  793. if err != nil {
  794. t.Fatal(err)
  795. }
  796. if patsLen != len(pats.patterns) {
  797. t.Fatalf("Parsed patterns differ when manually removing duplicate lines")
  798. }
  799. }
  800. func TestIssue4680(t *testing.T) {
  801. stignore := `
  802. #snapshot
  803. `
  804. testcases := []struct {
  805. file string
  806. matches bool
  807. }{
  808. {"#snapshot", true},
  809. {"#snapshot/foo", true},
  810. }
  811. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  812. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  813. if err != nil {
  814. t.Fatal(err)
  815. }
  816. for _, tc := range testcases {
  817. res := pats.Match(tc.file).IsIgnored()
  818. if res != tc.matches {
  819. t.Errorf("Matches(%q) == %v, expected %v", tc.file, res, tc.matches)
  820. }
  821. }
  822. }
  823. func TestIssue4689(t *testing.T) {
  824. stignore := `// orig`
  825. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  826. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  827. if err != nil {
  828. t.Fatal(err)
  829. }
  830. if lines := pats.Lines(); len(lines) != 1 || lines[0] != "// orig" {
  831. t.Fatalf("wrong lines parsing original comment:\n%q", lines)
  832. }
  833. stignore = `// new`
  834. err = pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  835. if err != nil {
  836. t.Fatal(err)
  837. }
  838. if lines := pats.Lines(); len(lines) != 1 || lines[0] != "// new" {
  839. t.Fatalf("wrong lines parsing changed comment:\n%v", lines)
  840. }
  841. }
  842. func TestIssue4901(t *testing.T) {
  843. dir, err := ioutil.TempDir("", "")
  844. if err != nil {
  845. t.Fatal(err)
  846. }
  847. defer os.RemoveAll(dir)
  848. stignore := `
  849. #include unicorn-lazor-death
  850. puppy
  851. `
  852. if err := ioutil.WriteFile(filepath.Join(dir, ".stignore"), []byte(stignore), 0777); err != nil {
  853. t.Fatalf(err.Error())
  854. }
  855. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, dir), WithCache(true))
  856. // Cache does not suddenly make the load succeed.
  857. for i := 0; i < 2; i++ {
  858. err := pats.Load(".stignore")
  859. if err == nil {
  860. t.Fatalf("expected an error")
  861. }
  862. if fs.IsNotExist(err) {
  863. t.Fatalf("unexpected error type")
  864. }
  865. }
  866. if err := ioutil.WriteFile(filepath.Join(dir, "unicorn-lazor-death"), []byte(" "), 0777); err != nil {
  867. t.Fatalf(err.Error())
  868. }
  869. err = pats.Load(".stignore")
  870. if err != nil {
  871. t.Fatalf("unexpected error: %s", err.Error())
  872. }
  873. }
  874. // TestIssue5009 checks that ignored dirs are only skipped if there are no include patterns.
  875. // https://github.com/syncthing/syncthing/issues/5009 (rc-only bug)
  876. func TestIssue5009(t *testing.T) {
  877. pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
  878. stignore := `
  879. ign1
  880. i*2
  881. `
  882. if err := pats.Parse(bytes.NewBufferString(stignore), ".stignore"); err != nil {
  883. t.Fatal(err)
  884. }
  885. if !pats.skipIgnoredDirs {
  886. t.Error("skipIgnoredDirs should be true without includes")
  887. }
  888. stignore = `
  889. !iex2
  890. !ign1/ex
  891. ign1
  892. i*2
  893. !ign2
  894. `
  895. if err := pats.Parse(bytes.NewBufferString(stignore), ".stignore"); err != nil {
  896. t.Fatal(err)
  897. }
  898. if pats.skipIgnoredDirs {
  899. t.Error("skipIgnoredDirs should not be true with includes")
  900. }
  901. }