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