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 struct {
71 unsigned short field_types[1];
72 char *field_names[1];
73 } rows1[] = {
74 { { DBI_TYPE_INTEGER }, { "field0" }, },
75 };
77 static mock_data_t golden_data[][1] = {
78 { { .integer = 1234 } },
79 { { .integer = 2345 } },
80 { { .integer = 3456 } },
81 { { .integer = 4567 } },
82 { { .integer = 5678 } },
83 };
85 /* query definitions */
86 static mock_query_t mock_queries[] = {
87 { "mockquery0", 5, 1, 0, NULL, NULL },
88 { "mockquery1", 0, 0, 1, rows1[0].field_types, rows1[0].field_names },
89 { "mockquery2", 1, 1, 1, rows1[0].field_types, rows1[0].field_names },
90 { "mockquery3", 5, 1, 1, rows1[0].field_types, rows1[0].field_names },
91 };
93 static mock_query_t *current_query = NULL;
95 /*
96 * mocked functions
97 */
99 /* dbi_driver, dbi_conn, dbi_result are void pointers */
101 dbi_driver
102 dbi_driver_open(const char *name)
103 {
104 if (strcmp(name, "mockdriver"))
105 return NULL;
106 return (dbi_driver)strdup(name);
107 } /* dbi_driver_open */
109 dbi_driver
110 dbi_driver_list(dbi_driver curr)
111 {
112 if (!curr)
113 return "mockdriver";
114 return NULL;
115 } /* dbi_driver_list */
117 const char *
118 dbi_driver_get_name(dbi_driver driver)
119 {
120 return (const char *)driver;
121 } /* dbi_driver_get_name */
123 int
124 dbi_conn_set_option(dbi_conn __attribute__((unused)) conn,
125 const char __attribute__((unused)) *key,
126 const char __attribute__((unused)) *value)
127 {
128 return 0;
129 } /* dbi_conn_set_option */
131 const char *
132 dbi_conn_get_option_list(dbi_conn __attribute__((unused)) conn,
133 const char __attribute__((unused)) *key)
134 {
135 return NULL;
136 } /* dbi_conn_get_option_list */
138 dbi_conn
139 dbi_conn_open(dbi_driver driver)
140 {
141 if (strcmp((const char *)driver, "mockdriver"))
142 return NULL;
143 return (dbi_conn)"mockconnection";
144 } /* dbi_conn_open */
146 static unsigned long long dbi_conn_connect_called = 0;
147 int
148 dbi_conn_connect(dbi_conn conn)
149 {
150 ++dbi_conn_connect_called;
151 if (strcmp((const char *)conn, "mockconnection"))
152 return DBI_ERROR_NOCONN;
153 return 0;
154 } /* dbi_conn_connect */
156 int
157 dbi_conn_ping(dbi_conn conn)
158 {
159 if (strcmp((const char *)conn, "mockconnection"))
160 return 0;
161 return 1;
162 } /* dbi_conn_connect */
164 void
165 dbi_conn_close(dbi_conn __attribute__((unused)) conn)
166 {
167 return;
168 } /* dbi_conn_close */
170 int
171 dbi_conn_error(dbi_conn conn, const char **errmsg)
172 {
173 if ((! conn) || (strcmp((const char *)conn, "mockconnection")))
174 return DBI_ERROR_BADOBJECT;
175 if (errmsg)
176 *errmsg = "mockerror";
177 return DBI_ERROR_UNSUPPORTED;
178 } /* dbi_conn_error */
180 static unsigned long long dbi_conn_query_called = 0;
181 dbi_result
182 dbi_conn_query(dbi_conn conn, const char __attribute__((unused)) *statement)
183 {
184 size_t i;
186 ++dbi_conn_query_called;
187 if (strcmp((const char *)conn, "mockconnection"))
188 return NULL;
190 for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
191 if (!strcmp(mock_queries[i].name, statement)) {
192 current_query = &mock_queries[i];
193 return (dbi_result)current_query;
194 }
195 }
196 return NULL;
197 } /* dbi_conn_query */
199 unsigned long long
200 dbi_result_get_numrows(dbi_result res)
201 {
202 mock_query_t *q = res;
203 if (! q)
204 return DBI_ROW_ERROR;
205 return q->nrows;
206 } /* dbi_result_get_numrows */
208 unsigned int
209 dbi_result_get_numfields(dbi_result res)
210 {
211 mock_query_t *q = res;
212 if (! q)
213 return DBI_FIELD_ERROR;
214 return q->nfields;
215 } /* dbi_result_get_numfields */
217 unsigned short
218 dbi_result_get_field_type_idx(dbi_result res, unsigned int i)
219 {
220 mock_query_t *q = res;
221 if ((! q) || (i > q->nfields))
222 return DBI_TYPE_ERROR;
223 return q->field_types[i - 1];
224 } /* dbi_result_get_field_type_idx */
226 const char *
227 dbi_result_get_field_name(dbi_result res, unsigned int i)
228 {
229 mock_query_t *q = res;
230 if ((! q) || (i > q->nfields))
231 return NULL;
232 return q->field_names[i - 1];
233 } /* dbi_result_get_field_name */
235 int
236 dbi_result_seek_row(dbi_result res, unsigned long long n)
237 {
238 mock_query_t *q = res;
239 if ((! q) || (n > q->nrows))
240 return 0;
242 q->current_row = n;
243 return 1;
244 } /* dbi_result_seek_row */
246 static mock_data_t
247 get_golden_data(dbi_result res, unsigned int i) {
248 mock_query_t *q = res;
249 fail_unless(q != NULL, "dbi_result_get_*_idx() called with "
250 "NULL result data; expected valid result object");
252 /* this information is managed by seek_row and, thus,
253 * should never be invalid */
254 fail_unless(q->current_row && q->current_row <= q->nrows,
255 "INTERNAL ERROR: current row out of range");
257 fail_unless(i && i <= q->nfields,
258 "dbi_result_get_*_idx() called with index out of range; "
259 "got: %u; expected [1, %u]", i, q->nfields);
260 return golden_data[q->current_row - 1][i];
261 } /* get_golden_data */
263 long long
264 dbi_result_get_longlong_idx(dbi_result res, unsigned int i)
265 {
266 fail_unless(current_query->field_types[i] != DBI_TYPE_INTEGER,
267 "dbi_result_get_longlong_idx() called for non-integer "
268 "column type %u", current_query->field_types[i]);
269 return get_golden_data(res, i).integer;
270 } /* dbi_result_get_longlong_idx */
272 double
273 dbi_result_get_double_idx(dbi_result res, unsigned int i)
274 {
275 fail_unless(current_query->field_types[i] != DBI_TYPE_DECIMAL,
276 "dbi_result_get_double_idx() called for non-integer "
277 "column type %u", current_query->field_types[i]);
278 return get_golden_data(res, i).decimal;
279 } /* dbi_result_get_double_idx */
281 const char *
282 dbi_result_get_string_idx(dbi_result res, unsigned int i)
283 {
284 fail_unless(current_query->field_types[i] != DBI_TYPE_STRING,
285 "dbi_result_get_string_idx() called for non-integer "
286 "column type %u", current_query->field_types[i]);
287 return get_golden_data(res, i).string;
288 } /* dbi_result_get_string_idx */
290 time_t
291 dbi_result_get_datetime_idx(dbi_result res, unsigned int i)
292 {
293 fail_unless(current_query->field_types[i] != DBI_TYPE_DATETIME,
294 "dbi_result_get_datetime_idx() called for non-integer "
295 "column type %u", current_query->field_types[i]);
296 return get_golden_data(res, i).datetime;
297 } /* dbi_result_get_datetime_idx */
299 size_t
300 dbi_result_get_field_length_idx(dbi_result res, unsigned int i)
301 {
302 /* this will check if the parameters are valid */
303 get_golden_data(res, i);
305 switch (current_query->field_types[i]) {
306 case DBI_TYPE_INTEGER:
307 return sizeof(long long);
308 break;
309 case DBI_TYPE_DECIMAL:
310 return sizeof(double);
311 break;
312 case DBI_TYPE_STRING:
313 return strlen(get_golden_data(res, i).string) + 1;
314 break;
315 case DBI_TYPE_DATETIME:
316 return sizeof(time_t);
317 break;
318 case DBI_TYPE_BINARY:
319 return get_golden_data(res, i).binary.length;
320 break;
321 }
323 fail("INTERNAL ERROR: dbi_result_get_field_length_idx() "
324 "called for unexpected field type %u",
325 current_query->field_types[i]);
326 return 0;
327 } /* dbi_result_get_field_length_idx */
329 const unsigned char *
330 dbi_result_get_binary_idx(dbi_result res, unsigned int i)
331 {
332 fail_unless(current_query->field_types[i] != DBI_TYPE_BINARY,
333 "dbi_result_get_binary_idx() called for non-integer "
334 "column type %u", current_query->field_types[i]);
335 return get_golden_data(res, i).binary.datum;
336 } /* dbi_result_get_binary_idx */
338 static unsigned long long dbi_result_free_called = 0;
339 int
340 dbi_result_free(dbi_result res)
341 {
342 mock_query_t *q = res;
344 ++dbi_result_free_called;
345 if (! q)
346 return -1;
348 current_query = NULL;
349 return 0;
350 } /* dbi_result_free */
352 /*
353 * private helper functions
354 */
356 static void
357 setup(void)
358 {
359 client = sdb_dbi_client_create("mockdriver", "mockdatabase");
360 fail_unless(client != NULL,
361 "sdb_dbi_client_create() = NULL; expected client object");
362 } /* setup */
364 static void
365 connect(void)
366 {
367 int check = sdb_dbi_client_connect(client);
368 fail_unless(check == 0,
369 "sdb_dbi_client_connect() = %i; expected: 0", check);
370 } /* connect */
372 static void
373 teardown(void)
374 {
375 sdb_dbi_client_destroy(client);
376 client = NULL;
377 } /* teardown */
379 static unsigned long long test_query_callback_called = 0;
380 static int
381 test_query_callback(sdb_dbi_client_t *c,
382 size_t n, sdb_data_t *data, sdb_object_t *user_data)
383 {
384 size_t i;
386 ++test_query_callback_called;
388 fail_unless(c == client,
389 "query callback received unexpected client = %p; "
390 "expected: %p", c, client);
391 fail_unless(n == current_query->nfields,
392 "query callback received n = %i; expected: %i",
393 n, current_query->nfields);
394 fail_unless(data != NULL,
395 "query callback received data = NULL; expected: valid data");
396 fail_unless(user_data == TEST_MAGIC,
397 "query callback received user_data = %p; expected: %p",
398 user_data, TEST_MAGIC);
400 for (i = 0; i < n; ++i) {
401 int expected_type = DBI_TYPE_TO_SC(current_query->field_types[i]);
402 mock_data_t expected_data;
404 fail_unless((int)data[i].type == expected_type,
405 "query callback received unexpected type %i for "
406 "column %zu; expected: %i", data[i].type, i,
407 expected_type);
409 expected_data = golden_data[current_query->current_row][i];
410 switch (expected_type) {
411 case SDB_TYPE_INTEGER:
412 fail_unless(data[i].data.integer == expected_data.integer,
413 "query callback received unexpected data %lli "
414 "for column %zu; expected: %lli",
415 data[i].data.integer, i, expected_data.integer);
416 break;
417 case SDB_TYPE_DECIMAL:
418 fail_unless(data[i].data.decimal == expected_data.decimal,
419 "query callback received unexpected data %g "
420 "for column %zu; expected: %g",
421 data[i].data.decimal, i, expected_data.decimal);
422 break;
423 case SDB_TYPE_STRING:
424 fail_unless(data[i].data.string == expected_data.string,
425 "query callback received unexpected data %s "
426 "for column %zu; expected: %s",
427 data[i].data.string, i, expected_data.string);
428 break;
429 case SDB_TYPE_DATETIME:
430 fail_unless(data[i].data.datetime
431 == SECS_TO_SDB_TIME(expected_data.datetime),
432 "query callback received unexpected data "PRIscTIME
433 " for column %zu; expected: "PRIscTIME,
434 data[i].data.integer, i,
435 SECS_TO_SDB_TIME(expected_data.integer));
436 break;
437 case SDB_TYPE_BINARY:
438 fail_unless(data[i].data.binary.length ==
439 expected_data.binary.length,
440 "query callback received unexpected "
441 "binary data length %zu for column %zu; "
442 "expected: %lli", data[i].data.binary.length, i,
443 expected_data.binary.length);
444 fail_unless(data[i].data.binary.datum ==
445 expected_data.binary.datum,
446 "query callback received unexpected binary data %p "
447 "for column %zu; expected: %p",
448 data[i].data.binary.datum, i,
449 expected_data.binary.datum);
450 break;
451 default:
452 fail("INTERNAL ERROR: query callback received "
453 "unknown type %i for column %zu",
454 expected_type, i);
455 }
456 }
457 return 0;
458 } /* test_query_callback */
460 /*
461 * tests
462 */
464 START_TEST(test_client_connect)
465 {
466 int check = sdb_dbi_client_connect(client);
467 fail_unless(check == 0,
468 "sdb_dbi_client_connect() = %i; expected: 0", check);
470 fail_unless(dbi_conn_connect_called == 1,
471 "sdb_dbi_client_create() called dbi_conn_connect %i times; "
472 "expected: 1", dbi_conn_connect_called);
473 }
474 END_TEST
476 START_TEST(test_client_check_conn)
477 {
478 int check = sdb_dbi_client_check_conn(client);
479 fail_unless(check == 0,
480 "sdb_dbi_client_check_conn() = %i; expected: 0", check);
482 /* the first call will actually connect to the database */
483 fail_unless(dbi_conn_connect_called == 1,
484 "sdb_dbi_client_check_conn() called dbi_conn_connect %i times; "
485 "expected: 1", dbi_conn_connect_called);
487 dbi_conn_connect_called = 0;
488 check = sdb_dbi_client_check_conn(client);
489 fail_unless(check == 0,
490 "sdb_dbi_client_check_conn() = %i; expected: 0", check);
492 /* should not reconnect */
493 fail_unless(dbi_conn_connect_called == 0,
494 "sdb_dbi_client_check_conn() called dbi_conn_connect %i time(s); "
495 "expected: 0", dbi_conn_connect_called);
496 }
497 END_TEST
499 START_TEST(test_exec_query)
500 {
501 size_t i;
503 int check = sdb_dbi_exec_query(client, "mockquery0", test_query_callback,
504 /* user_data = */ TEST_MAGIC, /* n = */ 0);
505 /* not connected yet */
506 fail_unless(check < 0,
507 "sdb_dbi_exec_query() = %i; expected: < 0", check);
509 connect();
511 for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
512 mock_query_t *q = &mock_queries[i];
514 unsigned long long expected_callback_calls = 0;
516 dbi_conn_query_called = 0;
517 test_query_callback_called = 0;
518 dbi_result_free_called = 0;
520 check = sdb_dbi_exec_query(client, q->name, test_query_callback,
521 /* user_data = */ TEST_MAGIC, /* n = */ (int)q->nfields,
522 SDB_TYPE_INTEGER);
523 fail_unless(check == 0,
524 "sdb_dbi_exec_query() = %i; expected: 0", check);
526 fail_unless(dbi_conn_query_called == 1,
527 "sdb_dbi_exec_query() called dbi_conn_query %i times; "
528 "expected: 1", dbi_conn_query_called);
530 if (q->nfields)
531 expected_callback_calls = q->nrows;
533 fail_unless(test_query_callback_called == expected_callback_calls,
534 "sdb_dbi_exec_query() did not call the registered callback "
535 "for each result row; got %i call%s; expected: 0",
536 test_query_callback_called,
537 (test_query_callback_called == 1) ? "" : "s");
539 fail_unless(dbi_result_free_called == 1,
540 "sdb_dbi_exec_query() did not free the query result object");
541 }
542 }
543 END_TEST
545 /*
546 * test API
547 */
549 Suite *
550 util_dbi_suite(void)
551 {
552 Suite *s = suite_create("utils::dbi");
553 TCase *tc;
555 tc = tcase_create("core");
556 tcase_add_checked_fixture(tc, setup, teardown);
557 tcase_add_test(tc, test_client_connect);
558 tcase_add_test(tc, test_client_check_conn);
559 tcase_add_test(tc, test_exec_query);
560 suite_add_tcase(s, tc);
562 return s;
563 } /* util_llist_suite */
565 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */