Test z intellij przechodzi, a z gradle nie.

0

Hej,

Mam napisany test który jest wielowątkowy.
Test ma na celu zweryfikowanie rzuconego execptionu z timeout przy pobieraniu connection z connection pool.

W skrócie - odpalam na raz n wątków, które odpytują w tym samym momencie resource który ma connectionPoolSize=n-1.

Jeśli chodzi o run z poziomu intellij to test jest w 100% deterministyczny, zawsze ten exception się pojawia.
Problem pojawia się gdy odpalam wszystkie testy ./gradlew test i test przestaje działać.

Przy ustawieniu forkEvery=1 test z poziomu gradle przechodzi, tylko baardzo zwiększa to czas wykonania wszystkich testów.

Ktoś spotkał się z podobnym problemem i go jakoś rozwiązał?

3

Skąd to przekonanie, że test jest deterministyczny? Gdyby tak było, to i tak potrzebujesz 2 wątków, a nie 100. Śliskie to bardzo. Musiałbyś pokazać kod.

2

Testy wielowatkowe bez customowego zegara sa skazane na porazke. To jest przyklad kompletnie zlego podejscia do testowania, wrecz nie rozumienia jak to powinno sie robic.

0

@sultan_kosmitow: Jakiś artykuł?
@Charles_Ray: bo jeszcze ani razu się z poziomu intellij nie wysypał, a z gradle robi to zawsze? nigdzie nie napisałem że używam 100 wątków, mam connectionPoolSize=1 a wątki są 2.

2

Inne podejrzenie ze twoj connection pool jest wpodzielony miedzy testami i to ma wplyw. Gradle uruchamia X testow na raz domyslnie

0

@Charles_Ray:
@sultan_kosmitow:

@ContextConfiguration(classes = {
    ClientConfig.class
})
@TestPropertySource(properties = {
    "max.concurrent.connections=2"
})
public class ClientTest extends WiremockTestBase {

    @Value("${max.concurrent.connections:2}")
    private int maxConcurrentConnections = 2;

    @Autowired
    private Client client;
    
    @Test
    void shouldNotBeAbleToHandleConcurrentConnections() throws JsonProcessingException {
        //given
        int connections = maxConcurrentConnections + 1;

        stubPost("/", getResponseBody(), HttpStatus.OK);

        ExecutorService executorService = Executors.newFixedThreadPool(connections);
        CountDownLatch latch = new CountDownLatch(connections);
        Waiter waiter = new Waiter();

        //when
        for (int i = 0; i < connections; i++) {
            executorService.execute(new Worker(latch, client, waiter));
        }

        //then
        assertThrows(TimeoutException.class, () -> waiter.await(2, TimeUnit.SECONDS, connections));
        executorService.shutdownNow();
    }

    static class Worker implements Runnable {

        private final CountDownLatch countDownLatch;
        private final Client client;
        private final Waiter waiter;

        public Worker(CountDownLatch countDownLatch, Client client, Waiter waiter) {
            this.countDownLatch = countDownLatch;
            this.client = client;
            this.waiter = waiter;
        }

        @SneakyThrows
        @Override
        public void run() {
            countDownLatch.countDown();
            countDownLatch.await();

            Object response = client.getUser()

            waiter.assertNotNull(response);
            waiter.resume();
        }
    }
}

Client jest skonfigurowany ze ma 2 connection w connectionPoolu. Oprócz tego mam timeoutAquisition na connection = 10 millis, ale to juz w klasie ClientConfig

2

@Override
public void run() {
countDownLatch.countDown();
countDownLatch.await();

await() powinno być raczej wołane z głównego wątku, aby poczekać na Workery. Co robi ten Waiter?

0

@Charles_Ray: countDownLatch.await() robie w wątkach, żeby się zblokowały i potem 3 na raz w tym samym momencie strzeliły clientem który ma tylko 2 connections w pool'u co powinno skutkować że jeden z nich rzuci runtimeException i się wysypie, w wyniku czego ten Waiter się odpali w wątku głównym.

waiter.await(2, TimeUnit.SECONDS, connections) -> czeka 2 sekundy żeby uzyskać connections razy waiter.resume() inaczej rzuci TimeoutException

0

Najlepiej by było, gdyby główny wątek poczekał aż będzie zajętych N połączeń i spróbował zrobić N+1. Nie jestem przekonany co do pomysłu wymuszenia wyścigu. Requesty możesz w sumie zblokować od strony Wiremocka wydłużając czas odpowiedzi albo właśnie po jego stronie czekać na jakimś semaforze - wtedy jest pewność, że każdy request będzie musiał pobrać nowe połączenie z puli.

Nie wiem właściwie co chcesz przetestować :) sam pisałeś te pule połączeń, że chcesz ja sprawdzić czy działa? To jest w zasadzie testowanie semafora i to nie swojego :)

0

@Charles_Ray:

Spróbowałem wedle tego co zasugerowałeś, test wygląda tak:

    @SneakyThrows
    @Test
    void shouldNotBeAbleToHandleConcurrentConnections() {
        //given
        final ClientConfiguration clientConfiguration =
            new ClientConfiguration(wireMockPort, maxConcurrentConnections);

        final Client client =
            clientConfiguration.getClient(clientConfiguration .httpClient());

        int connections = maxConcurrentConnections + 1;
        int workerThreads = connections - 1;

        stubPost("/", getWorkerRequest().username(), getResponseBody(), HttpStatus.OK, 1500);
        stubPost("/", getMainThreadRequest().username(), getResponseBody(), HttpStatus.OK);

        ExecutorService executorService = Executors.newFixedThreadPool(workerThreads);
        CountDownLatch latch = new CountDownLatch(connections);
        Waiter waiter = new Waiter();

        //when
        for (int i = 0; i < workerThreads; i++) {
            executorService.execute(new Worker(latch, client, waiter));
        }
        
        latch.countDown();

        //sleep for a while to make sure worker threads acquire connections from pool
        Thread.sleep(300);

        //then
        ClientException exception =
            assertThrows(ClientException.class, () -> client.getUser(getMainThreadRequest()));

        assertThat(exception.getMessage())
            .isEqualTo("Unable to execute HTTP request: Timeout waiting for connection from pool");

        executorService.shutdown();
    }

Zrobiłem delay 1500ms na wiremocku przy requestach z worker threads, potem żeby mieć pewność że one najpierw wykonają request to w głównym wątku robie Thread.sleep()... co pewnie nie jest najlepszym rozwiązaniem. Tylko na wiremocku chyba nie ma żadnej semafory, albo przynajmniej nie znam czegoś takiego?

Z tym rozwiązaniem mam taki rezultat, że lokalny gradle run przechodzi, wszystkie testy green, ale na jobach w repozytorium fail...

0

@Charles_Ray: brakuje mi własnie jakiejś semafory/countDownLatcha po stronie wiremocka która potwierdziłaby że 2 requesty wiszą

2

To weź zamiast WireMocka wystaw normalny serwer HTTP (siakiś sparkjava - javalin czy cos) - to 5 minut pracy. Będziesz miał całość pod kontrolą.

1 użytkowników online, w tym zalogowanych: 0, gości: 1