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 */
|