mirror of
https://github.com/go-delve/delve.git
synced 2025-11-03 13:57:33 +08:00
service,proc: fix tests to enable parallel runs (#4135)
* *: randomize testnextnethttp.go listen port * service/test: prefer t.Setenv * *: cleanup port pid files * service: fix final parallelization bugs * address review feedback * pkg/proc: fix test on windows * fix finding port file on TestIssue462
This commit is contained in:
13
_fixtures/testenv2.go
Normal file
13
_fixtures/testenv2.go
Normal file
@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
x, y := os.LookupEnv("SOMEVAR")
|
||||
runtime.Breakpoint()
|
||||
fmt.Printf("SOMEVAR=%s\n%v", x, y)
|
||||
}
|
||||
@ -1,10 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
runtime.Breakpoint()
|
||||
@ -17,7 +20,23 @@ func main() {
|
||||
header := w.Header().Get("Content-Type")
|
||||
w.Write([]byte(msg + header))
|
||||
})
|
||||
err := http.ListenAndServe(":9191", nil)
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
port := listener.Addr().(*net.TCPAddr).Port
|
||||
fmt.Printf("LISTENING:%d\n", port)
|
||||
|
||||
// Also write port to a file for tests that can't capture stdout
|
||||
// Include PID in filename to avoid conflicts when tests run in parallel
|
||||
tmpdir := os.TempDir()
|
||||
portFile := filepath.Join(tmpdir, fmt.Sprintf("testnextnethttp_port_%d", os.Getpid()))
|
||||
os.WriteFile(portFile, []byte(fmt.Sprintf("%d", port)), 0644)
|
||||
|
||||
// Clean up port file when program exits
|
||||
defer os.Remove(portFile)
|
||||
|
||||
err = http.Serve(listener, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@ -48,7 +48,6 @@ else
|
||||
getgo $version
|
||||
fi
|
||||
|
||||
|
||||
GOPATH=$(pwd)/go
|
||||
export GOPATH
|
||||
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -526,21 +527,49 @@ func TestNextConcurrentVariant2(t *testing.T) {
|
||||
|
||||
func TestNextNetHTTP(t *testing.T) {
|
||||
testcases := []nextTest{
|
||||
{11, 12},
|
||||
{12, 13},
|
||||
{14, 15},
|
||||
{15, 16},
|
||||
}
|
||||
withTestProcess("testnextnethttp", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||
pid := p.Pid()
|
||||
tmpdir := os.TempDir()
|
||||
portFile := filepath.Join(tmpdir, fmt.Sprintf("testnextnethttp_port_%d", pid))
|
||||
|
||||
defer os.Remove(portFile)
|
||||
|
||||
go func() {
|
||||
// Wait for program to start listening.
|
||||
// Wait for program to write the port to file with timeout
|
||||
var port int
|
||||
t0 := time.Now()
|
||||
for {
|
||||
conn, err := net.Dial("tcp", "127.0.0.1:9191")
|
||||
if data, err := os.ReadFile(portFile); err == nil {
|
||||
if parsedPort, err := strconv.Atoi(strings.TrimSpace(string(data))); err == nil {
|
||||
port = parsedPort
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if time.Since(t0) > 10*time.Second {
|
||||
t.Errorf("timeout waiting for port file")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for program to start listening with timeout
|
||||
t0 = time.Now()
|
||||
for {
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if time.Since(t0) > 10*time.Second {
|
||||
t.Errorf("timeout waiting for server to start listening")
|
||||
return
|
||||
}
|
||||
}
|
||||
resp, err := http.Get("http://127.0.0.1:9191")
|
||||
resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d", port))
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
@ -1821,15 +1850,46 @@ func TestCmdLineArgs(t *testing.T) {
|
||||
func TestIssue462(t *testing.T) {
|
||||
skipOn(t, "broken", "windows") // Stacktrace of Goroutine 0 fails with an error
|
||||
withTestProcess("testnextnethttp", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||
pid := p.Pid()
|
||||
tmpdir := os.TempDir()
|
||||
portFile := filepath.Join(tmpdir, fmt.Sprintf("testnextnethttp_port_%d", pid))
|
||||
|
||||
// Ensure cleanup of port file
|
||||
defer func() {
|
||||
os.Remove(portFile)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// Wait for program to start listening.
|
||||
// Wait for program to write the port to file with timeout
|
||||
var port int
|
||||
t0 := time.Now()
|
||||
for {
|
||||
conn, err := net.Dial("tcp", "127.0.0.1:9191")
|
||||
if data, err := os.ReadFile(portFile); err == nil {
|
||||
if parsedPort, err := strconv.Atoi(strings.TrimSpace(string(data))); err == nil {
|
||||
port = parsedPort
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if time.Since(t0) > 10*time.Second {
|
||||
t.Errorf("timeout waiting for port file")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for program to start listening with timeout
|
||||
t0 = time.Now()
|
||||
for {
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if time.Since(t0) > 10*time.Second {
|
||||
t.Errorf("timeout waiting for server to start listening")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
grp.RequestManualStop()
|
||||
@ -2482,17 +2542,46 @@ func TestAttachDetach(t *testing.T) {
|
||||
cmd.Stderr = os.Stderr
|
||||
assertNoError(cmd.Start(), t, "starting fixture")
|
||||
|
||||
// wait for testnextnethttp to start listening
|
||||
// Read port from PID-specific file and wait for testnextnethttp to start listening
|
||||
var port int
|
||||
pid := cmd.Process.Pid
|
||||
tmpdir := os.TempDir()
|
||||
portFile := filepath.Join(tmpdir, fmt.Sprintf("testnextnethttp_port_%d", pid))
|
||||
|
||||
// Ensure cleanup of port file
|
||||
defer func() {
|
||||
os.Remove(portFile)
|
||||
if cmd.Process != nil {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
// First wait for port file to be written
|
||||
t0 := time.Now()
|
||||
for {
|
||||
conn, err := net.Dial("tcp", "127.0.0.1:9191")
|
||||
if data, err := os.ReadFile(portFile); err == nil {
|
||||
if parsedPort, err := strconv.Atoi(strings.TrimSpace(string(data))); err == nil {
|
||||
port = parsedPort
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if time.Since(t0) > 10*time.Second {
|
||||
t.Fatal("fixture did not write port file")
|
||||
}
|
||||
}
|
||||
|
||||
// Then wait for server to start listening
|
||||
t0 = time.Now()
|
||||
for {
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if time.Since(t0) > 10*time.Second {
|
||||
t.Fatal("fixture did not start")
|
||||
t.Fatal("fixture did not start listening")
|
||||
}
|
||||
}
|
||||
|
||||
@ -2515,21 +2604,21 @@ func TestAttachDetach(t *testing.T) {
|
||||
assertNoError(err, t, "Attach")
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
resp, err := http.Get("http://127.0.0.1:9191")
|
||||
resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d", port))
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
assertNoError(p.Continue(), t, "Continue")
|
||||
assertLineNumber(p.Selected, t, 11, "Did not continue to correct location,")
|
||||
assertLineNumber(p.Selected, t, 14, "Did not continue to correct location,")
|
||||
|
||||
assertNoError(p.Detach(false), t, "Detach")
|
||||
|
||||
if runtime.GOOS != "darwin" {
|
||||
// Debugserver sometimes will leave a zombie process after detaching, this
|
||||
// seems to be a bug with debugserver.
|
||||
resp, err := http.Get("http://127.0.0.1:9191/nobp")
|
||||
resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/nobp", port))
|
||||
assertNoError(err, t, "Page request after detach")
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
assertNoError(err, t, "Reading /nobp page")
|
||||
|
||||
@ -87,6 +87,7 @@ func startDAPServerWithClient(t *testing.T, defaultDebugInfoDirs bool, serverSto
|
||||
// To mock a server created by dap.NewServer(config) or serving dap.NewSession(conn, config, debugger)
|
||||
// set those arg fields manually after the server creation.
|
||||
func startDAPServer(t *testing.T, defaultDebugInfoDirs bool, serverStopped chan struct{}) (server *Server, forceStop chan struct{}) {
|
||||
t.Helper()
|
||||
// Start the DAP server.
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
@ -5421,14 +5422,14 @@ func TestLaunchRequestDefaults(t *testing.T) {
|
||||
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSession(t, client, "launch", func() {
|
||||
client.LaunchRequestWithArgs(map[string]any{
|
||||
"mode": "" /*"debug" by default*/, "program": fixture.Source, "output": "__mybin",
|
||||
"mode": "" /*"debug" by default*/, "program": fixture.Source,
|
||||
})
|
||||
})
|
||||
})
|
||||
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSession(t, client, "launch", func() {
|
||||
client.LaunchRequestWithArgs(map[string]any{
|
||||
/*"mode":"debug" by default*/ "program": fixture.Source, "output": "__mybin",
|
||||
/*"mode":"debug" by default*/ "program": fixture.Source,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -5504,7 +5505,7 @@ func TestNoDebug_GoodExitStatus(t *testing.T) {
|
||||
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runNoDebugSession(t, client, func() {
|
||||
client.LaunchRequestWithArgs(map[string]any{
|
||||
"noDebug": true, "mode": "debug", "program": fixture.Source, "output": "__mybin",
|
||||
"noDebug": true, "mode": "debug", "program": fixture.Source,
|
||||
})
|
||||
}, 0)
|
||||
})
|
||||
@ -5761,7 +5762,7 @@ 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]any{
|
||||
"mode": "debug", "program": fixture.Source, "output": "__mybin",
|
||||
"mode": "debug", "program": fixture.Source,
|
||||
"buildFlags": "-ldflags '-X main.Hello=World'",
|
||||
})
|
||||
})
|
||||
@ -5774,7 +5775,7 @@ func TestLaunchRequestWithBuildFlags2(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]any{
|
||||
"mode": "debug", "program": fixture.Source, "output": "__mybin",
|
||||
"mode": "debug", "program": fixture.Source,
|
||||
"buildFlags": []string{"-ldflags", "-X main.Hello=World"},
|
||||
})
|
||||
})
|
||||
@ -7844,12 +7845,42 @@ func TestBreakpointAfterDisconnect(t *testing.T) {
|
||||
fixture := protest.BuildFixture(t, "testnextnethttp", protest.AllNonOptimized)
|
||||
|
||||
cmd := exec.Command(fixture.Path)
|
||||
cmd.Stdout = os.Stdout
|
||||
|
||||
// Capture stdout to read the port number
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatal("failed to create stdout pipe:", err)
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the port from stdout in a goroutine
|
||||
var port int
|
||||
portChan := make(chan int, 1)
|
||||
go func() {
|
||||
var portLine string
|
||||
buf := make([]byte, 256)
|
||||
for {
|
||||
n, err := stdout.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
portLine += string(buf[:n])
|
||||
if strings.Contains(portLine, "LISTENING:") {
|
||||
parts := strings.Split(portLine, "LISTENING:")
|
||||
if len(parts) > 1 {
|
||||
portStr := strings.TrimSpace(strings.Split(parts[1], "\n")[0])
|
||||
if p, err := strconv.Atoi(portStr); err == nil {
|
||||
portChan <- p
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var server MultiClientCloseServerMock
|
||||
server.stopped = make(chan struct{})
|
||||
server.impl, server.forceStop = startDAPServer(t, false, server.stopped)
|
||||
@ -7881,9 +7912,16 @@ func TestBreakpointAfterDisconnect(t *testing.T) {
|
||||
|
||||
server.impl.session.conn = &connection{ReadWriteCloser: discard{}} // fake a race condition between onDisconnectRequest and the runUntilStopAndNotify goroutine
|
||||
|
||||
// Wait for port to be available
|
||||
select {
|
||||
case port = <-portChan:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timed out waiting for fixture to start listening")
|
||||
}
|
||||
|
||||
httpClient := &http.Client{Timeout: time.Second}
|
||||
|
||||
resp, err := httpClient.Get("http://127.0.0.1:9191/nobp")
|
||||
resp, err := httpClient.Get(fmt.Sprintf("http://127.0.0.1:%d/nobp", port))
|
||||
if err != nil {
|
||||
t.Fatalf("Page request after disconnect failed: %v", err)
|
||||
}
|
||||
|
||||
@ -233,7 +233,9 @@ func TestRestart_rebuild(t *testing.T) {
|
||||
// In the original fixture file the env var tested for is SOMEVAR.
|
||||
t.Setenv("SOMEVAR", "bah")
|
||||
|
||||
withTestClient2Extended("testenv", t, 0, [3]string{}, nil, func(c service.Client, f protest.Fixture) {
|
||||
// This test must use `testenv2` and it should be the *only* test that uses it. This is because it will overwrite
|
||||
// the fixture file with new source.
|
||||
withTestClient2Extended("testenv2", t, 0, [3]string{}, nil, func(c service.Client, f protest.Fixture) {
|
||||
<-c.Continue()
|
||||
|
||||
var1, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, "x", normalLoadConfig)
|
||||
@ -2498,15 +2500,39 @@ func TestDetachLeaveRunning(t *testing.T) {
|
||||
fixture := protest.BuildFixture(t, "testnextnethttp", buildFlags)
|
||||
|
||||
cmd := exec.Command(fixture.Path)
|
||||
cmd.Stdout = os.Stdout
|
||||
|
||||
// Capture stdout to read the port number
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
assertNoError(err, t, "creating stdout pipe")
|
||||
cmd.Stderr = os.Stderr
|
||||
assertNoError(cmd.Start(), t, "starting fixture")
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// Read the port from stdout
|
||||
var port int
|
||||
var portLine string
|
||||
buf := make([]byte, 256)
|
||||
for {
|
||||
n, err := stdout.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal("failed to read port from fixture stdout:", err)
|
||||
}
|
||||
portLine += string(buf[:n])
|
||||
if strings.Contains(portLine, "LISTENING:") {
|
||||
parts := strings.Split(portLine, "LISTENING:")
|
||||
if len(parts) > 1 {
|
||||
portStr := strings.TrimSpace(strings.Split(parts[1], "\n")[0])
|
||||
port, err = strconv.Atoi(portStr)
|
||||
assertNoError(err, t, "parsing port number")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wait for testnextnethttp to start listening
|
||||
t0 := time.Now()
|
||||
for {
|
||||
conn, err := net.Dial("tcp", "127.0.0.1:9191")
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
break
|
||||
@ -3185,9 +3211,7 @@ func TestGuessSubstitutePath(t *testing.T) {
|
||||
}
|
||||
|
||||
guess := func(t *testing.T, goflags string) [][2]string {
|
||||
oldgoflags := os.Getenv("GOFLAGS")
|
||||
os.Setenv("GOFLAGS", goflags)
|
||||
defer os.Setenv("GOFLAGS", oldgoflags)
|
||||
t.Setenv("GOFLAGS", goflags)
|
||||
|
||||
dlvbin := protest.GetDlvBinary(t)
|
||||
|
||||
@ -3212,11 +3236,11 @@ func TestGuessSubstitutePath(t *testing.T) {
|
||||
|
||||
switch runtime.GOARCH {
|
||||
case "ppc64le":
|
||||
os.Setenv("GOFLAGS", "-tags=exp.linuxppc64le")
|
||||
t.Setenv("GOFLAGS", "-tags=exp.linuxppc64le")
|
||||
case "riscv64":
|
||||
os.Setenv("GOFLAGS", "-tags=exp.linuxriscv64")
|
||||
t.Setenv("GOFLAGS", "-tags=exp.linuxriscv64")
|
||||
case "loong64":
|
||||
os.Setenv("GOFLAGS", "-tags=exp.linuxloong64")
|
||||
t.Setenv("GOFLAGS", "-tags=exp.linuxloong64")
|
||||
}
|
||||
|
||||
gsp, err := client.GuessSubstitutePath()
|
||||
|
||||
Reference in New Issue
Block a user