1 /*
2 * SysDB - t/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 #include "libsysdb_test.h"
29 #include "utils/dbi.h"
31 #include <check.h>
32 #include <dbi/dbi.h>
34 #define TEST_MAGIC ((void *)0x1337)
36 /*
37 * private data-types
38 */
39 typedef union {
40 long long integer;
41 double decimal;
42 const char *string;
43 time_t datetime;
44 struct {
45 size_t length;
46 const unsigned char *datum;
47 } binary;
48 } mock_data_t;
50 typedef struct {
51 const char *name;
52 unsigned long long nrows;
53 unsigned long long current_row;
54 unsigned int nfields;
55 unsigned short *field_types;
56 char **field_names;
57 } mock_query_t;
59 /*
60 * private variables
61 */
63 static sdb_dbi_client_t *client;
65 /*
66 * mock queries
67 */
69 /* field definitions */
70 static unsigned short field_types[] = {
71 DBI_TYPE_INTEGER,
72 DBI_TYPE_DECIMAL,
73 DBI_TYPE_STRING,
74 DBI_TYPE_DATETIME,
75 DBI_TYPE_BINARY,
76 };
77 static char *field_names[] = {
78 "field0",
79 "field1",
80 "field2",
81 "field3",
82 "field4",
83 };
85 #define DATUM(p) ((const unsigned char *)(p))
86 static mock_data_t golden_data[][5] = {
87 {
88 { .integer = 1234 },
89 { .decimal = 1.234 },
90 { .string = "abcd" },
91 { .datetime = 0 },
92 { .binary = { 1, DATUM("a") } },
93 },
94 {
95 { .integer = 2345 },
96 { .decimal = 23.45 },
97 { .string = "bcde" },
98 { .datetime = 1 },
99 { .binary = { 4, DATUM("bcde") } },
100 },
101 {
102 { .integer = 3456 },
103 { .decimal = 345.6 },
104 { .string = "cd" },
105 { .datetime = 2 },
106 { .binary = { 0, DATUM(NULL) } },
107 },
108 {
109 { .integer = 4567 },
110 { .decimal = 4567 },
111 { .string = "d" },
112 { .datetime = 3 },
113 { .binary = { 13, DATUM("defghijklmnop") } },
114 },
115 {
116 { .integer = 5678 },
117 { .decimal = 56.78 },
118 { .string = "efgh" },
119 { .datetime = 4 },
120 { .binary = { 5, DATUM("efghi") } },
121 },
122 };
124 /* query definitions */
125 static mock_query_t mock_queries[] = {
126 { "mockquery0", 5, 1, 0, NULL, NULL },
127 { "mockquery1", 0, 0, 1, field_types, field_names },
128 { "mockquery2", 1, 1, 1, field_types, field_names },
129 { "mockquery3", 2, 1, 1, field_types, field_names },
130 { "mockquery4", 5, 1, 1, field_types, field_names },
131 { "mockquery5", 5, 1, 2, field_types, field_names },
132 { "mockquery6", 5, 1, 3, field_types, field_names },
133 { "mockquery7", 5, 1, 4, field_types, field_names },
134 { "mockquery8", 5, 1, 5, field_types, field_names },
135 };
137 static mock_query_t *current_query = NULL;
139 /*
140 * mocked functions
141 */
143 /* dbi_driver, dbi_conn, dbi_result are void pointers */
145 dbi_driver
146 dbi_driver_open(const char *name)
147 {
148 if (strcmp(name, "mockdriver"))
149 return NULL;
150 return (dbi_driver)strdup(name);
151 } /* dbi_driver_open */
153 dbi_driver
154 dbi_driver_list(dbi_driver curr)
155 {
156 if (!curr)
157 return "mockdriver";
158 return NULL;
159 } /* dbi_driver_list */
161 const char *
162 dbi_driver_get_name(dbi_driver driver)
163 {
164 return (const char *)driver;
165 } /* dbi_driver_get_name */
167 int
168 dbi_conn_set_option(dbi_conn __attribute__((unused)) conn,
169 const char __attribute__((unused)) *key,
170 const char __attribute__((unused)) *value)
171 {
172 return 0;
173 } /* dbi_conn_set_option */
175 const char *
176 dbi_conn_get_option_list(dbi_conn __attribute__((unused)) conn,
177 const char __attribute__((unused)) *key)
178 {
179 return NULL;
180 } /* dbi_conn_get_option_list */
182 dbi_conn
183 dbi_conn_open(dbi_driver driver)
184 {
185 if (strcmp((const char *)driver, "mockdriver"))
186 return NULL;
187 return (dbi_conn)"mockconnection";
188 } /* dbi_conn_open */
190 static unsigned long long dbi_conn_connect_called = 0;
191 int
192 dbi_conn_connect(dbi_conn conn)
193 {
194 ++dbi_conn_connect_called;
195 if (strcmp((const char *)conn, "mockconnection"))
196 return DBI_ERROR_NOCONN;
197 return 0;
198 } /* dbi_conn_connect */
200 int
201 dbi_conn_ping(dbi_conn conn)
202 {
203 if (strcmp((const char *)conn, "mockconnection"))
204 return 0;
205 return 1;
206 } /* dbi_conn_connect */
208 void
209 dbi_conn_close(dbi_conn __attribute__((unused)) conn)
210 {
211 return;
212 } /* dbi_conn_close */
214 int
215 dbi_conn_error(dbi_conn conn, const char **errmsg)
216 {
217 if ((! conn) || (strcmp((const char *)conn, "mockconnection")))
218 return DBI_ERROR_BADOBJECT;
219 if (errmsg)
220 *errmsg = "mockerror";
221 return DBI_ERROR_UNSUPPORTED;
222 } /* dbi_conn_error */
224 static unsigned long long dbi_conn_query_called = 0;
225 dbi_result
226 dbi_conn_query(dbi_conn conn, const char __attribute__((unused)) *statement)
227 {
228 size_t i;
230 ++dbi_conn_query_called;
231 if (strcmp((const char *)conn, "mockconnection"))
232 return NULL;
234 for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
235 if (!strcmp(mock_queries[i].name, statement)) {
236 current_query = &mock_queries[i];
237 return (dbi_result)current_query;
238 }
239 }
240 return NULL;
241 } /* dbi_conn_query */
243 unsigned long long
244 dbi_result_get_numrows(dbi_result res)
245 {
246 mock_query_t *q = res;
247 if (! q)
248 return DBI_ROW_ERROR;
249 return q->nrows;
250 } /* dbi_result_get_numrows */
252 unsigned int
253 dbi_result_get_numfields(dbi_result res)
254 {
255 mock_query_t *q = res;
256 if (! q)
257 return DBI_FIELD_ERROR;
258 return q->nfields;
259 } /* dbi_result_get_numfields */
261 unsigned short
262 dbi_result_get_field_type_idx(dbi_result res, unsigned int i)
263 {
264 mock_query_t *q = res;
265 if ((! q) || (i > q->nfields))
266 return DBI_TYPE_ERROR;
267 return q->field_types[i - 1];
268 } /* dbi_result_get_field_type_idx */
270 const char *
271 dbi_result_get_field_name(dbi_result res, unsigned int i)
272 {
273 mock_query_t *q = res;
274 if ((! q) || (i > q->nfields))
275 return NULL;
276 return q->field_names[i - 1];
277 } /* dbi_result_get_field_name */
279 int
280 dbi_result_seek_row(dbi_result res, unsigned long long n)
281 {
282 mock_query_t *q = res;
283 if ((! q) || (n > q->nrows))
284 return 0;
286 q->current_row = n;
287 return 1;
288 } /* dbi_result_seek_row */
290 static mock_data_t
291 get_golden_data(dbi_result res, unsigned int i) {
292 mock_query_t *q = res;
293 fail_unless(q != NULL, "dbi_result_get_*_idx() called with "
294 "NULL result data; expected valid result object");
296 /* this information is managed by seek_row and, thus,
297 * should never be invalid */
298 fail_unless(q->current_row && q->current_row <= q->nrows,
299 "INTERNAL ERROR: current row out of range");
301 fail_unless(i && i <= q->nfields,
302 "dbi_result_get_*_idx() called with index out of range; "
303 "got: %u; expected [1, %u]", i, q->nfields);
304 return golden_data[q->current_row - 1][i - 1];
305 } /* get_golden_data */
307 long long
308 dbi_result_get_longlong_idx(dbi_result res, unsigned int i)
309 {
310 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_INTEGER,
311 "dbi_result_get_longlong_idx() called for non-integer "
312 "column type %u", current_query->field_types[i - 1]);
313 return get_golden_data(res, i).integer;
314 } /* dbi_result_get_longlong_idx */
316 double
317 dbi_result_get_double_idx(dbi_result res, unsigned int i)
318 {
319 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_DECIMAL,
320 "dbi_result_get_double_idx() called for non-decimal "
321 "column type %u", current_query->field_types[i - 1]);
322 return get_golden_data(res, i).decimal;
323 } /* dbi_result_get_double_idx */
325 const char *
326 dbi_result_get_string_idx(dbi_result res, unsigned int i)
327 {
328 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_STRING,
329 "dbi_result_get_string_idx() called for non-string "
330 "column type %u", current_query->field_types[i - 1]);
331 return get_golden_data(res, i).string;
332 } /* dbi_result_get_string_idx */
334 time_t
335 dbi_result_get_datetime_idx(dbi_result res, unsigned int i)
336 {
337 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_DATETIME,
338 "dbi_result_get_datetime_idx() called for non-datetime "
339 "column type %u", current_query->field_types[i - 1]);
340 return get_golden_data(res, i).datetime;
341 } /* dbi_result_get_datetime_idx */
343 size_t
344 dbi_result_get_field_length_idx(dbi_result res, unsigned int i)
345 {
346 /* this will check if the parameters are valid */
347 get_golden_data(res, i);
349 switch (current_query->field_types[i - 1]) {
350 case DBI_TYPE_INTEGER:
351 return sizeof(long long);
352 break;
353 case DBI_TYPE_DECIMAL:
354 return sizeof(double);
355 break;
356 case DBI_TYPE_STRING:
357 return strlen(get_golden_data(res, i).string) + 1;
358 break;
359 case DBI_TYPE_DATETIME:
360 return sizeof(time_t);
361 break;
362 case DBI_TYPE_BINARY:
363 return get_golden_data(res, i).binary.length;
364 break;
365 }
367 fail("INTERNAL ERROR: dbi_result_get_field_length_idx() "
368 "called for unexpected field type %u",
369 current_query->field_types[i - 1]);
370 return 0;
371 } /* dbi_result_get_field_length_idx */
373 const unsigned char *
374 dbi_result_get_binary_idx(dbi_result res, unsigned int i)
375 {
376 fail_unless(current_query->field_types[i - 1] == DBI_TYPE_BINARY,
377 "dbi_result_get_binary_idx() called for non-binary "
378 "column type %u", current_query->field_types[i - 1]);
379 return get_golden_data(res, i).binary.datum;
380 } /* dbi_result_get_binary_idx */
382 static unsigned long long dbi_result_free_called = 0;
383 int
384 dbi_result_free(dbi_result res)
385 {
386 mock_query_t *q = res;
388 ++dbi_result_free_called;
389 if (! q)
390 return -1;
392 current_query = NULL;
393 return 0;
394 } /* dbi_result_free */
396 /*
397 * private helper functions
398 */
400 static void
401 setup(void)
402 {
403 client = sdb_dbi_client_create("mockdriver", "mockdatabase");
404 fail_unless(client != NULL,
405 "sdb_dbi_client_create() = NULL; expected client object");
407 dbi_conn_connect_called = 0;
408 } /* setup */
410 static void
411 connect(void)
412 {
413 int check = sdb_dbi_client_connect(client);
414 fail_unless(check == 0,
415 "sdb_dbi_client_connect() = %i; expected: 0", check);
416 } /* connect */
418 static void
419 teardown(void)
420 {
421 sdb_dbi_client_destroy(client);
422 client = NULL;
423 } /* teardown */
425 static unsigned long long query_callback_called = 0;
426 static int
427 query_callback(sdb_dbi_client_t *c,
428 size_t n, sdb_data_t *data, sdb_object_t *user_data)
429 {
430 size_t i;
432 ++query_callback_called;
434 fail_unless(c == client,
435 "query callback received unexpected client = %p; "
436 "expected: %p", c, client);
437 fail_unless(n == current_query->nfields,
438 "query callback received n = %i; expected: %i",
439 n, current_query->nfields);
440 fail_unless(data != NULL,
441 "query callback received data = NULL; expected: valid data");
442 fail_unless(user_data == TEST_MAGIC,
443 "query callback received user_data = %p; expected: %p",
444 user_data, TEST_MAGIC);
446 for (i = 0; i < n; ++i) {
447 int expected_type = DBI_TYPE_TO_SC(current_query->field_types[i]);
448 mock_data_t expected_data;
450 fail_unless((int)data[i].type == expected_type,
451 "query callback received unexpected type %i for "
452 "column %zu; expected: %i", data[i].type, i,
453 expected_type);
455 expected_data = golden_data[current_query->current_row - 1][i];
456 switch (expected_type) {
457 case SDB_TYPE_INTEGER:
458 fail_unless(data[i].data.integer == expected_data.integer,
459 "query callback received unexpected data %lli "
460 "for column %zu; expected: %lli",
461 data[i].data.integer, i, expected_data.integer);
462 break;
463 case SDB_TYPE_DECIMAL:
464 fail_unless(data[i].data.decimal == expected_data.decimal,
465 "query callback received unexpected data %g "
466 "for column %zu; expected: %g",
467 data[i].data.decimal, i, expected_data.decimal);
468 break;
469 case SDB_TYPE_STRING:
470 fail_unless(data[i].data.string == expected_data.string,
471 "query callback received unexpected data %s "
472 "for column %zu; expected: %s",
473 data[i].data.string, i, expected_data.string);
474 break;
475 case SDB_TYPE_DATETIME:
476 fail_unless(data[i].data.datetime
477 == SECS_TO_SDB_TIME(expected_data.datetime),
478 "query callback received unexpected data "PRIscTIME
479 " for column %zu; expected: "PRIscTIME,
480 data[i].data.integer, i,
481 SECS_TO_SDB_TIME(expected_data.integer));
482 break;
483 case SDB_TYPE_BINARY:
484 fail_unless(data[i].data.binary.length ==
485 expected_data.binary.length,
486 "query callback received unexpected "
487 "binary data length %zu for column %zu; "
488 "expected: %lli", data[i].data.binary.length, i,
489 expected_data.binary.length);
490 fail_unless(data[i].data.binary.datum ==
491 expected_data.binary.datum,
492 "query callback received unexpected binary data %p "
493 "for column %zu; expected: %p",
494 data[i].data.binary.datum, i,
495 expected_data.binary.datum);
496 break;
497 default:
498 fail("INTERNAL ERROR: query callback received "
499 "unknown type %i for column %zu",
500 expected_type, i);
501 }
502 }
503 return 0;
504 } /* query_callback */
506 /*
507 * tests
508 */
510 START_TEST(test_sdb_dbi_client_connect)
511 {
512 int check = sdb_dbi_client_connect(client);
513 fail_unless(check == 0,
514 "sdb_dbi_client_connect() = %i; expected: 0", check);
516 fail_unless(dbi_conn_connect_called == 1,
517 "sdb_dbi_client_create() called dbi_conn_connect %i times; "
518 "expected: 1", dbi_conn_connect_called);
519 }
520 END_TEST
522 START_TEST(test_sdb_dbi_client_check_conn)
523 {
524 int check = sdb_dbi_client_check_conn(client);
525 fail_unless(check == 0,
526 "sdb_dbi_client_check_conn() = %i; expected: 0", check);
528 /* the first call will actually connect to the database */
529 fail_unless(dbi_conn_connect_called == 1,
530 "sdb_dbi_client_check_conn() called dbi_conn_connect %i times; "
531 "expected: 1", dbi_conn_connect_called);
533 dbi_conn_connect_called = 0;
534 check = sdb_dbi_client_check_conn(client);
535 fail_unless(check == 0,
536 "sdb_dbi_client_check_conn() = %i; expected: 0", check);
538 /* should not reconnect */
539 fail_unless(dbi_conn_connect_called == 0,
540 "sdb_dbi_client_check_conn() called dbi_conn_connect %i time(s); "
541 "expected: 0", dbi_conn_connect_called);
542 }
543 END_TEST
545 START_TEST(test_sdb_dbi_exec_query)
546 {
547 size_t i;
549 int check = sdb_dbi_exec_query(client, "mockquery0", query_callback,
550 /* user_data = */ TEST_MAGIC, /* n = */ 0);
551 /* not connected yet */
552 fail_unless(check < 0,
553 "sdb_dbi_exec_query() = %i; expected: < 0", check);
555 connect();
557 for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
558 mock_query_t *q = &mock_queries[i];
560 unsigned long long expected_callback_calls = 0;
562 dbi_conn_query_called = 0;
563 query_callback_called = 0;
564 dbi_result_free_called = 0;
566 /* sdb_dbi_exec_query will only use as many type arguments are needed,
567 * so we can safely pass in the maximum number of arguments required
568 * on each call */
569 check = sdb_dbi_exec_query(client, q->name, query_callback,
570 /* user_data = */ TEST_MAGIC, /* n = */ (int)q->nfields,
571 SDB_TYPE_INTEGER, SDB_TYPE_DECIMAL, SDB_TYPE_STRING,
572 SDB_TYPE_DATETIME, SDB_TYPE_BINARY);
573 fail_unless(check == 0,
574 "sdb_dbi_exec_query() = %i; expected: 0", check);
576 fail_unless(dbi_conn_query_called == 1,
577 "sdb_dbi_exec_query() called dbi_conn_query %i times; "
578 "expected: 1", dbi_conn_query_called);
580 if (q->nfields)
581 expected_callback_calls = q->nrows;
583 fail_unless(query_callback_called == expected_callback_calls,
584 "sdb_dbi_exec_query() did not call the registered callback "
585 "for each result row; got %i call%s; expected: 0",
586 query_callback_called,
587 (query_callback_called == 1) ? "" : "s");
589 fail_unless(dbi_result_free_called == 1,
590 "sdb_dbi_exec_query() did not free the query result object");
591 }
592 }
593 END_TEST
595 /*
596 * test API
597 */
599 Suite *
600 util_dbi_suite(void)
601 {
602 Suite *s = suite_create("utils::dbi");
603 TCase *tc;
605 tc = tcase_create("core");
606 tcase_add_checked_fixture(tc, setup, teardown);
607 tcase_add_test(tc, test_sdb_dbi_client_connect);
608 tcase_add_test(tc, test_sdb_dbi_client_check_conn);
609 tcase_add_test(tc, test_sdb_dbi_exec_query);
610 suite_add_tcase(s, tc);
612 return s;
613 } /* util_llist_suite */
615 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */