Skip to content

Commit b3f00f2

Browse files
committed
loadtest-controller: fix test with multiple test cases erroring on the second one
1 parent 22c2c82 commit b3f00f2

5 files changed

Lines changed: 166 additions & 22 deletions

File tree

loadtest-controller/src/main/java/io/openvidu/loadtest/models/testcase/TestCase.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public TestCase(TestCase testCase) {
3939
this.resolution = testCase.resolution;
4040
this.frameRate = testCase.frameRate;
4141
this.openviduRecordingMode = testCase.openviduRecordingMode;
42+
// Ensure browser is copied when creating a defensive copy of TestCase
43+
this.browser = testCase.browser;
4244
this.browserRecording = testCase.browserRecording;
4345
this.headlessBrowser = testCase.headlessBrowser;
4446
this.showBrowserVideoElements = testCase.showBrowserVideoElements;

loadtest-controller/src/main/java/io/openvidu/loadtest/services/BrowserEmulatorClient.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,21 @@ public BrowserEmulatorClient(LoadTestConfig loadTestConfig, CustomHttpClient htt
120120
this.httpProtocolPrefix = loadTestConfig.isHttpsDisabled() ? "http://" : "https://";
121121
}
122122

123-
public void clean() {
124-
this.isClean.set(true);
125-
this.clientFailures.clear();
126-
this.clientRetryAttempts.clear();
127-
this.clientRoles.clear();
128-
this.participantTestCases.clear();
129-
this.participantConnecting.clear();
130-
this.participantReconnecting.clear();
131-
this.userDisconnectTimestamps.clear();
132-
this.isClean.set(false);
133-
}
123+
public void clean() {
124+
this.isClean.set(true);
125+
this.clientFailures.clear();
126+
this.clientRetryAttempts.clear();
127+
this.clientRoles.clear();
128+
this.participantTestCases.clear();
129+
this.participantConnecting.clear();
130+
this.participantReconnecting.clear();
131+
this.userDisconnectTimestamps.clear();
132+
// Clear static collections that hold per-worker state so subsequent test
133+
// cases don't observe accumulated data from previous runs.
134+
BrowserEmulatorClient.publishersAndSubscribersInWorker.clear();
135+
BrowserEmulatorClient.recordingParticipantCreated.clear();
136+
this.isClean.set(false);
137+
}
134138

135139
public void addDisconnectTimestamp(String userId, String sessionId) {
136140
String key = userId + "-" + sessionId;

loadtest-controller/src/test/java/io/openvidu/loadtest/integration/MultiTestCaseIntegrationTest.java

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
import org.jsoup.Jsoup;
1616
import org.jsoup.nodes.Document;
1717
import org.jsoup.select.Elements;
18+
import com.fasterxml.jackson.databind.ObjectMapper;
19+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
20+
import com.fasterxml.jackson.core.type.TypeReference;
21+
import java.io.InputStream;
22+
import java.util.List;
23+
import java.util.Map;
1824
import org.junit.jupiter.api.AfterAll;
1925
import org.junit.jupiter.api.BeforeAll;
2026
import org.junit.jupiter.api.BeforeEach;
@@ -178,14 +184,93 @@ private void validateMultiTestCaseHtmlReport(Path htmlReport) throws IOException
178184
assertTrue(comparisonRows.size() >= 3,
179185
"Comparison table should have at least 3 rows (one per test case), found: " + comparisonRows.size());
180186

181-
// Log all test case results for visibility
182-
for (var row : comparisonRows) {
183-
Elements cells = row.select("td");
184-
if (cells.size() >= 6) {
185-
String label = cells.get(1).text();
186-
String sessions = cells.get(4).text();
187-
String participants = cells.get(5).text();
188-
log.info("Test case '{}': sessions={}, participants={}", label, sessions, participants);
187+
// Parse the YAML test config to know expected participants for each test case
188+
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
189+
try (InputStream is = getClass().getClassLoader()
190+
.getResourceAsStream("integration/config/multi-test-config.yaml")) {
191+
Map<String, Object> cfg = mapper.readValue(is, new TypeReference<Map<String, Object>>() {
192+
});
193+
List<Map<String, Object>> testcases = (List<Map<String, Object>>) cfg.get("testcases");
194+
195+
// Global capacity: distribution.usersPerWorker (if present)
196+
int usersPerWorkerFromCfg = 0;
197+
Object distributionObj = cfg.get("distribution");
198+
if (distributionObj instanceof Map) {
199+
Object upw = ((Map<?, ?>) distributionObj).get("usersPerWorker");
200+
if (upw instanceof Number) {
201+
usersPerWorkerFromCfg = ((Number) upw).intValue();
202+
} else if (upw != null) {
203+
usersPerWorkerFromCfg = Integer.parseInt(upw.toString());
204+
}
205+
}
206+
207+
// For each overview row, assert the participants column matches the expected
208+
// total
209+
int idx = 0;
210+
// Explicit expected totals for the 3 test cases in this config
211+
int[] explicitExpected = new int[] { 25, 215, 303 };
212+
for (var row : comparisonRows) {
213+
Elements cells = row.select("td");
214+
if (cells.size() >= 7 && idx < testcases.size()) {
215+
String label = cells.get(1).text();
216+
String sessionsCell = cells.get(4).text();
217+
String participantsCell = cells.get(5).text();
218+
String workersCell = cells.get(6).text();
219+
// Stop reason is the last column in the overview table
220+
String stopReasonCell = cells.size() >= 9 ? cells.get(8).text() : "";
221+
log.info("Test case '{}': sessions={}, participants={}, workers={}, stopReason={}", label,
222+
sessionsCell, participantsCell, workersCell, stopReasonCell);
223+
224+
// Expected participants per session is declared in config under 'participants'
225+
Map<String, Object> tc = testcases.get(idx);
226+
List<String> participantsList = (List<String>) tc.get("participants");
227+
// We only support single-entry participants in the test config for this
228+
// assertion
229+
String participantsSpec = participantsList.get(0);
230+
231+
// Compute expected per-session participants: sum parts if "a:b" or single
232+
// integer
233+
int expectedPerSession = 0;
234+
if (participantsSpec.contains(":")) {
235+
String[] parts = participantsSpec.split(":");
236+
for (String p : parts) {
237+
expectedPerSession += Integer.parseInt(p.trim());
238+
}
239+
} else {
240+
expectedPerSession = Integer.parseInt(participantsSpec.trim());
241+
}
242+
243+
String[] sessionsParts = sessionsCell.split("/");
244+
int sessionsCreated = Integer.parseInt(sessionsParts[0].trim());
245+
int expectedTotalParticipants = expectedPerSession * sessionsCreated;
246+
247+
// Apply capacity cap if distribution.usersPerWorker is configured
248+
int workersUsed = Integer.parseInt(workersCell.trim());
249+
if (usersPerWorkerFromCfg > 0 && workersUsed > 0) {
250+
expectedTotalParticipants = Math.min(expectedTotalParticipants,
251+
usersPerWorkerFromCfg * workersUsed);
252+
}
253+
254+
try {
255+
int actualTotal = Integer.parseInt(participantsCell.trim());
256+
assertEquals(expectedTotalParticipants, actualTotal,
257+
"Total participants for test case does not match expected for: " + label);
258+
259+
// Additional explicit checks requested:
260+
if (idx < explicitExpected.length) {
261+
assertEquals(explicitExpected[idx], actualTotal,
262+
"Explicit total participants expected for test case does not match: " + label);
263+
}
264+
265+
// Assert stop reason is exactly "Test finished"
266+
assertEquals("Test finished", stopReasonCell,
267+
"Stop reason should be 'Test finished' for test case: " + label);
268+
} catch (NumberFormatException nfe) {
269+
fail("Participants cell is not a number for test case: " + label + " value: "
270+
+ participantsCell);
271+
}
272+
idx++;
273+
}
189274
}
190275
}
191276

@@ -198,4 +283,4 @@ private void validateMultiTestCaseHtmlReport(Path htmlReport) throws IOException
198283
}
199284
assertTrue(hasOverviewActive, "Overview tab should be active by default");
200285
}
201-
}
286+
}

loadtest-controller/src/test/java/io/openvidu/loadtest/unit/services/BrowserEmulatorClientTests.java

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,4 +645,57 @@ void multipleCleanCallsAllowParticipantCreationTest() throws IOException, Interr
645645
assertTrue(cpr.isResponseOk(), "Participant creation should succeed after multiple clean() calls");
646646
}
647647

648-
}
648+
@Test
649+
void cleanClearsStaticWorkerStateTest() throws Exception {
650+
// Prepare a simple test case and a successful HTTP response to populate
651+
// the static per-worker collections
652+
TestCase testCase = new TestCase("N:N", Arrays.asList("2"), -1,
653+
30, Resolution.MEDIUM, OpenViduRecordingMode.NONE, false, false,
654+
true, Browser.CHROME);
655+
656+
@SuppressWarnings("unchecked")
657+
HttpResponse<String> response = mock(HttpResponse.class);
658+
when(response.statusCode()).thenReturn(200);
659+
JsonObject responseBody = new JsonObject();
660+
responseBody.addProperty("connectionId", "connectionId");
661+
responseBody.addProperty("workerCpuUsage", 0.0);
662+
responseBody.addProperty("streams", 2);
663+
responseBody.addProperty("participants", 1);
664+
responseBody.addProperty("userId", "UserX");
665+
responseBody.addProperty("sessionId", "LoadTestSession0");
666+
String responseString = responseBody.toString();
667+
when(response.body()).thenReturn(responseString);
668+
when(jsonUtilsMock.getJson(responseString)).thenReturn(responseBody);
669+
670+
// Return the same successful response for all create calls
671+
when(this.httpClientMock.sendPost(anyString(), any(), any(), any())).thenReturn(response);
672+
673+
// Create participants that will update the static collections
674+
this.browserEmulatorClient.createPublisher("localhost", 0, 0, testCase);
675+
this.browserEmulatorClient.createSubscriber("localhost", 1, 0, testCase);
676+
this.browserEmulatorClient.createExternalRecordingPublisher("localhost", 2, 0, testCase, "meta");
677+
678+
// Verify the static-backed accessors reflect populated state
679+
assertTrue(this.browserEmulatorClient.isRecordingParticipantCreated(0));
680+
int pubs = this.browserEmulatorClient.getRoleInWorker("localhost",
681+
io.openvidu.loadtest.models.testcase.Role.PUBLISHER);
682+
int subs = this.browserEmulatorClient.getRoleInWorker("localhost",
683+
io.openvidu.loadtest.models.testcase.Role.SUBSCRIBER);
684+
assertTrue(pubs > 0, "There should be at least one publisher recorded for the worker");
685+
assertTrue(subs > 0, "There should be at least one subscriber recorded for the worker");
686+
687+
// Now clean and ensure the static collections were cleared
688+
this.browserEmulatorClient.clean();
689+
690+
assertFalse(this.browserEmulatorClient.isRecordingParticipantCreated(0),
691+
"Recording flag should be cleared after clean()");
692+
int pubsAfter = this.browserEmulatorClient.getRoleInWorker("localhost",
693+
io.openvidu.loadtest.models.testcase.Role.PUBLISHER);
694+
int subsAfter = this.browserEmulatorClient.getRoleInWorker("localhost",
695+
io.openvidu.loadtest.models.testcase.Role.SUBSCRIBER);
696+
// After clean the counts should report zero
697+
assertEquals(0, pubsAfter);
698+
assertEquals(0, subsAfter);
699+
}
700+
701+
}

loadtest-controller/src/test/resources/integration/config/multi-test-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ workers:
3434
- localhost
3535

3636
distribution:
37-
usersPerWorker: 150
37+
usersPerWorker: 500
3838

3939
advanced:
4040
reportOutput:

0 commit comments

Comments
 (0)