Skip to content

SLCORE-1555 Run medium tests in parallel #1438

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

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions medium-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkCount>1C</forkCount>
<reuseForks>true</reuseForks>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ void it_should_fail_when_rule_key_unknown_and_project_is_not_bound(SonarLintTest

var futureResponse = backend.getRulesService().getEffectiveRuleDetails(new GetEffectiveRuleDetailsParams("scopeId", "python:SXXXX", null));

assertThat(futureResponse).failsWithin(1, TimeUnit.SECONDS)
assertThat(futureResponse).failsWithin(2, TimeUnit.SECONDS)
.withThrowableOfType(ExecutionException.class)
.withCauseInstanceOf(ResponseErrorException.class)
.withMessageContaining("Could not find rule 'python:SXXXX' in embedded rules");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;

class ExtraEnabledLanguagesInConnectedModePromotionMediumTests {
@RegisterExtension
Expand All @@ -58,7 +57,6 @@ void it_should_notify_clients_for_a_detected_language_that_is_enabled_only_in_co
var backend = harness.newBackend()
.withExtraEnabledLanguagesInConnectedMode(Language.ABAP)
.withUnboundConfigScope("configScopeId")
.withBackendCapability(EMBEDDED_SERVER)
.withTelemetryEnabled()
.start(fakeClient);

Expand All @@ -83,7 +81,6 @@ void it_should_not_notify_clients_when_already_in_connected_mode(SonarLintTestHa
.withExtraEnabledLanguagesInConnectedMode(Language.ABAP)
.withSonarQubeConnection("connectionId", server, storage -> storage.withProject("projectKey", project -> project.withRuleSet("abap", ruleSet -> ruleSet.withActiveRule("abap:S100", "MAJOR")).withMainBranch("main")))
.withBoundConfigScope("configScopeId", "connectionId", "projectKey")
.withBackendCapability(EMBEDDED_SERVER)
.withTelemetryEnabled()
.start(fakeClient);

Expand All @@ -104,7 +101,6 @@ void it_should_not_notify_clients_when_detected_language_is_not_an_extra_languag
var backend = harness.newBackend()
.withEnabledLanguageInStandaloneMode(Language.ABAP)
.withUnboundConfigScope("configScopeId")
.withBackendCapability(EMBEDDED_SERVER)
.withTelemetryEnabled()
.start(fakeClient);

Expand All @@ -125,7 +121,6 @@ void it_should_not_notify_clients_when_no_language_was_detected_during_analysis(
.build();
var backend = harness.newBackend()
.withUnboundConfigScope("configScopeId")
.withBackendCapability(EMBEDDED_SERVER)
.withTelemetryEnabled()
.start(fakeClient);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ void it_should_skip_polling_notifications_when_sonarcloud_websocket_opened(Sonar

harness.newBackend()
.withSonarQubeCloudEuRegionUri(mockWebServerExtension.endpointParams().getBaseUrl())
.withSonarQubeCloudEuRegionWebSocketUri(webSocketServer.getUrl())
.withSonarQubeCloudEuRegionWebSocketUri(webSocketServer.getUri())
.withSonarCloudConnectionAndNotifications(CONNECTION_ID, "myOrg", storage -> storage.withProject(PROJECT_KEY, project -> project.withLastSmartNotificationPoll(STORED_DATE)))
.withBoundConfigScope("scopeId", CONNECTION_ID, PROJECT_KEY)
.withBackendCapability(SMART_NOTIFICATIONS, SERVER_SENT_EVENTS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class WebSocketMediumTests {
void prepare() {
webSocketServerEU = new WebSocketServer();
webSocketServerEU.start();
webSocketServerUS = new WebSocketServer(WebSocketServer.DEFAULT_PORT + 1);
webSocketServerUS = new WebSocketServer();
webSocketServerUS.start();
}

Expand Down Expand Up @@ -512,9 +512,9 @@ void should_log_failure_and_reconnect_later_if_server_unavailable(SonarLintTestH
backend.getConnectionService()
.didUpdateConnections(new DidUpdateConnectionsParams(emptyList(), List.of(new SonarCloudConnectionConfigurationDto("connectionId", "orgKey", SonarCloudRegion.EU, false))));

await().untilAsserted(() -> assertThat(client.getLogMessages()).contains("Error while trying to create websocket connection for ws://localhost:54321/endpoint"));
await().untilAsserted(() -> assertThat(client.getLogMessages()).contains("Error while trying to create websocket connection for " + webSocketServerEU.getUri()));

webSocketServerEU.start();
webSocketServerEU.restart();
// Emulate a change on the connection to force websocket service to reconnect
backend.getConnectionService().didChangeCredentials(new DidChangeCredentialsParams("connectionId"));

Expand Down Expand Up @@ -1493,8 +1493,8 @@ void should_send_one_subscribe_message_per_project_key_when_reopening_connection
public SonarLintBackendFixture.SonarLintBackendBuilder newBackendWithWebSockets(SonarLintTestHarness harness) {
return harness.newBackend()
.withBackendCapability(SERVER_SENT_EVENTS)
.withSonarQubeCloudEuRegionWebSocketUri(webSocketServerEU.getUrl())
.withSonarQubeCloudUsRegionWebSocketUri(webSocketServerUS.getUrl());
.withSonarQubeCloudEuRegionWebSocketUri(webSocketServerEU.getUri())
.withSonarQubeCloudUsRegionWebSocketUri(webSocketServerUS.getUri());
}

public static class WebSocketPayloadBuilder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,14 @@ public static class SonarLintBackendBuilder {
@Nullable
private String euRegionApiUri;
@Nullable
private String euRegionWebSocketUri;
private URI euRegionWebSocketUri;

@Nullable
private String usRegionUri;
@Nullable
private String usRegionApiUri;
@Nullable
private String usRegionWebSocketUri;
private URI usRegionWebSocketUri;

private Duration responseTimeout;
private Path keyStorePath;
Expand Down Expand Up @@ -253,7 +253,7 @@ public SonarLintBackendBuilder withSonarQubeCloudEuRegionApiUri(String euRegionA
return this;
}

public SonarLintBackendBuilder withSonarQubeCloudEuRegionWebSocketUri(String euRegionWebSocketUri) {
public SonarLintBackendBuilder withSonarQubeCloudEuRegionWebSocketUri(URI euRegionWebSocketUri) {
this.euRegionWebSocketUri = euRegionWebSocketUri;
return this;
}
Expand All @@ -268,7 +268,7 @@ public SonarLintBackendBuilder withSonarQubeCloudUsRegionApiUri(String usRegionA
return this;
}

public SonarLintBackendBuilder withSonarQubeCloudUsRegionWebSocketUri(String usRegionWebSocketUri) {
public SonarLintBackendBuilder withSonarQubeCloudUsRegionWebSocketUri(URI usRegionWebSocketUri) {
this.usRegionWebSocketUri = usRegionWebSocketUri;
return this;
}
Expand Down Expand Up @@ -496,9 +496,9 @@ public SonarLintTestRpcServer start(SonarLintRpcClientDelegate client) {
// If more regions are added in the future, extend this by adding a new entry set and add the fields / methods above!
var sonarCloudAlternativeEnvironment = new SonarCloudAlternativeEnvironmentDto(Map.of(
SonarCloudRegion.EU,
new SonarQubeCloudRegionDto(createUriFromString(euRegionUri), createUriFromString(euRegionApiUri), createUriFromString(euRegionWebSocketUri)),
new SonarQubeCloudRegionDto(createUriFromString(euRegionUri), createUriFromString(euRegionApiUri), euRegionWebSocketUri),
SonarCloudRegion.US,
new SonarQubeCloudRegionDto(createUriFromString(usRegionUri), createUriFromString(usRegionApiUri), createUriFromString(usRegionWebSocketUri))));
new SonarQubeCloudRegionDto(createUriFromString(usRegionUri), createUriFromString(usRegionApiUri), usRegionWebSocketUri)));

var sslConfiguration = new SslConfigurationDto(null, null, null, keyStorePath, keyStorePassword, keyStoreType);
var httpConfiguration = new HttpConfigurationDto(sslConfiguration, null, null, null, responseTimeout);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* SonarLint Core - Test Utils
* Copyright (C) 2016-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarsource.sonarlint.core.test.utils.server;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Set;

public final class NetworkUtils {

private static final Set<Integer> ALREADY_ALLOCATED = new HashSet<>();
private static final int MAX_TRIES = 50;

private static final Set<Integer> HTTP_BLOCKED_PORTS = Set.of(2_049, 4_045, 6_000);

private NetworkUtils() {
// prevent instantiation
}

public static int getNextAvailablePort() {
return getNextAvailablePort(getLocalhost());
}

static int getNextAvailablePort(InetAddress inetAddress) {
return getNextAvailablePort(inetAddress, new PortAllocator());
}

private static InetAddress getLocalhost() {
try {
return InetAddress.getLocalHost();
} catch (UnknownHostException e) {
throw new IllegalStateException("Fail to get localhost IP", e);
}
}

static int getNextAvailablePort(InetAddress address, PortAllocator portAllocator) {
for (var i = 0; i < MAX_TRIES; i++) {
int port = portAllocator.getAvailable(address);
if (isValidPort(port)) {
ALREADY_ALLOCATED.add(port);
return port;
}
}
throw new IllegalStateException("Fail to find an available port on " + address);
}

private static boolean isValidPort(int port) {
return port > 1023 && !HTTP_BLOCKED_PORTS.contains(port) && !ALREADY_ALLOCATED.contains(port);
}

static class PortAllocator {

int getAvailable(InetAddress address) {
try (var socket = new ServerSocket(0, 50, address)) {
return socket.getLocalPort();
} catch (IOException e) {
throw new IllegalStateException("Fail to find an available port on " + address, e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.github.tomakehurst.wiremock.matching.AnythingPattern;
import com.google.protobuf.Message;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
Expand Down Expand Up @@ -775,6 +776,7 @@ public static class Server {
private final boolean serverSentEventsEnabled;
private final Set<String> features;
private SSEServer sseServer;
private URI sseServerUri;

public Server(ServerKind serverKind, ServerStatus serverStatus, @Nullable String version,
Map<String, SonarQubeCloudBuilder.SonarQubeCloudOrganizationBuilder> organizationsByKey, Map<String, AbstractServerBuilder.ServerProjectBuilder> projectsByProjectKey,
Expand All @@ -800,7 +802,7 @@ public void start() {
mockServer.start();
if (serverSentEventsEnabled) {
sseServer = new SSEServer();
sseServer.start();
sseServerUri = sseServer.start();
}
registerWebApiResponses();
}
Expand Down Expand Up @@ -1508,7 +1510,7 @@ private void registerPushApiResponses() {
.withQueryParam("languages", new AnythingPattern())
.willReturn(aResponse()
.withStatus(302)
.withHeader("Location", sseServer.getUrl())));
.withHeader("Location", sseServerUri.toString())));
}

private void registerFeaturesApiResponses() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,30 @@
package org.sonarsource.sonarlint.core.test.utils.server.sse;

import java.io.File;
import java.net.URI;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.sonarsource.sonarlint.core.test.utils.server.NetworkUtils;

public class SSEServer {

public static final int DEFAULT_PORT = 54321;
private Tomcat tomcat;
private SSEServlet sseServlet;

public void start() {
public URI start() {
try {
var baseDir = new File("").getAbsoluteFile().getParentFile().getPath();
tomcat = new Tomcat();
tomcat.setBaseDir(baseDir);
tomcat.setPort(DEFAULT_PORT);
var port = NetworkUtils.getNextAvailablePort();
tomcat.setPort(port);
var context = tomcat.addContext("", baseDir);
sseServlet = new SSEServlet();
Tomcat.addServlet(context, "sse", sseServlet).addMapping("/");
// needed to start the endpoint
tomcat.getConnector();
tomcat.start();
return URI.create("http://localhost:" + port);
} catch (LifecycleException e) {
throw new IllegalStateException(e);
}
Expand All @@ -55,10 +58,6 @@ public void stop() {
}
}

public String getUrl() {
return "http://localhost:" + DEFAULT_PORT;
}

public void sendEventToAllClients(String eventPayload) {
sseServlet.sendEventToAllClients(eventPayload);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,35 @@
package org.sonarsource.sonarlint.core.test.utils.server.websockets;

import java.io.File;
import java.net.URI;
import java.util.List;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.servlets.DefaultServlet;
import org.apache.catalina.startup.Tomcat;
import org.sonarsource.sonarlint.core.test.utils.server.NetworkUtils;

public class WebSocketServer {

public static final int DEFAULT_PORT = 54321;
public static final String CONNECTION_REPOSITORY_ATTRIBUTE_KEY = "connectionRepository";
private Tomcat tomcat;
private WebSocketConnectionRepository connectionRepository;
private final int port;
private int port = -1;

public WebSocketServer(int port) {
this.port = port;
public void start() {
start(NetworkUtils.getNextAvailablePort());
}

public WebSocketServer() {
this(DEFAULT_PORT);
public void restart() {
start(port);
}

public void start() {
private void start(int port) {
try {
var baseDir = new File("").getAbsoluteFile().getParentFile().getPath();
tomcat = new Tomcat();
tomcat.setBaseDir(baseDir);
tomcat.setPort(port);
this.port = port;
var context = tomcat.addContext("", baseDir);
connectionRepository = new WebSocketConnectionRepository();
context.getServletContext().setAttribute(CONNECTION_REPOSITORY_ATTRIBUTE_KEY, connectionRepository);
Expand All @@ -69,8 +71,8 @@ public void stop() {
}
}

public String getUrl() {
return "ws://localhost:" + port + "/endpoint";
public URI getUri() {
return URI.create("ws://localhost:" + port + "/endpoint");
}

public List<WebSocketConnection> getConnections() {
Expand Down
Loading
Loading