github.com/undoio/delve@v1.9.0/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/undoio/delve/pkg/config"
    21  	"github.com/undoio/delve/pkg/goversion"
    22  	"github.com/undoio/delve/pkg/logflags"
    23  	"github.com/undoio/delve/pkg/proc/test"
    24  	"github.com/undoio/delve/service"
    25  	"github.com/undoio/delve/service/api"
    26  	"github.com/undoio/delve/service/debugger"
    27  	"github.com/undoio/delve/service/rpc2"
    28  	"github.com/undoio/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" || testBackend == "undo" {
   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  	test.AllowRecording(t)
   295  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
   296  		term.Exec("continue")
   297  		status, err := term.handleExit()
   298  		if err != nil {
   299  			t.Fatal(err)
   300  		}
   301  		if status != 0 {
   302  			t.Fatalf("incorrect exit status, expected 0, got %d", status)
   303  		}
   304  	})
   305  }
   306  
   307  func TestScopePrefix(t *testing.T) {
   308  	const goroutinesLinePrefix = "  Goroutine "
   309  	const goroutinesCurLinePrefix = "* Goroutine "
   310  	test.AllowRecording(t)
   311  
   312  	lenient := 0
   313  	if runtime.GOOS == "windows" {
   314  		lenient = 1
   315  	}
   316  
   317  	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
   318  		term.MustExec("b stacktraceme")
   319  		term.MustExec("continue")
   320  
   321  		goroutinesOut := strings.Split(term.MustExec("goroutines"), "\n")
   322  		agoroutines := []int{}
   323  		nonagoroutines := []int{}
   324  		curgid := -1
   325  
   326  		for _, line := range goroutinesOut {
   327  			iscur := strings.HasPrefix(line, goroutinesCurLinePrefix)
   328  			if !iscur && !strings.HasPrefix(line, goroutinesLinePrefix) {
   329  				continue
   330  			}
   331  
   332  			dash := strings.Index(line, " - ")
   333  			if dash < 0 {
   334  				continue
   335  			}
   336  
   337  			gid, err := strconv.Atoi(line[len(goroutinesLinePrefix):dash])
   338  			if err != nil {
   339  				continue
   340  			}
   341  
   342  			if iscur {
   343  				curgid = gid
   344  			}
   345  
   346  			if idx := strings.Index(line, " main.agoroutine "); idx < 0 {
   347  				nonagoroutines = append(nonagoroutines, gid)
   348  				continue
   349  			}
   350  
   351  			agoroutines = append(agoroutines, gid)
   352  		}
   353  
   354  		if len(agoroutines) > 10 {
   355  			t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d found): %q", len(agoroutines), goroutinesOut)
   356  		}
   357  
   358  		if len(agoroutines) < 10 {
   359  			extraAgoroutines := 0
   360  			for _, gid := range nonagoroutines {
   361  				stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n")
   362  				for _, line := range stackOut {
   363  					if strings.HasSuffix(line, " main.agoroutine") {
   364  						extraAgoroutines++
   365  						break
   366  					}
   367  				}
   368  			}
   369  			if len(agoroutines)+extraAgoroutines < 10-lenient {
   370  				t.Fatalf("Output of goroutines did not have 10 goroutines stopped on main.agoroutine (%d+%d found): %q", len(agoroutines), extraAgoroutines, goroutinesOut)
   371  			}
   372  		}
   373  
   374  		if curgid < 0 {
   375  			t.Fatalf("Could not find current goroutine in output of goroutines: %q", goroutinesOut)
   376  		}
   377  
   378  		seen := make([]bool, 10)
   379  		for _, gid := range agoroutines {
   380  			stackOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d stack", gid)), "\n")
   381  			fid := -1
   382  			for _, line := range stackOut {
   383  				line = strings.TrimLeft(line, " ")
   384  				space := strings.Index(line, " ")
   385  				if space < 0 {
   386  					continue
   387  				}
   388  				curfid, err := strconv.Atoi(line[:space])
   389  				if err != nil {
   390  					continue
   391  				}
   392  
   393  				if idx := strings.Index(line, " main.agoroutine"); idx >= 0 {
   394  					fid = curfid
   395  					break
   396  				}
   397  			}
   398  			if fid < 0 {
   399  				t.Fatalf("Could not find frame for goroutine %d: %q", gid, stackOut)
   400  			}
   401  			term.AssertExec(fmt.Sprintf("goroutine     %d    frame     %d     locals", gid, fid), "(no locals)\n")
   402  			argsOut := strings.Split(term.MustExec(fmt.Sprintf("goroutine %d frame %d args", gid, fid)), "\n")
   403  			if len(argsOut) != 4 || argsOut[3] != "" {
   404  				t.Fatalf("Wrong number of arguments in goroutine %d frame %d: %v", gid, fid, argsOut)
   405  			}
   406  			out := term.MustExec(fmt.Sprintf("goroutine %d frame %d p i", gid, fid))
   407  			ival, err := strconv.Atoi(out[:len(out)-1])
   408  			if err != nil {
   409  				t.Fatalf("could not parse value %q of i for goroutine %d frame %d: %v", out, gid, fid, err)
   410  			}
   411  			seen[ival] = true
   412  		}
   413  
   414  		for i := range seen {
   415  			if !seen[i] {
   416  				if lenient > 0 {
   417  					lenient--
   418  				} else {
   419  					t.Fatalf("goroutine %d not found", i)
   420  				}
   421  			}
   422  		}
   423  
   424  		term.MustExec("c")
   425  
   426  		term.AssertExecError("frame", "not enough arguments")
   427  		term.AssertExecError(fmt.Sprintf("goroutine %d frame 10 locals", curgid), fmt.Sprintf("Frame 10 does not exist in goroutine %d", curgid))
   428  		term.AssertExecError("goroutine 9000 locals", "unknown goroutine 9000")
   429  
   430  		term.AssertExecError("print n", "could not find symbol value for n")
   431  		term.AssertExec("frame 1 print n", "3\n")
   432  		term.AssertExec("frame 2 print n", "2\n")
   433  		term.AssertExec("frame 3 print n", "1\n")
   434  		term.AssertExec("frame 4 print n", "0\n")
   435  		term.AssertExecError("frame 5 print n", "could not find symbol value for n")
   436  
   437  		term.MustExec("frame 2")
   438  		term.AssertExec("print n", "2\n")
   439  		term.MustExec("frame 4")
   440  		term.AssertExec("print n", "0\n")
   441  		term.MustExec("down")
   442  		term.AssertExec("print n", "1\n")
   443  		term.MustExec("down 2")
   444  		term.AssertExec("print n", "3\n")
   445  		term.AssertExecError("down 2", "Invalid frame -1")
   446  		term.AssertExec("print n", "3\n")
   447  		term.MustExec("up 2")
   448  		term.AssertExec("print n", "1\n")
   449  		term.AssertExecError("up 100", "Invalid frame 103")
   450  		term.AssertExec("print n", "1\n")
   451  
   452  		term.MustExec("step")
   453  		term.AssertExecError("print n", "could not find symbol value for n")
   454  		term.MustExec("frame 2")
   455  		term.AssertExec("print n", "2\n")
   456  	})
   457  }
   458  
   459  func TestOnPrefix(t *testing.T) {
   460  	if runtime.GOOS == "freebsd" {
   461  		t.Skip("test is not valid on FreeBSD")
   462  	}
   463  	const prefix = "\ti: "
   464  	test.AllowRecording(t)
   465  	lenient := false
   466  	if runtime.GOOS == "windows" {
   467  		lenient = true
   468  	}
   469  	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
   470  		term.MustExec("b agobp main.agoroutine")
   471  		term.MustExec("on agobp print i")
   472  
   473  		seen := make([]bool, 10)
   474  
   475  		for {
   476  			outstr, err := term.Exec("continue")
   477  			if err != nil {
   478  				if !strings.Contains(err.Error(), "exited") {
   479  					t.Fatalf("Unexpected error executing 'continue': %v", err)
   480  				}
   481  				break
   482  			}
   483  			out := strings.Split(outstr, "\n")
   484  
   485  			for i := range out {
   486  				if !strings.HasPrefix(out[i], prefix) {
   487  					continue
   488  				}
   489  				id, err := strconv.Atoi(out[i][len(prefix):])
   490  				if err != nil {
   491  					continue
   492  				}
   493  				if seen[id] {
   494  					t.Fatalf("Goroutine %d seen twice\n", id)
   495  				}
   496  				seen[id] = true
   497  			}
   498  		}
   499  
   500  		for i := range seen {
   501  			if !seen[i] {
   502  				if lenient {
   503  					lenient = false
   504  				} else {
   505  					t.Fatalf("Goroutine %d not seen\n", i)
   506  				}
   507  			}
   508  		}
   509  	})
   510  }
   511  
   512  func TestNoVars(t *testing.T) {
   513  	test.AllowRecording(t)
   514  	withTestTerminal("locationsUpperCase", t, func(term *FakeTerminal) {
   515  		term.MustExec("b main.main")
   516  		term.MustExec("continue")
   517  		term.AssertExec("args", "(no args)\n")
   518  		term.AssertExec("locals", "(no locals)\n")
   519  		term.AssertExec("vars filterThatMatchesNothing", "(no vars)\n")
   520  	})
   521  }
   522  
   523  func TestOnPrefixLocals(t *testing.T) {
   524  	if runtime.GOOS == "freebsd" {
   525  		t.Skip("test is not valid on FreeBSD")
   526  	}
   527  	const prefix = "\ti: "
   528  	test.AllowRecording(t)
   529  	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
   530  		term.MustExec("b agobp main.agoroutine")
   531  		term.MustExec("on agobp args -v")
   532  
   533  		seen := make([]bool, 10)
   534  
   535  		for {
   536  			outstr, err := term.Exec("continue")
   537  			if err != nil {
   538  				if !strings.Contains(err.Error(), "exited") {
   539  					t.Fatalf("Unexpected error executing 'continue': %v", err)
   540  				}
   541  				break
   542  			}
   543  			out := strings.Split(outstr, "\n")
   544  
   545  			for i := range out {
   546  				if !strings.HasPrefix(out[i], prefix) {
   547  					continue
   548  				}
   549  				id, err := strconv.Atoi(out[i][len(prefix):])
   550  				if err != nil {
   551  					continue
   552  				}
   553  				if seen[id] {
   554  					t.Fatalf("Goroutine %d seen twice\n", id)
   555  				}
   556  				seen[id] = true
   557  			}
   558  		}
   559  
   560  		for i := range seen {
   561  			if !seen[i] {
   562  				t.Fatalf("Goroutine %d not seen\n", i)
   563  			}
   564  		}
   565  	})
   566  }
   567  
   568  func countOccurrences(s, needle string) int {
   569  	count := 0
   570  	for {
   571  		idx := strings.Index(s, needle)
   572  		if idx < 0 {
   573  			break
   574  		}
   575  		count++
   576  		s = s[idx+len(needle):]
   577  	}
   578  	return count
   579  }
   580  
   581  func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end int) {
   582  	t.Helper()
   583  	outstr := term.MustExec(listcmd)
   584  	lines := strings.Split(outstr, "\n")
   585  
   586  	t.Logf("%q: %q", listcmd, outstr)
   587  
   588  	if cur >= 0 && !strings.Contains(lines[0], fmt.Sprintf(":%d", cur)) {
   589  		t.Fatalf("Could not find current line number in first output line: %q", lines[0])
   590  	}
   591  
   592  	re := regexp.MustCompile(`(=>)?\s+(\d+):`)
   593  
   594  	outStart, outEnd := 0, 0
   595  
   596  	for _, line := range lines[1:] {
   597  		if line == "" {
   598  			continue
   599  		}
   600  		v := re.FindStringSubmatch(line)
   601  		if len(v) != 3 {
   602  			continue
   603  		}
   604  		curline, _ := strconv.Atoi(v[2])
   605  		if v[1] == "=>" {
   606  			if cur != curline {
   607  				t.Fatalf("Wrong current line, got %d expected %d", curline, cur)
   608  			}
   609  		}
   610  		if outStart == 0 {
   611  			outStart = curline
   612  		}
   613  		outEnd = curline
   614  	}
   615  
   616  	if start != -1 || end != -1 {
   617  		if outStart != start || outEnd != end {
   618  			t.Fatalf("Wrong output range, got %d:%d expected %d:%d", outStart, outEnd, start, end)
   619  		}
   620  	}
   621  }
   622  
   623  func TestListCmd(t *testing.T) {
   624  	withTestTerminal("testvariables", t, func(term *FakeTerminal) {
   625  		term.MustExec("continue")
   626  		term.MustExec("continue")
   627  		listIsAt(t, term, "list", 27, 22, 32)
   628  		listIsAt(t, term, "list 69", 69, 64, 74)
   629  		listIsAt(t, term, "frame 1 list", 66, 61, 71)
   630  		listIsAt(t, term, "frame 1 list 69", 69, 64, 74)
   631  		_, err := term.Exec("frame 50 list")
   632  		if err == nil {
   633  			t.Fatalf("Expected error requesting 50th frame")
   634  		}
   635  		listIsAt(t, term, "list testvariables.go:1", -1, 1, 6)
   636  		listIsAt(t, term, "list testvariables.go:10000", -1, 0, 0)
   637  	})
   638  }
   639  
   640  func TestReverseContinue(t *testing.T) {
   641  	test.AllowRecording(t)
   642  	if testBackend != "rr" && testBackend != "undo" {
   643  		return
   644  	}
   645  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
   646  		term.MustExec("break main.main")
   647  		term.MustExec("break main.sayhi")
   648  		listIsAt(t, term, "continue", 16, -1, -1)
   649  		listIsAt(t, term, "continue", 12, -1, -1)
   650  		listIsAt(t, term, "rewind", 16, -1, -1)
   651  	})
   652  }
   653  
   654  func TestCheckpoints(t *testing.T) {
   655  	test.AllowRecording(t)
   656  	if testBackend != "rr" && testBackend != "undo" {
   657  		return
   658  	}
   659  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
   660  		term.MustExec("break main.main")
   661  		listIsAt(t, term, "continue", 16, -1, -1)
   662  		term.MustExec("checkpoint")
   663  		term.MustExec("checkpoints")
   664  		listIsAt(t, term, "next", 17, -1, -1)
   665  		listIsAt(t, term, "next", 18, -1, -1)
   666  		term.MustExec("restart c1")
   667  		term.MustExec("goroutine 1")
   668  		listIsAt(t, term, "list", 16, -1, -1)
   669  	})
   670  }
   671  
   672  func TestNextWithCount(t *testing.T) {
   673  	test.AllowRecording(t)
   674  	withTestTerminal("nextcond", t, func(term *FakeTerminal) {
   675  		term.MustExec("break main.main")
   676  		listIsAt(t, term, "continue", 8, -1, -1)
   677  		listIsAt(t, term, "next 2", 10, -1, -1)
   678  	})
   679  }
   680  
   681  func TestRestart(t *testing.T) {
   682  	withTestTerminal("restartargs", t, func(term *FakeTerminal) {
   683  		term.MustExec("break main.printArgs")
   684  		term.MustExec("continue")
   685  		if out := term.MustExec("print main.args"); !strings.Contains(out, ", []") {
   686  			t.Fatalf("wrong args: %q", out)
   687  		}
   688  		// Reset the arg list
   689  		term.MustExec("restart hello")
   690  		term.MustExec("continue")
   691  		if out := term.MustExec("print main.args"); !strings.Contains(out, ", [\"hello\"]") {
   692  			t.Fatalf("wrong args: %q ", out)
   693  		}
   694  		// Restart w/o arg should retain the current args.
   695  		term.MustExec("restart")
   696  		term.MustExec("continue")
   697  		if out := term.MustExec("print main.args"); !strings.Contains(out, ", [\"hello\"]") {
   698  			t.Fatalf("wrong args: %q ", out)
   699  		}
   700  		// Empty arg list
   701  		term.MustExec("restart -noargs")
   702  		term.MustExec("continue")
   703  		if out := term.MustExec("print main.args"); !strings.Contains(out, ", []") {
   704  			t.Fatalf("wrong args: %q ", out)
   705  		}
   706  	})
   707  }
   708  
   709  func TestIssue827(t *testing.T) {
   710  	// switching goroutines when the current thread isn't running any goroutine
   711  	// causes nil pointer dereference.
   712  	withTestTerminal("notify-v2", t, func(term *FakeTerminal) {
   713  		go func() {
   714  			time.Sleep(1 * time.Second)
   715  			http.Get("http://127.0.0.1:8888/test")
   716  			time.Sleep(1 * time.Second)
   717  			term.client.Halt()
   718  		}()
   719  		term.MustExec("continue")
   720  		term.MustExec("goroutine 1")
   721  	})
   722  }
   723  
   724  func findCmdName(c *Commands, cmdstr string, prefix cmdPrefix) string {
   725  	for _, v := range c.cmds {
   726  		if v.match(cmdstr) {
   727  			if prefix != noPrefix && v.allowedPrefixes&prefix == 0 {
   728  				continue
   729  			}
   730  			return v.aliases[0]
   731  		}
   732  	}
   733  	return ""
   734  }
   735  
   736  func TestConfig(t *testing.T) {
   737  	var term Term
   738  	term.conf = &config.Config{}
   739  	term.cmds = DebugCommands(nil)
   740  
   741  	err := configureCmd(&term, callContext{}, "nonexistent-parameter 10")
   742  	if err == nil {
   743  		t.Fatalf("expected error executing configureCmd(nonexistent-parameter)")
   744  	}
   745  
   746  	err = configureCmd(&term, callContext{}, "max-string-len 10")
   747  	if err != nil {
   748  		t.Fatalf("error executing configureCmd(max-string-len): %v", err)
   749  	}
   750  	if term.conf.MaxStringLen == nil {
   751  		t.Fatalf("expected MaxStringLen 10, got nil")
   752  	}
   753  	if *term.conf.MaxStringLen != 10 {
   754  		t.Fatalf("expected MaxStringLen 10, got: %d", *term.conf.MaxStringLen)
   755  	}
   756  	err = configureCmd(&term, callContext{}, "show-location-expr   true")
   757  	if err != nil {
   758  		t.Fatalf("error executing configureCmd(show-location-expr   true)")
   759  	}
   760  	if term.conf.ShowLocationExpr != true {
   761  		t.Fatalf("expected ShowLocationExpr true, got false")
   762  	}
   763  	err = configureCmd(&term, callContext{}, "max-variable-recurse 4")
   764  	if err != nil {
   765  		t.Fatalf("error executing configureCmd(max-variable-recurse): %v", err)
   766  	}
   767  	if term.conf.MaxVariableRecurse == nil {
   768  		t.Fatalf("expected MaxVariableRecurse 4, got nil")
   769  	}
   770  	if *term.conf.MaxVariableRecurse != 4 {
   771  		t.Fatalf("expected MaxVariableRecurse 4, got: %d", *term.conf.MaxVariableRecurse)
   772  	}
   773  
   774  	err = configureCmd(&term, callContext{}, "substitute-path a b")
   775  	if err != nil {
   776  		t.Fatalf("error executing configureCmd(substitute-path a b): %v", err)
   777  	}
   778  	if len(term.conf.SubstitutePath) != 1 || (term.conf.SubstitutePath[0] != config.SubstitutePathRule{From: "a", To: "b"}) {
   779  		t.Fatalf("unexpected SubstitutePathRules after insert %v", term.conf.SubstitutePath)
   780  	}
   781  
   782  	err = configureCmd(&term, callContext{}, "substitute-path a")
   783  	if err != nil {
   784  		t.Fatalf("error executing configureCmd(substitute-path a): %v", err)
   785  	}
   786  	if len(term.conf.SubstitutePath) != 0 {
   787  		t.Fatalf("unexpected SubstitutePathRules after delete %v", term.conf.SubstitutePath)
   788  	}
   789  
   790  	err = configureCmd(&term, callContext{}, "alias print blah")
   791  	if err != nil {
   792  		t.Fatalf("error executing configureCmd(alias print blah): %v", err)
   793  	}
   794  	if len(term.conf.Aliases["print"]) != 1 {
   795  		t.Fatalf("aliases not changed after configure command %v", term.conf.Aliases)
   796  	}
   797  	if findCmdName(term.cmds, "blah", noPrefix) != "print" {
   798  		t.Fatalf("new alias not found")
   799  	}
   800  
   801  	err = configureCmd(&term, callContext{}, "alias blah")
   802  	if err != nil {
   803  		t.Fatalf("error executing configureCmd(alias blah): %v", err)
   804  	}
   805  	if len(term.conf.Aliases["print"]) != 0 {
   806  		t.Fatalf("alias not removed after configure command %v", term.conf.Aliases)
   807  	}
   808  	if findCmdName(term.cmds, "blah", noPrefix) != "" {
   809  		t.Fatalf("new alias found after delete")
   810  	}
   811  }
   812  
   813  func TestIssue1090(t *testing.T) {
   814  	// Exit while executing 'next' should report the "Process exited" error
   815  	// message instead of crashing.
   816  	test.AllowRecording(t)
   817  	withTestTerminal("math", t, func(term *FakeTerminal) {
   818  		term.MustExec("break main.main")
   819  		term.MustExec("continue")
   820  		for {
   821  			_, err := term.Exec("next")
   822  			if err != nil && strings.Contains(err.Error(), " has exited with status ") {
   823  				break
   824  			}
   825  		}
   826  	})
   827  }
   828  
   829  func TestPrintContextParkedGoroutine(t *testing.T) {
   830  	test.AllowRecording(t)
   831  	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
   832  		term.MustExec("break stacktraceme")
   833  		term.MustExec("continue")
   834  
   835  		// pick a goroutine that isn't running on a thread
   836  		gid := ""
   837  		gout := strings.Split(term.MustExec("goroutines"), "\n")
   838  		t.Logf("goroutines -> %q", gout)
   839  		for _, gline := range gout {
   840  			if !strings.Contains(gline, "thread ") && strings.Contains(gline, "agoroutine") {
   841  				if dash := strings.Index(gline, " - "); dash > 0 {
   842  					gid = gline[len("  Goroutine "):dash]
   843  					break
   844  				}
   845  			}
   846  		}
   847  
   848  		t.Logf("picked %q", gid)
   849  		term.MustExec(fmt.Sprintf("goroutine %s", gid))
   850  
   851  		frameout := strings.Split(term.MustExec("frame 0"), "\n")
   852  		t.Logf("frame 0 -> %q", frameout)
   853  		if strings.Contains(frameout[0], "stacktraceme") {
   854  			t.Fatal("bad output for `frame 0` command on a parked goorutine")
   855  		}
   856  
   857  		listout := strings.Split(term.MustExec("list"), "\n")
   858  		t.Logf("list -> %q", listout)
   859  		if strings.Contains(listout[0], "stacktraceme") {
   860  			t.Fatal("bad output for list command on a parked goroutine")
   861  		}
   862  	})
   863  }
   864  
   865  func TestStepOutReturn(t *testing.T) {
   866  	ver, _ := goversion.Parse(runtime.Version())
   867  	if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
   868  		t.Skip("return variables aren't marked on 1.9 or earlier")
   869  	}
   870  	test.AllowRecording(t)
   871  	withTestTerminal("stepoutret", t, func(term *FakeTerminal) {
   872  		term.MustExec("break main.stepout")
   873  		term.MustExec("continue")
   874  		out := term.MustExec("stepout")
   875  		t.Logf("output: %q", out)
   876  		if !strings.Contains(out, "num: ") || !strings.Contains(out, "str: ") {
   877  			t.Fatal("could not find parameter")
   878  		}
   879  	})
   880  }
   881  
   882  func TestOptimizationCheck(t *testing.T) {
   883  	test.AllowRecording(t)
   884  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
   885  		term.MustExec("break main.main")
   886  		out := term.MustExec("continue")
   887  		t.Logf("output %q", out)
   888  		if strings.Contains(out, optimizedFunctionWarning) {
   889  			t.Fatal("optimized function warning")
   890  		}
   891  	})
   892  
   893  	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 10) {
   894  		withTestTerminalBuildFlags("continuetestprog", t, test.EnableOptimization|test.EnableInlining, func(term *FakeTerminal) {
   895  			term.MustExec("break main.main")
   896  			out := term.MustExec("continue")
   897  			t.Logf("output %q", out)
   898  			if !strings.Contains(out, optimizedFunctionWarning) {
   899  				t.Fatal("optimized function warning missing")
   900  			}
   901  		})
   902  	}
   903  }
   904  
   905  func TestTruncateStacktrace(t *testing.T) {
   906  	const stacktraceTruncatedMessage = "(truncated)"
   907  	test.AllowRecording(t)
   908  	withTestTerminal("stacktraceprog", t, func(term *FakeTerminal) {
   909  		term.MustExec("break main.stacktraceme")
   910  		term.MustExec("continue")
   911  		out1 := term.MustExec("stack")
   912  		t.Logf("untruncated output %q", out1)
   913  		if strings.Contains(out1, stacktraceTruncatedMessage) {
   914  			t.Fatalf("stacktrace was truncated")
   915  		}
   916  		out2 := term.MustExec("stack 1")
   917  		t.Logf("truncated output %q", out2)
   918  		if !strings.Contains(out2, stacktraceTruncatedMessage) {
   919  			t.Fatalf("stacktrace was not truncated")
   920  		}
   921  	})
   922  }
   923  
   924  func TestIssue1493(t *testing.T) {
   925  	// The 'regs' command without the '-a' option should only return
   926  	// general purpose registers.
   927  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
   928  		r := term.MustExec("regs")
   929  		nr := len(strings.Split(r, "\n"))
   930  		t.Logf("regs: %s", r)
   931  		ra := term.MustExec("regs -a")
   932  		nra := len(strings.Split(ra, "\n"))
   933  		t.Logf("regs -a: %s", ra)
   934  		if nr > nra/2+1 {
   935  			t.Fatalf("'regs' returned too many registers (%d) compared to 'regs -a' (%d)", nr, nra)
   936  		}
   937  	})
   938  }
   939  
   940  func findStarFile(name string) string {
   941  	return filepath.Join(test.FindFixturesDir(), name+".star")
   942  }
   943  
   944  func TestIssue1598(t *testing.T) {
   945  	test.MustSupportFunctionCalls(t, testBackend)
   946  	withTestTerminal("issue1598", t, func(term *FakeTerminal) {
   947  		term.MustExec("break issue1598.go:5")
   948  		term.MustExec("continue")
   949  		term.MustExec("config max-string-len 500")
   950  		r := term.MustExec("call x()")
   951  		t.Logf("result %q", r)
   952  		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") {
   953  			t.Fatalf("wrong value returned")
   954  		}
   955  	})
   956  }
   957  
   958  func TestExamineMemoryCmd(t *testing.T) {
   959  	withTestTerminal("examinememory", t, func(term *FakeTerminal) {
   960  		term.MustExec("break examinememory.go:19")
   961  		term.MustExec("break examinememory.go:24")
   962  		term.MustExec("continue")
   963  
   964  		addressStr := strings.TrimSpace(term.MustExec("p bspUintptr"))
   965  		address, err := strconv.ParseInt(addressStr, 0, 64)
   966  		if err != nil {
   967  			t.Fatalf("could convert %s into int64, err %s", addressStr, err)
   968  		}
   969  
   970  		res := term.MustExec("examinemem  -count 52 -fmt hex " + addressStr)
   971  		t.Logf("the result of examining memory \n%s", res)
   972  		// check first line
   973  		firstLine := fmt.Sprintf("%#x:   0x0a   0x0b   0x0c   0x0d   0x0e   0x0f   0x10   0x11", address)
   974  		if !strings.Contains(res, firstLine) {
   975  			t.Fatalf("expected first line: %s", firstLine)
   976  		}
   977  
   978  		// check last line
   979  		lastLine := fmt.Sprintf("%#x:   0x3a   0x3b   0x3c   0x00", address+6*8)
   980  		if !strings.Contains(res, lastLine) {
   981  			t.Fatalf("expected last line: %s", lastLine)
   982  		}
   983  
   984  		// second examining memory
   985  		term.MustExec("continue")
   986  		res = term.MustExec("x -count 52 -fmt bin " + addressStr)
   987  		t.Logf("the second result of examining memory result \n%s", res)
   988  
   989  		// check first line
   990  		firstLine = fmt.Sprintf("%#x:   11111111   00001011   00001100   00001101", address)
   991  		if !strings.Contains(res, firstLine) {
   992  			t.Fatalf("expected first line: %s", firstLine)
   993  		}
   994  
   995  		// third examining memory: -x addr
   996  		res = term.MustExec("examinemem -x " + addressStr)
   997  		t.Logf("the third result of examining memory result \n%s", res)
   998  		firstLine = fmt.Sprintf("%#x:   0xff", address)
   999  		if !strings.Contains(res, firstLine) {
  1000  			t.Fatalf("expected first line: %s", firstLine)
  1001  		}
  1002  
  1003  		// fourth examining memory: -x addr + offset
  1004  		res = term.MustExec("examinemem -x " + addressStr + " + 8")
  1005  		t.Logf("the fourth result of examining memory result \n%s", res)
  1006  		firstLine = fmt.Sprintf("%#x:   0x12", address+8)
  1007  		if !strings.Contains(res, firstLine) {
  1008  			t.Fatalf("expected first line: %s", firstLine)
  1009  		}
  1010  		// fifth examining memory: -x &var
  1011  		res = term.MustExec("examinemem -x &bs[0]")
  1012  		t.Logf("the fifth result of examining memory result \n%s", res)
  1013  		firstLine = fmt.Sprintf("%#x:   0xff", address)
  1014  		if !strings.Contains(res, firstLine) {
  1015  			t.Fatalf("expected first line: %s", firstLine)
  1016  		}
  1017  
  1018  		// sixth examining memory: -fmt and double spaces
  1019  		res = term.MustExec("examinemem -fmt  hex  -x &bs[0]")
  1020  		t.Logf("the sixth result of examining memory result \n%s", res)
  1021  		firstLine = fmt.Sprintf("%#x:   0xff", address)
  1022  		if !strings.Contains(res, firstLine) {
  1023  			t.Fatalf("expected first line: %s", firstLine)
  1024  		}
  1025  	})
  1026  
  1027  	withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
  1028  		tests := []struct {
  1029  			Expr string
  1030  			Want int
  1031  		}{
  1032  			{Expr: "&i1", Want: 1},
  1033  			{Expr: "&i2", Want: 2},
  1034  			{Expr: "p1", Want: 1},
  1035  			{Expr: "*pp1", Want: 1},
  1036  			{Expr: "&str1[1]", Want: '1'},
  1037  			{Expr: "c1.pb", Want: 1},
  1038  			{Expr: "&c1.pb.a", Want: 1},
  1039  			{Expr: "&c1.pb.a.A", Want: 1},
  1040  			{Expr: "&c1.pb.a.B", Want: 2},
  1041  		}
  1042  		term.MustExec("continue")
  1043  		for _, test := range tests {
  1044  			res := term.MustExec("examinemem -fmt dec -x " + test.Expr)
  1045  			// strip addr from output, e.g. "0xc0000160b8:   023" -> "023"
  1046  			res = strings.TrimSpace(strings.Split(res, ":")[1])
  1047  			got, err := strconv.Atoi(res)
  1048  			if err != nil {
  1049  				t.Fatalf("expr=%q err=%s", test.Expr, err)
  1050  			} else if got != test.Want {
  1051  				t.Errorf("expr=%q got=%d want=%d", test.Expr, got, test.Want)
  1052  			}
  1053  		}
  1054  	})
  1055  }
  1056  
  1057  func TestPrintOnTracepoint(t *testing.T) {
  1058  	test.AllowRecording(t)
  1059  	withTestTerminal("increment", t, func(term *FakeTerminal) {
  1060  		term.MustExec("trace main.Increment")
  1061  		term.MustExec("on 1 print y+1")
  1062  		out, _ := term.Exec("continue")
  1063  		if !strings.Contains(out, "y+1: 4") || !strings.Contains(out, "y+1: 2") || !strings.Contains(out, "y+1: 1") {
  1064  			t.Errorf("output did not contain breakpoint information: %q", out)
  1065  		}
  1066  	})
  1067  }
  1068  
  1069  func TestPrintCastToInterface(t *testing.T) {
  1070  	withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
  1071  		term.MustExec("continue")
  1072  		out := term.MustExec(`p (*"interface {}")(uintptr(&iface2))`)
  1073  		t.Logf("%q", out)
  1074  	})
  1075  }
  1076  
  1077  func TestParseNewArgv(t *testing.T) {
  1078  	testCases := []struct {
  1079  		in       string
  1080  		tgtargs  string
  1081  		tgtredir string
  1082  		tgterr   string
  1083  	}{
  1084  		{"-noargs", "", " |  | ", ""},
  1085  		{"-noargs arg1", "", "", "too many arguments to restart"},
  1086  		{"arg1 arg2", "arg1 | arg2", " |  | ", ""},
  1087  		{"arg1 arg2 <input.txt", "arg1 | arg2", "input.txt |  | ", ""},
  1088  		{"arg1 arg2 < input.txt", "arg1 | arg2", "input.txt |  | ", ""},
  1089  		{"<input.txt", "", "input.txt |  | ", ""},
  1090  		{"< input.txt", "", "input.txt |  | ", ""},
  1091  		{"arg1 < input.txt > output.txt 2> error.txt", "arg1", "input.txt | output.txt | error.txt", ""},
  1092  		{"< input.txt > output.txt 2> error.txt", "", "input.txt | output.txt | error.txt", ""},
  1093  		{"arg1 <input.txt >output.txt 2>error.txt", "arg1", "input.txt | output.txt | error.txt", ""},
  1094  		{"<input.txt >output.txt 2>error.txt", "", "input.txt | output.txt | error.txt", ""},
  1095  		{"<input.txt <input2.txt", "", "", "redirect error: stdin redirected twice"},
  1096  	}
  1097  
  1098  	for _, tc := range testCases {
  1099  		resetArgs, newArgv, newRedirects, err := parseNewArgv(tc.in)
  1100  		t.Logf("%q -> %q %q %v\n", tc.in, newArgv, newRedirects, err)
  1101  		if tc.tgterr != "" {
  1102  			if err == nil {
  1103  				t.Errorf("Expected error %q, got no error", tc.tgterr)
  1104  			} else if errstr := err.Error(); errstr != tc.tgterr {
  1105  				t.Errorf("Expected error %q, got error %q", tc.tgterr, errstr)
  1106  			}
  1107  		} else {
  1108  			if !resetArgs {
  1109  				t.Errorf("parse error, resetArgs is false")
  1110  				continue
  1111  			}
  1112  			argvstr := strings.Join(newArgv, " | ")
  1113  			if argvstr != tc.tgtargs {
  1114  				t.Errorf("Expected new arguments %q, got %q", tc.tgtargs, argvstr)
  1115  			}
  1116  			redirstr := strings.Join(newRedirects[:], " | ")
  1117  			if redirstr != tc.tgtredir {
  1118  				t.Errorf("Expected new redirects %q, got %q", tc.tgtredir, redirstr)
  1119  			}
  1120  		}
  1121  	}
  1122  }
  1123  
  1124  func TestContinueUntil(t *testing.T) {
  1125  	test.AllowRecording(t)
  1126  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
  1127  		if runtime.GOARCH != "386" {
  1128  			listIsAt(t, term, "continue main.main", 16, -1, -1)
  1129  		} else {
  1130  			listIsAt(t, term, "continue main.main", 17, -1, -1)
  1131  		}
  1132  		listIsAt(t, term, "continue main.sayhi", 12, -1, -1)
  1133  	})
  1134  }
  1135  
  1136  func TestContinueUntilExistingBreakpoint(t *testing.T) {
  1137  	test.AllowRecording(t)
  1138  	withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
  1139  		term.MustExec("break main.main")
  1140  		if runtime.GOARCH != "386" {
  1141  			listIsAt(t, term, "continue main.main", 16, -1, -1)
  1142  		} else {
  1143  			listIsAt(t, term, "continue main.main", 17, -1, -1)
  1144  		}
  1145  		listIsAt(t, term, "continue main.sayhi", 12, -1, -1)
  1146  	})
  1147  }
  1148  
  1149  func TestPrintFormat(t *testing.T) {
  1150  	withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
  1151  		term.MustExec("continue")
  1152  		out := term.MustExec("print %#x m2[1].B")
  1153  		if !strings.Contains(out, "0xb\n") {
  1154  			t.Fatalf("output did not contain '0xb': %q", out)
  1155  		}
  1156  	})
  1157  }
  1158  
  1159  func TestHitCondBreakpoint(t *testing.T) {
  1160  	test.AllowRecording(t)
  1161  	withTestTerminal("break", t, func(term *FakeTerminal) {
  1162  		term.MustExec("break bp1 main.main:4")
  1163  		term.MustExec("condition -hitcount bp1 > 2")
  1164  		listIsAt(t, term, "continue", 7, -1, -1)
  1165  		out := term.MustExec("print i")
  1166  		t.Logf("%q", out)
  1167  		if !strings.Contains(out, "3\n") {
  1168  			t.Fatalf("wrong value of i")
  1169  		}
  1170  	})
  1171  }
  1172  
  1173  func TestClearCondBreakpoint(t *testing.T) {
  1174  	withTestTerminal("break", t, func(term *FakeTerminal) {
  1175  		term.MustExec("break main.main:4")
  1176  		term.MustExec("condition 1 i%3==2")
  1177  		listIsAt(t, term, "continue", 7, -1, -1)
  1178  		out := term.MustExec("print i")
  1179  		t.Logf("%q", out)
  1180  		if !strings.Contains(out, "2\n") {
  1181  			t.Fatalf("wrong value of i")
  1182  		}
  1183  		term.MustExec("condition -clear 1")
  1184  		listIsAt(t, term, "continue", 7, -1, -1)
  1185  		out = term.MustExec("print i")
  1186  		t.Logf("%q", out)
  1187  		if !strings.Contains(out, "3\n") {
  1188  			t.Fatalf("wrong value of i")
  1189  		}
  1190  	})
  1191  }
  1192  
  1193  func TestBreakpointEditing(t *testing.T) {
  1194  	term := &FakeTerminal{
  1195  		t:    t,
  1196  		Term: New(nil, &config.Config{}),
  1197  	}
  1198  	_ = term
  1199  
  1200  	var testCases = []struct {
  1201  		inBp    *api.Breakpoint
  1202  		inBpStr string
  1203  		edit    string
  1204  		outBp   *api.Breakpoint
  1205  	}{
  1206  		{ // tracepoint -> breakpoint
  1207  			&api.Breakpoint{Tracepoint: true},
  1208  			"trace",
  1209  			"",
  1210  			&api.Breakpoint{}},
  1211  		{ // breakpoint -> tracepoint
  1212  			&api.Breakpoint{Variables: []string{"a"}},
  1213  			"print a",
  1214  			"print a\ntrace",
  1215  			&api.Breakpoint{Tracepoint: true, Variables: []string{"a"}}},
  1216  		{ // add print var
  1217  			&api.Breakpoint{Variables: []string{"a"}},
  1218  			"print a",
  1219  			"print b\nprint a\n",
  1220  			&api.Breakpoint{Variables: []string{"b", "a"}}},
  1221  		{ // add goroutine flag
  1222  			&api.Breakpoint{},
  1223  			"",
  1224  			"goroutine",
  1225  			&api.Breakpoint{Goroutine: true}},
  1226  		{ // remove goroutine flag
  1227  			&api.Breakpoint{Goroutine: true},
  1228  			"goroutine",
  1229  			"",
  1230  			&api.Breakpoint{}},
  1231  		{ // add stack directive
  1232  			&api.Breakpoint{},
  1233  			"",
  1234  			"stack 10",
  1235  			&api.Breakpoint{Stacktrace: 10}},
  1236  		{ // remove stack directive
  1237  			&api.Breakpoint{Stacktrace: 20},
  1238  			"stack 20",
  1239  			"print a",
  1240  			&api.Breakpoint{Variables: []string{"a"}}},
  1241  		{ // add condition
  1242  			&api.Breakpoint{Variables: []string{"a"}},
  1243  			"print a",
  1244  			"print a\ncond a < b",
  1245  			&api.Breakpoint{Variables: []string{"a"}, Cond: "a < b"}},
  1246  		{ // remove condition
  1247  			&api.Breakpoint{Cond: "a < b"},
  1248  			"cond a < b",
  1249  			"",
  1250  			&api.Breakpoint{}},
  1251  		{ // change condition
  1252  			&api.Breakpoint{Cond: "a < b"},
  1253  			"cond a < b",
  1254  			"cond a < 5",
  1255  			&api.Breakpoint{Cond: "a < 5"}},
  1256  		{ // change hitcount condition
  1257  			&api.Breakpoint{HitCond: "% 2"},
  1258  			"cond -hitcount % 2",
  1259  			"cond -hitcount = 2",
  1260  			&api.Breakpoint{HitCond: "= 2"}},
  1261  	}
  1262  
  1263  	for _, tc := range testCases {
  1264  		bp := *tc.inBp
  1265  		bpStr := strings.Join(formatBreakpointAttrs("", &bp, true), "\n")
  1266  		if bpStr != tc.inBpStr {
  1267  			t.Errorf("Expected %q got %q for:\n%#v", tc.inBpStr, bpStr, tc.inBp)
  1268  		}
  1269  		ctx := callContext{Prefix: onPrefix, Scope: api.EvalScope{GoroutineID: -1, Frame: 0, DeferredCall: 0}, Breakpoint: &bp}
  1270  		err := term.cmds.parseBreakpointAttrs(nil, ctx, strings.NewReader(tc.edit))
  1271  		if err != nil {
  1272  			t.Errorf("Unexpected error during edit %q", tc.edit)
  1273  		}
  1274  		if !reflect.DeepEqual(bp, *tc.outBp) {
  1275  			t.Errorf("mismatch after edit\nexpected: %#v\ngot: %#v", tc.outBp, bp)
  1276  		}
  1277  	}
  1278  }
  1279  
  1280  func TestTranscript(t *testing.T) {
  1281  	withTestTerminal("math", t, func(term *FakeTerminal) {
  1282  		term.MustExec("break main.main")
  1283  		out := term.MustExec("continue")
  1284  		if !strings.HasPrefix(out, "> main.main()") {
  1285  			t.Fatalf("Wrong output for next: <%s>", out)
  1286  		}
  1287  		fh, err := ioutil.TempFile("", "test-transcript-*")
  1288  		if err != nil {
  1289  			t.Fatalf("TempFile: %v", err)
  1290  		}
  1291  		name := fh.Name()
  1292  		fh.Close()
  1293  		t.Logf("output to %q", name)
  1294  
  1295  		slurp := func() string {
  1296  			b, err := ioutil.ReadFile(name)
  1297  			if err != nil {
  1298  				t.Fatalf("could not read transcript file: %v", err)
  1299  			}
  1300  			return string(b)
  1301  		}
  1302  
  1303  		term.MustExec(fmt.Sprintf("transcript %s", name))
  1304  		out = term.MustExec("list")
  1305  		//term.MustExec("transcript -off")
  1306  		if out != slurp() {
  1307  			t.Logf("output of list %s", out)
  1308  			t.Logf("contents of transcript: %s", slurp())
  1309  			t.Errorf("transcript and command out differ")
  1310  		}
  1311  
  1312  		term.MustExec(fmt.Sprintf("transcript -t -x %s", name))
  1313  		out = term.MustExec(`print "hello"`)
  1314  		if out != "" {
  1315  			t.Errorf("output of print is %q but should have been suppressed by transcript", out)
  1316  		}
  1317  		if slurp() != "\"hello\"\n" {
  1318  			t.Errorf("wrong contents of transcript: %q", slurp())
  1319  		}
  1320  
  1321  		os.Remove(name)
  1322  	})
  1323  }