-
Notifications
You must be signed in to change notification settings - Fork 38.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce TestSocketUtils as a replacement for SocketUtils
SocketUtils was officially deprecated in 5.3.16 (gh-28052) and removed in 6.0 M3 (gh-28054); however, there is still need for a subset of this functionality in integration tests for testing scenarios in which it is not possible for the system under test to select its own random port (or rely on the operating system to provide an ephemeral port). This commit therefore introduces a scaled down version in the spring-test module called TestSocketUtils which supports retrieval of a single TCP port. See gh-29132
- Loading branch information
Showing
3 changed files
with
165 additions
and
0 deletions.
There are no files selected for viewing
109 changes: 109 additions & 0 deletions
109
spring-test/src/main/java/org/springframework/test/util/TestSocketUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
* Copyright 2002-2022 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.test.util; | ||
|
||
import java.net.InetAddress; | ||
import java.net.ServerSocket; | ||
import java.util.Random; | ||
|
||
import javax.net.ServerSocketFactory; | ||
|
||
/** | ||
* Simple utility methods for finding available ports on {@code localhost} for | ||
* use in integration testing scenarios. | ||
* | ||
* <p>This is a limited form of {@code SocketUtils} which is deprecated in Spring | ||
* Framework 5.3 and removed in Spring Framework 6.0. | ||
* | ||
* <p>{@code SocketUtils} was introduced in Spring Framework 4.0, primarily to | ||
* assist in writing integration tests which start an external server on an | ||
* available random port. However, these utilities make no guarantee about the | ||
* subsequent availability of a given port and are therefore unreliable (the reason | ||
* for deprecation and removal). | ||
* | ||
* <p>Instead of using {@code TestSocketUtils} to find an available local port for a server, | ||
* it is recommended that you rely on a server's ability to start on a random port | ||
* that it selects or is assigned by the operating system. To interact with that | ||
* server, you should query the server for the port it is currently using. | ||
* | ||
* @author Sam Brannen | ||
* @author Ben Hale | ||
* @author Arjen Poutsma | ||
* @author Gunnar Hillert | ||
* @author Gary Russell | ||
* @author Chris Bono | ||
* @since 5.3 | ||
*/ | ||
public final class TestSocketUtils { | ||
|
||
/** | ||
* The minimum value for port ranges used when finding an available TCP port. | ||
*/ | ||
private static final int PORT_RANGE_MIN = 1024; | ||
|
||
/** | ||
* The maximum value for port ranges used when finding an available TCP port. | ||
*/ | ||
private static final int PORT_RANGE_MAX = 65535; | ||
|
||
private static final int PORT_RANGE = PORT_RANGE_MAX - PORT_RANGE_MIN; | ||
|
||
private static final int MAX_ATTEMPTS = 1_000; | ||
|
||
private static final Random random = new Random(System.nanoTime()); | ||
|
||
private TestSocketUtils() { | ||
} | ||
|
||
/** | ||
* Find an available TCP port randomly selected from the range [1024, 65535]. | ||
* @return an available TCP port number | ||
* @throws IllegalStateException if no available port could be found within max attempts | ||
*/ | ||
public static int findAvailableTcpPort() { | ||
int candidatePort; | ||
int searchCounter = 0; | ||
do { | ||
if (searchCounter > MAX_ATTEMPTS) { | ||
throw new IllegalStateException(String.format( | ||
"Could not find an available TCP port in the range [%d, %d] after %d attempts", | ||
PORT_RANGE_MIN, PORT_RANGE_MAX, MAX_ATTEMPTS)); | ||
} | ||
candidatePort = PORT_RANGE_MIN + random.nextInt(PORT_RANGE + 1); | ||
searchCounter++; | ||
} | ||
while (!isPortAvailable(candidatePort)); | ||
|
||
return candidatePort; | ||
} | ||
|
||
/** | ||
* Determine if the specified TCP port is currently available on {@code localhost}. | ||
*/ | ||
private static boolean isPortAvailable(int port) { | ||
try { | ||
ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket( | ||
port, 1, InetAddress.getByName("localhost")); | ||
serverSocket.close(); | ||
return true; | ||
} | ||
catch (Exception ex) { | ||
return false; | ||
} | ||
} | ||
|
||
} |
55 changes: 55 additions & 0 deletions
55
spring-test/src/test/java/org/springframework/test/util/TestSocketUtilsTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* Copyright 2002-2022 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.test.util; | ||
|
||
import javax.net.ServerSocketFactory; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.mockito.MockedStatic; | ||
import org.mockito.Mockito; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; | ||
|
||
/** | ||
* Unit tests for {@link TestSocketUtils}. | ||
* | ||
* @author Sam Brannen | ||
* @author Gary Russell | ||
* @author Chris Bono | ||
*/ | ||
class TestSocketUtilsTests { | ||
|
||
@Test | ||
void findAvailableTcpPort() { | ||
int port = TestSocketUtils.findAvailableTcpPort(); | ||
assertThat(port >= 1024).as("port [" + port + "] >= " + 1024).isTrue(); | ||
assertThat(port <= 65535).as("port [" + port + "] <= " + 65535).isTrue(); | ||
} | ||
|
||
@Test | ||
void findAvailableTcpPortWhenNoAvailablePortFoundInMaxAttempts() { | ||
try (MockedStatic<ServerSocketFactory> mockedServerSocketFactory = Mockito.mockStatic(ServerSocketFactory.class)) { | ||
mockedServerSocketFactory.when(ServerSocketFactory::getDefault).thenThrow(new RuntimeException("Boom")); | ||
assertThatIllegalStateException().isThrownBy(TestSocketUtils::findAvailableTcpPort) | ||
.withMessageStartingWith("Could not find an available TCP port") | ||
.withMessageEndingWith("after 1000 attempts"); | ||
|
||
} | ||
} | ||
|
||
} |
1 change: 1 addition & 0 deletions
1
spring-test/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
mock-maker-inline |