github.com/neilgarb/delve@v1.9.2-nobreaks/pkg/terminal/command_test.go (about)

     1  package terminal
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"path/filepath"
    12  	"reflect"
    13  	"regexp"
    14  	"runtime"
    15  	"strconv"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/go-delve/delve/pkg/config"
    21  	"github.com/go-delve/delve/pkg/goversion"
    22  	"github.com/go-delve/delve/pkg/logflags"
    23  	"github.com/go-delve/delve/pkg/proc/test"
    24  	"github.com/go-delve/delve/service"
    25  	"github.com/go-delve/delve/service/api"
    26  	"github.com/go-delve/delve/service/debugger"
    27  	"github.com/go-delve/delve/service/rpc2"
    28  	"github.com/go-delve/delve/service/rpccommon"
    29  )
    30  
    31  var testBackend, buildMode string
    32  
    33  func TestMain(m *testing.M) {
    34  	flag.StringVar(&testBackend, "backend", "", "selects backend")
    35  	flag.StringVar(&buildMode, "test-buildmode", "", "selects build mode")
    36  	var logConf string
    37  	flag.StringVar(&logConf, "log", "", "configures logging")
    38  	flag.Parse()
    39  	test.DefaultTestBackend(&testBackend)
    40  	if buildMode != "" && buildMode != "pie" {
    41  		fmt.Fprintf(os.Stderr, "unknown build mode %q", buildMode)
    42  		os.Exit(1)
    43  	}
    44  	logflags.Setup(logConf != "", logConf, "")
    45  	os.Exit(test.RunTestsWithFixtures(m))
    46  }
    47  
    48  type FakeTerminal struct {
    49  	*Term
    50  	t testing.TB
    51  }
    52  
    53  const logCommandOutput = false
    54  
    55  func (ft *FakeTerminal) Exec(cmdstr string) (outstr string, err error) {
    56  	var buf bytes.Buffer
    57  	ft.Term.stdout.w = &buf
    58  	ft.Term.starlarkEnv.Redirect(ft.Term.stdout)
    59  	err = ft.cmds.Call(cmdstr, ft.Term)
    60  	outstr = buf.String()
    61  	if logCommandOutput {
    62  		ft.t.Logf("command %q -> %q", cmdstr, outstr)
    63  	}
    64  	ft.Term.stdout.Flush()
    65  	return
    66  }
    67  
    68  func (ft *FakeTerminal) ExecStarlark(starlarkProgram string) (outstr string, err error) {
    69  	var buf bytes.Buffer
    70  	ft.Term.stdout.w = &buf
    71  	ft.Term.starlarkEnv.Redirect(ft.Term.stdout)
    72  	_, err = ft.Term.starlarkEnv.Execute("<stdin>", starlarkProgram, "main", nil)
    73  	outstr = buf.String()
    74  	if logCommandOutput {
    75  		ft.t.Logf("command %q -> %q", starlarkProgram, outstr)
    76  	}
    77  	ft.Term.stdout.Flush()
    78  	return
    79  }
    80  
    81  func (ft *FakeTerminal) MustExec(cmdstr string) string {
    82  	outstr, err := ft.Exec(cmdstr)
    83  	if err != nil {
    84  		ft.t.Errorf("output of %q: %q", cmdstr, outstr)
    85  		ft.t.Fatalf("Error executing <%s>: %v", cmdstr, err)
    86  	}
    87  	return outstr
    88  }
    89  
    90  func (ft *FakeTerminal) MustExecStarlark(starlarkProgram string) string {
    91  	outstr, err := ft.ExecStarlark(starlarkProgram)
    92  	if err != nil {
    93  		ft.t.Errorf("output of %q: %q", starlarkProgram, outstr)
    94  		ft.t.Fatalf("Error executing <%s>: %v", starlarkProgram, err)
    95  	}
    96  	return outstr
    97  }
    98  
    99  func (ft *FakeTerminal) AssertExec(cmdstr, tgt string) {
   100  	out := ft.MustExec(cmdstr)
   101  	if out != tgt {
   102  		ft.t.Fatalf("Error executing %q, expected %q got %q", cmdstr, tgt, out)
   103  	}
   104  }
   105  
   106  func (ft *FakeTerminal) AssertExecError(cmdstr, tgterr string) {
   107  	_, err := ft.Exec(cmdstr)
   108  	if err == nil {
   109  		ft.t.Fatalf("Expected error executing %q", cmdstr)
   110  	}
   111  	if err.Error() != tgterr {
   112  		ft.t.Fatalf("Expected error %q executing %q, got error %q", tgterr, cmdstr, err.Error())
   113  	}
   114  }
   115  
   116  func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) {
   117  	withTestTerminalBuildFlags(name, t, 0, fn)
   118  }
   119  
   120  func withTestTerminalBuildFlags(name string, t testing.TB, buildFlags test.BuildFlags, fn func(*FakeTerminal)) {
   121  	if testBackend == "rr" {
   122  		test.MustHaveRecordingAllowed(t)
   123  	}
   124  	os.Setenv("TERM", "dumb")
   125  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   126  	if err != nil {
   127  		t.Fatalf("couldn't start listener: %s\n", err)
   128  	}
   129  	defer listener.Close()
   130  	if buildMode == "pie" {
   131  		buildFlags |= test.BuildModePIE
   132  	}
   133  	server := rpccommon.NewServer(&service.Config{
   134  		Listener:    listener,
   135  		ProcessArgs: []string{test.BuildFixture(name, buildFlags).Path},
   136  		Debugger: debugger.Config{
   137  			Backend: testBackend,
   138  		},
   139  	})
   140  	if err := server.Run(); err != nil {
   141  		t.Fatal(err)
   142  	}
   143  	client := rpc2.NewClient(listener.Addr().String())
   144  	defer func() {
   145  		client.Detach(true)
   146  	}()
   147  
   148  	ft := &FakeTerminal{
   149  		t:    t,
   150  		Term: New(client, &config.Config{}),
   151  	}
   152  	fn(ft)
   153  }
   154  
   155  func TestCommandDefault(t *testing.T) {
   156  	var (
   157  		cmds = Commands{}
   158  		cmd  = cmds.Find("non-existent-command", noPrefix).cmdFn
   159  	)
   160  
   161  	err := cmd(nil, callContext{}, "")
   162  	if err == nil {
   163  		t.Fatal("cmd() did not default")
   164  	}
   165  
   166  	if err.Error() != "command not available" {
   167  		t.Fatal("wrong command output")
   168  	}
   169  }
   170  
   171  func TestCommandReplayWithoutPreviousCommand(t *testing.T) {
   172  	var (
   173  		cmds = DebugCommands(nil)
   174  		cmd  = cmds.Find("", noPrefix).cmdFn
   175  		err  = cmd(nil, callContext{}, "")
   176  	)
   177  
   178  	if err != nil {
   179  		t.Error("Null command not returned", err)
   180  	}
   181  }
   182  
   183  func TestCommandThread(t *testing.T) {
   184  	var (
   185  		cmds = DebugCommands(nil)
   186  		cmd  = cmds.Find("thread", noPrefix).cmdFn
   187  	)
   188  
   189  	err := cmd(nil, callContext{}, "")
   190  	if err == nil {
   191  		t.Fatal("thread terminal command did not default")
   192  	}
   193  
   194  	if err.Error() != "you must specify a thread" {
   195  		t.Fatal("wrong command output: ", err.Error())
   196  	}
   197  }
   198  
   199  func TestExecuteFile(t *testing.T) {
   200  	breakCount := 0
   201  	traceCount := 0
   202  	c := &Commands{
   203  		client: nil,
   204  		cmds: []command{
   205  			{aliases: []string{"trace"}, cmdFn: func(t *Term, ctx callContext, args string) error {
   206  				traceCount++
   207  				return nil
   208  			}},
   209  			{aliases: []string{"break"}, cmdFn: func(t *Term, ctx callContext, args string) error {
   210  				breakCount++
   211  				return nil
   212  			}},
   213  		},
   214  	}
   215  
   216  	fixturesDir := test.FindFixturesDir()
   217  	err := c.executeFile(nil, filepath.Join(fixturesDir, "bpfile"))
   218  	if err != nil {
   219  		t.Fatalf("executeFile: %v", err)
   220  	}
   221  
   222  	if breakCount != 1 || traceCount != 1 {
   223  		t.Fatalf("Wrong counts break: %d trace: %d\n", breakCount, traceCount)
   224  	}
   225  }
   226  
   227  func TestIssue354(t *testing.T) {
   228  	printStack(&Term{}, os.Stdout, []api.Stackframe{}, "", false)
   229  	printStack(&Term{}, os.Stdout, []api.Stackframe{
   230  		{Location: api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil},
   231  			Bottom: true}}, "", false)
   232  }
   233  
   234  func TestIssue411(t *testing.T) {
   235  	test.AllowRecording(t)
   236  	withTestTerminal("math", t, func(term *FakeTerminal) {
   237  		term.MustExec("break _fixtures/math.go:8")
   238  		term.MustExec("trace _fixtures/math.go:9")
   239  		term.MustExec("continue")
   240  		out := term.MustExec("next")
   241  		if !strings.HasPrefix(out, "> goroutine(1): main.main()") {
   242  			t.Fatalf("Wrong output for next: <%s>", out)
   243  		}
   244  	})
   245  }
   246  
   247  func TestTrace(t *testing.T) {
   248  	test.AllowRecording(t)
   249  	withTestTerminal("issue573", t, func(term *FakeTerminal) {
   250  		term.MustExec("trace foo")
   251  		out, _ := term.Exec("continue")
   252  		// The output here is a little strange, but we don't filter stdout vs stderr so it gets jumbled.
   253  		// Therefore we assert about the call and return values separately.
   254  		if !strings.Contains(out, "> goroutine(1): main.foo(99, 9801)") {
   255  			t.Fatalf("Wrong output for tracepoint: %s", out)
   256  		}
   257  		if !strings.Contains(out, "=> (9900)") {
   258  			t.Fatalf("Wrong output for tracepoint return value: %s", out)
   259  		}
   260  	})
   261  }
   262  
   263  func TestTraceWithName(t *testing.T) {
   264  	test.AllowRecording(t)
   265  	withTestTerminal("issue573", t, func(term *FakeTerminal) {
   266  		term.MustExec("trace foobar foo")
   267  		out, _ := term.Exec("continue")
   268  		// The output here is a little strange, but we don't filter stdout vs stderr so it gets jumbled.
   269  		// Therefore we assert about the call and return values separately.
   270  		if !strings.Contains(out, "> goroutine(1): [foobar] main.foo(99, 9801)") {
   271  			t.Fatalf("Wrong output for tracepoint: %s", out)
   272  		}
   273  		if !strings.Contains(out, "=> (9900)") {
   274  			t.Fatalf("Wrong output for tracepoint return value: %s", out)
   275  		}
   276  	})
   277  }
   278  
   279  func TestTraceOnNonFunctionEntry(t *testing.T) {
   280  	test.AllowRecording(t)
   281  	withTestTerminal("issue573", t, func(term *FakeTerminal) {
   282  		term.MustExec("trace foobar issue573.go:19")
   283  		out, _ := term.Exec("continue")
   284  		if !strings.Contains(out, "> goroutine(1): [foobar] main.foo(99, 9801)") {
   285  			t.Fatalf("Wrong output for tracepoint: %s", out)
   286  		}
   287  		if strings.Contains(out, "=> (9900)") {
   288  			t.Fatalf("Tracepoint on non-function locspec should not have return value:\n%s", out)
   289  		}
   290  	})
   291  }
   292  
   293  func TestExitStatus(t *testing.T) {
   294  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
   295  		term.Exec("continue")
   296  		status, err := term.handleExit()
   297  		if err != nil {
   298  			t.Fatal(err)
   299  		}
   300  		if status != 0 {
   301  			t.Fatalf("incorrect exit status, expected 0, got %d", status)
   302  		}
   303  	})
   304  }
   305  
   306  func TestScopePrefix(t *testing.T) {
   307  	const goroutinesLinePrefix = "  Goroutine "
   308  	const goroutinesCurLinePrefix = "* Goroutine "
   309  	test.AllowRecording(t)
   310  
   311  	lenient := 0
   312  	if runtime.GOOS == "windows" {
   313  		lenient = 1
   314  	}
   315  
   316  	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
   317  		term.MustExec("b stacktraceme")
   318  		term.MustExec("continue")
   319  
   320  		goroutinesOut := strings.Split(term.MustExec("goroutines"), "\n")
   321  		agoroutines := []int{}
   322  		nonagoroutines := []int{}
   323  		curgid := -1
   324  
   325  		for _, line := range goroutinesOut {
   326  			iscur := strings.HasPrefix(line, goroutinesCurLinePrefix)
   327  			if !iscur && !strings.HasPrefix(line, goroutinesLinePrefix) {
   328  				continue
   329  			}
   330  
   331  			dash := strings.Index(line, " - ")
   332  			if dash < 0 {
   333  				continue
   334  			}
   335  
   336  			gid, err := strconv.Atoi(line[len(goroutinesLinePrefix):dash])
   337  			if err != nil {
   338  				continue
   339  			}
   340  
   341  			if iscur {
   342  				curgid = gid
   343  			}
   344  
   345  			if idx := strings.Index(line, " main.agoroutine "); idx < 0 {
   346  				nonagoroutines = append(nonagoroutines, gid)
   347  				continue
   348  			}
   349  
   350  			agoroutines = append(agoroutines, gid)
   351  		}
   352  
   353  		if len(agoroutines) > 10 {
   354  			t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d found): %q", len(agoroutines), goroutinesOut)
   355  		}
   356  
   357  		if len(agoroutines) < 10 {
   358  			extraAgoroutines := 0
   359  			for _, gid := range nonagoroutines {
   360  				stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n")
   361  				for _, line := range stackOut {
   362  					if strings.HasSuffix(line, " main.agoroutine") {
   363  						extraAgoroutines++
   364  						break
   365  					}
   366  				}
   367  			}
   368  			if len(agoroutines)+extraAgoroutines < 10-lenient {
   369  				t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d+%d found): %q", len(agoroutines), extraAgoroutines, goroutinesOut)
   370  			}
   371  		}
   372  
   373  		if curgid < 0 {
   374  			t.Fatalf("Could not find current goroutine in output of goroutines: %q", goroutinesOut)
   375  		}
   376  
   377  		seen := make([]bool, 10)
   378  		for _, gid := range agoroutines {
   379  			stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n")
   380  			fid := -1
   381  			for _, line := range stackOut {
   382  				line = strings.TrimLeft(line, " ")
   383  				space := strings.Index(line, " ")
   384  				if space < 0 {
   385  					continue
   386  				}
   387  				curfid, err := strconv.Atoi(line[:space])
   388  				if err != nil {
   389  					continue
   390  				}
   391  
   392  				if idx := strings.Index(line, " main.agoroutine"); idx >= 0 {
   393  					fid = curfid
   394  					break
   395  				}
   396  			}
   397  			if fid < 0 {
   398  				t.Fatalf("Could not find frame for goroutine %d: %q", gid, stackOut)
   399  			}
   400  			term.AssertExec(fmt.Sprintf("goroutine     %d    frame     %d     locals", gid, fid), "(no locals)\n")
   401  			argsOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d frame %d args", gid, fid)), "\n")
   402  			if len(argsOut) != 4 || argsOut[3] != "" {
   403  				t.Fatalf("Wrong number of arguments in goroutine %d frame %d: %v", gid, fid, argsOut)
   404  			}
   405  			out := term.MustExec(fmt.Sprintf("goroutine %d frame %d p i", gid, fid))
   406  			ival, err := strconv.Atoi(out[:len(out)-1])
   407  			if err != nil {
   408  				t.Fatalf("could not parse value %q of i for goroutine %d frame %d: %v", out, gid, fid, err)
   409  			}
   410  			seen[ival] = true
   411  		}
   412  
   413  		for i := range seen {
   414  			if !seen[i] {
   415  				if lenient > 0 {
   416  					lenient--
   417  				} else {
   418  					t.Fatalf("goroutine %d not found", i)
   419  				}
   420  			}
   421  		}
   422  
   423  		term.MustExec("c")
   424  
   425  		term.AssertExecError("frame", "not enough arguments")
   426  		term.AssertExecError(fmt.Sprintf("goroutine %d frame 10 locals", curgid), fmt.Sprintf("Frame 10 does not exist in goroutine %d", curgid))
   427  		term.AssertExecError("goroutine 9000 locals", "unknown goroutine 9000")
   428  
   429  		term.AssertExecError("print n", "could not find symbol value for n")
   430  		term.AssertExec("frame 1 print n", "3\n")
   431  		term.AssertExec("frame 2 print n", "2\n")
   432  		term.AssertExec("frame 3 print n", "1\n")
   433  		term.AssertExec("frame 4 print n", "0\n")
   434  		term.AssertExecError("frame 5 print n", "could not find symbol value for n")
   435  
   436  		term.MustExec("frame 2")
   437  		term.AssertExec("print n", "2\n")
   438  		term.MustExec("frame 4")
   439  		term.AssertExec("print n", "0\n")
   440  		term.MustExec("down")
   441  		term.AssertExec("print n", "1\n")
   442  		term.MustExec("down 2")
   443  		term.AssertExec("print n", "3\n")
   444  		term.AssertExecError("down 2", "Invalid frame -1")
   445  		term.AssertExec("print n", "3\n")
   446  		term.MustExec("up 2")
   447  		term.AssertExec("print n", "1\n")
   448  		term.AssertExecError("up 100", "Invalid frame 103")
   449  		term.AssertExec("print n", "1\n")
   450  
   451  		term.MustExec("step")
   452  		term.AssertExecError("print n", "could not find symbol value for n")
   453  		term.MustExec("frame 2")
   454  		term.AssertExec("print n", "2\n")
   455  	})
   456  }
   457  
   458  func TestOnPrefix(t *testing.T) {
   459  	if runtime.GOOS == "freebsd" {
   460  		t.Skip("test is not valid on FreeBSD")
   461  	}
   462  	const prefix = "\ti: "
   463  	test.AllowRecording(t)
   464  	lenient := false
   465  	if runtime.GOOS == "windows" {
   466  		lenient = true
   467  	}
   468  	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
   469  		term.MustExec("b agobp main.agoroutine")
   470  		term.MustExec("on agobp print i")
   471  
   472  		seen := make([]bool, 10)
   473  
   474  		for {
   475  			outstr, err := term.Exec("continue")
   476  			if err != nil {
   477  				if !strings.Contains(err.Error(), "exited") {
   478  					t.Fatalf("Unexpected error executing 'continue': %v", err)
   479  				}
   480  				break
   481  			}
   482  			out := strings.Split(outstr, "\n")
   483  
   484  			for i := range out {
   485  				if !strings.HasPrefix(out[i], prefix) {
   486  					continue
   487  				}
   488  				id, err := strconv.Atoi(out[i][len(prefix):])
   489  				if err != nil {
   490  					continue
   491  				}
   492  				if seen[id] {
   493  					t.Fatalf("Goroutine %d seen twice\n", id)
   494  				}
   495  				seen[id] = true
   496  			}
   497  		}
   498  
   499  		for i := range seen {
   500  			if !seen[i] {
   501  				if lenient {
   502  					lenient = false
   503  				} else {
   504  					t.Fatalf("Goroutine %d not seen\n", i)
   505  				}
   506  			}
   507  		}
   508  	})
   509  }
   510  
   511  func TestNoVars(t *testing.T) {
   512  	test.AllowRecording(t)
   513  	withTestTerminal("locationsUpperCase", t, func(term *FakeTerminal) {
   514  		term.MustExec("b main.main")
   515  		term.MustExec("continue")
   516  		term.AssertExec("args", "(no args)\n")
   517  		term.AssertExec("locals", "(no locals)\n")
   518  		term.AssertExec("vars filterThatMatchesNothing", "(no vars)\n")
   519  	})
   520  }
   521  
   522  func TestOnPrefixLocals(t *testing.T) {
   523  	if runtime.GOOS == "freebsd" {
   524  		t.Skip("test is not valid on FreeBSD")
   525  	}
   526  	const prefix = "\ti: "
   527  	test.AllowRecording(t)
   528  	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
   529  		term.MustExec("b agobp main.agoroutine")
   530  		term.MustExec("on agobp args -v")
   531  
   532  		seen := make([]bool, 10)
   533  
   534  		for {
   535  			outstr, err := term.Exec("continue")
   536  			if err != nil {
   537  				if !strings.Contains(err.Error(), "exited") {
   538  					t.Fatalf("Unexpected error executing 'continue': %v", err)
   539  				}
   540  				break
   541  			}
   542  			out := strings.Split(outstr, "\n")
   543  
   544  			for i := range out {
   545  				if !strings.HasPrefix(out[i], prefix) {
   546  					continue
   547  				}
   548  				id, err := strconv.Atoi(out[i][len(prefix):])
   549  				if err != nil {
   550  					continue
   551  				}
   552  				if seen[id] {
   553  					t.Fatalf("Goroutine %d seen twice\n", id)
   554  				}
   555  				seen[id] = true
   556  			}
   557  		}
   558  
   559  		for i := range seen {
   560  			if !seen[i] {
   561  				t.Fatalf("Goroutine %d not seen\n", i)
   562  			}
   563  		}
   564  	})
   565  }
   566  
   567  func countOccurrences(s, needle string) int {
   568  	count := 0
   569  	for {
   570  		idx := strings.Index(s, needle)
   571  		if idx < 0 {
   572  			break
   573  		}
   574  		count++
   575  		s = s[idx+len(needle):]
   576  	}
   577  	return count
   578  }
   579  
   580  func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end int) {
   581  	t.Helper()
   582  	outstr := term.MustExec(listcmd)
   583  	lines := strings.Split(outstr, "\n")
   584  
   585  	t.Logf("%q: %q", listcmd, outstr)
   586  
   587  	if cur >= 0 && !strings.Contains(lines[0], fmt.Sprintf(":%d", cur)) {
   588  		t.Fatalf("Could not find current line number in first output line: %q", lines[0])
   589  	}
   590  
   591  	re := regexp.MustCompile(`(=>)?\s+(\d+):`)
   592  
   593  	outStart, outEnd := 0, 0
   594  
   595  	for _, line := range lines[1:] {
   596  		if line == "" {
   597  			continue
   598  		}
   599  		v := re.FindStringSubmatch(line)
   600  		if len(v) != 3 {
   601  			continue
   602  		}
   603  		curline, _ := strconv.Atoi(v[2])
   604  		if v[1] == "=>" {
   605  			if cur != curline {
   606  				t.Fatalf("Wrong current line, got %d expected %d", curline, cur)
   607  			}
   608  		}
   609  		if outStart == 0 {
   610  			outStart = curline
   611  		}
   612  		outEnd = curline
   613  	}
   614  
   615  	if start != -1 || end != -1 {
   616  		if outStart != start || outEnd != end {
   617  			t.Fatalf("Wrong output range, got %d:%d expected %d:%d", outStart, outEnd, start, end)
   618  		}
   619  	}
   620  }
   621  
   622  func TestListCmd(t *testing.T) {
   623  	withTestTerminal("testvariables", t, func(term *FakeTerminal) {
   624  		term.MustExec("continue")
   625  		term.MustExec("continue")
   626  		listIsAt(t, term, "list", 27, 22, 32)
   627  		listIsAt(t, term, "list 69", 69, 64, 74)
   628  		listIsAt(t, term, "frame 1 list", 66, 61, 71)
   629  		listIsAt(t, term, "frame 1 list 69", 69, 64, 74)
   630  		_, err := term.Exec("frame 50 list")
   631  		if err == nil {
   632  			t.Fatalf("Expected error requesting 50th frame")
   633  		}
   634  		listIsAt(t, term, "list testvariables.go:1", -1, 1, 6)
   635  		listIsAt(t, term, "list testvariables.go:10000", -1, 0, 0)
   636  	})
   637  }
   638  
   639  func TestReverseContinue(t *testing.T) {
   640  	test.AllowRecording(t)
   641  	if testBackend != "rr" {
   642  		return
   643  	}
   644  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
   645  		term.MustExec("break main.main")
   646  		term.MustExec("break main.sayhi")
   647  		listIsAt(t, term, "continue", 16, -1, -1)
   648  		listIsAt(t, term, "continue", 12, -1, -1)
   649  		listIsAt(t, term, "rewind", 16, -1, -1)
   650  	})
   651  }
   652  
   653  func TestCheckpoints(t *testing.T) {
   654  	test.AllowRecording(t)
   655  	if testBackend != "rr" {
   656  		return
   657  	}
   658  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
   659  		term.MustExec("break main.main")
   660  		listIsAt(t, term, "continue", 16, -1, -1)
   661  		term.MustExec("checkpoint")
   662  		term.MustExec("checkpoints")
   663  		listIsAt(t, term, "next", 17, -1, -1)
   664  		listIsAt(t, term, "next", 18, -1, -1)
   665  		term.MustExec("restart c1")
   666  		term.MustExec("goroutine 1")
   667  		listIsAt(t, term, "list", 16, -1, -1)
   668  	})
   669  }
   670  
   671  func TestNextWithCount(t *testing.T) {
   672  	test.AllowRecording(t)
   673  	withTestTerminal("nextcond", t, func(term *FakeTerminal) {
   674  		term.MustExec("break main.main")
   675  		listIsAt(t, term, "continue", 8, -1, -1)
   676  		listIsAt(t, term, "next 2", 10, -1, -1)
   677  	})
   678  }
   679  
   680  func TestRestart(t *testing.T) {
   681  	withTestTerminal("restartargs", t, func(term *FakeTerminal) {
   682  		term.MustExec("break main.printArgs")
   683  		term.MustExec("continue")
   684  		if out := term.MustExec("print main.args"); !strings.Contains(out, ", []") {
   685  			t.Fatalf("wrong args: %q", out)
   686  		}
   687  		// Reset the arg list
   688  		term.MustExec("restart hello")
   689  		term.MustExec("continue")
   690  		if out := term.MustExec("print main.args"); !strings.Contains(out, ", [\"hello\"]") {
   691  			t.Fatalf("wrong args: %q ", out)
   692  		}
   693  		// Restart w/o arg should retain the current args.
   694  		term.MustExec("restart")
   695  		term.MustExec("continue")
   696  		if out := term.MustExec("print main.args"); !strings.Contains(out, ", [\"hello\"]") {
   697  			t.Fatalf("wrong args: %q ", out)
   698  		}
   699  		// Empty arg list
   700  		term.MustExec("restart -noargs")
   701  		term.MustExec("continue")
   702  		if out := term.MustExec("print main.args"); !strings.Contains(out, ", []") {
   703  			t.Fatalf("wrong args: %q ", out)
   704  		}
   705  	})
   706  }
   707  
   708  func TestIssue827(t *testing.T) {
   709  	// switching goroutines when the current thread isn't running any goroutine
   710  	// causes nil pointer dereference.
   711  	withTestTerminal("notify-v2", t, func(term *FakeTerminal) {
   712  		go func() {
   713  			time.Sleep(1 * time.Second)
   714  			http.Get("http://127.0.0.1:8888/test")
   715  			time.Sleep(1 * time.Second)
   716  			term.client.Halt()
   717  		}()
   718  		term.MustExec("continue")
   719  		term.MustExec("goroutine 1")
   720  	})
   721  }
   722  
   723  func findCmdName(c *Commands, cmdstr string, prefix cmdPrefix) string {
   724  	for _, v := range c.cmds {
   725  		if v.match(cmdstr) {
   726  			if prefix != noPrefix && v.allowedPrefixes&prefix == 0 {
   727  				continue
   728  			}
   729  			return v.aliases[0]
   730  		}
   731  	}
   732  	return ""
   733  }
   734  
   735  func TestConfig(t *testing.T) {
   736  	var term Term
   737  	term.conf = &config.Config{}
   738  	term.cmds = DebugCommands(nil)
   739  
   740  	err := configureCmd(&term, callContext{}, "nonexistent-parameter 10")
   741  	if err == nil {
   742  		t.Fatalf("expected error executing configureCmd(nonexistent-parameter)")
   743  	}
   744  
   745  	err = configureCmd(&term, callContext{}, "max-string-len 10")
   746  	if err != nil {
   747  		t.Fatalf("error executing configureCmd(max-string-len): %v", err)
   748  	}
   749  	if term.conf.MaxStringLen == nil {
   750  		t.Fatalf("expected MaxStringLen 10, got nil")
   751  	}
   752  	if *term.conf.MaxStringLen != 10 {
   753  		t.Fatalf("expected MaxStringLen 10, got: %d", *term.conf.MaxStringLen)
   754  	}
   755  	err = configureCmd(&term, callContext{}, "show-location-expr   true")
   756  	if err != nil {
   757  		t.Fatalf("error executing configureCmd(show-location-expr   true)")
   758  	}
   759  	if term.conf.ShowLocationExpr != true {
   760  		t.Fatalf("expected ShowLocationExpr true, got false")
   761  	}
   762  	err = configureCmd(&term, callContext{}, "max-variable-recurse 4")
   763  	if err != nil {
   764  		t.Fatalf("error executing configureCmd(max-variable-recurse): %v", err)
   765  	}
   766  	if term.conf.MaxVariableRecurse == nil {
   767  		t.Fatalf("expected MaxVariableRecurse 4, got nil")
   768  	}
   769  	if *term.conf.MaxVariableRecurse != 4 {
   770  		t.Fatalf("expected MaxVariableRecurse 4, got: %d", *term.conf.MaxVariableRecurse)
   771  	}
   772  
   773  	err = configureCmd(&term, callContext{}, "substitute-path a b")
   774  	if err != nil {
   775  		t.Fatalf("error executing configureCmd(substitute-path a b): %v", err)
   776  	}
   777  	if len(term.conf.SubstitutePath) != 1 || (term.conf.SubstitutePath[0] != config.SubstitutePathRule{From: "a", To: "b"}) {
   778  		t.Fatalf("unexpected SubstitutePathRules after insert %v", term.conf.SubstitutePath)
   779  	}
   780  
   781  	err = configureCmd(&term, callContext{}, "substitute-path a")
   782  	if err != nil {
   783  		t.Fatalf("error executing configureCmd(substitute-path a): %v", err)
   784  	}
   785  	if len(term.conf.SubstitutePath) != 0 {
   786  		t.Fatalf("unexpected SubstitutePathRules after delete %v", term.conf.SubstitutePath)
   787  	}
   788  
   789  	err = configureCmd(&term, callContext{}, "alias print blah")
   790  	if err != nil {
   791  		t.Fatalf("error executing configureCmd(alias print blah): %v", err)
   792  	}
   793  	if len(term.conf.Aliases["print"]) != 1 {
   794  		t.Fatalf("aliases not changed after configure command %v", term.conf.Aliases)
   795  	}
   796  	if findCmdName(term.cmds, "blah", noPrefix) != "print" {
   797  		t.Fatalf("new alias not found")
   798  	}
   799  
   800  	err = configureCmd(&term, callContext{}, "alias blah")
   801  	if err != nil {
   802  		t.Fatalf("error executing configureCmd(alias blah): %v", err)
   803  	}
   804  	if len(term.conf.Aliases["print"]) != 0 {
   805  		t.Fatalf("alias not removed after configure command %v", term.conf.Aliases)
   806  	}
   807  	if findCmdName(term.cmds, "blah", noPrefix) != "" {
   808  		t.Fatalf("new alias found after delete")
   809  	}
   810  }
   811  
   812  func TestIssue1090(t *testing.T) {
   813  	// Exit while executing 'next' should report the "Process exited" error
   814  	// message instead of crashing.
   815  	withTestTerminal("math", t, func(term *FakeTerminal) {
   816  		term.MustExec("break main.main")
   817  		term.MustExec("continue")
   818  		for {
   819  			_, err := term.Exec("next")
   820  			if err != nil && strings.Contains(err.Error(), " has exited with status ") {
   821  				break
   822  			}
   823  		}
   824  	})
   825  }
   826  
   827  func TestPrintContextParkedGoroutine(t *testing.T) {
   828  	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
   829  		term.MustExec("break stacktraceme")
   830  		term.MustExec("continue")
   831  
   832  		// pick a goroutine that isn't running on a thread
   833  		gid := ""
   834  		gout := strings.Split(term.MustExec("goroutines"), "\n")
   835  		t.Logf("goroutines -> %q", gout)
   836  		for _, gline := range gout {
   837  			if !strings.Contains(gline, "thread ") && strings.Contains(gline, "agoroutine") {
   838  				if dash := strings.Index(gline, " - "); dash > 0 {
   839  					gid = gline[len("  Goroutine "):dash]
   840  					break
   841  				}
   842  			}
   843  		}
   844  
   845  		t.Logf("picked %q", gid)
   846  		term.MustExec(fmt.Sprintf("goroutine %s", gid))
   847  
   848  		frameout := strings.Split(term.MustExec("frame 0"), "\n")
   849  		t.Logf("frame 0 -> %q", frameout)
   850  		if strings.Contains(frameout[0], "stacktraceme") {
   851  			t.Fatal("bad output for `frame 0` command on a parked goorutine")
   852  		}
   853  
   854  		listout := strings.Split(term.MustExec("list"), "\n")
   855  		t.Logf("list -> %q", listout)
   856  		if strings.Contains(listout[0], "stacktraceme") {
   857  			t.Fatal("bad output for list command on a parked goroutine")
   858  		}
   859  	})
   860  }
   861  
   862  func TestStepOutReturn(t *testing.T) {
   863  	ver, _ := goversion.Parse(runtime.Version())
   864  	if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
   865  		t.Skip("return variables aren't marked on 1.9 or earlier")
   866  	}
   867  	withTestTerminal("stepoutret", t, func(term *FakeTerminal) {
   868  		term.MustExec("break main.stepout")
   869  		term.MustExec("continue")
   870  		out := term.MustExec("stepout")
   871  		t.Logf("output: %q", out)
   872  		if !strings.Contains(out, "num: ") || !strings.Contains(out, "str: ") {
   873  			t.Fatal("could not find parameter")
   874  		}
   875  	})
   876  }
   877  
   878  func TestOptimizationCheck(t *testing.T) {
   879  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
   880  		term.MustExec("break main.main")
   881  		out := term.MustExec("continue")
   882  		t.Logf("output %q", out)
   883  		if strings.Contains(out, optimizedFunctionWarning) {
   884  			t.Fatal("optimized function warning")
   885  		}
   886  	})
   887  
   888  	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 10) {
   889  		withTestTerminalBuildFlags("continuetestprog", t, test.EnableOptimization|test.EnableInlining, func(term *FakeTerminal) {
   890  			term.MustExec("break main.main")
   891  			out := term.MustExec("continue")
   892  			t.Logf("output %q", out)
   893  			if !strings.Contains(out, optimizedFunctionWarning) {
   894  				t.Fatal("optimized function warning missing")
   895  			}
   896  		})
   897  	}
   898  }
   899  
   900  func TestTruncateStacktrace(t *testing.T) {
   901  	const stacktraceTruncatedMessage = "(truncated)"
   902  	withTestTerminal("stacktraceprog", t, func(term *FakeTerminal) {
   903  		term.MustExec("break main.stacktraceme")
   904  		term.MustExec("continue")
   905  		out1 := term.MustExec("stack")
   906  		t.Logf("untruncated output %q", out1)
   907  		if strings.Contains(out1, stacktraceTruncatedMessage) {
   908  			t.Fatalf("stacktrace was truncated")
   909  		}
   910  		out2 := term.MustExec("stack 1")
   911  		t.Logf("truncated output %q", out2)
   912  		if !strings.Contains(out2, stacktraceTruncatedMessage) {
   913  			t.Fatalf("stacktrace was not truncated")
   914  		}
   915  	})
   916  }
   917  
   918  func TestIssue1493(t *testing.T) {
   919  	// The 'regs' command without the '-a' option should only return
   920  	// general purpose registers.
   921  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
   922  		r := term.MustExec("regs")
   923  		nr := len(strings.Split(r, "\n"))
   924  		t.Logf("regs: %s", r)
   925  		ra := term.MustExec("regs -a")
   926  		nra := len(strings.Split(ra, "\n"))
   927  		t.Logf("regs -a: %s", ra)
   928  		if nr > nra/2+1 {
   929  			t.Fatalf("'regs' returned too many registers (%d) compared to 'regs -a' (%d)", nr, nra)
   930  		}
   931  	})
   932  }
   933  
   934  func findStarFile(name string) string {
   935  	return filepath.Join(test.FindFixturesDir(), name+".star")
   936  }
   937  
   938  func TestIssue1598(t *testing.T) {
   939  	test.MustSupportFunctionCalls(t, testBackend)
   940  	withTestTerminal("issue1598", t, func(term *FakeTerminal) {
   941  		term.MustExec("break issue1598.go:5")
   942  		term.MustExec("continue")
   943  		term.MustExec("config max-string-len 500")
   944  		r := term.MustExec("call x()")
   945  		t.Logf("result %q", r)
   946  		if !strings.Contains(r, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut \\nlabore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut") {
   947  			t.Fatalf("wrong value returned")
   948  		}
   949  	})
   950  }
   951  
   952  func TestExamineMemoryCmd(t *testing.T) {
   953  	withTestTerminal("examinememory", t, func(term *FakeTerminal) {
   954  		term.MustExec("break examinememory.go:19")
   955  		term.MustExec("break examinememory.go:24")
   956  		term.MustExec("continue")
   957  
   958  		addressStr := strings.TrimSpace(term.MustExec("p bspUintptr"))
   959  		address, err := strconv.ParseInt(addressStr, 0, 64)
   960  		if err != nil {
   961  			t.Fatalf("could convert %s into int64, err %s", addressStr, err)
   962  		}
   963  
   964  		res := term.MustExec("examinemem  -count 52 -fmt hex " + addressStr)
   965  		t.Logf("the result of examining memory \n%s", res)
   966  		// check first line
   967  		firstLine := fmt.Sprintf("%#x:   0x0a   0x0b   0x0c   0x0d   0x0e   0x0f   0x10   0x11", address)
   968  		if !strings.Contains(res, firstLine) {
   969  			t.Fatalf("expected first line: %s", firstLine)
   970  		}
   971  
   972  		// check last line
   973  		lastLine := fmt.Sprintf("%#x:   0x3a   0x3b   0x3c   0x00", address+6*8)
   974  		if !strings.Contains(res, lastLine) {
   975  			t.Fatalf("expected last line: %s", lastLine)
   976  		}
   977  
   978  		// second examining memory
   979  		term.MustExec("continue")
   980  		res = term.MustExec("x -count 52 -fmt bin " + addressStr)
   981  		t.Logf("the second result of examining memory result \n%s", res)
   982  
   983  		// check first line
   984  		firstLine = fmt.Sprintf("%#x:   11111111   00001011   00001100   00001101", address)
   985  		if !strings.Contains(res, firstLine) {
   986  			t.Fatalf("expected first line: %s", firstLine)
   987  		}
   988  
   989  		// third examining memory: -x addr
   990  		res = term.MustExec("examinemem -x " + addressStr)
   991  		t.Logf("the third result of examining memory result \n%s", res)
   992  		firstLine = fmt.Sprintf("%#x:   0xff", address)
   993  		if !strings.Contains(res, firstLine) {
   994  			t.Fatalf("expected first line: %s", firstLine)
   995  		}
   996  
   997  		// fourth examining memory: -x addr + offset
   998  		res = term.MustExec("examinemem -x " + addressStr + " + 8")
   999  		t.Logf("the fourth result of examining memory result \n%s", res)
  1000  		firstLine = fmt.Sprintf("%#x:   0x12", address+8)
  1001  		if !strings.Contains(res, firstLine) {
  1002  			t.Fatalf("expected first line: %s", firstLine)
  1003  		}
  1004  		// fifth examining memory: -x &var
  1005  		res = term.MustExec("examinemem -x &bs[0]")
  1006  		t.Logf("the fifth result of examining memory result \n%s", res)
  1007  		firstLine = fmt.Sprintf("%#x:   0xff", address)
  1008  		if !strings.Contains(res, firstLine) {
  1009  			t.Fatalf("expected first line: %s", firstLine)
  1010  		}
  1011  
  1012  		// sixth examining memory: -fmt and double spaces
  1013  		res = term.MustExec("examinemem -fmt  hex  -x &bs[0]")
  1014  		t.Logf("the sixth result of examining memory result \n%s", res)
  1015  		firstLine = fmt.Sprintf("%#x:   0xff", address)
  1016  		if !strings.Contains(res, firstLine) {
  1017  			t.Fatalf("expected first line: %s", firstLine)
  1018  		}
  1019  	})
  1020  
  1021  	withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
  1022  		tests := []struct {
  1023  			Expr string
  1024  			Want int
  1025  		}{
  1026  			{Expr: "&i1", Want: 1},
  1027  			{Expr: "&i2", Want: 2},
  1028  			{Expr: "p1", Want: 1},
  1029  			{Expr: "*pp1", Want: 1},
  1030  			{Expr: "&str1[1]", Want: '1'},
  1031  			{Expr: "c1.pb", Want: 1},
  1032  			{Expr: "&c1.pb.a", Want: 1},
  1033  			{Expr: "&c1.pb.a.A", Want: 1},
  1034  			{Expr: "&c1.pb.a.B", Want: 2},
  1035  		}
  1036  		term.MustExec("continue")
  1037  		for _, test := range tests {
  1038  			res := term.MustExec("examinemem -fmt dec -x " + test.Expr)
  1039  			// strip addr from output, e.g. "0xc0000160b8:   023" -> "023"
  1040  			res = strings.TrimSpace(strings.Split(res, ":")[1])
  1041  			got, err := strconv.Atoi(res)
  1042  			if err != nil {
  1043  				t.Fatalf("expr=%q err=%s", test.Expr, err)
  1044  			} else if got != test.Want {
  1045  				t.Errorf("expr=%q got=%d want=%d", test.Expr, got, test.Want)
  1046  			}
  1047  		}
  1048  	})
  1049  }
  1050  
  1051  func TestPrintOnTracepoint(t *testing.T) {
  1052  	withTestTerminal("increment", t, func(term *FakeTerminal) {
  1053  		term.MustExec("trace main.Increment")
  1054  		term.MustExec("on 1 print y+1")
  1055  		out, _ := term.Exec("continue")
  1056  		if !strings.Contains(out, "y+1: 4") || !strings.Contains(out, "y+1: 2") || !strings.Contains(out, "y+1: 1") {
  1057  			t.Errorf("output did not contain breakpoint information: %q", out)
  1058  		}
  1059  	})
  1060  }
  1061  
  1062  func TestPrintCastToInterface(t *testing.T) {
  1063  	withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
  1064  		term.MustExec("continue")
  1065  		out := term.MustExec(`p (*"interface {}")(uintptr(&iface2))`)
  1066  		t.Logf("%q", out)
  1067  	})
  1068  }
  1069  
  1070  func TestParseNewArgv(t *testing.T) {
  1071  	testCases := []struct {
  1072  		in       string
  1073  		tgtargs  string
  1074  		tgtredir string
  1075  		tgterr   string
  1076  	}{
  1077  		{"-noargs", "", " |  | ", ""},
  1078  		{"-noargs arg1", "", "", "too many arguments to restart"},
  1079  		{"arg1 arg2", "arg1 | arg2", " |  | ", ""},
  1080  		{"arg1 arg2 <input.txt", "arg1 | arg2", "input.txt |  | ", ""},
  1081  		{"arg1 arg2 < input.txt", "arg1 | arg2", "input.txt |  | ", ""},
  1082  		{"<input.txt", "", "input.txt |  | ", ""},
  1083  		{"< input.txt", "", "input.txt |  | ", ""},
  1084  		{"arg1 < input.txt > output.txt 2> error.txt", "arg1", "input.txt | output.txt | error.txt", ""},
  1085  		{"< input.txt > output.txt 2> error.txt", "", "input.txt | output.txt | error.txt", ""},
  1086  		{"arg1 <input.txt >output.txt 2>error.txt", "arg1", "input.txt | output.txt | error.txt", ""},
  1087  		{"<input.txt >output.txt 2>error.txt", "", "input.txt | output.txt | error.txt", ""},
  1088  		{"<input.txt <input2.txt", "", "", "redirect error: stdin redirected twice"},
  1089  	}
  1090  
  1091  	for _, tc := range testCases {
  1092  		resetArgs, newArgv, newRedirects, err := parseNewArgv(tc.in)
  1093  		t.Logf("%q -> %q %q %v\n", tc.in, newArgv, newRedirects, err)
  1094  		if tc.tgterr != "" {
  1095  			if err == nil {
  1096  				t.Errorf("Expected error %q, got no error", tc.tgterr)
  1097  			} else if errstr := err.Error(); errstr != tc.tgterr {
  1098  				t.Errorf("Expected error %q, got error %q", tc.tgterr, errstr)
  1099  			}
  1100  		} else {
  1101  			if !resetArgs {
  1102  				t.Errorf("parse error, resetArgs is false")
  1103  				continue
  1104  			}
  1105  			argvstr := strings.Join(newArgv, " | ")
  1106  			if argvstr != tc.tgtargs {
  1107  				t.Errorf("Expected new arguments %q, got %q", tc.tgtargs, argvstr)
  1108  			}
  1109  			redirstr := strings.Join(newRedirects[:], " | ")
  1110  			if redirstr != tc.tgtredir {
  1111  				t.Errorf("Expected new redirects %q, got %q", tc.tgtredir, redirstr)
  1112  			}
  1113  		}
  1114  	}
  1115  }
  1116  
  1117  func TestContinueUntil(t *testing.T) {
  1118  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
  1119  		if runtime.GOARCH != "386" {
  1120  			listIsAt(t, term, "continue main.main", 16, -1, -1)
  1121  		} else {
  1122  			listIsAt(t, term, "continue main.main", 17, -1, -1)
  1123  		}
  1124  		listIsAt(t, term, "continue main.sayhi", 12, -1, -1)
  1125  	})
  1126  }
  1127  
  1128  func TestContinueUntilExistingBreakpoint(t *testing.T) {
  1129  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
  1130  		term.MustExec("break main.main")
  1131  		if runtime.GOARCH != "386" {
  1132  			listIsAt(t, term, "continue main.main", 16, -1, -1)
  1133  		} else {
  1134  			listIsAt(t, term, "continue main.main", 17, -1, -1)
  1135  		}
  1136  		listIsAt(t, term, "continue main.sayhi", 12, -1, -1)
  1137  	})
  1138  }
  1139  
  1140  func TestPrintFormat(t *testing.T) {
  1141  	withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
  1142  		term.MustExec("continue")
  1143  		out := term.MustExec("print %#x m2[1].B")
  1144  		if !strings.Contains(out, "0xb\n") {
  1145  			t.Fatalf("output did not contain '0xb': %q", out)
  1146  		}
  1147  	})
  1148  }
  1149  
  1150  func TestHitCondBreakpoint(t *testing.T) {
  1151  	withTestTerminal("break", t, func(term *FakeTerminal) {
  1152  		term.MustExec("break bp1 main.main:4")
  1153  		term.MustExec("condition -hitcount bp1 > 2")
  1154  		listIsAt(t, term, "continue", 7, -1, -1)
  1155  		out := term.MustExec("print i")
  1156  		t.Logf("%q", out)
  1157  		if !strings.Contains(out, "3\n") {
  1158  			t.Fatalf("wrong value of i")
  1159  		}
  1160  	})
  1161  }
  1162  
  1163  func TestClearCondBreakpoint(t *testing.T) {
  1164  	withTestTerminal("break", t, func(term *FakeTerminal) {
  1165  		term.MustExec("break main.main:4")
  1166  		term.MustExec("condition 1 i%3==2")
  1167  		listIsAt(t, term, "continue", 7, -1, -1)
  1168  		out := term.MustExec("print i")
  1169  		t.Logf("%q", out)
  1170  		if !strings.Contains(out, "2\n") {
  1171  			t.Fatalf("wrong value of i")
  1172  		}
  1173  		term.MustExec("condition -clear 1")
  1174  		listIsAt(t, term, "continue", 7, -1, -1)
  1175  		out = term.MustExec("print i")
  1176  		t.Logf("%q", out)
  1177  		if !strings.Contains(out, "3\n") {
  1178  			t.Fatalf("wrong value of i")
  1179  		}
  1180  	})
  1181  }
  1182  
  1183  func TestBreakpointEditing(t *testing.T) {
  1184  	term := &FakeTerminal{
  1185  		t:    t,
  1186  		Term: New(nil, &config.Config{}),
  1187  	}
  1188  	_ = term
  1189  
  1190  	var testCases = []struct {
  1191  		inBp    *api.Breakpoint
  1192  		inBpStr string
  1193  		edit    string
  1194  		outBp   *api.Breakpoint
  1195  	}{
  1196  		{ // tracepoint -> breakpoint
  1197  			&api.Breakpoint{Tracepoint: true},
  1198  			"trace",
  1199  			"",
  1200  			&api.Breakpoint{}},
  1201  		{ // breakpoint -> tracepoint
  1202  			&api.Breakpoint{Variables: []string{"a"}},
  1203  			"print a",
  1204  			"print a\ntrace",
  1205  			&api.Breakpoint{Tracepoint: true, Variables: []string{"a"}}},
  1206  		{ // add print var
  1207  			&api.Breakpoint{Variables: []string{"a"}},
  1208  			"print a",
  1209  			"print b\nprint a\n",
  1210  			&api.Breakpoint{Variables: []string{"b", "a"}}},
  1211  		{ // add goroutine flag
  1212  			&api.Breakpoint{},
  1213  			"",
  1214  			"goroutine",
  1215  			&api.Breakpoint{Goroutine: true}},
  1216  		{ // remove goroutine flag
  1217  			&api.Breakpoint{Goroutine: true},
  1218  			"goroutine",
  1219  			"",
  1220  			&api.Breakpoint{}},
  1221  		{ // add stack directive
  1222  			&api.Breakpoint{},
  1223  			"",
  1224  			"stack 10",
  1225  			&api.Breakpoint{Stacktrace: 10}},
  1226  		{ // remove stack directive
  1227  			&api.Breakpoint{Stacktrace: 20},
  1228  			"stack 20",
  1229  			"print a",
  1230  			&api.Breakpoint{Variables: []string{"a"}}},
  1231  		{ // add condition
  1232  			&api.Breakpoint{Variables: []string{"a"}},
  1233  			"print a",
  1234  			"print a\ncond a < b",
  1235  			&api.Breakpoint{Variables: []string{"a"}, Cond: "a < b"}},
  1236  		{ // remove condition
  1237  			&api.Breakpoint{Cond: "a < b"},
  1238  			"cond a < b",
  1239  			"",
  1240  			&api.Breakpoint{}},
  1241  		{ // change condition
  1242  			&api.Breakpoint{Cond: "a < b"},
  1243  			"cond a < b",
  1244  			"cond a < 5",
  1245  			&api.Breakpoint{Cond: "a < 5"}},
  1246  		{ // change hitcount condition
  1247  			&api.Breakpoint{HitCond: "% 2"},
  1248  			"cond -hitcount % 2",
  1249  			"cond -hitcount = 2",
  1250  			&api.Breakpoint{HitCond: "= 2"}},
  1251  	}
  1252  
  1253  	for _, tc := range testCases {
  1254  		bp := *tc.inBp
  1255  		bpStr := strings.Join(formatBreakpointAttrs("", &bp, true), "\n")
  1256  		if bpStr != tc.inBpStr {
  1257  			t.Errorf("Expected %q got %q for:\n%#v", tc.inBpStr, bpStr, tc.inBp)
  1258  		}
  1259  		ctx := callContext{Prefix: onPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: 0, DeferredCall: 0}, Breakpoint: &bp}
  1260  		err := term.cmds.parseBreakpointAttrs(nil, ctx, strings.NewReader(tc.edit))
  1261  		if err != nil {
  1262  			t.Errorf("Unexpected error during edit %q", tc.edit)
  1263  		}
  1264  		if !reflect.DeepEqual(bp, *tc.outBp) {
  1265  			t.Errorf("mismatch after edit\nexpected: %#v\ngot: %#v", tc.outBp, bp)
  1266  		}
  1267  	}
  1268  }
  1269  
  1270  func TestTranscript(t *testing.T) {
  1271  	withTestTerminal("math", t, func(term *FakeTerminal) {
  1272  		term.MustExec("break main.main")
  1273  		out := term.MustExec("continue")
  1274  		if !strings.HasPrefix(out, "> main.main()") {
  1275  			t.Fatalf("Wrong output for next: <%s>", out)
  1276  		}
  1277  		fh, err := ioutil.TempFile("", "test-transcript-*")
  1278  		if err != nil {
  1279  			t.Fatalf("TempFile: %v", err)
  1280  		}
  1281  		name := fh.Name()
  1282  		fh.Close()
  1283  		t.Logf("output to %q", name)
  1284  
  1285  		slurp := func() string {
  1286  			b, err := ioutil.ReadFile(name)
  1287  			if err != nil {
  1288  				t.Fatalf("could not read transcript file: %v", err)
  1289  			}
  1290  			return string(b)
  1291  		}
  1292  
  1293  		term.MustExec(fmt.Sprintf("transcript %s", name))
  1294  		out = term.MustExec("list")
  1295  		//term.MustExec("transcript -off")
  1296  		if out != slurp() {
  1297  			t.Logf("output of list %s", out)
  1298  			t.Logf("contents of transcript: %s", slurp())
  1299  			t.Errorf("transcript and command out differ")
  1300  		}
  1301  
  1302  		term.MustExec(fmt.Sprintf("transcript -t -x %s", name))
  1303  		out = term.MustExec(`print "hello"`)
  1304  		if out != "" {
  1305  			t.Errorf("output of print is %q but should have been suppressed by transcript", out)
  1306  		}
  1307  		if slurp() != "\"hello\"\n" {
  1308  			t.Errorf("wrong contents of transcript: %q", slurp())
  1309  		}
  1310  
  1311  		os.Remove(name)
  1312  	})
  1313  }