...

Source file src/github.com/rogpeppe/go-internal/testscript/cmd.go

Documentation: github.com/rogpeppe/go-internal/testscript

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package testscript
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"regexp"
    16  	"runtime"
    17  	"strconv"
    18  	"strings"
    19  
    20  	"github.com/rogpeppe/go-internal/diff"
    21  	"github.com/rogpeppe/go-internal/testscript/internal/pty"
    22  	"github.com/rogpeppe/go-internal/txtar"
    23  )
    24  
    25  // scriptCmds are the script command implementations.
    26  // Keep list and the implementations below sorted by name.
    27  //
    28  // NOTE: If you make changes here, update doc.go.
    29  var scriptCmds = map[string]func(*TestScript, bool, []string){
    30  	"cd":       (*TestScript).cmdCd,
    31  	"chmod":    (*TestScript).cmdChmod,
    32  	"cmp":      (*TestScript).cmdCmp,
    33  	"cmpenv":   (*TestScript).cmdCmpenv,
    34  	"cp":       (*TestScript).cmdCp,
    35  	"env":      (*TestScript).cmdEnv,
    36  	"exec":     (*TestScript).cmdExec,
    37  	"exists":   (*TestScript).cmdExists,
    38  	"grep":     (*TestScript).cmdGrep,
    39  	"mkdir":    (*TestScript).cmdMkdir,
    40  	"mv":       (*TestScript).cmdMv,
    41  	"rm":       (*TestScript).cmdRm,
    42  	"skip":     (*TestScript).cmdSkip,
    43  	"stderr":   (*TestScript).cmdStderr,
    44  	"stdin":    (*TestScript).cmdStdin,
    45  	"stdout":   (*TestScript).cmdStdout,
    46  	"ttyin":    (*TestScript).cmdTtyin,
    47  	"ttyout":   (*TestScript).cmdTtyout,
    48  	"stop":     (*TestScript).cmdStop,
    49  	"symlink":  (*TestScript).cmdSymlink,
    50  	"unix2dos": (*TestScript).cmdUNIX2DOS,
    51  	"unquote":  (*TestScript).cmdUnquote,
    52  	"wait":     (*TestScript).cmdWait,
    53  }
    54  
    55  // cd changes to a different directory.
    56  func (ts *TestScript) cmdCd(neg bool, args []string) {
    57  	if neg {
    58  		ts.Fatalf("unsupported: ! cd")
    59  	}
    60  	if len(args) != 1 {
    61  		ts.Fatalf("usage: cd dir")
    62  	}
    63  
    64  	dir := args[0]
    65  	if !filepath.IsAbs(dir) {
    66  		dir = filepath.Join(ts.cd, dir)
    67  	}
    68  	info, err := os.Stat(dir)
    69  	if os.IsNotExist(err) {
    70  		ts.Fatalf("directory %s does not exist", dir)
    71  	}
    72  	ts.Check(err)
    73  	if !info.IsDir() {
    74  		ts.Fatalf("%s is not a directory", dir)
    75  	}
    76  	ts.cd = dir
    77  	ts.Logf("%s\n", ts.cd)
    78  }
    79  
    80  func (ts *TestScript) cmdChmod(neg bool, args []string) {
    81  	if neg {
    82  		ts.Fatalf("unsupported: ! chmod")
    83  	}
    84  	if len(args) != 2 {
    85  		ts.Fatalf("usage: chmod perm paths...")
    86  	}
    87  	perm, err := strconv.ParseUint(args[0], 8, 32)
    88  	if err != nil || perm&uint64(os.ModePerm) != perm {
    89  		ts.Fatalf("invalid mode: %s", args[0])
    90  	}
    91  	for _, arg := range args[1:] {
    92  		path := arg
    93  		if !filepath.IsAbs(path) {
    94  			path = filepath.Join(ts.cd, arg)
    95  		}
    96  		err := os.Chmod(path, os.FileMode(perm))
    97  		ts.Check(err)
    98  	}
    99  }
   100  
   101  // cmp compares two files.
   102  func (ts *TestScript) cmdCmp(neg bool, args []string) {
   103  	if len(args) != 2 {
   104  		ts.Fatalf("usage: cmp file1 file2")
   105  	}
   106  
   107  	ts.doCmdCmp(neg, args, false)
   108  }
   109  
   110  // cmpenv compares two files with environment variable substitution.
   111  func (ts *TestScript) cmdCmpenv(neg bool, args []string) {
   112  	if len(args) != 2 {
   113  		ts.Fatalf("usage: cmpenv file1 file2")
   114  	}
   115  	ts.doCmdCmp(neg, args, true)
   116  }
   117  
   118  func (ts *TestScript) doCmdCmp(neg bool, args []string, env bool) {
   119  	name1, name2 := args[0], args[1]
   120  	text1 := ts.ReadFile(name1)
   121  
   122  	absName2 := ts.MkAbs(name2)
   123  	data, err := ioutil.ReadFile(absName2)
   124  	ts.Check(err)
   125  	text2 := string(data)
   126  	if env {
   127  		text2 = ts.expand(text2)
   128  	}
   129  	eq := text1 == text2
   130  	if neg {
   131  		if eq {
   132  			ts.Fatalf("%s and %s do not differ", name1, name2)
   133  		}
   134  		return // they differ, as expected
   135  	}
   136  	if eq {
   137  		return // they are equal, as expected
   138  	}
   139  	if ts.params.UpdateScripts && !env {
   140  		if scriptFile, ok := ts.scriptFiles[absName2]; ok {
   141  			ts.scriptUpdates[scriptFile] = text1
   142  			return
   143  		}
   144  		// The file being compared against isn't in the txtar archive, so don't
   145  		// update the script.
   146  	}
   147  
   148  	unifiedDiff := diff.Diff(name1, []byte(text1), name2, []byte(text2))
   149  
   150  	ts.Logf("%s", unifiedDiff)
   151  	ts.Fatalf("%s and %s differ", name1, name2)
   152  }
   153  
   154  // cp copies files, maybe eventually directories.
   155  func (ts *TestScript) cmdCp(neg bool, args []string) {
   156  	if neg {
   157  		ts.Fatalf("unsupported: ! cp")
   158  	}
   159  	if len(args) < 2 {
   160  		ts.Fatalf("usage: cp src... dst")
   161  	}
   162  
   163  	dst := ts.MkAbs(args[len(args)-1])
   164  	info, err := os.Stat(dst)
   165  	dstDir := err == nil && info.IsDir()
   166  	if len(args) > 2 && !dstDir {
   167  		ts.Fatalf("cp: destination %s is not a directory", dst)
   168  	}
   169  
   170  	for _, arg := range args[:len(args)-1] {
   171  		var (
   172  			src  string
   173  			data []byte
   174  			mode os.FileMode
   175  		)
   176  		switch arg {
   177  		case "stdout":
   178  			src = arg
   179  			data = []byte(ts.stdout)
   180  			mode = 0o666
   181  		case "stderr":
   182  			src = arg
   183  			data = []byte(ts.stderr)
   184  			mode = 0o666
   185  		case "ttyout":
   186  			src = arg
   187  			data = []byte(ts.ttyout)
   188  			mode = 0o666
   189  		default:
   190  			src = ts.MkAbs(arg)
   191  			info, err := os.Stat(src)
   192  			ts.Check(err)
   193  			mode = info.Mode() & 0o777
   194  			data, err = ioutil.ReadFile(src)
   195  			ts.Check(err)
   196  		}
   197  		targ := dst
   198  		if dstDir {
   199  			targ = filepath.Join(dst, filepath.Base(src))
   200  		}
   201  		ts.Check(ioutil.WriteFile(targ, data, mode))
   202  	}
   203  }
   204  
   205  // env displays or adds to the environment.
   206  func (ts *TestScript) cmdEnv(neg bool, args []string) {
   207  	if neg {
   208  		ts.Fatalf("unsupported: ! env")
   209  	}
   210  	if len(args) == 0 {
   211  		printed := make(map[string]bool) // env list can have duplicates; only print effective value (from envMap) once
   212  		for _, kv := range ts.env {
   213  			k := envvarname(kv[:strings.Index(kv, "=")])
   214  			if !printed[k] {
   215  				printed[k] = true
   216  				ts.Logf("%s=%s\n", k, ts.envMap[k])
   217  			}
   218  		}
   219  		return
   220  	}
   221  	for _, env := range args {
   222  		i := strings.Index(env, "=")
   223  		if i < 0 {
   224  			// Display value instead of setting it.
   225  			ts.Logf("%s=%s\n", env, ts.Getenv(env))
   226  			continue
   227  		}
   228  		ts.Setenv(env[:i], env[i+1:])
   229  	}
   230  }
   231  
   232  var backgroundSpecifier = regexp.MustCompile(`^&([a-zA-Z_0-9]+&)?$`)
   233  
   234  // exec runs the given command.
   235  func (ts *TestScript) cmdExec(neg bool, args []string) {
   236  	if len(args) < 1 || (len(args) == 1 && args[0] == "&") {
   237  		ts.Fatalf("usage: exec program [args...] [&]")
   238  	}
   239  
   240  	var err error
   241  	if len(args) > 0 && backgroundSpecifier.MatchString(args[len(args)-1]) {
   242  		bgName := strings.TrimSuffix(strings.TrimPrefix(args[len(args)-1], "&"), "&")
   243  		if ts.findBackground(bgName) != nil {
   244  			ts.Fatalf("duplicate background process name %q", bgName)
   245  		}
   246  		var cmd *exec.Cmd
   247  		cmd, err = ts.execBackground(args[0], args[1:len(args)-1]...)
   248  		if err == nil {
   249  			wait := make(chan struct{})
   250  			go func() {
   251  				waitOrStop(ts.ctxt, cmd, -1)
   252  				close(wait)
   253  			}()
   254  			ts.background = append(ts.background, backgroundCmd{bgName, cmd, wait, neg})
   255  		}
   256  		ts.stdout, ts.stderr = "", ""
   257  	} else {
   258  		ts.stdout, ts.stderr, err = ts.exec(args[0], args[1:]...)
   259  		if ts.stdout != "" {
   260  			fmt.Fprintf(&ts.log, "[stdout]\n%s", ts.stdout)
   261  		}
   262  		if ts.stderr != "" {
   263  			fmt.Fprintf(&ts.log, "[stderr]\n%s", ts.stderr)
   264  		}
   265  		if err == nil && neg {
   266  			ts.Fatalf("unexpected command success")
   267  		}
   268  	}
   269  
   270  	if err != nil {
   271  		fmt.Fprintf(&ts.log, "[%v]\n", err)
   272  		if ts.ctxt.Err() != nil {
   273  			ts.Fatalf("test timed out while running command")
   274  		} else if !neg {
   275  			ts.Fatalf("unexpected command failure")
   276  		}
   277  	}
   278  }
   279  
   280  // exists checks that the list of files exists.
   281  func (ts *TestScript) cmdExists(neg bool, args []string) {
   282  	var readonly bool
   283  	if len(args) > 0 && args[0] == "-readonly" {
   284  		readonly = true
   285  		args = args[1:]
   286  	}
   287  	if len(args) == 0 {
   288  		ts.Fatalf("usage: exists [-readonly] file...")
   289  	}
   290  
   291  	for _, file := range args {
   292  		file = ts.MkAbs(file)
   293  		info, err := os.Stat(file)
   294  		if err == nil && neg {
   295  			what := "file"
   296  			if info.IsDir() {
   297  				what = "directory"
   298  			}
   299  			ts.Fatalf("%s %s unexpectedly exists", what, file)
   300  		}
   301  		if err != nil && !neg {
   302  			ts.Fatalf("%s does not exist", file)
   303  		}
   304  		if err == nil && !neg && readonly && info.Mode()&0o222 != 0 {
   305  			ts.Fatalf("%s exists but is writable", file)
   306  		}
   307  	}
   308  }
   309  
   310  // mkdir creates directories.
   311  func (ts *TestScript) cmdMkdir(neg bool, args []string) {
   312  	if neg {
   313  		ts.Fatalf("unsupported: ! mkdir")
   314  	}
   315  	if len(args) < 1 {
   316  		ts.Fatalf("usage: mkdir dir...")
   317  	}
   318  	for _, arg := range args {
   319  		ts.Check(os.MkdirAll(ts.MkAbs(arg), 0o777))
   320  	}
   321  }
   322  
   323  func (ts *TestScript) cmdMv(neg bool, args []string) {
   324  	if neg {
   325  		ts.Fatalf("unsupported: ! mv")
   326  	}
   327  	if len(args) != 2 {
   328  		ts.Fatalf("usage: mv old new")
   329  	}
   330  	ts.Check(os.Rename(ts.MkAbs(args[0]), ts.MkAbs(args[1])))
   331  }
   332  
   333  // unquote unquotes files.
   334  func (ts *TestScript) cmdUnquote(neg bool, args []string) {
   335  	if neg {
   336  		ts.Fatalf("unsupported: ! unquote")
   337  	}
   338  	for _, arg := range args {
   339  		file := ts.MkAbs(arg)
   340  		data, err := ioutil.ReadFile(file)
   341  		ts.Check(err)
   342  		data, err = txtar.Unquote(data)
   343  		ts.Check(err)
   344  		err = ioutil.WriteFile(file, data, 0o666)
   345  		ts.Check(err)
   346  	}
   347  }
   348  
   349  // rm removes files or directories.
   350  func (ts *TestScript) cmdRm(neg bool, args []string) {
   351  	if neg {
   352  		ts.Fatalf("unsupported: ! rm")
   353  	}
   354  	if len(args) < 1 {
   355  		ts.Fatalf("usage: rm file...")
   356  	}
   357  	for _, arg := range args {
   358  		file := ts.MkAbs(arg)
   359  		removeAll(file)              // does chmod and then attempts rm
   360  		ts.Check(os.RemoveAll(file)) // report error
   361  	}
   362  }
   363  
   364  // skip marks the test skipped.
   365  func (ts *TestScript) cmdSkip(neg bool, args []string) {
   366  	if len(args) > 1 {
   367  		ts.Fatalf("usage: skip [msg]")
   368  	}
   369  	if neg {
   370  		ts.Fatalf("unsupported: ! skip")
   371  	}
   372  
   373  	// Before we mark the test as skipped, shut down any background processes and
   374  	// make sure they have returned the correct status.
   375  	for _, bg := range ts.background {
   376  		interruptProcess(bg.cmd.Process)
   377  	}
   378  	ts.cmdWait(false, nil)
   379  
   380  	if len(args) == 1 {
   381  		ts.t.Skip(args[0])
   382  	}
   383  	ts.t.Skip()
   384  }
   385  
   386  func (ts *TestScript) cmdStdin(neg bool, args []string) {
   387  	if neg {
   388  		ts.Fatalf("unsupported: ! stdin")
   389  	}
   390  	if len(args) != 1 {
   391  		ts.Fatalf("usage: stdin filename")
   392  	}
   393  	if ts.stdinPty {
   394  		ts.Fatalf("conflicting use of 'stdin' and 'ttyin -stdin'")
   395  	}
   396  	ts.stdin = ts.ReadFile(args[0])
   397  }
   398  
   399  // stdout checks that the last go command standard output matches a regexp.
   400  func (ts *TestScript) cmdStdout(neg bool, args []string) {
   401  	scriptMatch(ts, neg, args, ts.stdout, "stdout")
   402  }
   403  
   404  // stderr checks that the last go command standard output matches a regexp.
   405  func (ts *TestScript) cmdStderr(neg bool, args []string) {
   406  	scriptMatch(ts, neg, args, ts.stderr, "stderr")
   407  }
   408  
   409  // grep checks that file content matches a regexp.
   410  // Like stdout/stderr and unlike Unix grep, it accepts Go regexp syntax.
   411  func (ts *TestScript) cmdGrep(neg bool, args []string) {
   412  	scriptMatch(ts, neg, args, "", "grep")
   413  }
   414  
   415  func (ts *TestScript) cmdTtyin(neg bool, args []string) {
   416  	if !pty.Supported {
   417  		ts.Fatalf("unsupported: ttyin on %s", runtime.GOOS)
   418  	}
   419  	if neg {
   420  		ts.Fatalf("unsupported: ! ttyin")
   421  	}
   422  	switch len(args) {
   423  	case 1:
   424  		ts.ttyin = ts.ReadFile(args[0])
   425  	case 2:
   426  		if args[0] != "-stdin" {
   427  			ts.Fatalf("usage: ttyin [-stdin] filename")
   428  		}
   429  		if ts.stdin != "" {
   430  			ts.Fatalf("conflicting use of 'stdin' and 'ttyin -stdin'")
   431  		}
   432  		ts.stdinPty = true
   433  		ts.ttyin = ts.ReadFile(args[1])
   434  	default:
   435  		ts.Fatalf("usage: ttyin [-stdin] filename")
   436  	}
   437  	if ts.ttyin == "" {
   438  		ts.Fatalf("tty input file is empty")
   439  	}
   440  }
   441  
   442  func (ts *TestScript) cmdTtyout(neg bool, args []string) {
   443  	scriptMatch(ts, neg, args, ts.ttyout, "ttyout")
   444  }
   445  
   446  // stop stops execution of the test (marking it passed).
   447  func (ts *TestScript) cmdStop(neg bool, args []string) {
   448  	if neg {
   449  		ts.Fatalf("unsupported: ! stop")
   450  	}
   451  	if len(args) > 1 {
   452  		ts.Fatalf("usage: stop [msg]")
   453  	}
   454  	if len(args) == 1 {
   455  		ts.Logf("stop: %s\n", args[0])
   456  	} else {
   457  		ts.Logf("stop\n")
   458  	}
   459  	ts.stopped = true
   460  }
   461  
   462  // symlink creates a symbolic link.
   463  func (ts *TestScript) cmdSymlink(neg bool, args []string) {
   464  	if neg {
   465  		ts.Fatalf("unsupported: ! symlink")
   466  	}
   467  	if len(args) != 3 || args[1] != "->" {
   468  		ts.Fatalf("usage: symlink file -> target")
   469  	}
   470  	// Note that the link target args[2] is not interpreted with MkAbs:
   471  	// it will be interpreted relative to the directory file is in.
   472  	ts.Check(os.Symlink(args[2], ts.MkAbs(args[0])))
   473  }
   474  
   475  // cmdUNIX2DOS converts files from UNIX line endings to DOS line endings.
   476  func (ts *TestScript) cmdUNIX2DOS(neg bool, args []string) {
   477  	if neg {
   478  		ts.Fatalf("unsupported: ! unix2dos")
   479  	}
   480  	if len(args) < 1 {
   481  		ts.Fatalf("usage: unix2dos paths...")
   482  	}
   483  	for _, arg := range args {
   484  		filename := ts.MkAbs(arg)
   485  		data, err := ioutil.ReadFile(filename)
   486  		ts.Check(err)
   487  		dosData, err := unix2DOS(data)
   488  		ts.Check(err)
   489  		if err := ioutil.WriteFile(filename, dosData, 0o666); err != nil {
   490  			ts.Fatalf("%s: %v", filename, err)
   491  		}
   492  	}
   493  }
   494  
   495  // Tait waits for background commands to exit, setting stderr and stdout to their result.
   496  func (ts *TestScript) cmdWait(neg bool, args []string) {
   497  	if len(args) > 1 {
   498  		ts.Fatalf("usage: wait [name]")
   499  	}
   500  	if neg {
   501  		ts.Fatalf("unsupported: ! wait")
   502  	}
   503  	if len(args) > 0 {
   504  		ts.waitBackgroundOne(args[0])
   505  	} else {
   506  		ts.waitBackground(true)
   507  	}
   508  }
   509  
   510  func (ts *TestScript) waitBackgroundOne(bgName string) {
   511  	bg := ts.findBackground(bgName)
   512  	if bg == nil {
   513  		ts.Fatalf("unknown background process %q", bgName)
   514  	}
   515  	<-bg.wait
   516  	ts.stdout = bg.cmd.Stdout.(*strings.Builder).String()
   517  	ts.stderr = bg.cmd.Stderr.(*strings.Builder).String()
   518  	if ts.stdout != "" {
   519  		fmt.Fprintf(&ts.log, "[stdout]\n%s", ts.stdout)
   520  	}
   521  	if ts.stderr != "" {
   522  		fmt.Fprintf(&ts.log, "[stderr]\n%s", ts.stderr)
   523  	}
   524  	// Note: ignore bg.neg, which only takes effect on the non-specific
   525  	// wait command.
   526  	if bg.cmd.ProcessState.Success() {
   527  		if bg.neg {
   528  			ts.Fatalf("unexpected command success")
   529  		}
   530  	} else {
   531  		if ts.ctxt.Err() != nil {
   532  			ts.Fatalf("test timed out while running command")
   533  		} else if !bg.neg {
   534  			ts.Fatalf("unexpected command failure")
   535  		}
   536  	}
   537  	// Remove this process from the list of running background processes.
   538  	for i := range ts.background {
   539  		if bg == &ts.background[i] {
   540  			ts.background = append(ts.background[:i], ts.background[i+1:]...)
   541  			break
   542  		}
   543  	}
   544  }
   545  
   546  func (ts *TestScript) findBackground(bgName string) *backgroundCmd {
   547  	if bgName == "" {
   548  		return nil
   549  	}
   550  	for i := range ts.background {
   551  		bg := &ts.background[i]
   552  		if bg.name == bgName {
   553  			return bg
   554  		}
   555  	}
   556  	return nil
   557  }
   558  
   559  func (ts *TestScript) waitBackground(checkStatus bool) {
   560  	var stdouts, stderrs []string
   561  	for _, bg := range ts.background {
   562  		<-bg.wait
   563  
   564  		args := append([]string{filepath.Base(bg.cmd.Args[0])}, bg.cmd.Args[1:]...)
   565  		fmt.Fprintf(&ts.log, "[background] %s: %v\n", strings.Join(args, " "), bg.cmd.ProcessState)
   566  
   567  		cmdStdout := bg.cmd.Stdout.(*strings.Builder).String()
   568  		if cmdStdout != "" {
   569  			fmt.Fprintf(&ts.log, "[stdout]\n%s", cmdStdout)
   570  			stdouts = append(stdouts, cmdStdout)
   571  		}
   572  
   573  		cmdStderr := bg.cmd.Stderr.(*strings.Builder).String()
   574  		if cmdStderr != "" {
   575  			fmt.Fprintf(&ts.log, "[stderr]\n%s", cmdStderr)
   576  			stderrs = append(stderrs, cmdStderr)
   577  		}
   578  
   579  		if !checkStatus {
   580  			continue
   581  		}
   582  		if bg.cmd.ProcessState.Success() {
   583  			if bg.neg {
   584  				ts.Fatalf("unexpected command success")
   585  			}
   586  		} else {
   587  			if ts.ctxt.Err() != nil {
   588  				ts.Fatalf("test timed out while running command")
   589  			} else if !bg.neg {
   590  				ts.Fatalf("unexpected command failure")
   591  			}
   592  		}
   593  	}
   594  
   595  	ts.stdout = strings.Join(stdouts, "")
   596  	ts.stderr = strings.Join(stderrs, "")
   597  	ts.background = nil
   598  }
   599  
   600  // scriptMatch implements both stdout and stderr.
   601  func scriptMatch(ts *TestScript, neg bool, args []string, text, name string) {
   602  	n := 0
   603  	if len(args) >= 1 && strings.HasPrefix(args[0], "-count=") {
   604  		if neg {
   605  			ts.Fatalf("cannot use -count= with negated match")
   606  		}
   607  		var err error
   608  		n, err = strconv.Atoi(args[0][len("-count="):])
   609  		if err != nil {
   610  			ts.Fatalf("bad -count=: %v", err)
   611  		}
   612  		if n < 1 {
   613  			ts.Fatalf("bad -count=: must be at least 1")
   614  		}
   615  		args = args[1:]
   616  	}
   617  
   618  	extraUsage := ""
   619  	want := 1
   620  	if name == "grep" {
   621  		extraUsage = " file"
   622  		want = 2
   623  	}
   624  	if len(args) != want {
   625  		ts.Fatalf("usage: %s [-count=N] 'pattern'%s", name, extraUsage)
   626  	}
   627  
   628  	pattern := args[0]
   629  	re, err := regexp.Compile(`(?m)` + pattern)
   630  	ts.Check(err)
   631  
   632  	isGrep := name == "grep"
   633  	if isGrep {
   634  		name = args[1] // for error messages
   635  		data, err := ioutil.ReadFile(ts.MkAbs(args[1]))
   636  		ts.Check(err)
   637  		text = string(data)
   638  	}
   639  
   640  	if neg {
   641  		if re.MatchString(text) {
   642  			if isGrep {
   643  				ts.Logf("[%s]\n%s\n", name, text)
   644  			}
   645  			ts.Fatalf("unexpected match for %#q found in %s: %s", pattern, name, re.FindString(text))
   646  		}
   647  	} else {
   648  		if !re.MatchString(text) {
   649  			if isGrep {
   650  				ts.Logf("[%s]\n%s\n", name, text)
   651  			}
   652  			ts.Fatalf("no match for %#q found in %s", pattern, name)
   653  		}
   654  		if n > 0 {
   655  			count := len(re.FindAllString(text, -1))
   656  			if count != n {
   657  				if isGrep {
   658  					ts.Logf("[%s]\n%s\n", name, text)
   659  				}
   660  				ts.Fatalf("have %d matches for %#q, want %d", count, pattern, n)
   661  			}
   662  		}
   663  	}
   664  }
   665  
   666  // unix2DOS returns data with UNIX line endings converted to DOS line endings.
   667  func unix2DOS(data []byte) ([]byte, error) {
   668  	sb := &strings.Builder{}
   669  	s := bufio.NewScanner(bytes.NewReader(data))
   670  	for s.Scan() {
   671  		if _, err := sb.Write(s.Bytes()); err != nil {
   672  			return nil, err
   673  		}
   674  		if _, err := sb.WriteString("\r\n"); err != nil {
   675  			return nil, err
   676  		}
   677  	}
   678  	if err := s.Err(); err != nil {
   679  		return nil, err
   680  	}
   681  	return []byte(sb.String()), nil
   682  }
   683  

View as plain text