1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
# Copyright (c) 2021-2025, PostgreSQL Global Development Group
# Test success or failure of the incremental (table-driven) JSON parser
# for a variety of small inputs.
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Utils;
use Test::More;
use File::Temp qw(tempfile);
my $dir = PostgreSQL::Test::Utils::tempdir;
my @exe;
sub test
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
my ($name, $json, %params) = @_;
my $chunk = length($json);
# Test the input with chunk sizes from max(input_size, 64) down to 1
if ($chunk > 64)
{
$chunk = 64;
}
my ($fh, $fname) = tempfile(DIR => $dir);
print $fh "$json";
close($fh);
foreach my $size (reverse(1 .. $chunk))
{
my ($stdout, $stderr) = run_command([ @exe, "-c", $size, $fname ]);
if (defined($params{error}))
{
unlike($stdout, qr/SUCCESS/,
"$name, chunk size $size: test fails");
like($stderr, $params{error},
"$name, chunk size $size: correct error output");
}
else
{
like($stdout, qr/SUCCESS/,
"$name, chunk size $size: test succeeds");
is($stderr, "", "$name, chunk size $size: no error output");
}
}
}
my @exes = (
[ "test_json_parser_incremental", ],
[ "test_json_parser_incremental", "-o", ],
[ "test_json_parser_incremental_shlib", ],
[ "test_json_parser_incremental_shlib", "-o", ]);
foreach (@exes)
{
@exe = @$_;
note "testing executable @exe";
test("number", "12345");
test("string", '"hello"');
test("false", "false");
test("true", "true");
test("null", "null");
test("empty object", "{}");
test("empty array", "[]");
test("array with number", "[12345]");
test("array with numbers", "[12345,67890]");
test("array with null", "[null]");
test("array with string", '["hello"]');
test("array with boolean", '[false]');
test("single pair", '{"key": "value"}');
test("heavily nested array", "[" x 3200 . "]" x 3200);
test("serial escapes", '"\\\\\\\\\\\\\\\\"');
test("interrupted escapes", '"\\\\\\"\\\\\\\\\\"\\\\"');
test("whitespace", ' "" ');
test("unclosed empty object",
"{", error => qr/input string ended unexpectedly/);
test("bad key", "{{",
error => qr/Expected string or "}", but found "\{"/);
test("bad key", "{{}",
error => qr/Expected string or "}", but found "\{"/);
test("numeric key", "{1234: 2}",
error => qr/Expected string or "}", but found "1234"/);
test(
"second numeric key",
'{"a": "a", 1234: 2}',
error => qr/Expected string, but found "1234"/);
test(
"unclosed object with pair",
'{"key": "value"',
error => qr/input string ended unexpectedly/);
test("missing key value",
'{"key": }', error => qr/Expected JSON value, but found "}"/);
test(
"missing colon",
'{"key" 12345}',
error => qr/Expected ":", but found "12345"/);
test(
"missing comma",
'{"key": 12345 12345}',
error => qr/Expected "," or "}", but found "12345"/);
test("overnested array",
"[" x 6401, error => qr/maximum permitted depth is 6400/);
test("overclosed array",
"[]]", error => qr/Expected end of input, but found "]"/);
test("unexpected token in array",
"[ }}} ]", error => qr/Expected array element or "]", but found "}"/);
test("junk punctuation", "[ ||| ]", error => qr/Token "|" is invalid/);
test("missing comma in array",
"[123 123]", error => qr/Expected "," or "]", but found "123"/);
test("misspelled boolean", "tru", error => qr/Token "tru" is invalid/);
test(
"misspelled boolean in array",
"[tru]",
error => qr/Token "tru" is invalid/);
test(
"smashed top-level scalar",
"12zz",
error => qr/Token "12zz" is invalid/);
test(
"smashed scalar in array",
"[12zz]",
error => qr/Token "12zz" is invalid/);
test(
"unknown escape sequence",
'"hello\vworld"',
error => qr/Escape sequence "\\v" is invalid/);
test("unescaped control",
"\"hello\tworld\"",
error => qr/Character with value 0x09 must be escaped/);
test(
"incorrect escape count",
'"\\\\\\\\\\\\\\"',
error => qr/Token ""\\\\\\\\\\\\\\"" is invalid/);
# Case with three bytes: double-quote, backslash and <f5>.
# Both invalid-token and invalid-escape are possible errors, because for
# smaller chunk sizes the incremental parser skips the string parsing when
# it cannot find an ending quote.
test("incomplete UTF-8 sequence",
"\"\\\x{F5}",
error => qr/(Token|Escape sequence) ""?\\\x{F5}" is invalid/);
}
done_testing();
|