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, 2);
48 datum.data.string = "v3";
49 sdb_store_attribute("h1", "k3", &datum, 2);
51 /* make sure that older updates don't overwrite existing values */
52 datum.data.string = "fail";
53 sdb_store_attribute("h1", "k2", &datum, 1);
54 sdb_store_attribute("h1", "k3", &datum, 2);
56 sdb_store_service("h2", "s1", 1);
57 sdb_store_service("h2", "s2", 1);
59 datum.type = SDB_TYPE_INTEGER;
60 datum.data.integer = 123;
61 sdb_store_service_attr("h2", "s2", "k1", &datum, 2);
63 /* don't overwrite */
64 datum.data.integer = 666;
65 sdb_store_service_attr("h2", "s2", "k1", &datum, 2);
66 } /* populate */
68 START_TEST(test_store_host)
69 {
70 struct {
71 const char *name;
72 sdb_time_t last_update;
73 int expected;
74 } golden_data[] = {
75 { "a", 1, 0 },
76 { "a", 2, 0 },
77 { "a", 1, 1 },
78 { "b", 1, 0 },
79 { "b", 1, 1 },
80 { "A", 1, 1 }, /* case-insensitive */
81 { "A", 3, 0 },
82 };
84 struct {
85 const char *name;
86 _Bool has;
87 } golden_hosts[] = {
88 { "a", 1 == 1 },
89 { "b", 1 == 1 },
90 { "c", 0 == 1 },
91 { "A", 1 == 1 },
92 };
94 size_t i;
96 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
97 int status;
99 status = sdb_store_host(golden_data[i].name,
100 golden_data[i].last_update);
101 fail_unless(status == golden_data[i].expected,
102 "sdb_store_host(%s, %d) = %d; expected: %d",
103 golden_data[i].name, (int)golden_data[i].last_update,
104 status, golden_data[i].expected);
105 }
107 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_hosts); ++i) {
108 _Bool has;
110 has = sdb_store_has_host(golden_hosts[i].name);
111 fail_unless(has == golden_hosts[i].has,
112 "sdb_store_has_host(%s) = %d; expected: %d",
113 golden_hosts[i].name, has, golden_hosts[i].has);
114 }
115 }
116 END_TEST
118 START_TEST(test_store_get_host)
119 {
120 char *golden_hosts[] = { "a", "b", "c" };
121 char *unknown_hosts[] = { "x", "y", "z" };
122 size_t i;
124 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_hosts); ++i) {
125 int status = sdb_store_host(golden_hosts[i], 1);
126 fail_unless(status >= 0,
127 "sdb_store_host(%s) = %d; expected: >=0",
128 golden_hosts[i], status);
129 }
131 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_hosts); ++i) {
132 sdb_store_obj_t *sobj1, *sobj2;
133 int ref_cnt;
135 fail_unless(sdb_store_has_host(golden_hosts[i]),
136 "sdb_store_has_host(%s) = FALSE; expected: TRUE",
137 golden_hosts[i]);
139 sobj1 = sdb_store_get_host(golden_hosts[i]);
140 fail_unless(sobj1 != NULL,
141 "sdb_store_get_host(%s) = NULL; expected: <host>",
142 golden_hosts[i]);
143 ref_cnt = SDB_OBJ(sobj1)->ref_cnt;
145 fail_unless(ref_cnt > 1,
146 "sdb_store_get_host(%s) did not increment ref count: "
147 "got: %d; expected: >1", golden_hosts[i], ref_cnt);
149 sobj2 = sdb_store_get_host(golden_hosts[i]);
150 fail_unless(sobj2 != NULL,
151 "sdb_store_get_host(%s) = NULL; expected: <host>",
152 golden_hosts[i]);
154 fail_unless(sobj1 == sobj2,
155 "sdb_store_get_host(%s) returned different objects "
156 "in successive calls", golden_hosts[i]);
157 fail_unless(SDB_OBJ(sobj2)->ref_cnt == ref_cnt + 1,
158 "sdb_store_get_hosts(%s) did not increment ref count "
159 "(first call: %d; second call: %d)",
160 golden_hosts[i], ref_cnt, SDB_OBJ(sobj2)->ref_cnt);
162 sdb_object_deref(SDB_OBJ(sobj1));
163 sdb_object_deref(SDB_OBJ(sobj2));
164 }
165 for (i = 0; i < SDB_STATIC_ARRAY_LEN(unknown_hosts); ++i) {
166 sdb_store_obj_t *sobj;
168 fail_unless(!sdb_store_has_host(unknown_hosts[i]),
169 "sdb_store_has_host(%s) = TRUE; expected: FALSE",
170 unknown_hosts[i]);
172 sobj = sdb_store_get_host(unknown_hosts[i]);
173 fail_unless(!sobj, "sdb_store_get_host(%s) = <host:%s>; expected: NULL",
174 unknown_hosts[i], sobj ? SDB_OBJ(sobj)->name : "NULL");
175 }
176 }
177 END_TEST
179 START_TEST(test_store_attr)
180 {
181 struct {
182 const char *host;
183 const char *key;
184 char *value;
185 sdb_time_t last_update;
186 int expected;
187 } golden_data[] = {
188 { "k", "k", "v", 1, -1 },
189 { "k", "k", "v", 1, -1 }, /* retry to ensure the host is not created */
190 { "l", "k1", "v1", 1, 0 },
191 { "l", "k1", "v2", 2, 0 },
192 { "l", "k1", "v3", 2, 1 },
193 { "l", "k2", "v1", 1, 0 },
194 { "m", "k", "v1", 1, 0 },
195 { "m", "k", "v2", 1, 1 },
196 };
198 size_t i;
200 sdb_store_host("l", 1);
201 sdb_store_host("m", 1);
202 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
203 sdb_data_t datum;
204 int status;
206 /* XXX: test other types as well */
207 datum.type = SDB_TYPE_STRING;
208 datum.data.string = golden_data[i].value;
210 status = sdb_store_attribute(golden_data[i].host,
211 golden_data[i].key, &datum,
212 golden_data[i].last_update);
213 fail_unless(status == golden_data[i].expected,
214 "sdb_store_attribute(%s, %s, %s, %d) = %d; expected: %d",
215 golden_data[i].host, golden_data[i].key, golden_data[i].value,
216 golden_data[i].last_update, status, golden_data[i].expected);
217 }
218 }
219 END_TEST
221 START_TEST(test_store_service)
222 {
223 struct {
224 const char *host;
225 const char *svc;
226 sdb_time_t last_update;
227 int expected;
228 } golden_data[] = {
229 { "k", "s", 1, -1 },
230 { "k", "s", 1, -1 }, /* retry to ensure the host is not created */
231 { "l", "s1", 1, 0 },
232 { "l", "s1", 2, 0 },
233 { "l", "s1", 2, 1 },
234 { "l", "s2", 1, 0 },
235 { "m", "s", 1, 0 },
236 { "m", "s", 1, 1 },
237 };
239 size_t i;
241 sdb_store_host("m", 1);
242 sdb_store_host("l", 1);
243 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
244 int status;
246 status = sdb_store_service(golden_data[i].host,
247 golden_data[i].svc, golden_data[i].last_update);
248 fail_unless(status == golden_data[i].expected,
249 "sdb_store_service(%s, %s, %d) = %d; expected: %d",
250 golden_data[i].host, golden_data[i].svc,
251 golden_data[i].last_update, status, golden_data[i].expected);
252 }
253 }
254 END_TEST
256 START_TEST(test_store_service_attr)
257 {
258 struct {
259 const char *host;
260 const char *svc;
261 const char *attr;
262 const sdb_data_t value;
263 sdb_time_t last_update;
264 int expected;
265 } golden_data[] = {
266 { "k", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
267 /* retry, it should still fail */
268 { "k", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
269 { "l", "sX", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
270 /* retry, it should still fail */
271 { "l", "sX", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, -1 },
272 { "l", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 0 },
273 { "l", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 1 },
274 { "l", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 2, 0 },
275 { "l", "s1", "a2", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 0 },
276 { "l", "s1", "a2", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 1 },
277 { "l", "s2", "a2", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 0 },
278 { "m", "s1", "a1", { SDB_TYPE_INTEGER, { .integer = 123 } }, 1, 0 },
279 };
281 size_t i;
283 sdb_store_host("m", 1);
284 sdb_store_host("l", 1);
285 sdb_store_service("m", "s1", 1);
286 sdb_store_service("l", "s1", 1);
287 sdb_store_service("l", "s2", 1);
289 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
290 int status;
292 status = sdb_store_service_attr(golden_data[i].host,
293 golden_data[i].svc, golden_data[i].attr,
294 &golden_data[i].value, golden_data[i].last_update);
295 fail_unless(status == golden_data[i].expected,
296 "sdb_store_service_attr(%s, %s, %s, %d, %d) = %d; "
297 "expected: %d", golden_data[i].host, golden_data[i].svc,
298 golden_data[i].attr, golden_data[i].value.data.integer,
299 golden_data[i].last_update, status, golden_data[i].expected);
300 }
301 }
302 END_TEST
304 static void
305 verify_json_output(sdb_strbuf_t *buf, const char *expected, int flags)
306 {
307 int pos;
308 size_t len1, len2;
309 size_t i;
311 len1 = strlen(sdb_strbuf_string(buf));
312 len2 = strlen(expected);
314 pos = -1;
315 if (len1 != len2)
316 pos = (int)(len1 <= len2 ? len1 : len2);
318 for (i = 0; i < (len1 <= len2 ? len1 : len2); ++i) {
319 if (sdb_strbuf_string(buf)[i] != expected[i]) {
320 pos = (int)i;
321 break;
322 }
323 }
325 fail_unless(pos == -1,
326 "sdb_store_tojson(%x) returned unexpected result\n"
327 " got: %s\n %*s\n expected: %s",
328 flags, sdb_strbuf_string(buf), pos + 1, "^", expected);
329 } /* verify_json_output */
331 START_TEST(test_store_tojson)
332 {
333 sdb_strbuf_t *buf;
334 size_t i;
336 struct {
337 int flags;
338 const char *expected;
339 } golden_data[] = {
340 { 0, "{\"hosts\":["
341 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
342 "\"update_interval\": \"0s\", \"backends\": [], "
343 "\"attributes\": ["
344 "{\"name\": \"k1\", \"value\": \"v1\", "
345 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
346 "\"update_interval\": \"0s\", \"backends\": []},"
347 "{\"name\": \"k2\", \"value\": \"v2\", "
348 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
349 "\"update_interval\": \"0s\", \"backends\": []},"
350 "{\"name\": \"k3\", \"value\": \"v3\", "
351 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
352 "\"update_interval\": \"0s\", \"backends\": []}"
353 "], "
354 "\"services\": []},"
355 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
356 "\"update_interval\": \"0s\", \"backends\": [], "
357 "\"attributes\": [], "
358 "\"services\": ["
359 "{\"name\": \"s1\", "
360 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
361 "\"update_interval\": \"0s\", \"backends\": [], "
362 "\"attributes\": []},"
363 "{\"name\": \"s2\", "
364 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
365 "\"update_interval\": \"0s\", \"backends\": [], "
366 "\"attributes\": ["
367 "{\"name\": \"k1\", \"value\": 123, "
368 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
369 "\"update_interval\": \"0s\", \"backends\": []}"
370 "]}"
371 "]}"
372 "]}" },
373 { SDB_SKIP_SERVICES,
374 "{\"hosts\":["
375 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
376 "\"update_interval\": \"0s\", \"backends\": [], "
377 "\"attributes\": ["
378 "{\"name\": \"k1\", \"value\": \"v1\", "
379 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
380 "\"update_interval\": \"0s\", \"backends\": []},"
381 "{\"name\": \"k2\", \"value\": \"v2\", "
382 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
383 "\"update_interval\": \"0s\", \"backends\": []},"
384 "{\"name\": \"k3\", \"value\": \"v3\", "
385 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
386 "\"update_interval\": \"0s\", \"backends\": []}"
387 "]},"
388 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
389 "\"update_interval\": \"0s\", \"backends\": [], "
390 "\"attributes\": []}"
391 "]}" },
392 { SDB_SKIP_ATTRIBUTES,
393 "{\"hosts\":["
394 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
395 "\"update_interval\": \"0s\", \"backends\": [], "
396 "\"services\": []},"
397 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
398 "\"update_interval\": \"0s\", \"backends\": [], "
399 "\"services\": ["
400 "{\"name\": \"s1\", "
401 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
402 "\"update_interval\": \"0s\", \"backends\": []},"
403 "{\"name\": \"s2\", "
404 "\"last_update\": \"1970-01-01 00:00:00 +0000\", "
405 "\"update_interval\": \"0s\", \"backends\": []}"
406 "]}"
407 "]}" },
408 { SDB_SKIP_SERVICES | SDB_SKIP_ATTRIBUTES,
409 "{\"hosts\":["
410 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
411 "\"update_interval\": \"0s\", \"backends\": []},"
412 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", "
413 "\"update_interval\": \"0s\", \"backends\": []}"
414 "]}" },
415 };
417 buf = sdb_strbuf_create(0);
418 fail_unless(buf != NULL, "INTERNAL ERROR: failed to create string buffer");
419 populate();
421 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
422 int status;
424 sdb_strbuf_clear(buf);
426 status = sdb_store_tojson(buf, golden_data[i].flags);
427 fail_unless(status == 0,
428 "sdb_store_tojson(%x) = %d; expected: 0",
429 golden_data[i].flags, status);
431 verify_json_output(buf, golden_data[i].expected, golden_data[i].flags);
432 }
433 sdb_strbuf_destroy(buf);
434 }
435 END_TEST
437 START_TEST(test_interval)
438 {
439 sdb_store_obj_t *host;
441 /* 10 us interval */
442 sdb_store_host("host", 10);
443 sdb_store_host("host", 20);
444 sdb_store_host("host", 30);
445 sdb_store_host("host", 40);
447 host = sdb_store_get_host("host");
448 fail_unless(host != NULL,
449 "INTERNAL ERROR: store doesn't have host after adding it");
451 fail_unless(host->interval == 10,
452 "sdb_store_host() did not calculate interval correctly: "
453 "got: %"PRIscTIME"; expected: %"PRIscTIME, host->interval, 10);
455 /* multiple updates for the same timestamp don't modify the interval */
456 sdb_store_host("host", 40);
457 sdb_store_host("host", 40);
458 sdb_store_host("host", 40);
459 sdb_store_host("host", 40);
461 fail_unless(host->interval == 10,
462 "sdb_store_host() changed interval when doing multiple updates "
463 "using the same timestamp; got: %"PRIscTIME"; "
464 "expected: %"PRIscTIME, host->interval, 10);
466 /* multiple updates using an timestamp don't modify the interval */
467 sdb_store_host("host", 20);
468 sdb_store_host("host", 20);
469 sdb_store_host("host", 20);
470 sdb_store_host("host", 20);
472 fail_unless(host->interval == 10,
473 "sdb_store_host() changed interval when doing multiple updates "
474 "using an old timestamp; got: %"PRIscTIME"; expected: %"PRIscTIME,
475 host->interval, 10);
477 /* new interval: 20 us */
478 sdb_store_host("host", 60);
479 fail_unless(host->interval == 11,
480 "sdb_store_host() did not calculate interval correctly: "
481 "got: %"PRIscTIME"; expected: %"PRIscTIME, host->interval, 11);
483 /* new interval: 40 us */
484 sdb_store_host("host", 100);
485 fail_unless(host->interval == 13,
486 "sdb_store_host() did not calculate interval correctly: "
487 "got: %"PRIscTIME"; expected: %"PRIscTIME, host->interval, 11);
489 sdb_object_deref(SDB_OBJ(host));
490 }
491 END_TEST
493 static int
494 iter_incr(sdb_store_obj_t *obj, void *user_data)
495 {
496 intptr_t *i = user_data;
498 fail_unless(obj != NULL,
499 "sdb_store_iterate callback received NULL obj; expected: "
500 "<store base obj>");
501 fail_unless(i != NULL,
502 "sdb_store_iterate callback received NULL user_data; "
503 "expected: <pointer to data>");
505 ++(*i);
506 return 0;
507 } /* iter_incr */
509 static int
510 iter_error(sdb_store_obj_t *obj, void *user_data)
511 {
512 intptr_t *i = user_data;
514 fail_unless(obj != NULL,
515 "sdb_store_iterate callback received NULL obj; expected: "
516 "<store base obj>");
517 fail_unless(i != NULL,
518 "sdb_store_iterate callback received NULL user_data; "
519 "expected: <pointer to data>");
521 ++(*i);
522 return -1;
523 } /* iter_error */
525 START_TEST(test_iterate)
526 {
527 intptr_t i = 0;
528 int check;
530 /* empty store */
531 check = sdb_store_iterate(iter_incr, &i);
532 fail_unless(check == -1,
533 "sdb_store_iterate(), empty store = %d; expected: -1", check);
534 fail_unless(i == 0,
535 "sdb_store_iterate called callback %d times; expected: 0", (int)i);
537 populate();
539 check = sdb_store_iterate(iter_incr, &i);
540 fail_unless(check == 0,
541 "sdb_store_iterate() = %d; expected: 0", check);
542 fail_unless(i == 2,
543 "sdb_store_iterate called callback %d times; expected: 1", (int)i);
545 i = 0;
546 check = sdb_store_iterate(iter_error, &i);
547 fail_unless(check == -1,
548 "sdb_store_iterate(), error callback = %d; expected: -1", check);
549 fail_unless(i == 1,
550 "sdb_store_iterate called callback %d times "
551 "(callback returned error); expected: 1", (int)i);
552 }
553 END_TEST
555 Suite *
556 core_store_suite(void)
557 {
558 Suite *s = suite_create("core::store");
559 TCase *tc;
561 tc = tcase_create("core");
562 tcase_add_test(tc, test_store_tojson);
563 tcase_add_test(tc, test_store_host);
564 tcase_add_test(tc, test_store_get_host);
565 tcase_add_test(tc, test_store_attr);
566 tcase_add_test(tc, test_store_service);
567 tcase_add_test(tc, test_store_service_attr);
568 tcase_add_test(tc, test_interval);
569 tcase_add_test(tc, test_iterate);
570 tcase_add_unchecked_fixture(tc, NULL, sdb_store_clear);
571 suite_add_tcase(s, tc);
573 return s;
574 } /* core_store_suite */
576 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */