blob: 4979f8b5f28978e8e891b818e5df709b3eccf17a [file] [log] [blame]
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package constraint
import (
"fmt"
"reflect"
"strings"
"testing"
)
var exprStringTests = []struct {
x Expr
out string
}{
{
x: tag("abc"),
out: "abc",
},
{
x: not(tag("abc")),
out: "!abc",
},
{
x: not(and(tag("abc"), tag("def"))),
out: "!(abc && def)",
},
{
x: and(tag("abc"), or(tag("def"), tag("ghi"))),
out: "abc && (def || ghi)",
},
{
x: or(and(tag("abc"), tag("def")), tag("ghi")),
out: "(abc && def) || ghi",
},
}
func TestExprString(t *testing.T) {
for i, tt := range exprStringTests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
s := tt.x.String()
if s != tt.out {
t.Errorf("String() mismatch:\nhave %s\nwant %s", s, tt.out)
}
})
}
}
var lexTests = []struct {
in string
out string
}{
{"", ""},
{"x", "x"},
{"x.y", "x.y"},
{"x_y", "x_y"},
{"αx", "αx"},
{"αx²", "αx err: invalid syntax at ²"},
{"go1.2", "go1.2"},
{"x y", "x y"},
{"x!y", "x ! y"},
{"&&||!()xy yx ", "&& || ! ( ) xy yx"},
{"x~", "x err: invalid syntax at ~"},
{"x ~", "x err: invalid syntax at ~"},
{"x &", "x err: invalid syntax at &"},
{"x &y", "x err: invalid syntax at &"},
}
func TestLex(t *testing.T) {
for i, tt := range lexTests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
p := &exprParser{s: tt.in}
out := ""
for {
tok, err := lexHelp(p)
if tok == "" && err == nil {
break
}
if out != "" {
out += " "
}
if err != nil {
out += "err: " + err.Error()
break
}
out += tok
}
if out != tt.out {
t.Errorf("lex(%q):\nhave %s\nwant %s", tt.in, out, tt.out)
}
})
}
}
func lexHelp(p *exprParser) (tok string, err error) {
defer func() {
if e := recover(); e != nil {
if e, ok := e.(*SyntaxError); ok {
err = e
return
}
panic(e)
}
}()
p.lex()
return p.tok, nil
}
var parseExprTests = []struct {
in string
x Expr
}{
{"x", tag("x")},
{"x&&y", and(tag("x"), tag("y"))},
{"x||y", or(tag("x"), tag("y"))},
{"(x)", tag("x")},
{"x||y&&z", or(tag("x"), and(tag("y"), tag("z")))},
{"x&&y||z", or(and(tag("x"), tag("y")), tag("z"))},
{"x&&(y||z)", and(tag("x"), or(tag("y"), tag("z")))},
{"(x||y)&&z", and(or(tag("x"), tag("y")), tag("z"))},
{"!(x&&y)", not(and(tag("x"), tag("y")))},
}
func TestParseExpr(t *testing.T) {
for i, tt := range parseExprTests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
x, err := parseExpr(tt.in)
if err != nil {
t.Fatal(err)
}
if x.String() != tt.x.String() {
t.Errorf("parseExpr(%q):\nhave %s\nwant %s", tt.in, x, tt.x)
}
})
}
}
var parseExprErrorTests = []struct {
in string
err error
}{
{"x && ", &SyntaxError{Offset: 5, Err: "unexpected end of expression"}},
{"x && (", &SyntaxError{Offset: 6, Err: "missing close paren"}},
{"x && ||", &SyntaxError{Offset: 5, Err: "unexpected token ||"}},
{"x && !", &SyntaxError{Offset: 6, Err: "unexpected end of expression"}},
{"x && !!", &SyntaxError{Offset: 6, Err: "double negation not allowed"}},
{"x !", &SyntaxError{Offset: 2, Err: "unexpected token !"}},
{"x && (y", &SyntaxError{Offset: 5, Err: "missing close paren"}},
}
func TestParseError(t *testing.T) {
for i, tt := range parseExprErrorTests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
x, err := parseExpr(tt.in)
if err == nil {
t.Fatalf("parseExpr(%q) = %v, want error", tt.in, x)
}
if !reflect.DeepEqual(err, tt.err) {
t.Fatalf("parseExpr(%q): wrong error:\nhave %#v\nwant %#v", tt.in, err, tt.err)
}
})
}
}
var exprEvalTests = []struct {
in string
ok bool
tags string
}{
{"x", false, "x"},
{"x && y", false, "x y"},
{"x || y", false, "x y"},
{"!x && yes", true, "x yes"},
{"yes || y", true, "y yes"},
}
func TestExprEval(t *testing.T) {
for i, tt := range exprEvalTests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
x, err := parseExpr(tt.in)
if err != nil {
t.Fatal(err)
}
tags := make(map[string]bool)
wantTags := make(map[string]bool)
for _, tag := range strings.Fields(tt.tags) {
wantTags[tag] = true
}
hasTag := func(tag string) bool {
tags[tag] = true
return tag == "yes"
}
ok := x.Eval(hasTag)
if ok != tt.ok || !reflect.DeepEqual(tags, wantTags) {
t.Errorf("Eval(%#q):\nhave ok=%v, tags=%v\nwant ok=%v, tags=%v",
tt.in, ok, tags, tt.ok, wantTags)
}
})
}
}
var parsePlusBuildExprTests = []struct {
in string
x Expr
}{
{"x", tag("x")},
{"x,y", and(tag("x"), tag("y"))},
{"x y", or(tag("x"), tag("y"))},
{"x y,z", or(tag("x"), and(tag("y"), tag("z")))},
{"x,y z", or(and(tag("x"), tag("y")), tag("z"))},
{"x,!y !z", or(and(tag("x"), not(tag("y"))), not(tag("z")))},
{"!! x", or(tag("ignore"), tag("x"))},
{"!!x", tag("ignore")},
{"!x", not(tag("x"))},
{"!", tag("ignore")},
}
func TestParsePlusBuildExpr(t *testing.T) {
for i, tt := range parsePlusBuildExprTests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
x := parsePlusBuildExpr(tt.in)
if x.String() != tt.x.String() {
t.Errorf("parsePlusBuildExpr(%q):\nhave %v\nwant %v", tt.in, x, tt.x)
}
})
}
}
var constraintTests = []struct {
in string
x Expr
err error
}{
{"//+build x y", or(tag("x"), tag("y")), nil},
{"// +build x y \n", or(tag("x"), tag("y")), nil},
{"// +build x y \n ", nil, errNotConstraint},
{"// +build x y \nmore", nil, errNotConstraint},
{" //+build x y", nil, errNotConstraint},
{"//go:build x && y", and(tag("x"), tag("y")), nil},
{"//go:build x && y\n", and(tag("x"), tag("y")), nil},
{"//go:build x && y\n ", nil, errNotConstraint},
{"//go:build x && y\nmore", nil, errNotConstraint},
{" //go:build x && y", nil, errNotConstraint},
}
func TestParse(t *testing.T) {
for i, tt := range constraintTests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
x, err := Parse(tt.in)
if err != nil {
if tt.err == nil {
t.Errorf("Constraint(%q): unexpected error: %v", tt.in, err)
} else if tt.err != err {
t.Errorf("Constraint(%q): error %v, want %v", tt.in, err, tt.err)
}
return
}
if tt.err != nil {
t.Errorf("Constraint(%q) = %v, want error %v", tt.in, x, tt.err)
return
}
if x.String() != tt.x.String() {
t.Errorf("Constraint(%q):\nhave %v\nwant %v", tt.in, x, tt.x)
}
})
}
}
var plusBuildLinesTests = []struct {
in string
out []string
err error
}{
{"x", []string{"x"}, nil},
{"x && !y", []string{"x,!y"}, nil},
{"x || y", []string{"x y"}, nil},
{"x && (y || z)", []string{"x", "y z"}, nil},
{"!(x && y)", []string{"!x !y"}, nil},
{"x || (y && z)", []string{"x y,z"}, nil},
{"w && (x || (y && z))", []string{"w", "x y,z"}, nil},
{"v || (w && (x || (y && z)))", nil, errComplex},
}
func TestPlusBuildLines(t *testing.T) {
for i, tt := range plusBuildLinesTests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
x, err := parseExpr(tt.in)
if err != nil {
t.Fatal(err)
}
lines, err := PlusBuildLines(x)
if err != nil {
if tt.err == nil {
t.Errorf("PlusBuildLines(%q): unexpected error: %v", tt.in, err)
} else if tt.err != err {
t.Errorf("PlusBuildLines(%q): error %v, want %v", tt.in, err, tt.err)
}
return
}
if tt.err != nil {
t.Errorf("PlusBuildLines(%q) = %v, want error %v", tt.in, lines, tt.err)
return
}
var want []string
for _, line := range tt.out {
want = append(want, "// +build "+line)
}
if !reflect.DeepEqual(lines, want) {
t.Errorf("PlusBuildLines(%q):\nhave %q\nwant %q", tt.in, lines, want)
}
})
}
}