buildtags: add Evaluate method

Updates #3
This commit is contained in:
Michael McLoughlin
2019-01-02 00:10:55 -08:00
parent beeb9ed525
commit d09e9ce5f9
2 changed files with 107 additions and 11 deletions

View File

@@ -34,6 +34,7 @@ import (
type Interface interface {
ConstraintsConvertable
fmt.GoStringer
Evaluate(v map[string]bool) bool
Validate() error
}
@@ -66,6 +67,15 @@ func (cs Constraints) Validate() error {
}
return nil
}
func (cs Constraints) Evaluate(v map[string]bool) bool {
r := true
for _, c := range cs {
r = r && c.Evaluate(v)
}
return r
}
func (cs Constraints) GoString() string {
s := ""
for _, c := range cs {
@@ -86,6 +96,14 @@ func (c Constraint) Validate() error {
return nil
}
func (c Constraint) Evaluate(v map[string]bool) bool {
r := false
for _, o := range c {
r = r || o.Evaluate(v)
}
return r
}
func (c Constraint) GoString() string {
s := "// +build"
for _, o := range c {
@@ -107,6 +125,14 @@ func (o Option) Validate() error {
return nil
}
func (o Option) Evaluate(v map[string]bool) bool {
r := true
for _, t := range o {
r = r && t.Evaluate(v)
}
return r
}
func (o Option) GoString() string {
var ts []string
for _, t := range o {
@@ -119,22 +145,24 @@ func (t Term) ToConstraints() Constraints { return t.ToOption().ToConstraints()
func (t Term) ToConstraint() Constraint { return t.ToOption().ToConstraint() }
func (t Term) ToOption() Option { return Option{t} }
func (t Term) Validate() error {
name := string(t)
func (t Term) IsNegated() bool { return strings.HasPrefix(string(t), "!") }
func (t Term) Name() string {
return strings.TrimPrefix(string(t), "!")
}
func (t Term) Validate() error {
// Reference: https://github.com/golang/go/blob/204a8f55dc2e0ac8d27a781dab0da609b98560da/src/cmd/go/internal/imports/build.go#L110-L112
//
// if strings.HasPrefix(name, "!!") { // bad syntax, reject always
// return false
// }
//
if strings.HasPrefix(name, "!!") {
if strings.HasPrefix(string(t), "!!") {
return errors.New("at most one '!' allowed")
}
name = strings.TrimPrefix(name, "!")
if len(name) == 0 {
if len(t.Name()) == 0 {
return errors.New("empty tag name")
}
@@ -148,7 +176,7 @@ func (t Term) Validate() error {
// }
// }
//
for _, c := range name {
for _, c := range t.Name() {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
return fmt.Errorf("character '%c' disallowed in tags", c)
}
@@ -157,6 +185,10 @@ func (t Term) Validate() error {
return nil
}
func (t Term) Evaluate(v map[string]bool) bool {
return (t.Validate() == nil) && (v[t.Name()] == !t.IsNegated())
}
func (t Term) GoString() string { return string(t) }
func Not(ident string) Term {
@@ -202,3 +234,11 @@ func ParseConstraint(expr string) (Constraint, error) {
}
return c, nil
}
func SetTags(names ...string) map[string]bool {
v := map[string]bool{}
for _, n := range names {
v[n] = true
}
return v
}

View File

@@ -65,10 +65,7 @@ func TestParseConstraintRoundTrip(t *testing.T) {
"linux,386 darwin,!cgo",
}
for _, expr := range exprs {
c, err := ParseConstraint(expr)
if err != nil {
t.Fatalf("error parsing expression %q: %q", expr, err)
}
c := AssertParseConstraint(t, expr)
got := c.GoString()
expect := "// +build " + expr + "\n"
if got != expect {
@@ -84,3 +81,62 @@ func TestParseConstraintError(t *testing.T) {
t.Fatalf("expected error parsing %q", expr)
}
}
func TestEvaluate(t *testing.T) {
cases := []struct {
Constraint Interface
Values map[string]bool
Expect bool
}{
{Term("a"), SetTags("a"), true},
{Term("!a"), SetTags("a"), false},
{Term("!a"), SetTags(), true},
{Term("inval-id"), SetTags("inval-id"), false},
{Opt(Term("a"), Term("b")), SetTags(), false},
{Opt(Term("a"), Term("b")), SetTags("a"), false},
{Opt(Term("a"), Term("b")), SetTags("b"), false},
{Opt(Term("a"), Term("b")), SetTags("a", "b"), true},
{Opt(Term("a"), Term("b-a-d")), SetTags("a", "b-a-d"), false},
{
Any(Opt(Term("linux"), Term("386")), Opt("darwin", Not("cgo"))),
SetTags("linux", "386"),
true,
},
{
Any(Opt(Term("linux"), Term("386")), Opt("darwin", Not("cgo"))),
SetTags("darwin"),
true,
},
{
Any(Opt(Term("linux"), Term("386")), Opt("darwin", Not("cgo"))),
SetTags("linux", "darwin", "cgo"),
false,
},
{
And(Any(Term("linux"), Term("darwin")), Term("386")),
SetTags("darwin", "386"),
true,
},
}
for _, c := range cases {
got := c.Constraint.Evaluate(c.Values)
if c.Constraint.Validate() != nil && got {
t.Fatal("invalid expressions must evaluate false")
}
if got != c.Expect {
t.Errorf("%#v evaluated with %#v got %v expect %v", c.Constraint, c.Values, got, c.Expect)
}
}
}
func AssertParseConstraint(t *testing.T, expr string) Constraint {
t.Helper()
c, err := ParseConstraint(expr)
if err != nil {
t.Fatalf("error parsing expression %q: %q", expr, err)
}
return c
}