Age Owner Branch data TLA 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, 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
12 jchampion@postgresql 32 :GNC 55 : ok_impl(bool ok, const char *test, const char *teststr, const char *file, int line)
33 : : {
34 [ + - ]: 55 : printf("%sok %d - %s\n", ok ? "" : "not ", ++num_tests, test);
35 : :
36 [ - + ]: 55 : if (!ok)
37 : : {
12 jchampion@postgresql 38 :UNC 0 : printf("# at %s:%d:\n", file, line);
39 : 0 : printf("# expression is false: %s\n", teststr);
40 : : }
41 : :
12 jchampion@postgresql 42 :GNC 55 : 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 : 36 : is_diag(bool ok, int this, const char *thisstr, int that, const char *thatstr)
67 : : {
68 [ - + ]: 36 : if (!ok)
12 jchampion@postgresql 69 :UNC 0 : printf("# %s = %d; %s = %d\n", thisstr, this, thatstr, that);
70 : :
12 jchampion@postgresql 71 :GNC 36 : 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 : 2 : init_test_actx(void)
84 : : {
85 : : struct async_ctx *actx;
86 : :
87 : 2 : actx = calloc(1, sizeof(*actx));
88 [ - + ]: 2 : Assert(actx);
89 : :
90 : 2 : actx->mux = PGINVALID_SOCKET;
91 : 2 : actx->timerfd = -1;
92 : 2 : actx->debugging = true;
93 : :
94 : 2 : initPQExpBuffer(&actx->errbuf);
95 : :
96 [ - + ]: 2 : Assert(setup_multiplexer(actx));
97 : :
98 : 2 : return actx;
99 : : }
100 : :
101 : : static void
102 : 2 : free_test_actx(struct async_ctx *actx)
103 : : {
104 : 2 : termPQExpBuffer(&actx->errbuf);
105 : :
106 [ + - ]: 2 : if (actx->mux != PGINVALID_SOCKET)
107 : 2 : close(actx->mux);
108 [ + - ]: 2 : if (actx->timerfd >= 0)
109 : 2 : close(actx->timerfd);
110 : :
111 : 2 : free(actx);
112 : 2 : }
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 : 2 : fill_pipe(int fd)
122 : : {
123 : : int mode;
124 : 2 : ssize_t written = 0;
125 : :
126 : : /* Don't block. */
127 [ - + ]: 2 : Assert((mode = fcntl(fd, F_GETFL)) != -1);
128 [ - + ]: 2 : Assert(fcntl(fd, F_SETFL, mode | O_NONBLOCK) == 0);
129 : :
130 : : while (true)
131 : 32 : {
132 : : ssize_t w;
133 : :
134 : 34 : w = write(fd, dummy_buf, sizeof(dummy_buf));
135 [ + + ]: 34 : if (w < 0)
136 : : {
137 [ - + - - ]: 2 : if (errno != EAGAIN && errno != EWOULDBLOCK)
138 : : {
12 jchampion@postgresql 139 :UNC 0 : perror("write to pipe");
140 : 0 : written = -1;
141 : : }
12 jchampion@postgresql 142 :GNC 2 : break;
143 : : }
144 : :
145 : 32 : written += w;
146 : : }
147 : :
148 : : /* Reset the descriptor flags. */
149 [ - + ]: 2 : Assert(fcntl(fd, F_SETFD, mode) == 0);
150 : :
151 : 2 : return written;
152 : : }
153 : :
154 : : /*
155 : : * Drains the requested amount of data from the read side of a pipe.
156 : : */
157 : : static bool
158 : 10 : drain_pipe(int fd, ssize_t n)
159 : : {
160 [ - + ]: 10 : Assert(n > 0);
161 : :
162 [ + + ]: 50 : while (n)
163 : : {
164 : 40 : size_t to_read = (n <= sizeof(dummy_buf)) ? n : sizeof(dummy_buf);
165 : : ssize_t drained;
166 : :
167 : 40 : drained = read(fd, dummy_buf, to_read);
168 [ - + ]: 40 : if (drained < 0)
169 : : {
12 jchampion@postgresql 170 :UNC 0 : perror("read from pipe");
171 : 0 : return false;
172 : : }
173 : :
12 jchampion@postgresql 174 :GNC 40 : n -= drained;
175 : : }
176 : :
177 : 10 : return true;
178 : : }
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 : 1 : test_set_timer(void)
216 : : {
217 : 1 : struct async_ctx *actx = init_test_actx();
218 : 1 : const pg_usec_time_t deadline = PQgetCurrentTimeUSec() + timeout_us;
219 : :
220 : 1 : printf("# test_set_timer\n");
221 : :
222 : : /* A zero-duration timer should result in a near-immediate ready signal. */
223 [ - + ]: 1 : Assert(set_timer(actx, 0));
224 [ - + ]: 1 : mux_is_ready(actx->mux, deadline, "when timer expires");
225 : 1 : 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 [ - + ]: 1 : Assert(set_timer(actx, INT_MAX));
229 [ - + ]: 1 : mux_is_not_ready(actx->mux, "when timer is reset to the future");
230 : 1 : 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 [ - + ]: 1 : Assert(set_timer(actx, 0));
234 [ - + ]: 1 : mux_is_ready(actx->mux, deadline, "when timer is re-expired");
235 : 1 : 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 [ - + ]: 1 : Assert(set_timer(actx, -1));
239 [ - + ]: 1 : mux_is_not_ready(actx->mux, "when timer is unset");
240 : 1 : is(timer_expired(actx), 0, "timer_expired() returns 0 when timer is unset");
241 : :
242 : : {
243 : : bool expired;
244 : :
245 : : /* Make sure drain_timer_events() functions correctly as well. */
246 [ - + ]: 1 : Assert(set_timer(actx, 0));
247 [ - + ]: 1 : mux_is_ready(actx->mux, deadline, "when timer is re-expired (drain_timer_events)");
248 : :
249 [ - + ]: 1 : Assert(drain_timer_events(actx, &expired));
250 [ - + ]: 1 : mux_is_not_ready(actx->mux, "when timer is drained after expiring");
251 : 1 : is(expired, 1, "drain_timer_events() reports expiration");
252 : 1 : is(timer_expired(actx), 0, "timer_expired() returns 0 after timer is drained");
253 : :
254 : : /* A second drain should do nothing. */
255 [ - + ]: 1 : Assert(drain_timer_events(actx, &expired));
256 [ - + ]: 1 : mux_is_not_ready(actx->mux, "when timer is drained a second time");
257 : 1 : is(expired, 0, "drain_timer_events() reports no expiration");
258 : 1 : is(timer_expired(actx), 0, "timer_expired() still returns 0");
259 : : }
260 : :
261 : 1 : free_test_actx(actx);
262 : 1 : }
263 : :
264 : : static void
265 : 1 : test_register_socket(void)
266 : : {
267 : 1 : struct async_ctx *actx = init_test_actx();
268 : : int pipefd[2];
269 : : int rfd,
270 : : wfd;
271 : : bool bidirectional;
272 : :
273 : : /* Create a local pipe for communication. */
274 [ - + ]: 1 : Assert(pipe(pipefd) == 0);
275 : 1 : rfd = pipefd[0];
276 : 1 : 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 : 1 : 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 [ + + ]: 3 : for (int inout = 0; inout < 2; inout++)
290 : : {
291 [ + + ]: 2 : const int in_event = inout ? CURL_POLL_INOUT : CURL_POLL_IN;
292 [ + + ]: 2 : const int out_event = inout ? CURL_POLL_INOUT : CURL_POLL_OUT;
293 : 2 : const pg_usec_time_t deadline = PQgetCurrentTimeUSec() + timeout_us;
294 : 2 : size_t bidi_pipe_size = 0; /* silence compiler warnings */
295 : :
296 [ + + ]: 2 : 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 [ - + ]: 2 : Assert(PQsocketPoll(rfd, 1, 0, 0) == 0);
304 [ - + ]: 2 : 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 [ - + ]: 2 : if (bidirectional)
12 jchampion@postgresql 311 [ # # ]:UNC 0 : Assert((bidi_pipe_size = fill_pipe(rfd)) > 0);
312 : :
313 : : /* Listen on the read side. The multiplexer shouldn't be ready yet. */
12 jchampion@postgresql 314 [ - + ]:GNC 2 : Assert(register_socket(NULL, rfd, in_event, actx, NULL) == 0);
315 [ - + ]: 2 : 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 [ - + ]: 2 : Assert(write(wfd, "x", 1) == 1);
319 [ - + ]: 2 : 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 [ - + ]: 2 : Assert(register_socket(NULL, rfd, CURL_POLL_OUT, actx, NULL) == 0);
326 [ - + ]: 2 : mux_is_not_ready(actx->mux, "when waiting for writes on readable fd");
327 : :
328 : : /* Re-register for read events. */
329 [ - + ]: 2 : Assert(register_socket(NULL, rfd, in_event, actx, NULL) == 0);
330 [ - + ]: 2 : mux_is_ready(actx->mux, deadline, "when waiting for reads again");
331 : :
332 : : /* Stop listening. The multiplexer should be unset. */
333 [ - + ]: 2 : Assert(register_socket(NULL, rfd, CURL_POLL_REMOVE, actx, NULL) == 0);
334 [ - + ]: 2 : mux_is_not_ready(actx->mux, "when readable fd is removed");
335 : :
336 : : /* Listen again. */
337 [ - + ]: 2 : Assert(register_socket(NULL, rfd, in_event, actx, NULL) == 0);
338 [ - + ]: 2 : 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 [ - + ]: 2 : Assert(drain_pipe(rfd, 1));
345 [ - + ]: 2 : Assert(comb_multiplexer(actx));
346 [ - + ]: 2 : mux_is_not_ready(actx->mux, "when fd is drained");
347 : :
348 : : /* Undo any unidirectional emulation. */
349 [ - + ]: 2 : if (bidirectional)
12 jchampion@postgresql 350 [ # # ]:UNC 0 : Assert(drain_pipe(wfd, bidi_pipe_size));
351 : :
352 : : /* Listen on the write side. An empty buffer should be writable. */
12 jchampion@postgresql 353 [ - + ]:GNC 2 : Assert(register_socket(NULL, rfd, CURL_POLL_REMOVE, actx, NULL) == 0);
354 [ - + ]: 2 : Assert(register_socket(NULL, wfd, out_event, actx, NULL) == 0);
355 [ - + ]: 2 : mux_is_ready(actx->mux, deadline, "when fd is writable");
356 : :
357 : : /* As above, wait on read events instead. */
358 [ - + ]: 2 : Assert(register_socket(NULL, wfd, CURL_POLL_IN, actx, NULL) == 0);
359 [ - + ]: 2 : mux_is_not_ready(actx->mux, "when waiting for reads on writable fd");
360 : :
361 : : /* Re-register for write events. */
362 [ - + ]: 2 : Assert(register_socket(NULL, wfd, out_event, actx, NULL) == 0);
363 [ - + ]: 2 : mux_is_ready(actx->mux, deadline, "when waiting for writes again");
364 : :
365 : : {
366 : : 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 [ - + ]: 2 : Assert((written = fill_pipe(wfd)) > 0);
373 : 2 : printf("# pipe buffer is full at %zd bytes\n", written);
374 : :
375 [ - + ]: 2 : Assert(comb_multiplexer(actx));
376 [ - + ]: 2 : mux_is_not_ready(actx->mux, "when fd buffer is full");
377 : :
378 : : /* Drain the pipe again. */
379 [ - + ]: 2 : Assert(drain_pipe(rfd, written));
380 [ - + ]: 2 : mux_is_ready(actx->mux, deadline, "when fd buffer is drained");
381 : : }
382 : :
383 : : /* Stop listening. */
384 [ - + ]: 2 : Assert(register_socket(NULL, wfd, CURL_POLL_REMOVE, actx, NULL) == 0);
385 [ - + ]: 2 : 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 : : bool expired;
390 : :
391 : : /* Make the rfd appear unidirectional if necessary. */
392 [ - + ]: 2 : if (bidirectional)
12 jchampion@postgresql 393 [ # # ]:UNC 0 : Assert((bidi_pipe_size = fill_pipe(rfd)) > 0);
394 : :
395 : : /* Set the timer and wait for it to expire. */
12 jchampion@postgresql 396 [ - + ]:GNC 2 : Assert(set_timer(actx, 0));
397 [ - + ]: 2 : Assert(PQsocketPoll(actx->timerfd, 1, 0, deadline) > 0);
398 : 2 : is(timer_expired(actx), 1, "timer is expired");
399 : :
400 : : /* Register for read events and make the fd readable. */
401 [ - + ]: 2 : Assert(register_socket(NULL, rfd, in_event, actx, NULL) == 0);
402 [ - + ]: 2 : Assert(write(wfd, "x", 1) == 1);
403 [ - + ]: 2 : 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 [ - + ]: 2 : Assert(drain_timer_events(actx, &expired));
415 [ - + ]: 2 : Assert(drain_pipe(rfd, 1));
416 [ - + ]: 2 : Assert(comb_multiplexer(actx));
417 : :
418 : 2 : is(expired, 1, "drain_timer_events() reports expiration");
419 : 2 : is(timer_expired(actx), 0, "timer is no longer expired");
420 [ - + ]: 2 : mux_is_not_ready(actx->mux, "when fd is drained and timer reset");
421 : :
422 : : /* Stop listening. */
423 [ - + ]: 2 : Assert(register_socket(NULL, rfd, CURL_POLL_REMOVE, actx, NULL) == 0);
424 : :
425 : : /* Undo any unidirectional emulation. */
426 [ - + ]: 2 : if (bidirectional)
12 jchampion@postgresql 427 [ # # ]:UNC 0 : Assert(drain_pipe(wfd, bidi_pipe_size));
428 : : }
429 : :
430 : : /* Ensure comb_multiplexer() can handle multiple stale events. */
431 : : {
432 : : int rfd2,
433 : : wfd2;
434 : :
435 : : /* Create a second local pipe. */
12 jchampion@postgresql 436 [ - + ]:GNC 2 : Assert(pipe(pipefd) == 0);
437 : 2 : rfd2 = pipefd[0];
438 : 2 : wfd2 = pipefd[1];
439 : :
440 : : /* Make both rfds appear unidirectional if necessary. */
441 [ - + ]: 2 : if (bidirectional)
442 : : {
12 jchampion@postgresql 443 [ # # ]:UNC 0 : Assert((bidi_pipe_size = fill_pipe(rfd)) > 0);
444 [ # # ]: 0 : Assert(fill_pipe(rfd2) == bidi_pipe_size);
445 : : }
446 : :
447 : : /* Register for read events on both fds, and make them readable. */
12 jchampion@postgresql 448 [ - + ]:GNC 2 : Assert(register_socket(NULL, rfd, in_event, actx, NULL) == 0);
449 [ - + ]: 2 : Assert(register_socket(NULL, rfd2, in_event, actx, NULL) == 0);
450 : :
451 [ - + ]: 2 : Assert(write(wfd, "x", 1) == 1);
452 [ - + ]: 2 : Assert(write(wfd2, "x", 1) == 1);
453 : :
454 [ - + ]: 2 : 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 [ - + ]: 2 : Assert(drain_pipe(rfd, 1));
461 [ - + ]: 2 : Assert(drain_pipe(rfd2, 1));
462 [ - + ]: 2 : Assert(comb_multiplexer(actx));
463 [ - + ]: 2 : mux_is_not_ready(actx->mux, "when two fds are drained");
464 : :
465 : : /* Stop listening. */
466 [ - + ]: 2 : Assert(register_socket(NULL, rfd, CURL_POLL_REMOVE, actx, NULL) == 0);
467 [ - + ]: 2 : Assert(register_socket(NULL, rfd2, CURL_POLL_REMOVE, actx, NULL) == 0);
468 : :
469 : : /* Undo any unidirectional emulation. */
470 [ - + ]: 2 : if (bidirectional)
471 : : {
12 jchampion@postgresql 472 [ # # ]:UNC 0 : Assert(drain_pipe(wfd, bidi_pipe_size));
473 [ # # ]: 0 : Assert(drain_pipe(wfd2, bidi_pipe_size));
474 : : }
475 : :
12 jchampion@postgresql 476 :GNC 2 : close(rfd2);
477 : 2 : close(wfd2);
478 : : }
479 : : }
480 : :
481 : 1 : close(rfd);
482 : 1 : close(wfd);
483 : 1 : free_test_actx(actx);
484 : 1 : }
485 : :
486 : : int
487 : 1 : main(int argc, char *argv[])
488 : : {
489 : : const char *timeout;
490 : :
491 : : /* Grab the default timeout. */
492 : 1 : timeout = getenv("PG_TEST_TIMEOUT_DEFAULT");
493 [ - + ]: 1 : if (timeout)
494 : : {
12 jchampion@postgresql 495 :UNC 0 : int timeout_s = atoi(timeout);
496 : :
497 [ # # ]: 0 : if (timeout_s > 0)
498 : 0 : timeout_us = timeout_s * 1000 * 1000;
499 : : }
500 : :
501 : : /*
502 : : * Set up line buffering for our output, to let stderr interleave in the
503 : : * log files.
504 : : */
12 jchampion@postgresql 505 :GNC 1 : setvbuf(stdout, NULL, PG_IOLBF, 0);
506 : :
507 : 1 : test_set_timer();
508 : 1 : test_register_socket();
509 : :
510 : 1 : printf("1..%d\n", num_tests);
511 : 1 : return 0;
512 : : }
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 */
|