Skip to content

Commit ac777c7

Browse files
SLCORE-1555 Run medium tests in parallel
1 parent acff1ec commit ac777c7

File tree

11 files changed

+201
-36
lines changed

11 files changed

+201
-36
lines changed

medium-tests/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@
9090

9191
<build>
9292
<plugins>
93+
<plugin>
94+
<groupId>org.apache.maven.plugins</groupId>
95+
<artifactId>maven-surefire-plugin</artifactId>
96+
<configuration>
97+
<forkCount>1C</forkCount>
98+
<reuseForks>true</reuseForks>
99+
</configuration>
100+
</plugin>
93101
<plugin>
94102
<groupId>org.apache.maven.plugins</groupId>
95103
<artifactId>maven-dependency-plugin</artifactId>

medium-tests/src/test/java/mediumtest/EffectiveRulesMediumTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ void it_should_fail_when_rule_key_unknown_and_project_is_not_bound(SonarLintTest
125125

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

128-
assertThat(futureResponse).failsWithin(1, TimeUnit.SECONDS)
128+
assertThat(futureResponse).failsWithin(2, TimeUnit.SECONDS)
129129
.withThrowableOfType(ExecutionException.class)
130130
.withCauseInstanceOf(ResponseErrorException.class)
131131
.withMessageContaining("Could not find rule 'python:SXXXX' in embedded rules");

medium-tests/src/test/java/mediumtest/promotion/ExtraEnabledLanguagesInConnectedModePromotionMediumTests.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import static org.mockito.Mockito.after;
4343
import static org.mockito.Mockito.never;
4444
import static org.mockito.Mockito.verify;
45-
import static org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.BackendCapability.EMBEDDED_SERVER;
4645

4746
class ExtraEnabledLanguagesInConnectedModePromotionMediumTests {
4847
@RegisterExtension
@@ -58,7 +57,6 @@ void it_should_notify_clients_for_a_detected_language_that_is_enabled_only_in_co
5857
var backend = harness.newBackend()
5958
.withExtraEnabledLanguagesInConnectedMode(Language.ABAP)
6059
.withUnboundConfigScope("configScopeId")
61-
.withBackendCapability(EMBEDDED_SERVER)
6260
.withTelemetryEnabled()
6361
.start(fakeClient);
6462

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

@@ -104,7 +101,6 @@ void it_should_not_notify_clients_when_detected_language_is_not_an_extra_languag
104101
var backend = harness.newBackend()
105102
.withEnabledLanguageInStandaloneMode(Language.ABAP)
106103
.withUnboundConfigScope("configScopeId")
107-
.withBackendCapability(EMBEDDED_SERVER)
108104
.withTelemetryEnabled()
109105
.start(fakeClient);
110106

@@ -125,7 +121,6 @@ void it_should_not_notify_clients_when_no_language_was_detected_during_analysis(
125121
.build();
126122
var backend = harness.newBackend()
127123
.withUnboundConfigScope("configScopeId")
128-
.withBackendCapability(EMBEDDED_SERVER)
129124
.withTelemetryEnabled()
130125
.start(fakeClient);
131126

medium-tests/src/test/java/mediumtest/smartnotifications/SmartNotificationsMediumTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ void it_should_skip_polling_notifications_when_sonarcloud_websocket_opened(Sonar
287287

288288
harness.newBackend()
289289
.withSonarQubeCloudEuRegionUri(mockWebServerExtension.endpointParams().getBaseUrl())
290-
.withSonarQubeCloudEuRegionWebSocketUri(webSocketServer.getUrl())
290+
.withSonarQubeCloudEuRegionWebSocketUri(webSocketServer.getUri())
291291
.withSonarCloudConnectionAndNotifications(CONNECTION_ID, "myOrg", storage -> storage.withProject(PROJECT_KEY, project -> project.withLastSmartNotificationPoll(STORED_DATE)))
292292
.withBoundConfigScope("scopeId", CONNECTION_ID, PROJECT_KEY)
293293
.withBackendCapability(SMART_NOTIFICATIONS, SERVER_SENT_EVENTS)

medium-tests/src/test/java/mediumtest/websockets/WebSocketMediumTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class WebSocketMediumTests {
7474
void prepare() {
7575
webSocketServerEU = new WebSocketServer();
7676
webSocketServerEU.start();
77-
webSocketServerUS = new WebSocketServer(WebSocketServer.DEFAULT_PORT + 1);
77+
webSocketServerUS = new WebSocketServer();
7878
webSocketServerUS.start();
7979
}
8080

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

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

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

@@ -1493,8 +1493,8 @@ void should_send_one_subscribe_message_per_project_key_when_reopening_connection
14931493
public SonarLintBackendFixture.SonarLintBackendBuilder newBackendWithWebSockets(SonarLintTestHarness harness) {
14941494
return harness.newBackend()
14951495
.withBackendCapability(SERVER_SENT_EVENTS)
1496-
.withSonarQubeCloudEuRegionWebSocketUri(webSocketServerEU.getUrl())
1497-
.withSonarQubeCloudUsRegionWebSocketUri(webSocketServerUS.getUrl());
1496+
.withSonarQubeCloudEuRegionWebSocketUri(webSocketServerEU.getUri())
1497+
.withSonarQubeCloudUsRegionWebSocketUri(webSocketServerUS.getUri());
14981498
}
14991499

15001500
public static class WebSocketPayloadBuilder {

test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/SonarLintBackendFixture.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,14 @@ public static class SonarLintBackendBuilder {
162162
@Nullable
163163
private String euRegionApiUri;
164164
@Nullable
165-
private String euRegionWebSocketUri;
165+
private URI euRegionWebSocketUri;
166166

167167
@Nullable
168168
private String usRegionUri;
169169
@Nullable
170170
private String usRegionApiUri;
171171
@Nullable
172-
private String usRegionWebSocketUri;
172+
private URI usRegionWebSocketUri;
173173

174174
private Duration responseTimeout;
175175
private Path keyStorePath;
@@ -253,7 +253,7 @@ public SonarLintBackendBuilder withSonarQubeCloudEuRegionApiUri(String euRegionA
253253
return this;
254254
}
255255

256-
public SonarLintBackendBuilder withSonarQubeCloudEuRegionWebSocketUri(String euRegionWebSocketUri) {
256+
public SonarLintBackendBuilder withSonarQubeCloudEuRegionWebSocketUri(URI euRegionWebSocketUri) {
257257
this.euRegionWebSocketUri = euRegionWebSocketUri;
258258
return this;
259259
}
@@ -268,7 +268,7 @@ public SonarLintBackendBuilder withSonarQubeCloudUsRegionApiUri(String usRegionA
268268
return this;
269269
}
270270

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

503503
var sslConfiguration = new SslConfigurationDto(null, null, null, keyStorePath, keyStorePassword, keyStoreType);
504504
var httpConfiguration = new HttpConfigurationDto(sslConfiguration, null, null, null, responseTimeout);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* SonarLint Core - Test Utils
3+
* Copyright (C) 2016-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonarsource.sonarlint.core.test.utils.server;
21+
22+
import java.io.IOException;
23+
import java.net.InetAddress;
24+
import java.net.ServerSocket;
25+
import java.net.UnknownHostException;
26+
import java.util.HashSet;
27+
import java.util.Set;
28+
29+
public final class NetworkUtils {
30+
31+
private static final Set<Integer> ALREADY_ALLOCATED = new HashSet<>();
32+
private static final int MAX_TRIES = 50;
33+
34+
private static final Set<Integer> HTTP_BLOCKED_PORTS = Set.of(2_049, 4_045, 6_000);
35+
36+
private NetworkUtils() {
37+
// prevent instantiation
38+
}
39+
40+
public static int getNextAvailablePort() {
41+
return getNextAvailablePort(getLocalhost());
42+
}
43+
44+
static int getNextAvailablePort(InetAddress inetAddress) {
45+
return getNextAvailablePort(inetAddress, new PortAllocator());
46+
}
47+
48+
private static InetAddress getLocalhost() {
49+
try {
50+
return InetAddress.getLocalHost();
51+
} catch (UnknownHostException e) {
52+
throw new IllegalStateException("Fail to get localhost IP", e);
53+
}
54+
}
55+
56+
static int getNextAvailablePort(InetAddress address, PortAllocator portAllocator) {
57+
for (var i = 0; i < MAX_TRIES; i++) {
58+
int port = portAllocator.getAvailable(address);
59+
if (isValidPort(port)) {
60+
ALREADY_ALLOCATED.add(port);
61+
return port;
62+
}
63+
}
64+
throw new IllegalStateException("Fail to find an available port on " + address);
65+
}
66+
67+
private static boolean isValidPort(int port) {
68+
return port > 1023 && !HTTP_BLOCKED_PORTS.contains(port) && !ALREADY_ALLOCATED.contains(port);
69+
}
70+
71+
static class PortAllocator {
72+
73+
int getAvailable(InetAddress address) {
74+
try (var socket = new ServerSocket(0, 50, address)) {
75+
return socket.getLocalPort();
76+
} catch (IOException e) {
77+
throw new IllegalStateException("Fail to find an available port on " + address, e);
78+
}
79+
}
80+
}
81+
}

test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/ServerFixture.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.github.tomakehurst.wiremock.matching.AnythingPattern;
2828
import com.google.protobuf.Message;
2929
import java.io.IOException;
30+
import java.net.URI;
3031
import java.nio.file.Files;
3132
import java.nio.file.Path;
3233
import java.time.Instant;
@@ -775,6 +776,7 @@ public static class Server {
775776
private final boolean serverSentEventsEnabled;
776777
private final Set<String> features;
777778
private SSEServer sseServer;
779+
private URI sseServerUri;
778780

779781
public Server(ServerKind serverKind, ServerStatus serverStatus, @Nullable String version,
780782
Map<String, SonarQubeCloudBuilder.SonarQubeCloudOrganizationBuilder> organizationsByKey, Map<String, AbstractServerBuilder.ServerProjectBuilder> projectsByProjectKey,
@@ -800,7 +802,7 @@ public void start() {
800802
mockServer.start();
801803
if (serverSentEventsEnabled) {
802804
sseServer = new SSEServer();
803-
sseServer.start();
805+
sseServerUri = sseServer.start();
804806
}
805807
registerWebApiResponses();
806808
}
@@ -1508,7 +1510,7 @@ private void registerPushApiResponses() {
15081510
.withQueryParam("languages", new AnythingPattern())
15091511
.willReturn(aResponse()
15101512
.withStatus(302)
1511-
.withHeader("Location", sseServer.getUrl())));
1513+
.withHeader("Location", sseServerUri.toString())));
15121514
}
15131515

15141516
private void registerFeaturesApiResponses() {

test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/sse/SSEServer.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,30 @@
2020
package org.sonarsource.sonarlint.core.test.utils.server.sse;
2121

2222
import java.io.File;
23+
import java.net.URI;
2324
import org.apache.catalina.LifecycleException;
2425
import org.apache.catalina.startup.Tomcat;
26+
import org.sonarsource.sonarlint.core.test.utils.server.NetworkUtils;
2527

2628
public class SSEServer {
2729

28-
public static final int DEFAULT_PORT = 54321;
2930
private Tomcat tomcat;
3031
private SSEServlet sseServlet;
3132

32-
public void start() {
33+
public URI start() {
3334
try {
3435
var baseDir = new File("").getAbsoluteFile().getParentFile().getPath();
3536
tomcat = new Tomcat();
3637
tomcat.setBaseDir(baseDir);
37-
tomcat.setPort(DEFAULT_PORT);
38+
var port = NetworkUtils.getNextAvailablePort();
39+
tomcat.setPort(port);
3840
var context = tomcat.addContext("", baseDir);
3941
sseServlet = new SSEServlet();
4042
Tomcat.addServlet(context, "sse", sseServlet).addMapping("/");
4143
// needed to start the endpoint
4244
tomcat.getConnector();
4345
tomcat.start();
46+
return URI.create("http://localhost:" + port);
4447
} catch (LifecycleException e) {
4548
throw new IllegalStateException(e);
4649
}
@@ -55,10 +58,6 @@ public void stop() {
5558
}
5659
}
5760

58-
public String getUrl() {
59-
return "http://localhost:" + DEFAULT_PORT;
60-
}
61-
6261
public void sendEventToAllClients(String eventPayload) {
6362
sseServlet.sendEventToAllClients(eventPayload);
6463
}

test-utils/src/main/java/org/sonarsource/sonarlint/core/test/utils/server/websockets/WebSocketServer.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,35 @@
2020
package org.sonarsource.sonarlint.core.test.utils.server.websockets;
2121

2222
import java.io.File;
23+
import java.net.URI;
2324
import java.util.List;
2425
import org.apache.catalina.LifecycleException;
2526
import org.apache.catalina.servlets.DefaultServlet;
2627
import org.apache.catalina.startup.Tomcat;
28+
import org.sonarsource.sonarlint.core.test.utils.server.NetworkUtils;
2729

2830
public class WebSocketServer {
2931

30-
public static final int DEFAULT_PORT = 54321;
3132
public static final String CONNECTION_REPOSITORY_ATTRIBUTE_KEY = "connectionRepository";
3233
private Tomcat tomcat;
3334
private WebSocketConnectionRepository connectionRepository;
34-
private final int port;
35+
private int port = -1;
3536

36-
public WebSocketServer(int port) {
37-
this.port = port;
37+
public void start() {
38+
start(NetworkUtils.getNextAvailablePort());
3839
}
3940

40-
public WebSocketServer() {
41-
this(DEFAULT_PORT);
41+
public void restart() {
42+
start(port);
4243
}
4344

44-
public void start() {
45+
private void start(int port) {
4546
try {
4647
var baseDir = new File("").getAbsoluteFile().getParentFile().getPath();
4748
tomcat = new Tomcat();
4849
tomcat.setBaseDir(baseDir);
4950
tomcat.setPort(port);
51+
this.port = port;
5052
var context = tomcat.addContext("", baseDir);
5153
connectionRepository = new WebSocketConnectionRepository();
5254
context.getServletContext().setAttribute(CONNECTION_REPOSITORY_ATTRIBUTE_KEY, connectionRepository);
@@ -69,8 +71,8 @@ public void stop() {
6971
}
7072
}
7173

72-
public String getUrl() {
73-
return "ws://localhost:" + port + "/endpoint";
74+
public URI getUri() {
75+
return URI.create("ws://localhost:" + port + "/endpoint");
7476
}
7577

7678
public List<WebSocketConnection> getConnections() {

0 commit comments

Comments
 (0)