Skip to content

Commit fd4fc92

Browse files
authored
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
1 parent 7c9e79b commit fd4fc92

File tree

6 files changed

+214
-32
lines changed

6 files changed

+214
-32
lines changed

_fixtures/testenv2.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"runtime"
7+
)
8+
9+
func main() {
10+
x, y := os.LookupEnv("SOMEVAR")
11+
runtime.Breakpoint()
12+
fmt.Printf("SOMEVAR=%s\n%v", x, y)
13+
}

_fixtures/testnextnethttp.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package main
22

33
import (
4+
"fmt"
5+
"net"
46
"net/http"
7+
"os"
8+
"path/filepath"
59
"runtime"
610
)
7-
811
func main() {
912
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
1013
runtime.Breakpoint()
@@ -17,7 +20,23 @@ func main() {
1720
header := w.Header().Get("Content-Type")
1821
w.Write([]byte(msg + header))
1922
})
20-
err := http.ListenAndServe(":9191", nil)
23+
listener, err := net.Listen("tcp", ":0")
24+
if err != nil {
25+
panic(err)
26+
}
27+
port := listener.Addr().(*net.TCPAddr).Port
28+
fmt.Printf("LISTENING:%d\n", port)
29+
30+
// Also write port to a file for tests that can't capture stdout
31+
// Include PID in filename to avoid conflicts when tests run in parallel
32+
tmpdir := os.TempDir()
33+
portFile := filepath.Join(tmpdir, fmt.Sprintf("testnextnethttp_port_%d", os.Getpid()))
34+
os.WriteFile(portFile, []byte(fmt.Sprintf("%d", port)), 0644)
35+
36+
// Clean up port file when program exits
37+
defer os.Remove(portFile)
38+
39+
err = http.Serve(listener, nil)
2140
if err != nil {
2241
panic(err)
2342
}

_scripts/test_linux.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ else
4848
getgo $version
4949
fi
5050

51-
5251
GOPATH=$(pwd)/go
5352
export GOPATH
5453
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

pkg/proc/proc_test.go

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"runtime"
2222
"slices"
2323
"sort"
24+
"strconv"
2425
"strings"
2526
"sync"
2627
"testing"
@@ -526,21 +527,49 @@ func TestNextConcurrentVariant2(t *testing.T) {
526527

527528
func TestNextNetHTTP(t *testing.T) {
528529
testcases := []nextTest{
529-
{11, 12},
530-
{12, 13},
530+
{14, 15},
531+
{15, 16},
531532
}
532533
withTestProcess("testnextnethttp", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
534+
pid := p.Pid()
535+
tmpdir := os.TempDir()
536+
portFile := filepath.Join(tmpdir, fmt.Sprintf("testnextnethttp_port_%d", pid))
537+
538+
defer os.Remove(portFile)
539+
533540
go func() {
534-
// Wait for program to start listening.
541+
// Wait for program to write the port to file with timeout
542+
var port int
543+
t0 := time.Now()
544+
for {
545+
if data, err := os.ReadFile(portFile); err == nil {
546+
if parsedPort, err := strconv.Atoi(strings.TrimSpace(string(data))); err == nil {
547+
port = parsedPort
548+
break
549+
}
550+
}
551+
time.Sleep(50 * time.Millisecond)
552+
if time.Since(t0) > 10*time.Second {
553+
t.Errorf("timeout waiting for port file")
554+
return
555+
}
556+
}
557+
558+
// Wait for program to start listening with timeout
559+
t0 = time.Now()
535560
for {
536-
conn, err := net.Dial("tcp", "127.0.0.1:9191")
561+
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
537562
if err == nil {
538563
conn.Close()
539564
break
540565
}
541566
time.Sleep(50 * time.Millisecond)
567+
if time.Since(t0) > 10*time.Second {
568+
t.Errorf("timeout waiting for server to start listening")
569+
return
570+
}
542571
}
543-
resp, err := http.Get("http://127.0.0.1:9191")
572+
resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d", port))
544573
if err == nil {
545574
resp.Body.Close()
546575
}
@@ -1821,15 +1850,46 @@ func TestCmdLineArgs(t *testing.T) {
18211850
func TestIssue462(t *testing.T) {
18221851
skipOn(t, "broken", "windows") // Stacktrace of Goroutine 0 fails with an error
18231852
withTestProcess("testnextnethttp", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
1853+
pid := p.Pid()
1854+
tmpdir := os.TempDir()
1855+
portFile := filepath.Join(tmpdir, fmt.Sprintf("testnextnethttp_port_%d", pid))
1856+
1857+
// Ensure cleanup of port file
1858+
defer func() {
1859+
os.Remove(portFile)
1860+
}()
1861+
18241862
go func() {
1825-
// Wait for program to start listening.
1863+
// Wait for program to write the port to file with timeout
1864+
var port int
1865+
t0 := time.Now()
18261866
for {
1827-
conn, err := net.Dial("tcp", "127.0.0.1:9191")
1867+
if data, err := os.ReadFile(portFile); err == nil {
1868+
if parsedPort, err := strconv.Atoi(strings.TrimSpace(string(data))); err == nil {
1869+
port = parsedPort
1870+
break
1871+
}
1872+
}
1873+
time.Sleep(50 * time.Millisecond)
1874+
if time.Since(t0) > 10*time.Second {
1875+
t.Errorf("timeout waiting for port file")
1876+
return
1877+
}
1878+
}
1879+
1880+
// Wait for program to start listening with timeout
1881+
t0 = time.Now()
1882+
for {
1883+
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
18281884
if err == nil {
18291885
conn.Close()
18301886
break
18311887
}
18321888
time.Sleep(50 * time.Millisecond)
1889+
if time.Since(t0) > 10*time.Second {
1890+
t.Errorf("timeout waiting for server to start listening")
1891+
return
1892+
}
18331893
}
18341894

18351895
grp.RequestManualStop()
@@ -2482,17 +2542,46 @@ func TestAttachDetach(t *testing.T) {
24822542
cmd.Stderr = os.Stderr
24832543
assertNoError(cmd.Start(), t, "starting fixture")
24842544

2485-
// wait for testnextnethttp to start listening
2545+
// Read port from PID-specific file and wait for testnextnethttp to start listening
2546+
var port int
2547+
pid := cmd.Process.Pid
2548+
tmpdir := os.TempDir()
2549+
portFile := filepath.Join(tmpdir, fmt.Sprintf("testnextnethttp_port_%d", pid))
2550+
2551+
// Ensure cleanup of port file
2552+
defer func() {
2553+
os.Remove(portFile)
2554+
if cmd.Process != nil {
2555+
cmd.Process.Kill()
2556+
}
2557+
}()
2558+
2559+
// First wait for port file to be written
24862560
t0 := time.Now()
24872561
for {
2488-
conn, err := net.Dial("tcp", "127.0.0.1:9191")
2562+
if data, err := os.ReadFile(portFile); err == nil {
2563+
if parsedPort, err := strconv.Atoi(strings.TrimSpace(string(data))); err == nil {
2564+
port = parsedPort
2565+
break
2566+
}
2567+
}
2568+
time.Sleep(50 * time.Millisecond)
2569+
if time.Since(t0) > 10*time.Second {
2570+
t.Fatal("fixture did not write port file")
2571+
}
2572+
}
2573+
2574+
// Then wait for server to start listening
2575+
t0 = time.Now()
2576+
for {
2577+
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
24892578
if err == nil {
24902579
conn.Close()
24912580
break
24922581
}
24932582
time.Sleep(50 * time.Millisecond)
24942583
if time.Since(t0) > 10*time.Second {
2495-
t.Fatal("fixture did not start")
2584+
t.Fatal("fixture did not start listening")
24962585
}
24972586
}
24982587

@@ -2515,21 +2604,21 @@ func TestAttachDetach(t *testing.T) {
25152604
assertNoError(err, t, "Attach")
25162605
go func() {
25172606
time.Sleep(1 * time.Second)
2518-
resp, err := http.Get("http://127.0.0.1:9191")
2607+
resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d", port))
25192608
if err == nil {
25202609
resp.Body.Close()
25212610
}
25222611
}()
25232612

25242613
assertNoError(p.Continue(), t, "Continue")
2525-
assertLineNumber(p.Selected, t, 11, "Did not continue to correct location,")
2614+
assertLineNumber(p.Selected, t, 14, "Did not continue to correct location,")
25262615

25272616
assertNoError(p.Detach(false), t, "Detach")
25282617

25292618
if runtime.GOOS != "darwin" {
25302619
// Debugserver sometimes will leave a zombie process after detaching, this
25312620
// seems to be a bug with debugserver.
2532-
resp, err := http.Get("http://127.0.0.1:9191/nobp")
2621+
resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/nobp", port))
25332622
assertNoError(err, t, "Page request after detach")
25342623
bs, err := io.ReadAll(resp.Body)
25352624
assertNoError(err, t, "Reading /nobp page")

service/dap/server_test.go

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func startDAPServerWithClient(t *testing.T, defaultDebugInfoDirs bool, serverSto
8787
// To mock a server created by dap.NewServer(config) or serving dap.NewSession(conn, config, debugger)
8888
// set those arg fields manually after the server creation.
8989
func startDAPServer(t *testing.T, defaultDebugInfoDirs bool, serverStopped chan struct{}) (server *Server, forceStop chan struct{}) {
90+
t.Helper()
9091
// Start the DAP server.
9192
listener, err := net.Listen("tcp", ":0")
9293
if err != nil {
@@ -5421,14 +5422,14 @@ func TestLaunchRequestDefaults(t *testing.T) {
54215422
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
54225423
runDebugSession(t, client, "launch", func() {
54235424
client.LaunchRequestWithArgs(map[string]any{
5424-
"mode": "" /*"debug" by default*/, "program": fixture.Source, "output": "__mybin",
5425+
"mode": "" /*"debug" by default*/, "program": fixture.Source,
54255426
})
54265427
})
54275428
})
54285429
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
54295430
runDebugSession(t, client, "launch", func() {
54305431
client.LaunchRequestWithArgs(map[string]any{
5431-
/*"mode":"debug" by default*/ "program": fixture.Source, "output": "__mybin",
5432+
/*"mode":"debug" by default*/ "program": fixture.Source,
54325433
})
54335434
})
54345435
})
@@ -5504,7 +5505,7 @@ func TestNoDebug_GoodExitStatus(t *testing.T) {
55045505
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
55055506
runNoDebugSession(t, client, func() {
55065507
client.LaunchRequestWithArgs(map[string]any{
5507-
"noDebug": true, "mode": "debug", "program": fixture.Source, "output": "__mybin",
5508+
"noDebug": true, "mode": "debug", "program": fixture.Source,
55085509
})
55095510
}, 0)
55105511
})
@@ -5761,7 +5762,7 @@ func TestLaunchRequestWithBuildFlags(t *testing.T) {
57615762
// We reuse the harness that builds, but ignore the built binary,
57625763
// only relying on the source to be built in response to LaunchRequest.
57635764
client.LaunchRequestWithArgs(map[string]any{
5764-
"mode": "debug", "program": fixture.Source, "output": "__mybin",
5765+
"mode": "debug", "program": fixture.Source,
57655766
"buildFlags": "-ldflags '-X main.Hello=World'",
57665767
})
57675768
})
@@ -5774,7 +5775,7 @@ func TestLaunchRequestWithBuildFlags2(t *testing.T) {
57745775
// We reuse the harness that builds, but ignore the built binary,
57755776
// only relying on the source to be built in response to LaunchRequest.
57765777
client.LaunchRequestWithArgs(map[string]any{
5777-
"mode": "debug", "program": fixture.Source, "output": "__mybin",
5778+
"mode": "debug", "program": fixture.Source,
57785779
"buildFlags": []string{"-ldflags", "-X main.Hello=World"},
57795780
})
57805781
})
@@ -7844,12 +7845,42 @@ func TestBreakpointAfterDisconnect(t *testing.T) {
78447845
fixture := protest.BuildFixture(t, "testnextnethttp", protest.AllNonOptimized)
78457846

78467847
cmd := exec.Command(fixture.Path)
7847-
cmd.Stdout = os.Stdout
7848+
7849+
// Capture stdout to read the port number
7850+
stdout, err := cmd.StdoutPipe()
7851+
if err != nil {
7852+
t.Fatal("failed to create stdout pipe:", err)
7853+
}
78487854
cmd.Stderr = os.Stderr
78497855
if err := cmd.Start(); err != nil {
78507856
t.Fatal(err)
78517857
}
78527858

7859+
// Read the port from stdout in a goroutine
7860+
var port int
7861+
portChan := make(chan int, 1)
7862+
go func() {
7863+
var portLine string
7864+
buf := make([]byte, 256)
7865+
for {
7866+
n, err := stdout.Read(buf)
7867+
if err != nil {
7868+
return
7869+
}
7870+
portLine += string(buf[:n])
7871+
if strings.Contains(portLine, "LISTENING:") {
7872+
parts := strings.Split(portLine, "LISTENING:")
7873+
if len(parts) > 1 {
7874+
portStr := strings.TrimSpace(strings.Split(parts[1], "\n")[0])
7875+
if p, err := strconv.Atoi(portStr); err == nil {
7876+
portChan <- p
7877+
return
7878+
}
7879+
}
7880+
}
7881+
}
7882+
}()
7883+
78537884
var server MultiClientCloseServerMock
78547885
server.stopped = make(chan struct{})
78557886
server.impl, server.forceStop = startDAPServer(t, false, server.stopped)
@@ -7881,9 +7912,16 @@ func TestBreakpointAfterDisconnect(t *testing.T) {
78817912

78827913
server.impl.session.conn = &connection{ReadWriteCloser: discard{}} // fake a race condition between onDisconnectRequest and the runUntilStopAndNotify goroutine
78837914

7915+
// Wait for port to be available
7916+
select {
7917+
case port = <-portChan:
7918+
case <-time.After(5 * time.Second):
7919+
t.Fatal("timed out waiting for fixture to start listening")
7920+
}
7921+
78847922
httpClient := &http.Client{Timeout: time.Second}
78857923

7886-
resp, err := httpClient.Get("http://127.0.0.1:9191/nobp")
7924+
resp, err := httpClient.Get(fmt.Sprintf("http://127.0.0.1:%d/nobp", port))
78877925
if err != nil {
78887926
t.Fatalf("Page request after disconnect failed: %v", err)
78897927
}

0 commit comments

Comments
 (0)