db467fdfdb0dbd08dae33b67337732ef3577bc8d
1 /*
2 * SysDB - t/unit/utils/dbi_test.c
3 * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
28 #if HAVE_CONFIG_H
29 # include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "testutils.h"
33 #include "utils/dbi.h"
35 #include <check.h>
36 #include <dbi/dbi.h>
38 #define TEST_MAGIC ((void *)0x1337)
40 /*
41 * private data-types
42 */
43 typedef union {
44 long long integer;
45 double decimal;
46 const char *string;
47 time_t datetime;
48 struct {
49 size_t length;
50 const unsigned char *datum;
51 } binary;
52 } mock_data_t;
54 typedef struct {
55 const char *name;
56 unsigned long long nrows;
57 unsigned long long current_row;
58 unsigned int nfields;
59 unsigned short *field_types;
60 char **field_names;
61 } mock_query_t;
63 /*
64 * private variables
65 */
67 static sdb_dbi_client_t *client;
69 /*
70 * mock queries
71 */
73 /* field definitions */
74 static unsigned short field_types[] = {
75 DBI_TYPE_INTEGER,
76 DBI_TYPE_DECIMAL,
77 DBI_TYPE_STRING,
78 DBI_TYPE_DATETIME,
79 DBI_TYPE_BINARY,
80 };
81 static char *field_names[] = {
82 "field0",
83 "field1",
84 "field2",
85 "field3",
86 "field4",
87 };
89 #define DATUM(p) ((const unsigned char *)(p))
90 static mock_data_t golden_data[][5] = {
91 {
92 { .integer = 1234 },
93 { .decimal = 1.234 },
94 { .string = "abcd" },
95 { .datetime = 0 },
96 { .binary = { 1, DATUM("a") } },
97 },
98 {
99 { .integer = 2345 },
100 { .decimal = 23.45 },
101 { .string = "bcde" },
102 { .datetime = 1 },
103 { .binary = { 4, DATUM("bcde") } },
104 },
105 {
106 { .integer = 3456 },
107 { .decimal = 345.6 },
108 { .string = "cd" },
109 { .datetime = 2 },
110 { .binary = { 0, DATUM(NULL) } },
111 },
112 {
113 { .integer = 4567 },
114 { .decimal = 4567 },
115 { .string = "d" },
116 { .datetime = 3 },
117 { .binary = { 13, DATUM("defghijklmnop") } },
118 },
119 {
120 { .integer = 5678 },
121 { .decimal = 56.78 },
122 { .string = "efgh" },
123 { .datetime = 4 },
124 { .binary = { 5, DATUM("efghi") } },
125 },
126 };
128 /* query definitions */
129 static mock_query_t mock_queries[] = {
130 { "mockquery0", 5, 1, 0, NULL, NULL },
131 { "mockquery1", 0, 0, 1, field_types, field_names },
132 { "mockquery2", 1, 1, 1, field_types, field_names },
133 { "mockquery3", 2, 1, 1, field_types, field_names },
134 { "mockquery4", 5, 1, 1, field_types, field_names },
135 { "mockquery5", 5, 1, 2, field_types, field_names },
136 { "mockquery6", 5, 1, 3, field_types, field_names },
137 { "mockquery7", 5, 1, 4, field_types, field_names },
138 { "mockquery8", 5, 1, 5, field_types, field_names },
139 };
141 static mock_query_t *current_query = NULL;
143 /*
144 * mocked functions
145 */
147 /* dbi_driver, dbi_conn, dbi_result are void pointers */
149 #if LIBDBI_VERSION < 900
150 typedef void *dbi_inst;
152 int
153 dbi_initialize_r(const char *driverdir, dbi_inst *pInst);
154 void
155 dbi_shutdown_r(dbi_inst inst);
156 dbi_driver
157 dbi_driver_open_r(const char *name, dbi_inst inst);
158 dbi_driver
159 dbi_driver_list_r(dbi_driver curr, dbi_inst inst);
160 #endif
162 int
163 dbi_initialize_r(const char __attribute__((unused)) *driverdir,
164 dbi_inst __attribute__((unused)) *pInst)
165 {
166 return 0;
167 } /* dbi_initialize_r */
169 void
170 dbi_shutdown_r(dbi_inst __attribute__((unused)) inst)
171 {
172 } /* dbi_shutdown_r */
174 dbi_driver
175 dbi_driver_open_r(const char *name, dbi_inst __attribute__((unused)) inst)
176 {
177 if (strcmp(name, "mockdriver"))
178 return NULL;
179 return (dbi_driver)"mockdriver";
180 } /* dbi_driver_open */
182 dbi_driver
183 dbi_driver_list_r(dbi_driver curr, dbi_inst __attribute__((unused)) inst)
184 {
185 if (!curr)
186 return "mockdriver";
187 return NULL;
188 } /* dbi_driver_list */
190 #if LIBDBI_VERSION < 900
191 int
192 dbi_initialize(const char *driverdir)
193 {
194 return dbi_initialize_r(driverdir, NULL);
195 } /* dbi_initialize */
197 /* for some weird reason, gcc and clang complain about a missing prototype for
198 * dbi_shutdown; however, the function is declared in dbi/dbi.h */
199 void
200 dbi_shutdown(void);
201 void
202 dbi_shutdown(void)
203 {
204 dbi_shutdown_r(NULL);
205 } /* dbi_shutdown */
207 dbi_driver
208 dbi_driver_open(const char *name)
209 {
210 return dbi_driver_open_r(name, NULL);
211 } /* dbi_driver_open */
213 dbi_driver
214 dbi_driver_list(dbi_driver curr)
215 {
216 return dbi_driver_list_r(curr, NULL);
217 } /* dbi_driver_list */
218 #endif
220 const char *
221 dbi_driver_get_name(dbi_driver driver)
222 {
223 return (const char *)driver;
224 } /* dbi_driver_get_name */
226 int
227 dbi_conn_set_option(dbi_conn __attribute__((unused)) conn,
228 const char __attribute__((unused)) *key,
229 const char __attribute__((unused)) *value)
230 {
231 return 0;
232 } /* dbi_conn_set_option */
234 const char *
235 dbi_conn_get_option_list(dbi_conn __attribute__((unused)) conn,
236 const char __attribute__((unused)) *key)
237 {
238 return NULL;
239 } /* dbi_conn_get_option_list */
241 dbi_conn
242 dbi_conn_open(dbi_driver driver)
243 {
244 if (strcmp((const char *)driver, "mockdriver"))
245 return NULL;
246 return (dbi_conn)"mockconnection";
247 } /* dbi_conn_open */
249 static unsigned long long dbi_conn_connect_called = 0;
250 int
251 dbi_conn_connect(dbi_conn conn)
252 {
253 ++dbi_conn_connect_called;
254 if (strcmp((const char *)conn, "mockconnection"))
255 return DBI_ERROR_NOCONN;
256 return 0;
257 } /* dbi_conn_connect */
259 int
260 dbi_conn_ping(dbi_conn conn)
261 {
262 if (strcmp((const char *)conn, "mockconnection"))
263 return 0;
264 return 1;
265 } /* dbi_conn_connect */
267 void
268 dbi_conn_close(dbi_conn __attribute__((unused)) conn)
269 {
270 return;
271 } /* dbi_conn_close */
273 int
274 dbi_conn_error(dbi_conn conn, const char **errmsg)
275 {
276 if ((! conn) || (strcmp((const char *)conn, "mockconnection")))
277 return DBI_ERROR_BADOBJECT;
278 if (errmsg)
279 *errmsg = "mockerror";
280 return DBI_ERROR_UNSUPPORTED;
281 } /* dbi_conn_error */
283 static unsigned long long dbi_conn_query_called = 0;
284 dbi_result
285 dbi_conn_query(dbi_conn conn, const char __attribute__((unused)) *statement)
286 {
287 size_t i;
289 ++dbi_conn_query_called;
290 if (strcmp((const char *)conn, "mockconnection"))
291 return NULL;
293 for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
294 if (!strcmp(mock_queries[i].name, statement)) {
295 current_query = &mock_queries[i];
296 return (dbi_result)current_query;
297 }
298 }
299 return NULL;
300 } /* dbi_conn_query */
302 unsigned long long
303 dbi_result_get_numrows(dbi_result res)
304 {
305 mock_query_t *q = res;
306 if (! q)
307 return DBI_ROW_ERROR;
308 return q->nrows;
309 } /* dbi_result_get_numrows */
311 unsigned int
312 dbi_result_get_numfields(dbi_result res)
313 {
314 mock_query_t *q = res;
315 if (! q)
316 return DBI_FIELD_ERROR;
317 return q->nfields;
318 } /* dbi_result_get_numfields */
320 unsigned short
321 dbi_result_get_field_type_idx(dbi_result res, unsigned int i)
322 {
323 mock_query_t *q = res;
324 if ((! q) || (i > q->nfields))
325 return DBI_TYPE_ERROR;
326 return q->field_types[i - 1];
327 } /* dbi_result_get_field_type_idx */
329 const char *
330 dbi_result_get_field_name(dbi_result res, unsigned int i)
331 {
332 mock_query_t *q = res;
333 if ((! q) || (i > q->nfields))
334 return NULL;
335 return q->field_names[i - 1];
336 } /* dbi_result_get_field_name */
338 int
339 dbi_result_seek_row(dbi_result res, unsigned long long n)
340 {
341 mock_query_t *q = res;
342 if ((! q) || (n > q->nrows))
343 return 0;
345 q->current_row = n;
346 return 1;
347 } /* dbi_result_seek_row */
349 static mock_data_t
350 get_golden_data(dbi_result res, unsigned int i) {
351 mock_query_t *q = res;
352 fail_unless(q != NULL, "dbi_result_get_*_idx() called with "
353 "NULL result data; expected valid result object");
355 /* this information is managed by seek_row and, thus,
356 * should never be invalid */
357 fail_unless(q->current_row && q->current_row <= q->nrows,
358 "INTERNAL ERROR: current row out of range");
360 fail_unless(i && i <= q->nfields,
361 "dbi_result_get_*_idx() called with index out of range; "
362 "got: %u; expected [1, %u]", i, q->nfields);
363 return golden_data[q->current_row - 1][i - 1];
364 } /* get_golden_data */
366 long long
367 dbi_result_get_longlong_idx(dbi_result res, unsigned int i)
368 {
369 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_INTEGER,
370 "dbi_result_get_longlong_idx() called for non-integer "
371 "column type %u", current_query->field_types[i - 1]);
372 return get_golden_data(res, i).integer;
373 } /* dbi_result_get_longlong_idx */
375 double
376 dbi_result_get_double_idx(dbi_result res, unsigned int i)
377 {
378 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_DECIMAL,
379 "dbi_result_get_double_idx() called for non-decimal "
380 "column type %u", current_query->field_types[i - 1]);
381 return get_golden_data(res, i).decimal;
382 } /* dbi_result_get_double_idx */
384 const char *
385 dbi_result_get_string_idx(dbi_result res, unsigned int i)
386 {
387 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_STRING,
388 "dbi_result_get_string_idx() called for non-string "
389 "column type %u", current_query->field_types[i - 1]);
390 return get_golden_data(res, i).string;
391 } /* dbi_result_get_string_idx */
393 char *
394 dbi_result_get_string_copy_idx(dbi_result res, unsigned int i)
395 {
396 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_STRING,
397 "dbi_result_get_string_copy_idx() called for non-string "
398 "column type %u", current_query->field_types[i - 1]);
399 if (! get_golden_data(res, i).string)
400 return NULL;
401 return strdup(get_golden_data(res, i).string);
402 } /* dbi_result_get_string_copy_idx */
404 time_t
405 dbi_result_get_datetime_idx(dbi_result res, unsigned int i)
406 {
407 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_DATETIME,
408 "dbi_result_get_datetime_idx() called for non-datetime "
409 "column type %u", current_query->field_types[i - 1]);
410 return get_golden_data(res, i).datetime;
411 } /* dbi_result_get_datetime_idx */
413 size_t
414 dbi_result_get_field_length_idx(dbi_result res, unsigned int i)
415 {
416 /* this will check if the parameters are valid */
417 get_golden_data(res, i);
419 switch (current_query->field_types[i - 1]) {
420 case DBI_TYPE_INTEGER:
421 return sizeof(long long);
422 break;
423 case DBI_TYPE_DECIMAL:
424 return sizeof(double);
425 break;
426 case DBI_TYPE_STRING:
427 return strlen(get_golden_data(res, i).string) + 1;
428 break;
429 case DBI_TYPE_DATETIME:
430 return sizeof(time_t);
431 break;
432 case DBI_TYPE_BINARY:
433 return get_golden_data(res, i).binary.length;
434 break;
435 }
437 fail("INTERNAL ERROR: dbi_result_get_field_length_idx() "
438 "called for unexpected field type %u",
439 current_query->field_types[i - 1]);
440 return 0;
441 } /* dbi_result_get_field_length_idx */
443 const unsigned char *
444 dbi_result_get_binary_idx(dbi_result res, unsigned int i)
445 {
446 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_BINARY,
447 "dbi_result_get_binary_idx() called for non-binary "
448 "column type %u", current_query->field_types[i - 1]);
449 return get_golden_data(res, i).binary.datum;
450 } /* dbi_result_get_binary_idx */
452 unsigned char *
453 dbi_result_get_binary_copy_idx(dbi_result res, unsigned int i)
454 {
455 const char *data;
456 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_BINARY,
457 "dbi_result_get_binary_copy_idx() called for non-binary "
458 "column type %u", current_query->field_types[i - 1]);
459 data = (const char *)get_golden_data(res, i).binary.datum;
460 if (! data)
461 return NULL;
462 return (unsigned char *)strdup(data);
463 } /* dbi_result_get_binary_copy_idx */
465 static unsigned long long dbi_result_free_called = 0;
466 int
467 dbi_result_free(dbi_result res)
468 {
469 mock_query_t *q = res;
471 ++dbi_result_free_called;
472 if (! q)
473 return -1;
475 current_query = NULL;
476 return 0;
477 } /* dbi_result_free */
479 /*
480 * private helper functions
481 */
483 static void
484 setup(void)
485 {
486 client = sdb_dbi_client_create("mockdriver", "mockdatabase");
487 fail_unless(client != NULL,
488 "sdb_dbi_client_create() = NULL; expected client object");
490 dbi_conn_connect_called = 0;
491 } /* setup */
493 static void
494 connect(void)
495 {
496 int check = sdb_dbi_client_connect(client);
497 fail_unless(check == 0,
498 "sdb_dbi_client_connect() = %i; expected: 0", check);
499 } /* connect */
501 static void
502 teardown(void)
503 {
504 sdb_dbi_client_destroy(client);
505 client = NULL;
506 } /* teardown */
508 static unsigned long long query_callback_called = 0;
509 static int
510 query_callback(sdb_dbi_client_t *c,
511 size_t n, sdb_data_t *data, sdb_object_t *user_data)
512 {
513 size_t i;
515 ++query_callback_called;
517 fail_unless(c == client,
518 "query callback received unexpected client = %p; "
519 "expected: %p", c, client);
520 fail_unless(n == current_query->nfields,
521 "query callback received n = %i; expected: %i",
522 n, current_query->nfields);
523 fail_unless(data != NULL,
524 "query callback received data = NULL; expected: valid data");
525 fail_unless(user_data == TEST_MAGIC,
526 "query callback received user_data = %p; expected: %p",
527 user_data, TEST_MAGIC);
529 for (i = 0; i < n; ++i) {
530 int expected_type = DBI_TYPE_TO_SDB(current_query->field_types[i]);
531 mock_data_t expected_data;
533 fail_unless((int)data[i].type == expected_type,
534 "query callback received unexpected type %i for "
535 "column %zu; expected: %i", data[i].type, i,
536 expected_type);
538 expected_data = golden_data[current_query->current_row - 1][i];
539 switch (expected_type) {
540 case SDB_TYPE_INTEGER:
541 fail_unless(data[i].data.integer == expected_data.integer,
542 "query callback received unexpected data %lli "
543 "for column %zu; expected: %lli",
544 data[i].data.integer, i, expected_data.integer);
545 break;
546 case SDB_TYPE_DECIMAL:
547 fail_unless(data[i].data.decimal == expected_data.decimal,
548 "query callback received unexpected data %g "
549 "for column %zu; expected: %g",
550 data[i].data.decimal, i, expected_data.decimal);
551 break;
552 case SDB_TYPE_STRING:
553 fail_unless(!strcmp(data[i].data.string, expected_data.string),
554 "query callback received unexpected data %s "
555 "for column %zu; expected: %s",
556 data[i].data.string, i, expected_data.string);
557 break;
558 case SDB_TYPE_DATETIME:
559 fail_unless(data[i].data.datetime
560 == SECS_TO_SDB_TIME(expected_data.datetime),
561 "query callback received unexpected data "PRIsdbTIME
562 " for column %zu; expected: "PRIsdbTIME,
563 data[i].data.integer, i,
564 SECS_TO_SDB_TIME(expected_data.integer));
565 break;
566 case SDB_TYPE_BINARY:
567 fail_unless(data[i].data.binary.length ==
568 expected_data.binary.length,
569 "query callback received unexpected "
570 "binary data length %zu for column %zu; "
571 "expected: %lli", data[i].data.binary.length, i,
572 expected_data.binary.length);
573 fail_unless(!memcmp(data[i].data.binary.datum,
574 expected_data.binary.datum,
575 expected_data.binary.length),
576 "query callback received unexpected binary data %p "
577 "for column %zu; expected: %p",
578 data[i].data.binary.datum, i,
579 expected_data.binary.datum);
580 break;
581 default:
582 fail("INTERNAL ERROR: query callback received "
583 "unknown type %i for column %zu",
584 expected_type, i);
585 }
586 }
587 return 0;
588 } /* query_callback */
590 /*
591 * tests
592 */
594 START_TEST(test_sdb_dbi_client_connect)
595 {
596 int check = sdb_dbi_client_connect(client);
597 fail_unless(check == 0,
598 "sdb_dbi_client_connect() = %i; expected: 0", check);
600 fail_unless(dbi_conn_connect_called == 1,
601 "sdb_dbi_client_create() called dbi_conn_connect %i times; "
602 "expected: 1", dbi_conn_connect_called);
603 }
604 END_TEST
606 START_TEST(test_sdb_dbi_client_check_conn)
607 {
608 int check = sdb_dbi_client_check_conn(client);
609 fail_unless(check == 0,
610 "sdb_dbi_client_check_conn() = %i; expected: 0", check);
612 /* the first call will actually connect to the database */
613 fail_unless(dbi_conn_connect_called == 1,
614 "sdb_dbi_client_check_conn() called dbi_conn_connect %i times; "
615 "expected: 1", dbi_conn_connect_called);
617 dbi_conn_connect_called = 0;
618 check = sdb_dbi_client_check_conn(client);
619 fail_unless(check == 0,
620 "sdb_dbi_client_check_conn() = %i; expected: 0", check);
622 /* should not reconnect */
623 fail_unless(dbi_conn_connect_called == 0,
624 "sdb_dbi_client_check_conn() called dbi_conn_connect %i time(s); "
625 "expected: 0", dbi_conn_connect_called);
626 }
627 END_TEST
629 START_TEST(test_sdb_dbi_exec_query)
630 {
631 size_t i;
633 int check = sdb_dbi_exec_query(client, "mockquery0", query_callback,
634 /* user_data = */ TEST_MAGIC, /* n = */ 0);
635 /* not connected yet */
636 fail_unless(check < 0,
637 "sdb_dbi_exec_query() = %i; expected: < 0", check);
639 connect();
641 for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
642 mock_query_t *q = &mock_queries[i];
644 unsigned long long expected_callback_calls = 0;
646 dbi_conn_query_called = 0;
647 query_callback_called = 0;
648 dbi_result_free_called = 0;
650 /* sdb_dbi_exec_query will only use as many type arguments are needed,
651 * so we can safely pass in the maximum number of arguments required
652 * on each call */
653 check = sdb_dbi_exec_query(client, q->name, query_callback,
654 /* user_data = */ TEST_MAGIC, /* n = */ (int)q->nfields,
655 SDB_TYPE_INTEGER, SDB_TYPE_DECIMAL, SDB_TYPE_STRING,
656 SDB_TYPE_DATETIME, SDB_TYPE_BINARY);
657 fail_unless(check == 0,
658 "sdb_dbi_exec_query() = %i; expected: 0", check);
660 fail_unless(dbi_conn_query_called == 1,
661 "sdb_dbi_exec_query() called dbi_conn_query %i times; "
662 "expected: 1", dbi_conn_query_called);
664 if (q->nfields)
665 expected_callback_calls = q->nrows;
667 fail_unless(query_callback_called == expected_callback_calls,
668 "sdb_dbi_exec_query() did not call the registered callback "
669 "for each result row; got %i call%s; expected: 0",
670 query_callback_called,
671 (query_callback_called == 1) ? "" : "s");
673 fail_unless(dbi_result_free_called == 1,
674 "sdb_dbi_exec_query() did not free the query result object");
675 }
676 }
677 END_TEST
679 TEST_MAIN("utils::dbi")
680 {
681 TCase *tc = tcase_create("core");
682 tcase_add_checked_fixture(tc, setup, teardown);
683 tcase_add_test(tc, test_sdb_dbi_client_connect);
684 tcase_add_test(tc, test_sdb_dbi_client_check_conn);
685 tcase_add_test(tc, test_sdb_dbi_exec_query);
686 ADD_TCASE(tc);
687 }
688 TEST_MAIN_END
690 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */