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 "libsysdb_test.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 int
150 dbi_initialize_r(const char __attribute__((unused)) *driverdir,
151 dbi_inst __attribute__((unused)) *pInst)
152 {
153 return 0;
154 } /* dbi_initialize_r */
156 void
157 dbi_shutdown_r(dbi_inst __attribute__((unused)) inst)
158 {
159 } /* dbi_shutdown_r */
161 dbi_driver
162 dbi_driver_open_r(const char *name, dbi_inst __attribute__((unused)) inst)
163 {
164 if (strcmp(name, "mockdriver"))
165 return NULL;
166 return (dbi_driver)strdup(name);
167 } /* dbi_driver_open */
169 dbi_driver
170 dbi_driver_list_r(dbi_driver curr, dbi_inst __attribute__((unused)) inst)
171 {
172 if (!curr)
173 return "mockdriver";
174 return NULL;
175 } /* dbi_driver_list */
177 const char *
178 dbi_driver_get_name(dbi_driver driver)
179 {
180 return (const char *)driver;
181 } /* dbi_driver_get_name */
183 int
184 dbi_conn_set_option(dbi_conn __attribute__((unused)) conn,
185 const char __attribute__((unused)) *key,
186 const char __attribute__((unused)) *value)
187 {
188 return 0;
189 } /* dbi_conn_set_option */
191 const char *
192 dbi_conn_get_option_list(dbi_conn __attribute__((unused)) conn,
193 const char __attribute__((unused)) *key)
194 {
195 return NULL;
196 } /* dbi_conn_get_option_list */
198 dbi_conn
199 dbi_conn_open(dbi_driver driver)
200 {
201 if (strcmp((const char *)driver, "mockdriver"))
202 return NULL;
203 return (dbi_conn)"mockconnection";
204 } /* dbi_conn_open */
206 static unsigned long long dbi_conn_connect_called = 0;
207 int
208 dbi_conn_connect(dbi_conn conn)
209 {
210 ++dbi_conn_connect_called;
211 if (strcmp((const char *)conn, "mockconnection"))
212 return DBI_ERROR_NOCONN;
213 return 0;
214 } /* dbi_conn_connect */
216 int
217 dbi_conn_ping(dbi_conn conn)
218 {
219 if (strcmp((const char *)conn, "mockconnection"))
220 return 0;
221 return 1;
222 } /* dbi_conn_connect */
224 void
225 dbi_conn_close(dbi_conn __attribute__((unused)) conn)
226 {
227 return;
228 } /* dbi_conn_close */
230 int
231 dbi_conn_error(dbi_conn conn, const char **errmsg)
232 {
233 if ((! conn) || (strcmp((const char *)conn, "mockconnection")))
234 return DBI_ERROR_BADOBJECT;
235 if (errmsg)
236 *errmsg = "mockerror";
237 return DBI_ERROR_UNSUPPORTED;
238 } /* dbi_conn_error */
240 static unsigned long long dbi_conn_query_called = 0;
241 dbi_result
242 dbi_conn_query(dbi_conn conn, const char __attribute__((unused)) *statement)
243 {
244 size_t i;
246 ++dbi_conn_query_called;
247 if (strcmp((const char *)conn, "mockconnection"))
248 return NULL;
250 for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
251 if (!strcmp(mock_queries[i].name, statement)) {
252 current_query = &mock_queries[i];
253 return (dbi_result)current_query;
254 }
255 }
256 return NULL;
257 } /* dbi_conn_query */
259 unsigned long long
260 dbi_result_get_numrows(dbi_result res)
261 {
262 mock_query_t *q = res;
263 if (! q)
264 return DBI_ROW_ERROR;
265 return q->nrows;
266 } /* dbi_result_get_numrows */
268 unsigned int
269 dbi_result_get_numfields(dbi_result res)
270 {
271 mock_query_t *q = res;
272 if (! q)
273 return DBI_FIELD_ERROR;
274 return q->nfields;
275 } /* dbi_result_get_numfields */
277 unsigned short
278 dbi_result_get_field_type_idx(dbi_result res, unsigned int i)
279 {
280 mock_query_t *q = res;
281 if ((! q) || (i > q->nfields))
282 return DBI_TYPE_ERROR;
283 return q->field_types[i - 1];
284 } /* dbi_result_get_field_type_idx */
286 const char *
287 dbi_result_get_field_name(dbi_result res, unsigned int i)
288 {
289 mock_query_t *q = res;
290 if ((! q) || (i > q->nfields))
291 return NULL;
292 return q->field_names[i - 1];
293 } /* dbi_result_get_field_name */
295 int
296 dbi_result_seek_row(dbi_result res, unsigned long long n)
297 {
298 mock_query_t *q = res;
299 if ((! q) || (n > q->nrows))
300 return 0;
302 q->current_row = n;
303 return 1;
304 } /* dbi_result_seek_row */
306 static mock_data_t
307 get_golden_data(dbi_result res, unsigned int i) {
308 mock_query_t *q = res;
309 fail_unless(q != NULL, "dbi_result_get_*_idx() called with "
310 "NULL result data; expected valid result object");
312 /* this information is managed by seek_row and, thus,
313 * should never be invalid */
314 fail_unless(q->current_row && q->current_row <= q->nrows,
315 "INTERNAL ERROR: current row out of range");
317 fail_unless(i && i <= q->nfields,
318 "dbi_result_get_*_idx() called with index out of range; "
319 "got: %u; expected [1, %u]", i, q->nfields);
320 return golden_data[q->current_row - 1][i - 1];
321 } /* get_golden_data */
323 long long
324 dbi_result_get_longlong_idx(dbi_result res, unsigned int i)
325 {
326 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_INTEGER,
327 "dbi_result_get_longlong_idx() called for non-integer "
328 "column type %u", current_query->field_types[i - 1]);
329 return get_golden_data(res, i).integer;
330 } /* dbi_result_get_longlong_idx */
332 double
333 dbi_result_get_double_idx(dbi_result res, unsigned int i)
334 {
335 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_DECIMAL,
336 "dbi_result_get_double_idx() called for non-decimal "
337 "column type %u", current_query->field_types[i - 1]);
338 return get_golden_data(res, i).decimal;
339 } /* dbi_result_get_double_idx */
341 const char *
342 dbi_result_get_string_idx(dbi_result res, unsigned int i)
343 {
344 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_STRING,
345 "dbi_result_get_string_idx() called for non-string "
346 "column type %u", current_query->field_types[i - 1]);
347 return get_golden_data(res, i).string;
348 } /* dbi_result_get_string_idx */
350 char *
351 dbi_result_get_string_copy_idx(dbi_result res, unsigned int i)
352 {
353 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_STRING,
354 "dbi_result_get_string_copy_idx() called for non-string "
355 "column type %u", current_query->field_types[i - 1]);
356 if (! get_golden_data(res, i).string)
357 return NULL;
358 return strdup(get_golden_data(res, i).string);
359 } /* dbi_result_get_string_copy_idx */
361 time_t
362 dbi_result_get_datetime_idx(dbi_result res, unsigned int i)
363 {
364 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_DATETIME,
365 "dbi_result_get_datetime_idx() called for non-datetime "
366 "column type %u", current_query->field_types[i - 1]);
367 return get_golden_data(res, i).datetime;
368 } /* dbi_result_get_datetime_idx */
370 size_t
371 dbi_result_get_field_length_idx(dbi_result res, unsigned int i)
372 {
373 /* this will check if the parameters are valid */
374 get_golden_data(res, i);
376 switch (current_query->field_types[i - 1]) {
377 case DBI_TYPE_INTEGER:
378 return sizeof(long long);
379 break;
380 case DBI_TYPE_DECIMAL:
381 return sizeof(double);
382 break;
383 case DBI_TYPE_STRING:
384 return strlen(get_golden_data(res, i).string) + 1;
385 break;
386 case DBI_TYPE_DATETIME:
387 return sizeof(time_t);
388 break;
389 case DBI_TYPE_BINARY:
390 return get_golden_data(res, i).binary.length;
391 break;
392 }
394 fail("INTERNAL ERROR: dbi_result_get_field_length_idx() "
395 "called for unexpected field type %u",
396 current_query->field_types[i - 1]);
397 return 0;
398 } /* dbi_result_get_field_length_idx */
400 const unsigned char *
401 dbi_result_get_binary_idx(dbi_result res, unsigned int i)
402 {
403 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_BINARY,
404 "dbi_result_get_binary_idx() called for non-binary "
405 "column type %u", current_query->field_types[i - 1]);
406 return get_golden_data(res, i).binary.datum;
407 } /* dbi_result_get_binary_idx */
409 unsigned char *
410 dbi_result_get_binary_copy_idx(dbi_result res, unsigned int i)
411 {
412 const char *data;
413 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_BINARY,
414 "dbi_result_get_binary_copy_idx() called for non-binary "
415 "column type %u", current_query->field_types[i - 1]);
416 data = (const char *)get_golden_data(res, i).binary.datum;
417 if (! data)
418 return NULL;
419 return (unsigned char *)strdup(data);
420 } /* dbi_result_get_binary_copy_idx */
422 static unsigned long long dbi_result_free_called = 0;
423 int
424 dbi_result_free(dbi_result res)
425 {
426 mock_query_t *q = res;
428 ++dbi_result_free_called;
429 if (! q)
430 return -1;
432 current_query = NULL;
433 return 0;
434 } /* dbi_result_free */
436 /*
437 * private helper functions
438 */
440 static void
441 setup(void)
442 {
443 client = sdb_dbi_client_create("mockdriver", "mockdatabase");
444 fail_unless(client != NULL,
445 "sdb_dbi_client_create() = NULL; expected client object");
447 dbi_conn_connect_called = 0;
448 } /* setup */
450 static void
451 connect(void)
452 {
453 int check = sdb_dbi_client_connect(client);
454 fail_unless(check == 0,
455 "sdb_dbi_client_connect() = %i; expected: 0", check);
456 } /* connect */
458 static void
459 teardown(void)
460 {
461 sdb_dbi_client_destroy(client);
462 client = NULL;
463 } /* teardown */
465 static unsigned long long query_callback_called = 0;
466 static int
467 query_callback(sdb_dbi_client_t *c,
468 size_t n, sdb_data_t *data, sdb_object_t *user_data)
469 {
470 size_t i;
472 ++query_callback_called;
474 fail_unless(c == client,
475 "query callback received unexpected client = %p; "
476 "expected: %p", c, client);
477 fail_unless(n == current_query->nfields,
478 "query callback received n = %i; expected: %i",
479 n, current_query->nfields);
480 fail_unless(data != NULL,
481 "query callback received data = NULL; expected: valid data");
482 fail_unless(user_data == TEST_MAGIC,
483 "query callback received user_data = %p; expected: %p",
484 user_data, TEST_MAGIC);
486 for (i = 0; i < n; ++i) {
487 int expected_type = DBI_TYPE_TO_SC(current_query->field_types[i]);
488 mock_data_t expected_data;
490 fail_unless((int)data[i].type == expected_type,
491 "query callback received unexpected type %i for "
492 "column %zu; expected: %i", data[i].type, i,
493 expected_type);
495 expected_data = golden_data[current_query->current_row - 1][i];
496 switch (expected_type) {
497 case SDB_TYPE_INTEGER:
498 fail_unless(data[i].data.integer == expected_data.integer,
499 "query callback received unexpected data %lli "
500 "for column %zu; expected: %lli",
501 data[i].data.integer, i, expected_data.integer);
502 break;
503 case SDB_TYPE_DECIMAL:
504 fail_unless(data[i].data.decimal == expected_data.decimal,
505 "query callback received unexpected data %g "
506 "for column %zu; expected: %g",
507 data[i].data.decimal, i, expected_data.decimal);
508 break;
509 case SDB_TYPE_STRING:
510 fail_unless(!strcmp(data[i].data.string, expected_data.string),
511 "query callback received unexpected data %s "
512 "for column %zu; expected: %s",
513 data[i].data.string, i, expected_data.string);
514 break;
515 case SDB_TYPE_DATETIME:
516 fail_unless(data[i].data.datetime
517 == SECS_TO_SDB_TIME(expected_data.datetime),
518 "query callback received unexpected data "PRIscTIME
519 " for column %zu; expected: "PRIscTIME,
520 data[i].data.integer, i,
521 SECS_TO_SDB_TIME(expected_data.integer));
522 break;
523 case SDB_TYPE_BINARY:
524 fail_unless(data[i].data.binary.length ==
525 expected_data.binary.length,
526 "query callback received unexpected "
527 "binary data length %zu for column %zu; "
528 "expected: %lli", data[i].data.binary.length, i,
529 expected_data.binary.length);
530 fail_unless(!memcmp(data[i].data.binary.datum,
531 expected_data.binary.datum,
532 expected_data.binary.length),
533 "query callback received unexpected binary data %p "
534 "for column %zu; expected: %p",
535 data[i].data.binary.datum, i,
536 expected_data.binary.datum);
537 break;
538 default:
539 fail("INTERNAL ERROR: query callback received "
540 "unknown type %i for column %zu",
541 expected_type, i);
542 }
543 }
544 return 0;
545 } /* query_callback */
547 /*
548 * tests
549 */
551 START_TEST(test_sdb_dbi_client_connect)
552 {
553 int check = sdb_dbi_client_connect(client);
554 fail_unless(check == 0,
555 "sdb_dbi_client_connect() = %i; expected: 0", check);
557 fail_unless(dbi_conn_connect_called == 1,
558 "sdb_dbi_client_create() called dbi_conn_connect %i times; "
559 "expected: 1", dbi_conn_connect_called);
560 }
561 END_TEST
563 START_TEST(test_sdb_dbi_client_check_conn)
564 {
565 int check = sdb_dbi_client_check_conn(client);
566 fail_unless(check == 0,
567 "sdb_dbi_client_check_conn() = %i; expected: 0", check);
569 /* the first call will actually connect to the database */
570 fail_unless(dbi_conn_connect_called == 1,
571 "sdb_dbi_client_check_conn() called dbi_conn_connect %i times; "
572 "expected: 1", dbi_conn_connect_called);
574 dbi_conn_connect_called = 0;
575 check = sdb_dbi_client_check_conn(client);
576 fail_unless(check == 0,
577 "sdb_dbi_client_check_conn() = %i; expected: 0", check);
579 /* should not reconnect */
580 fail_unless(dbi_conn_connect_called == 0,
581 "sdb_dbi_client_check_conn() called dbi_conn_connect %i time(s); "
582 "expected: 0", dbi_conn_connect_called);
583 }
584 END_TEST
586 START_TEST(test_sdb_dbi_exec_query)
587 {
588 size_t i;
590 int check = sdb_dbi_exec_query(client, "mockquery0", query_callback,
591 /* user_data = */ TEST_MAGIC, /* n = */ 0);
592 /* not connected yet */
593 fail_unless(check < 0,
594 "sdb_dbi_exec_query() = %i; expected: < 0", check);
596 connect();
598 for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
599 mock_query_t *q = &mock_queries[i];
601 unsigned long long expected_callback_calls = 0;
603 dbi_conn_query_called = 0;
604 query_callback_called = 0;
605 dbi_result_free_called = 0;
607 /* sdb_dbi_exec_query will only use as many type arguments are needed,
608 * so we can safely pass in the maximum number of arguments required
609 * on each call */
610 check = sdb_dbi_exec_query(client, q->name, query_callback,
611 /* user_data = */ TEST_MAGIC, /* n = */ (int)q->nfields,
612 SDB_TYPE_INTEGER, SDB_TYPE_DECIMAL, SDB_TYPE_STRING,
613 SDB_TYPE_DATETIME, SDB_TYPE_BINARY);
614 fail_unless(check == 0,
615 "sdb_dbi_exec_query() = %i; expected: 0", check);
617 fail_unless(dbi_conn_query_called == 1,
618 "sdb_dbi_exec_query() called dbi_conn_query %i times; "
619 "expected: 1", dbi_conn_query_called);
621 if (q->nfields)
622 expected_callback_calls = q->nrows;
624 fail_unless(query_callback_called == expected_callback_calls,
625 "sdb_dbi_exec_query() did not call the registered callback "
626 "for each result row; got %i call%s; expected: 0",
627 query_callback_called,
628 (query_callback_called == 1) ? "" : "s");
630 fail_unless(dbi_result_free_called == 1,
631 "sdb_dbi_exec_query() did not free the query result object");
632 }
633 }
634 END_TEST
636 /*
637 * test API
638 */
640 Suite *
641 util_dbi_suite(void)
642 {
643 Suite *s = suite_create("utils::dbi");
644 TCase *tc;
646 tc = tcase_create("core");
647 tcase_add_checked_fixture(tc, setup, teardown);
648 tcase_add_test(tc, test_sdb_dbi_client_connect);
649 tcase_add_test(tc, test_sdb_dbi_client_check_conn);
650 tcase_add_test(tc, test_sdb_dbi_exec_query);
651 suite_add_tcase(s, tc);
653 return s;
654 } /* util_llist_suite */
656 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */