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: b45a8d7d8b306b43f31a002f1b3f1dddc8defeaf vs 8767b449a3a1e75626dfb08f24da54933171d4c5 Lines: 95.3 % 170 162 8 162
Current Date: 2025-10-28 08:26:42 +0900 Functions: 100.0 % 12 12 12
Baseline: lcov-20251028-005825-baseline Branches: 84.1 % 63 53 10 53
Baseline Date: 2025-10-27 06:37:35 +0000 Line coverage date bins:
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
(7,30] days: 97.6 % 41 40 1 40
(30,360] days: 94.7 % 19 18 1 18
(360..) days: 94.5 % 110 104 6 104
Function coverage date bins:
(360..) days: 100.0 % 12 12 12
Branch coverage date bins:
(7,30] days: 95.0 % 20 19 1 19
(30,360] days: 83.3 % 6 5 1 5
(360..) days: 78.4 % 37 29 8 29

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

Generated by: LCOV version 2.4-beta