Repository: go-delve/delve
Pull request #2372 (2021-03-05 18:57:25)

gobuild: wait longer when removing files on Windows
1usec is very much not enough



Patch:
diff --git a/.deepsource.toml b/.deepsource.toml
index 6889372d..5f2b68ef 100644
--- a/.deepsource.toml
+++ b/.deepsource.toml
@@ -2,7 +2,7 @@ version = 1
 
 test_patterns = ["**/*_test.go"]
 
-exclude_patterns = ["vendor", "_fixtures", "_scripts"]
+exclude_patterns = ["vendor/**", "_fixtures/**", "_scripts/**"]
 
 [[analyzers]]
 name = "go"
diff --git a/pkg/gobuild/gobuild.go b/pkg/gobuild/gobuild.go
index 2465ca54..5cbea983 100644
--- a/pkg/gobuild/gobuild.go
+++ b/pkg/gobuild/gobuild.go
@@ -16,12 +16,15 @@ import (
 // Remove the file at path and issue a warning to stderr if this fails.
 // This can be used to remove the temporary binary generated for the session.
 func Remove(path string) {
-	err := os.Remove(path)
-	// Open files can be removed on Unix, but not on Windows, where there also appears
-	// to be a delay in releasing the binary when the process exits. So we try again.
-	if err != nil && runtime.GOOS == "windows" {
-		time.Sleep(100 * time.Microsecond)
+	var err error
+	for i := 0; i < 20; i++ {
 		err = os.Remove(path)
+		// Open files can be removed on Unix, but not on Windows, where there also appears
+		// to be a delay in releasing the binary when the process exits. So we try again.
+		if err == nil || runtime.GOOS != "windows" {
+			break
+		}
+		time.Sleep(1 * time.Millisecond)
 	}
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "could not remove %v: %v\n", path, err)
diff --git a/service/dap/server.go b/service/dap/server.go
index 1f0eade0..798ea15a 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -549,7 +549,7 @@ func isValidLaunchMode(launchMode interface{}) bool {
 func (s *Server) onDisconnectRequest(request *dap.DisconnectRequest) {
 	s.send(&dap.DisconnectResponse{Response: *newResponse(request.Request)})
 	if s.debugger != nil {
-		_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt})
+		_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
 		if err != nil {
 			s.log.Error(err)
 		}
@@ -1150,11 +1150,21 @@ func (s *Server) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr s
 		} else if len(v.Children) == 0 || v.Children[0].Kind == reflect.Invalid && v.Children[0].Addr == 0 {
 			value = "nil <" + typeName + ">"
 		} else {
+			value = "<" + typeName + "(" + v.Children[0].TypeString() + ")" + ">"
 			if v.Children[0].OnlyAddr { // Not fully loaded
-				value = "<" + typeName + "(" + v.Children[0].TypeString() + ")" + ">" + " (data not loaded)"
-				// Since child is not fully loaded, don't provide a reference to view it.
-			} else {
-				value = "<" + typeName + "(" + v.Children[0].TypeString() + ")" + ">"
+				loadExpr := fmt.Sprintf("*(*%q)(%#x)", typeName, v.Addr)
+				// We might be loading variables from the frame that's not topmost, so use
+				// frame-independent address-based expression.
+				s.log.Debugf("loading %s (type %s) with %s", qualifiedNameOrExpr, typeName, loadExpr)
+				vLoaded, err := s.debugger.EvalVariableInScope(-1, 0, 0, loadExpr, DefaultLoadConfig)
+				if err != nil {
+					value += fmt.Sprintf(" - FAILED TO LOAD: %s", err)
+				} else {
+					v.Children = vLoaded.Children
+				}
+			}
+			// Provide a reference to the child only if it fully loaded
+			if !v.Children[0].OnlyAddr {
 				// TODO(polina): should we remove one level of indirection and skip "data"?
 				// Then we will have:
 				//   Before:
@@ -1255,7 +1265,7 @@ func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) {
 			Expr:                 strings.Replace(request.Arguments.Expression, "call ", "", 1),
 			UnsafeCall:           false,
 			GoroutineID:          goid,
-		})
+		}, nil)
 		if _, isexited := err.(proc.ErrProcessExited); isexited || err == nil && state.Exited {
 			e := &dap.TerminatedEvent{Event: *newEvent("terminated")}
 			s.send(e)
@@ -1477,7 +1487,7 @@ func (s *Server) doCommand(command string) {
 		return
 	}
 
-	state, err := s.debugger.Command(&api.DebuggerCommand{Name: command})
+	state, err := s.debugger.Command(&api.DebuggerCommand{Name: command}, nil)
 	if _, isexited := err.(proc.ErrProcessExited); isexited || err == nil && state.Exited {
 		e := &dap.TerminatedEvent{Event: *newEvent("terminated")}
 		s.send(e)
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index 7c336311..e5ca6db5 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -895,7 +895,7 @@ func (d *Debugger) isRunning() bool {
 }
 
 // Command handles commands which control the debugger lifecycle
-func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, error) {
+func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struct{}) (*api.DebuggerState, error) {
 	var err error
 
 	if command.Name == api.Halt {
@@ -918,6 +918,12 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
 	d.setRunning(true)
 	defer d.setRunning(false)
 
+	if command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread && command.Name != api.Halt {
+		d.target.ResumeNotify(resumeNotify)
+	} else if resumeNotify != nil {
+		close(resumeNotify)
+	}
+
 	switch command.Name {
 	case api.Continue:
 		d.log.Debug("continuing")
diff --git a/service/rpc1/server.go b/service/rpc1/server.go
index c2ec5a78..da3e3c8d 100644
--- a/service/rpc1/server.go
+++ b/service/rpc1/server.go
@@ -60,7 +60,7 @@ func (s *RPCServer) State(arg interface{}, state *api.DebuggerState) error {
 }
 
 func (s *RPCServer) Command(command *api.DebuggerCommand, cb service.RPCCallback) {
-	st, err := s.debugger.Command(command)
+	st, err := s.debugger.Command(command, cb.SetupDoneChan())
 	cb.Return(st, err)
 }
 
diff --git a/service/rpc2/server.go b/service/rpc2/server.go
index a27eae0c..65a323d8 100644
--- a/service/rpc2/server.go
+++ b/service/rpc2/server.go
@@ -92,6 +92,7 @@ type RestartOut struct {
 
 // Restart restarts program.
 func (s *RPCServer) Restart(arg RestartIn, cb service.RPCCallback) {
+	close(cb.SetupDoneChan())
 	if s.config.Debugger.AttachPid != 0 {
 		cb.Return(nil, errors.New("cannot restart process Delve did not create"))
 		return
@@ -113,6 +114,7 @@ type StateOut struct {
 
 // State returns the current debugger state.
 func (s *RPCServer) State(arg StateIn, cb service.RPCCallback) {
+	close(cb.SetupDoneChan())
 	var out StateOut
 	st, err := s.debugger.State(arg.NonBlocking)
 	if err != nil {
@@ -129,7 +131,7 @@ type CommandOut struct {
 
 // Command interrupts, continues and steps through the program.
 func (s *RPCServer) Command(command api.DebuggerCommand, cb service.RPCCallback) {
-	st, err := s.debugger.Command(&command)
+	st, err := s.debugger.Command(&command, cb.SetupDoneChan())
 	if err != nil {
 		cb.Return(nil, err)
 		return
@@ -856,6 +858,7 @@ type StopRecordingOut struct {
 }
 
 func (s *RPCServer) StopRecording(arg StopRecordingIn, cb service.RPCCallback) {
+	close(cb.SetupDoneChan())
 	var out StopRecordingOut
 	err := s.debugger.StopRecording()
 	if err != nil {
diff --git a/service/rpccallback.go b/service/rpccallback.go
index 9b047826..224dd6c9 100644
--- a/service/rpccallback.go
+++ b/service/rpccallback.go
@@ -3,4 +3,9 @@ package service
 // RPCCallback is used by RPC methods to return their result asynchronously.
 type RPCCallback interface {
 	Return(out interface{}, err error)
+
+	// SetupDone returns a channel that should be closed to signal that the
+	// asynchornous method has completed setup and the server is ready to
+	// receive other requests.
+	SetupDoneChan() chan struct{}
 }
diff --git a/service/rpccommon/server.go b/service/rpccommon/server.go
index 887540c4..d3eb47dc 100644
--- a/service/rpccommon/server.go
+++ b/service/rpccommon/server.go
@@ -46,12 +46,15 @@ type ServerImpl struct {
 }
 
 type RPCCallback struct {
-	s       *ServerImpl
-	sending *sync.Mutex
-	codec   rpc.ServerCodec
-	req     rpc.Request
+	s         *ServerImpl
+	sending   *sync.Mutex
+	codec     rpc.ServerCodec
+	req       rpc.Request
+	setupDone chan struct{}
 }
 
+var _ service.RPCCallback = &RPCCallback{}
+
 // RPCServer implements the RPC method calls common to all versions of the API.
 type RPCServer struct {
 	s *ServerImpl
@@ -163,8 +166,8 @@ var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
 
 // Is this an exported - upper case - name?
 func isExported(name string) bool {
-	rune, _ := utf8.DecodeRuneInString(name)
-	return unicode.IsUpper(rune)
+	ch, _ := utf8.DecodeRuneInString(name)
+	return unicode.IsUpper(ch)
 }
 
 // Is this type exported or a builtin?
@@ -236,12 +239,10 @@ func suitableMethods(rcvr interface{}, methods map[string]*methodType, log *logr
 				log.Warn("method", mname, "returns", returnType.String(), "not error")
 				continue
 			}
-		} else {
+		} else if mtype.NumOut() != 0 {
 			// Method needs zero outs.
-			if mtype.NumOut() != 0 {
-				log.Warn("method", mname, "has wrong number of outs:", mtype.NumOut())
-				continue
-			}
+			log.Warn("method", mname, "has wrong number of outs:", mtype.NumOut())
+			continue
 		}
 		methods[sname+"."+mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType, Synchronous: synchronous, Rcvr: rcvrv}
 	}
@@ -328,7 +329,7 @@ func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
 				s.log.Debugf("(async %d) <- %s(%T%s)", req.Seq, req.ServiceMethod, argv.Interface(), argvbytes)
 			}
 			function := mtype.method.Func
-			ctl := &RPCCallback{s, sending, codec, req}
+			ctl := &RPCCallback{s, sending, codec, req, make(chan struct{})}
 			go func() {
 				defer func() {
 					if ierr := recover(); ierr != nil {
@@ -337,6 +338,7 @@ func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
 				}()
 				function.Call([]reflect.Value{mtype.Rcvr, argv, reflect.ValueOf(ctl)})
 			}()
+			<-ctl.setupDone
 		}
 	}
 	codec.Close()
@@ -363,6 +365,12 @@ func (s *ServerImpl) sendResponse(sending *sync.Mutex, req *rpc.Request, resp *r
 }
 
 func (cb *RPCCallback) Return(out interface{}, err error) {
+	select {
+	case <-cb.setupDone:
+		// already closed
+	default:
+		close(cb.setupDone)
+	}
 	errmsg := ""
 	if err != nil {
 		errmsg = err.Error()
@@ -375,6 +383,10 @@ func (cb *RPCCallback) Return(out interface{}, err error) {
 	cb.s.sendResponse(cb.sending, &cb.req, &resp, out, cb.codec, errmsg)
 }
 
+func (cb *RPCCallback) SetupDoneChan() chan struct{} {
+	return cb.setupDone
+}
+
 // GetVersion returns the version of delve as well as the API version
 // currently served.
 func (s *RPCServer) GetVersion(args api.GetVersionIn, out *api.GetVersionOut) error {


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index 1e94da3b..00000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Delve CI
-
-on: [push, pull_request]
-
-jobs:
-  build:
-    runs-on: ${{matrix.os}}
-    strategy:
-      matrix:
-        include:
-          - go: 1.15
-            os: macos-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: actions/setup-go@v1
-        with:
-          go-version: ${{matrix.go}}
-      - run: go run _scripts/make.go test
diff --git a/Documentation/backend_test_health.md b/Documentation/backend_test_health.md
index 400a9588..279ac095 100644
--- a/Documentation/backend_test_health.md
+++ b/Documentation/backend_test_health.md
@@ -7,6 +7,8 @@ Tests skipped by each supported backend:
 	* 2 broken
 	* 1 broken - cgo stacktraces
 	* 1 broken - global variable symbolication
+* darwin/arm64 skipped = 0.68% (1/148)
+	* 1 broken - cgo stacktraces
 * darwin/lldb skipped = 0.68% (1/148)
 	* 1 upstream issue
 * freebsd skipped = 8.1% (12/148)
diff --git a/_fixtures/testvariables.go b/_fixtures/testvariables.go
index d957d29e..41bbf429 100644
--- a/_fixtures/testvariables.go
+++ b/_fixtures/testvariables.go
@@ -56,14 +56,15 @@ func foobar(baz string, bar FooBar) {
 		c128 = complex(float64(2), float64(3))
 		i32  = [2]int32{1, 2}
 		f    = barfoo
-		ms   = Nest{0, &Nest{1, &Nest{2, &Nest{3, &Nest{4, nil}}}}} // Test recursion capping
+		ms   = Nest{0, &Nest{1, &Nest{2, &Nest{3, &Nest{4, nil}}}}} // Test recursion capping on structs
+		ni   = []interface{}([]interface{}{[]interface{}{123}})     // Test recursion capping on interfaces
 		ba   = make([]int, 200, 200)                                // Test array size capping
 		mp   = map[int]interface{}{1: 42, 2: 43}
 	)
 
 	runtime.Breakpoint()
 	barfoo()
-	fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, b1, b2, baz, neg, i8, u8, u16, u32, u64, up, f32, c64, c128, i32, bar, f, ms, ba, p1, mp)
+	fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, b1, b2, baz, neg, i8, u8, u16, u32, u64, up, f32, c64, c128, i32, bar, f, ms, ni, ba, p1, mp)
 }
 
 var p1 = 10
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index 7d3593b2..02fada29 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -837,6 +837,8 @@ func TestCGONext(t *testing.T) {
 	}
 	protest.MustHaveCgo(t)
 
+	skipOn(t, "broken - cgo stacktraces", "darwin", "arm64")
+
 	protest.AllowRecording(t)
 	withTestProcess("cgotest", t, func(p *proc.Target, fixture protest.Fixture) {
 		setFunctionBreakpoint(p, t, "main.main")
@@ -943,6 +945,8 @@ func stackMatch(stack []loc, locations []proc.Stackframe, skipRuntime bool) bool
 }
 
 func TestStacktraceGoroutine(t *testing.T) {
+	skipOn(t, "broken - cgo stacktraces", "darwin", "arm64")
+
 	mainStack := []loc{{14, "main.stacktraceme"}, {29, "main.main"}}
 	if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
 		mainStack[0].line = 15
diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go
index 5669c98d..f4c2a514 100644
--- a/pkg/terminal/command_test.go
+++ b/pkg/terminal/command_test.go
@@ -690,9 +690,9 @@ func TestListCmd(t *testing.T) {
 		term.MustExec("continue")
 		term.MustExec("continue")
 		listIsAt(t, term, "list", 27, 22, 32)
-		listIsAt(t, term, "list 69", 69, 64, 73)
-		listIsAt(t, term, "frame 1 list", 65, 60, 70)
-		listIsAt(t, term, "frame 1 list 69", 69, 64, 73)
+		listIsAt(t, term, "list 69", 69, 64, 74)
+		listIsAt(t, term, "frame 1 list", 66, 61, 71)
+		listIsAt(t, term, "frame 1 list 69", 69, 64, 74)
 		_, err := term.Exec("frame 50 list")
 		if err == nil {
 			t.Fatalf("Expected error requesting 50th frame")
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 02310022..d7882842 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -751,7 +751,7 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 					client.StackTraceRequest(1, 0, 20)
 					stack := client.ExpectStackTraceResponse(t)
 
-					startLineno := 65
+					startLineno := 66
 					if runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
 						// Go1.15 on windows inserts a NOP after the call to
 						// runtime.Breakpoint and marks it same line as the
@@ -794,7 +794,7 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 
 					client.VariablesRequest(1001)
 					locals := client.ExpectVariablesResponse(t)
-					expectChildren(t, locals, "Locals", 30)
+					expectChildren(t, locals, "Locals", 31)
 
 					// reflect.Kind == Bool
 					expectVarExact(t, locals, -1, "b1", "b1", "true", noChildren)
@@ -1397,42 +1397,17 @@ func TestVariablesLoading(t *testing.T) {
 						ref = expectVarExact(t, tm, 0, "v", "tm.v", "<[]map[string]main.astruct> (length: 1, cap: 1)", hasChildren)
 						if ref > 0 {
 							client.VariablesRequest(ref)
-							tm_v := client.ExpectVariablesResponse(t)
-							expectChildren(t, tm_v, "tm.v", 1)
+							tmV := client.ExpectVariablesResponse(t)
+							expectChildren(t, tmV, "tm.v", 1)
 							// TODO(polina): there should be children here once we support auto loading - test for "tm.v[0]["gutters"]"
-							expectVarExact(t, tm_v, 0, "[0]", "tm.v[0]", "<map[string]main.astruct> (length: 66, loaded: 0)", noChildren)
+							expectVarExact(t, tmV, 0, "[0]", "tm.v[0]", "<map[string]main.astruct> (length: 66, loaded: 0)", noChildren)
 						}
 					}
-
-					// Interface fully missing due to hitting LoadConfig.MaxVariableRecurse
-					ref = expectVarRegex(t, locals, -1, "ptrinf", "ptrinf", `<\*interface {}>\(0x[0-9a-f]+\)`, hasChildren)
-					if ref > 0 {
-						client.VariablesRequest(ref)
-						ptrinf := client.ExpectVariablesResponse(t)
-						ref = expectVarExact(t, ptrinf, 0, "", "(*ptrinf)", "<interface {}(**interface {})>", hasChildren)
-						if ref > 0 {
-							client.VariablesRequest(ref)
-							ptrinfVal := client.ExpectVariablesResponse(t)
-							ref = expectVarRegex(t, ptrinfVal, 0, "", `\(\*ptrinf\)\.\(data\)`, `<\*\*interface {}>\(0x[0-9a-f]+\)`, hasChildren)
-							if ref > 0 {
-								client.VariablesRequest(ref)
-								ptrinfValData := client.ExpectVariablesResponse(t)
-								ref = expectVarRegex(t, ptrinfValData, 0, "", `\(\*\(\*ptrinf\)\.\(data\)\)`, `<\*interface {}>\(0x[0-9a-f]+\)`, hasChildren)
-								if ref > 0 {
-									client.VariablesRequest(ref)
-									iface6dataValVal := client.ExpectVariablesResponse(t)
-									expectVarExact(t, iface6dataValVal, 0, "", "(*(*(*ptrinf).(data)))", "<interface {}(**interface {})> (data not loaded)", noChildren)
-								}
-							}
-						}
-					}
-
-					// Pointer fully missing - see below
 				},
 				disconnect: true,
 			}})
 	})
-	runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) {
+	runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
 		runDebugSessionWithBPs(t, client, "launch",
 			// Launch
 			func() {
@@ -1448,14 +1423,54 @@ func TestVariablesLoading(t *testing.T) {
 					client.StackTraceRequest(1, 0, 0)
 					client.ExpectStackTraceResponse(t)
 
-					client.ScopesRequest(1000)
-					client.ExpectScopesResponse(t)
+					var loadvars = func(frame int) {
+						client.ScopesRequest(frame)
+						scopes := client.ExpectScopesResponse(t)
+						localsRef := 0
+						for _, s := range scopes.Body.Scopes {
+							if s.Name == "Locals" {
+								localsRef = s.VariablesReference
+							}
+						}
 
-					client.VariablesRequest(1001) // Locals
-					locals := client.ExpectVariablesResponse(t)
+						client.VariablesRequest(localsRef)
+						locals := client.ExpectVariablesResponse(t)
+
+						// Interface auto-loaded when hitting LoadConfig.MaxVariableRecurse=1
+
+						ref := expectVarExact(t, locals, -1, "ni", "ni", "<[]interface {}> (length: 1, cap: 1)", hasChildren)
+						if ref > 0 {
+							client.VariablesRequest(ref)
+							ni := client.ExpectVariablesResponse(t)
+							ref = expectVarExact(t, ni, 0, "[0]", "ni[0]", "<interface {}([]interface {})>", hasChildren)
+							if ref > 0 {
+								client.VariablesRequest(ref)
+								niI1 := client.ExpectVariablesResponse(t)
+								ref = expectVarExact(t, niI1, 0, "data", "ni[0].(data)", "<[]interface {}> (length: 1, cap: 1)", hasChildren)
+								if ref > 0 {
+									// Auto-loading happens here
+									client.VariablesRequest(ref)
+									niI1Data := client.ExpectVariablesResponse(t)
+									ref = expectVarExact(t, niI1Data, 0, "[0]", "ni[0].(data)[0]", "<interface {}(int)>", hasChildren)
+									if ref > 0 {
+										client.VariablesRequest(ref)
+										niI1DataI2 := client.ExpectVariablesResponse(t)
+										expectVarExact(t, niI1DataI2, 0, "data", "ni[0].(data)[0].(data)", "123", noChildren)
+									}
+								}
+							}
+						}
 
-					// Pointer value not loaded
-					expectVarRegex(t, locals, -1, "ptrinf", "ptrinf", `<\*interface {}>\(0x[0-9a-f]+\) \(value not loaded\)`, noChildren)
+						// Pointer value not loaded with LoadConfig.FollowPointers=false
+						expectVarRegex(t, locals, -1, "a7", "a7", `<\*main.FooBar>\(0x[0-9a-f]+\) \(value not loaded\)`, noChildren)
+					}
+					loadvars(1000 /*first topmost frame*/)
+					// step into another function
+					client.StepInRequest(1)
+					client.ExpectStepInResponse(t)
+					client.ExpectStoppedEvent(t)
+					handleStop(t, client, 1, "main.barfoo", 24)
+					loadvars(1001 /*second frame here is same as topmost above*/)
 				},
 				disconnect: true,
 			}})
@@ -1676,7 +1691,7 @@ func TestEvaluateRequest(t *testing.T) {
 			fixture.Source, []int{}, // Breakpoint set in the program
 			[]onBreakpoint{{ // Stop at first breakpoint
 				execute: func() {
-					handleStop(t, client, 1, "main.foobar", 65)
+					handleStop(t, client, 1, "main.foobar", 66)
 
 					// Variable lookup
 					client.EvaluateRequest("a2", 1000, "this context will be ignored")
@@ -2249,7 +2264,6 @@ func TestLaunchDebugRequest(t *testing.T) {
 		// We reuse the harness that builds, but ignore the built binary,
 		// only relying on the source to be built in response to LaunchRequest.
 		runDebugSession(t, client, "launch", func() {
-			// Use the default output directory.
 			client.LaunchRequestWithArgs(map[string]interface{}{
 				"mode": "debug", "program": fixture.Source, "output": "__mydir"})
 		}, fixture.Source)
@@ -2274,6 +2288,31 @@ func TestLaunchDebugRequest(t *testing.T) {
 	}
 }
 
+// TestLaunchRequestDefaults tests defaults for launch attribute that are explicit in other tests.
+func TestLaunchRequestDefaults(t *testing.T) {
+	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSession(t, client, "launch", func() {
+			client.LaunchRequestWithArgs(map[string]interface{}{
+				"mode": "" /*"debug" by default*/, "program": fixture.Source, "output": "__mydir"})
+		}, fixture.Source)
+	})
+	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSession(t, client, "launch", func() {
+			// Use the default output directory.
+			client.LaunchRequestWithArgs(map[string]interface{}{
+				/*"mode":"debug" by default*/ "program": fixture.Source, "output": "__mydir"})
+		}, fixture.Source)
+	})
+	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSession(t, client, "launch", func() {
+			// Use the default output directory.
+			client.LaunchRequestWithArgs(map[string]interface{}{
+				"mode": "debug", "program": fixture.Source})
+			// writes to default output dir __debug_bin
+		}, fixture.Source)
+	})
+}
+
 func TestLaunchTestRequest(t *testing.T) {
 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 		runDebugSession(t, client, "launch", func() {
@@ -2311,9 +2350,8 @@ func TestLaunchRequestWithBuildFlags(t *testing.T) {
 			// We reuse the harness that builds, but ignore the built binary,
 			// only relying on the source to be built in response to LaunchRequest.
 			client.LaunchRequestWithArgs(map[string]interface{}{
-				"mode": "debug", "program": fixture.Source,
+				"mode": "debug", "program": fixture.Source, "output": "__mydir",
 				"buildFlags": "-ldflags '-X main.Hello=World'"})
-			// will write to default output dir __debug_bin
 		}, fixture.Source)
 	})
 }
@@ -2338,7 +2376,8 @@ func TestAttachRequest(t *testing.T) {
 		runDebugSessionWithBPs(t, client, "attach",
 			// Attach
 			func() {
-				client.AttachRequest(map[string]interface{}{"mode": "local", "processId": cmd.Process.Pid, "stopOnEntry": false})
+				client.AttachRequest(map[string]interface{}{
+					/*"mode": "local" by default*/ "processId": cmd.Process.Pid, "stopOnEntry": false})
 			},
 			// Set breakpoints
 			fixture.Source, []int{8},
@@ -2506,18 +2545,20 @@ func TestBadLaunchRequests(t *testing.T) {
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: The program attribute is missing in debug configuration.")
 
-		client.LaunchRequestWithArgs(map[string]interface{}{"program": 12345})
+		// Bad "program"
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": 12345})
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: The program attribute is missing in debug configuration.")
 
-		client.LaunchRequestWithArgs(map[string]interface{}{"program": nil})
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": nil})
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: The program attribute is missing in debug configuration.")
 
-		client.LaunchRequestWithArgs(map[string]interface{}{})
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug"})
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: The program attribute is missing in debug configuration.")
 
+		// Bad "mode"
 		client.LaunchRequest("remote", fixture.Path, stopOnEntry)
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: Unsupported 'mode' value \"remote\" in debug configuration.")
@@ -2530,6 +2571,15 @@ func TestBadLaunchRequests(t *testing.T) {
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: Unsupported 'mode' value %!q(float64=12345) in debug configuration.")
 
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": ""}) // empty mode defaults to "debug" (not an error)
+		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
+			"Failed to launch: The program attribute is missing in debug configuration.")
+
+		client.LaunchRequestWithArgs(map[string]interface{}{}) // missing mode defaults to "debug" (not an error)
+		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
+			"Failed to launch: The program attribute is missing in debug configuration.")
+
+		// Bad "args"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "exec", "program": fixture.Path, "args": nil})
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: 'args' attribute '<nil>' in debug configuration is not an array.")
@@ -2542,26 +2592,26 @@ func TestBadLaunchRequests(t *testing.T) {
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: value '1' in 'args' attribute in debug configuration is not a string.")
 
+		// Bad "buildFlags"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": 123})
 		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
 			"Failed to launch: 'buildFlags' attribute '123' in debug configuration is not a string.")
 
 		// Skip detailed message checks for potentially different OS-specific errors.
 		client.LaunchRequest("exec", fixture.Path+"_does_not_exist", stopOnEntry)
-		expectFailedToLaunch(client.ExpectErrorResponse(t))
+		expectFailedToLaunch(client.ExpectErrorResponse(t)) // No such file or directory
 
 		client.LaunchRequest("debug", fixture.Path+"_does_not_exist", stopOnEntry)
-		expectFailedToLaunch(client.ExpectErrorResponse(t)) // Build error
+		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: Build error: exit status 1")
+
+		client.LaunchRequest("" /*debug by default*/, fixture.Path+"_does_not_exist", stopOnEntry)
+		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: Build error: exit status 1")
 
 		client.LaunchRequest("exec", fixture.Source, stopOnEntry)
 		expectFailedToLaunch(client.ExpectErrorResponse(t)) // Not an executable
 
-		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": "123"})
-		expectFailedToLaunch(client.ExpectErrorResponse(t)) // Build error
-
-		client.LaunchRequest("", fixture.Path, stopOnEntry)
-		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t),
-			"Failed to launch: Build error: exit status 1")
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": "bad flags"})
+		expectFailedToLaunchWithMessage(client.ExpectErrorResponse(t), "Failed to launch: Build error: exit status 1")
 
 		// We failed to launch the program. Make sure shutdown still works.
 		client.DisconnectRequest()
@@ -2600,6 +2650,7 @@ func TestBadAttachRequest(t *testing.T) {
 			}
 		}
 
+		// Bad "mode"
 		client.AttachRequest(map[string]interface{}{"mode": "remote"})
 		expectFailedToAttachWithMessage(client.ExpectErrorResponse(t),
 			"Failed to attach: Unsupported 'mode' value \"remote\" in debug configuration")
@@ -2612,28 +2663,37 @@ func TestBadAttachRequest(t *testing.T) {
 		expectFailedToAttachWithMessage(client.ExpectErrorResponse(t),
 			"Failed to attach: Unsupported 'mode' value %!q(float64=123) in debug configuration")
 
-		client.AttachRequest(map[string]interface{}{"mode": ""}) // defaults to "local"
+		client.AttachRequest(map[string]interface{}{"mode": ""}) // empty mode defaults to "local" (not an error)
+		expectFailedToAttachWithMessage(client.ExpectErrorResponse(t),
+			"Failed to attach: The 'processId' attribute is missing in debug configuration")
+
+		client.AttachRequest(map[string]interface{}{}) // no mode defaults to "local" (not an error)
+		expectFailedToAttachWithMessage(client.ExpectErrorResponse(t),
+			"Failed to attach: The 'processId' attribute is missing in debug configuration")
+
+		// Bad "processId"
+		client.AttachRequest(map[string]interface{}{"mode": "local"})
 		expectFailedToAttachWithMessage(client.ExpectErrorResponse(t),
 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 
-		client.AttachRequest(map[string]interface{}{}) // defaults to "local"
+		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": nil})
 		expectFailedToAttachWithMessage(client.ExpectErrorResponse(t),
 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 
-		client.AttachRequest(map[string]interface{}{"processId": 0}) // defaults to "local"
+		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 0})
 		expectFailedToAttachWithMessage(client.ExpectErrorResponse(t),
 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 
-		client.AttachRequest(map[string]interface{}{"processId": "1"}) // defaults to "local"
+		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": "1"})
 		expectFailedToAttachWithMessage(client.ExpectErrorResponse(t),
 			"Failed to attach: The 'processId' attribute is missing in debug configuration")
 
-		client.AttachRequest(map[string]interface{}{"processId": 1}) // defaults to "local"
+		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1})
 		// The exact message varies on different systems, so skip that check
 		expectFailedToAttach(client.ExpectErrorResponse(t)) // could not attach to pid 1
 
 		// This will make debugger.(*Debugger) panic, which we will catch as an internal error.
-		client.AttachRequest(map[string]interface{}{"processId": -1}) // defaults to "local"
+		client.AttachRequest(map[string]interface{}{"mode": "local", "processId": -1})
 		er := client.ExpectErrorResponse(t)
 		if er.RequestSeq != seqCnt {
 			t.Errorf("RequestSeq got %d, want %d", seqCnt, er.RequestSeq)
diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go
index 3cb3a995..bde8f9fd 100644
--- a/service/test/integration1_test.go
+++ b/service/test/integration1_test.go
@@ -716,6 +716,9 @@ func Test1ClientServer_SetVariable(t *testing.T) {
 }
 
 func Test1ClientServer_FullStacktrace(t *testing.T) {
+	if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
+		t.Skip("cgo doesn't work on darwin/arm64")
+	}
 	withTestClient1("goroutinestackprog", t, func(c *rpc1.RPCClient) {
 		_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1})
 		assertNoError(err, t, "CreateBreakpoint()")
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index 5cab11d8..270b1bd7 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -911,6 +911,9 @@ func TestClientServer_SetVariable(t *testing.T) {
 
 func TestClientServer_FullStacktrace(t *testing.T) {
 	protest.AllowRecording(t)
+	if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
+		t.Skip("cgo doesn't work on darwin/arm64")
+	}
 	withTestClient2("goroutinestackprog", t, func(c service.Client) {
 		_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1})
 		assertNoError(err, t, "CreateBreakpoint()")
diff --git a/service/test/variables_test.go b/service/test/variables_test.go
index 64ec5dc9..c2087930 100644
--- a/service/test/variables_test.go
+++ b/service/test/variables_test.go
@@ -446,6 +446,7 @@ func TestLocalVariables(t *testing.T) {
 				{"mp", true, "map[int]interface {} [1: 42, 2: 43, ]", "", "map[int]interface {}", nil},
 				{"ms", true, "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)…", "", "main.Nest", nil},
 				{"neg", true, "-1", "", "int", nil},
+				{"ni", true, "[]interface {} len: 1, cap: 1, [[]interface {} len: 1, cap: 1, [*(*interface {})…", "", "[]interface {}", nil},
 				{"u16", true, "65535", "", "uint16", nil},
 				{"u32", true, "4294967295", "", "uint32", nil},
 				{"u64", true, "18446744073709551615", "", "uint64", nil},
@@ -1564,6 +1565,10 @@ func TestCgoEval(t *testing.T) {
 		{"v_align_check[90]", false, "align_check {a: 90, b: 90}", "align_check {a: 90, b: 90}", "align_check", nil},
 	}
 
+	if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
+		t.Skip("cgo doesn't work on darwin/arm64")
+	}
+
 	protest.AllowRecording(t)
 	withTestProcess("testvariablescgo/", t, func(p *proc.Target, fixture protest.Fixture) {
 		assertNoError(p.Continue(), t, "Continue() returned an error")