Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce TestSocketUtils as a replacement for SocketUtils #29132

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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(
sbrannen marked this conversation as resolved.
Show resolved Hide resolved
port, 1, InetAddress.getByName("localhost"));
serverSocket.close();
return true;
}
catch (Exception ex) {
return false;
}
}

}
@@ -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)) {
sbrannen marked this conversation as resolved.
Show resolved Hide resolved
mockedServerSocketFactory.when(ServerSocketFactory::getDefault).thenThrow(new RuntimeException("Boom"));
assertThatIllegalStateException().isThrownBy(TestSocketUtils::findAvailableTcpPort)
.withMessageStartingWith("Could not find an available TCP port")
.withMessageEndingWith("after 1000 attempts");

}
}

}
@@ -0,0 +1 @@
mock-maker-inline