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", 5, 1, 1, rows1[0].field_types, rows1[0].field_names },
89 };
91 static mock_query_t *current_query = NULL;
93 /*
94 * mocked functions
95 */
97 /* dbi_driver, dbi_conn, dbi_result are void pointers */
99 dbi_driver
100 dbi_driver_open(const char *name)
101 {
102 if (strcmp(name, "mockdriver"))
103 return NULL;
104 return (dbi_driver)strdup(name);
105 } /* dbi_driver_open */
107 dbi_driver
108 dbi_driver_list(dbi_driver curr)
109 {
110 if (!curr)
111 return "mockdriver";
112 return NULL;
113 } /* dbi_driver_list */
115 const char *
116 dbi_driver_get_name(dbi_driver driver)
117 {
118 return (const char *)driver;
119 } /* dbi_driver_get_name */
121 int
122 dbi_conn_set_option(dbi_conn __attribute__((unused)) conn,
123 const char __attribute__((unused)) *key,
124 const char __attribute__((unused)) *value)
125 {
126 return 0;
127 } /* dbi_conn_set_option */
129 const char *
130 dbi_conn_get_option_list(dbi_conn __attribute__((unused)) conn,
131 const char __attribute__((unused)) *key)
132 {
133 return NULL;
134 } /* dbi_conn_get_option_list */
136 dbi_conn
137 dbi_conn_open(dbi_driver driver)
138 {
139 if (strcmp((const char *)driver, "mockdriver"))
140 return NULL;
141 return (dbi_conn)"mockconnection";
142 } /* dbi_conn_open */
144 static unsigned long long dbi_conn_connect_called = 0;
145 int
146 dbi_conn_connect(dbi_conn conn)
147 {
148 ++dbi_conn_connect_called;
149 if (strcmp((const char *)conn, "mockconnection"))
150 return DBI_ERROR_NOCONN;
151 return 0;
152 } /* dbi_conn_connect */
154 int
155 dbi_conn_ping(dbi_conn conn)
156 {
157 if (strcmp((const char *)conn, "mockconnection"))
158 return 0;
159 return 1;
160 } /* dbi_conn_connect */
162 void
163 dbi_conn_close(dbi_conn __attribute__((unused)) conn)
164 {
165 return;
166 } /* dbi_conn_close */
168 int
169 dbi_conn_error(dbi_conn conn, const char **errmsg)
170 {
171 if ((! conn) || (strcmp((const char *)conn, "mockconnection")))
172 return DBI_ERROR_BADOBJECT;
173 if (errmsg)
174 *errmsg = "mockerror";
175 return DBI_ERROR_UNSUPPORTED;
176 } /* dbi_conn_error */
178 static unsigned long long dbi_conn_query_called = 0;
179 dbi_result
180 dbi_conn_query(dbi_conn conn, const char __attribute__((unused)) *statement)
181 {
182 size_t i;
184 ++dbi_conn_query_called;
185 if (strcmp((const char *)conn, "mockconnection"))
186 return NULL;
188 for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
189 if (!strcmp(mock_queries[i].name, statement)) {
190 current_query = &mock_queries[i];
191 return (dbi_result)current_query;
192 }
193 }
194 return NULL;
195 } /* dbi_conn_query */
197 unsigned long long
198 dbi_result_get_numrows(dbi_result res)
199 {
200 mock_query_t *q = res;
201 if (! q)
202 return DBI_ROW_ERROR;
203 return q->nrows;
204 } /* dbi_result_get_numrows */
206 unsigned int
207 dbi_result_get_numfields(dbi_result res)
208 {
209 mock_query_t *q = res;
210 if (! q)
211 return DBI_FIELD_ERROR;
212 return q->nfields;
213 } /* dbi_result_get_numfields */
215 unsigned short
216 dbi_result_get_field_type_idx(dbi_result res, unsigned int i)
217 {
218 mock_query_t *q = res;
219 if ((! q) || (i > q->nfields))
220 return DBI_TYPE_ERROR;
221 return q->field_types[i - 1];
222 } /* dbi_result_get_field_type_idx */
224 const char *
225 dbi_result_get_field_name(dbi_result res, unsigned int i)
226 {
227 mock_query_t *q = res;
228 if ((! q) || (i > q->nfields))
229 return NULL;
230 return q->field_names[i - 1];
231 } /* dbi_result_get_field_name */
233 int
234 dbi_result_seek_row(dbi_result res, unsigned long long n)
235 {
236 mock_query_t *q = res;
237 if ((! q) || (n > q->nrows))
238 return 0;
240 q->current_row = n;
241 return 1;
242 } /* dbi_result_seek_row */
244 static mock_data_t
245 get_golden_data(dbi_result res, unsigned int i) {
246 mock_query_t *q = res;
247 fail_unless(q != NULL, "dbi_result_get_*_idx() called with "
248 "NULL result data; expected valid result object");
250 /* this information is managed by seek_row and, thus,
251 * should never be invalid */
252 fail_unless(q->current_row && q->current_row <= q->nrows,
253 "INTERNAL ERROR: current row out of range");
255 fail_unless(i && i <= q->nfields,
256 "dbi_result_get_*_idx() called with index out of range; "
257 "got: %u; expected [1, %u]", i, q->nfields);
258 return golden_data[q->current_row - 1][i];
259 } /* get_golden_data */
261 long long
262 dbi_result_get_longlong_idx(dbi_result res, unsigned int i)
263 {
264 fail_unless(current_query->field_types[i] != DBI_TYPE_INTEGER,
265 "dbi_result_get_longlong_idx() called for non-integer "
266 "column type %u", current_query->field_types[i]);
267 return get_golden_data(res, i).integer;
268 } /* dbi_result_get_longlong_idx */
270 double
271 dbi_result_get_double_idx(dbi_result res, unsigned int i)
272 {
273 fail_unless(current_query->field_types[i] != DBI_TYPE_DECIMAL,
274 "dbi_result_get_double_idx() called for non-integer "
275 "column type %u", current_query->field_types[i]);
276 return get_golden_data(res, i).decimal;
277 } /* dbi_result_get_double_idx */
279 const char *
280 dbi_result_get_string_idx(dbi_result res, unsigned int i)
281 {
282 fail_unless(current_query->field_types[i] != DBI_TYPE_STRING,
283 "dbi_result_get_string_idx() called for non-integer "
284 "column type %u", current_query->field_types[i]);
285 return get_golden_data(res, i).string;
286 } /* dbi_result_get_string_idx */
288 time_t
289 dbi_result_get_datetime_idx(dbi_result res, unsigned int i)
290 {
291 fail_unless(current_query->field_types[i] != DBI_TYPE_DATETIME,
292 "dbi_result_get_datetime_idx() called for non-integer "
293 "column type %u", current_query->field_types[i]);
294 return get_golden_data(res, i).datetime;
295 } /* dbi_result_get_datetime_idx */
297 size_t
298 dbi_result_get_field_length_idx(dbi_result res, unsigned int i)
299 {
300 /* this will check if the parameters are valid */
301 get_golden_data(res, i);
303 switch (current_query->field_types[i]) {
304 case DBI_TYPE_INTEGER:
305 break;
306 case DBI_TYPE_DECIMAL:
307 break;
308 case DBI_TYPE_STRING:
309 break;
310 case DBI_TYPE_DATETIME:
311 break;
312 case DBI_TYPE_BINARY:
313 return get_golden_data(res, i).binary.length;
314 break;
315 }
317 fail("INTERNAL ERROR: dbi_result_get_field_length_idx() "
318 "called for unexpected field type %u",
319 current_query->field_types[i]);
320 return 0;
321 } /* dbi_result_get_field_length_idx */
323 const unsigned char *
324 dbi_result_get_binary_idx(dbi_result res, unsigned int i)
325 {
326 fail_unless(current_query->field_types[i] != DBI_TYPE_BINARY,
327 "dbi_result_get_binary_idx() called for non-integer "
328 "column type %u", current_query->field_types[i]);
329 return get_golden_data(res, i).binary.datum;
330 } /* dbi_result_get_binary_idx */
332 static unsigned long long dbi_result_free_called = 0;
333 int
334 dbi_result_free(dbi_result res)
335 {
336 mock_query_t *q = res;
338 ++dbi_result_free_called;
339 if (! q)
340 return -1;
342 current_query = NULL;
343 return 0;
344 } /* dbi_result_free */
346 /*
347 * private helper functions
348 */
350 static void
351 setup(void)
352 {
353 client = sdb_dbi_client_create("mockdriver", "mockdatabase");
354 fail_unless(client != NULL,
355 "sdb_dbi_client_create() = NULL; expected client object");
356 } /* setup */
358 static void
359 connect(void)
360 {
361 int check = sdb_dbi_client_connect(client);
362 fail_unless(check == 0,
363 "sdb_dbi_client_connect() = %i; expected: 0", check);
364 } /* connect */
366 static void
367 teardown(void)
368 {
369 sdb_dbi_client_destroy(client);
370 client = NULL;
371 } /* teardown */
373 static unsigned long long test_query_callback_called = 0;
374 static int
375 test_query_callback(sdb_dbi_client_t *c,
376 size_t n, sdb_data_t *data, sdb_object_t *user_data)
377 {
378 ++test_query_callback_called;
380 fail_unless(c == client,
381 "query callback received unexpected client = %p; "
382 "expected: %p", c, client);
383 fail_unless(n == current_query->nfields,
384 "query callback received n = %i; expected: %i",
385 n, current_query->nfields);
386 fail_unless(data != NULL,
387 "query callback received data = NULL; expected: valid data");
388 fail_unless(user_data == TEST_MAGIC,
389 "query callback received user_data = %p; expected: %p",
390 user_data, TEST_MAGIC);
391 return 0;
392 } /* test_query_callback */
394 /*
395 * tests
396 */
398 START_TEST(test_client_connect)
399 {
400 int check = sdb_dbi_client_connect(client);
401 fail_unless(check == 0,
402 "sdb_dbi_client_connect() = %i; expected: 0", check);
404 fail_unless(dbi_conn_connect_called == 1,
405 "sdb_dbi_client_create() called dbi_conn_connect %i times; "
406 "expected: 1", dbi_conn_connect_called);
407 }
408 END_TEST
410 START_TEST(test_client_check_conn)
411 {
412 int check = sdb_dbi_client_check_conn(client);
413 fail_unless(check == 0,
414 "sdb_dbi_client_check_conn() = %i; expected: 0", check);
416 /* the first call will actually connect to the database */
417 fail_unless(dbi_conn_connect_called == 1,
418 "sdb_dbi_client_check_conn() called dbi_conn_connect %i times; "
419 "expected: 1", dbi_conn_connect_called);
421 dbi_conn_connect_called = 0;
422 check = sdb_dbi_client_check_conn(client);
423 fail_unless(check == 0,
424 "sdb_dbi_client_check_conn() = %i; expected: 0", check);
426 /* should not reconnect */
427 fail_unless(dbi_conn_connect_called == 0,
428 "sdb_dbi_client_check_conn() called dbi_conn_connect %i time(s); "
429 "expected: 0", dbi_conn_connect_called);
430 }
431 END_TEST
433 START_TEST(test_exec_query)
434 {
435 size_t i;
437 int check = sdb_dbi_exec_query(client, "mockquery0", test_query_callback,
438 /* user_data = */ TEST_MAGIC, /* n = */ 0);
439 /* not connected yet */
440 fail_unless(check < 0,
441 "sdb_dbi_exec_query() = %i; expected: < 0", check);
443 connect();
445 for (i = 0; i < SDB_STATIC_ARRAY_LEN(mock_queries); ++i) {
446 mock_query_t *q = &mock_queries[i];
448 unsigned long long expected_callback_calls = 0;
450 dbi_conn_query_called = 0;
451 test_query_callback_called = 0;
452 dbi_result_free_called = 0;
454 check = sdb_dbi_exec_query(client, q->name, test_query_callback,
455 /* user_data = */ TEST_MAGIC, /* n = */ (int)q->nfields,
456 SDB_TYPE_INTEGER);
457 fail_unless(check == 0,
458 "sdb_dbi_exec_query() = %i; expected: 0", check);
460 fail_unless(dbi_conn_query_called == 1,
461 "sdb_dbi_exec_query() called dbi_conn_query %i times; "
462 "expected: 1", dbi_conn_query_called);
464 if (q->nfields)
465 expected_callback_calls = q->nrows;
467 fail_unless(test_query_callback_called == expected_callback_calls,
468 "sdb_dbi_exec_query() did not call the registered callback "
469 "for each result row; got %i call%s; expected: 0",
470 test_query_callback_called,
471 (test_query_callback_called == 1) ? "" : "s");
473 fail_unless(dbi_result_free_called == 1,
474 "sdb_dbi_exec_query() did not free the query result object");
475 }
476 }
477 END_TEST
479 /*
480 * test API
481 */
483 Suite *
484 util_dbi_suite(void)
485 {
486 Suite *s = suite_create("utils::dbi");
487 TCase *tc;
489 tc = tcase_create("core");
490 tcase_add_checked_fixture(tc, setup, teardown);
491 tcase_add_test(tc, test_client_connect);
492 tcase_add_test(tc, test_client_check_conn);
493 tcase_add_test(tc, test_exec_query);
494 suite_add_tcase(s, tc);
496 return s;
497 } /* util_llist_suite */
499 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */