Skip to content

Commit d405a30

Browse files
committed
Clear some internal state of REST Assured between tests
It seems to improve the class loader leaks issues.
1 parent 9c4c8c4 commit d405a30

File tree

9 files changed

+204
-147
lines changed

9 files changed

+204
-147
lines changed

extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/root/ApplicationPathHttpRootTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class ApplicationPathHttpRootTest {
2626

2727
@Test
2828
public void testResources() {
29-
// Note that /foo is added automatically by RestAssuredURLManager
29+
// Note that /foo is added automatically by RestAssuredStateManager
3030
RestAssured.when().get("/hello/world").then().body(Matchers.is("hello world"));
3131
RestAssured.when().get("/world").then().statusCode(404);
3232
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package io.quarkus.test.common;
2+
3+
import java.time.Duration;
4+
import java.util.Optional;
5+
6+
import org.eclipse.microprofile.config.ConfigProvider;
7+
8+
import io.restassured.RestAssured;
9+
import io.restassured.config.HttpClientConfig;
10+
import io.restassured.config.RestAssuredConfig;
11+
import io.restassured.internal.path.json.ConfigurableJsonSlurper;
12+
import io.restassured.path.json.JsonPath;
13+
import io.restassured.specification.RequestSpecification;
14+
15+
/**
16+
* Utility class that sets the rest assured port to the default test port and meaningful timeouts.
17+
* <p>
18+
* This class works whether RestAssured is on the classpath or not - if it is not, invoking the methods of the class are
19+
* essentially NO-OPs
20+
* <p>
21+
* TODO: do we actually want this here, or should it be in a different module?
22+
*/
23+
public class RestAssuredStateManager {
24+
25+
private static final int DEFAULT_HTTP_PORT = 8081;
26+
private static final int DEFAULT_HTTPS_PORT = 8444;
27+
28+
private static int oldPort;
29+
private static String oldBaseURI;
30+
private static String oldBasePath;
31+
private static Object oldRestAssuredConfig; // we can't declare the type here as that would prevent this class for being loaded if RestAssured is not present
32+
private static Object oldRequestSpecification;
33+
34+
private static final boolean REST_ASSURED_PRESENT;
35+
36+
static {
37+
boolean present = false;
38+
try {
39+
Class.forName("io.restassured.RestAssured");
40+
present = true;
41+
} catch (ClassNotFoundException ignored) {
42+
}
43+
REST_ASSURED_PRESENT = present;
44+
}
45+
46+
private RestAssuredStateManager() {
47+
48+
}
49+
50+
private static int getPortFromConfig(int defaultValue, String... keys) {
51+
for (String key : keys) {
52+
Optional<Integer> port = ConfigProvider.getConfig().getOptionalValue(key, Integer.class);
53+
if (port.isPresent())
54+
return port.get();
55+
}
56+
return defaultValue;
57+
}
58+
59+
public static void setURL(boolean useSecureConnection) {
60+
setURL(useSecureConnection, null, null);
61+
}
62+
63+
public static void setURL(boolean useSecureConnection, String additionalPath) {
64+
setURL(useSecureConnection, null, additionalPath);
65+
}
66+
67+
public static void setURL(boolean useSecureConnection, Integer port) {
68+
setURL(useSecureConnection, port, null);
69+
}
70+
71+
public static void setURL(boolean useSecureConnection, Integer port, String additionalPath) {
72+
if (!REST_ASSURED_PRESENT) {
73+
return;
74+
}
75+
76+
oldPort = RestAssured.port;
77+
if (port == null) {
78+
port = useSecureConnection ? getPortFromConfig(DEFAULT_HTTPS_PORT, "quarkus.http.test-ssl-port")
79+
: getPortFromConfig(DEFAULT_HTTP_PORT, "quarkus.lambda.mock-event-server.test-port",
80+
"quarkus.http.test-port");
81+
}
82+
RestAssured.port = port;
83+
84+
oldBaseURI = RestAssured.baseURI;
85+
final String protocol = useSecureConnection ? "https://" : "http://";
86+
String host = ConfigProvider.getConfig().getOptionalValue("quarkus.http.host", String.class)
87+
.orElse("localhost");
88+
if (host.equals("0.0.0.0")) {
89+
host = "localhost";
90+
}
91+
RestAssured.baseURI = protocol + host;
92+
93+
oldBasePath = RestAssured.basePath;
94+
Optional<String> basePath = ConfigProvider.getConfig().getOptionalValue("quarkus.http.root-path",
95+
String.class);
96+
if (basePath.isPresent() || additionalPath != null) {
97+
StringBuilder bp = new StringBuilder();
98+
if (basePath.isPresent()) {
99+
if (basePath.get().startsWith("/")) {
100+
bp.append(basePath.get().substring(1));
101+
} else {
102+
bp.append(basePath.get());
103+
}
104+
if (bp.toString().endsWith("/")) {
105+
bp.setLength(bp.length() - 1);
106+
}
107+
}
108+
if (additionalPath != null) {
109+
if (!additionalPath.startsWith("/")) {
110+
bp.append("/");
111+
}
112+
bp.append(additionalPath);
113+
if (bp.toString().endsWith("/")) {
114+
bp.setLength(bp.length() - 1);
115+
}
116+
}
117+
RestAssured.basePath = bp.toString();
118+
}
119+
120+
oldRestAssuredConfig = RestAssured.config();
121+
122+
Duration timeout = ConfigProvider.getConfig()
123+
.getOptionalValue("quarkus.http.test-timeout", Duration.class).orElse(Duration.ofSeconds(30));
124+
configureTimeouts(timeout);
125+
126+
oldRequestSpecification = RestAssured.requestSpecification;
127+
if (ConfigProvider.getConfig()
128+
.getOptionalValue("quarkus.test.rest-assured.enable-logging-on-failure", Boolean.class).orElse(true)) {
129+
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
130+
}
131+
}
132+
133+
private static void configureTimeouts(Duration d) {
134+
RestAssured.config = RestAssured.config().httpClient(new HttpClientConfig()
135+
.setParam("http.conn-manager.timeout", d.toMillis()) // this needs to be long
136+
.setParam("http.connection.timeout", (int) d.toMillis()) // this needs to be int
137+
.setParam("http.socket.timeout", (int) d.toMillis())); // same here
138+
}
139+
140+
public static void clearState() {
141+
if (!REST_ASSURED_PRESENT) {
142+
return;
143+
}
144+
145+
clearURL();
146+
147+
JsonPath.config = null;
148+
// Reset the numberReturnType ThreadLocal in ConfigurableJsonSlurper as it is causing class loader leaks
149+
try {
150+
java.lang.reflect.Field numberReturnTypeField = ConfigurableJsonSlurper.class.getDeclaredField("numberReturnType");
151+
numberReturnTypeField.setAccessible(true);
152+
ThreadLocal<?> threadLocal = (ThreadLocal<?>) numberReturnTypeField.get(null);
153+
if (threadLocal != null) {
154+
threadLocal.remove();
155+
}
156+
} catch (Exception e) {
157+
// Ignore if the field doesn't exist or can't be accessed
158+
}
159+
}
160+
161+
/**
162+
* @deprecated most probably, you want to call {@link #clearState()}. If it's not the case, please let us know so that we
163+
* can reconsider the deprecation.
164+
* Note: when removing, please move the code to {@link #clearState()}.
165+
*/
166+
@Deprecated(forRemoval = true, since = "3.31")
167+
static void clearURL() {
168+
if (!REST_ASSURED_PRESENT) {
169+
return;
170+
}
171+
172+
RestAssured.port = oldPort;
173+
RestAssured.baseURI = oldBaseURI;
174+
RestAssured.basePath = oldBasePath;
175+
RestAssured.config = (RestAssuredConfig) oldRestAssuredConfig;
176+
RestAssured.requestSpecification = (RequestSpecification) oldRequestSpecification;
177+
}
178+
}
Lines changed: 4 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,11 @@
11
package io.quarkus.test.common;
22

3-
import java.time.Duration;
4-
import java.util.Optional;
5-
6-
import org.eclipse.microprofile.config.ConfigProvider;
7-
8-
import io.restassured.RestAssured;
9-
import io.restassured.config.HttpClientConfig;
10-
import io.restassured.config.RestAssuredConfig;
11-
import io.restassured.specification.RequestSpecification;
12-
133
/**
14-
* Utility class that sets the rest assured port to the default test port and meaningful timeouts.
15-
* <p>
16-
* This class works whether RestAssured is on the classpath or not - if it is not, invoking the methods of the class are
17-
* essentially NO-OPs
18-
* <p>
19-
* TODO: do we actually want this here, or should it be in a different module?
4+
* @deprecated use {@link RestAssuredStateManager} instead
205
*/
6+
@Deprecated(forRemoval = true, since = "3.31")
217
public class RestAssuredURLManager {
228

23-
private static final int DEFAULT_HTTP_PORT = 8081;
24-
private static final int DEFAULT_HTTPS_PORT = 8444;
25-
26-
private static int oldPort;
27-
private static String oldBaseURI;
28-
private static String oldBasePath;
29-
private static Object oldRestAssuredConfig; // we can't declare the type here as that would prevent this class for being loaded if RestAssured is not present
30-
private static Object oldRequestSpecification;
31-
32-
private static final boolean REST_ASSURED_PRESENT;
33-
34-
static {
35-
boolean present = false;
36-
try {
37-
Class.forName("io.restassured.RestAssured");
38-
present = true;
39-
} catch (ClassNotFoundException ignored) {
40-
}
41-
REST_ASSURED_PRESENT = present;
42-
}
43-
44-
private RestAssuredURLManager() {
45-
46-
}
47-
48-
private static int getPortFromConfig(int defaultValue, String... keys) {
49-
for (String key : keys) {
50-
Optional<Integer> port = ConfigProvider.getConfig().getOptionalValue(key, Integer.class);
51-
if (port.isPresent())
52-
return port.get();
53-
}
54-
return defaultValue;
55-
}
56-
579
public static void setURL(boolean useSecureConnection) {
5810
setURL(useSecureConnection, null, null);
5911
}
@@ -67,83 +19,10 @@ public static void setURL(boolean useSecureConnection, Integer port) {
6719
}
6820

6921
public static void setURL(boolean useSecureConnection, Integer port, String additionalPath) {
70-
if (!REST_ASSURED_PRESENT) {
71-
return;
72-
}
73-
74-
oldPort = RestAssured.port;
75-
if (port == null) {
76-
port = useSecureConnection ? getPortFromConfig(DEFAULT_HTTPS_PORT, "quarkus.http.test-ssl-port")
77-
: getPortFromConfig(DEFAULT_HTTP_PORT, "quarkus.lambda.mock-event-server.test-port",
78-
"quarkus.http.test-port");
79-
}
80-
RestAssured.port = port;
81-
82-
oldBaseURI = RestAssured.baseURI;
83-
final String protocol = useSecureConnection ? "https://" : "http://";
84-
String host = ConfigProvider.getConfig().getOptionalValue("quarkus.http.host", String.class)
85-
.orElse("localhost");
86-
if (host.equals("0.0.0.0")) {
87-
host = "localhost";
88-
}
89-
RestAssured.baseURI = protocol + host;
90-
91-
oldBasePath = RestAssured.basePath;
92-
Optional<String> basePath = ConfigProvider.getConfig().getOptionalValue("quarkus.http.root-path",
93-
String.class);
94-
if (basePath.isPresent() || additionalPath != null) {
95-
StringBuilder bp = new StringBuilder();
96-
if (basePath.isPresent()) {
97-
if (basePath.get().startsWith("/")) {
98-
bp.append(basePath.get().substring(1));
99-
} else {
100-
bp.append(basePath.get());
101-
}
102-
if (bp.toString().endsWith("/")) {
103-
bp.setLength(bp.length() - 1);
104-
}
105-
}
106-
if (additionalPath != null) {
107-
if (!additionalPath.startsWith("/")) {
108-
bp.append("/");
109-
}
110-
bp.append(additionalPath);
111-
if (bp.toString().endsWith("/")) {
112-
bp.setLength(bp.length() - 1);
113-
}
114-
}
115-
RestAssured.basePath = bp.toString();
116-
}
117-
118-
oldRestAssuredConfig = RestAssured.config();
119-
120-
Duration timeout = ConfigProvider.getConfig()
121-
.getOptionalValue("quarkus.http.test-timeout", Duration.class).orElse(Duration.ofSeconds(30));
122-
configureTimeouts(timeout);
123-
124-
oldRequestSpecification = RestAssured.requestSpecification;
125-
if (ConfigProvider.getConfig()
126-
.getOptionalValue("quarkus.test.rest-assured.enable-logging-on-failure", Boolean.class).orElse(true)) {
127-
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
128-
}
129-
}
130-
131-
private static void configureTimeouts(Duration d) {
132-
RestAssured.config = RestAssured.config().httpClient(new HttpClientConfig()
133-
.setParam("http.conn-manager.timeout", d.toMillis()) // this needs to be long
134-
.setParam("http.connection.timeout", (int) d.toMillis()) // this needs to be int
135-
.setParam("http.socket.timeout", (int) d.toMillis())); // same here
22+
RestAssuredStateManager.setURL(useSecureConnection, port, additionalPath);
13623
}
13724

13825
public static void clearURL() {
139-
if (!REST_ASSURED_PRESENT) {
140-
return;
141-
}
142-
143-
RestAssured.port = oldPort;
144-
RestAssured.baseURI = oldBaseURI;
145-
RestAssured.basePath = oldBasePath;
146-
RestAssured.config = (RestAssuredConfig) oldRestAssuredConfig;
147-
RestAssured.requestSpecification = (RequestSpecification) oldRequestSpecification;
26+
RestAssuredStateManager.clearURL();
14827
}
14928
}

test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusProdModeTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
import io.quarkus.deployment.util.FileUtil;
5858
import io.quarkus.maven.dependency.Dependency;
5959
import io.quarkus.test.common.PathTestHelper;
60-
import io.quarkus.test.common.RestAssuredURLManager;
60+
import io.quarkus.test.common.RestAssuredStateManager;
6161
import io.quarkus.test.common.TestConfigUtil;
6262
import io.quarkus.test.common.TestResourceManager;
6363
import io.smallrye.common.process.ProcessUtil;
@@ -642,7 +642,7 @@ public void stop() {
642642

643643
}
644644
if (clearRestAssuredURL) {
645-
RestAssuredURLManager.clearURL();
645+
RestAssuredStateManager.clearState();
646646
clearRestAssuredURL = false;
647647
}
648648
}
@@ -653,12 +653,12 @@ private void setupRestAssured() {
653653
.orElse(DEFAULT_HTTP_PORT_INT);
654654

655655
// If http port is 0, then we need to set the port to null in order to use the `quarkus.http.test-ssl-port` property
656-
// which is done in `RestAssuredURLManager.setURL`.
656+
// which is done in `RestAssuredStateManager.setURL`.
657657
if (httpPort == 0) {
658658
httpPort = null;
659659
}
660660

661-
RestAssuredURLManager.setURL(false, httpPort);
661+
RestAssuredStateManager.setURL(false, httpPort);
662662
}
663663

664664
private void ensureApplicationStartupOrFailure() throws IOException {

test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusUnitTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
import io.quarkus.test.common.GroovyClassValue;
7171
import io.quarkus.test.common.PathTestHelper;
7272
import io.quarkus.test.common.PropertyTestUtil;
73-
import io.quarkus.test.common.RestAssuredURLManager;
73+
import io.quarkus.test.common.RestAssuredStateManager;
7474
import io.quarkus.test.common.TestConfigUtil;
7575
import io.quarkus.test.common.TestResourceManager;
7676
import io.quarkus.test.common.http.TestHTTPResourceManager;
@@ -849,8 +849,8 @@ public void afterAll(ExtensionContext extensionContext) throws Exception {
849849
public void afterEach(ExtensionContext context) throws Exception {
850850
if (runningQuarkusApplication != null) {
851851
//this kinda sucks, but everything is isolated, so we need to hook into everything via reflection
852-
runningQuarkusApplication.getClassLoader().loadClass(RestAssuredURLManager.class.getName())
853-
.getDeclaredMethod("clearURL")
852+
runningQuarkusApplication.getClassLoader().loadClass(RestAssuredStateManager.class.getName())
853+
.getDeclaredMethod("clearState")
854854
.invoke(null);
855855
}
856856
}
@@ -862,7 +862,7 @@ public void beforeEach(ExtensionContext context) throws Exception {
862862
return;
863863
}
864864
if (runningQuarkusApplication != null) {
865-
runningQuarkusApplication.getClassLoader().loadClass(RestAssuredURLManager.class.getName())
865+
runningQuarkusApplication.getClassLoader().loadClass(RestAssuredStateManager.class.getName())
866866
.getDeclaredMethod("setURL", boolean.class).invoke(null, useSecureConnection);
867867
} else {
868868
Optional<Class<?>> testClass = context.getTestClass();

0 commit comments

Comments
 (0)