package proc_test

import (
	"fmt"
	"go/constant"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"testing"

	"github.com/go-delve/delve/pkg/goversion"
	"github.com/go-delve/delve/pkg/proc"
	protest "github.com/go-delve/delve/pkg/proc/test"
	"github.com/go-delve/delve/service/api"
)

type nextTest struct {
	begin, end int
}

type contFunc int

const (
	contContinue contFunc = iota
	contNext
	contStep
	contStepout
	contReverseNext
	contReverseStep
	contReverseStepout
	contContinueToBreakpoint
	contNothing
)

type seqTest struct {
	cf  contFunc
	pos any
}

func testseq(program string, contFunc contFunc, testcases []nextTest, initialLocation string, t *testing.T) {
	seqTestcases := make([]seqTest, len(testcases)+1)
	seqTestcases[0] = seqTest{contContinue, testcases[0].begin}
	for i := range testcases {
		if i > 0 {
			if testcases[i-1].end != testcases[i].begin {
				panic(fmt.Errorf("begin/end mismatch at index %d", i))
			}
		}
		seqTestcases[i+1] = seqTest{contFunc, testcases[i].end}
	}
	testseq2(t, program, initialLocation, seqTestcases)
}

const traceTestseq2 = true

func testseq2(t *testing.T, program string, initialLocation string, testcases []seqTest) {
	testseq2Args(".", []string{}, 0, t, program, initialLocation, testcases)
}

func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *testing.T, program string, initialLocation string, testcases []seqTest) {
	protest.AllowRecording(t)
	t.Helper()
	withTestProcessArgs(program, t, wd, args, buildFlags, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
		checkBreakpointClear := true
		var bp *proc.Breakpoint
		if initialLocation != "" {
			bp = setFunctionBreakpoint(p, t, initialLocation)
		} else if testcases[0].cf == contContinue {
			bp = setFileBreakpoint(p, t, fixture.Source, testcases[0].pos.(int))
		} else if testcases[0].cf == contNothing {
			// Do nothing
			checkBreakpointClear = false
		} else {
			panic("testseq2 can not set initial breakpoint")
		}
		if traceTestseq2 {
			t.Logf("initial breakpoint %v", bp)
		}

		testseq2intl(t, fixture, grp, p, bp, testcases)

		if countBreakpoints(p) != 0 && checkBreakpointClear {
			t.Fatal("Not all breakpoints were cleaned up", len(p.Breakpoints().M))
		}
	})
}

func testseq2intl(t *testing.T, fixture protest.Fixture, grp *proc.TargetGroup, p *proc.Target, bp *proc.Breakpoint, testcases []seqTest) {
	f, ln := currentLineNumber(p, t)
	for i, tc := range testcases {
		switch tc.cf {
		case contNext:
			if traceTestseq2 {
				t.Log("next")
			}
			assertNoError(grp.Next(), t, "Next() returned an error")
		case contStep:
			if traceTestseq2 {
				t.Log("step")
			}
			assertNoError(grp.Step(), t, "Step() returned an error")
		case contStepout:
			if traceTestseq2 {
				t.Log("stepout")
			}
			assertNoError(grp.StepOut(), t, "StepOut() returned an error")
		case contContinue:
			if traceTestseq2 {
				t.Log("continue")
			}
			assertNoError(grp.Continue(), t, "Continue() returned an error")
			if i == 0 {
				if traceTestseq2 {
					t.Log("clearing initial breakpoint")
				}
				err := p.ClearBreakpoint(bp.Addr)
				assertNoError(err, t, "ClearBreakpoint() returned an error")
			}
		case contReverseNext:
			if traceTestseq2 {
				t.Log("reverse-next")
			}
			assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch")
			assertNoError(grp.Next(), t, "reverse Next() returned an error")
			assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch")
		case contReverseStep:
			if traceTestseq2 {
				t.Log("reverse-step")
			}
			assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch")
			assertNoError(grp.Step(), t, "reverse Step() returned an error")
			assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch")
		case contReverseStepout:
			if traceTestseq2 {
				t.Log("reverse-stepout")
			}
			assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch")
			assertNoError(grp.StepOut(), t, "reverse StepOut() returned an error")
			assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch")
		case contContinueToBreakpoint:
			bp := setFileBreakpoint(p, t, fixture.Source, tc.pos.(int))
			if traceTestseq2 {
				t.Log("continue")
			}
			assertNoError(grp.Continue(), t, "Continue() returned an error")
			err := p.ClearBreakpoint(bp.Addr)
			assertNoError(err, t, "ClearBreakpoint() returned an error")
		case contNothing:
			// do nothing
		}

		if err := p.CurrentThread().Breakpoint().CondError; err != nil {
			t.Logf("breakpoint condition error: %v", err)
		}

		f, ln = currentLineNumber(p, t)
		regs, _ := p.CurrentThread().Registers()
		pc := regs.PC()
		_, _, fn := p.BinInfo().PCToLine(pc)

		if traceTestseq2 {
			fnname := "?"
			if fn != nil {
				fnname = fn.Name
			}
			t.Logf("at %#x (%s) %s:%d", pc, fnname, f, ln)
		}
		switch pos := tc.pos.(type) {
		case int:
			if pos >= 0 && ln != pos {
				t.Fatalf("Program did not continue to correct next location expected %d was %s:%d (%#x) (testcase %d)", pos, filepath.Base(f), ln, pc, i)
			}
		case string:
			v := strings.Split(pos, ":")
			tgtln, _ := strconv.Atoi(v[1])
			if !strings.HasSuffix(f, v[0]) || (ln != tgtln) {
				t.Fatalf("Program did not continue to correct next location, expected %s was %s:%d (%#x) (testcase %d)", pos, filepath.Base(f), ln, pc, i)
			}
		case func(*proc.Target):
			pos(p)
		case func(*proc.TargetGroup, *proc.Target):
			pos(grp, p)
		default:
			panic(fmt.Errorf("unexpected type %T", pos))
		}
	}
}

func TestNextGeneral(t *testing.T) {
	var testcases []nextTest

	ver, _ := goversion.Parse(runtime.Version())

	if ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 7, Rev: -1}) {
		testcases = []nextTest{
			{17, 19},
			{19, 20},
			{20, 23},
			{23, 24},
			{24, 26},
			{26, 31},
			{31, 23},
			{23, 24},
			{24, 26},
			{26, 31},
			{31, 23},
			{23, 24},
			{24, 26},
			{26, 27},
			{27, 28},
			{28, 34},
		}
	} else {
		testcases = []nextTest{
			{17, 19},
			{19, 20},
			{20, 23},
			{23, 24},
			{24, 26},
			{26, 31},
			{31, 23},
			{23, 24},
			{24, 26},
			{26, 31},
			{31, 23},
			{23, 24},
			{24, 26},
			{26, 27},
			{27, 34},
		}
	}

	testseq("testnextprog", contNext, testcases, "main.testnext", t)
}

func TestNextFunctionReturn(t *testing.T) {
	testcases := []nextTest{
		{13, 14},
		{14, 15},
		{15, 35},
	}
	protest.AllowRecording(t)
	testseq("testnextprog", contNext, testcases, "main.helloworld", t)
}

func TestNextFunctionReturnDefer(t *testing.T) {
	var testcases []nextTest

	ver, _ := goversion.Parse(runtime.Version())

	if ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) {
		testcases = []nextTest{
			{5, 6},
			{6, 9},
			{9, 10},
		}
	} else {
		testcases = []nextTest{
			{5, 8},
			{8, 9},
			{9, 10},
		}
	}
	protest.AllowRecording(t)
	testseq("testnextdefer", contNext, testcases, "main.main", t)
}

func TestNextDeferReturnAndDirectCall(t *testing.T) {
	// Next should not step into a deferred function if it is called
	// directly, only if it is called through a panic or a deferreturn.
	// Here we test the case where the function is called by a deferreturn
	testseq("defercall", contNext, []nextTest{
		{9, 10},
		{10, 11},
		{11, 12},
		{12, 13},
		{13, 28}}, "main.callAndDeferReturn", t)
}

func TestNextPanicAndDirectCall(t *testing.T) {
	// Next should not step into a deferred function if it is called
	// directly, only if it is called through a panic or a deferreturn.
	// Here we test the case where the function is called by a panic
	testseq("defercall", contNext, []nextTest{
		{15, 16},
		{16, 17},
		{17, 18},
		{18, 6}}, "main.callAndPanic2", t)
}

func TestStepCall(t *testing.T) {
	testseq("testnextprog", contStep, []nextTest{
		{34, 13},
		{13, 14}}, "", t)
}

func TestStepCallPtr(t *testing.T) {
	// Tests that Step works correctly when calling functions with a
	// function pointer.
	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) && !protest.RegabiSupported() {
		testseq("teststepprog", contStep, []nextTest{
			{9, 10},
			{10, 6},
			{6, 7},
			{7, 11}}, "", t)
	} else {
		if runtime.GOOS == "linux" && runtime.GOARCH == "ppc64le" && buildMode == "pie" {
			testseq("teststepprog", contStep, []nextTest{
				{9, 10},
				{10, 5},
				{5, 6},
				{6, 7},
				{7, 10},
				{10, 11}}, "", t)
		} else {
			testseq("teststepprog", contStep, []nextTest{
				{9, 10},
				{10, 5},
				{5, 6},
				{6, 7},
				{7, 11}}, "", t)
		}
	}
}

func TestStepReturnAndPanic(t *testing.T) {
	// Tests that Step works correctly when returning from functions
	// and when a deferred function is called when panic'ing.
	testseq("defercall", contStep, []nextTest{
		{17, 6},
		{6, 7},
		{7, 18},
		{18, 6},
		{6, 7}}, "", t)
}

func TestStepDeferReturn(t *testing.T) {
	// Tests that Step works correctly when a deferred function is
	// called during a return.
	testseq("defercall", contStep, []nextTest{
		{11, 6},
		{6, 7},
		{7, 12},
		{12, 13},
		{13, 6},
		{6, 7},
		{7, 13},
		{13, 28}}, "", t)
}

func TestStepIgnorePrivateRuntime(t *testing.T) {
	// Tests that Step will ignore calls to private runtime functions
	// (such as runtime.convT2E in this case)
	switch {
	case goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) && protest.RegabiSupported():
		testseq("teststepprog", contStep, []nextTest{
			{21, 13},
			{13, 14},
			{14, 15},
			{15, 17},
			{17, 22}}, "", t)
	case goversion.VersionAfterOrEqual(runtime.Version(), 1, 17):
		testseq("teststepprog", contStep, []nextTest{
			{21, 14},
			{14, 15},
			{15, 17},
			{17, 22}}, "", t)
	case goversion.VersionAfterOrEqual(runtime.Version(), 1, 11):
		testseq("teststepprog", contStep, []nextTest{
			{21, 14},
			{14, 15},
			{15, 22}}, "", t)
	default:
		panic("too old")
	}
}

func TestInlineStep(t *testing.T) {
	skipOn(t, "broken", "ppc64le")
	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
		// Versions of go before 1.10 do not have DWARF information for inlined calls
		t.Skip("inlining not supported")
	}
	testseq2Args(".", []string{}, protest.EnableInlining, t, "testinline", "", []seqTest{
		{contContinue, 18},
		{contStep, 6},
		{contStep, 7},
		{contStep, 24},
		{contStep, 25},
		{contStep, 7},
		{contStep, 18},
		{contStep, 19},
	})
}

func TestInlineNext(t *testing.T) {
	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
		// Versions of go before 1.10 do not have DWARF information for inlined calls
		t.Skip("inlining not supported")
	}
	testseq2Args(".", []string{}, protest.EnableInlining, t, "testinline", "", []seqTest{
		{contContinue, 18},
		{contStep, 6},
		{contNext, 7},
		{contNext, 18},
		{contNext, 19},
	})
}

func TestInlineStepOver(t *testing.T) {
	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
		// Versions of go before 1.10 do not have DWARF information for inlined calls
		t.Skip("inlining not supported")
	}
	testseq2Args(".", []string{}, protest.EnableInlining, t, "testinline", "", []seqTest{
		{contContinue, 18},
		{contNext, 19},
		{contNext, 20},
	})
}

func TestInlineStepOut(t *testing.T) {
	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
		// Versions of go before 1.10 do not have DWARF information for inlined calls
		t.Skip("inlining not supported")
	}
	testseq2Args(".", []string{}, protest.EnableInlining, t, "testinline", "", []seqTest{
		{contContinue, 18},
		{contStep, 6},
		{contStepout, 18},
	})
}

func TestBackwardNextGeneral(t *testing.T) {
	if testBackend != "rr" {
		t.Skip("Reverse stepping test needs rr")
	}
	testseq2(t, "testnextprog", "main.helloworld", []seqTest{
		{contContinue, 13},
		{contNext, 14},
		{contReverseNext, 13},
		{contReverseNext, 34},
		{contReverseNext, 28},
		{contReverseNext, 27},
		{contReverseNext, 26},
		{contReverseNext, 24},
		{contReverseNext, 23},
		{contReverseNext, 31},
		{contReverseNext, 26},
		{contReverseNext, 24},
		{contReverseNext, 23},
		{contReverseNext, 31},
		{contReverseNext, 26},
		{contReverseNext, 24},
		{contReverseNext, 23},
		{contReverseNext, 20},
		{contReverseNext, 19},
		{contReverseNext, 17},
		{contReverseNext, 39},
		{contReverseNext, 38},
		{contReverseNext, 37},
	})
}

func TestBackwardStepOutGeneral(t *testing.T) {
	if testBackend != "rr" {
		t.Skip("Reverse stepping test needs rr")
	}
	testseq2(t, "testnextprog", "main.helloworld", []seqTest{
		{contContinue, 13},
		{contNext, 14},
		{contReverseStepout, 34},
		{contReverseStepout, 39},
	})
}

func TestBackwardStepGeneral(t *testing.T) {
	if testBackend != "rr" {
		t.Skip("Reverse stepping test needs rr")
	}
	testseq2(t, "testnextprog", "main.helloworld", []seqTest{
		{contContinue, 13},
		{contNext, 14},
		{contReverseStep, 13},
		{contReverseStep, 34},
		{contReverseStep, 28},
		{contReverseNext, 27}, // skip fmt.Printf
		{contReverseStep, 26},
		{contReverseStep, 24},
		{contReverseStep, 23},
		{contReverseStep, 11},
		{contReverseNext, 10}, // skip time.Sleep
		{contReverseStep, 9},

		{contReverseStep, 31},
		{contReverseStep, 26},
		{contReverseStep, 24},
		{contReverseStep, 23},
		{contReverseStep, 11},
		{contReverseNext, 10}, // skip time.Sleep
		{contReverseStep, 9},

		{contReverseStep, 31},
		{contReverseStep, 26},
		{contReverseStep, 24},
		{contReverseStep, 23},
		{contReverseStep, 20},
		{contReverseStep, 19},
		{contReverseStep, 17},
		{contReverseStep, 39},
		{contReverseStep, 38},
		{contReverseStep, 37},
	})
}

func TestBackwardNextDeferPanic(t *testing.T) {
	if testBackend != "rr" {
		t.Skip("Reverse stepping test needs rr")
	}
	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) {
		testseq2(t, "defercall", "", []seqTest{
			{contContinue, 12},
			{contReverseNext, 11},
			{contReverseNext, 10},
			{contReverseNext, 9},
			{contReverseNext, 27},

			{contContinueToBreakpoint, 12}, // skip first call to sampleFunction
			{contContinueToBreakpoint, 6},  // go to call to sampleFunction through deferreturn
			{contReverseNext, -1},          // runtime.deferreturn, maybe we should try to skip this
			{contReverseStepout, 13},
			{contReverseNext, 12},
			{contReverseNext, 11},
			{contReverseNext, 10},
			{contReverseNext, 9},
			{contReverseNext, 27},

			{contContinueToBreakpoint, 18}, // go to panic call
			{contNext, 6},                  // panic so the deferred call happens
			{contReverseNext, 18},
			{contReverseNext, 17},
			{contReverseNext, 16},
			{contReverseNext, 15},
			{contReverseNext, 23},
			{contReverseNext, 22},
			{contReverseNext, 21},
			{contReverseNext, 28},
		})
	} else {
		testseq2(t, "defercall", "", []seqTest{
			{contContinue, 12},
			{contReverseNext, 11},
			{contReverseNext, 10},
			{contReverseNext, 9},
			{contReverseNext, 27},

			{contContinueToBreakpoint, 12}, // skip first call to sampleFunction
			{contContinueToBreakpoint, 6},  // go to call to sampleFunction through deferreturn
			{contReverseNext, 13},
			{contReverseNext, 12},
			{contReverseNext, 11},
			{contReverseNext, 10},
			{contReverseNext, 9},
			{contReverseNext, 27},

			{contContinueToBreakpoint, 18}, // go to panic call
			{contNext, 6},                  // panic so the deferred call happens
			{contReverseNext, 18},
			{contReverseNext, 17},
			{contReverseNext, 16},
			{contReverseNext, 15},
			{contReverseNext, 23},
			{contReverseNext, 22},
			{contReverseNext, 21},
			{contReverseNext, 28},
		})
	}
}

func TestStepIntoWrapperForEmbeddedPointer(t *testing.T) {
	skipOn(t, "N/A", "linux", "386", "pie") // skipping wrappers doesn't work on linux/386/PIE due to the use of get_pc_thunk
	// Under some circumstances (when using an interface to call a method on an
	// embedded field, see _fixtures/ifaceembcall.go) the compiler will
	// autogenerate a wrapper function that uses a tail call (i.e. it ends in
	// an unconditional jump instruction to a different function).
	// Delve should be able to step into this tail call.
	testseq2(t, "ifaceembcall", "", []seqTest{
		{contContinue, 28}, // main.main, the line calling iface.PtrReceiver()
		{contStep, 18},     // main.(*A).PtrReceiver
		{contStep, 19},
		{contStepout, 28},
		{contContinueToBreakpoint, 29}, // main.main, the line calling iface.NonPtrReceiver()
		{contStep, 22},                 // main.(A).NonPtrReceiver
		{contStep, 23},
		{contStepout, 29}})

	// same test but with next instead of stepout
	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) && runtime.GOARCH != "386" && !goversion.VersionAfterOrEqualRev(runtime.Version(), 1, 15, 4) {
		// Line numbers generated for versions 1.14 through 1.15.3 on any system except linux/386
		testseq2(t, "ifaceembcall", "", []seqTest{
			{contContinue, 28}, // main.main, the line calling iface.PtrReceiver()
			{contStep, 18},     // main.(*A).PtrReceiver
			{contNext, 19},
			{contNext, 19},
			{contNext, 28},
			{contContinueToBreakpoint, 29}, // main.main, the line calling iface.NonPtrReceiver()
			{contStep, 22},
			{contNext, 23},
			{contNext, 23},
			{contNext, 29},
		})
	} else {
		testseq2(t, "ifaceembcall", "", []seqTest{
			{contContinue, 28}, // main.main, the line calling iface.PtrReceiver()
			{contStep, 18},     // main.(*A).PtrReceiver
			{contNext, 19},
			{contNext, 28},
			{contContinueToBreakpoint, 29}, // main.main, the line calling iface.NonPtrReceiver()
			{contStep, 22},
			{contNext, 23},
			{contNext, 29},
		})
	}
}

func TestNextGenericMethodThroughInterface(t *testing.T) {
	// Tests that autogenerated wrappers for generic methods called through an
	// interface are skipped.

	varcheck := func(p *proc.Target) {
		yvar := evalVariable(p, t, "y")
		yval, _ := constant.Int64Val(yvar.Value)
		if yval != 2 {
			t.Errorf("expected 2 got %#v", yvar.Value)
		}
	}

	if runtime.GOOS == "linux" && runtime.GOARCH == "386" {
		testseq2(t, "genericintoiface", "main.callf", []seqTest{
			{contContinue, 17},
			{contStep, 18},
			{contStep, 10},
			{contNothing, varcheck},
			{contNext, 11},
			{contNext, 19},
		})
	} else {
		testseq2(t, "genericintoiface", "main.callf", []seqTest{
			{contContinue, 17},
			{contStep, 18},
			{contStep, 9},
			{contNext, 10},
			{contNothing, varcheck},
			{contNext, 11},
			{contNext, 19},
		})
	}
}

func TestRangeOverFuncNext(t *testing.T) {
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 23) {
		t.Skip("N/A")
	}

	var bp *proc.Breakpoint

	funcBreak := func(t *testing.T, fnname string) seqTest {
		return seqTest{
			contNothing,
			func(p *proc.Target) {
				bp = setFunctionBreakpoint(p, t, fnname)
			}}
	}

	clearBreak := func(t *testing.T) seqTest {
		return seqTest{
			contNothing,
			func(p *proc.Target) {
				assertNoError(p.ClearBreakpoint(bp.Addr), t, "ClearBreakpoint")
			}}
	}

	notAtEntryPoint := func(t *testing.T) seqTest {
		return seqTest{contNothing, func(p *proc.Target) {
			pc := currentPC(p, t)
			fn := p.BinInfo().PCToFunc(pc)
			if pc == fn.Entry {
				t.Fatalf("current PC is entry point")
			}
		}}
	}

	nx := func(n int) seqTest {
		return seqTest{contNext, n}
	}

	nop := seqTest{contNothing, func(*proc.Target) {}}

	ifcond := func(v bool, a seqTest, b seqTest) seqTest {
		if v {
			return a
		}
		return b
	}

	assertLocals := func(t *testing.T, varnames ...string) seqTest {
		return seqTest{
			contNothing,
			func(p *proc.Target) {
				scope, err := proc.GoroutineScope(p, p.CurrentThread())
				assertNoError(err, t, "GoroutineScope")
				vars, err := scope.Locals(0, "")
				assertNoError(err, t, "Locals")

				gotnames := make([]string, len(vars))
				for i := range vars {
					gotnames[i] = vars[i].Name
				}

				ok := true
				if len(vars) != len(varnames) {
					ok = false
				} else {
					for i := range vars {
						if vars[i].Name != varnames[i] {
							ok = false
							break
						}
					}
				}
				if !ok {
					t.Errorf("Wrong variable names, expected %q, got %q", varnames, gotnames)
				}
			},
		}
	}

	assertEval := func(t *testing.T, exprvals ...string) seqTest {
		return seqTest{
			contNothing,
			func(p *proc.Target) {
				scope, err := proc.GoroutineScope(p, p.CurrentThread())
				assertNoError(err, t, "GoroutineScope")
				for i := 0; i < len(exprvals); i += 2 {
					expr, tgt := exprvals[i], exprvals[i+1]
					v, err := scope.EvalExpression(expr, normalLoadConfig)
					if err != nil {
						t.Errorf("Could not evaluate %q: %v", expr, err)
					} else {
						out := api.ConvertVar(v).SinglelineString()
						if out != tgt {
							t.Errorf("Wrong value for %q, got %q expected %q", expr, out, tgt)
						}
					}
				}
			},
		}
	}

	assertFunc := func(t *testing.T, fname string) seqTest {
		return seqTest{
			contNothing,
			func(p *proc.Target) {
				pc := currentPC(p, t)
				fn := p.BinInfo().PCToFunc(pc)
				if fn.Name != fname {
					t.Errorf("Wrong function name, expected %s got %s", fname, fn.Name)
				}
			},
		}
	}

	withTestProcessArgs("rangeoverfunc", t, ".", []string{}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
		t.Run("TestTrickyIterAll1", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestTrickyIterAll"),
				{contContinue, 24}, // TestTrickyIterAll
				ifcond(runtime.GOARCH == "arm64" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 25), nop, nx(25)),
				nx(26),
				nx(27), // for _, x := range ...
				assertLocals(t, "trickItAll", "i"),
				assertEval(t, "i", "0"),
				nx(28), // i += x
				assertLocals(t, "trickItAll", "i", "x"),
				assertEval(t,
					"i", "0",
					"x", "30"),
				nx(29), // if i >= 36 {
				nx(32),
				nx(27), // for _, x := range ...
				notAtEntryPoint(t),
				nx(28), // i += x
				assertEval(t,
					"i", "30",
					"x", "7"),
				nx(29), // if i >= 36 {
				nx(30), // break
				nx(32),
				nx(34), // fmt.Println
			})
		})

		t.Run("TestTrickyIterAll2", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestTrickyIterAll2"),
				{contContinue, 37}, // TestTrickyIterAll2
				ifcond(runtime.GOARCH == "arm64" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 25), nop, nx(38)),
				nx(39),
				nx(40), // for _, x := range...
				nx(41),
				nx(42),
				nx(40),
				notAtEntryPoint(t),
				nx(41),
				nx(42),
				nx(42), // different function from the one above...
				nx(43),
			})
		})

		t.Run("TestBreak1", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestBreak1"),
				{contContinue, 46}, // TestBreak1
				nx(47),
				nx(48), // for _, x := range... (x == -1)
				nx(49), // if x == -4
				assertLocals(t, "result", "x"),
				assertEval(t,
					"result", "[]int len: 0, cap: 0, nil",
					"x", "-1"),

				nx(52), // for _, y := range... (y == 1)
				nx(53), // if y == 3
				assertLocals(t, "result", "x", "y"),
				assertEval(t,
					"result", "[]int len: 0, cap: 0, nil",
					"x", "-1",
					"y", "1"),
				nx(56), // result = append(result, y)
				nx(57),
				nx(52), // for _, y := range... (y == 2)
				notAtEntryPoint(t),
				nx(53), // if y == 3
				assertEval(t,
					"x", "-1",
					"y", "2"),
				nx(56), // result = append(result, y)
				nx(57),
				nx(52), // for _, y := range... (y == 3)
				nx(53), // if y == 3
				assertEval(t,
					"x", "-1",
					"y", "3"),
				nx(54), // break
				nx(57),
				nx(58), // result = append(result, x)
				nx(59),

				nx(48), // for _, x := range... (x == -2)
				notAtEntryPoint(t),
				nx(49), // if x == -4
				assertEval(t,
					"result", "[]int len: 3, cap: 4, [1,2,-1]",
					"x", "-2"),
				nx(52), // for _, y := range... (y == 1)
				nx(53), // if y == 3
				nx(56), // result = append(result, y)
				nx(57),
				nx(52), // for _, y := range... (y == 2)
				notAtEntryPoint(t),
				nx(53), // if y == 3
				nx(56), // result = append(result, y)
				nx(57),
				nx(52), // for _, y := range... (y == 3)
				nx(53), // if y == 3
				nx(54), // break
				nx(57),
				nx(58), // result = append(result, x)
				nx(59),

				nx(48), // for _, x := range... (x == -4)
				assertEval(t,
					"result", "[]int len: 6, cap: 8, [1,2,-1,1,2,-2]",
					"x", "-4"),
				nx(49), // if x == -4
				nx(50), // break
				nx(59),
				nx(60),
				nx(61),
			})
		})

		t.Run("TestBreak2", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestBreak2"),

				{contContinue, 63}, // TestBreak2
				nx(64),
				nx(65),

				nx(66), // for _, x := range (x == -1)
				nx(67), // for _, y := range (y == 1)
				nx(68), // if y == 3
				nx(71), // if x == -4
				nx(74), // result = append(result, y)
				nx(75),

				nx(67), // for _, y := range (y == 2)
				nx(68), // if y == 3
				nx(71), // if x == -4
				nx(74), // result = append(result, y)
				nx(75),

				nx(67), // for _, y := range (y == 3)
				nx(68), // if y == 3
				nx(69), // break
				nx(75),
				nx(76), // result = append(result, x)
				nx(77),

				nx(66), // for _, x := range (x == -2)
				nx(67), // for _, y := range (y == 1)
				nx(68), // if y == 3
				nx(71), // if x == -4
				nx(74), // result = append(result, y)
				nx(75),

				nx(67), // for _, y := range (y == 2)
				nx(68), // if y == 3
				nx(71), // if x == -4
				nx(74), // result = append(result, y)
				nx(75),

				nx(67), // for _, y := range (y == 3)
				nx(68), // if y == 3
				nx(69), // break
				nx(75),
				nx(76), // result = append(result, x)
				nx(77),

				nx(66), // for _, x := range (x == -4)
				nx(67), // for _, y := range (y == 1)
				nx(68), // if y == 3
				nx(71), // if x == -4
				nx(72), // break outer
				nx(75),
				nx(77),
				nx(78),
				nx(79),
			})
		})

		t.Run("TestMultiCont0", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestMultiCont0"),
				{contContinue, 81},
				nx(82),
				nx(84),
				nx(85), // for _, w := range (w == 1000)
				nx(86), // result = append(result, w)
				assertEval(t,
					"w", "1000",
					"result", "[]int len: 0, cap: 10, []"),
				nx(87), // if w == 2000
				assertLocals(t, "result", "w"),
				assertEval(t, "result", "[]int len: 1, cap: 10, [1000]"),
				nx(90), // for _, x := range (x == 100)
				nx(91), // for _, y := range (y == 10)
				nx(92), // result = append(result, y)
				assertLocals(t, "result", "w", "x", "y"),
				assertEval(t,
					"w", "1000",
					"x", "100",
					"y", "10"),

				nx(93), // for _, z := range (z == 1)
				nx(94), // if z&1 == 1
				assertLocals(t, "result", "w", "x", "y", "z"),
				assertEval(t,
					"w", "1000",
					"x", "100",
					"y", "10",
					"z", "1"),
				nx(95), // continue

				nx(93), // for _, z := range (z == 2)
				nx(94), // if z&1 == 1
				assertEval(t, "z", "2"),
				nx(97), // result = append(result, z)
				nx(98), // if z >= 4 {
				nx(101),

				nx(93), // for _, z := range (z == 3)
				nx(94), // if z&1 == 1
				assertEval(t, "z", "3"),
				nx(95), // continue

				nx(93), // for _, z := range (z == 4)
				nx(94), // if z&1 == 1
				assertEval(t, "z", "4"),
				nx(97), // result = append(result, z)
				assertEval(t, "result", "[]int len: 3, cap: 10, [1000,10,2]"),
				nx(98), // if z >= 4 {
				nx(99), // continue W
				nx(101),
				nx(103),
				nx(105),

				nx(85), // for _, w := range (w == 2000)
				nx(86), // result = append(result, w)
				nx(87), // if w == 2000
				assertEval(t,
					"w", "2000",
					"result", "[]int len: 5, cap: 10, [1000,10,2,4,2000]"),
				nx(88), // break
				nx(106),
				nx(107), // fmt.Println
			})
		})

		t.Run("TestPanickyIterator1", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestPanickyIterator1"),
				{contContinue, 110},
				nx(111),
				nx(112),
				nx(116), // for _, z := range (z == 1)
				nx(117), // result = append(result, z)
				nx(118), // if z == 4
				nx(121),

				nx(116), // for _, z := range (z == 2)
				nx(117), // result = append(result, z)
				nx(118), // if z == 4
				nx(121),

				nx(116), // for _, z := range (z == 3)
				nx(117), // result = append(result, z)
				nx(118), // if z == 4
				nx(121),

				nx(116), // for _, z := range (z == 4)
				nx(117), // result = append(result, z)
				nx(118), // if z == 4
				nx(119), // break

				nx(112), // defer func()
				nx(113), // r := recover()
				nx(114), // fmt.Println
			})
		})

		t.Run("TestPanickyIterator2", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestPanickyIterator2"),
				{contContinue, 125},
				nx(126),
				nx(127),
				nx(131), // for _, x := range (x == 100)
				nx(132),
				nx(133),
				nx(135), // for _, y := range (y == 10)
				nx(136), // result = append(result, y)
				nx(139), // for k, z := range (k == 0, z == 1)
				nx(140), // result = append(result, z)
				nx(141), // if k == 1
				nx(144),

				nx(139), // for k, z := range (k == 1, z == 2)
				nx(140), // result = append(result, z)
				nx(141), // if k == 1
				nx(142), // break Y
				nx(135),
				nx(145),
				nx(127), // defer func()
				nx(128), // r := recover()
				nx(129), // fmt.Println
			})
		})

		t.Run("TestPanickyIteratorWithNewDefer", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestPanickyIteratorWithNewDefer"),
				{contContinue, 149},
				nx(150),
				nx(151),
				nx(155), // for _, x := range (x == 100)
				nx(156),
				nx(157),
				nx(159), // for _, y := range (y == 10)
				nx(160),
				nx(163), // result = append(result, y)
				nx(166), // for k, z := range (k == 0, z == 1)
				nx(167), // result = append(result, z)
				nx(168), // if k == 1
				nx(171),

				nx(166), // for k, z := range (k == 0, z == 1)
				nx(167), // result = append(result, z)
				nx(168), // if k == 1
				nx(169), // break Y
				nx(159),
				nx(172),
				nx(160), // defer func()
				nx(161), // fmt.Println
			})
		})

		t.Run("TestLongReturn", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestLongReturn"),
				{contContinue, 181},
				nx(182), // for _, x := range (x == 1)
				nx(183), // for _, y := range (y == 10)
				nx(184), // if y == 10
				nx(185), // return
				nx(187),
				nx(189),
				nx(178), // into TestLongReturnWrapper, fmt.Println
			})
		})

		if goversion.VersionAfterOrEqual(runtime.Version(), 1, 26) && runtime.GOARCH == "arm64" {
			t.Run("TestGotoA1", func(t *testing.T) {
				testseq2intl(t, fixture, grp, p, nil, []seqTest{
					funcBreak(t, "main.TestGotoA1"),
					{contContinue, 192},
					nx(194), // for _, x := range (x == -1)
					nx(195), // result = append(result, x)
					nx(196), // if x == -4
					nx(199), // for _, y := range (y == 1)
					nx(200), // if y == 3
					nx(203), // result = append(result, y)
					nx(204),

					nx(199), // for _, y := range (y == 2)
					nx(200), // if y == 3
					nx(203), // result = append(result, y)
					nx(204),

					nx(199), // for _, y := range (y == 3)
					nx(200), // if y == 3
					nx(201), // goto A
					nx(204),
					nx(206), // result = append(result, x)
					nx(207),

					nx(194), // for _, x := range (x == -4)
					nx(195), // result = append(result, x)
					nx(196), // if x == -4
					nx(197), // break
					nx(207),
					nx(208), // fmt.Println
				})
			})
		} else {
			t.Run("TestGotoA1", func(t *testing.T) {
				testseq2intl(t, fixture, grp, p, nil, []seqTest{
					funcBreak(t, "main.TestGotoA1"),
					{contContinue, 192},
					nx(193),
					nx(194), // for _, x := range (x == -1)
					nx(195), // result = append(result, x)
					nx(196), // if x == -4
					nx(199), // for _, y := range (y == 1)
					nx(200), // if y == 3
					nx(203), // result = append(result, y)
					nx(204),

					nx(199), // for _, y := range (y == 2)
					nx(200), // if y == 3
					nx(203), // result = append(result, y)
					nx(204),

					nx(199), // for _, y := range (y == 3)
					nx(200), // if y == 3
					nx(201), // goto A
					nx(204),
					nx(206), // result = append(result, x)
					nx(207),

					nx(194), // for _, x := range (x == -4)
					nx(195), // result = append(result, x)
					nx(196), // if x == -4
					nx(197), // break
					nx(207),
					nx(208), // fmt.Println
				})
			})
		}

		t.Run("TestGotoB1", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestGotoB1"),
				{contContinue, 211},
				nx(212),
				nx(213), // for _, x := range (x == -1)
				nx(214), // result = append(result, x)
				nx(215), // if x == -4
				nx(218), // for _, y := range (y == 1)
				nx(219), // if y == 3
				nx(222), // result = append(result, y)
				nx(223),

				nx(218), // for _, y := range (y == 2)
				nx(219), // if y == 3
				nx(222), // result = append(result, y)
				nx(223),

				nx(218), // for _, y := range (y == 3)
				nx(219), // if y == 3
				nx(220), // goto B
				nx(223),
				nx(225),
				nx(227), // result = append(result, 999)
				nx(228), // fmt.Println
			})
		})

		t.Run("TestRecur", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestRecur"),
				{contContinue, 231},
				clearBreak(t),
				nx(232), // result := []int{}
				assertEval(t, "n", "3"),
				nx(233), // if n > 0 {
				nx(234), // TestRecur

				nx(236), // for _, x := range (x == 10)
				assertFunc(t, "main.TestRecur"),
				nx(237), // result = ...
				assertEval(t, "n", "3"),
				assertFunc(t, "main.TestRecur-range1"),
				assertEval(t, "x", "10", "n", "3"),
				nx(238), // if n == 3
				nx(239), // TestRecur(0)
				nx(241),

				nx(236), // for _, x := range (x == 20)
				nx(237), // result = ...
				assertEval(t, "x", "20", "n", "3"),
			})
		})
	})
}

func TestRangeOverFuncStepOut(t *testing.T) {
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 23) {
		t.Skip("N/A")
	}

	testseq2(t, "rangeoverfunc", "", []seqTest{
		{contContinue, 97},
		{contStepout, 251},
	})
}

func TestRangeOverFuncNextInlined(t *testing.T) {
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 23) {
		t.Skip("N/A")
	}
	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 24) && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 27) {
		t.Skip("broken due to inlined symbol names")
	}

	var bp *proc.Breakpoint

	funcBreak := func(t *testing.T, fnname string) seqTest {
		return seqTest{
			contNothing,
			func(p *proc.Target) {
				bp = setFunctionBreakpoint(p, t, fnname)
			}}
	}

	clearBreak := func(t *testing.T) seqTest {
		return seqTest{
			contNothing,
			func(p *proc.Target) {
				assertNoError(p.ClearBreakpoint(bp.Addr), t, "ClearBreakpoint")
			}}
	}

	nx := func(n int) seqTest {
		return seqTest{contNext, n}
	}

	assertLocals := func(t *testing.T, varnames ...string) seqTest {
		return seqTest{
			contNothing,
			func(p *proc.Target) {
				scope, err := proc.GoroutineScope(p, p.CurrentThread())
				assertNoError(err, t, "GoroutineScope")
				vars, err := scope.Locals(0, "")
				assertNoError(err, t, "Locals")

				gotnames := make([]string, len(vars))
				for i := range vars {
					gotnames[i] = vars[i].Name
				}

				ok := true
				if len(vars) != len(varnames) {
					ok = false
				} else {
					for i := range vars {
						if vars[i].Name != varnames[i] {
							ok = false
							break
						}
					}
				}
				if !ok {
					t.Errorf("Wrong variable names, expected %q, got %q", varnames, gotnames)
				}
			},
		}
	}

	assertEval := func(t *testing.T, exprvals ...string) seqTest {
		return seqTest{
			contNothing,
			func(p *proc.Target) {
				scope, err := proc.GoroutineScope(p, p.CurrentThread())
				assertNoError(err, t, "GoroutineScope")
				for i := 0; i < len(exprvals); i += 2 {
					expr, tgt := exprvals[i], exprvals[i+1]
					v, err := scope.EvalExpression(expr, normalLoadConfig)
					if err != nil {
						t.Errorf("Could not evaluate %q: %v", expr, err)
					} else {
						out := api.ConvertVar(v).SinglelineString()
						if out != tgt {
							t.Errorf("Wrong value for %q, got %q expected %q", expr, out, tgt)
						}
					}
				}
			},
		}
	}

	assertFunc := func(t *testing.T, fname string) seqTest {
		return seqTest{
			contNothing,
			func(p *proc.Target) {
				pc := currentPC(p, t)
				fn := p.BinInfo().PCToFunc(pc)
				if fn.Name != fname {
					t.Errorf("Wrong function name, expected %s got %s", fname, fn.Name)
				}
			},
		}
	}

	withTestProcessArgs("rangeoverfunc", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
		t.Run("TestTrickyIterAll1", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestTrickyIterAll"),
				{contContinue, 24}, // TestTrickyIterAll
				nx(25),
				nx(26),
				nx(27), // for _, x := range ...
				assertLocals(t, "trickItAll", "i"),
				assertEval(t, "i", "0"),
				nx(28), // i += x
				assertLocals(t, "trickItAll", "i", "x"),
				assertEval(t,
					"i", "0",
					"x", "30"),
				nx(29), // if i >= 36 {
				nx(32),
				nx(27), // for _, x := range ...
				nx(28), // i += x
				assertEval(t,
					"i", "30",
					"x", "7"),
				nx(29), // if i >= 36 {
				nx(30), // break
				nx(27),
				nx(32),
				nx(34), // fmt.Println
			})
		})

		t.Run("TestTrickyIterAll2", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestTrickyIterAll2"),
				{contContinue, 37}, // TestTrickyIterAll2
				nx(38),
				nx(39),
				nx(40), // for _, x := range...
				nx(41),
				nx(42),
				nx(40),
				nx(41),
				nx(42),
				nx(40),
				nx(42),
				nx(43),
			})
		})

		t.Run("TestBreak1", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestBreak1"),
				{contContinue, 46}, // TestBreak1
				nx(47),
				nx(48), // for _, x := range... (x == -1)
				nx(49), // if x == -4
				assertLocals(t, "result", "x"),
				assertEval(t,
					"result", "[]int len: 0, cap: 0, nil",
					"x", "-1"),

				nx(52), // for _, y := range... (y == 1)
				nx(53), // if y == 3
				assertLocals(t, "result", "x", "y"),
				assertEval(t,
					"result", "[]int len: 0, cap: 0, nil",
					"x", "-1",
					"y", "1"),
				nx(56), // result = append(result, y)
				nx(57),
				nx(52), // for _, y := range... (y == 2)
				nx(53), // if y == 3
				assertEval(t,
					"x", "-1",
					"y", "2"),
				nx(56), // result = append(result, y)
				nx(57),
				nx(52), // for _, y := range... (y == 3)
				nx(53), // if y == 3
				assertEval(t,
					"x", "-1",
					"y", "3"),
				nx(54), // break
				nx(52),
				nx(57),
				nx(58), // result = append(result, x)
				nx(59),

				nx(48), // for _, x := range... (x == -2)
				nx(49), // if x == -4
				assertEval(t,
					"result", "[]int len: 3, cap: 4, [1,2,-1]",
					"x", "-2"),
				nx(52), // for _, y := range... (y == 1)
				nx(53), // if y == 3
				nx(56), // result = append(result, y)
				nx(57),
				nx(52), // for _, y := range... (y == 2)
				nx(53), // if y == 3
				nx(56), // result = append(result, y)
				nx(57),
				nx(52), // for _, y := range... (y == 3)
				nx(53), // if y == 3
				nx(54), // break
				nx(52),
				nx(57),
				nx(58), // result = append(result, x)
				nx(59),

				nx(48), // for _, x := range... (x == -4)
				assertEval(t,
					"result", "[]int len: 6, cap: 8, [1,2,-1,1,2,-2]",
					"x", "-4"),
				nx(49), // if x == -4
				nx(50), // break
				nx(48),
				nx(59),
				nx(60),
				nx(61),
			})
		})

		t.Run("TestBreak2", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestBreak2"),

				{contContinue, 63}, // TestBreak2
				nx(64),
				nx(65),

				nx(66), // for _, x := range (x == -1)
				nx(67), // for _, y := range (y == 1)
				nx(68), // if y == 3
				nx(71), // if x == -4
				nx(74), // result = append(result, y)
				nx(75),

				nx(67), // for _, y := range (y == 2)
				nx(68), // if y == 3
				nx(71), // if x == -4
				nx(74), // result = append(result, y)
				nx(75),

				nx(67), // for _, y := range (y == 3)
				nx(68), // if y == 3
				nx(69), // break
				nx(67),
				nx(75),
				nx(76), // result = append(result, x)
				nx(77),

				nx(66), // for _, x := range (x == -2)
				nx(67), // for _, y := range (y == 1)
				nx(68), // if y == 3
				nx(71), // if x == -4
				nx(74), // result = append(result, y)
				nx(75),

				nx(67), // for _, y := range (y == 2)
				nx(68), // if y == 3
				nx(71), // if x == -4
				nx(74), // result = append(result, y)
				nx(75),

				nx(67), // for _, y := range (y == 3)
				nx(68), // if y == 3
				nx(69), // break
				nx(67),
				nx(75),
				nx(76), // result = append(result, x)
				nx(77),

				nx(66), // for _, x := range (x == -4)
				nx(67), // for _, y := range (y == 1)
				nx(68), // if y == 3
				nx(71), // if x == -4
				nx(72), // break outer
				nx(67),
				nx(75),
				nx(66),
				nx(77),
				nx(78),
				nx(79),
			})
		})

		t.Run("TestMultiCont0", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestMultiCont0"),
				{contContinue, 81},
				nx(82),
				nx(84),
				nx(85), // for _, w := range (w == 1000)
				nx(86), // result = append(result, w)
				assertEval(t,
					"w", "1000",
					"result", "[]int len: 0, cap: 10, []"),
				nx(87), // if w == 2000
				assertLocals(t, "result", "w"),
				assertEval(t, "result", "[]int len: 1, cap: 10, [1000]"),
				nx(90), // for _, x := range (x == 100)
				nx(91), // for _, y := range (y == 10)
				nx(92), // result = append(result, y)
				assertLocals(t, "result", "w", "x", "y"),
				assertEval(t,
					"w", "1000",
					"x", "100",
					"y", "10"),

				nx(93), // for _, z := range (z == 1)
				nx(94), // if z&1 == 1
				assertLocals(t, "result", "w", "x", "y", "z"),
				assertEval(t,
					"w", "1000",
					"x", "100",
					"y", "10",
					"z", "1"),
				nx(95), // continue

				nx(93), // for _, z := range (z == 2)
				nx(94), // if z&1 == 1
				assertEval(t, "z", "2"),
				nx(97), // result = append(result, z)
				nx(98), // if z >= 4 {
				nx(101),

				nx(93), // for _, z := range (z == 3)
				nx(94), // if z&1 == 1
				assertEval(t, "z", "3"),
				nx(95), // continue

				nx(93), // for _, z := range (z == 4)
				nx(94), // if z&1 == 1
				assertEval(t, "z", "4"),
				nx(97), // result = append(result, z)
				assertEval(t, "result", "[]int len: 3, cap: 10, [1000,10,2]"),
				nx(98), // if z >= 4 {
				nx(99), // continue W
				nx(93),
				nx(101),
				nx(91),
				nx(103),
				nx(90),
				nx(105),

				nx(85), // for _, w := range (w == 2000)
				nx(86), // result = append(result, w)
				nx(87), // if w == 2000
				assertEval(t,
					"w", "2000",
					"result", "[]int len: 5, cap: 10, [1000,10,2,4,2000]"),
				nx(88), // break
				nx(85),
				nx(106),
				nx(107), // fmt.Println
			})
		})

		t.Run("TestPanickyIterator1", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestPanickyIterator1"),
				{contContinue, 110},
				nx(111),
				nx(112),
				nx(116), // for _, z := range (z == 1)
				nx(117), // result = append(result, z)
				nx(118), // if z == 4
				nx(121),

				nx(116), // for _, z := range (z == 2)
				nx(117), // result = append(result, z)
				nx(118), // if z == 4
				nx(121),

				nx(116), // for _, z := range (z == 3)
				nx(117), // result = append(result, z)
				nx(118), // if z == 4
				nx(121),

				nx(116), // for _, z := range (z == 4)
				nx(117), // result = append(result, z)
				nx(118), // if z == 4
				nx(119), // break

				nx(112), // defer func()
				nx(113), // r := recover()
				nx(114), // fmt.Println
			})
		})

		t.Run("TestPanickyIterator2", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestPanickyIterator2"),
				{contContinue, 125},
				nx(126),
				nx(127),
				nx(131), // for _, x := range (x == 100)
				nx(132),
				nx(133),
				nx(135), // for _, y := range (y == 10)
				nx(136), // result = append(result, y)
				nx(139), // for k, z := range (k == 0, z == 1)
				nx(140), // result = append(result, z)
				nx(141), // if k == 1
				nx(144),

				nx(139), // for k, z := range (k == 1, z == 2)
				nx(140), // result = append(result, z)
				nx(141), // if k == 1
				nx(142), // break Y
				nx(135),
				nx(145),
				nx(127), // defer func()
				nx(128), // r := recover()
				nx(129), // fmt.Println
			})
		})

		t.Run("TestPanickyIteratorWithNewDefer", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestPanickyIteratorWithNewDefer"),
				{contContinue, 149},
				nx(150),
				nx(151),
				nx(155), // for _, x := range (x == 100)
				nx(156),
				nx(157),
				nx(159), // for _, y := range (y == 10)
				nx(160),
				nx(163), // result = append(result, y)
				nx(166), // for k, z := range (k == 0, z == 1)
				nx(167), // result = append(result, z)
				nx(168), // if k == 1
				nx(171),

				nx(166), // for k, z := range (k == 0, z == 1)
				nx(167), // result = append(result, z)
				nx(168), // if k == 1
				nx(169), // break Y
				nx(159),
				nx(172),
				nx(160), // defer func()
				nx(161), // fmt.Println
			})
		})

		t.Run("TestLongReturn", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestLongReturn"),
				{contContinue, 181},
				nx(182), // for _, x := range (x == 1)
				nx(183), // for _, y := range (y == 10)
				nx(184), // if y == 10
				nx(185), // return
				nx(183),
				nx(187),
				nx(182),
				nx(189),
				nx(178), // into TestLongReturnWrapper, fmt.Println
			})
		})

		t.Run("TestGotoA1", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestGotoA1"),
				{contContinue, 192},
				nx(193),
				nx(194), // for _, x := range (x == -1)
				nx(195), // result = append(result, x)
				nx(196), // if x == -4
				nx(199), // for _, y := range (y == 1)
				nx(200), // if y == 3
				nx(203), // result = append(result, y)
				nx(204),

				nx(199), // for _, y := range (y == 2)
				nx(200), // if y == 3
				nx(203), // result = append(result, y)
				nx(204),

				nx(199), // for _, y := range (y == 3)
				nx(200), // if y == 3
				nx(201), // goto A
				nx(199),
				nx(204),
				nx(206), // result = append(result, x)
				nx(207),

				nx(194), // for _, x := range (x == -4)
				nx(195), // result = append(result, x)
				nx(196), // if x == -4
				nx(197), // break
				nx(194),
				nx(207),
				nx(208), // fmt.Println
			})
		})

		t.Run("TestGotoB1", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestGotoB1"),
				{contContinue, 211},
				nx(212),
				nx(213), // for _, x := range (x == -1)
				nx(214), // result = append(result, x)
				nx(215), // if x == -4
				nx(218), // for _, y := range (y == 1)
				nx(219), // if y == 3
				nx(222), // result = append(result, y)
				nx(223),

				nx(218), // for _, y := range (y == 2)
				nx(219), // if y == 3
				nx(222), // result = append(result, y)
				nx(223),

				nx(218), // for _, y := range (y == 3)
				nx(219), // if y == 3
				nx(220), // goto B
				nx(218),
				nx(223),
				nx(213),
				nx(225),
				nx(227), // result = append(result, 999)
				nx(228), // fmt.Println
			})
		})

		t.Run("TestRecur", func(t *testing.T) {
			testseq2intl(t, fixture, grp, p, nil, []seqTest{
				funcBreak(t, "main.TestRecur"),
				{contContinue, 231},
				clearBreak(t),
				nx(232), // result := []int{}
				assertEval(t, "n", "3"),
				nx(233), // if n > 0 {
				nx(234), // TestRecur

				nx(236), // for _, x := range (x == 10)
				assertFunc(t, "main.TestRecur"),
				assertEval(t, "n", "3"),
				nx(237), // result = ...
				assertFunc(t, "main.TestRecur-range1"),
				assertEval(t, "x", "10", "n", "3"),
				nx(238), // if n == 3
				nx(239), // TestRecur(0)
				nx(241),

				nx(236), // for _, x := range (x == 20)
				nx(237), // result = ...
				assertEval(t, "x", "20", "n", "3"),
			})
		})
	})
}

func TestStepIntoCoroutine(t *testing.T) {
	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 23) {
		t.Skip("N/A")
	}
	skipOn(t, "not working due to optimizations", "386")
	withTestProcessArgs("backwardsiter", t, ".", []string{}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
		testseq2intl(t, fixture, grp, p, nil, []seqTest{
			{contContinueToBreakpoint, 20}, // fmt.Println(next()) -- first call
			{contStep, 9},                  // func(yield)
			{contNext, 10},                 // for...
			{contNext, 11},                 // if !yield
			{contStep, 20},                 // fmt.Println(next()) -- first call (returning from next)
			{contNext, 21},                 // fmt.Println(next()) -- second call
			{contStep, 11},                 // if !yield
			{contNext, 10},                 // for...
			{contNext, 11},                 // if !yield
			{contStep, 21},                 // fmt.Println(next()) -- second call (returning from next)
			{contNext, 22},                 // fmt.Println(next()) -- third call
			{contNext, 23},                 // stop()
			{contStep, 11},                 // if !yield
		})
	})
}
