LCOV - code coverage report
Current view: top level - src/interfaces/libpq-oauth - test-oauth-curl.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 209 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 9 0
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*
       2              :  * test-oauth-curl.c
       3              :  *
       4              :  * A unit test driver for libpq-oauth. This #includes oauth-curl.c, which lets
       5              :  * the tests reference static functions and other internals.
       6              :  *
       7              :  * USE_ASSERT_CHECKING is required, to make it easy for tests to wrap
       8              :  * must-succeed code as part of test setup.
       9              :  *
      10              :  * Copyright (c) 2025-2026, PostgreSQL Global Development Group
      11              :  */
      12              : 
      13              : #include "oauth-curl.c"
      14              : 
      15              : #include <fcntl.h>
      16              : 
      17              : #ifdef USE_ASSERT_CHECKING
      18              : 
      19              : /*
      20              :  * TAP Helpers
      21              :  */
      22              : 
      23              : static int      num_tests = 0;
      24              : 
      25              : /*
      26              :  * Reports ok/not ok to the TAP stream on stdout.
      27              :  */
      28              : #define ok(OK, TEST) \
      29              :         ok_impl(OK, TEST, #OK, __FILE__, __LINE__)
      30              : 
      31              : static bool
      32            0 : ok_impl(bool ok, const char *test, const char *teststr, const char *file, int line)
      33              : {
      34            0 :         printf("%sok %d - %s\n", ok ? "" : "not ", ++num_tests, test);
      35              : 
      36            0 :         if (!ok)
      37              :         {
      38            0 :                 printf("# at %s:%d:\n", file, line);
      39            0 :                 printf("#   expression is false: %s\n", teststr);
      40            0 :         }
      41              : 
      42            0 :         return ok;
      43              : }
      44              : 
      45              : /*
      46              :  * Like ok(this == that), but with more diagnostics on failure.
      47              :  *
      48              :  * Only works on ints, but luckily that's all we need here. Note that the much
      49              :  * simpler-looking macro implementation
      50              :  *
      51              :  *     is_diag(ok(THIS == THAT, TEST), THIS, #THIS, THAT, #THAT)
      52              :  *
      53              :  * suffers from multiple evaluation of the macro arguments...
      54              :  */
      55              : #define is(THIS, THAT, TEST) \
      56              :         do { \
      57              :                 int this_ = (THIS), \
      58              :                         that_ = (THAT); \
      59              :                 is_diag( \
      60              :                         ok_impl(this_ == that_, TEST, #THIS " == " #THAT, __FILE__, __LINE__), \
      61              :                         this_, #THIS, that_, #THAT \
      62              :                 ); \
      63              :         } while (0)
      64              : 
      65              : static bool
      66            0 : is_diag(bool ok, int this, const char *thisstr, int that, const char *thatstr)
      67              : {
      68            0 :         if (!ok)
      69            0 :                 printf("#   %s = %d; %s = %d\n", thisstr, this, thatstr, that);
      70              : 
      71            0 :         return ok;
      72              : }
      73              : 
      74              : /*
      75              :  * Utilities
      76              :  */
      77              : 
      78              : /*
      79              :  * Creates a partially-initialized async_ctx for the purposes of testing. Free
      80              :  * with free_test_actx().
      81              :  */
      82              : static struct async_ctx *
      83            0 : init_test_actx(void)
      84              : {
      85            0 :         struct async_ctx *actx;
      86              : 
      87            0 :         actx = calloc(1, sizeof(*actx));
      88            0 :         Assert(actx);
      89              : 
      90            0 :         actx->mux = PGINVALID_SOCKET;
      91            0 :         actx->timerfd = -1;
      92            0 :         actx->debugging = true;
      93              : 
      94            0 :         initPQExpBuffer(&actx->errbuf);
      95              : 
      96            0 :         Assert(setup_multiplexer(actx));
      97              : 
      98            0 :         return actx;
      99            0 : }
     100              : 
     101              : static void
     102            0 : free_test_actx(struct async_ctx *actx)
     103              : {
     104            0 :         termPQExpBuffer(&actx->errbuf);
     105              : 
     106            0 :         if (actx->mux != PGINVALID_SOCKET)
     107            0 :                 close(actx->mux);
     108            0 :         if (actx->timerfd >= 0)
     109            0 :                 close(actx->timerfd);
     110              : 
     111            0 :         free(actx);
     112            0 : }
     113              : 
     114              : static char dummy_buf[4 * 1024];        /* for fill_pipe/drain_pipe */
     115              : 
     116              : /*
     117              :  * Writes to the write side of a pipe until it won't take any more data. Returns
     118              :  * the amount written.
     119              :  */
     120              : static ssize_t
     121            0 : fill_pipe(int fd)
     122              : {
     123            0 :         int                     mode;
     124            0 :         ssize_t         written = 0;
     125              : 
     126              :         /* Don't block. */
     127            0 :         Assert((mode = fcntl(fd, F_GETFL)) != -1);
     128            0 :         Assert(fcntl(fd, F_SETFL, mode | O_NONBLOCK) == 0);
     129              : 
     130            0 :         while (true)
     131              :         {
     132            0 :                 ssize_t         w;
     133              : 
     134            0 :                 w = write(fd, dummy_buf, sizeof(dummy_buf));
     135            0 :                 if (w < 0)
     136              :                 {
     137            0 :                         if (errno != EAGAIN && errno != EWOULDBLOCK)
     138              :                         {
     139            0 :                                 perror("write to pipe");
     140            0 :                                 written = -1;
     141            0 :                         }
     142            0 :                         break;
     143              :                 }
     144              : 
     145            0 :                 written += w;
     146            0 :         }
     147              : 
     148              :         /* Reset the descriptor flags. */
     149            0 :         Assert(fcntl(fd, F_SETFD, mode) == 0);
     150              : 
     151            0 :         return written;
     152            0 : }
     153              : 
     154              : /*
     155              :  * Drains the requested amount of data from the read side of a pipe.
     156              :  */
     157              : static bool
     158            0 : drain_pipe(int fd, ssize_t n)
     159              : {
     160            0 :         Assert(n > 0);
     161              : 
     162            0 :         while (n)
     163              :         {
     164            0 :                 size_t          to_read = (n <= sizeof(dummy_buf)) ? n : sizeof(dummy_buf);
     165            0 :                 ssize_t         drained;
     166              : 
     167            0 :                 drained = read(fd, dummy_buf, to_read);
     168            0 :                 if (drained < 0)
     169              :                 {
     170            0 :                         perror("read from pipe");
     171            0 :                         return false;
     172              :                 }
     173              : 
     174            0 :                 n -= drained;
     175            0 :         }
     176              : 
     177            0 :         return true;
     178            0 : }
     179              : 
     180              : /*
     181              :  * Tests whether the multiplexer is marked ready by the deadline. This is a
     182              :  * macro so that file/line information makes sense during failures.
     183              :  *
     184              :  * NB: our current multiplexer implementations (epoll/kqueue) are *readable*
     185              :  * when the underlying libcurl sockets are *writable*. This behavior is pinned
     186              :  * here to record that expectation; PGRES_POLLING_READING is hardcoded
     187              :  * throughout the flow and would need to be changed if a new multiplexer does
     188              :  * something different.
     189              :  */
     190              : #define mux_is_ready(MUX, DEADLINE, TEST) \
     191              :         do { \
     192              :                 int res_ = PQsocketPoll(MUX, 1, 0, DEADLINE); \
     193              :                 Assert(res_ != -1); \
     194              :                 ok(res_ > 0, "multiplexer is ready " TEST); \
     195              :         } while (0)
     196              : 
     197              : /*
     198              :  * The opposite of mux_is_ready().
     199              :  */
     200              : #define mux_is_not_ready(MUX, TEST) \
     201              :         do { \
     202              :                 int res_ = PQsocketPoll(MUX, 1, 0, 0); \
     203              :                 Assert(res_ != -1); \
     204              :                 is(res_, 0, "multiplexer is not ready " TEST); \
     205              :         } while (0)
     206              : 
     207              : /*
     208              :  * Test Suites
     209              :  */
     210              : 
     211              : /* Per-suite timeout. Set via the PG_TEST_TIMEOUT_DEFAULT envvar. */
     212              : static pg_usec_time_t timeout_us = 180 * 1000 * 1000;
     213              : 
     214              : static void
     215            0 : test_set_timer(void)
     216              : {
     217            0 :         struct async_ctx *actx = init_test_actx();
     218            0 :         const pg_usec_time_t deadline = PQgetCurrentTimeUSec() + timeout_us;
     219              : 
     220            0 :         printf("# test_set_timer\n");
     221              : 
     222              :         /* A zero-duration timer should result in a near-immediate ready signal. */
     223            0 :         Assert(set_timer(actx, 0));
     224            0 :         mux_is_ready(actx->mux, deadline, "when timer expires");
     225            0 :         is(timer_expired(actx), 1, "timer_expired() returns 1 when timer expires");
     226              : 
     227              :         /* Resetting the timer far in the future should unset the ready signal. */
     228            0 :         Assert(set_timer(actx, INT_MAX));
     229            0 :         mux_is_not_ready(actx->mux, "when timer is reset to the future");
     230            0 :         is(timer_expired(actx), 0, "timer_expired() returns 0 with unexpired timer");
     231              : 
     232              :         /* Setting another zero-duration timer should override the previous one. */
     233            0 :         Assert(set_timer(actx, 0));
     234            0 :         mux_is_ready(actx->mux, deadline, "when timer is re-expired");
     235            0 :         is(timer_expired(actx), 1, "timer_expired() returns 1 when timer is re-expired");
     236              : 
     237              :         /* And disabling that timer should once again unset the ready signal. */
     238            0 :         Assert(set_timer(actx, -1));
     239            0 :         mux_is_not_ready(actx->mux, "when timer is unset");
     240            0 :         is(timer_expired(actx), 0, "timer_expired() returns 0 when timer is unset");
     241              : 
     242              :         {
     243            0 :                 bool            expired;
     244              : 
     245              :                 /* Make sure drain_timer_events() functions correctly as well. */
     246            0 :                 Assert(set_timer(actx, 0));
     247            0 :                 mux_is_ready(actx->mux, deadline, "when timer is re-expired (drain_timer_events)");
     248              : 
     249            0 :                 Assert(drain_timer_events(actx, &expired));
     250            0 :                 mux_is_not_ready(actx->mux, "when timer is drained after expiring");
     251            0 :                 is(expired, 1, "drain_timer_events() reports expiration");
     252            0 :                 is(timer_expired(actx), 0, "timer_expired() returns 0 after timer is drained");
     253              : 
     254              :                 /* A second drain should do nothing. */
     255            0 :                 Assert(drain_timer_events(actx, &expired));
     256            0 :                 mux_is_not_ready(actx->mux, "when timer is drained a second time");
     257            0 :                 is(expired, 0, "drain_timer_events() reports no expiration");
     258            0 :                 is(timer_expired(actx), 0, "timer_expired() still returns 0");
     259            0 :         }
     260              : 
     261            0 :         free_test_actx(actx);
     262            0 : }
     263              : 
     264              : static void
     265            0 : test_register_socket(void)
     266              : {
     267            0 :         struct async_ctx *actx = init_test_actx();
     268            0 :         int                     pipefd[2];
     269            0 :         int                     rfd,
     270              :                                 wfd;
     271            0 :         bool            bidirectional;
     272              : 
     273              :         /* Create a local pipe for communication. */
     274            0 :         Assert(pipe(pipefd) == 0);
     275            0 :         rfd = pipefd[0];
     276            0 :         wfd = pipefd[1];
     277              : 
     278              :         /*
     279              :          * Some platforms (FreeBSD) implement bidirectional pipes, affecting the
     280              :          * behavior of some of these tests. Store that knowledge for later.
     281              :          */
     282            0 :         bidirectional = PQsocketPoll(rfd /* read */ , 0, 1 /* write */ , 0) > 0;
     283              : 
     284              :         /*
     285              :          * This suite runs twice -- once using CURL_POLL_IN/CURL_POLL_OUT for
     286              :          * read/write operations, respectively, and once using CURL_POLL_INOUT for
     287              :          * both sides.
     288              :          */
     289            0 :         for (int inout = 0; inout < 2; inout++)
     290              :         {
     291            0 :                 const int       in_event = inout ? CURL_POLL_INOUT : CURL_POLL_IN;
     292            0 :                 const int       out_event = inout ? CURL_POLL_INOUT : CURL_POLL_OUT;
     293            0 :                 const pg_usec_time_t deadline = PQgetCurrentTimeUSec() + timeout_us;
     294            0 :                 size_t          bidi_pipe_size = 0; /* silence compiler warnings */
     295              : 
     296            0 :                 printf("# test_register_socket %s\n", inout ? "(INOUT)" : "");
     297              : 
     298              :                 /*
     299              :                  * At the start of the test, the read side should be blocked and the
     300              :                  * write side should be open. (There's a mistake at the end of this
     301              :                  * loop otherwise.)
     302              :                  */
     303            0 :                 Assert(PQsocketPoll(rfd, 1, 0, 0) == 0);
     304            0 :                 Assert(PQsocketPoll(wfd, 0, 1, 0) > 0);
     305              : 
     306              :                 /*
     307              :                  * For bidirectional systems, emulate unidirectional behavior here by
     308              :                  * filling up the "read side" of the pipe.
     309              :                  */
     310            0 :                 if (bidirectional)
     311            0 :                         Assert((bidi_pipe_size = fill_pipe(rfd)) > 0);
     312              : 
     313              :                 /* Listen on the read side. The multiplexer shouldn't be ready yet. */
     314            0 :                 Assert(register_socket(NULL, rfd, in_event, actx, NULL) == 0);
     315            0 :                 mux_is_not_ready(actx->mux, "when fd is not readable");
     316              : 
     317              :                 /* Writing to the pipe should result in a read-ready multiplexer. */
     318            0 :                 Assert(write(wfd, "x", 1) == 1);
     319            0 :                 mux_is_ready(actx->mux, deadline, "when fd is readable");
     320              : 
     321              :                 /*
     322              :                  * Update the registration to wait on write events instead. The
     323              :                  * multiplexer should be unset.
     324              :                  */
     325            0 :                 Assert(register_socket(NULL, rfd, CURL_POLL_OUT, actx, NULL) == 0);
     326            0 :                 mux_is_not_ready(actx->mux, "when waiting for writes on readable fd");
     327              : 
     328              :                 /* Re-register for read events. */
     329            0 :                 Assert(register_socket(NULL, rfd, in_event, actx, NULL) == 0);
     330            0 :                 mux_is_ready(actx->mux, deadline, "when waiting for reads again");
     331              : 
     332              :                 /* Stop listening. The multiplexer should be unset. */
     333            0 :                 Assert(register_socket(NULL, rfd, CURL_POLL_REMOVE, actx, NULL) == 0);
     334            0 :                 mux_is_not_ready(actx->mux, "when readable fd is removed");
     335              : 
     336              :                 /* Listen again. */
     337            0 :                 Assert(register_socket(NULL, rfd, in_event, actx, NULL) == 0);
     338            0 :                 mux_is_ready(actx->mux, deadline, "when readable fd is re-added");
     339              : 
     340              :                 /*
     341              :                  * Draining the pipe should unset the multiplexer again, once the old
     342              :                  * event is cleared.
     343              :                  */
     344            0 :                 Assert(drain_pipe(rfd, 1));
     345            0 :                 Assert(comb_multiplexer(actx));
     346            0 :                 mux_is_not_ready(actx->mux, "when fd is drained");
     347              : 
     348              :                 /* Undo any unidirectional emulation. */
     349            0 :                 if (bidirectional)
     350            0 :                         Assert(drain_pipe(wfd, bidi_pipe_size));
     351              : 
     352              :                 /* Listen on the write side. An empty buffer should be writable. */
     353            0 :                 Assert(register_socket(NULL, rfd, CURL_POLL_REMOVE, actx, NULL) == 0);
     354            0 :                 Assert(register_socket(NULL, wfd, out_event, actx, NULL) == 0);
     355            0 :                 mux_is_ready(actx->mux, deadline, "when fd is writable");
     356              : 
     357              :                 /* As above, wait on read events instead. */
     358            0 :                 Assert(register_socket(NULL, wfd, CURL_POLL_IN, actx, NULL) == 0);
     359            0 :                 mux_is_not_ready(actx->mux, "when waiting for reads on writable fd");
     360              : 
     361              :                 /* Re-register for write events. */
     362            0 :                 Assert(register_socket(NULL, wfd, out_event, actx, NULL) == 0);
     363            0 :                 mux_is_ready(actx->mux, deadline, "when waiting for writes again");
     364              : 
     365              :                 {
     366            0 :                         ssize_t         written;
     367              : 
     368              :                         /*
     369              :                          * Fill the pipe. Once the old writable event is cleared, the mux
     370              :                          * should not be ready.
     371              :                          */
     372            0 :                         Assert((written = fill_pipe(wfd)) > 0);
     373            0 :                         printf("# pipe buffer is full at %zd bytes\n", written);
     374              : 
     375            0 :                         Assert(comb_multiplexer(actx));
     376            0 :                         mux_is_not_ready(actx->mux, "when fd buffer is full");
     377              : 
     378              :                         /* Drain the pipe again. */
     379            0 :                         Assert(drain_pipe(rfd, written));
     380            0 :                         mux_is_ready(actx->mux, deadline, "when fd buffer is drained");
     381            0 :                 }
     382              : 
     383              :                 /* Stop listening. */
     384            0 :                 Assert(register_socket(NULL, wfd, CURL_POLL_REMOVE, actx, NULL) == 0);
     385            0 :                 mux_is_not_ready(actx->mux, "when fd is removed");
     386              : 
     387              :                 /* Make sure an expired timer doesn't interfere with event draining. */
     388              :                 {
     389            0 :                         bool            expired;
     390              : 
     391              :                         /* Make the rfd appear unidirectional if necessary. */
     392            0 :                         if (bidirectional)
     393            0 :                                 Assert((bidi_pipe_size = fill_pipe(rfd)) > 0);
     394              : 
     395              :                         /* Set the timer and wait for it to expire. */
     396            0 :                         Assert(set_timer(actx, 0));
     397            0 :                         Assert(PQsocketPoll(actx->timerfd, 1, 0, deadline) > 0);
     398            0 :                         is(timer_expired(actx), 1, "timer is expired");
     399              : 
     400              :                         /* Register for read events and make the fd readable. */
     401            0 :                         Assert(register_socket(NULL, rfd, in_event, actx, NULL) == 0);
     402            0 :                         Assert(write(wfd, "x", 1) == 1);
     403            0 :                         mux_is_ready(actx->mux, deadline, "when fd is readable and timer expired");
     404              : 
     405              :                         /*
     406              :                          * Draining the pipe should unset the multiplexer again, once the
     407              :                          * old event is drained and the timer is reset.
     408              :                          *
     409              :                          * Order matters, since comb_multiplexer() doesn't have to remove
     410              :                          * stale events when active events exist. Follow the call sequence
     411              :                          * used in the code: drain the timer expiration, drain the pipe,
     412              :                          * then clear the stale events.
     413              :                          */
     414            0 :                         Assert(drain_timer_events(actx, &expired));
     415            0 :                         Assert(drain_pipe(rfd, 1));
     416            0 :                         Assert(comb_multiplexer(actx));
     417              : 
     418            0 :                         is(expired, 1, "drain_timer_events() reports expiration");
     419            0 :                         is(timer_expired(actx), 0, "timer is no longer expired");
     420            0 :                         mux_is_not_ready(actx->mux, "when fd is drained and timer reset");
     421              : 
     422              :                         /* Stop listening. */
     423            0 :                         Assert(register_socket(NULL, rfd, CURL_POLL_REMOVE, actx, NULL) == 0);
     424              : 
     425              :                         /* Undo any unidirectional emulation. */
     426            0 :                         if (bidirectional)
     427            0 :                                 Assert(drain_pipe(wfd, bidi_pipe_size));
     428            0 :                 }
     429              : 
     430              :                 /* Ensure comb_multiplexer() can handle multiple stale events. */
     431              :                 {
     432            0 :                         int                     rfd2,
     433              :                                                 wfd2;
     434              : 
     435              :                         /* Create a second local pipe. */
     436            0 :                         Assert(pipe(pipefd) == 0);
     437            0 :                         rfd2 = pipefd[0];
     438            0 :                         wfd2 = pipefd[1];
     439              : 
     440              :                         /* Make both rfds appear unidirectional if necessary. */
     441            0 :                         if (bidirectional)
     442              :                         {
     443            0 :                                 Assert((bidi_pipe_size = fill_pipe(rfd)) > 0);
     444            0 :                                 Assert(fill_pipe(rfd2) == bidi_pipe_size);
     445            0 :                         }
     446              : 
     447              :                         /* Register for read events on both fds, and make them readable. */
     448            0 :                         Assert(register_socket(NULL, rfd, in_event, actx, NULL) == 0);
     449            0 :                         Assert(register_socket(NULL, rfd2, in_event, actx, NULL) == 0);
     450              : 
     451            0 :                         Assert(write(wfd, "x", 1) == 1);
     452            0 :                         Assert(write(wfd2, "x", 1) == 1);
     453              : 
     454            0 :                         mux_is_ready(actx->mux, deadline, "when two fds are readable");
     455              : 
     456              :                         /*
     457              :                          * Drain both fds. comb_multiplexer() should then ensure that the
     458              :                          * mux is no longer readable.
     459              :                          */
     460            0 :                         Assert(drain_pipe(rfd, 1));
     461            0 :                         Assert(drain_pipe(rfd2, 1));
     462            0 :                         Assert(comb_multiplexer(actx));
     463            0 :                         mux_is_not_ready(actx->mux, "when two fds are drained");
     464              : 
     465              :                         /* Stop listening. */
     466            0 :                         Assert(register_socket(NULL, rfd, CURL_POLL_REMOVE, actx, NULL) == 0);
     467            0 :                         Assert(register_socket(NULL, rfd2, CURL_POLL_REMOVE, actx, NULL) == 0);
     468              : 
     469              :                         /* Undo any unidirectional emulation. */
     470            0 :                         if (bidirectional)
     471              :                         {
     472            0 :                                 Assert(drain_pipe(wfd, bidi_pipe_size));
     473            0 :                                 Assert(drain_pipe(wfd2, bidi_pipe_size));
     474            0 :                         }
     475              : 
     476            0 :                         close(rfd2);
     477            0 :                         close(wfd2);
     478            0 :                 }
     479            0 :         }
     480              : 
     481            0 :         close(rfd);
     482            0 :         close(wfd);
     483            0 :         free_test_actx(actx);
     484            0 : }
     485              : 
     486              : int
     487            0 : main(int argc, char *argv[])
     488              : {
     489            0 :         const char *timeout;
     490              : 
     491              :         /* Grab the default timeout. */
     492            0 :         timeout = getenv("PG_TEST_TIMEOUT_DEFAULT");
     493            0 :         if (timeout)
     494              :         {
     495            0 :                 int                     timeout_s = atoi(timeout);
     496              : 
     497            0 :                 if (timeout_s > 0)
     498            0 :                         timeout_us = timeout_s * 1000 * 1000;
     499            0 :         }
     500              : 
     501              :         /*
     502              :          * Set up line buffering for our output, to let stderr interleave in the
     503              :          * log files.
     504              :          */
     505            0 :         setvbuf(stdout, NULL, PG_IOLBF, 0);
     506              : 
     507            0 :         test_set_timer();
     508            0 :         test_register_socket();
     509              : 
     510            0 :         printf("1..%d\n", num_tests);
     511            0 :         return 0;
     512            0 : }
     513              : 
     514              : #else                                                   /* !USE_ASSERT_CHECKING */
     515              : 
     516              : /*
     517              :  * Skip the test suite when we don't have assertions.
     518              :  */
     519              : int
     520              : main(int argc, char *argv[])
     521              : {
     522              :         printf("1..0 # skip: cassert is not enabled\n");
     523              : 
     524              :         return 0;
     525              : }
     526              : 
     527              : #endif                                                  /* USE_ASSERT_CHECKING */
        

Generated by: LCOV version 2.3.2-1