1 /*
2 * SysDB - t/unit/core/store_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 "core/store.h"
29 #include "core/store-private.h"
30 #include "libsysdb_test.h"
32 #include <check.h>
33 #include <string.h>
35 static void
36 populate(void)
37 {
38 sdb_data_t datum;
40 sdb_store_host("h1", 1);
41 sdb_store_host("h2", 1);
43 datum.type = SDB_TYPE_STRING;
44 datum.data.string = "v1";
45 sdb_store_attribute("h1", "k1", &datum, 1);
46 datum.data.string = "v2";
47 sdb_store_attribute("h1", "k2", &datum, 1);
48 datum.data.string = "v3";
49 sdb_store_attribute("h1", "k3", &datum, 1);
51 sdb_store_service("h2", "s1", 1);
52 sdb_store_service("h2", "s2", 1);
53 } /* populate */
55 START_TEST(test_store_host)
56 {
57 struct {
58 const char *name;
59 sdb_time_t last_update;
60 int expected;
61 } golden_data[] = {
62 { "a", 1, 0 },
63 { "a", 2, 0 },
64 { "a", 1, 1 },
65 { "b", 1, 0 },
66 { "b", 1, 1 },
67 { "A", 1, 1 }, /* case-insensitive */
68 { "A", 3, 0 },
69 };
71 struct {
72 const char *name;
73 _Bool has;
74 } golden_hosts[] = {
75 { "a", 1 == 1 },
76 { "b", 1 == 1 },
77 { "c", 0 == 1 },
78 { "A", 1 == 1 },
79 };
81 size_t i;
83 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
84 int status;
86 status = sdb_store_host(golden_data[i].name,
87 golden_data[i].last_update);
88 fail_unless(status == golden_data[i].expected,
89 "sdb_store_host(%s, %d) = %d; expected: %d",
90 golden_data[i].name, (int)golden_data[i].last_update,
91 status, golden_data[i].expected);
92 }
94 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_hosts); ++i) {
95 _Bool has;
97 has = sdb_store_has_host(golden_hosts[i].name);
98 fail_unless(has == golden_hosts[i].has,
99 "sdb_store_has_host(%s) = %d; expected: %d",
100 golden_hosts[i].name, has, golden_hosts[i].has);
101 }
102 }
103 END_TEST
105 START_TEST(test_store_get_host)
106 {
107 char *golden_hosts[] = { "a", "b", "c" };
108 char *unknown_hosts[] = { "x", "y", "z" };
109 size_t i;
111 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_hosts); ++i) {
112 int status = sdb_store_host(golden_hosts[i], 1);
113 fail_unless(status >= 0,
114 "sdb_store_host(%s) = %d; expected: >=0",
115 golden_hosts[i], status);
116 }
118 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_hosts); ++i) {
119 sdb_store_obj_t *sobj1, *sobj2;
120 int ref_cnt;
122 fail_unless(sdb_store_has_host(golden_hosts[i]),
123 "sdb_store_has_host(%s) = FALSE; expected: TRUE",
124 golden_hosts[i]);
126 sobj1 = sdb_store_get_host(golden_hosts[i]);
127 fail_unless(sobj1 != NULL,
128 "sdb_store_get_host(%s) = NULL; expected: <host>",
129 golden_hosts[i]);
130 ref_cnt = SDB_OBJ(sobj1)->ref_cnt;
132 fail_unless(ref_cnt > 1,
133 "sdb_store_get_host(%s) did not increment ref count: "
134 "got: %d; expected: >1", golden_hosts[i], ref_cnt);
136 sobj2 = sdb_store_get_host(golden_hosts[i]);
137 fail_unless(sobj2 != NULL,
138 "sdb_store_get_host(%s) = NULL; expected: <host>",
139 golden_hosts[i]);
141 fail_unless(sobj1 == sobj2,
142 "sdb_store_get_host(%s) returned different objects "
143 "in successive calls", golden_hosts[i]);
144 fail_unless(SDB_OBJ(sobj2)->ref_cnt == ref_cnt + 1,
145 "sdb_store_get_hosts(%s) did not increment ref count "
146 "(first call: %d; second call: %d)",
147 golden_hosts[i], ref_cnt, SDB_OBJ(sobj2)->ref_cnt);
149 sdb_object_deref(SDB_OBJ(sobj1));
150 sdb_object_deref(SDB_OBJ(sobj2));
151 }
152 for (i = 0; i < SDB_STATIC_ARRAY_LEN(unknown_hosts); ++i) {
153 sdb_store_obj_t *sobj;
155 fail_unless(!sdb_store_has_host(unknown_hosts[i]),
156 "sdb_store_has_host(%s) = TRUE; expected: FALSE",
157 unknown_hosts[i]);
159 sobj = sdb_store_get_host(unknown_hosts[i]);
160 fail_unless(!sobj, "sdb_store_get_host(%s) = <host:%s>; expected: NULL",
161 unknown_hosts[i], sobj ? SDB_OBJ(sobj)->name : "NULL");
162 }
163 }
164 END_TEST
166 START_TEST(test_store_attr)
167 {
168 struct {
169 const char *host;
170 const char *key;
171 char *value;
172 sdb_time_t last_update;
173 int expected;
174 } golden_data[] = {
175 { "k", "k", "v", 1, -1 },
176 { "k", "k", "v", 1, -1 }, /* retry to ensure the host is not created */
177 { "l", "k1", "v1", 1, 0 },
178 { "l", "k1", "v2", 2, 0 },
179 { "l", "k1", "v3", 2, 1 },
180 { "l", "k2", "v1", 1, 0 },
181 { "m", "k", "v1", 1, 0 },
182 { "m", "k", "v2", 1, 1 },
183 };
185 size_t i;
187 sdb_store_host("l", 1);
188 sdb_store_host("m", 1);
189 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
190 sdb_data_t datum;
191 int status;
193 /* XXX: test other types as well */
194 datum.type = SDB_TYPE_STRING;
195 datum.data.string = golden_data[i].value;
197 status = sdb_store_attribute(golden_data[i].host,
198 golden_data[i].key, &datum,
199 golden_data[i].last_update);
200 fail_unless(status == golden_data[i].expected,
201 "sdb_store_attribute(%s, %s, %s, %d) = %d; expected: %d",
202 golden_data[i].host, golden_data[i].key, golden_data[i].value,
203 golden_data[i].last_update, status, golden_data[i].expected);
204 }
205 }
206 END_TEST
208 START_TEST(test_store_service)
209 {
210 struct {
211 const char *host;
212 const char *svc;
213 sdb_time_t last_update;
214 int expected;
215 } golden_data[] = {
216 { "k", "s", 1, -1 },
217 { "k", "s", 1, -1 }, /* retry to ensure the host is not created */
218 { "l", "s1", 1, 0 },
219 { "l", "s1", 2, 0 },
220 { "l", "s1", 2, 1 },
221 { "l", "s2", 1, 0 },
222 { "m", "s", 1, 0 },
223 { "m", "s", 1, 1 },
224 };
226 size_t i;
228 sdb_store_host("m", 1);
229 sdb_store_host("l", 1);
230 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
231 int status;
233 status = sdb_store_service(golden_data[i].host,
234 golden_data[i].svc, golden_data[i].last_update);
235 fail_unless(status == golden_data[i].expected,
236 "sdb_store_service(%s, %s, %d) = %d; expected: %d",
237 golden_data[i].host, golden_data[i].svc,
238 golden_data[i].last_update, status, golden_data[i].expected);
239 }
240 }
241 END_TEST
243 START_TEST(test_store_service_attr)
244 {
245 struct {
246 const char *host;
247 const char *svc;
248 const char *attr;
249 const sdb_data_t value;
250 sdb_time_t last_update;
251 int expected;
252 } golden_data[] = {
253 { "k", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
254 /* retry, it should still fail */
255 { "k", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
256 { "l", "sX", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
257 /* retry, it should still fail */
258 { "l", "sX", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
259 { "l", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 0 },
260 { "l", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 1 },
261 { "l", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 2, 0 },
262 { "l", "s1", "a2", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 0 },
263 { "l", "s1", "a2", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 1 },
264 { "l", "s2", "a2", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 0 },
265 { "m", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 0 },
266 };
268 size_t i;
270 sdb_store_host("m", 1);
271 sdb_store_host("l", 1);
272 sdb_store_service("m", "s1", 1);
273 sdb_store_service("l", "s1", 1);
274 sdb_store_service("l", "s2", 1);
276 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
277 int status;
279 status = sdb_store_service_attr(golden_data[i].host,
280 golden_data[i].svc, golden_data[i].attr,
281 &golden_data[i].value, golden_data[i].last_update);
282 fail_unless(status == golden_data[i].expected,
283 "sdb_store_service_attr(%s, %s, %s, %d, %d) = %d; "
284 "expected: %d", golden_data[i].host, golden_data[i].svc,
285 golden_data[i].attr, golden_data[i].value.data.integer,
286 golden_data[i].last_update, status, golden_data[i].expected);
287 }
288 }
289 END_TEST
291 static void
292 verify_json_output(sdb_strbuf_t *buf, const char *expected, int flags)
293 {
294 int pos;
295 size_t len1, len2;
296 size_t i;
298 len1 = strlen(sdb_strbuf_string(buf));
299 len2 = strlen(expected);
301 pos = -1;
302 if (len1 != len2)
303 pos = (int)(len1 <= len2 ? len1 : len2);
305 for (i = 0; i < (len1 <= len2 ? len1 : len2); ++i) {
306 if (sdb_strbuf_string(buf)[i] != expected[i]) {
307 pos = (int)i;
308 break;
309 }
310 }
312 fail_unless(pos == -1,
313 "sdb_store_tojson(%x) returned unexpected result\n"
314 " got: %s\n %*s\n expected: %s",
315 flags, sdb_strbuf_string(buf), pos + 1, "^", expected);
316 } /* verify_json_output */
318 START_TEST(test_store_tojson)
319 {
320 sdb_strbuf_t *buf;
321 size_t i;
323 struct {
324 int flags;
325 const char *expected;
326 } golden_data[] = {
327 { 0, "{\"hosts\":["
328 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
329 "\"update_interval\": \"0s\", \"backends\": [], "
330 "\"attributes\": ["
331 "{\"name\": \"k1\", \"value\": \"v1\", "
332 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
333 "\"update_interval\": \"0s\", \"backends\": []},"
334 "{\"name\": \"k2\", \"value\": \"v2\", "
335 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
336 "\"update_interval\": \"0s\", \"backends\": []},"
337 "{\"name\": \"k3\", \"value\": \"v3\", "
338 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
339 "\"update_interval\": \"0s\", \"backends\": []}"
340 "], "
341 "\"services\": []},"
342 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
343 "\"update_interval\": \"0s\", \"backends\": [], "
344 "\"attributes\": [], "
345 "\"services\": ["
346 "{\"name\": \"s1\", "
347 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
348 "\"update_interval\": \"0s\", \"backends\": []},"
349 "{\"name\": \"s2\", "
350 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
351 "\"update_interval\": \"0s\", \"backends\": []}"
352 "]}"
353 "]}" },
354 { SDB_SKIP_SERVICES,
355 "{\"hosts\":["
356 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
357 "\"update_interval\": \"0s\", \"backends\": [], "
358 "\"attributes\": ["
359 "{\"name\": \"k1\", \"value\": \"v1\", "
360 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
361 "\"update_interval\": \"0s\", \"backends\": []},"
362 "{\"name\": \"k2\", \"value\": \"v2\", "
363 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
364 "\"update_interval\": \"0s\", \"backends\": []},"
365 "{\"name\": \"k3\", \"value\": \"v3\", "
366 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
367 "\"update_interval\": \"0s\", \"backends\": []}"
368 "]},"
369 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
370 "\"update_interval\": \"0s\", \"backends\": [], "
371 "\"attributes\": []}"
372 "]}" },
373 { SDB_SKIP_ATTRIBUTES,
374 "{\"hosts\":["
375 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
376 "\"update_interval\": \"0s\", \"backends\": [], "
377 "\"services\": []},"
378 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
379 "\"update_interval\": \"0s\", \"backends\": [], "
380 "\"services\": ["
381 "{\"name\": \"s1\", "
382 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
383 "\"update_interval\": \"0s\", \"backends\": []},"
384 "{\"name\": \"s2\", "
385 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
386 "\"update_interval\": \"0s\", \"backends\": []}"
387 "]}"
388 "]}" },
389 { SDB_SKIP_SERVICES | SDB_SKIP_ATTRIBUTES,
390 "{\"hosts\":["
391 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
392 "\"update_interval\": \"0s\", \"backends\": []},"
393 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
394 "\"update_interval\": \"0s\", \"backends\": []}"
395 "]}" },
396 };
398 buf = sdb_strbuf_create(0);
399 fail_unless(buf != NULL, "INTERNAL ERROR: failed to create string buffer");
400 populate();
402 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
403 int status;
405 sdb_strbuf_clear(buf);
407 status = sdb_store_tojson(buf, golden_data[i].flags);
408 fail_unless(status == 0,
409 "sdb_store_tojson(%x) = %d; expected: 0",
410 golden_data[i].flags, status);
412 verify_json_output(buf, golden_data[i].expected, golden_data[i].flags);
413 }
414 sdb_strbuf_destroy(buf);
415 }
416 END_TEST
418 START_TEST(test_interval)
419 {
420 sdb_store_obj_t *host;
422 /* 10 us interval */
423 sdb_store_host("host", 10);
424 sdb_store_host("host", 20);
425 sdb_store_host("host", 30);
426 sdb_store_host("host", 40);
428 host = sdb_store_get_host("host");
429 fail_unless(host != NULL,
430 "INTERNAL ERROR: store doesn't have host after adding it");
432 fail_unless(host->interval == 10,
433 "sdb_store_host() did not calculate interval correctly: "
434 "got: %"PRIscTIME"; expected: %"PRIscTIME, host->interval, 10);
436 /* multiple updates for the same timestamp don't modify the interval */
437 sdb_store_host("host", 40);
438 sdb_store_host("host", 40);
439 sdb_store_host("host", 40);
440 sdb_store_host("host", 40);
442 fail_unless(host->interval == 10,
443 "sdb_store_host() changed interval when doing multiple updates "
444 "using the same timestamp; got: %"PRIscTIME"; "
445 "expected: %"PRIscTIME, host->interval, 10);
447 /* multiple updates using an timestamp don't modify the interval */
448 sdb_store_host("host", 20);
449 sdb_store_host("host", 20);
450 sdb_store_host("host", 20);
451 sdb_store_host("host", 20);
453 fail_unless(host->interval == 10,
454 "sdb_store_host() changed interval when doing multiple updates "
455 "using an old timestamp; got: %"PRIscTIME"; expected: %"PRIscTIME,
456 host->interval, 10);
458 /* new interval: 20 us */
459 sdb_store_host("host", 60);
460 fail_unless(host->interval == 11,
461 "sdb_store_host() did not calculate interval correctly: "
462 "got: %"PRIscTIME"; expected: %"PRIscTIME, host->interval, 11);
464 /* new interval: 40 us */
465 sdb_store_host("host", 100);
466 fail_unless(host->interval == 13,
467 "sdb_store_host() did not calculate interval correctly: "
468 "got: %"PRIscTIME"; expected: %"PRIscTIME, host->interval, 11);
470 sdb_object_deref(SDB_OBJ(host));
471 }
472 END_TEST
474 static int
475 iter_incr(sdb_store_obj_t *obj, void *user_data)
476 {
477 intptr_t *i = user_data;
479 fail_unless(obj != NULL,
480 "sdb_store_iterate callback received NULL obj; expected: "
481 "<store base obj>");
482 fail_unless(i != NULL,
483 "sdb_store_iterate callback received NULL user_data; "
484 "expected: <pointer to data>");
486 ++(*i);
487 return 0;
488 } /* iter_incr */
490 static int
491 iter_error(sdb_store_obj_t *obj, void *user_data)
492 {
493 intptr_t *i = user_data;
495 fail_unless(obj != NULL,
496 "sdb_store_iterate callback received NULL obj; expected: "
497 "<store base obj>");
498 fail_unless(i != NULL,
499 "sdb_store_iterate callback received NULL user_data; "
500 "expected: <pointer to data>");
502 ++(*i);
503 return -1;
504 } /* iter_error */
506 START_TEST(test_iterate)
507 {
508 intptr_t i = 0;
509 int check;
511 /* empty store */
512 check = sdb_store_iterate(iter_incr, &i);
513 fail_unless(check == -1,
514 "sdb_store_iterate(), empty store = %d; expected: -1", check);
515 fail_unless(i == 0,
516 "sdb_store_iterate called callback %d times; expected: 0", (int)i);
518 populate();
520 check = sdb_store_iterate(iter_incr, &i);
521 fail_unless(check == 0,
522 "sdb_store_iterate() = %d; expected: 0", check);
523 fail_unless(i == 2,
524 "sdb_store_iterate called callback %d times; expected: 1", (int)i);
526 i = 0;
527 check = sdb_store_iterate(iter_error, &i);
528 fail_unless(check == -1,
529 "sdb_store_iterate(), error callback = %d; expected: -1", check);
530 fail_unless(i == 1,
531 "sdb_store_iterate called callback %d times "
532 "(callback returned error); expected: 1", (int)i);
533 }
534 END_TEST
536 Suite *
537 core_store_suite(void)
538 {
539 Suite *s = suite_create("core::store");
540 TCase *tc;
542 tc = tcase_create("core");
543 tcase_add_test(tc, test_store_tojson);
544 tcase_add_test(tc, test_store_host);
545 tcase_add_test(tc, test_store_get_host);
546 tcase_add_test(tc, test_store_attr);
547 tcase_add_test(tc, test_store_service);
548 tcase_add_test(tc, test_store_service_attr);
549 tcase_add_test(tc, test_interval);
550 tcase_add_test(tc, test_iterate);
551 tcase_add_unchecked_fixture(tc, NULL, sdb_store_clear);
552 suite_add_tcase(s, tc);
554 return s;
555 } /* core_store_suite */
557 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */