Skip to content

Commit c858269

Browse files
jbachorikclaude
andcommitted
fix(test): use ephemeral port for ExtensionLifecycleIntegrationTest
testOnMethodUnattended in BTraceFunctionalTests leaks its target JVM process without stopping it, leaving the BTrace agent bound to port 2020 indefinitely. ExtensionLifecycleIntegrationTest then fails with "Port 2020 unavailable" because the port is never released. Fix by adding btracePort field to RuntimeTest that passes -p <port> to the btrace Loader, and using ServerSocket(0) in the lifecycle tests to pick a fresh ephemeral port for each test, avoiding the conflict entirely. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b4f734f commit c858269

2 files changed

Lines changed: 23 additions & 27 deletions

File tree

integration-tests/src/test/java/tests/ExtensionLifecycleIntegrationTest.java

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
package tests;
2626

27+
import java.io.IOException;
2728
import java.net.ServerSocket;
2829
import org.junit.jupiter.api.BeforeAll;
2930
import org.junit.jupiter.api.BeforeEach;
@@ -49,29 +50,12 @@ public static void setup() throws Exception {
4950
@Override
5051
public void reset() {
5152
super.reset();
52-
waitForPort(2020, 30_000);
53-
}
54-
55-
/**
56-
* Waits until the given port is available (not in use by a previous test's agent).
57-
*/
58-
private static void waitForPort(int port, long timeoutMs) {
59-
long deadline = System.currentTimeMillis() + timeoutMs;
60-
while (System.currentTimeMillis() < deadline) {
61-
try (ServerSocket ss = new ServerSocket(port)) {
62-
ss.setReuseAddress(true);
63-
return; // port is free
64-
} catch (Exception e) {
65-
// port still in use
66-
try {
67-
Thread.sleep(200);
68-
} catch (InterruptedException ie) {
69-
Thread.currentThread().interrupt();
70-
return;
71-
}
72-
}
53+
// Use an ephemeral port to avoid conflicts with leaked agents from other test classes
54+
try (ServerSocket ss = new ServerSocket(0)) {
55+
btracePort = ss.getLocalPort();
56+
} catch (IOException e) {
57+
throw new RuntimeException("Failed to find a free port", e);
7358
}
74-
System.err.println("WARNING: port " + port + " still unavailable after " + timeoutMs + "ms");
7559
}
7660

7761
@Test
@@ -89,7 +73,7 @@ public void validate(String stdout, String stderr, int retcode, String jfrFile)
8973
// Validate extension method was called
9074
assertTrue(
9175
stdout.contains("LIFECYCLE: extension method called"),
92-
"Extension method not called");
76+
"Extension method not called. stdout: " + stdout);
9377
}
9478
});
9579
}
@@ -107,9 +91,10 @@ public void validate(String stdout, String stderr, int retcode, String jfrFile)
10791
// Extension should still be called even on error exit
10892
assertTrue(
10993
stdout.contains("LIFECYCLE: extension method called"),
110-
"Extension method not called");
94+
"Extension method not called. stdout: " + stdout);
11195
assertTrue(
112-
stdout.contains("Triggering error exit"), "Error exit message not found");
96+
stdout.contains("Triggering error exit"),
97+
"Error exit message not found. stdout: " + stdout);
11398
}
11499
});
115100
}
@@ -129,10 +114,10 @@ public void validate(String stdout, String stderr, int retcode, String jfrFile)
129114
// Validate both extensions were called
130115
assertTrue(
131116
stdout.contains("LIFECYCLE: printer extension called"),
132-
"Printer extension method not called");
117+
"Printer extension method not called. stdout: " + stdout);
133118
assertTrue(
134119
stdout.contains("LIFECYCLE: metrics extension called"),
135-
"Metrics extension method not called");
120+
"Metrics extension method not called. stdout: " + stdout);
136121
}
137122
});
138123
}

integration-tests/src/test/java/tests/RuntimeTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ public abstract class RuntimeTest {
9595
protected boolean dumpOneliner = false;
9696
/** Dump verifier errors in target JVM */
9797
protected boolean dumpVerifierErrors = false;
98+
/** Override the BTrace agent/client port (0 = use default 2020) */
99+
protected int btracePort = 0;
98100
/** Provide extra JVM args */
99101
private static final List<String> extraJvmArgs = new ArrayList<>();
100102

@@ -218,6 +220,7 @@ protected void reset() {
218220
attachDelayMs = 0;
219221
dumpOneliner = false;
220222
dumpVerifierErrors = false;
223+
btracePort = 0;
221224
timeout = defaultTimeoutMs;
222225
}
223226

@@ -1305,6 +1308,10 @@ private Process attach(
13051308
if (unattended) {
13061309
argVals.add("-x");
13071310
}
1311+
if (btracePort > 0) {
1312+
argVals.add("-p");
1313+
argVals.add(String.valueOf(btracePort));
1314+
}
13081315
argVals.addAll(Arrays.asList(pid, traceFile.getAbsolutePath()));
13091316
if (cmdArgs != null) {
13101317
argVals.addAll(Arrays.asList(cmdArgs));
@@ -1424,6 +1431,10 @@ private Process attachOneliner(
14241431
if (unattended) {
14251432
argVals.add("-x");
14261433
}
1434+
if (btracePort > 0) {
1435+
argVals.add("-p");
1436+
argVals.add(String.valueOf(btracePort));
1437+
}
14271438
argVals.addAll(Arrays.asList(pid));
14281439
if (cmdArgs != null) {
14291440
argVals.addAll(Arrays.asList(cmdArgs));

0 commit comments

Comments
 (0)