ignore_test.go 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733
  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"
  11. "os"
  12. "path/filepath"
  13. "strings"
  14. "testing"
  15. "time"
  16. "github.com/syncthing/syncthing/lib/build"
  17. "github.com/syncthing/syncthing/lib/fs"
  18. "github.com/syncthing/syncthing/lib/ignore/ignoreresult"
  19. "github.com/syncthing/syncthing/lib/osutil"
  20. "github.com/syncthing/syncthing/lib/rand"
  21. )
  22. const escapePrefixEqual = escapePrefix + "="
  23. var testFiles = map[string]string{
  24. ".stignore": `#include excludes
  25. bfile
  26. dir1/cfile
  27. **/efile
  28. /ffile
  29. lost+found
  30. `,
  31. "excludes": "dir2/dfile\n#include further-excludes\n",
  32. "further-excludes": "dir3\n",
  33. }
  34. func newTestFS() fs.Filesystem {
  35. testFS := fs.NewFilesystem(fs.FilesystemTypeFake, rand.String(32)+"?content=true&nostfolder=true")
  36. // Add some data expected by the tests, previously existing on disk.
  37. testFS.Mkdir("dir3", 0o777)
  38. for name, content := range testFiles {
  39. fs.WriteFile(testFS, name, []byte(content), 0o666)
  40. }
  41. return testFS
  42. }
  43. func TestIgnore(t *testing.T) {
  44. testFs := newTestFS()
  45. pats := New(testFs, WithCache(true))
  46. err := pats.Load(".stignore")
  47. if err != nil {
  48. t.Fatal(err)
  49. }
  50. tests := []struct {
  51. f string
  52. r bool
  53. }{
  54. {"afile", false},
  55. {"bfile", true},
  56. {"cfile", false},
  57. {"dfile", false},
  58. {"efile", true},
  59. {"ffile", true},
  60. {"dir1", false},
  61. {filepath.Join("dir1", "cfile"), true},
  62. {filepath.Join("dir1", "dfile"), false},
  63. {filepath.Join("dir1", "efile"), true},
  64. {filepath.Join("dir1", "ffile"), false},
  65. {"dir2", false},
  66. {filepath.Join("dir2", "cfile"), false},
  67. {filepath.Join("dir2", "dfile"), true},
  68. {filepath.Join("dir2", "efile"), true},
  69. {filepath.Join("dir2", "ffile"), false},
  70. {filepath.Join("dir3"), true},
  71. {filepath.Join("dir3", "afile"), true},
  72. {"lost+found", true},
  73. }
  74. for i, tc := range tests {
  75. if r := pats.Match(tc.f); r.IsIgnored() != tc.r {
  76. t.Errorf("Incorrect ignoreFile() #%d (%s); E: %v, A: %v", i, tc.f, tc.r, r)
  77. }
  78. }
  79. }
  80. func TestExcludes(t *testing.T) {
  81. testFs := newTestFS()
  82. stignore := `
  83. !iex2
  84. !ign1/ex
  85. ign1
  86. i*2
  87. !ign2
  88. `
  89. pats := New(testFs, WithCache(true))
  90. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  91. if err != nil {
  92. t.Fatal(err)
  93. }
  94. tests := []struct {
  95. f string
  96. r bool
  97. }{
  98. {"ign1", true},
  99. {"ign2", true},
  100. {"ibla2", true},
  101. {"iex2", false},
  102. {filepath.Join("ign1", "ign"), true},
  103. {filepath.Join("ign1", "ex"), false},
  104. {filepath.Join("ign1", "iex2"), false},
  105. {filepath.Join("iex2", "ign"), false},
  106. {filepath.Join("foo", "bar", "ign1"), true},
  107. {filepath.Join("foo", "bar", "ign2"), true},
  108. {filepath.Join("foo", "bar", "iex2"), false},
  109. }
  110. for _, tc := range tests {
  111. if r := pats.Match(tc.f); r.IsIgnored() != tc.r {
  112. t.Errorf("Incorrect match for %s: %v != %v", tc.f, r, tc.r)
  113. }
  114. }
  115. }
  116. func TestFlagOrder(t *testing.T) {
  117. testFs := newTestFS()
  118. stignore := `
  119. ## Ok cases
  120. (?i)(?d)!ign1
  121. (?d)(?i)!ign2
  122. (?i)!(?d)ign3
  123. (?d)!(?i)ign4
  124. !(?i)(?d)ign5
  125. !(?d)(?i)ign6
  126. ## Bad cases
  127. !!(?i)(?d)ign7
  128. (?i)(?i)(?d)ign8
  129. (?i)(?d)(?d)!ign9
  130. (?d)(?d)!ign10
  131. `
  132. pats := New(testFs, WithCache(true))
  133. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  134. if err != nil {
  135. t.Fatal(err)
  136. }
  137. for i := 1; i < 7; i++ {
  138. pat := fmt.Sprintf("ign%d", i)
  139. if r := pats.Match(pat); r.IsIgnored() || r.IsDeletable() {
  140. t.Errorf("incorrect %s", pat)
  141. }
  142. }
  143. for i := 7; i < 10; i++ {
  144. pat := fmt.Sprintf("ign%d", i)
  145. if r := pats.Match(pat); r.IsDeletable() {
  146. t.Errorf("incorrect %s", pat)
  147. }
  148. }
  149. if r := pats.Match("(?d)!ign10"); !r.IsIgnored() {
  150. t.Errorf("incorrect")
  151. }
  152. }
  153. func TestDeletables(t *testing.T) {
  154. testFs := newTestFS()
  155. stignore := `
  156. (?d)ign1
  157. (?d)(?i)ign2
  158. (?i)(?d)ign3
  159. !(?d)ign4
  160. !ign5
  161. !(?i)(?d)ign6
  162. ign7
  163. (?i)ign8
  164. `
  165. pats := New(testFs, WithCache(true))
  166. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  167. if err != nil {
  168. t.Fatal(err)
  169. }
  170. tests := []struct {
  171. f string
  172. i bool
  173. d bool
  174. }{
  175. {"ign1", true, true},
  176. {"ign2", true, true},
  177. {"ign3", true, true},
  178. {"ign4", false, false},
  179. {"ign5", false, false},
  180. {"ign6", false, false},
  181. {"ign7", true, false},
  182. {"ign8", true, false},
  183. }
  184. for _, tc := range tests {
  185. if r := pats.Match(tc.f); r.IsIgnored() != tc.i || r.IsDeletable() != tc.d {
  186. t.Errorf("Incorrect match for %s: %v != Result{%t, %t}", tc.f, r, tc.i, tc.d)
  187. }
  188. }
  189. }
  190. func TestBadPatterns(t *testing.T) {
  191. testFs := newTestFS()
  192. t.Skip("to fix: bad pattern not happening")
  193. badPatterns := []string{
  194. "[",
  195. "/[",
  196. "**/[",
  197. "#include nonexistent",
  198. "#include .stignore",
  199. }
  200. for _, pat := range badPatterns {
  201. err := New(testFs, WithCache(true)).Parse(bytes.NewBufferString(pat), ".stignore")
  202. if err == nil {
  203. t.Errorf("No error for pattern %q", pat)
  204. }
  205. if !IsParseError(err) {
  206. t.Error("Should have been a parse error:", err)
  207. }
  208. if strings.HasPrefix(pat, "#include") {
  209. if fs.IsNotExist(err) {
  210. t.Error("Includes should not toss a regular isNotExist error")
  211. }
  212. }
  213. }
  214. }
  215. func TestCaseSensitivity(t *testing.T) {
  216. testFs := newTestFS()
  217. ign := New(testFs, WithCache(true))
  218. err := ign.Parse(bytes.NewBufferString("test"), ".stignore")
  219. if err != nil {
  220. t.Error(err)
  221. }
  222. match := []string{"test"}
  223. dontMatch := []string{"foo"}
  224. if build.IsDarwin || build.IsWindows {
  225. match = append(match, "TEST", "Test", "tESt")
  226. } else {
  227. dontMatch = append(dontMatch, "TEST", "Test", "tESt")
  228. }
  229. for _, tc := range match {
  230. if !ign.Match(tc).IsIgnored() {
  231. t.Errorf("Incorrect match for %q: should be matched", tc)
  232. }
  233. }
  234. for _, tc := range dontMatch {
  235. if ign.Match(tc).IsIgnored() {
  236. t.Errorf("Incorrect match for %q: should not be matched", tc)
  237. }
  238. }
  239. }
  240. func TestCaching(t *testing.T) {
  241. fs := fs.NewFilesystem(fs.FilesystemTypeFake, rand.String(32)+"?content=true")
  242. fd1, err := osutil.TempFile(fs, "", "")
  243. if err != nil {
  244. t.Fatal(err)
  245. }
  246. fd2, err := osutil.TempFile(fs, "", "")
  247. if err != nil {
  248. t.Fatal(err)
  249. }
  250. defer fd1.Close()
  251. defer fd2.Close()
  252. defer fs.Remove(fd1.Name())
  253. defer fs.Remove(fd2.Name())
  254. _, err = fd1.Write([]byte("/x/\n#include " + filepath.Base(fd2.Name()) + "\n"))
  255. if err != nil {
  256. t.Fatal(err)
  257. }
  258. fd2.Write([]byte("/y/\n"))
  259. pats := New(fs, WithCache(true))
  260. err = pats.Load(fd1.Name())
  261. if err != nil {
  262. t.Fatal(err)
  263. }
  264. if pats.matches.len() != 0 {
  265. t.Fatal("Expected empty cache")
  266. }
  267. // Cache some outcomes
  268. for _, letter := range []string{"a", "b", "x", "y"} {
  269. pats.Match(letter)
  270. }
  271. if pats.matches.len() != 4 {
  272. t.Fatal("Expected 4 cached results")
  273. }
  274. // Reload file, expect old outcomes to be preserved
  275. err = pats.Load(fd1.Name())
  276. if err != nil {
  277. t.Fatal(err)
  278. }
  279. if pats.matches.len() != 4 {
  280. t.Fatal("Expected 4 cached results")
  281. }
  282. // Modify the include file, expect empty cache. Ensure the timestamp on
  283. // the file changes.
  284. fd2.Write([]byte("/z/\n"))
  285. fd2.Sync()
  286. fakeTime := time.Now().Add(5 * time.Second)
  287. fs.Chtimes(fd2.Name(), fakeTime, fakeTime)
  288. err = pats.Load(fd1.Name())
  289. if err != nil {
  290. t.Fatal(err)
  291. }
  292. if pats.matches.len() != 0 {
  293. t.Fatal("Expected 0 cached results")
  294. }
  295. // Cache some outcomes again
  296. for _, letter := range []string{"b", "x", "y"} {
  297. pats.Match(letter)
  298. }
  299. // Verify that outcomes preserved on next load
  300. err = pats.Load(fd1.Name())
  301. if err != nil {
  302. t.Fatal(err)
  303. }
  304. if pats.matches.len() != 3 {
  305. t.Fatal("Expected 3 cached results")
  306. }
  307. // Modify the root file, expect cache to be invalidated
  308. fd1.Write([]byte("/a/\n"))
  309. fd1.Sync()
  310. fakeTime = time.Now().Add(5 * time.Second)
  311. fs.Chtimes(fd1.Name(), fakeTime, fakeTime)
  312. err = pats.Load(fd1.Name())
  313. if err != nil {
  314. t.Fatal(err)
  315. }
  316. if pats.matches.len() != 0 {
  317. t.Fatal("Expected cache invalidation")
  318. }
  319. // Cache some outcomes again
  320. for _, letter := range []string{"b", "x", "y"} {
  321. pats.Match(letter)
  322. }
  323. // Verify that outcomes provided on next load
  324. err = pats.Load(fd1.Name())
  325. if err != nil {
  326. t.Fatal(err)
  327. }
  328. if pats.matches.len() != 3 {
  329. t.Fatal("Expected 3 cached results")
  330. }
  331. }
  332. func TestCommentsAndBlankLines(t *testing.T) {
  333. testFs := newTestFS()
  334. stignore := `
  335. // foo
  336. //bar
  337. //!baz
  338. //#dex
  339. // ips
  340. `
  341. pats := New(testFs, WithCache(true))
  342. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  343. if err != nil {
  344. t.Error(err)
  345. }
  346. if len(pats.patterns) > 0 {
  347. t.Errorf("Expected no patterns")
  348. }
  349. }
  350. var result ignoreresult.R
  351. func BenchmarkMatch(b *testing.B) {
  352. testFs := newTestFS()
  353. stignore := `
  354. .frog
  355. .frog*
  356. .frogfox
  357. .whale
  358. .whale/*
  359. .dolphin
  360. .dolphin/*
  361. ~ferret~.*
  362. .ferret.*
  363. flamingo.*
  364. flamingo
  365. *.crow
  366. *.crow
  367. `
  368. pats := New(testFs)
  369. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  370. if err != nil {
  371. b.Error(err)
  372. }
  373. b.ResetTimer()
  374. for i := 0; i < b.N; i++ {
  375. result = pats.Match("filename")
  376. }
  377. }
  378. func BenchmarkMatchCached(b *testing.B) {
  379. stignore := `
  380. .frog
  381. .frog*
  382. .frogfox
  383. .whale
  384. .whale/*
  385. .dolphin
  386. .dolphin/*
  387. ~ferret~.*
  388. .ferret.*
  389. flamingo.*
  390. flamingo
  391. *.crow
  392. *.crow
  393. `
  394. // Caches per file, hence write the patterns to a file.
  395. fs := fs.NewFilesystem(fs.FilesystemTypeFake, rand.String(32)+"?content=true")
  396. fd, err := osutil.TempFile(fs, "", "")
  397. if err != nil {
  398. b.Fatal(err)
  399. }
  400. _, err = fd.Write([]byte(stignore))
  401. defer fd.Close()
  402. defer fs.Remove(fd.Name())
  403. if err != nil {
  404. b.Fatal(err)
  405. }
  406. // Load the patterns
  407. pats := New(fs, WithCache(true))
  408. err = pats.Load(fd.Name())
  409. if err != nil {
  410. b.Fatal(err)
  411. }
  412. // Cache the outcome for "filename"
  413. pats.Match("filename")
  414. // This load should now load the cached outcomes as the set of patterns
  415. // has not changed.
  416. err = pats.Load(fd.Name())
  417. if err != nil {
  418. b.Fatal(err)
  419. }
  420. b.ResetTimer()
  421. for i := 0; i < b.N; i++ {
  422. result = pats.Match("filename")
  423. }
  424. }
  425. func TestCacheReload(t *testing.T) {
  426. fs := fs.NewFilesystem(fs.FilesystemTypeFake, rand.String(32)+"?content=true")
  427. fd, err := osutil.TempFile(fs, "", "")
  428. if err != nil {
  429. t.Fatal(err)
  430. }
  431. defer fd.Close()
  432. defer fs.Remove(fd.Name())
  433. // Ignore file matches f1 and f2
  434. _, err = fd.Write([]byte("f1\nf2\n"))
  435. if err != nil {
  436. t.Fatal(err)
  437. }
  438. pats := New(fs, WithCache(true))
  439. err = pats.Load(fd.Name())
  440. if err != nil {
  441. t.Fatal(err)
  442. }
  443. // Verify that both are ignored
  444. if !pats.Match("f1").IsIgnored() {
  445. t.Error("Unexpected non-match for f1")
  446. }
  447. if !pats.Match("f2").IsIgnored() {
  448. t.Error("Unexpected non-match for f2")
  449. }
  450. if pats.Match("f3").IsIgnored() {
  451. t.Error("Unexpected match for f3")
  452. }
  453. // Rewrite file to match f1 and f3
  454. err = fd.Truncate(0)
  455. if err != nil {
  456. t.Fatal(err)
  457. }
  458. _, err = fd.Seek(0, io.SeekStart)
  459. if err != nil {
  460. t.Fatal(err)
  461. }
  462. _, err = fd.Write([]byte("f1\nf3\n"))
  463. if err != nil {
  464. t.Fatal(err)
  465. }
  466. fd.Sync()
  467. fakeTime := time.Now().Add(5 * time.Second)
  468. fs.Chtimes(fd.Name(), fakeTime, fakeTime)
  469. err = pats.Load(fd.Name())
  470. if err != nil {
  471. t.Fatal(err)
  472. }
  473. // Verify that the new patterns are in effect
  474. if !pats.Match("f1").IsIgnored() {
  475. t.Error("Unexpected non-match for f1")
  476. }
  477. if pats.Match("f2").IsIgnored() {
  478. t.Error("Unexpected match for f2")
  479. }
  480. if !pats.Match("f3").IsIgnored() {
  481. t.Error("Unexpected non-match for f3")
  482. }
  483. }
  484. func TestHash(t *testing.T) {
  485. testFs := newTestFS()
  486. p1 := New(testFs, WithCache(true))
  487. err := p1.Load(".stignore")
  488. if err != nil {
  489. t.Fatal(err)
  490. }
  491. // Same list of patterns as .stignore, after expansion
  492. stignore := `
  493. dir2/dfile
  494. dir3
  495. bfile
  496. dir1/cfile
  497. **/efile
  498. /ffile
  499. lost+found
  500. `
  501. p2 := New(testFs, WithCache(true))
  502. err = p2.Parse(bytes.NewBufferString(stignore), ".stignore")
  503. if err != nil {
  504. t.Fatal(err)
  505. }
  506. // Not same list of patterns
  507. stignore = `
  508. dir2/dfile
  509. dir3
  510. bfile
  511. dir1/cfile
  512. /ffile
  513. lost+found
  514. `
  515. p3 := New(testFs, WithCache(true))
  516. err = p3.Parse(bytes.NewBufferString(stignore), ".stignore")
  517. if err != nil {
  518. t.Fatal(err)
  519. }
  520. if p1.Hash() == "" {
  521. t.Error("p1 hash blank")
  522. }
  523. if p2.Hash() == "" {
  524. t.Error("p2 hash blank")
  525. }
  526. if p3.Hash() == "" {
  527. t.Error("p3 hash blank")
  528. }
  529. if p1.Hash() != p2.Hash() {
  530. t.Error("p1-p2 hashes differ")
  531. }
  532. if p1.Hash() == p3.Hash() {
  533. t.Error("p1-p3 hashes same")
  534. }
  535. }
  536. func TestHashOfEmpty(t *testing.T) {
  537. testFs := newTestFS()
  538. p1 := New(testFs, WithCache(true))
  539. err := p1.Load(".stignore")
  540. if err != nil {
  541. t.Fatal(err)
  542. }
  543. firstHash := p1.Hash()
  544. // Reloading with a non-existent file should empty the patterns and
  545. // recalculate the hash.
  546. // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 is
  547. // the sah256 of nothing.
  548. p1.Load("file/does/not/exist")
  549. secondHash := p1.Hash()
  550. if firstHash == secondHash {
  551. t.Error("hash did not change")
  552. }
  553. if secondHash != "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
  554. t.Error("second hash is not hash of empty string")
  555. }
  556. if len(p1.patterns) != 0 {
  557. t.Error("there are more than zero patterns")
  558. }
  559. }
  560. func TestWindowsPatterns(t *testing.T) {
  561. testFs := newTestFS()
  562. // We should accept patterns as both a/b and a\b and match that against
  563. // both kinds of slash as well.
  564. if !build.IsWindows {
  565. t.Skip("Windows specific test")
  566. return
  567. }
  568. stignore := `
  569. a/b
  570. c\d
  571. `
  572. pats := New(testFs, WithCache(true))
  573. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  574. if err != nil {
  575. t.Fatal(err)
  576. }
  577. tests := []string{`a\b`, `c\d`}
  578. for _, pat := range tests {
  579. if !pats.Match(pat).IsIgnored() {
  580. t.Errorf("Should match %s", pat)
  581. }
  582. }
  583. }
  584. func TestAutomaticCaseInsensitivity(t *testing.T) {
  585. testFs := newTestFS()
  586. // We should do case insensitive matching by default on some platforms.
  587. if !build.IsWindows && !build.IsDarwin {
  588. t.Skip("Windows/Mac specific test")
  589. return
  590. }
  591. stignore := `
  592. A/B
  593. c/d
  594. `
  595. pats := New(testFs, WithCache(true))
  596. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  597. if err != nil {
  598. t.Fatal(err)
  599. }
  600. tests := []string{`a/B`, `C/d`}
  601. for _, pat := range tests {
  602. if !pats.Match(pat).IsIgnored() {
  603. t.Errorf("Should match %s", pat)
  604. }
  605. }
  606. }
  607. func TestCommas(t *testing.T) {
  608. testFs := newTestFS()
  609. stignore := `
  610. foo,bar.txt
  611. {baz,quux}.txt
  612. `
  613. pats := New(testFs, WithCache(true))
  614. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  615. if err != nil {
  616. t.Fatal(err)
  617. }
  618. tests := []struct {
  619. name string
  620. match bool
  621. }{
  622. {"foo.txt", false},
  623. {"bar.txt", false},
  624. {"foo,bar.txt", true},
  625. {"baz.txt", true},
  626. {"quux.txt", true},
  627. {"baz,quux.txt", false},
  628. }
  629. for _, tc := range tests {
  630. if pats.Match(tc.name).IsIgnored() != tc.match {
  631. t.Errorf("Match of %s was %v, should be %v", tc.name, !tc.match, tc.match)
  632. }
  633. }
  634. }
  635. func TestIssue3164(t *testing.T) {
  636. testFs := newTestFS()
  637. stignore := `
  638. (?d)(?i)*.part
  639. (?d)(?i)/foo
  640. (?d)(?i)**/bar
  641. `
  642. pats := New(testFs, WithCache(true))
  643. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  644. if err != nil {
  645. t.Fatal(err)
  646. }
  647. expanded := pats.Patterns()
  648. t.Log(expanded)
  649. expected := []string{
  650. "(?d)(?i)*.part",
  651. "(?d)(?i)**/*.part",
  652. "(?d)(?i)*.part/**",
  653. "(?d)(?i)**/*.part/**",
  654. "(?d)(?i)/foo",
  655. "(?d)(?i)/foo/**",
  656. "(?d)(?i)**/bar",
  657. "(?d)(?i)bar",
  658. "(?d)(?i)**/bar/**",
  659. "(?d)(?i)bar/**",
  660. }
  661. if len(expanded) != len(expected) {
  662. t.Errorf("Unmatched count: %d != %d", len(expanded), len(expected))
  663. }
  664. for i := range expanded {
  665. if expanded[i] != expected[i] {
  666. t.Errorf("Pattern %d does not match: %s != %s", i, expanded[i], expected[i])
  667. }
  668. }
  669. }
  670. func TestIssue3174(t *testing.T) {
  671. testFs := newTestFS()
  672. stignore := `
  673. *ä*
  674. `
  675. pats := New(testFs, WithCache(true))
  676. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  677. if err != nil {
  678. t.Fatal(err)
  679. }
  680. // The pattern above is normalized when parsing, and in order for this
  681. // string to match the pattern, it needs to use the same normalization. And
  682. // Go always uses NFC regardless of OS, while we use NFD on macos.
  683. if !pats.Match(nativeUnicodeNorm("åäö")).IsIgnored() {
  684. t.Error("Should match")
  685. }
  686. }
  687. func TestIssue3639(t *testing.T) {
  688. testFs := newTestFS()
  689. stignore := `
  690. foo/
  691. `
  692. pats := New(testFs, WithCache(true))
  693. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  694. if err != nil {
  695. t.Fatal(err)
  696. }
  697. if !pats.Match("foo/bar").IsIgnored() {
  698. t.Error("Should match 'foo/bar'")
  699. }
  700. if pats.Match("foo").IsIgnored() {
  701. t.Error("Should not match 'foo'")
  702. }
  703. }
  704. func TestIssue3674(t *testing.T) {
  705. testFs := newTestFS()
  706. stignore := `
  707. a*b
  708. a**c
  709. `
  710. testcases := []struct {
  711. file string
  712. matches bool
  713. }{
  714. {"ab", true},
  715. {"asdfb", true},
  716. {"ac", true},
  717. {"asdfc", true},
  718. {"as/db", false},
  719. {"as/dc", true},
  720. }
  721. pats := New(testFs, WithCache(true))
  722. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  723. if err != nil {
  724. t.Fatal(err)
  725. }
  726. for _, tc := range testcases {
  727. res := pats.Match(tc.file).IsIgnored()
  728. if res != tc.matches {
  729. t.Errorf("Matches(%q) == %v, expected %v", tc.file, res, tc.matches)
  730. }
  731. }
  732. }
  733. func TestGobwasGlobIssue18(t *testing.T) {
  734. testFs := newTestFS()
  735. stignore := `
  736. a?b
  737. bb?
  738. `
  739. testcases := []struct {
  740. file string
  741. matches bool
  742. }{
  743. {"ab", false},
  744. {"acb", true},
  745. {"asdb", false},
  746. {"bb", false},
  747. {"bba", true},
  748. {"bbaa", false},
  749. }
  750. pats := New(testFs, WithCache(true))
  751. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  752. if err != nil {
  753. t.Fatal(err)
  754. }
  755. for _, tc := range testcases {
  756. res := pats.Match(tc.file).IsIgnored()
  757. if res != tc.matches {
  758. t.Errorf("Matches(%q) == %v, expected %v", tc.file, res, tc.matches)
  759. }
  760. }
  761. }
  762. func TestRoot(t *testing.T) {
  763. testFs := newTestFS()
  764. stignore := `
  765. !/a
  766. /*
  767. `
  768. testcases := []struct {
  769. file string
  770. matches bool
  771. }{
  772. {".", false},
  773. {"a", false},
  774. {"b", true},
  775. }
  776. pats := New(testFs, WithCache(true))
  777. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  778. if err != nil {
  779. t.Fatal(err)
  780. }
  781. for _, tc := range testcases {
  782. res := pats.Match(tc.file).IsIgnored()
  783. if res != tc.matches {
  784. t.Errorf("Matches(%q) == %v, expected %v", tc.file, res, tc.matches)
  785. }
  786. }
  787. }
  788. func TestLines(t *testing.T) {
  789. testFs := newTestFS()
  790. stignore := `
  791. #include excludes
  792. !/a
  793. /*
  794. !/a
  795. `
  796. pats := New(testFs, WithCache(true))
  797. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  798. if err != nil {
  799. t.Fatal(err)
  800. }
  801. expectedLines := []string{
  802. "",
  803. "#include excludes",
  804. "",
  805. "!/a",
  806. "/*",
  807. "!/a",
  808. "",
  809. }
  810. lines := pats.Lines()
  811. if len(lines) != len(expectedLines) {
  812. t.Fatalf("len(Lines()) == %d, expected %d", len(lines), len(expectedLines))
  813. }
  814. for i := range lines {
  815. if lines[i] != expectedLines[i] {
  816. t.Fatalf("Lines()[%d] == %s, expected %s", i, lines[i], expectedLines[i])
  817. }
  818. }
  819. }
  820. func TestDuplicateLines(t *testing.T) {
  821. testFs := newTestFS()
  822. stignore := `
  823. !/a
  824. /*
  825. !/a
  826. `
  827. stignoreFiltered := `
  828. !/a
  829. /*
  830. `
  831. pats := New(testFs, WithCache(true))
  832. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  833. if err != nil {
  834. t.Fatal(err)
  835. }
  836. patsLen := len(pats.patterns)
  837. err = pats.Parse(bytes.NewBufferString(stignoreFiltered), ".stignore")
  838. if err != nil {
  839. t.Fatal(err)
  840. }
  841. if patsLen != len(pats.patterns) {
  842. t.Fatalf("Parsed patterns differ when manually removing duplicate lines")
  843. }
  844. }
  845. func TestIssue4680(t *testing.T) {
  846. testFs := newTestFS()
  847. stignore := `
  848. #snapshot
  849. `
  850. testcases := []struct {
  851. file string
  852. matches bool
  853. }{
  854. {"#snapshot", true},
  855. {"#snapshot/foo", true},
  856. }
  857. pats := New(testFs, WithCache(true))
  858. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  859. if err != nil {
  860. t.Fatal(err)
  861. }
  862. for _, tc := range testcases {
  863. res := pats.Match(tc.file).IsIgnored()
  864. if res != tc.matches {
  865. t.Errorf("Matches(%q) == %v, expected %v", tc.file, res, tc.matches)
  866. }
  867. }
  868. }
  869. func TestIssue4689(t *testing.T) {
  870. testFs := newTestFS()
  871. stignore := `// orig`
  872. pats := New(testFs, WithCache(true))
  873. err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  874. if err != nil {
  875. t.Fatal(err)
  876. }
  877. if lines := pats.Lines(); len(lines) != 1 || lines[0] != "// orig" {
  878. t.Fatalf("wrong lines parsing original comment:\n%q", lines)
  879. }
  880. stignore = `// new`
  881. err = pats.Parse(bytes.NewBufferString(stignore), ".stignore")
  882. if err != nil {
  883. t.Fatal(err)
  884. }
  885. if lines := pats.Lines(); len(lines) != 1 || lines[0] != "// new" {
  886. t.Fatalf("wrong lines parsing changed comment:\n%v", lines)
  887. }
  888. }
  889. func TestIssue4901(t *testing.T) {
  890. testFs := newTestFS()
  891. stignore := `
  892. #include unicorn-lazor-death
  893. puppy
  894. `
  895. pats := New(testFs, WithCache(true))
  896. fd, err := pats.fs.Create(".stignore")
  897. if err != nil {
  898. t.Fatalf(err.Error())
  899. }
  900. if _, err := fd.Write([]byte(stignore)); err != nil {
  901. t.Fatal(err)
  902. }
  903. // Cache does not suddenly make the load succeed.
  904. for i := 0; i < 2; i++ {
  905. err := pats.Load(".stignore")
  906. if err == nil {
  907. t.Fatal("expected an error")
  908. }
  909. if err == fs.ErrNotExist {
  910. t.Fatalf("unexpected error type: %T", err)
  911. }
  912. if !IsParseError(err) {
  913. t.Fatal("failure to load included file should be a parse error")
  914. }
  915. }
  916. fd, err = pats.fs.Create("unicorn-lazor-death")
  917. if err != nil {
  918. t.Fatalf(err.Error())
  919. }
  920. if _, err := fd.Write([]byte(" ")); err != nil {
  921. t.Fatal(err)
  922. }
  923. err = pats.Load(".stignore")
  924. if err != nil {
  925. t.Fatalf("unexpected error: %s", err.Error())
  926. }
  927. }
  928. // TestIssue5009 checks that ignored dirs are only skipped if there are no include patterns.
  929. // https://github.com/syncthing/syncthing/issues/5009 (rc-only bug)
  930. func TestIssue5009(t *testing.T) {
  931. testFs := newTestFS()
  932. pats := New(testFs, WithCache(true))
  933. stignore := `
  934. ign1
  935. i*2
  936. `
  937. if err := pats.Parse(bytes.NewBufferString(stignore), ".stignore"); err != nil {
  938. t.Fatal(err)
  939. }
  940. if m := pats.Match("ign2"); !m.CanSkipDir() {
  941. t.Error("CanSkipDir should be true without excludes")
  942. }
  943. stignore = `
  944. !iex2
  945. !ign1/ex
  946. ign1
  947. i*2
  948. !ign2
  949. `
  950. if err := pats.Parse(bytes.NewBufferString(stignore), ".stignore"); err != nil {
  951. t.Fatal(err)
  952. }
  953. if m := pats.Match("ign2"); m.CanSkipDir() {
  954. t.Error("CanSkipDir should not be true with excludes")
  955. }
  956. }
  957. func TestSpecialChars(t *testing.T) {
  958. testFs := newTestFS()
  959. pats := New(testFs, WithCache(true))
  960. stignore := `(?i)/#recycle
  961. (?i)/#nosync
  962. (?i)/$Recycle.bin
  963. (?i)/$RECYCLE.BIN
  964. (?i)/System Volume Information`
  965. if err := pats.Parse(bytes.NewBufferString(stignore), ".stignore"); err != nil {
  966. t.Fatal(err)
  967. }
  968. cases := []string{
  969. "#nosync",
  970. "$RECYCLE.BIN",
  971. filepath.FromSlash("$RECYCLE.BIN/S-1-5-18/desktop.ini"),
  972. }
  973. for _, c := range cases {
  974. if !pats.Match(c).IsIgnored() {
  975. t.Errorf("%q should be ignored", c)
  976. }
  977. }
  978. }
  979. func TestIntlWildcards(t *testing.T) {
  980. testFs := newTestFS()
  981. pats := New(testFs, WithCache(true))
  982. stignore := `1000春
  983. 200?春
  984. 300[0-9]春
  985. 400[0-9]?`
  986. if err := pats.Parse(bytes.NewBufferString(stignore), ".stignore"); err != nil {
  987. t.Fatal(err)
  988. }
  989. cases := []string{
  990. "1000春",
  991. "2002春",
  992. "3003春",
  993. "4004春",
  994. }
  995. for _, c := range cases {
  996. if !pats.Match(c).IsIgnored() {
  997. t.Errorf("%q should be ignored", c)
  998. }
  999. }
  1000. }
  1001. func TestPartialIncludeLine(t *testing.T) {
  1002. testFs := newTestFS()
  1003. // Loading a partial #include line (no file mentioned) should error but not crash.
  1004. pats := New(testFs, WithCache(true))
  1005. cases := []string{
  1006. "#include",
  1007. "#include\n",
  1008. "#include ",
  1009. "#include \n",
  1010. "#include \n\n\n",
  1011. }
  1012. for _, tc := range cases {
  1013. err := pats.Parse(bytes.NewBufferString(tc), ".stignore")
  1014. if err == nil {
  1015. t.Fatal("should error out")
  1016. }
  1017. if !IsParseError(err) {
  1018. t.Fatal("failure to load included file should be a parse error")
  1019. }
  1020. }
  1021. }
  1022. func TestSkipIgnoredDirs(t *testing.T) {
  1023. testFs := newTestFS()
  1024. tcs := []struct {
  1025. pattern string
  1026. expected bool
  1027. }{
  1028. {`!/test`, true},
  1029. {`!/t[eih]t`, true},
  1030. {`!/t*t`, true},
  1031. {`!/t?t`, true},
  1032. {`!/**`, true},
  1033. {`!/parent/test`, false},
  1034. {`!/parent/t[eih]t`, false},
  1035. {`!/parent/t*t`, false},
  1036. {`!/parent/t?t`, false},
  1037. {`!/**.mp3`, false},
  1038. {`!/pa*nt/test`, false},
  1039. {`!/pa[sdf]nt/t[eih]t`, false},
  1040. {`!/lowest/pa[sdf]nt/test`, false},
  1041. {`!/lo*st/parent/test`, false},
  1042. {`/pa*nt/test`, true},
  1043. {`test`, true},
  1044. {`*`, true},
  1045. }
  1046. for _, tc := range tcs {
  1047. pats, err := parseLine(tc.pattern)
  1048. if err != nil {
  1049. t.Error(err)
  1050. }
  1051. for _, pat := range pats {
  1052. if got := pat.allowsSkippingIgnoredDirs(); got != tc.expected {
  1053. t.Errorf(`Pattern "%v": got %v, expected %v`, pat, got, tc.expected)
  1054. }
  1055. }
  1056. }
  1057. pats := New(testFs, WithCache(true))
  1058. stignore := `
  1059. /foo/ign*
  1060. !/f*
  1061. !/bar
  1062. *
  1063. `
  1064. if err := pats.Parse(bytes.NewBufferString(stignore), ".stignore"); err != nil {
  1065. t.Fatal(err)
  1066. }
  1067. if m := pats.Match("whatever"); !m.CanSkipDir() {
  1068. t.Error("CanSkipDir should be true")
  1069. }
  1070. stignore = `
  1071. !/foo/ign*
  1072. *
  1073. `
  1074. if err := pats.Parse(bytes.NewBufferString(stignore), ".stignore"); err != nil {
  1075. t.Fatal(err)
  1076. }
  1077. if m := pats.Match("whatever"); m.CanSkipDir() {
  1078. t.Error("CanSkipDir should be false")
  1079. }
  1080. }
  1081. func TestEmptyPatterns(t *testing.T) {
  1082. testFs := newTestFS()
  1083. // These patterns are all invalid and should be rejected as such (without panicking...)
  1084. tcs := []string{
  1085. "!",
  1086. "(?d)",
  1087. "(?i)",
  1088. }
  1089. for _, tc := range tcs {
  1090. m := New(testFs)
  1091. err := m.Parse(strings.NewReader(tc), ".stignore")
  1092. if err == nil {
  1093. t.Error("Should reject invalid pattern", tc)
  1094. }
  1095. if !IsParseError(err) {
  1096. t.Fatal("bad pattern should be a parse error")
  1097. }
  1098. }
  1099. }
  1100. func TestWindowsLineEndings(t *testing.T) {
  1101. testFs := newTestFS()
  1102. if !build.IsWindows {
  1103. t.Skip("Windows specific")
  1104. }
  1105. lines := "foo\nbar\nbaz\n"
  1106. m := New(testFs)
  1107. if err := m.Parse(strings.NewReader(lines), ".stignore"); err != nil {
  1108. t.Fatal(err)
  1109. }
  1110. if err := WriteIgnores(testFs, ".stignore", m.Lines()); err != nil {
  1111. t.Fatal(err)
  1112. }
  1113. fd, err := testFs.Open(".stignore")
  1114. if err != nil {
  1115. t.Fatal(err)
  1116. }
  1117. bs, err := io.ReadAll(fd)
  1118. fd.Close()
  1119. if err != nil {
  1120. t.Fatal(err)
  1121. }
  1122. unixLineEndings := bytes.Count(bs, []byte("\n"))
  1123. windowsLineEndings := bytes.Count(bs, []byte("\r\n"))
  1124. if unixLineEndings == 0 || windowsLineEndings != unixLineEndings {
  1125. t.Error("expected there to be a non-zero number of Windows line endings")
  1126. }
  1127. }
  1128. type escapeTest struct {
  1129. pattern string
  1130. match string
  1131. want bool
  1132. }
  1133. // pathSepIsBackslash could also be set to build.IsWindows, but this will work
  1134. // on any platform where the os.PathSeparator is a backslash (which is
  1135. // currently only Windows).
  1136. const pathSepIsBackslash = os.PathSeparator == '\\'
  1137. var backslashTests = []escapeTest{
  1138. {`a`, `a`, true},
  1139. {`a*`, `a`, true},
  1140. {`a*b`, `ab`, true},
  1141. {`*a`, `a`, true},
  1142. {`*a*`, `a`, true},
  1143. {`a?`, `ab`, true},
  1144. {`a?b`, `acb`, true},
  1145. {`?a`, `ba`, true},
  1146. {`?a?`, `bac`, true},
  1147. {`a[bc]`, `ab`, true},
  1148. {`a[bc]d`, `abd`, true},
  1149. {`[ab]c`, `ac`, true},
  1150. {`[ab]c[de]`, `acd`, true},
  1151. {`a{b,c}`, `ab`, true},
  1152. {`a{b,c}d`, `abd`, true},
  1153. {`{a,b}c`, `ac`, true},
  1154. {`{a,b}c{d,e}`, `acd`, true},
  1155. {`a/**`, `a/b/c`, true},
  1156. {`a**c`, `a/b/c`, true},
  1157. {`**/c`, `a/b/c`, true},
  1158. {`a**b**c`, `a/b/c`, true},
  1159. {`**/c/**`, `a/b/c/d/e`, true},
  1160. {`a]b`, `a]b`, true},
  1161. {`a}b`, `a}b`, true},
  1162. {`a\*`, `a*`, !pathSepIsBackslash},
  1163. {`a\*b`, `a*b`, !pathSepIsBackslash},
  1164. {`\*a`, `*a`, true}, // backslash is first character
  1165. {`\*a\*`, `*a*`, !pathSepIsBackslash},
  1166. {`a\?`, `a?`, !pathSepIsBackslash},
  1167. {`a\?b`, `a?b`, !pathSepIsBackslash},
  1168. {`\?a`, `?a`, true}, // backslash is first character
  1169. {`\?a\?`, `?a?`, !pathSepIsBackslash},
  1170. {`a\[bc\]`, `a[bc]`, !pathSepIsBackslash},
  1171. {`a\[bc\]d`, `a[bc]d`, !pathSepIsBackslash},
  1172. {`\[ab\]c`, `[ab]c`, !pathSepIsBackslash},
  1173. {`\[ab\]c\[de\]`, `[ab]c[de]`, !pathSepIsBackslash},
  1174. {`a\{b,c\}`, `a{b,c}`, !pathSepIsBackslash},
  1175. {`a\{b,c\}d`, `a{b,c}d`, !pathSepIsBackslash},
  1176. {`\{a,b\}c`, `{a,b}c`, !pathSepIsBackslash},
  1177. {`\{a,b\}c\{d,e\}`, `{a,b}c{d,e}`, !pathSepIsBackslash},
  1178. {`a/\*\*`, `a/**`, !pathSepIsBackslash},
  1179. {`a\*\*c`, `a**c`, !pathSepIsBackslash},
  1180. {`\*\*/c`, `**/c`, !pathSepIsBackslash},
  1181. {`a\*\*b\*\*c`, `a**b**c`, !pathSepIsBackslash},
  1182. {`\*\*/c/\*\*`, `**/c/**`, !pathSepIsBackslash},
  1183. {`\a`, `a`, true}, // backslash is first character
  1184. {`\a\b`, `ab`, !pathSepIsBackslash},
  1185. {`\a\b`, `a/b`, pathSepIsBackslash},
  1186. {`a\r\n`, `arn`, !pathSepIsBackslash},
  1187. {`a\r\n`, `a/r/n`, pathSepIsBackslash},
  1188. {`\a\r\n`, `arn`, !pathSepIsBackslash},
  1189. {`\a\r\n`, `a/r/n`, pathSepIsBackslash},
  1190. {`\a\r\n`, `/a/r/n`, false}, // leading backslash is stripped off
  1191. }
  1192. // TestEscapeBackslash tests backslash (\) as the escape character.
  1193. func TestEscapeBackslash(t *testing.T) {
  1194. testEscape(t, backslashTests, true)
  1195. }
  1196. // pipeTests contains the same wants as backslashTests, but
  1197. // !pathSepIsBackslash is changed to true and
  1198. // pathSepIsBackslash is changed to false.
  1199. var pipeTests = []escapeTest{
  1200. {`a|*`, `a*`, true},
  1201. {`a|*b`, `a*b`, true},
  1202. {`|*a`, `*a`, true}, // backslash is first character
  1203. {`|*a|*`, `*a*`, true},
  1204. {`a|?`, `a?`, true},
  1205. {`a|?b`, `a?b`, true},
  1206. {`|?a`, `?a`, true}, // backslash is first character
  1207. {`|?a|?`, `?a?`, true},
  1208. {`a|[bc|]`, `a[bc]`, true},
  1209. {`a|[bc|]d`, `a[bc]d`, true},
  1210. {`|[ab|]c`, `[ab]c`, true},
  1211. {`|[ab|]c|[de|]`, `[ab]c[de]`, true},
  1212. {`a|{b,c|}`, `a{b,c}`, true},
  1213. {`a|{b,c|}d`, `a{b,c}d`, true},
  1214. {`|{a,b|}c`, `{a,b}c`, true},
  1215. {`|{a,b|}c|{d,e|}`, `{a,b}c{d,e}`, true},
  1216. {`a/|*|*`, `a/**`, true},
  1217. {`a|*|*c`, `a**c`, true},
  1218. {`|*|*/c`, `**/c`, true},
  1219. {`a|*|*b|*|*c`, `a**b**c`, true},
  1220. {`|*|*/c/|*|*`, `**/c/**`, true},
  1221. {`a]b`, `a]b`, true},
  1222. {`a}b`, `a}b`, true},
  1223. {`|a`, `a`, true}, // backslash is first character
  1224. {`|a|b`, `ab`, true},
  1225. {`|a|b`, `a/b`, false},
  1226. {`a|r|n`, `arn`, true},
  1227. {`a|r|n`, `a/r/n`, false},
  1228. {`|a|r|n`, `arn`, true},
  1229. {`|a|r|n`, `a/r/n`, false},
  1230. {`|a|r|n`, `/a/r/n`, false}, // leading backslash is stripped off
  1231. }
  1232. // TestEscapePipe tests when pipe (|) is the defaultEscapeChar character
  1233. // (as it is on Windows).
  1234. func TestEscapePipe(t *testing.T) {
  1235. if defaultEscapeChar != '|' {
  1236. t.Skip("Skipping: defaultEscapeChar is not a '|'")
  1237. }
  1238. testEscape(t, pipeTests, true)
  1239. }
  1240. // overrideBackslashTests has the same wants as the pipeTests tests.
  1241. // The only difference in the tests is the pipe symbol in the pattern has been
  1242. // changed to a backslash. This could be done programmatically, if desired.
  1243. var overrideBackslashTests = []escapeTest{
  1244. {`a\*`, `a*`, true},
  1245. {`a\*b`, `a*b`, true},
  1246. {`\*a`, `*a`, true}, // backslash is first character
  1247. {`\*a\*`, `*a*`, true},
  1248. {`a\?`, `a?`, true},
  1249. {`a\?b`, `a?b`, true},
  1250. {`\?a`, `?a`, true}, // backslash is first character
  1251. {`\?a\?`, `?a?`, true},
  1252. {`a\[bc\]`, `a[bc]`, true},
  1253. {`a\[bc\]d`, `a[bc]d`, true},
  1254. {`\[ab\]c`, `[ab]c`, true},
  1255. {`\[ab\]c\[de\]`, `[ab]c[de]`, true},
  1256. {`a\{b,c\}`, `a{b,c}`, true},
  1257. {`a\{b,c\}d`, `a{b,c}d`, true},
  1258. {`\{a,b\}c`, `{a,b}c`, true},
  1259. {`\{a,b\}c\{d,e\}`, `{a,b}c{d,e}`, true},
  1260. {`a/\*\*`, `a/**`, true},
  1261. {`a\*\*c`, `a**c`, true},
  1262. {`\*\*/c`, `**/c`, true},
  1263. {`a\*\*b\*\*c`, `a**b**c`, true},
  1264. {`\*\*/c/\*\*`, `**/c/**`, true},
  1265. {`a]b`, `a]b`, true},
  1266. {`a}b`, `a}b`, true},
  1267. {`\a`, `a`, true}, // backslash is first character
  1268. {`\a\b`, `ab`, true},
  1269. {`\a\b`, `a/b`, false},
  1270. {`a\r\n`, `arn`, true},
  1271. {`a\r\n`, `a/r/n`, false},
  1272. {`\a\r\n`, `arn`, true},
  1273. {`\a\r\n`, `a/r/n`, false},
  1274. {`\a\r\n`, `/a/r/n`, false}, // leading backslash is stripped off
  1275. }
  1276. // TestEscapeOverrideBackslash tests when #escape=\ is in the .stignore file.
  1277. func TestEscapeOverrideBackslash(t *testing.T) {
  1278. tests := make([]escapeTest, 0, len(overrideBackslashTests))
  1279. for _, test := range overrideBackslashTests {
  1280. tests = append(tests, escapeTest{
  1281. escapePrefixEqual + "\\\n" + test.pattern,
  1282. test.match,
  1283. test.want,
  1284. })
  1285. }
  1286. testEscape(t, tests, true)
  1287. }
  1288. // TestEscapeOverridePipe tests when #escape=| (or another character) is in the
  1289. // .stignore file.
  1290. func TestEscapeOverridePipe(t *testing.T) {
  1291. escapeChars := []string{
  1292. "|",
  1293. ">",
  1294. "\u241B", // ␛
  1295. }
  1296. tests := make([]escapeTest, 0, len(pipeTests))
  1297. for _, test := range pipeTests {
  1298. for _, escapeChar := range escapeChars {
  1299. tests = append(tests, escapeTest{
  1300. escapePrefixEqual + escapeChar + "\n" + strings.ReplaceAll(test.pattern, "|", escapeChar),
  1301. test.match,
  1302. test.want,
  1303. })
  1304. }
  1305. }
  1306. testEscape(t, tests, true)
  1307. }
  1308. var escapePrefixes = []string{
  1309. "",
  1310. "\n",
  1311. "// comment\n",
  1312. "\n// comment\n",
  1313. "#include escape-excludes\n",
  1314. "// comment\n#include escape-excludes\n",
  1315. "#include escape-excludes\n//comment\n",
  1316. "// comment\n#include escape-excludes\n//comment\n",
  1317. }
  1318. // TestEscapeBeforePattern tests when #escape= is found before a pattern in the
  1319. // .stignore file.
  1320. func TestEscapeBeforePattern(t *testing.T) {
  1321. tests := make([]escapeTest, 0, len(overrideBackslashTests)*len(escapePrefixes))
  1322. for _, test := range overrideBackslashTests {
  1323. for _, prefix := range escapePrefixes {
  1324. tests = append(tests, escapeTest{
  1325. // Use backslash, as it should not be ignored,
  1326. // so test against the overrideBackslashTests.
  1327. prefix + escapePrefixEqual + "\\\n" + test.pattern,
  1328. test.match,
  1329. test.want,
  1330. })
  1331. }
  1332. }
  1333. testEscape(t, tests, true)
  1334. }
  1335. // TestEscapeEmpty tests when #escape= (no char) is in the .stignore file.
  1336. func TestEscapeEmpty(t *testing.T) {
  1337. suffixes := []string{"", " ", "\t", "=", "= ", "=\t", "x"}
  1338. tests := make([]escapeTest, 0, len(backslashTests)*len(suffixes))
  1339. for _, test := range backslashTests {
  1340. for _, suffix := range suffixes {
  1341. tests = append(tests, escapeTest{
  1342. escapePrefix + suffix + "\n" + test.pattern,
  1343. test.match,
  1344. false,
  1345. })
  1346. }
  1347. }
  1348. testEscape(t, tests, false)
  1349. }
  1350. // TestEscapeInvalid tests when #escape=x has extra characters after it
  1351. func TestEscapeInvalid(t *testing.T) {
  1352. suffixes := []string{"\\\\", "||", "\u241B\u241B", "xx"} // ␛
  1353. tests := make([]escapeTest, 0, len(backslashTests)*len(suffixes))
  1354. for _, test := range backslashTests {
  1355. for _, suffix := range suffixes {
  1356. tests = append(tests, escapeTest{
  1357. escapePrefixEqual + suffix + "\n" + test.pattern,
  1358. test.match,
  1359. false,
  1360. })
  1361. }
  1362. }
  1363. testEscape(t, tests, false)
  1364. }
  1365. // TestEscapeAfterPattern tests when #escape= is found after a pattern in the
  1366. // .stignore file.
  1367. func TestEscapeAfterPattern(t *testing.T) {
  1368. suffixes := []string{
  1369. "pattern\n",
  1370. "pattern/\n",
  1371. "pattern/**\n",
  1372. }
  1373. tests := make([]escapeTest, 0, len(backslashTests)*len(escapePrefixes)*len(suffixes))
  1374. for _, test := range backslashTests {
  1375. for _, prefix := range escapePrefixes {
  1376. for _, suffix := range suffixes {
  1377. tests = append(tests, escapeTest{
  1378. // Use a different character, as it should be ignored,
  1379. // so test against the backslashTests.
  1380. prefix + suffix + escapePrefixEqual + "\u241B\n" + test.pattern,
  1381. test.match,
  1382. false,
  1383. })
  1384. }
  1385. }
  1386. }
  1387. testEscape(t, tests, false)
  1388. }
  1389. // TestEscapeDoubled tests when #escape= is found more than once.
  1390. func TestEscapeDoubled(t *testing.T) {
  1391. suffixes := []string{
  1392. "#escape\n",
  1393. "#escape=\n",
  1394. "#escape=\\\n",
  1395. "#escape=|\n",
  1396. }
  1397. tests := make([]escapeTest, 0, len(backslashTests)*len(suffixes))
  1398. for _, test := range backslashTests {
  1399. for _, suffix := range suffixes {
  1400. tests = append(tests, escapeTest{
  1401. escapePrefixEqual + "\\\n" + suffix + test.pattern,
  1402. test.match,
  1403. false,
  1404. })
  1405. }
  1406. }
  1407. testEscape(t, tests, false)
  1408. }
  1409. var testEscapeFiles = map[string]string{
  1410. "escape-excludes": "dir4\n",
  1411. }
  1412. func testEscape(t *testing.T, tests []escapeTest, noErrors bool) {
  1413. t.Helper()
  1414. for i, test := range tests {
  1415. testFS := fs.NewFilesystem(fs.FilesystemTypeFake, rand.String(32)+"?content=true&nostfolder=true")
  1416. for name, content := range testEscapeFiles {
  1417. fs.WriteFile(testFS, name, []byte(content), 0o666)
  1418. }
  1419. pats := New(testFS, WithCache(true))
  1420. err := pats.Parse(bytes.NewBufferString(test.pattern), ".stignore")
  1421. if noErrors {
  1422. if err != nil {
  1423. t.Fatalf("%q: err=%v (test %d)", test.pattern, err, i+1)
  1424. }
  1425. } else {
  1426. if err == nil {
  1427. t.Fatalf("%q: got nil, want error (test %d)", test.pattern, i+1)
  1428. }
  1429. continue
  1430. }
  1431. got := pats.Match(test.match).IsIgnored()
  1432. if got != test.want {
  1433. t.Errorf("%-20q: %-20q: got %v, want %v (test %d)", test.pattern, test.match, got, test.want, i+1)
  1434. }
  1435. }
  1436. }