Source file src/go/build/constraint/expr_test.go

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     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