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 WHERE "
62 "host = 'host'", -1, 1, CONNECTION_LOOKUP },
63 { "LOOKUP hosts WHERE NOT "
64 "host = 'host'", -1, 1, CONNECTION_LOOKUP },
65 { "LOOKUP hosts WHERE "
66 "host =~ 'p' AND "
67 "service =~ 'p'", -1, 1, CONNECTION_LOOKUP },
68 { "LOOKUP hosts WHERE NOT "
69 "host =~ 'p' AND "
70 "service =~ 'p'", -1, 1, CONNECTION_LOOKUP },
71 { "LOOKUP hosts WHERE "
72 "host =~ 'p' AND "
73 "service =~ 'p' OR "
74 "service =~ 'r'", -1, 1, CONNECTION_LOOKUP },
75 { "LOOKUP hosts WHERE NOT "
76 "host =~ 'p' AND "
77 "service =~ 'p' OR "
78 "service =~ 'r'", -1, 1, CONNECTION_LOOKUP },
80 /* numeric constants */
81 { "LOOKUP hosts WHERE "
82 "attribute.foo = "
83 "1234", -1, 1, CONNECTION_LOOKUP },
84 { "LOOKUP hosts WHERE "
85 "attribute.foo != "
86 "+234", -1, 1, CONNECTION_LOOKUP },
87 { "LOOKUP hosts WHERE "
88 "attribute.foo < "
89 "-234", -1, 1, CONNECTION_LOOKUP },
90 { "LOOKUP hosts WHERE "
91 "attribute.foo > "
92 "12.4", -1, 1, CONNECTION_LOOKUP },
93 { "LOOKUP hosts WHERE "
94 "attribute.foo <= "
95 "12.", -1, 1, CONNECTION_LOOKUP },
96 { "LOOKUP hosts WHERE "
97 "attribute.foo >= "
98 ".4", -1, 1, CONNECTION_LOOKUP },
99 { "LOOKUP hosts WHERE "
100 "attribute.foo = "
101 "+12e3", -1, 1, CONNECTION_LOOKUP },
102 { "LOOKUP hosts WHERE "
103 "attribute.foo = "
104 "+12e-3", -1, 1, CONNECTION_LOOKUP },
105 { "LOOKUP hosts WHERE "
106 "attribute.foo = "
107 "-12e+3", -1, 1, CONNECTION_LOOKUP },
109 /* NULL */
110 { "LOOKUP hosts WHERE "
111 "attribute.foo "
112 "IS NULL", -1, 1, CONNECTION_LOOKUP },
113 { "LOOKUP hosts WHERE "
114 "attribute.foo "
115 "IS NOT NULL", -1, 1, CONNECTION_LOOKUP },
116 { "LOOKUP hosts WHERE "
117 "NOT attribute.foo "
118 "IS NULL", -1, 1, CONNECTION_LOOKUP },
119 { "LOOKUP hosts WHERE "
120 "host IS NULL", -1, -1, 0 },
121 { "LOOKUP hosts WHERE "
122 "service IS NULL", -1, -1, 0 },
124 /* invalid numeric constants */
125 { "LOOKUP hosts WHERE "
126 "attribute.foo = "
127 "+-12e+3", -1, -1, 0 },
128 { "LOOKUP hosts WHERE "
129 "attribute.foo = "
130 "-12e-+3", -1, -1, 0 },
131 { "LOOKUP hosts WHERE "
132 "attribute.foo = "
133 "e+3", -1, -1, 0 },
134 { "LOOKUP hosts WHERE "
135 "attribute.foo = "
136 "3e", -1, -1, 0 },
137 /* following SQL standard, we don't support hex numbers */
138 { "LOOKUP hosts WHERE "
139 "attribute.foo = "
140 "0x12", -1, -1, 0 },
142 /* comments */
143 { "/* some comment */", -1, 0, 0 },
144 { "-- another comment", -1, 0, 0 },
146 /* syntax errors */
147 { "INVALID", -1, -1, 0 },
148 { "FETCH host", -1, -1, 0 },
149 { "LIST; INVALID", 8, -1, 0 },
150 { "/* some incomplete", -1, -1, 0 },
152 { "LOOKUP hosts", -1, -1, 0 },
153 { "LOOKUP foo WHERE "
154 "host = 'host'", -1, -1, 0 },
155 };
157 size_t i;
158 sdb_llist_t *check;
160 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
161 sdb_object_t *obj;
162 _Bool ok;
164 check = sdb_fe_parse(golden_data[i].query, golden_data[i].len);
165 if (golden_data[i].expected < 0)
166 ok = check == 0;
167 else
168 ok = sdb_llist_len(check) == (size_t)golden_data[i].expected;
170 fail_unless(ok, "sdb_fe_parse(%s) = %p (len: %zu); expected: %d",
171 golden_data[i].query, check, sdb_llist_len(check),
172 golden_data[i].expected);
174 if (! check)
175 continue;
177 if ((! golden_data[i].expected_cmd)
178 || (golden_data[i].expected <= 0)) {
179 sdb_llist_destroy(check);
180 continue;
181 }
183 obj = sdb_llist_get(check, 0);
184 fail_unless(SDB_CONN_NODE(obj)->cmd == golden_data[i].expected_cmd,
185 "sdb_fe_parse(%s)->cmd = %i; expected: %d",
186 golden_data[i].query, SDB_CONN_NODE(obj)->cmd,
187 golden_data[i].expected_cmd);
188 sdb_object_deref(obj);
189 sdb_llist_destroy(check);
190 }
191 }
192 END_TEST
194 START_TEST(test_parse_matcher)
195 {
196 struct {
197 const char *expr;
198 int len;
199 int expected;
200 } golden_data[] = {
201 /* empty expressions */
202 { NULL, -1, -1 },
203 { "", -1, -1 },
205 /* valid expressions */
206 { "host = 'localhost'", -1, MATCHER_NAME },
207 { "host != 'localhost'", -1, MATCHER_NOT },
208 { "host =~ 'host'", -1, MATCHER_NAME },
209 { "host !~ 'host'", -1, MATCHER_NOT },
210 { "host = 'localhost' -- foo", -1, MATCHER_NAME },
211 { "host = 'host' <garbage>", 13, MATCHER_NAME },
212 /* match hosts by service */
213 { "service = 'name'", -1, MATCHER_NAME },
214 { "service != 'name'", -1, MATCHER_NOT },
215 { "service =~ 'pattern'", -1, MATCHER_NAME },
216 { "service !~ 'pattern'", -1, MATCHER_NOT },
217 /* match hosts by attribute */
218 { "attribute = 'name'", -1, MATCHER_NAME },
219 { "attribute != 'name'", -1, MATCHER_NOT },
220 { "attribute =~ 'pattern'", -1, MATCHER_NAME },
221 { "attribute !~ 'pattern'", -1, MATCHER_NOT },
222 /* composite expressions */
223 { "host =~ 'pattern' AND "
224 "service =~ 'pattern'", -1, MATCHER_AND },
225 { "host =~ 'pattern' OR "
226 "service =~ 'pattern'", -1, MATCHER_OR },
227 { "NOT host = 'host'", -1, MATCHER_NOT },
228 /* numeric expressions */
229 { "attribute.foo < 123", -1, MATCHER_LT },
230 { "attribute.foo <= 123", -1, MATCHER_LE },
231 { "attribute.foo = 123", -1, MATCHER_EQ },
232 { "attribute.foo >= 123", -1, MATCHER_GE },
233 { "attribute.foo > 123", -1, MATCHER_GT },
234 /* NULL; while this is an implementation detail,
235 * IS NULL currently maps to an equality matcher */
236 { "attribute.foo IS NULL", -1, MATCHER_ISNULL },
237 { "attribute.foo IS NOT NULL", -1, MATCHER_NOT },
239 /* check operator precedence */
240 { "host = 'name' OR "
241 "service = 'name' AND "
242 "attribute = 'name' OR "
243 "attribute.foo = 'bar'", -1, MATCHER_OR },
244 { "host = 'name' AND "
245 "service = 'name' AND "
246 "attribute = 'name' OR "
247 "attribute.foo = 'bar'", -1, MATCHER_OR },
248 { "host = 'name' AND "
249 "service = 'name' OR "
250 "attribute = 'name' AND "
251 "attribute.foo = 'bar'", -1, MATCHER_OR },
252 { "(host = 'name' OR "
253 "service = 'name') AND "
254 "(attribute = 'name' OR "
255 "attribute.foo = 'bar')", -1, MATCHER_AND },
256 { "NOT host = 'name' OR "
257 "service = 'name'", -1, MATCHER_OR },
258 { "NOT host = 'name' OR "
259 "NOT service = 'name'", -1, MATCHER_OR },
260 { "NOT (host = 'name' OR "
261 "NOT service = 'name')", -1, MATCHER_NOT },
263 /* syntax errors */
264 { "LIST", -1, -1 },
265 { "foo &^ bar", -1, -1 },
266 };
268 size_t i;
270 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
271 sdb_store_matcher_t *m;
272 m = sdb_fe_parse_matcher(golden_data[i].expr, golden_data[i].len);
274 if (golden_data[i].expected < 0) {
275 fail_unless(m == NULL,
276 "sdb_fe_parse_matcher(%s) = %p; expected: NULL",
277 golden_data[i].expr, m);
278 continue;
279 }
281 fail_unless(m != NULL, "sdb_fe_parse_matcher(%s) = NULL; "
282 "expected: <matcher>", golden_data[i].expr);
283 fail_unless(M(m)->type == golden_data[i].expected,
284 "sdb_fe_parse_matcher(%s) returned matcher of type %d; "
285 "expected: %d", golden_data[i].expr, M(m)->type,
286 golden_data[i].expected);
288 sdb_object_deref(SDB_OBJ(m));
289 }
290 }
291 END_TEST
293 Suite *
294 fe_parser_suite(void)
295 {
296 Suite *s = suite_create("frontend::parser");
297 TCase *tc;
299 tc = tcase_create("core");
300 tcase_add_test(tc, test_parse);
301 tcase_add_test(tc, test_parse_matcher);
302 suite_add_tcase(s, tc);
304 return s;
305 } /* util_parser_suite */
307 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */