From a5385580035abaf4ce6aeb2835e70371f4fde77a Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Tue, 13 Aug 2013 12:25:41 -0400 Subject: [PATCH] test/run: process build tags like go/build R=bradfitz, dave, rsc, r CC=golang-dev https://golang.org/cl/10001045 --- test/run.go | 87 +++++++++++++++++++++++++++++++++++++++++----------- test/testlib | 51 +++++++++++++++++++++--------- 2 files changed, 105 insertions(+), 33 deletions(-) diff --git a/test/run.go b/test/run.go index 5e167d6b0c..3535532406 100644 --- a/test/run.go +++ b/test/run.go @@ -27,6 +27,7 @@ import ( "sort" "strconv" "strings" + "unicode" ) var ( @@ -299,14 +300,17 @@ func goDirPackages(longdir string) ([][]string, error) { return pkgs, nil } +type context struct { + GOOS string + GOARCH string +} + // shouldTest looks for build tags in a source file and returns // whether the file should be used according to the tags. func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) { if idx := strings.Index(src, "\npackage"); idx >= 0 { src = src[:idx] } - notgoos := "!" + goos - notgoarch := "!" + goarch for _, line := range strings.Split(src, "\n") { line = strings.TrimSpace(line) if strings.HasPrefix(line, "//") { @@ -318,29 +322,59 @@ func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) { if len(line) == 0 || line[0] != '+' { continue } + ctxt := &context{ + GOOS: goos, + GOARCH: goarch, + } words := strings.Fields(line) if words[0] == "+build" { - for _, word := range words { - switch word { - case goos, goarch: - return true, "" - case notgoos, notgoarch: - continue - default: - if word[0] == '!' { - // NOT something-else - return true, "" - } + ok := false + for _, word := range words[1:] { + if ctxt.match(word) { + ok = true + break } } - // no matching tag found. - return false, line + if !ok { + // no matching tag found. + return false, line + } } } - // no build tags. + // no build tags return true, "" } +func (ctxt *context) match(name string) bool { + if name == "" { + return false + } + if i := strings.Index(name, ","); i >= 0 { + // comma-separated list + return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) + } + if strings.HasPrefix(name, "!!") { // bad syntax, reject always + return false + } + if strings.HasPrefix(name, "!") { // negation + return len(name) > 1 && !ctxt.match(name[1:]) + } + + // Tags must be letters, digits, underscores or dots. + // Unlike in Go identifiers, all digits are fine (e.g., "386"). + for _, c := range name { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { + return false + } + } + + if name == ctxt.GOOS || name == ctxt.GOARCH { + return true + } + + return false +} + func init() { checkShouldTest() } // run runs a test. @@ -815,7 +849,7 @@ func defaultRunOutputLimit() int { return cpu } -// checkShouldTest runs canity checks on the shouldTest function. +// checkShouldTest runs sanity checks on the shouldTest function. func checkShouldTest() { assert := func(ok bool, _ string) { if !ok { @@ -823,11 +857,28 @@ func checkShouldTest() { } } assertNot := func(ok bool, _ string) { assert(!ok, "") } + + // Simple tests. assert(shouldTest("// +build linux", "linux", "arm")) assert(shouldTest("// +build !windows", "linux", "arm")) assertNot(shouldTest("// +build !windows", "windows", "amd64")) - assertNot(shouldTest("// +build arm 386", "linux", "amd64")) + + // A file with no build tags will always be tested. assert(shouldTest("// This is a test.", "os", "arch")) + + // Build tags separated by a space are OR-ed together. + assertNot(shouldTest("// +build arm 386", "linux", "amd64")) + + // Build tags seperated by a comma are AND-ed together. + assertNot(shouldTest("// +build !windows,!plan9", "windows", "amd64")) + assertNot(shouldTest("// +build !windows,!plan9", "plan9", "386")) + + // Build tags on multiple lines are AND-ed together. + assert(shouldTest("// +build !windows\n// +build amd64", "linux", "amd64")) + assertNot(shouldTest("// +build !windows\n// +build amd64", "windows", "amd64")) + + // Test that (!a OR !b) matches anything. + assert(shouldTest("// +build !windows !plan9", "windows", "amd64")) } // envForDir returns a copy of the environment diff --git a/test/testlib b/test/testlib index de138b1d19..4a17f4feb9 100644 --- a/test/testlib +++ b/test/testlib @@ -16,29 +16,50 @@ pkgs() { done | sort } +_match() { + case $1 in + *,*) + #echo >&2 "match comma separated $1" + first=$(echo $1 | sed 's/,.*//') + rest=$(echo $1 | sed 's/[^,]*,//') + if _match $first && _match $rest; then + return 0 + fi + return 1 + ;; + '!'*) + #echo >&2 "match negation $1" + neg=$(echo $1 | sed 's/^!//') + if _match $neg; then + return 1 + fi + return 0 + ;; + $GOARCH|$GOOS) + #echo >&2 "match GOARCH or GOOS $1" + return 0 + ;; + esac + return 1 +} + # +build aborts execution if the supplied tags don't match, # i.e. none of the tags (x or !x) matches GOARCH or GOOS. +build() { if (( $# == 0 )); then return fi + m=0 for tag; do - case $tag in - $GOARCH|$GOOS) - #echo >&2 "match $tag in $1" - return # don't exclude. - ;; - '!'$GOARCH|'!'$GOOS) - ;; - '!'*) - # not x where x is neither GOOS nor GOARCH. - #echo >&2 "match $tag in $1" - return # don't exclude - ;; - esac + if _match $tag; then + m=1 + fi done - # no match. - exit 0 + if [ $m = 0 ]; then + #echo >&2 no match + exit 0 + fi + unset m } compile() {