Code

a1ac30a37983afa98c60228ed846ad567bb13e6d
[sysdb.git] / t / unit / utils / dbi_test.c
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 #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)
166         return 0;
167 } /* dbi_initialize_r */
169 void
170 dbi_shutdown_r(dbi_inst __attribute__((unused)) inst)
172 } /* dbi_shutdown_r */
174 dbi_driver
175 dbi_driver_open_r(const char *name, dbi_inst __attribute__((unused)) inst)
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)
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)
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)
204         dbi_shutdown_r(NULL);
205 } /* dbi_shutdown */
207 dbi_driver
208 dbi_driver_open(const char *name)
210         return dbi_driver_open_r(name, NULL);
211 } /* dbi_driver_open */
213 dbi_driver
214 dbi_driver_list(dbi_driver curr)
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)
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)
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)
238         return NULL;
239 } /* dbi_conn_get_option_list */
241 dbi_conn
242 dbi_conn_open(dbi_driver driver)
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)
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)
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)
270         return;
271 } /* dbi_conn_close */
273 int
274 dbi_conn_error(dbi_conn conn, const char **errmsg)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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);
604 END_TEST
606 START_TEST(test_sdb_dbi_client_check_conn)
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);
627 END_TEST
629 START_TEST(test_sdb_dbi_exec_query)
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         }
677 END_TEST
679 /*
680  * test API
681  */
683 Suite *
684 util_dbi_suite(void)
686         Suite *s = suite_create("utils::dbi");
687         TCase *tc;
689         tc = tcase_create("core");
690         tcase_add_checked_fixture(tc, setup, teardown);
691         tcase_add_test(tc, test_sdb_dbi_client_connect);
692         tcase_add_test(tc, test_sdb_dbi_client_check_conn);
693         tcase_add_test(tc, test_sdb_dbi_exec_query);
694         suite_add_tcase(s, tc);
696         return s;
697 } /* util_llist_suite */
699 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */