Test z intellij przechodzi, a z gradle nie.

Test z intellij przechodzi, a z gradle nie.
VV
  • Rejestracja:ponad 3 lata
  • Ostatnio:ponad 3 lata
  • Postów:6
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ł?

edytowany 2x, ostatnio: vincenty_v
Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:około 7 godzin
  • Postów:1881
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.


”Engineering is easy. People are hard.” Bill Coughran
sultan_kosmitow
  • Rejestracja:ponad 4 lata
  • Ostatnio:prawie 2 lata
  • Postów:10
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.

VV
  • Rejestracja:ponad 3 lata
  • Ostatnio:ponad 3 lata
  • Postów:6
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.

Charles_Ray
Dokładnie: 2 wątki i CountDownLatch
VV
ale panowie... nigdzie nie napisałem ze nie użyłem CDL :) znam się trochę na multithreadingu w javie, dlatego zakładam ze mój kod jest w 100% deterministyczny, tutaj podejrzewam że gradle coś psoci.
Charles_Ray
Pokażesz ten kod czy będziemy dalej zgadywać?
sultan_kosmitow
Inna kwestia to jaki masz timeout ?
sultan_kosmitow
  • Rejestracja:ponad 4 lata
  • Ostatnio:prawie 2 lata
  • Postów:10
2

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

VV
  • Rejestracja:ponad 3 lata
  • Ostatnio:ponad 3 lata
  • Postów:6
0

@Charles_Ray:
@sultan_kosmitow:

Kopiuj
@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

edytowany 3x, ostatnio: vincenty_v
sultan_kosmitow
A jaka adnotacje ma client, @Singleton?
sultan_kosmitow
A masz inne testy korzystajace z tego Client? Bo jesli tak to inny test tez dostaje ten sam client
VV
@sultan_kosmitow: zmienilem na prototype/zadeklarowalem bezposrednio w klasie testowej i to samo
Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:około 7 godzin
  • Postów:1881
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?


”Engineering is easy. People are hard.” Bill Coughran
edytowany 2x, ostatnio: Charles_Ray
VV
  • Rejestracja:ponad 3 lata
  • Ostatnio:ponad 3 lata
  • Postów:6
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

Charles_Ray
  • Rejestracja:około 17 lat
  • Ostatnio:około 7 godzin
  • Postów:1881
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 :)


”Engineering is easy. People are hard.” Bill Coughran
edytowany 2x, ostatnio: Charles_Ray
VV
  • Rejestracja:ponad 3 lata
  • Ostatnio:ponad 3 lata
  • Postów:6
0

@Charles_Ray:

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

Kopiuj
    @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...

edytowany 2x, ostatnio: vincenty_v
PI
A te maxConcurrentConnections na pewno wszędzie wynosi tyle samo? Bo to z propertiesów bierzesz.
VV
tak, wszędzie wynosi tyle samo, nad testem mam: @TestPropertySource(properties = { "max.concurrent.connections=2" })
VV
  • Rejestracja:ponad 3 lata
  • Ostatnio:ponad 3 lata
  • Postów:6
0

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

jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:41 minut
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4708
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ą.


jeden i pół terabajta powinno wystarczyć każdemu
Charles_Ray
Albo podnieść testowego Spring Boota na innym porcie. Mało kodu, a i technologia ta sama
VV
szkoda że o tym nie pomyślałem :) problem solved, dzieki

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.