1
2
3
4
5 package constraint
6
7 import (
8 "fmt"
9 "maps"
10 "reflect"
11 "slices"
12 "strings"
13 "testing"
14 )
15
16 var exprStringTests = []struct {
17 x Expr
18 out string
19 }{
20 {
21 x: tag("abc"),
22 out: "abc",
23 },
24 {
25 x: not(tag("abc")),
26 out: "!abc",
27 },
28 {
29 x: not(and(tag("abc"), tag("def"))),
30 out: "!(abc && def)",
31 },
32 {
33 x: and(tag("abc"), or(tag("def"), tag("ghi"))),
34 out: "abc && (def || ghi)",
35 },
36 {
37 x: or(and(tag("abc"), tag("def")), tag("ghi")),
38 out: "(abc && def) || ghi",
39 },
40 }
41
42 func TestExprString(t *testing.T) {
43 for i, tt := range exprStringTests {
44 t.Run(fmt.Sprint(i), func(t *testing.T) {
45 s := tt.x.String()
46 if s != tt.out {
47 t.Errorf("String() mismatch:\nhave %s\nwant %s", s, tt.out)
48 }
49 })
50 }
51 }
52
53 var lexTests = []struct {
54 in string
55 out string
56 }{
57 {"", ""},
58 {"x", "x"},
59 {"x.y", "x.y"},
60 {"x_y", "x_y"},
61 {"αx", "αx"},
62 {"αx²", "αx err: invalid syntax at ²"},
63 {"go1.2", "go1.2"},
64 {"x y", "x y"},
65 {"x!y", "x ! y"},
66 {"&&||!()xy yx ", "&& || ! ( ) xy yx"},
67 {"x~", "x err: invalid syntax at ~"},
68 {"x ~", "x err: invalid syntax at ~"},
69 {"x &", "x err: invalid syntax at &"},
70 {"x &y", "x err: invalid syntax at &"},
71 }
72
73 func TestLex(t *testing.T) {
74 for i, tt := range lexTests {
75 t.Run(fmt.Sprint(i), func(t *testing.T) {
76 p := &exprParser{s: tt.in}
77 out := ""
78 for {
79 tok, err := lexHelp(p)
80 if tok == "" && err == nil {
81 break
82 }
83 if out != "" {
84 out += " "
85 }
86 if err != nil {
87 out += "err: " + err.Error()
88 break
89 }
90 out += tok
91 }
92 if out != tt.out {
93 t.Errorf("lex(%q):\nhave %s\nwant %s", tt.in, out, tt.out)
94 }
95 })
96 }
97 }
98
99 func lexHelp(p *exprParser) (tok string, err error) {
100 defer func() {
101 if e := recover(); e != nil {
102 if e, ok := e.(*SyntaxError); ok {
103 err = e
104 return
105 }
106 panic(e)
107 }
108 }()
109
110 p.lex()
111 return p.tok, nil
112 }
113
114 var parseExprTests = []struct {
115 in string
116 x Expr
117 }{
118 {"x", tag("x")},
119 {"x&&y", and(tag("x"), tag("y"))},
120 {"x||y", or(tag("x"), tag("y"))},
121 {"(x)", tag("x")},
122 {"x||y&&z", or(tag("x"), and(tag("y"), tag("z")))},
123 {"x&&y||z", or(and(tag("x"), tag("y")), tag("z"))},
124 {"x&&(y||z)", and(tag("x"), or(tag("y"), tag("z")))},
125 {"(x||y)&&z", and(or(tag("x"), tag("y")), tag("z"))},
126 {"!(x&&y)", not(and(tag("x"), tag("y")))},
127 }
128
129 func TestParseExpr(t *testing.T) {
130 for i, tt := range parseExprTests {
131 t.Run(fmt.Sprint(i), func(t *testing.T) {
132 x, err := parseExpr(tt.in)
133 if err != nil {
134 t.Fatal(err)
135 }
136 if x.String() != tt.x.String() {
137 t.Errorf("parseExpr(%q):\nhave %s\nwant %s", tt.in, x, tt.x)
138 }
139 })
140 }
141 }
142
143 var parseExprErrorTests = []struct {
144 in string
145 err error
146 }{
147 {"x && ", &SyntaxError{Offset: 5, Err: "unexpected end of expression"}},
148 {"x && (", &SyntaxError{Offset: 6, Err: "missing close paren"}},
149 {"x && ||", &SyntaxError{Offset: 5, Err: "unexpected token ||"}},
150 {"x && !", &SyntaxError{Offset: 6, Err: "unexpected end of expression"}},
151 {"x && !!", &SyntaxError{Offset: 6, Err: "double negation not allowed"}},
152 {"x !", &SyntaxError{Offset: 2, Err: "unexpected token !"}},
153 {"x && (y", &SyntaxError{Offset: 5, Err: "missing close paren"}},
154 }
155
156 func TestParseError(t *testing.T) {
157 for i, tt := range parseExprErrorTests {
158 t.Run(fmt.Sprint(i), func(t *testing.T) {
159 x, err := parseExpr(tt.in)
160 if err == nil {
161 t.Fatalf("parseExpr(%q) = %v, want error", tt.in, x)
162 }
163 if !reflect.DeepEqual(err, tt.err) {
164 t.Fatalf("parseExpr(%q): wrong error:\nhave %#v\nwant %#v", tt.in, err, tt.err)
165 }
166 })
167 }
168 }
169
170 var exprEvalTests = []struct {
171 in string
172 ok bool
173 tags string
174 }{
175 {"x", false, "x"},
176 {"x && y", false, "x y"},
177 {"x || y", false, "x y"},
178 {"!x && yes", true, "x yes"},
179 {"yes || y", true, "y yes"},
180 }
181
182 func TestExprEval(t *testing.T) {
183 for i, tt := range exprEvalTests {
184 t.Run(fmt.Sprint(i), func(t *testing.T) {
185 x, err := parseExpr(tt.in)
186 if err != nil {
187 t.Fatal(err)
188 }
189 tags := make(map[string]bool)
190 wantTags := make(map[string]bool)
191 for _, tag := range strings.Fields(tt.tags) {
192 wantTags[tag] = true
193 }
194 hasTag := func(tag string) bool {
195 tags[tag] = true
196 return tag == "yes"
197 }
198 ok := x.Eval(hasTag)
199 if ok != tt.ok || !maps.Equal(tags, wantTags) {
200 t.Errorf("Eval(%#q):\nhave ok=%v, tags=%v\nwant ok=%v, tags=%v",
201 tt.in, ok, tags, tt.ok, wantTags)
202 }
203 })
204 }
205 }
206
207 var parsePlusBuildExprTests = []struct {
208 in string
209 x Expr
210 }{
211 {"x", tag("x")},
212 {"x,y", and(tag("x"), tag("y"))},
213 {"x y", or(tag("x"), tag("y"))},
214 {"x y,z", or(tag("x"), and(tag("y"), tag("z")))},
215 {"x,y z", or(and(tag("x"), tag("y")), tag("z"))},
216 {"x,!y !z", or(and(tag("x"), not(tag("y"))), not(tag("z")))},
217 {"!! x", or(tag("ignore"), tag("x"))},
218 {"!!x", tag("ignore")},
219 {"!x", not(tag("x"))},
220 {"!", tag("ignore")},
221 {"", tag("ignore")},
222 }
223
224 func TestParsePlusBuildExpr(t *testing.T) {
225 for i, tt := range parsePlusBuildExprTests {
226 t.Run(fmt.Sprint(i), func(t *testing.T) {
227 x, _ := parsePlusBuildExpr(tt.in)
228 if x.String() != tt.x.String() {
229 t.Errorf("parsePlusBuildExpr(%q):\nhave %v\nwant %v", tt.in, x, tt.x)
230 }
231 })
232 }
233 }
234
235 var constraintTests = []struct {
236 in string
237 x Expr
238 err string
239 }{
240 {"//+build !", tag("ignore"), ""},
241 {"//+build", tag("ignore"), ""},
242 {"//+build x y", or(tag("x"), tag("y")), ""},
243 {"// +build x y \n", or(tag("x"), tag("y")), ""},
244 {"// +build x y \n ", nil, "not a build constraint"},
245 {"// +build x y \nmore", nil, "not a build constraint"},
246 {" //+build x y", nil, "not a build constraint"},
247
248 {"//go:build x && y", and(tag("x"), tag("y")), ""},
249 {"//go:build x && y\n", and(tag("x"), tag("y")), ""},
250 {"//go:build x && y\n ", nil, "not a build constraint"},
251 {"//go:build x && y\nmore", nil, "not a build constraint"},
252 {" //go:build x && y", nil, "not a build constraint"},
253 {"//go:build\n", nil, "unexpected end of expression"},
254 }
255
256 func TestParse(t *testing.T) {
257 for i, tt := range constraintTests {
258 t.Run(fmt.Sprint(i), func(t *testing.T) {
259 x, err := Parse(tt.in)
260 if err != nil {
261 if tt.err == "" {
262 t.Errorf("Constraint(%q): unexpected error: %v", tt.in, err)
263 } else if !strings.Contains(err.Error(), tt.err) {
264 t.Errorf("Constraint(%q): error %v, want %v", tt.in, err, tt.err)
265 }
266 return
267 }
268 if tt.err != "" {
269 t.Errorf("Constraint(%q) = %v, want error %v", tt.in, x, tt.err)
270 return
271 }
272 if x.String() != tt.x.String() {
273 t.Errorf("Constraint(%q):\nhave %v\nwant %v", tt.in, x, tt.x)
274 }
275 })
276 }
277 }
278
279 var plusBuildLinesTests = []struct {
280 in string
281 out []string
282 err error
283 }{
284 {"x", []string{"x"}, nil},
285 {"x && !y", []string{"x,!y"}, nil},
286 {"x || y", []string{"x y"}, nil},
287 {"x && (y || z)", []string{"x", "y z"}, nil},
288 {"!(x && y)", []string{"!x !y"}, nil},
289 {"x || (y && z)", []string{"x y,z"}, nil},
290 {"w && (x || (y && z))", []string{"w", "x y,z"}, nil},
291 {"v || (w && (x || (y && z)))", nil, errComplex},
292 }
293
294 func TestPlusBuildLines(t *testing.T) {
295 for i, tt := range plusBuildLinesTests {
296 t.Run(fmt.Sprint(i), func(t *testing.T) {
297 x, err := parseExpr(tt.in)
298 if err != nil {
299 t.Fatal(err)
300 }
301 lines, err := PlusBuildLines(x)
302 if err != nil {
303 if tt.err == nil {
304 t.Errorf("PlusBuildLines(%q): unexpected error: %v", tt.in, err)
305 } else if tt.err != err {
306 t.Errorf("PlusBuildLines(%q): error %v, want %v", tt.in, err, tt.err)
307 }
308 return
309 }
310 if tt.err != nil {
311 t.Errorf("PlusBuildLines(%q) = %v, want error %v", tt.in, lines, tt.err)
312 return
313 }
314 var want []string
315 for _, line := range tt.out {
316 want = append(want, "// +build "+line)
317 }
318 if !slices.Equal(lines, want) {
319 t.Errorf("PlusBuildLines(%q):\nhave %q\nwant %q", tt.in, lines, want)
320 }
321 })
322 }
323 }
324
325 func TestSizeLimits(t *testing.T) {
326 for _, tc := range []struct {
327 name string
328 expr string
329 }{
330 {
331 name: "go:build or limit",
332 expr: "//go:build " + strings.Repeat("a || ", maxSize+2),
333 },
334 {
335 name: "go:build and limit",
336 expr: "//go:build " + strings.Repeat("a && ", maxSize+2),
337 },
338 {
339 name: "go:build and depth limit",
340 expr: "//go:build " + strings.Repeat("(a &&", maxSize+2),
341 },
342 {
343 name: "go:build or depth limit",
344 expr: "//go:build " + strings.Repeat("(a ||", maxSize+2),
345 },
346 } {
347 t.Run(tc.name, func(t *testing.T) {
348 _, err := Parse(tc.expr)
349 if err == nil {
350 t.Error("expression did not trigger limit")
351 } else if syntaxErr, ok := err.(*SyntaxError); !ok || syntaxErr.Err != "build expression too large" {
352 if !ok {
353 t.Errorf("unexpected error: %v", err)
354 } else {
355 t.Errorf("unexpected syntax error: %s", syntaxErr.Err)
356 }
357 }
358 })
359 }
360 }
361
362 func TestPlusSizeLimits(t *testing.T) {
363 maxOldSize := 100
364 for _, tc := range []struct {
365 name string
366 expr string
367 }{
368 {
369 name: "+build or limit",
370 expr: "// +build " + strings.Repeat("a ", maxOldSize+2),
371 },
372 {
373 name: "+build and limit",
374 expr: "// +build " + strings.Repeat("a,", maxOldSize+2),
375 },
376 } {
377 t.Run(tc.name, func(t *testing.T) {
378 _, err := Parse(tc.expr)
379 if err == nil {
380 t.Error("expression did not trigger limit")
381 } else if err != errComplex {
382 t.Errorf("unexpected error: got %q, want %q", err, errComplex)
383 }
384 })
385 }
386 }
387
View as plain text