1package match
2
3import (
4	"fmt"
5	"math/rand"
6	"strings"
7	"testing"
8	"time"
9	"unicode/utf8"
10)
11
12func TestMatch(t *testing.T) {
13	if !Match("hello world", "hello world") {
14		t.Fatal("fail")
15	}
16	if Match("hello world", "jello world") {
17		t.Fatal("fail")
18	}
19	if !Match("hello world", "hello*") {
20		t.Fatal("fail")
21	}
22	if Match("hello world", "jello*") {
23		t.Fatal("fail")
24	}
25	if !Match("hello world", "hello?world") {
26		t.Fatal("fail")
27	}
28	if Match("hello world", "jello?world") {
29		t.Fatal("fail")
30	}
31	if !Match("hello world", "he*o?world") {
32		t.Fatal("fail")
33	}
34	if !Match("hello world", "he*o?wor*") {
35		t.Fatal("fail")
36	}
37	if !Match("hello world", "he*o?*r*") {
38		t.Fatal("fail")
39	}
40	if !Match("hello*world", `hello\*world`) {
41		t.Fatal("fail")
42	}
43	if !Match("he解lo*world", `helo\*world`) {
44		t.Fatal("fail")
45	}
46	if !Match("的情况下解析一个", "*") {
47		t.Fatal("fail")
48	}
49	if !Match("的情况下解析一个", "*况下*") {
50		t.Fatal("fail")
51	}
52	if !Match("的情况下解析一个", "*况?*") {
53		t.Fatal("fail")
54	}
55	if !Match("的情况下解析一个", "的情况?解析一个") {
56		t.Fatal("fail")
57	}
58	if Match("hello world\\", "hello world\\") {
59		t.Fatal("fail")
60	}
61}
62
63// TestWildcardMatch - Tests validate the logic of wild card matching.
64// `WildcardMatch` supports '*' and '?' wildcards.
65// Sample usage: In resource matching for folder policy validation.
66func TestWildcardMatch(t *testing.T) {
67	testCases := []struct {
68		pattern string
69		text    string
70		matched bool
71	}{
72		// Test case - 1.
73		// Test case with pattern containing key name with a prefix. Should accept the same text without a "*".
74		{
75			pattern: "my-folder/oo*",
76			text:    "my-folder/oo",
77			matched: true,
78		},
79		// Test case - 2.
80		// Test case with "*" at the end of the pattern.
81		{
82			pattern: "my-folder/In*",
83			text:    "my-folder/India/Karnataka/",
84			matched: true,
85		},
86		// Test case - 3.
87		// Test case with prefixes shuffled.
88		// This should fail.
89		{
90			pattern: "my-folder/In*",
91			text:    "my-folder/Karnataka/India/",
92			matched: false,
93		},
94		// Test case - 4.
95		// Test case with text expanded to the wildcards in the pattern.
96		{
97			pattern: "my-folder/In*/Ka*/Ban",
98			text:    "my-folder/India/Karnataka/Ban",
99			matched: true,
100		},
101		// Test case - 5.
102		// Test case with the  keyname part is repeated as prefix several times.
103		// This is valid.
104		{
105			pattern: "my-folder/In*/Ka*/Ban",
106			text:    "my-folder/India/Karnataka/Ban/Ban/Ban/Ban/Ban",
107			matched: true,
108		},
109		// Test case - 6.
110		// Test case to validate that `*` can be expanded into multiple prefixes.
111		{
112			pattern: "my-folder/In*/Ka*/Ban",
113			text:    "my-folder/India/Karnataka/Area1/Area2/Area3/Ban",
114			matched: true,
115		},
116		// Test case - 7.
117		// Test case to validate that `*` can be expanded into multiple prefixes.
118		{
119			pattern: "my-folder/In*/Ka*/Ban",
120			text:    "my-folder/India/State1/State2/Karnataka/Area1/Area2/Area3/Ban",
121			matched: true,
122		},
123		// Test case - 8.
124		// Test case where the keyname part of the pattern is expanded in the text.
125		{
126			pattern: "my-folder/In*/Ka*/Ban",
127			text:    "my-folder/India/Karnataka/Bangalore",
128			matched: false,
129		},
130		// Test case - 9.
131		// Test case with prefixes and wildcard expanded for all "*".
132		{
133			pattern: "my-folder/In*/Ka*/Ban*",
134			text:    "my-folder/India/Karnataka/Bangalore",
135			matched: true,
136		},
137		// Test case - 10.
138		// Test case with keyname part being a wildcard in the pattern.
139		{pattern: "my-folder/*",
140			text:    "my-folder/India",
141			matched: true,
142		},
143		// Test case - 11.
144		{
145			pattern: "my-folder/oo*",
146			text:    "my-folder/odo",
147			matched: false,
148		},
149
150		// Test case with pattern containing wildcard '?'.
151		// Test case - 12.
152		// "my-folder?/" matches "my-folder1/", "my-folder2/", "my-folder3" etc...
153		// doesn't match "myfolder/".
154		{
155			pattern: "my-folder?/abc*",
156			text:    "myfolder/abc",
157			matched: false,
158		},
159		// Test case - 13.
160		{
161			pattern: "my-folder?/abc*",
162			text:    "my-folder1/abc",
163			matched: true,
164		},
165		// Test case - 14.
166		{
167			pattern: "my-?-folder/abc*",
168			text:    "my--folder/abc",
169			matched: false,
170		},
171		// Test case - 15.
172		{
173			pattern: "my-?-folder/abc*",
174			text:    "my-1-folder/abc",
175			matched: true,
176		},
177		// Test case - 16.
178		{
179			pattern: "my-?-folder/abc*",
180			text:    "my-k-folder/abc",
181			matched: true,
182		},
183		// Test case - 17.
184		{
185			pattern: "my??folder/abc*",
186			text:    "myfolder/abc",
187			matched: false,
188		},
189		// Test case - 18.
190		{
191			pattern: "my??folder/abc*",
192			text:    "my4afolder/abc",
193			matched: true,
194		},
195		// Test case - 19.
196		{
197			pattern: "my-folder?abc*",
198			text:    "my-folder/abc",
199			matched: true,
200		},
201		// Test case 20-21.
202		// '?' matches '/' too. (works with s3).
203		// This is because the namespace is considered flat.
204		// "abc?efg" matches both "abcdefg" and "abc/efg".
205		{
206			pattern: "my-folder/abc?efg",
207			text:    "my-folder/abcdefg",
208			matched: true,
209		},
210		{
211			pattern: "my-folder/abc?efg",
212			text:    "my-folder/abc/efg",
213			matched: true,
214		},
215		// Test case - 22.
216		{
217			pattern: "my-folder/abc????",
218			text:    "my-folder/abc",
219			matched: false,
220		},
221		// Test case - 23.
222		{
223			pattern: "my-folder/abc????",
224			text:    "my-folder/abcde",
225			matched: false,
226		},
227		// Test case - 24.
228		{
229			pattern: "my-folder/abc????",
230			text:    "my-folder/abcdefg",
231			matched: true,
232		},
233		// Test case 25-26.
234		// test case with no '*'.
235		{
236			pattern: "my-folder/abc?",
237			text:    "my-folder/abc",
238			matched: false,
239		},
240		{
241			pattern: "my-folder/abc?",
242			text:    "my-folder/abcd",
243			matched: true,
244		},
245		{
246			pattern: "my-folder/abc?",
247			text:    "my-folder/abcde",
248			matched: false,
249		},
250		// Test case 27.
251		{
252			pattern: "my-folder/mnop*?",
253			text:    "my-folder/mnop",
254			matched: false,
255		},
256		// Test case 28.
257		{
258			pattern: "my-folder/mnop*?",
259			text:    "my-folder/mnopqrst/mnopqr",
260			matched: true,
261		},
262		// Test case 29.
263		{
264			pattern: "my-folder/mnop*?",
265			text:    "my-folder/mnopqrst/mnopqrs",
266			matched: true,
267		},
268		// Test case 30.
269		{
270			pattern: "my-folder/mnop*?",
271			text:    "my-folder/mnop",
272			matched: false,
273		},
274		// Test case 31.
275		{
276			pattern: "my-folder/mnop*?",
277			text:    "my-folder/mnopq",
278			matched: true,
279		},
280		// Test case 32.
281		{
282			pattern: "my-folder/mnop*?",
283			text:    "my-folder/mnopqr",
284			matched: true,
285		},
286		// Test case 33.
287		{
288			pattern: "my-folder/mnop*?and",
289			text:    "my-folder/mnopqand",
290			matched: true,
291		},
292		// Test case 34.
293		{
294			pattern: "my-folder/mnop*?and",
295			text:    "my-folder/mnopand",
296			matched: false,
297		},
298		// Test case 35.
299		{
300			pattern: "my-folder/mnop*?and",
301			text:    "my-folder/mnopqand",
302			matched: true,
303		},
304		// Test case 36.
305		{
306			pattern: "my-folder/mnop*?",
307			text:    "my-folder/mn",
308			matched: false,
309		},
310		// Test case 37.
311		{
312			pattern: "my-folder/mnop*?",
313			text:    "my-folder/mnopqrst/mnopqrs",
314			matched: true,
315		},
316		// Test case 38.
317		{
318			pattern: "my-folder/mnop*??",
319			text:    "my-folder/mnopqrst",
320			matched: true,
321		},
322		// Test case 39.
323		{
324			pattern: "my-folder/mnop*qrst",
325			text:    "my-folder/mnopabcdegqrst",
326			matched: true,
327		},
328		// Test case 40.
329		{
330			pattern: "my-folder/mnop*?and",
331			text:    "my-folder/mnopqand",
332			matched: true,
333		},
334		// Test case 41.
335		{
336			pattern: "my-folder/mnop*?and",
337			text:    "my-folder/mnopand",
338			matched: false,
339		},
340		// Test case 42.
341		{
342			pattern: "my-folder/mnop*?and?",
343			text:    "my-folder/mnopqanda",
344			matched: true,
345		},
346		// Test case 43.
347		{
348			pattern: "my-folder/mnop*?and",
349			text:    "my-folder/mnopqanda",
350			matched: false,
351		},
352		// Test case 44.
353
354		{
355			pattern: "my-?-folder/abc*",
356			text:    "my-folder/mnopqanda",
357			matched: false,
358		},
359	}
360	// Iterating over the test cases, call the function under test and asert the output.
361	for i, testCase := range testCases {
362		// println("=====", i+1, "=====")
363		actualResult := Match(testCase.text, testCase.pattern)
364		if testCase.matched != actualResult {
365			t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.matched, actualResult)
366		}
367	}
368}
369func TestRandomInput(t *testing.T) {
370	rand.Seed(time.Now().UnixNano())
371	b1 := make([]byte, 100)
372	b2 := make([]byte, 100)
373	for i := 0; i < 1000000; i++ {
374		if _, err := rand.Read(b1); err != nil {
375			t.Fatal(err)
376		}
377		if _, err := rand.Read(b2); err != nil {
378			t.Fatal(err)
379		}
380		Match(string(b1), string(b2))
381	}
382}
383func testAllowable(pattern, exmin, exmax string) error {
384	min, max := Allowable(pattern)
385	if min != exmin || max != exmax {
386		return fmt.Errorf("expected '%v'/'%v', got '%v'/'%v'",
387			exmin, exmax, min, max)
388	}
389	return nil
390}
391func TestAllowable(t *testing.T) {
392	if err := testAllowable("*", "", ""); err != nil {
393		t.Fatal(err)
394	}
395	if err := testAllowable("hell*", "hell", "helm"); err != nil {
396		t.Fatal(err)
397	}
398	if err := testAllowable("hell?", "hell"+string(rune(0)), "hell"+string(utf8.MaxRune)); err != nil {
399		t.Fatal(err)
400	}
401	if err := testAllowable("h解析ell*", "h解析ell", "h解析elm"); err != nil {
402		t.Fatal(err)
403	}
404	if err := testAllowable("h解*ell*", "h解", "h觤"); err != nil {
405		t.Fatal(err)
406	}
407}
408
409func TestIsPattern(t *testing.T) {
410	patterns := []string{
411		"*", "hello*", "hello*world", "*world",
412		"?", "hello?", "hello?world", "?world",
413	}
414	nonPatterns := []string{
415		"", "hello",
416	}
417	for _, pattern := range patterns {
418		if !IsPattern(pattern) {
419			t.Fatalf("expected true")
420		}
421	}
422
423	for _, s := range nonPatterns {
424		if IsPattern(s) {
425			t.Fatalf("expected false")
426		}
427	}
428}
429func BenchmarkAscii(t *testing.B) {
430	for i := 0; i < t.N; i++ {
431		if !Match("hello", "hello") {
432			t.Fatal("fail")
433		}
434	}
435}
436
437func BenchmarkUnicode(t *testing.B) {
438	for i := 0; i < t.N; i++ {
439		if !Match("h情llo", "h情llo") {
440			t.Fatal("fail")
441		}
442	}
443}
444
445func TestLotsaStars(t *testing.T) {
446	// This tests that a pattern with lots of stars will complete quickly.
447	var str, pat string
448
449	str = `,**,,**,**,**,**,**,**,`
450	pat = `,**********************************************{**",**,,**,**,` +
451		`**,**,"",**,**,**,**,**,**,**,**,**,**]`
452	Match(pat, str)
453
454	str = strings.Replace(str, ",", "", -1)
455	pat = strings.Replace(pat, ",", "", -1)
456	Match(pat, str)
457
458	str = strings.Repeat("hello", 100)
459	pat = `*?*?*?*?*?*?*""`
460	Match(str, pat)
461
462	str = `*?**?**?**?**?**?***?**?**?**?**?*""`
463	pat = `*?*?*?*?*?*?**?**?**?**?**?**?**?*""`
464	Match(str, pat)
465}
466
467func TestLimit(t *testing.T) {
468	var str, pat string
469	str = `,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,`
470	pat = `*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*"*,*`
471	_, stopped := MatchLimit(str, pat, 100)
472	if !stopped {
473		t.Fatal("expected true")
474	}
475
476	match, _ := MatchLimit(str, "*", 100)
477	if !match {
478		t.Fatal("expected true")
479	}
480	match, _ = MatchLimit(str, "*,*", 100)
481	if !match {
482		t.Fatal("expected true")
483	}
484}
485
486func TestSuffix(t *testing.T) {
487	sufmatch := func(t *testing.T, str, pat string, exstr, expat string, exok bool) {
488		t.Helper()
489		rstr, rpat, rok := matchTrimSuffix(str, pat)
490		if rstr != exstr || rpat != expat || rok != exok {
491			t.Fatalf(
492				"for '%s' '%s', expected '%s' '%s' '%t', got '%s' '%s' '%t'",
493				str, pat, exstr, expat, exok, rstr, rpat, rok)
494		}
495	}
496	sufmatch(t, "hello", "*hello", "", "*", true)
497	sufmatch(t, "jello", "*hello", "j", "*h", false)
498	sufmatch(t, "jello", "*?ello", "", "*", true)
499	sufmatch(t, "jello", "*\\?ello", "j", "*\\?", false)
500	sufmatch(t, "?ello", "*\\?ello", "", "*", true)
501	sufmatch(t, "?ello", "*\\?ello", "", "*", true)
502	sufmatch(t, "f?ello", "*\\?ello", "f", "*", true)
503	sufmatch(t, "f?ello", "**\\?ello", "f", "**", true)
504	sufmatch(t, "f?el*o", "**\\?el\\*o", "f", "**", true)
505}
506