a36037452ddbfd3fdfdffc7a2468c83e4c878664
1 /*
2 * SysDB - t/unit/frontend/query_test.c
3 * Copyright (C) 2015 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
32 #include "frontend/connection.h"
33 #include "frontend/parser.h"
34 #include "frontend/connection-private.h"
35 #include "libsysdb_test.h"
37 #include <check.h>
39 /*
40 * private helpers
41 */
43 static void
44 populate(void)
45 {
46 sdb_data_t datum;
48 sdb_store_host("h1", 1);
49 sdb_store_host("h2", 3);
51 datum.type = SDB_TYPE_STRING;
52 datum.data.string = "v1";
53 sdb_store_attribute("h1", "k1", &datum, 1);
54 datum.data.string = "v2";
55 sdb_store_attribute("h1", "k2", &datum, 2);
56 datum.data.string = "v3";
57 sdb_store_attribute("h1", "k3", &datum, 2);
59 sdb_store_metric("h1", "m1", /* store */ NULL, 2);
60 sdb_store_metric("h1", "m2", /* store */ NULL, 1);
61 sdb_store_metric("h2", "m1", /* store */ NULL, 1);
63 datum.type = SDB_TYPE_INTEGER;
64 datum.data.integer = 42;
65 sdb_store_metric_attr("h1", "m1", "k3", &datum, 2);
67 sdb_store_service("h2", "s1", 1);
68 sdb_store_service("h2", "s2", 2);
70 datum.data.integer = 123;
71 sdb_store_service_attr("h2", "s2", "k1", &datum, 2);
72 datum.data.integer = 4711;
73 sdb_store_service_attr("h2", "s2", "k2", &datum, 1);
74 } /* populate */
76 #define HOST_H1 \
77 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", " \
78 "\"update_interval\": \"0s\", \"backends\": [], " \
79 "\"attributes\": [" \
80 "{\"name\": \"k1\", \"value\": \"v1\", " \
81 "\"last_update\": \"1970-01-01 00:00:00 +0000\", " \
82 "\"update_interval\": \"0s\", \"backends\": []}," \
83 "{\"name\": \"k2\", \"value\": \"v2\", " \
84 "\"last_update\": \"1970-01-01 00:00:00 +0000\", " \
85 "\"update_interval\": \"0s\", \"backends\": []}," \
86 "{\"name\": \"k3\", \"value\": \"v3\", " \
87 "\"last_update\": \"1970-01-01 00:00:00 +0000\", " \
88 "\"update_interval\": \"0s\", \"backends\": []}], " \
89 "\"metrics\": [" \
90 "{\"name\": \"m1\", \"timeseries\": false, " \
91 "\"last_update\": \"1970-01-01 00:00:00 +0000\", " \
92 "\"update_interval\": \"0s\", \"backends\": [], " \
93 "\"attributes\": [" \
94 "{\"name\": \"k3\", \"value\": 42, " \
95 "\"last_update\": \"1970-01-01 00:00:00 +0000\", " \
96 "\"update_interval\": \"0s\", \"backends\": []}]}," \
97 "{\"name\": \"m2\", \"timeseries\": false, " \
98 "\"last_update\": \"1970-01-01 00:00:00 +0000\", " \
99 "\"update_interval\": \"0s\", \"backends\": []}]}"
101 #define SERVICE_H2_S1 \
102 "{\"name\": \"h2\", \"last_update\": \"1970-01-01 00:00:00 +0000\", " \
103 "\"update_interval\": \"0s\", \"backends\": [], " \
104 "\"services\": [" \
105 "{\"name\": \"s1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", " \
106 "\"update_interval\": \"0s\", \"backends\": []}]}"
108 #define METRIC_H1_M1 \
109 "{\"name\": \"h1\", \"last_update\": \"1970-01-01 00:00:00 +0000\", " \
110 "\"update_interval\": \"0s\", \"backends\": [], " \
111 "\"metrics\": [" \
112 "{\"name\": \"m1\", \"timeseries\": false, " \
113 "\"last_update\": \"1970-01-01 00:00:00 +0000\", " \
114 "\"update_interval\": \"0s\", \"backends\": [], " \
115 "\"attributes\": [" \
116 "{\"name\": \"k3\", \"value\": 42, " \
117 "\"last_update\": \"1970-01-01 00:00:00 +0000\", " \
118 "\"update_interval\": \"0s\", \"backends\": []}]}]}"
120 typedef struct {
121 sdb_conn_t conn;
122 sdb_strbuf_t *write_buf;
123 } mock_conn_t;
124 #define MOCK_CONN(obj) ((mock_conn_t *)(obj))
125 #define CONN(obj) ((sdb_conn_t *)(obj))
127 static void
128 mock_conn_destroy(sdb_conn_t *conn)
129 {
130 sdb_strbuf_destroy(conn->buf);
131 sdb_strbuf_destroy(conn->errbuf);
132 sdb_strbuf_destroy(MOCK_CONN(conn)->write_buf);
133 free(conn);
134 } /* mock_conn_destroy */
136 static ssize_t
137 mock_conn_read(sdb_conn_t *conn, size_t len)
138 {
139 if (! conn)
140 return -1;
141 /* unused so far */
142 return len;
143 } /* conn_read */
145 static ssize_t
146 mock_conn_write(sdb_conn_t *conn, const void *buf, size_t len)
147 {
148 if (! conn)
149 return -1;
150 return sdb_strbuf_memappend(MOCK_CONN(conn)->write_buf, buf, len);
151 } /* conn_write */
153 static sdb_conn_t *
154 mock_conn_create(void)
155 {
156 mock_conn_t *conn;
158 conn = calloc(1, sizeof(*conn));
159 if (! conn) {
160 fail("INTERNAL ERROR: failed to allocate connection object");
161 return NULL;
162 }
164 SDB_OBJ(conn)->name = "mock_connection";
165 SDB_OBJ(conn)->ref_cnt = 1;
167 conn->conn.buf = sdb_strbuf_create(0);
168 conn->conn.errbuf = sdb_strbuf_create(0);
169 conn->write_buf = sdb_strbuf_create(64);
170 if ((! conn->conn.buf) || (! conn->conn.errbuf) || (! conn->write_buf)) {
171 mock_conn_destroy(CONN(conn));
172 fail("INTERNAL ERROR: failed to allocate connection object");
173 return NULL;
174 }
176 conn->conn.read = mock_conn_read;
177 conn->conn.write = mock_conn_write;
179 conn->conn.username = "mock_user";
180 conn->conn.cmd = SDB_CONNECTION_IDLE;
181 conn->conn.cmd_len = 0;
182 return CONN(conn);
183 } /* mock_conn_create */
185 /* TODO: move this into a test helper module */
186 static void
187 fail_if_strneq(const char *got, const char *expected, size_t n, const char *fmt, ...)
188 {
189 sdb_strbuf_t *buf;
190 va_list ap;
192 size_t len1 = strlen(got);
193 size_t len2 = strlen(expected);
195 size_t i;
196 int pos = -1;
198 if (n) {
199 len1 = SDB_MIN(len1, n);
200 len2 = SDB_MIN(len2, n);
201 }
203 if (len1 != len2)
204 pos = (int)SDB_MIN(len1, len2);
206 for (i = 0; i < SDB_MIN(len1, len2); ++i) {
207 if (got[i] != expected[i]) {
208 pos = (int)i;
209 break;
210 }
211 }
213 if (pos == -1)
214 return;
216 buf = sdb_strbuf_create(64);
217 va_start(ap, fmt);
218 sdb_strbuf_vsprintf(buf, fmt, ap);
220 fail("%s\n got: %s\n %*s\n expected: %s",
221 sdb_strbuf_string(buf), got, pos + 1, "^", expected);
222 } /* fail_if_strneq */
224 /*
225 * tests
226 */
228 static struct {
229 int type;
230 const char *hostname;
231 const char *name;
232 const char *filter;
234 int expected;
235 uint32_t code;
236 size_t len;
237 const char *data;
238 } exec_fetch_data[] = {
239 /* hosts */
240 {
241 SDB_HOST, "h1", NULL, NULL,
242 0, SDB_CONNECTION_DATA, 851, HOST_H1,
243 },
244 {
245 SDB_HOST, "h1", NULL, "age >= 0s", /* always matches */
246 0, SDB_CONNECTION_DATA, 851, HOST_H1,
247 },
248 {
249 SDB_HOST, "h1", NULL, "age < 0s", /* never matches */
250 -1, UINT32_MAX, 0, NULL, /* FETCH fails if the object doesn't exist */
251 },
252 {
253 SDB_HOST, "x1", NULL, NULL, /* does not exist */
254 -1, UINT32_MAX, 0, NULL,
255 },
256 {
257 SDB_HOST, "h1", "s1", NULL, /* invalid args */
258 -1, UINT32_MAX, 0, NULL,
259 },
260 /* services */
261 {
262 SDB_SERVICE, "h2", "s1", NULL,
263 0, SDB_CONNECTION_DATA, 218, SERVICE_H2_S1,
264 },
265 {
266 SDB_SERVICE, "h2", "s1", "age >= 0s", /* always matches */
267 0, SDB_CONNECTION_DATA, 218, SERVICE_H2_S1,
268 },
269 {
270 SDB_SERVICE, "h2", "s1", "age < 0s", /* never matches */
271 -1, UINT32_MAX, 0, NULL,
272 },
273 {
274 SDB_SERVICE, "h2", "s1", "name = 'h2'", /* only matches host */
275 -1, UINT32_MAX, 0, NULL,
276 },
277 {
278 SDB_SERVICE, "h2", "x1", NULL, /* does not exist */
279 -1, UINT32_MAX, 0, NULL,
280 },
281 {
282 SDB_SERVICE, "x2", "s1", NULL, /* does not exist */
283 -1, UINT32_MAX, 0, NULL,
284 },
285 {
286 SDB_SERVICE, "h2", NULL, NULL, /* invalid args */
287 -1, UINT32_MAX, 0, NULL,
288 },
289 /* metrics */
290 {
291 SDB_METRIC, "h1", "m1", NULL,
292 0, SDB_CONNECTION_DATA, 368, METRIC_H1_M1,
293 },
294 {
295 SDB_METRIC, "h1", "m1", "age >= 0s", /* always matches */
296 0, SDB_CONNECTION_DATA, 368, METRIC_H1_M1,
297 },
298 {
299 SDB_METRIC, "h1", "m1", "age < 0s", /* never matches */
300 -1, UINT32_MAX, 0, NULL,
301 },
302 {
303 SDB_METRIC, "h1", "m1", "name = 'h1'", /* only matches host */
304 -1, UINT32_MAX, 0, NULL,
305 },
306 {
307 SDB_METRIC, "h1", "x1", NULL, /* does not exist */
308 -1, UINT32_MAX, 0, NULL,
309 },
310 {
311 SDB_METRIC, "x1", "m1", NULL, /* does not exist */
312 -1, UINT32_MAX, 0, NULL,
313 },
314 {
315 SDB_METRIC, "x1", NULL, NULL, /* invalid args */
316 -1, UINT32_MAX, 0, NULL,
317 },
318 };
320 START_TEST(test_exec_fetch)
321 {
322 sdb_conn_t *conn = mock_conn_create();
323 sdb_store_matcher_t *filter = NULL;
325 uint32_t code = UINT32_MAX, msg_len = UINT32_MAX;
326 const char *data;
327 ssize_t tmp;
328 size_t len;
329 int check;
331 if (exec_fetch_data[_i].filter) {
332 filter = sdb_fe_parse_matcher(exec_fetch_data[_i].filter, -1, NULL);
333 ck_assert_msg(filter != NULL);
334 }
336 check = sdb_fe_exec_fetch(conn, exec_fetch_data[_i].type,
337 exec_fetch_data[_i].hostname, exec_fetch_data[_i].name, filter);
338 fail_unless(check == exec_fetch_data[_i].expected,
339 "sdb_fe_exec_fetch(%s, %s, %s, %s) = %d; expected: %d",
340 SDB_STORE_TYPE_TO_NAME(exec_fetch_data[_i].type),
341 exec_fetch_data[_i].hostname, exec_fetch_data[_i].name,
342 exec_fetch_data[_i].filter, check, exec_fetch_data[_i].expected);
344 len = sdb_strbuf_len(MOCK_CONN(conn)->write_buf);
346 if (exec_fetch_data[_i].code == UINT32_MAX) {
347 fail_unless(len == 0,
348 "sdb_fe_exec_fetch(%s, %s, %s, %s) returned data on error",
349 SDB_STORE_TYPE_TO_NAME(exec_fetch_data[_i].type),
350 exec_fetch_data[_i].hostname, exec_fetch_data[_i].name,
351 exec_fetch_data[_i].filter);
352 sdb_object_deref(SDB_OBJ(filter));
353 mock_conn_destroy(conn);
354 return;
355 }
357 data = sdb_strbuf_string(MOCK_CONN(conn)->write_buf);
358 tmp = sdb_proto_unmarshal_header(data, len, &code, &msg_len);
359 ck_assert_msg(tmp == (ssize_t)(2 * sizeof(uint32_t)));
360 data += tmp;
361 len -= tmp;
363 fail_unless((code == exec_fetch_data[_i].code)
364 && ((size_t)msg_len == exec_fetch_data[_i].len),
365 "sdb_fe_exec_fetch(%s, %s, %s, %s) returned %u, %u; expected: %u, %zu",
366 SDB_STORE_TYPE_TO_NAME(exec_fetch_data[_i].type),
367 exec_fetch_data[_i].hostname, exec_fetch_data[_i].name,
368 exec_fetch_data[_i].filter, code, msg_len,
369 exec_fetch_data[_i].code, exec_fetch_data[_i].len);
371 tmp = sdb_proto_unmarshal_int32(data, len, &code);
372 fail_unless(code == SDB_CONNECTION_FETCH,
373 "sdb_fe_exec_fetch(%s, %s, %s, %s) returned %s object; expected: FETCH",
374 SDB_STORE_TYPE_TO_NAME(exec_fetch_data[_i].type),
375 exec_fetch_data[_i].hostname, exec_fetch_data[_i].name,
376 exec_fetch_data[_i].filter, SDB_CONN_MSGTYPE_TO_STRING((int)code));
377 data += tmp;
378 len -= tmp;
380 fail_if_strneq(data, exec_fetch_data[_i].data, (size_t)msg_len,
381 "sdb_fe_exec_fetch(%s, %s, %s, %s) returned '%s'; expected: '%s'",
382 SDB_STORE_TYPE_TO_NAME(exec_fetch_data[_i].type),
383 exec_fetch_data[_i].hostname, exec_fetch_data[_i].name,
384 exec_fetch_data[_i].filter, data, exec_fetch_data[_i].data);
386 sdb_object_deref(SDB_OBJ(filter));
387 mock_conn_destroy(conn);
388 }
389 END_TEST
391 Suite *
392 fe_query_suite(void)
393 {
394 Suite *s = suite_create("frontend::query");
395 TCase *tc;
397 tc = tcase_create("core");
398 tcase_add_checked_fixture(tc, populate, sdb_store_clear);
399 tcase_add_loop_test(tc, test_exec_fetch, 0, SDB_STATIC_ARRAY_LEN(exec_fetch_data));
400 suite_add_tcase(s, tc);
402 return s;
403 } /* fe_query_suite */
405 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */