LCOV - code coverage report
Current view: top level - clients/examples/C - testsfile.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 172 240 71.7 %
Date: 2024-11-15 19:37:45 Functions: 13 13 100.0 %

          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: %s\n", location, 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 successful 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             : }

Generated by: LCOV version 1.14