a0826070ed958a6e864998af1eecf3890e944906
1 /*
2 * SysDB - t/unit/frontend/parser_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 "frontend/connection.h"
29 #include "frontend/parser.h"
30 #include "core/store-private.h"
31 #include "core/object.h"
32 #include "libsysdb_test.h"
34 #include <check.h>
36 /*
37 * tests
38 */
40 START_TEST(test_parse)
41 {
42 struct {
43 const char *query;
44 int len;
45 int expected;
46 sdb_conn_state_t expected_cmd;
47 } golden_data[] = {
48 /* empty commands */
49 { NULL, -1, -1, 0 },
50 { "", -1, 0, 0 },
51 { ";", -1, 0, 0 },
52 { ";;", -1, 0, 0 },
54 /* valid commands */
55 { "FETCH 'host'", -1, 1, CONNECTION_FETCH },
56 { "LIST", -1, 1, CONNECTION_LIST },
57 { "LIST -- comment", -1, 1, CONNECTION_LIST },
58 { "LIST;", -1, 1, CONNECTION_LIST },
59 { "LIST; INVALID", 5, 1, CONNECTION_LIST },
61 { "LOOKUP hosts MATCHING "
62 "host = 'host'", -1, 1, CONNECTION_LOOKUP },
63 { "LOOKUP hosts MATCHING NOT "
64 "host = 'host'", -1, 1, CONNECTION_LOOKUP },
65 { "LOOKUP hosts MATCHING "
66 "host =~ 'p' AND "
67 "service =~ 'p'", -1, 1, CONNECTION_LOOKUP },
68 { "LOOKUP hosts MATCHING NOT "
69 "host =~ 'p' AND "
70 "service =~ 'p'", -1, 1, CONNECTION_LOOKUP },
71 { "LOOKUP hosts MATCHING "
72 "host =~ 'p' AND "
73 "service =~ 'p' OR "
74 "service =~ 'r'", -1, 1, CONNECTION_LOOKUP },
75 { "LOOKUP hosts MATCHING NOT "
76 "host =~ 'p' AND "
77 "service =~ 'p' OR "
78 "service =~ 'r'", -1, 1, CONNECTION_LOOKUP },
80 /* numeric constants */
81 { "LOOKUP hosts MATCHING "
82 "attribute.foo = "
83 "1234", -1, 1, CONNECTION_LOOKUP },
84 { "LOOKUP hosts MATCHING "
85 "attribute.foo != "
86 "+234", -1, 1, CONNECTION_LOOKUP },
87 { "LOOKUP hosts MATCHING "
88 "attribute.foo < "
89 "-234", -1, 1, CONNECTION_LOOKUP },
90 { "LOOKUP hosts MATCHING "
91 "attribute.foo > "
92 "12.4", -1, 1, CONNECTION_LOOKUP },
93 { "LOOKUP hosts MATCHING "
94 "attribute.foo <= "
95 "12. + .3", -1, 1, CONNECTION_LOOKUP },
96 { "LOOKUP hosts MATCHING "
97 "attribute.foo >= "
98 ".4", -1, 1, CONNECTION_LOOKUP },
99 { "LOOKUP hosts MATCHING "
100 "attribute.foo = "
101 "+12e3", -1, 1, CONNECTION_LOOKUP },
102 { "LOOKUP hosts MATCHING "
103 "attribute.foo = "
104 "+12e-3", -1, 1, CONNECTION_LOOKUP },
105 { "LOOKUP hosts MATCHING "
106 "attribute.foo = "
107 "-12e+3", -1, 1, CONNECTION_LOOKUP },
109 /* date, time, interval constants */
110 { "LOOKUP hosts MATCHING "
111 "attribute.foo = "
112 "1 Y 42D", -1, 1, CONNECTION_LOOKUP },
113 { "LOOKUP hosts MATCHING "
114 "attribute.foo = "
115 "1s 42D", -1, 1, CONNECTION_LOOKUP },
116 /*
117 * TODO: Something like 1Y42D should work as well but it doesn't since
118 * the scanner will tokenize it into {digit}{identifier} :-/
119 *
120 { "LOOKUP hosts MATCHING "
121 "attribute.foo = "
122 "1Y42D", -1, 1, CONNECTION_LOOKUP },
123 */
125 /* NULL */
126 { "LOOKUP hosts MATCHING "
127 "attribute.foo "
128 "IS NULL", -1, 1, CONNECTION_LOOKUP },
129 { "LOOKUP hosts MATCHING "
130 "attribute.foo "
131 "IS NOT NULL", -1, 1, CONNECTION_LOOKUP },
132 { "LOOKUP hosts MATCHING "
133 "NOT attribute.foo "
134 "IS NULL", -1, 1, CONNECTION_LOOKUP },
135 { "LOOKUP hosts MATCHING "
136 "host IS NULL", -1, -1, 0 },
137 { "LOOKUP hosts MATCHING "
138 "service IS NULL", -1, -1, 0 },
140 /* invalid numeric constants */
141 { "LOOKUP hosts MATCHING "
142 "attribute.foo = "
143 "+-12e+3", -1, -1, 0 },
144 { "LOOKUP hosts MATCHING "
145 "attribute.foo = "
146 "-12e-+3", -1, -1, 0 },
147 { "LOOKUP hosts MATCHING "
148 "attribute.foo = "
149 "e+3", -1, -1, 0 },
150 { "LOOKUP hosts MATCHING "
151 "attribute.foo = "
152 "3e", -1, -1, 0 },
153 /* following SQL standard, we don't support hex numbers */
154 { "LOOKUP hosts MATCHING "
155 "attribute.foo = "
156 "0x12", -1, -1, 0 },
158 /* invalid expressions */
159 { "LOOKUP hosts MATCHING "
160 "attribute.foo = "
161 "1.23 + 'foo'", -1, -1, 0 },
163 /* comments */
164 { "/* some comment */", -1, 0, 0 },
165 { "-- another comment", -1, 0, 0 },
167 /* syntax errors */
168 { "INVALID", -1, -1, 0 },
169 { "FETCH host", -1, -1, 0 },
170 { "LIST; INVALID", 8, -1, 0 },
171 { "/* some incomplete", -1, -1, 0 },
173 { "LOOKUP hosts", -1, -1, 0 },
174 { "LOOKUP foo MATCHING "
175 "host = 'host'", -1, -1, 0 },
176 };
178 size_t i;
179 sdb_llist_t *check;
181 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
182 sdb_object_t *obj;
183 _Bool ok;
185 check = sdb_fe_parse(golden_data[i].query, golden_data[i].len);
186 if (golden_data[i].expected < 0)
187 ok = check == 0;
188 else
189 ok = sdb_llist_len(check) == (size_t)golden_data[i].expected;
191 fail_unless(ok, "sdb_fe_parse(%s) = %p (len: %zu); expected: %d",
192 golden_data[i].query, check, sdb_llist_len(check),
193 golden_data[i].expected);
195 if (! check)
196 continue;
198 if ((! golden_data[i].expected_cmd)
199 || (golden_data[i].expected <= 0)) {
200 sdb_llist_destroy(check);
201 continue;
202 }
204 obj = sdb_llist_get(check, 0);
205 fail_unless(SDB_CONN_NODE(obj)->cmd == golden_data[i].expected_cmd,
206 "sdb_fe_parse(%s)->cmd = %i; expected: %d",
207 golden_data[i].query, SDB_CONN_NODE(obj)->cmd,
208 golden_data[i].expected_cmd);
209 sdb_object_deref(obj);
210 sdb_llist_destroy(check);
211 }
212 }
213 END_TEST
215 START_TEST(test_parse_matcher)
216 {
217 struct {
218 const char *expr;
219 int len;
220 int expected;
221 } golden_data[] = {
222 /* empty expressions */
223 { NULL, -1, -1 },
224 { "", -1, -1 },
226 /* valid expressions */
227 { "host = 'localhost'", -1, MATCHER_NAME },
228 { "host != 'localhost'", -1, MATCHER_NOT },
229 { "host =~ 'host'", -1, MATCHER_NAME },
230 { "host !~ 'host'", -1, MATCHER_NOT },
231 { "host = 'localhost' -- foo", -1, MATCHER_NAME },
232 { "host = 'host' <garbage>", 13, MATCHER_NAME },
233 /* match hosts by service */
234 { "service = 'name'", -1, MATCHER_NAME },
235 { "service != 'name'", -1, MATCHER_NOT },
236 { "service =~ 'pattern'", -1, MATCHER_NAME },
237 { "service !~ 'pattern'", -1, MATCHER_NOT },
238 /* match hosts by attribute */
239 { "attribute = 'name'", -1, MATCHER_NAME },
240 { "attribute != 'name'", -1, MATCHER_NOT },
241 { "attribute =~ 'pattern'", -1, MATCHER_NAME },
242 { "attribute !~ 'pattern'", -1, MATCHER_NOT },
243 /* composite expressions */
244 { "host =~ 'pattern' AND "
245 "service =~ 'pattern'", -1, MATCHER_AND },
246 { "host =~ 'pattern' OR "
247 "service =~ 'pattern'", -1, MATCHER_OR },
248 { "NOT host = 'host'", -1, MATCHER_NOT },
249 /* numeric expressions */
250 { "attribute.foo < 123", -1, MATCHER_LT },
251 { "attribute.foo <= 123", -1, MATCHER_LE },
252 { "attribute.foo = 123", -1, MATCHER_EQ },
253 { "attribute.foo >= 123", -1, MATCHER_GE },
254 { "attribute.foo > 123", -1, MATCHER_GT },
255 /* NULL; while this is an implementation detail,
256 * IS NULL currently maps to an equality matcher */
257 { "attribute.foo IS NULL", -1, MATCHER_ISNULL },
258 { "attribute.foo IS NOT NULL", -1, MATCHER_NOT },
260 /* check operator precedence */
261 { "host = 'name' OR "
262 "service = 'name' AND "
263 "attribute = 'name' OR "
264 "attribute.foo = 'bar'", -1, MATCHER_OR },
265 { "host = 'name' AND "
266 "service = 'name' AND "
267 "attribute = 'name' OR "
268 "attribute.foo = 'bar'", -1, MATCHER_OR },
269 { "host = 'name' AND "
270 "service = 'name' OR "
271 "attribute = 'name' AND "
272 "attribute.foo = 'bar'", -1, MATCHER_OR },
273 { "(host = 'name' OR "
274 "service = 'name') AND "
275 "(attribute = 'name' OR "
276 "attribute.foo = 'bar')", -1, MATCHER_AND },
277 { "NOT host = 'name' OR "
278 "service = 'name'", -1, MATCHER_OR },
279 { "NOT host = 'name' OR "
280 "NOT service = 'name'", -1, MATCHER_OR },
281 { "NOT (host = 'name' OR "
282 "NOT service = 'name')", -1, MATCHER_NOT },
284 /* syntax errors */
285 { "LIST", -1, -1 },
286 { "foo &^ bar", -1, -1 },
287 };
289 size_t i;
291 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
292 sdb_store_matcher_t *m;
293 m = sdb_fe_parse_matcher(golden_data[i].expr, golden_data[i].len);
295 if (golden_data[i].expected < 0) {
296 fail_unless(m == NULL,
297 "sdb_fe_parse_matcher(%s) = %p; expected: NULL",
298 golden_data[i].expr, m);
299 continue;
300 }
302 fail_unless(m != NULL, "sdb_fe_parse_matcher(%s) = NULL; "
303 "expected: <matcher>", golden_data[i].expr);
304 fail_unless(M(m)->type == golden_data[i].expected,
305 "sdb_fe_parse_matcher(%s) returned matcher of type %d; "
306 "expected: %d", golden_data[i].expr, M(m)->type,
307 golden_data[i].expected);
309 sdb_object_deref(SDB_OBJ(m));
310 }
311 }
312 END_TEST
314 Suite *
315 fe_parser_suite(void)
316 {
317 Suite *s = suite_create("frontend::parser");
318 TCase *tc;
320 tc = tcase_create("core");
321 tcase_add_test(tc, test_parse);
322 tcase_add_test(tc, test_parse_matcher);
323 suite_add_tcase(s, tc);
325 return s;
326 } /* util_parser_suite */
328 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */