mirror of https://github.com/golang/go.git
cmd/go: quote expanded shell variables used within regular expressions
We mostly use shell variables for paths, and we don't want file paths like "C:\work\go1.4" to turn into regular expressions. Updates #30228 Updates #30241 Change-Id: If18b775b2f8b2821eaf197c4be4a322066af839f Reviewed-on: https://go-review.googlesource.com/c/164626 Run-TryBot: Bryan C. Mills <bcmills@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Jay Conrod <jayconrod@google.com>
This commit is contained in:
parent
45861a64d3
commit
b45f5b5e16
|
|
@ -192,7 +192,7 @@ func (ts *testScript) run() {
|
|||
a, err := txtar.ParseFile(ts.file)
|
||||
ts.check(err)
|
||||
for _, f := range a.Files {
|
||||
name := ts.mkabs(ts.expand(f.Name))
|
||||
name := ts.mkabs(ts.expand(f.Name, false))
|
||||
ts.check(os.MkdirAll(filepath.Dir(name), 0777))
|
||||
ts.check(ioutil.WriteFile(name, f.Data, 0666))
|
||||
}
|
||||
|
|
@ -238,34 +238,24 @@ Script:
|
|||
}
|
||||
|
||||
// Parse input line. Ignore blanks entirely.
|
||||
args := ts.parse(line)
|
||||
if len(args) == 0 {
|
||||
parsed := ts.parse(line)
|
||||
if parsed.name == "" {
|
||||
if parsed.neg || len(parsed.conds) > 0 {
|
||||
ts.fatalf("missing command")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Echo command to log.
|
||||
fmt.Fprintf(&ts.log, "> %s\n", line)
|
||||
|
||||
// Command prefix [cond] means only run this command if cond is satisfied.
|
||||
for strings.HasPrefix(args[0], "[") && strings.HasSuffix(args[0], "]") {
|
||||
cond := args[0]
|
||||
cond = cond[1 : len(cond)-1]
|
||||
cond = strings.TrimSpace(cond)
|
||||
args = args[1:]
|
||||
if len(args) == 0 {
|
||||
ts.fatalf("missing command after condition")
|
||||
}
|
||||
want := true
|
||||
if strings.HasPrefix(cond, "!") {
|
||||
want = false
|
||||
cond = strings.TrimSpace(cond[1:])
|
||||
}
|
||||
for _, cond := range parsed.conds {
|
||||
// Known conds are: $GOOS, $GOARCH, runtime.Compiler, and 'short' (for testing.Short).
|
||||
//
|
||||
// NOTE: If you make changes here, update testdata/script/README too!
|
||||
//
|
||||
ok := false
|
||||
switch cond {
|
||||
switch cond.tag {
|
||||
case runtime.GOOS, runtime.GOARCH, runtime.Compiler:
|
||||
ok = true
|
||||
case "short":
|
||||
|
|
@ -285,8 +275,8 @@ Script:
|
|||
case "symlink":
|
||||
ok = testenv.HasSymlink()
|
||||
default:
|
||||
if strings.HasPrefix(cond, "exec:") {
|
||||
prog := cond[len("exec:"):]
|
||||
if strings.HasPrefix(cond.tag, "exec:") {
|
||||
prog := cond.tag[len("exec:"):]
|
||||
ok = execCache.Do(prog, func() interface{} {
|
||||
if runtime.GOOS == "plan9" && prog == "git" {
|
||||
// The Git command is usually not the real Git on Plan 9.
|
||||
|
|
@ -298,33 +288,22 @@ Script:
|
|||
}).(bool)
|
||||
break
|
||||
}
|
||||
if !imports.KnownArch[cond] && !imports.KnownOS[cond] && cond != "gc" && cond != "gccgo" {
|
||||
ts.fatalf("unknown condition %q", cond)
|
||||
if !imports.KnownArch[cond.tag] && !imports.KnownOS[cond.tag] && cond.tag != "gc" && cond.tag != "gccgo" {
|
||||
ts.fatalf("unknown condition %q", cond.tag)
|
||||
}
|
||||
}
|
||||
if ok != want {
|
||||
if ok != cond.want {
|
||||
// Don't run rest of line.
|
||||
continue Script
|
||||
}
|
||||
}
|
||||
|
||||
// Command prefix ! means negate the expectations about this command:
|
||||
// go command should fail, match should not be found, etc.
|
||||
neg := false
|
||||
if args[0] == "!" {
|
||||
neg = true
|
||||
args = args[1:]
|
||||
if len(args) == 0 {
|
||||
ts.fatalf("! on line by itself")
|
||||
}
|
||||
}
|
||||
|
||||
// Run command.
|
||||
cmd := scriptCmds[args[0]]
|
||||
cmd := scriptCmds[parsed.name]
|
||||
if cmd == nil {
|
||||
ts.fatalf("unknown command %q", args[0])
|
||||
ts.fatalf("unknown command %q", parsed.name)
|
||||
}
|
||||
cmd(ts, neg, args[1:])
|
||||
cmd(ts, parsed.neg, parsed.args)
|
||||
|
||||
// Command can ask script to stop early.
|
||||
if ts.stopped {
|
||||
|
|
@ -376,6 +355,14 @@ var scriptCmds = map[string]func(*testScript, bool, []string){
|
|||
"wait": (*testScript).cmdWait,
|
||||
}
|
||||
|
||||
// When expanding shell variables for these commands, we apply regexp quoting to
|
||||
// expanded strings within the first argument.
|
||||
var regexpCmd = map[string]bool{
|
||||
"grep": true,
|
||||
"stderr": true,
|
||||
"stdout": true,
|
||||
}
|
||||
|
||||
// addcrlf adds CRLF line endings to the named files.
|
||||
func (ts *testScript) cmdAddcrlf(neg bool, args []string) {
|
||||
if len(args) == 0 {
|
||||
|
|
@ -486,8 +473,8 @@ func (ts *testScript) doCmdCmp(args []string, env bool) {
|
|||
text2 = string(data)
|
||||
|
||||
if env {
|
||||
text1 = ts.expand(text1)
|
||||
text2 = ts.expand(text2)
|
||||
text1 = ts.expand(text1, false)
|
||||
text2 = ts.expand(text2, false)
|
||||
}
|
||||
|
||||
if text1 == text2 {
|
||||
|
|
@ -765,9 +752,11 @@ func scriptMatch(ts *testScript, neg bool, args []string, text, name string) {
|
|||
ts.fatalf("usage: %s [-count=N] 'pattern'%s", name, extraUsage)
|
||||
}
|
||||
|
||||
pattern := args[0]
|
||||
re, err := regexp.Compile(`(?m)` + pattern)
|
||||
ts.check(err)
|
||||
pattern := `(?m)` + args[0]
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
ts.fatalf("regexp.Compile(%q): %v", pattern, err)
|
||||
}
|
||||
|
||||
isGrep := name == "grep"
|
||||
if isGrep {
|
||||
|
|
@ -956,8 +945,20 @@ func interruptProcess(p *os.Process) {
|
|||
}
|
||||
|
||||
// expand applies environment variable expansion to the string s.
|
||||
func (ts *testScript) expand(s string) string {
|
||||
return os.Expand(s, func(key string) string { return ts.envMap[key] })
|
||||
func (ts *testScript) expand(s string, inRegexp bool) string {
|
||||
return os.Expand(s, func(key string) string {
|
||||
e := ts.envMap[key]
|
||||
if inRegexp {
|
||||
// Replace workdir with $WORK, since we have done the same substitution in
|
||||
// the text we're about to compare against.
|
||||
e = strings.ReplaceAll(e, ts.workdir, "$WORK")
|
||||
|
||||
// Quote to literal strings: we want paths like C:\work\go1.4 to remain
|
||||
// paths rather than regular expressions.
|
||||
e = regexp.QuoteMeta(e)
|
||||
}
|
||||
return e
|
||||
})
|
||||
}
|
||||
|
||||
// fatalf aborts the test with the given failure message.
|
||||
|
|
@ -975,27 +976,82 @@ func (ts *testScript) mkabs(file string) string {
|
|||
return filepath.Join(ts.cd, file)
|
||||
}
|
||||
|
||||
// A condition guards execution of a command.
|
||||
type condition struct {
|
||||
want bool
|
||||
tag string
|
||||
}
|
||||
|
||||
// A command is a complete command parsed from a script.
|
||||
type command struct {
|
||||
neg bool // if true, expect the command to fail
|
||||
conds []condition // all must be satisfied
|
||||
name string // the name of the command; must be non-empty
|
||||
args []string // shell-expanded arguments following name
|
||||
}
|
||||
|
||||
// parse parses a single line as a list of space-separated arguments
|
||||
// subject to environment variable expansion (but not resplitting).
|
||||
// Single quotes around text disable splitting and expansion.
|
||||
// To embed a single quote, double it: 'Don''t communicate by sharing memory.'
|
||||
func (ts *testScript) parse(line string) []string {
|
||||
func (ts *testScript) parse(line string) command {
|
||||
ts.line = line
|
||||
|
||||
var (
|
||||
args []string
|
||||
arg string // text of current arg so far (need to add line[start:i])
|
||||
start = -1 // if >= 0, position where current arg text chunk starts
|
||||
quoted = false // currently processing quoted text
|
||||
cmd command
|
||||
arg string // text of current arg so far (need to add line[start:i])
|
||||
start = -1 // if >= 0, position where current arg text chunk starts
|
||||
quoted = false // currently processing quoted text
|
||||
isRegexp = false // currently processing unquoted regular expression
|
||||
)
|
||||
|
||||
flushArg := func() {
|
||||
defer func() {
|
||||
arg = ""
|
||||
start = -1
|
||||
}()
|
||||
|
||||
if cmd.name != "" {
|
||||
cmd.args = append(cmd.args, arg)
|
||||
isRegexp = false // Commands take only one regexp argument, so no subsequent args are regexps.
|
||||
return
|
||||
}
|
||||
|
||||
// Command prefix ! means negate the expectations about this command:
|
||||
// go command should fail, match should not be found, etc.
|
||||
if arg == "!" {
|
||||
if cmd.neg {
|
||||
ts.fatalf("duplicated '!' token")
|
||||
}
|
||||
cmd.neg = true
|
||||
return
|
||||
}
|
||||
|
||||
// Command prefix [cond] means only run this command if cond is satisfied.
|
||||
if strings.HasPrefix(arg, "[") && strings.HasSuffix(arg, "]") {
|
||||
want := true
|
||||
arg = strings.TrimSpace(arg[1 : len(arg)-1])
|
||||
if strings.HasPrefix(arg, "!") {
|
||||
want = false
|
||||
arg = strings.TrimSpace(arg[1:])
|
||||
}
|
||||
if arg == "" {
|
||||
ts.fatalf("empty condition")
|
||||
}
|
||||
cmd.conds = append(cmd.conds, condition{want: want, tag: arg})
|
||||
return
|
||||
}
|
||||
|
||||
cmd.name = arg
|
||||
isRegexp = regexpCmd[cmd.name]
|
||||
}
|
||||
|
||||
for i := 0; ; i++ {
|
||||
if !quoted && (i >= len(line) || line[i] == ' ' || line[i] == '\t' || line[i] == '\r' || line[i] == '#') {
|
||||
// Found arg-separating space.
|
||||
if start >= 0 {
|
||||
arg += ts.expand(line[start:i])
|
||||
args = append(args, arg)
|
||||
start = -1
|
||||
arg = ""
|
||||
arg += ts.expand(line[start:i], isRegexp)
|
||||
flushArg()
|
||||
}
|
||||
if i >= len(line) || line[i] == '#' {
|
||||
break
|
||||
|
|
@ -1009,7 +1065,7 @@ func (ts *testScript) parse(line string) []string {
|
|||
if !quoted {
|
||||
// starting a quoted chunk
|
||||
if start >= 0 {
|
||||
arg += ts.expand(line[start:i])
|
||||
arg += ts.expand(line[start:i], isRegexp)
|
||||
}
|
||||
start = i + 1
|
||||
quoted = true
|
||||
|
|
@ -1033,7 +1089,7 @@ func (ts *testScript) parse(line string) []string {
|
|||
start = i
|
||||
}
|
||||
}
|
||||
return args
|
||||
return cmd
|
||||
}
|
||||
|
||||
// diff returns a formatted diff of the two texts,
|
||||
|
|
|
|||
Loading…
Reference in New Issue