Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : #include "monetdb_config.h"
14 :
15 : #include "murltest.h"
16 : #include "msettings.h"
17 : #include "stream.h"
18 :
19 : #include <assert.h>
20 : #include <ctype.h>
21 : #include <errno.h>
22 : #include <stdio.h>
23 : #include <stdlib.h>
24 : #include <string.h>
25 :
26 : // love it when a task is so straightforward I can use global variables!
27 : static int start_line = -1;
28 : static int nstarted = 0;
29 : static msettings *mp = NULL;
30 :
31 : static bool
32 3 : handle_parse_command(const char *location, char *url)
33 : {
34 3 : char *errmsg = NULL;
35 3 : bool ok = msettings_parse_url(mp, url, &errmsg);
36 3 : if (!ok) {
37 0 : assert(errmsg);
38 0 : fprintf(stderr, "%s: %s\n", location, errmsg);
39 0 : free(errmsg);
40 0 : return false;
41 : }
42 : return true;
43 : }
44 :
45 : static bool
46 131 : handle_accept_command(const char *location, char *url)
47 : {
48 131 : char *errmsg = NULL;
49 131 : bool ok = msettings_parse_url(mp, url, &errmsg);
50 131 : if (!ok) {
51 0 : assert(errmsg);
52 0 : fprintf(stderr, "%s: %s\n", location, errmsg);
53 0 : free(errmsg);
54 0 : return false;
55 : }
56 :
57 131 : char *msg = NULL;
58 131 : if (!msettings_validate(mp, &msg)) {
59 0 : fprintf(stderr, "%s: URL invalid: %s\n", location, msg);
60 0 : free(msg);
61 0 : return false;
62 : }
63 : return true;
64 : }
65 :
66 : static bool
67 60 : handle_reject_command(const char *location, char *url)
68 : {
69 60 : bool ok = msettings_parse_url(mp, url, NULL);
70 60 : if (!ok)
71 : return true;
72 :
73 28 : char *msg = NULL;
74 28 : if (!msettings_validate(mp, &msg)) {
75 28 : free(msg);
76 28 : return true;
77 : }
78 :
79 0 : fprintf(stderr, "%s: expected URL to be rejected.\n", location);
80 0 : return false;
81 : }
82 :
83 : static bool
84 130 : handle_set_command(const char *location, const char *key, const char *value)
85 : {
86 130 : msettings_error msg = msetting_set_named(mp, true, key, value);
87 130 : if (msg) {
88 0 : fprintf(stderr, "%s: cannot set '%s': %s\n", location, key, msg);
89 0 : return false;
90 : }
91 : return true;
92 : }
93 :
94 : static bool
95 177 : ensure_valid(const char *location) {
96 177 : char *msg = NULL;
97 177 : if (msettings_validate(mp, &msg))
98 : return true;
99 0 : fprintf(stderr, "%s: invalid parameter state: %s\n", location, msg);
100 0 : free(msg);
101 0 : return false;
102 : }
103 :
104 : static bool
105 81 : expect_bool(const char *location, const mparm parm, bool (*extract)(const msettings*), const char *value)
106 : {
107 81 : int x = msetting_parse_bool(value);
108 81 : if (x < 0) {
109 0 : fprintf(stderr, "%s: syntax error: invalid bool '%s'\n", location, value);
110 : }
111 81 : bool b = x > 0;
112 :
113 81 : bool actual;
114 81 : if (extract) {
115 44 : if (!ensure_valid(location))
116 : return false;
117 44 : actual = extract(mp);
118 : } else {
119 37 : actual = msetting_bool(mp, parm);
120 : }
121 :
122 81 : if (actual == b)
123 : return true;
124 :
125 0 : char *b_ = b ? "true" : "false";
126 0 : char *actual_ = actual ? "true" : "false";
127 0 : fprintf(stderr, "%s: expected %s, found %s\n", location, b_, actual_);
128 0 : return false;
129 :
130 : }
131 :
132 : static bool
133 51 : expect_long(const char *location, const mparm parm, long (*extract)(const msettings*), const char *value)
134 : {
135 51 : if (strlen(value) == 0) {
136 0 : fprintf(stderr, "%s: syntax error: integer value cannot be empty string\n", location);
137 0 : return false;
138 : }
139 51 : char *end = NULL;
140 51 : long n = strtol(value, &end, 10);
141 51 : if (*end != '\0') {
142 0 : fprintf(stderr, "%s: syntax error: invalid integer '%s'\n", location, value);
143 0 : return false;
144 : }
145 :
146 51 : long actual;
147 51 : if (extract) {
148 22 : if (!ensure_valid(location))
149 : return false;
150 22 : actual = extract(mp);
151 : } else {
152 29 : actual = msetting_long(mp, parm);
153 : }
154 :
155 51 : if (actual == n)
156 : return true;
157 :
158 0 : fprintf(stderr, "%s: expected %ld, found %ld\n", location, n, actual);
159 0 : return false;
160 : }
161 :
162 : static bool
163 268 : expect_string(const char *location, const mparm parm, const char *(*extract)(const msettings*), const char *value)
164 : {
165 268 : const char *actual;
166 268 : if (extract) {
167 111 : if (!ensure_valid(location))
168 : return false;
169 111 : actual = extract(mp);
170 : } else {
171 157 : actual = msetting_string(mp, parm);
172 : }
173 :
174 268 : if (strcmp(actual, value) == 0)
175 : return true;
176 :
177 0 : fprintf(stderr, "%s: expected '%s', found '%s'\n", location, value, actual);
178 0 : return false;
179 : }
180 :
181 : static const char *
182 12 : stringify_tls_verify(const msettings *mp)
183 : {
184 12 : enum msetting_tls_verify verify = msettings_connect_tls_verify(mp);
185 12 : switch (verify) {
186 : case verify_none:
187 : return "";
188 : case verify_system:
189 : return "system";
190 : case verify_cert:
191 : return "cert";
192 : case verify_hash:
193 : return "hash";
194 : default:
195 0 : assert(0 && "unknown msetting_tls_verify value");
196 : return NULL;
197 : }
198 : assert(0 && "unreachable");
199 : }
200 :
201 : static bool
202 457 : handle_expect_command(const char *location, char *key, char *value)
203 : {
204 457 : if (strcmp("valid", key) == 0) {
205 57 : int x = msetting_parse_bool(value);
206 57 : if (x < 0) {
207 0 : fprintf(stderr, "%s: invalid boolean value: %s\n", location, value);
208 0 : return false;
209 : }
210 57 : bool expected_valid = x > 0;
211 :
212 57 : char * msg = NULL;
213 57 : bool actually_valid = msettings_validate(mp, &msg);
214 57 : free(msg);
215 57 : if (actually_valid != expected_valid) {
216 0 : fprintf(stderr, "%s: expected '%s', found '%s'\n",
217 : location,
218 : expected_valid ? "true" : "false",
219 : actually_valid ? "true" : "false"
220 : );
221 0 : return false;
222 : }
223 : return true;
224 : }
225 :
226 400 : if (strcmp("connect_scan", key) == 0)
227 44 : return expect_bool(location, MP_UNKNOWN, msettings_connect_scan, value);
228 356 : if (strcmp("connect_unix", key) == 0)
229 48 : return expect_string(location, MP_UNKNOWN, msettings_connect_unix, value);
230 308 : if (strcmp("connect_tcp", key) == 0)
231 44 : return expect_string(location, MP_UNKNOWN, msettings_connect_tcp, value);
232 264 : if (strcmp("connect_port", key) == 0)
233 11 : return expect_long(location, MP_UNKNOWN, msettings_connect_port, value);
234 253 : if (strcmp("connect_tls_verify", key) == 0)
235 12 : return expect_string(location, MP_UNKNOWN, stringify_tls_verify, value);
236 241 : if (strcmp("connect_certhash_digits", key) == 0)
237 1 : return expect_string(location, MP_UNKNOWN, msettings_connect_certhash_digits, value);
238 240 : if (strcmp("connect_binary", key) == 0)
239 11 : return expect_long(location, MP_UNKNOWN, msettings_connect_binary, value);
240 229 : if (strcmp("connect_clientkey", key) == 0)
241 3 : return expect_string(location, MP_UNKNOWN, msettings_connect_clientkey, value);
242 226 : if (strcmp("connect_clientcert", key) == 0)
243 3 : return expect_string(location, MP_UNKNOWN, msettings_connect_clientcert, value);
244 :
245 223 : const mparm parm = mparm_parse(key);
246 223 : if (parm == MP_UNKNOWN) {
247 0 : fprintf(stderr, "%s: unknown parameter '%s'\n:", location, key);
248 0 : return false;
249 : }
250 223 : if (parm == MP_IGNORE) {
251 0 : if (strncmp(key, "connect_", 8) == 0)
252 0 : fprintf(stderr, "%s: unknown virtual parameter '%s'\n", location, key);
253 : else
254 0 : fprintf(stderr, "%s: EXPECTing ignored parameters is not supported yet\n", location);
255 0 : return false;
256 : }
257 :
258 223 : switch (mparm_classify(parm)) {
259 : case MPCLASS_BOOL:
260 37 : return expect_bool(location, parm, NULL, value);
261 : case MPCLASS_LONG:
262 29 : return expect_long(location, parm, NULL, value);
263 : case MPCLASS_STRING:
264 157 : return expect_string(location, parm, NULL, value);
265 : default:
266 : fprintf(stderr, "%s: internal error: unclassified parameter %d\n", location, (int)parm);
267 : return false;
268 : }
269 : }
270 :
271 :
272 :
273 :
274 : static bool
275 1456 : handle_line(int lineno, const char *location, char *line, int verbose)
276 : {
277 : // first trim trailing whitespace
278 1456 : size_t n = strlen(line);
279 2912 : while (n > 0 && isspace(line[n - 1]))
280 : n--;
281 1456 : line[n] = '\0';
282 :
283 1456 : if (mp == NULL) {
284 : // not in a code block
285 523 : if (strcmp(line, "```test") == 0) {
286 : // block starts here
287 138 : nstarted++;
288 138 : start_line = lineno;
289 138 : mp = msettings_create();
290 138 : if (mp == NULL) {
291 0 : fprintf(stderr, "%s: malloc failed\n", location);
292 0 : return false;
293 : }
294 138 : if (verbose >= 2)
295 0 : fprintf(stderr, "ยท %s\n", location);
296 138 : return true;
297 : } else {
298 : // ignore
299 : return true;
300 : }
301 : }
302 :
303 : // we're in a code block, does it end here?
304 933 : if (strlen(line) > 0 && line[0] == '`') {
305 135 : if (strcmp(line, "```") == 0) {
306 : // lone backticks, block ends here
307 135 : msettings_destroy(mp);
308 135 : mp = NULL;
309 135 : if (verbose >= 3)
310 0 : fprintf(stderr, "\n");
311 135 : return true;
312 : } else {
313 0 : fprintf(stderr, "%s: unexpected backtick\n", location);
314 0 : return false;
315 : }
316 : }
317 :
318 : // this is line from a code block
319 798 : if (verbose >= 3)
320 0 : fprintf(stderr, "%s\n", line);
321 798 : const char *whitespace = " \t";
322 798 : char *command = strtok(line, whitespace);
323 798 : if (command == NULL) {
324 : // empty line
325 : return true;
326 791 : } else if (strcasecmp(command, "ONLY") == 0) {
327 3 : char *impl = strtok(NULL, " \n");
328 3 : if (impl) {
329 3 : if (strcmp(impl, "libmapi") != 0) {
330 : // ONLY command is not about us. End the block here
331 3 : msettings_destroy(mp);
332 3 : mp = NULL;
333 : }
334 3 : return true;
335 : }
336 : // if !impl we print an error below
337 788 : } else if (strcasecmp(command, "NOT") == 0) {
338 7 : char *impl = strtok(NULL, " \n");
339 7 : if (impl) {
340 7 : if (strcmp(impl, "libmapi") == 0) {
341 : // NOT command is about us. End the block here.
342 0 : msettings_destroy(mp);
343 0 : mp = NULL;
344 : }
345 7 : return true;
346 : }
347 : // if !impl we print an error below
348 781 : } else if (strcasecmp(command, "PARSE") == 0) {
349 3 : char *url = strtok(NULL, "\n");
350 3 : if (url)
351 3 : return handle_parse_command(location, url);
352 778 : } else if (strcasecmp(command, "ACCEPT") == 0) {
353 131 : char *url = strtok(NULL, "\n");
354 131 : if (url)
355 131 : return handle_accept_command(location, url);
356 647 : } else if (strcasecmp(command, "REJECT") == 0) {
357 60 : char *url = strtok(NULL, "\n");
358 60 : if (url)
359 60 : return handle_reject_command(location, url);
360 587 : } else if (strcasecmp(command, "SET") == 0) {
361 130 : char *key = strtok(NULL, "=");
362 130 : char *value = strtok(NULL, "\n");
363 130 : if (key)
364 141 : return handle_set_command(location, key, value ? value : "");
365 457 : } else if (strcasecmp(command, "EXPECT") == 0) {
366 457 : char *key = strtok(NULL, "=");
367 457 : char *value = strtok(NULL, "\n");
368 457 : if (key)
369 528 : return handle_expect_command(location, key, value ? value : "");
370 : } else {
371 0 : fprintf(stderr, "%s: unknown command: %s\n", location, command);
372 0 : return false;
373 : }
374 :
375 : // if we get here, url or key was not present
376 0 : fprintf(stderr, "%s: syntax error\n", location);
377 0 : return false;
378 : }
379 :
380 : static bool
381 1 : run_tests_inner(stream *s, int verbose)
382 : {
383 1 : int orig_nstarted = nstarted;
384 1 : const char *filename = mnstr_name(s);
385 1 : char *location = malloc(strlen(filename) + 100);
386 1 : strcpy(location, filename);
387 1 : char *location_lineno = &location[strlen(filename)];
388 1 : *location_lineno++ = ':';
389 1 : *location_lineno = '\0';
390 1 : char line_buffer[1024];
391 :
392 1 : errno = 0;
393 :
394 1 : int lineno = 0;
395 :
396 1457 : while (true) {
397 1457 : lineno++;
398 1457 : sprintf(location_lineno, "%d", lineno);
399 1457 : ssize_t nread = mnstr_readline(s, line_buffer, sizeof(line_buffer));
400 1457 : if (nread == 0)
401 : break;
402 1456 : if (nread < 0) {
403 0 : if (errno) {
404 0 : fprintf(stderr, "%s: %s\n", location, strerror(errno));
405 0 : free(location);
406 0 : return false;
407 : } else {
408 : break;
409 : }
410 1456 : } else if (nread >= (ssize_t)sizeof(line_buffer) - 2) {
411 0 : fprintf(stderr, "%s: line too long\n", location);
412 :
413 : }
414 1456 : if (!handle_line(lineno, location, line_buffer, verbose)) {
415 0 : free(location);
416 0 : return false;
417 : }
418 : }
419 :
420 1 : if (mp) {
421 0 : fprintf(stderr, "%s:%d: unterminated code block starts here\n", filename, start_line);
422 0 : free(location);
423 0 : return false;
424 : }
425 :
426 1 : if (verbose >= 1) {
427 0 : fprintf(stderr, "ran %d succesful tests from %s\n", nstarted - orig_nstarted, filename);
428 : }
429 :
430 1 : free(location);
431 1 : return true;
432 : }
433 :
434 : bool
435 1 : run_tests(stream *s, int verbose)
436 : {
437 1 : assert(mp == NULL);
438 1 : bool ok = run_tests_inner(s, verbose);
439 1 : if (mp) {
440 0 : msettings_destroy(mp);
441 0 : mp = NULL;
442 : }
443 1 : return ok;
444 : }
|