Go · 3261 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package trigger
4
5 import "testing"
6
7 // Tests live in the same package so we can exercise unexported
8 // matchAny + globMatch directly. The trigger package's public API
9 // (Match, Discover, Enqueue) gets _test.go files in package
10 // trigger_test where the surface should be opaque.
11
12 func TestGlobMatch_LiteralAndStar(t *testing.T) {
13 t.Parallel()
14 cases := []struct {
15 pattern string
16 s string
17 want bool
18 }{
19 {"main", "main", true},
20 {"main", "feature/foo", false},
21 {"main", "main/sub", false},
22 {"feature/*", "feature/foo", true},
23 {"feature/*", "feature/foo/bar", false}, // * doesn't cross /
24 {"feature/*", "feature/", true}, // trailing-empty acceptable
25 {"*", "anything", true},
26 {"*", "with/slash", false},
27 {"*.tar.gz", "foo.tar.gz", true},
28 {"*.tar.gz", "foo/bar.tar.gz", false},
29 }
30 for _, tc := range cases {
31 t.Run(tc.pattern+"_"+tc.s, func(t *testing.T) {
32 t.Parallel()
33 got := globMatch(tc.pattern, tc.s)
34 if got != tc.want {
35 t.Errorf("globMatch(%q, %q) = %v, want %v", tc.pattern, tc.s, got, tc.want)
36 }
37 })
38 }
39 }
40
41 func TestGlobMatch_DoubleStar(t *testing.T) {
42 t.Parallel()
43 cases := []struct {
44 pattern string
45 s string
46 want bool
47 }{
48 {"feature/**", "feature", true}, // zero trailing segments
49 {"feature/**", "feature/foo", true},
50 {"feature/**", "feature/foo/bar", true},
51 {"feature/**", "main", false},
52 {"**/*.go", "main.go", true},
53 {"**/*.go", "pkg/sub/x.go", true},
54 {"**/*.go", "pkg/sub/x.txt", false},
55 {"docs/**/*.md", "docs/internal/x.md", true},
56 {"docs/**/*.md", "docs/x.md", true}, // ** matches zero segments
57 {"docs/**/*.md", "src/x.md", false},
58 {"**", "literally/any/path", true},
59 }
60 for _, tc := range cases {
61 t.Run(tc.pattern+"_"+tc.s, func(t *testing.T) {
62 t.Parallel()
63 got := globMatch(tc.pattern, tc.s)
64 if got != tc.want {
65 t.Errorf("globMatch(%q, %q) = %v, want %v", tc.pattern, tc.s, got, tc.want)
66 }
67 })
68 }
69 }
70
71 // TestMatchAny_MixedIncludeExclude pins the GHA-style include + `!exclude`
72 // semantics: last match wins in declaration order, but a list of *only*
73 // exclusions implicitly includes everything not excluded.
74 func TestMatchAny_MixedIncludeExclude(t *testing.T) {
75 t.Parallel()
76 cases := []struct {
77 name string
78 patterns []string
79 s string
80 want bool
81 }{
82 {"empty list matches all", nil, "anything", true},
83 {"single include matches", []string{"main"}, "main", true},
84 {"single include miss", []string{"main"}, "feature/foo", false},
85 {"include + exclude — included wins", []string{"feature/**", "!feature/skip"}, "feature/foo", true},
86 {"include + exclude — excluded loses", []string{"feature/**", "!feature/skip"}, "feature/skip", false},
87 {"only-exclusions implicit-include", []string{"!main"}, "feature/foo", true},
88 {"only-exclusions hit", []string{"!main"}, "main", false},
89 {"order matters — last-include re-includes", []string{"feature/**", "!feature/skip", "feature/skip"}, "feature/skip", true},
90 }
91 for _, tc := range cases {
92 t.Run(tc.name, func(t *testing.T) {
93 t.Parallel()
94 got := matchAny(tc.patterns, tc.s)
95 if got != tc.want {
96 t.Errorf("matchAny(%v, %q) = %v, want %v", tc.patterns, tc.s, got, tc.want)
97 }
98 })
99 }
100 }
101