Skip to content

Commit 7d731ee

Browse files
committed
Merge branch '5.3.x'
# Conflicts: # spring-core/src/test/java/org/springframework/util/SocketUtilsTests.java
2 parents 6efc3da + 698f899 commit 7d731ee

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.util;
18+
19+
import java.net.InetAddress;
20+
import java.net.ServerSocket;
21+
import java.util.Random;
22+
23+
import javax.net.ServerSocketFactory;
24+
25+
import org.springframework.util.Assert;
26+
27+
/**
28+
* Simple utility for finding available TCP ports on {@code localhost} for use in
29+
* integration testing scenarios.
30+
*
31+
* <p>This is a limited form of {@link org.springframework.util.SocketUtils} which
32+
* has been deprecated since Spring Framework 5.3.16 and removed in Spring
33+
* Framework 6.0.
34+
*
35+
* <p>{@code TestSocketUtils} can be used in integration tests which start an
36+
* external server on an available random port. However, these utilities make no
37+
* guarantee about the subsequent availability of a given port and are therefore
38+
* unreliable. Instead of using {@code TestSocketUtils} to find an available local
39+
* port for a server, it is recommended that you rely on a server's ability to
40+
* start on a random <em>ephemeral</em> port that it selects or is assigned by the
41+
* operating system. To interact with that server, you should query the server
42+
* for the port it is currently using.
43+
*
44+
* @author Sam Brannen
45+
* @author Ben Hale
46+
* @author Arjen Poutsma
47+
* @author Gunnar Hillert
48+
* @author Gary Russell
49+
* @author Chris Bono
50+
* @since 5.3.24
51+
*/
52+
public class TestSocketUtils {
53+
54+
/**
55+
* The minimum value for port ranges used when finding an available TCP port.
56+
*/
57+
static final int PORT_RANGE_MIN = 1024;
58+
59+
/**
60+
* The maximum value for port ranges used when finding an available TCP port.
61+
*/
62+
static final int PORT_RANGE_MAX = 65535;
63+
64+
private static final int PORT_RANGE_PLUS_ONE = PORT_RANGE_MAX - PORT_RANGE_MIN + 1;
65+
66+
private static final int MAX_ATTEMPTS = 1_000;
67+
68+
private static final Random random = new Random(System.nanoTime());
69+
70+
private static final TestSocketUtils INSTANCE = new TestSocketUtils();
71+
72+
73+
/**
74+
* Although {@code TestSocketUtils} consists solely of static utility methods,
75+
* this constructor is intentionally {@code public}.
76+
* <h4>Rationale</h4>
77+
* <p>Static methods from this class may be invoked from within XML
78+
* configuration files using the Spring Expression Language (SpEL) and the
79+
* following syntax.
80+
* <pre><code>
81+
* &lt;bean id="myBean" ... p:port="#{T(org.springframework.test.util.TestSocketUtils).findAvailableTcpPort()}" /&gt;</code>
82+
* </pre>
83+
* <p>If this constructor were {@code private}, you would be required to supply
84+
* the fully qualified class name to SpEL's {@code T()} function for each usage.
85+
* Thus, the fact that this constructor is {@code public} allows you to reduce
86+
* boilerplate configuration with SpEL as can be seen in the following example.
87+
* <pre><code>
88+
* &lt;bean id="socketUtils" class="org.springframework.test.util.TestSocketUtils" /&gt;
89+
* &lt;bean id="myBean" ... p:port="#{socketUtils.findAvailableTcpPort()}" /&gt;</code>
90+
* </pre>
91+
*/
92+
public TestSocketUtils() {
93+
}
94+
95+
/**
96+
* Find an available TCP port randomly selected from the range [1024, 65535].
97+
* @return an available TCP port number
98+
* @throws IllegalStateException if no available port could be found
99+
*/
100+
public static int findAvailableTcpPort() {
101+
return INSTANCE.findAvailableTcpPortInternal();
102+
}
103+
104+
105+
/**
106+
* Internal implementation of {@link #findAvailableTcpPort()}.
107+
* <p>Package-private solely for testing purposes.
108+
*/
109+
int findAvailableTcpPortInternal() {
110+
int candidatePort;
111+
int searchCounter = 0;
112+
do {
113+
Assert.state(++searchCounter <= MAX_ATTEMPTS, () -> String.format(
114+
"Could not find an available TCP port in the range [%d, %d] after %d attempts",
115+
PORT_RANGE_MIN, PORT_RANGE_MAX, MAX_ATTEMPTS));
116+
candidatePort = PORT_RANGE_MIN + random.nextInt(PORT_RANGE_PLUS_ONE);
117+
}
118+
while (!isPortAvailable(candidatePort));
119+
120+
return candidatePort;
121+
}
122+
123+
/**
124+
* Determine if the specified TCP port is currently available on {@code localhost}.
125+
* <p>Package-private solely for testing purposes.
126+
*/
127+
boolean isPortAvailable(int port) {
128+
try {
129+
ServerSocket serverSocket = ServerSocketFactory.getDefault()
130+
.createServerSocket(port, 1, InetAddress.getByName("localhost"));
131+
serverSocket.close();
132+
return true;
133+
}
134+
catch (Exception ex) {
135+
return false;
136+
}
137+
}
138+
139+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.util;
18+
19+
import org.junit.jupiter.api.RepeatedTest;
20+
import org.junit.jupiter.api.Test;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
24+
25+
/**
26+
* Unit tests for {@link TestSocketUtils}.
27+
*
28+
* @author Sam Brannen
29+
* @author Gary Russell
30+
* @since 5.3.24
31+
*/
32+
class TestSocketUtilsTests {
33+
34+
@Test
35+
void canBeInstantiated() {
36+
// Just making sure somebody doesn't try to make SocketUtils abstract,
37+
// since that would be a breaking change due to the intentional public
38+
// constructor.
39+
new TestSocketUtils();
40+
}
41+
42+
@RepeatedTest(10)
43+
void findAvailableTcpPort() {
44+
assertThat(TestSocketUtils.findAvailableTcpPort())
45+
.isBetween(TestSocketUtils.PORT_RANGE_MIN, TestSocketUtils.PORT_RANGE_MAX);
46+
}
47+
48+
@Test
49+
void findAvailableTcpPortWhenNoAvailablePortFoundInMaxAttempts() {
50+
TestSocketUtils socketUtils = new TestSocketUtils() {
51+
@Override
52+
boolean isPortAvailable(int port) {
53+
return false;
54+
}
55+
};
56+
assertThatIllegalStateException()
57+
.isThrownBy(socketUtils::findAvailableTcpPortInternal)
58+
.withMessage("Could not find an available TCP port in the range [1024, 65535] after 1000 attempts");
59+
}
60+
61+
}

0 commit comments

Comments
 (0)