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))
}
}
// 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":
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.
}).(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 {
"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 {
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 {
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 {
}
// 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.
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
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
start = i
}
}
- return args
+ return cmd
}
// diff returns a formatted diff of the two texts,