LCOV - differential code coverage report
Current view: top level - src/test/modules/test_json_parser - test_json_parser_incremental.c (source / functions) Coverage Total Hit UBC CBC
Current: c70b6db34ffeab48beef1fb4ce61bcad3772b8dd vs 06473f5a344df8c9594ead90a609b86f6724cff8 Lines: 95.0 % 160 152 8 152
Current Date: 2025-09-06 07:49:51 +0900 Functions: 100.0 % 12 12 12
Baseline: lcov-20250906-005545-baseline Branches: 82.1 % 56 46 10 46
Baseline Date: 2025-09-05 08:21:35 +0100 Line coverage date bins:
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
(30,360] days: 96.8 % 31 30 1 30
(360..) days: 94.6 % 129 122 7 122
Function coverage date bins:
(360..) days: 100.0 % 12 12 12
Branch coverage date bins:
(30,360] days: 87.5 % 8 7 1 7
(360..) days: 81.2 % 48 39 9 39

 Age         Owner                    Branch data    TLA  Line data    Source code
                                  1                 :                : /*-------------------------------------------------------------------------
                                  2                 :                :  *
                                  3                 :                :  * test_json_parser_incremental.c
                                  4                 :                :  *    Test program for incremental JSON parser
                                  5                 :                :  *
                                  6                 :                :  * Copyright (c) 2024-2025, PostgreSQL Global Development Group
                                  7                 :                :  *
                                  8                 :                :  * IDENTIFICATION
                                  9                 :                :  *    src/test/modules/test_json_parser/test_json_parser_incremental.c
                                 10                 :                :  *
                                 11                 :                :  * This program tests incremental parsing of json. The input is fed into
                                 12                 :                :  * the parser in very small chunks. In practice you would normally use
                                 13                 :                :  * much larger chunks, but doing this makes it more likely that the
                                 14                 :                :  * full range of increment handling, especially in the lexer, is exercised.
                                 15                 :                :  * If the "-c SIZE" option is provided, that chunk size is used instead
                                 16                 :                :  * of the default of 60.
                                 17                 :                :  *
                                 18                 :                :  * If the -s flag is given, the program does semantic processing. This should
                                 19                 :                :  * just mirror back the json, albeit with white space changes.
                                 20                 :                :  *
                                 21                 :                :  * If the -o flag is given, the JSONLEX_CTX_OWNS_TOKENS flag is set. (This can
                                 22                 :                :  * be used in combination with a leak sanitizer; without the option, the parser
                                 23                 :                :  * may leak memory with invalid JSON.)
                                 24                 :                :  *
                                 25                 :                :  * The argument specifies the file containing the JSON input.
                                 26                 :                :  *
                                 27                 :                :  *-------------------------------------------------------------------------
                                 28                 :                :  */
                                 29                 :                : 
                                 30                 :                : #include "postgres_fe.h"
                                 31                 :                : 
                                 32                 :                : #include <stdio.h>
                                 33                 :                : #include <sys/types.h>
                                 34                 :                : #include <sys/stat.h>
                                 35                 :                : #include <unistd.h>
                                 36                 :                : 
                                 37                 :                : #include "common/jsonapi.h"
                                 38                 :                : #include "common/logging.h"
                                 39                 :                : #include "lib/stringinfo.h"
                                 40                 :                : #include "mb/pg_wchar.h"
                                 41                 :                : #include "pg_getopt.h"
                                 42                 :                : 
                                 43                 :                : #define BUFSIZE 6000
                                 44                 :                : #define DEFAULT_CHUNK_SIZE 60
                                 45                 :                : 
                                 46                 :                : typedef struct DoState
                                 47                 :                : {
                                 48                 :                :     JsonLexContext *lex;
                                 49                 :                :     bool        elem_is_first;
                                 50                 :                :     StringInfo  buf;
                                 51                 :                : } DoState;
                                 52                 :                : 
                                 53                 :                : static void usage(const char *progname);
                                 54                 :                : static void escape_json(StringInfo buf, const char *str);
                                 55                 :                : 
                                 56                 :                : /* semantic action functions for parser */
                                 57                 :                : static JsonParseErrorType do_object_start(void *state);
                                 58                 :                : static JsonParseErrorType do_object_end(void *state);
                                 59                 :                : static JsonParseErrorType do_object_field_start(void *state, char *fname, bool isnull);
                                 60                 :                : static JsonParseErrorType do_object_field_end(void *state, char *fname, bool isnull);
                                 61                 :                : static JsonParseErrorType do_array_start(void *state);
                                 62                 :                : static JsonParseErrorType do_array_end(void *state);
                                 63                 :                : static JsonParseErrorType do_array_element_start(void *state, bool isnull);
                                 64                 :                : static JsonParseErrorType do_array_element_end(void *state, bool isnull);
                                 65                 :                : static JsonParseErrorType do_scalar(void *state, char *token, JsonTokenType tokentype);
                                 66                 :                : 
                                 67                 :                : static JsonSemAction sem = {
                                 68                 :                :     .object_start = do_object_start,
                                 69                 :                :     .object_end = do_object_end,
                                 70                 :                :     .object_field_start = do_object_field_start,
                                 71                 :                :     .object_field_end = do_object_field_end,
                                 72                 :                :     .array_start = do_array_start,
                                 73                 :                :     .array_end = do_array_end,
                                 74                 :                :     .array_element_start = do_array_element_start,
                                 75                 :                :     .array_element_end = do_array_element_end,
                                 76                 :                :     .scalar = do_scalar
                                 77                 :                : };
                                 78                 :                : 
                                 79                 :                : static bool lex_owns_tokens = false;
                                 80                 :                : 
                                 81                 :                : int
  545 andrew@dunslane.net        82                 :CBC        1964 : main(int argc, char **argv)
                                 83                 :                : {
                                 84                 :                :     char        buff[BUFSIZE];
                                 85                 :                :     FILE       *json_file;
                                 86                 :                :     JsonParseErrorType result;
                                 87                 :                :     JsonLexContext *lex;
                                 88                 :                :     StringInfoData json;
                                 89                 :                :     int         n_read;
  515                            90                 :           1964 :     size_t      chunk_size = DEFAULT_CHUNK_SIZE;
                                 91                 :                :     struct stat statbuf;
                                 92                 :                :     off_t       bytes_left;
  396 heikki.linnakangas@i       93                 :           1964 :     const JsonSemAction *testsem = &nullSemAction;
                                 94                 :                :     char       *testfile;
                                 95                 :                :     int         c;
  545 andrew@dunslane.net        96                 :           1964 :     bool        need_strings = false;
  283                            97                 :           1964 :     int         ret = 0;
                                 98                 :                : 
  500                            99                 :           1964 :     pg_logging_init(argv[0]);
                                100                 :                : 
  136 dgustafsson@postgres      101                 :           1964 :     lex = calloc(1, sizeof(JsonLexContext));
                                102         [ -  + ]:           1964 :     if (!lex)
  136 dgustafsson@postgres      103                 :UBC           0 :         pg_fatal("out of memory");
                                104                 :                : 
  283 andrew@dunslane.net       105         [ +  + ]:CBC        6874 :     while ((c = getopt(argc, argv, "c:os")) != -1)
                                106                 :                :     {
  545                           107   [ -  +  +  + ]:           2946 :         switch (c)
                                108                 :                :         {
                                109                 :           1960 :             case 'c':           /* chunksize */
  447 noah@leadboat.com         110                 :           1960 :                 chunk_size = strtou64(optarg, NULL, 10);
  515 andrew@dunslane.net       111         [ -  + ]:           1960 :                 if (chunk_size > BUFSIZE)
  501 andrew@dunslane.net       112                 :UBC           0 :                     pg_fatal("chunk size cannot exceed %d", BUFSIZE);
  545 andrew@dunslane.net       113                 :CBC        1960 :                 break;
  283                           114                 :            982 :             case 'o':           /* switch token ownership */
                                115                 :            982 :                 lex_owns_tokens = true;
                                116                 :            982 :                 break;
  545                           117                 :              4 :             case 's':           /* do semantic processing */
                                118                 :              4 :                 testsem = &sem;
                                119                 :              4 :                 sem.semstate = palloc(sizeof(struct DoState));
  136 dgustafsson@postgres      120                 :              4 :                 ((struct DoState *) sem.semstate)->lex = lex;
  545 andrew@dunslane.net       121                 :              4 :                 ((struct DoState *) sem.semstate)->buf = makeStringInfo();
                                122                 :              4 :                 need_strings = true;
                                123                 :              4 :                 break;
                                124                 :                :         }
                                125                 :                :     }
                                126                 :                : 
                                127         [ +  + ]:           1964 :     if (optind < argc)
                                128                 :                :     {
  283                           129                 :           1960 :         testfile = argv[optind];
  545                           130                 :           1960 :         optind++;
                                131                 :                :     }
                                132                 :                :     else
                                133                 :                :     {
                                134                 :              4 :         usage(argv[0]);
                                135                 :              4 :         exit(1);
                                136                 :                :     }
                                137                 :                : 
  136 dgustafsson@postgres      138                 :           1960 :     makeJsonLexContextIncremental(lex, PG_UTF8, need_strings);
                                139                 :           1960 :     setJsonLexContextOwnsTokens(lex, lex_owns_tokens);
  545 andrew@dunslane.net       140                 :           1960 :     initStringInfo(&json);
                                141                 :                : 
  424                           142         [ -  + ]:           1960 :     if ((json_file = fopen(testfile, PG_BINARY_R)) == NULL)
  501 andrew@dunslane.net       143                 :UBC           0 :         pg_fatal("error opening input: %m");
                                144                 :                : 
  501 andrew@dunslane.net       145         [ -  + ]:CBC        1960 :     if (fstat(fileno(json_file), &statbuf) != 0)
  501 andrew@dunslane.net       146                 :UBC           0 :         pg_fatal("error statting input: %m");
                                147                 :                : 
  545 andrew@dunslane.net       148                 :CBC        1960 :     bytes_left = statbuf.st_size;
                                149                 :                : 
                                150                 :                :     for (;;)
                                151                 :                :     {
                                152                 :                :         /* We will break when there's nothing left to read */
                                153                 :                : 
  501                           154         [ +  + ]:         372708 :         if (bytes_left < chunk_size)
                                155                 :           1312 :             chunk_size = bytes_left;
                                156                 :                : 
  545                           157                 :         372708 :         n_read = fread(buff, 1, chunk_size, json_file);
  501                           158         [ -  + ]:         372708 :         if (n_read < chunk_size)
  501 andrew@dunslane.net       159                 :UBC           0 :             pg_fatal("error reading input file: %d", ferror(json_file));
                                160                 :                : 
  545 andrew@dunslane.net       161                 :CBC      372708 :         appendBinaryStringInfo(&json, buff, n_read);
                                162                 :                : 
                                163                 :                :         /*
                                164                 :                :          * Append some trailing junk to the buffer passed to the parser. This
                                165                 :                :          * helps us ensure that the parser does the right thing even if the
                                166                 :                :          * chunk isn't terminated with a '\0'.
                                167                 :                :          */
                                168                 :         372708 :         appendStringInfoString(&json, "1+23 trailing junk");
                                169                 :         372708 :         bytes_left -= n_read;
                                170         [ +  + ]:         372708 :         if (bytes_left > 0)
                                171                 :                :         {
  136 dgustafsson@postgres      172                 :         370920 :             result = pg_parse_json_incremental(lex, testsem,
  545 andrew@dunslane.net       173                 :         370920 :                                                json.data, n_read,
                                174                 :                :                                                false);
                                175         [ +  + ]:         370920 :             if (result != JSON_INCOMPLETE)
                                176                 :                :             {
  136 dgustafsson@postgres      177                 :            172 :                 fprintf(stderr, "%s\n", json_errdetail(result, lex));
  283 andrew@dunslane.net       178                 :            172 :                 ret = 1;
                                179                 :            172 :                 goto cleanup;
                                180                 :                :             }
  545                           181                 :         370748 :             resetStringInfo(&json);
                                182                 :                :         }
                                183                 :                :         else
                                184                 :                :         {
  136 dgustafsson@postgres      185                 :           1788 :             result = pg_parse_json_incremental(lex, testsem,
  545 andrew@dunslane.net       186                 :           1788 :                                                json.data, n_read,
                                187                 :                :                                                true);
                                188         [ +  + ]:           1788 :             if (result != JSON_SUCCESS)
                                189                 :                :             {
  136 dgustafsson@postgres      190                 :            780 :                 fprintf(stderr, "%s\n", json_errdetail(result, lex));
  283 andrew@dunslane.net       191                 :            780 :                 ret = 1;
                                192                 :            780 :                 goto cleanup;
                                193                 :                :             }
  545                           194         [ +  + ]:           1008 :             if (!need_strings)
                                195                 :           1004 :                 printf("SUCCESS!\n");
                                196                 :           1008 :             break;
                                197                 :                :         }
                                198                 :                :     }
                                199                 :                : 
  283                           200                 :           1960 : cleanup:
  545                           201                 :           1960 :     fclose(json_file);
  136 dgustafsson@postgres      202                 :           1960 :     freeJsonLexContext(lex);
  283 andrew@dunslane.net       203                 :           1960 :     free(json.data);
  136 dgustafsson@postgres      204                 :           1960 :     free(lex);
                                205                 :                : 
  283 andrew@dunslane.net       206                 :           1960 :     return ret;
                                207                 :                : }
                                208                 :                : 
                                209                 :                : /*
                                210                 :                :  * The semantic routines here essentially just output the same json, except
                                211                 :                :  * for white space. We could pretty print it but there's no need for our
                                212                 :                :  * purposes. The result should be able to be fed to any JSON processor
                                213                 :                :  * such as jq for validation.
                                214                 :                :  */
                                215                 :                : 
                                216                 :                : static JsonParseErrorType
  545                           217                 :            160 : do_object_start(void *state)
                                218                 :                : {
                                219                 :            160 :     DoState    *_state = (DoState *) state;
                                220                 :                : 
                                221                 :            160 :     printf("{\n");
                                222                 :            160 :     _state->elem_is_first = true;
                                223                 :                : 
                                224                 :            160 :     return JSON_SUCCESS;
                                225                 :                : }
                                226                 :                : 
                                227                 :                : static JsonParseErrorType
                                228                 :            160 : do_object_end(void *state)
                                229                 :                : {
                                230                 :            160 :     DoState    *_state = (DoState *) state;
                                231                 :                : 
                                232                 :            160 :     printf("\n}\n");
                                233                 :            160 :     _state->elem_is_first = false;
                                234                 :                : 
                                235                 :            160 :     return JSON_SUCCESS;
                                236                 :                : }
                                237                 :                : 
                                238                 :                : static JsonParseErrorType
                                239                 :            624 : do_object_field_start(void *state, char *fname, bool isnull)
                                240                 :                : {
                                241                 :            624 :     DoState    *_state = (DoState *) state;
                                242                 :                : 
                                243         [ +  + ]:            624 :     if (!_state->elem_is_first)
                                244                 :            484 :         printf(",\n");
                                245                 :            624 :     resetStringInfo(_state->buf);
                                246                 :            624 :     escape_json(_state->buf, fname);
                                247                 :            624 :     printf("%s: ", _state->buf->data);
                                248                 :            624 :     _state->elem_is_first = false;
                                249                 :                : 
                                250                 :            624 :     return JSON_SUCCESS;
                                251                 :                : }
                                252                 :                : 
                                253                 :                : static JsonParseErrorType
                                254                 :            624 : do_object_field_end(void *state, char *fname, bool isnull)
                                255                 :                : {
  283                           256         [ +  + ]:            624 :     if (!lex_owns_tokens)
                                257                 :            312 :         free(fname);
                                258                 :                : 
  545                           259                 :            624 :     return JSON_SUCCESS;
                                260                 :                : }
                                261                 :                : 
                                262                 :                : static JsonParseErrorType
                                263                 :             44 : do_array_start(void *state)
                                264                 :                : {
                                265                 :             44 :     DoState    *_state = (DoState *) state;
                                266                 :                : 
                                267                 :             44 :     printf("[\n");
                                268                 :             44 :     _state->elem_is_first = true;
                                269                 :                : 
                                270                 :             44 :     return JSON_SUCCESS;
                                271                 :                : }
                                272                 :                : 
                                273                 :                : static JsonParseErrorType
                                274                 :             44 : do_array_end(void *state)
                                275                 :                : {
                                276                 :             44 :     DoState    *_state = (DoState *) state;
                                277                 :                : 
                                278                 :             44 :     printf("\n]\n");
                                279                 :             44 :     _state->elem_is_first = false;
                                280                 :                : 
                                281                 :             44 :     return JSON_SUCCESS;
                                282                 :                : }
                                283                 :                : 
                                284                 :                : static JsonParseErrorType
                                285                 :            120 : do_array_element_start(void *state, bool isnull)
                                286                 :                : {
                                287                 :            120 :     DoState    *_state = (DoState *) state;
                                288                 :                : 
                                289         [ +  + ]:            120 :     if (!_state->elem_is_first)
                                290                 :             76 :         printf(",\n");
                                291                 :            120 :     _state->elem_is_first = false;
                                292                 :                : 
                                293                 :            120 :     return JSON_SUCCESS;
                                294                 :                : }
                                295                 :                : 
                                296                 :                : static JsonParseErrorType
                                297                 :            120 : do_array_element_end(void *state, bool isnull)
                                298                 :                : {
                                299                 :                :     /* nothing to do */
                                300                 :                : 
                                301                 :            120 :     return JSON_SUCCESS;
                                302                 :                : }
                                303                 :                : 
                                304                 :                : static JsonParseErrorType
                                305                 :            544 : do_scalar(void *state, char *token, JsonTokenType tokentype)
                                306                 :                : {
                                307                 :            544 :     DoState    *_state = (DoState *) state;
                                308                 :                : 
                                309         [ +  + ]:            544 :     if (tokentype == JSON_TOKEN_STRING)
                                310                 :                :     {
                                311                 :            424 :         resetStringInfo(_state->buf);
                                312                 :            424 :         escape_json(_state->buf, token);
                                313                 :            424 :         printf("%s", _state->buf->data);
                                314                 :                :     }
                                315                 :                :     else
                                316                 :            120 :         printf("%s", token);
                                317                 :                : 
  283                           318         [ +  + ]:            544 :     if (!lex_owns_tokens)
                                319                 :            272 :         free(token);
                                320                 :                : 
  545                           321                 :            544 :     return JSON_SUCCESS;
                                322                 :                : }
                                323                 :                : 
                                324                 :                : 
                                325                 :                : /*  copied from backend code */
                                326                 :                : static void
                                327                 :           1048 : escape_json(StringInfo buf, const char *str)
                                328                 :                : {
                                329                 :                :     const char *p;
                                330                 :                : 
                                331         [ -  + ]:           1048 :     appendStringInfoCharMacro(buf, '"');
                                332         [ +  + ]:          16472 :     for (p = str; *p; p++)
                                333                 :                :     {
                                334   [ +  +  +  +  :          15424 :         switch (*p)
                                        +  -  +  + ]
                                335                 :                :         {
                                336                 :              4 :             case '\b':
                                337                 :              4 :                 appendStringInfoString(buf, "\\b");
                                338                 :              4 :                 break;
                                339                 :              4 :             case '\f':
                                340                 :              4 :                 appendStringInfoString(buf, "\\f");
                                341                 :              4 :                 break;
                                342                 :              4 :             case '\n':
                                343                 :              4 :                 appendStringInfoString(buf, "\\n");
                                344                 :              4 :                 break;
                                345                 :              4 :             case '\r':
                                346                 :              4 :                 appendStringInfoString(buf, "\\r");
                                347                 :              4 :                 break;
                                348                 :              4 :             case '\t':
                                349                 :              4 :                 appendStringInfoString(buf, "\\t");
                                350                 :              4 :                 break;
  545 andrew@dunslane.net       351                 :UBC           0 :             case '"':
                                352                 :              0 :                 appendStringInfoString(buf, "\\\"");
                                353                 :              0 :                 break;
  545 andrew@dunslane.net       354                 :CBC           4 :             case '\\':
                                355                 :              4 :                 appendStringInfoString(buf, "\\\\");
                                356                 :              4 :                 break;
                                357                 :          15400 :             default:
                                358         [ +  + ]:          15400 :                 if ((unsigned char) *p < ' ')
                                359                 :              4 :                     appendStringInfo(buf, "\\u%04x", (int) *p);
                                360                 :                :                 else
                                361         [ -  + ]:          15396 :                     appendStringInfoCharMacro(buf, *p);
                                362                 :          15400 :                 break;
                                363                 :                :         }
                                364                 :                :     }
                                365         [ -  + ]:           1048 :     appendStringInfoCharMacro(buf, '"');
                                366                 :           1048 : }
                                367                 :                : 
                                368                 :                : static void
                                369                 :              4 : usage(const char *progname)
                                370                 :                : {
                                371                 :              4 :     fprintf(stderr, "Usage: %s [OPTION ...] testfile\n", progname);
                                372                 :              4 :     fprintf(stderr, "Options:\n");
  283                           373                 :              4 :     fprintf(stderr, "  -c chunksize      size of piece fed to parser (default 64)\n");
                                374                 :              4 :     fprintf(stderr, "  -o                set JSONLEX_CTX_OWNS_TOKENS for leak checking\n");
  545                           375                 :              4 :     fprintf(stderr, "  -s                do semantic processing\n");
                                376                 :                : 
                                377                 :              4 : }
        

Generated by: LCOV version 2.4-beta