...30import org.openqa.selenium.grid.data.CreateSessionResponse;31import org.openqa.selenium.grid.data.DefaultSlotMatcher;32import org.openqa.selenium.grid.data.NewSessionRejectedEvent;33import org.openqa.selenium.grid.data.NewSessionRequestEvent;34import org.openqa.selenium.grid.data.RequestId;35import org.openqa.selenium.grid.data.Session;36import org.openqa.selenium.grid.data.SessionRequestCapability;37import org.openqa.selenium.grid.security.Secret;38import org.openqa.selenium.grid.sessionqueue.NewSessionQueue;39import org.openqa.selenium.grid.data.SessionRequest;40import org.openqa.selenium.grid.sessionqueue.remote.RemoteNewSessionQueue;41import org.openqa.selenium.grid.testing.PassthroughHttpClient;42import org.openqa.selenium.internal.Debug;43import org.openqa.selenium.internal.Either;44import org.openqa.selenium.json.Json;45import org.openqa.selenium.remote.SessionId;46import org.openqa.selenium.remote.http.Contents;47import org.openqa.selenium.remote.http.HttpClient;48import org.openqa.selenium.remote.http.HttpResponse;49import org.openqa.selenium.remote.tracing.DefaultTestTracer;50import org.openqa.selenium.remote.tracing.Tracer;51import java.net.URI;52import java.net.URISyntaxException;53import java.time.Duration;54import java.time.Instant;55import java.util.Collection;56import java.util.LinkedHashSet;57import java.util.List;58import java.util.Map;59import java.util.Optional;60import java.util.Set;61import java.util.UUID;62import java.util.concurrent.Callable;63import java.util.concurrent.CountDownLatch;64import java.util.concurrent.ExecutionException;65import java.util.concurrent.ExecutorService;66import java.util.concurrent.Executors;67import java.util.concurrent.Future;68import java.util.concurrent.TimeoutException;69import java.util.concurrent.atomic.AtomicBoolean;70import java.util.concurrent.atomic.AtomicInteger;71import java.util.concurrent.atomic.AtomicReference;72import java.util.function.Supplier;73import java.util.stream.Collectors;74import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;75import static java.net.HttpURLConnection.HTTP_OK;76import static java.nio.charset.StandardCharsets.UTF_8;77import static java.util.concurrent.TimeUnit.SECONDS;78import static org.assertj.core.api.Assertions.assertThat;79import static org.assertj.core.api.Assertions.fail;80import static org.junit.Assert.assertEquals;81import static org.junit.Assert.assertFalse;82import static org.junit.Assert.assertNotEquals;83import static org.junit.Assert.assertTrue;84import static org.openqa.selenium.remote.Dialect.W3C;85import static org.openqa.selenium.testing.Safely.safelyCall;86@RunWith(Parameterized.class)87public class LocalNewSessionQueueTest {88 @ClassRule89 public static Timeout classTimeout = new Timeout(60, SECONDS);90 private static final Json JSON = new Json();91 private static final Capabilities CAPS = new ImmutableCapabilities("browserName", "cheese");92 private static final Secret REGISTRATION_SECRET = new Secret("secret");93 private static final Instant LONG_AGO = Instant.parse("2007-01-03T21:49:10.00Z"); // Go check the git log94 private final NewSessionQueue queue;95 private final LocalNewSessionQueue localQueue;96 private final EventBus bus;97 private final SessionRequest sessionRequest;98 static class TestData {99 public final EventBus bus;100 public final LocalNewSessionQueue localQueue;101 public final NewSessionQueue queue;102 public TestData(EventBus bus, LocalNewSessionQueue localQueue, NewSessionQueue queue) {103 this.bus = bus;104 this.localQueue = localQueue;105 this.queue = queue;106 }107 }108 public LocalNewSessionQueueTest(Supplier<TestData> supplier) {109 TestData testData = supplier.get();110 this.bus = testData.bus;111 this.queue = testData.queue;112 this.localQueue = testData.localQueue;113 this.sessionRequest = new SessionRequest(114 new RequestId(UUID.randomUUID()),115 Instant.now(),116 Set.of(W3C),117 Set.of(CAPS),118 Map.of(),119 Map.of());120 }121 @Parameterized.Parameters122 public static Collection<Supplier<TestData>> createQueues() {123 Tracer tracer = DefaultTestTracer.createTracer();124 Set<Supplier<TestData>> toReturn = new LinkedHashSet<>();125 // Note: this method is called only once, so if we want each test to126 // be isolated, everything that they use has to be created via the127 // supplier. In particular, a shared event bus will cause weird128 // failures to happen.129 toReturn.add(() -> {130 EventBus bus = new GuavaEventBus();131 LocalNewSessionQueue local = new LocalNewSessionQueue(132 tracer,133 bus,134 new DefaultSlotMatcher(),135 Duration.ofSeconds(1),136 Duration.ofSeconds(Debug.isDebugging() ? 9999 : 5),137 REGISTRATION_SECRET);138 return new TestData(bus, local, local);139 });140 toReturn.add(() -> {141 EventBus bus = new GuavaEventBus();142 LocalNewSessionQueue local = new LocalNewSessionQueue(143 tracer,144 bus,145 new DefaultSlotMatcher(),146 Duration.ofSeconds(1),147 Duration.ofSeconds(Debug.isDebugging() ? 9999 : 5),148 REGISTRATION_SECRET);149 HttpClient client = new PassthroughHttpClient(local);150 return new TestData(bus, local, new RemoteNewSessionQueue(tracer, client, REGISTRATION_SECRET));151 });152 return toReturn;153 }154 @After155 public void shutdownQueue() {156 safelyCall(localQueue::close);157 }158 @Test159 public void shouldBeAbleToAddToQueueAndGetValidResponse() {160 AtomicBoolean isPresent = new AtomicBoolean(false);161 bus.addListener(NewSessionRequestEvent.listener(reqId -> {162 isPresent.set(true);163 Capabilities capabilities = new ImmutableCapabilities("browserName", "chrome");164 SessionId sessionId = new SessionId("123");165 Session session =166 new Session(167 sessionId,168 URI.create("http://example.com"),169 CAPS,170 capabilities,171 Instant.now());172 CreateSessionResponse sessionResponse = new CreateSessionResponse(173 session,174 JSON.toJson(175 ImmutableMap.of(176 "value", ImmutableMap.of(177 "sessionId", sessionId,178 "capabilities", capabilities)))179 .getBytes(UTF_8));180 queue.complete(reqId, Either.right(sessionResponse));181 }));182 HttpResponse httpResponse = queue.addToQueue(sessionRequest);183 assertThat(isPresent.get()).isTrue();184 assertEquals(httpResponse.getStatus(), HTTP_OK);185 }186 @Test187 public void shouldBeAbleToAddToQueueAndGetErrorResponse() {188 bus.addListener(NewSessionRequestEvent.listener(reqId ->189 queue.complete(reqId, Either.left(new SessionNotCreatedException("Error")))));190 HttpResponse httpResponse = queue.addToQueue(sessionRequest);191 assertEquals(httpResponse.getStatus(), HTTP_INTERNAL_ERROR);192 }193 @Test194 public void shouldBeAbleToRemoveFromQueue() {195 Optional<SessionRequest> httpRequest = queue.remove(new RequestId(UUID.randomUUID()));196 assertFalse(httpRequest.isPresent());197 }198 @Test199 public void shouldBeClearQueue() {200 RequestId requestId = new RequestId(UUID.randomUUID());201 localQueue.injectIntoQueue(sessionRequest);202 int count = queue.clearQueue();203 assertEquals(count, 1);204 assertFalse(queue.remove(requestId).isPresent());205 }206 @Test207 public void shouldBeAbleToGetQueueContents() {208 localQueue.injectIntoQueue(sessionRequest);209 List<Set<Capabilities>> response = queue.getQueueContents()210 .stream()211 .map(SessionRequestCapability::getDesiredCapabilities)212 .collect(Collectors.toList());213 assertThat(response).hasSize(1);214 assertEquals(Set.of(CAPS), response.get(0));215 }216 @Test217 public void shouldBeClearQueueAndFireRejectedEvent() throws InterruptedException {218 AtomicBoolean result = new AtomicBoolean(false);219 RequestId requestId = sessionRequest.getRequestId();220 CountDownLatch latch = new CountDownLatch(1);221 bus.addListener(222 NewSessionRejectedEvent.listener(223 response -> {224 result.set(response.getRequestId().equals(requestId));225 latch.countDown();226 }));227 localQueue.injectIntoQueue(sessionRequest);228 queue.remove(requestId);229 queue.retryAddToQueue(sessionRequest);230 int count = queue.clearQueue();231 assertThat(latch.await(2, SECONDS)).isTrue();232 assertThat(result.get()).isTrue();233 assertEquals(count, 1);234 assertFalse(queue.remove(requestId).isPresent());235 }236 @Test237 public void removingARequestIdThatDoesNotExistInTheQueueShouldNotBeAnError() {238 localQueue.injectIntoQueue(sessionRequest);239 Optional<SessionRequest> httpRequest = queue.remove(new RequestId(UUID.randomUUID()));240 assertFalse(httpRequest.isPresent());241 }242 @Test243 public void shouldBeAbleToAddAgainToQueue() {244 localQueue.injectIntoQueue(sessionRequest);245 Optional<SessionRequest> removed = queue.remove(sessionRequest.getRequestId());246 assertThat(removed).isPresent();247 boolean added = queue.retryAddToQueue(sessionRequest);248 assertTrue(added);249 }250 @Test251 public void shouldBeAbleToRetryRequest() {252 AtomicBoolean isPresent = new AtomicBoolean(false);253 AtomicBoolean retrySuccess = new AtomicBoolean(false);254 AtomicInteger count = new AtomicInteger(0);255 bus.addListener(256 NewSessionRequestEvent.listener(257 reqId -> {258 // Keep a count of event fired259 count.incrementAndGet();260 Optional<SessionRequest> sessionRequest = this.queue.remove(reqId);261 isPresent.set(sessionRequest.isPresent());262 if (count.get() == 1) {263 retrySuccess.set(queue.retryAddToQueue(sessionRequest.get()));264 }265 // Only if it was retried after an interval, the count is 2266 if (count.get() == 2) {267 ImmutableCapabilities capabilities =268 new ImmutableCapabilities("browserName", "edam");269 try {270 SessionId sessionId = new SessionId("123");271 Session session =272 new Session(273 sessionId,274 new URI("http://example.com"),275 CAPS,276 capabilities,277 Instant.now());278 CreateSessionResponse sessionResponse =279 new CreateSessionResponse(280 session,281 JSON.toJson(282 ImmutableMap.of(283 "value",284 ImmutableMap.of(285 "sessionId", sessionId,286 "capabilities", capabilities)))287 .getBytes(UTF_8));288 queue.complete(reqId, Either.right(sessionResponse));289 } catch (URISyntaxException e) {290 throw new RuntimeException(e);291 }292 }293 }));294 HttpResponse httpResponse = queue.addToQueue(sessionRequest);295 assertThat(isPresent.get()).isTrue();296 assertThat(retrySuccess.get()).isTrue();297 assertEquals(httpResponse.getStatus(), HTTP_OK);298 }299 @Test(timeout = 5000)300 public void shouldBeAbleToHandleMultipleSessionRequestsAtTheSameTime() {301 bus.addListener(NewSessionRequestEvent.listener(reqId -> {302 queue.remove(reqId);303 ImmutableCapabilities capabilities = new ImmutableCapabilities("browserName", "chrome");304 try {305 SessionId sessionId = new SessionId(UUID.randomUUID());306 Session session =307 new Session(308 sessionId,309 new URI("http://example.com"),310 CAPS,311 capabilities,312 Instant.now());313 CreateSessionResponse sessionResponse = new CreateSessionResponse(314 session,315 JSON.toJson(316 ImmutableMap.of(317 "value", ImmutableMap.of(318 "sessionId", sessionId,319 "capabilities", capabilities)))320 .getBytes(UTF_8));321 queue.complete(reqId, Either.right(sessionResponse));322 } catch (URISyntaxException e) {323 queue.complete(reqId, Either.left(new SessionNotCreatedException(e.getMessage())));324 }325 }));326 ExecutorService executor = Executors.newFixedThreadPool(2);327 Callable<HttpResponse> callable = () -> {328 SessionRequest sessionRequest = new SessionRequest(329 new RequestId(UUID.randomUUID()),330 Instant.now(),331 Set.of(W3C),332 Set.of(CAPS),333 Map.of(),334 Map.of());335 return queue.addToQueue(sessionRequest);336 };337 Future<HttpResponse> firstRequest = executor.submit(callable);338 Future<HttpResponse> secondRequest = executor.submit(callable);339 try {340 HttpResponse firstResponse = firstRequest.get(30, SECONDS);341 HttpResponse secondResponse = secondRequest.get(30, SECONDS);342 String firstResponseContents = Contents.string(firstResponse);343 String secondResponseContents = Contents.string(secondResponse);344 assertEquals(firstResponse.getStatus(), HTTP_OK);345 assertEquals(secondResponse.getStatus(), HTTP_OK);346 assertNotEquals(firstResponseContents, secondResponseContents);347 } catch (InterruptedException | ExecutionException | TimeoutException e) {348 fail("Could not create session");349 }350 executor.shutdown();351 }352 @Test(timeout = 5000)353 public void shouldBeAbleToTimeoutARequestOnRetry() {354 final SessionRequest request = new SessionRequest(355 new RequestId(UUID.randomUUID()),356 LONG_AGO,357 Set.of(W3C),358 Set.of(CAPS),359 Map.of(),360 Map.of());361 AtomicInteger count = new AtomicInteger();362 bus.addListener(NewSessionRejectedEvent.listener(reqId -> {363 count.incrementAndGet();364 }));365 HttpResponse httpResponse = queue.addToQueue(request);366 assertEquals(1, count.get());367 assertEquals(HTTP_INTERNAL_ERROR, httpResponse.getStatus());368 }369 @Test(timeout = 10000)370 public void shouldBeAbleToTimeoutARequestOnRemove() throws InterruptedException {371 AtomicReference<RequestId> isPresent = new AtomicReference<>();372 CountDownLatch latch = new CountDownLatch(1);373 bus.addListener(374 NewSessionRejectedEvent.listener(375 response -> {376 isPresent.set(response.getRequestId());377 latch.countDown();378 }));379 SessionRequest sessionRequest = new SessionRequest(380 new RequestId(UUID.randomUUID()),381 LONG_AGO,382 Set.of(W3C),383 Set.of(CAPS),384 Map.of(),385 Map.of());386 localQueue.injectIntoQueue(sessionRequest);387 queue.remove(sessionRequest.getRequestId());388 assertThat(latch.await(4, SECONDS)).isTrue();389 assertThat(isPresent.get()).isEqualTo(sessionRequest.getRequestId());390 }391 @Test(timeout = 5000)392 public void shouldBeAbleToClearQueueAndRejectMultipleRequests() {393 ExecutorService executor = Executors.newFixedThreadPool(2);394 Callable<HttpResponse> callable = () -> {395 SessionRequest sessionRequest = new SessionRequest(396 new RequestId(UUID.randomUUID()),397 Instant.now(),398 Set.of(W3C),399 Set.of(CAPS),400 Map.of(),401 Map.of());402 return queue.addToQueue(sessionRequest);403 };404 Future<HttpResponse> firstRequest = executor.submit(callable);405 Future<HttpResponse> secondRequest = executor.submit(callable);406 int count = 0;407 while (count < 2) {408 count += queue.clearQueue();409 }410 try {411 HttpResponse firstResponse = firstRequest.get(30, SECONDS);412 HttpResponse secondResponse = secondRequest.get(30, SECONDS);413 assertEquals(firstResponse.getStatus(), HTTP_INTERNAL_ERROR);414 assertEquals(secondResponse.getStatus(), HTTP_INTERNAL_ERROR);415 } catch (InterruptedException | ExecutionException | TimeoutException e) {416 fail("Could not create session");417 }418 executor.shutdownNow();419 }420 @Test421 public void shouldBeAbleToReturnTheNextAvailableEntryThatMatchesAStereotype() {422 SessionRequest expected = new SessionRequest(423 new RequestId(UUID.randomUUID()),424 Instant.now(),425 Set.of(W3C),426 Set.of(new ImmutableCapabilities("browserName", "cheese", "se:kind", "smoked")),427 Map.of(),428 Map.of());429 localQueue.injectIntoQueue(expected);430 localQueue.injectIntoQueue(new SessionRequest(431 new RequestId(UUID.randomUUID()),432 Instant.now(),433 Set.of(W3C),434 Set.of(new ImmutableCapabilities("browserName", "peas", "se:kind", "mushy")),435 Map.of(),436 Map.of()));437 Optional<SessionRequest> returned = queue.getNextAvailable(438 Set.of(new ImmutableCapabilities("browserName", "cheese")));439 assertThat(returned).isEqualTo(Optional.of(expected));440 }441 @Test442 public void shouldNotReturnANextAvailableEntryThatDoesNotMatchTheStereotypes() {443 // Note that this is basically the same test as getting the entry444 // from queue, but we've cleverly reversed the entries, so the one445 // that doesn't match should be first in the queue.446 localQueue.injectIntoQueue(new SessionRequest(447 new RequestId(UUID.randomUUID()),448 Instant.now(),449 Set.of(W3C),450 Set.of(new ImmutableCapabilities("browserName", "peas", "se:kind", "mushy")),451 Map.of(),452 Map.of()));453 SessionRequest expected = new SessionRequest(454 new RequestId(UUID.randomUUID()),455 Instant.now(),456 Set.of(W3C),457 Set.of(new ImmutableCapabilities("browserName", "cheese", "se:kind", "smoked")),458 Map.of(),459 Map.of());460 localQueue.injectIntoQueue(expected);461 Optional<SessionRequest> returned = queue.getNextAvailable(462 Set.of(new ImmutableCapabilities("browserName", "cheese")));463 assertThat(returned).isEqualTo(Optional.of(expected));464 }465}...