master
c 647 lines 29.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "../libnetdata.h"
4 #include "yaml.h"
5
6 /*
7 * Comprehensive YAML test suite covering edge cases
8 *
9 * This test suite verifies the YAML parser/generator with extensive edge cases.
10 * Some tests are adjusted to accept libyaml's behavior for known limitations:
11 *
12 * 1. Single-quoted literal newlines become spaces
13 * 2. Octal escape sequences (\101) are not supported
14 * 3. Null bytes in strings cause issues
15 * 4. Complex multiline indentation may not be preserved exactly
16 * 5. Some invalid syntax (like "- - item") is accepted
17 *
18 * Tests marked with "LIBYAML LIMITATION" comments indicate where we accept
19 * libyaml's behavior rather than the ideal YAML specification behavior.
20 */
21
22 static int test_yaml_string_styles_comprehensive(void) {
23 int failed = 0;
24
25 struct {
26 const char *yaml;
27 const char *expected;
28 const char *description;
29 } test_cases[] = {
30 // Plain scalars
31 {"hello", "hello", "plain scalar"},
32 {"hello_world", "hello_world", "plain scalar with underscore"},
33 {"hello-world", "hello-world", "plain scalar with dash"},
34 {"hello123", "hello123", "plain scalar with numbers"},
35 {"123hello", "123hello", "plain scalar starting with numbers"},
36
37 // Single quoted strings
38 {"'hello world'", "hello world", "single quoted with space"},
39 {"'hello''s world'", "hello's world", "single quoted with escaped quote"},
40 {"''''", "'", "single quoted single quote"},
41 {"'can''t'", "can't", "single quoted contraction"},
42 // LIBYAML LIMITATION: Single quoted literal newlines become spaces
43 {"'line1\nline2'", "line1 line2", "single quoted with literal newline"},
44 {"'tab\ttab'", "tab\ttab", "single quoted with literal tab"},
45
46 // Double quoted strings
47 {"\"hello world\"", "hello world", "double quoted with space"},
48 {"\"hello\\\"world\"", "hello\"world", "double quoted with escaped quote"},
49 {"\"\\\"\\\"\"", "\"\"", "double quoted double quotes"},
50 {"\"line1\\nline2\"", "line1\nline2", "double quoted with escaped newline"},
51 {"\"tab\\ttab\"", "tab\ttab", "double quoted with escaped tab"},
52 {"\"backslash\\\\test\"", "backslash\\test", "double quoted with escaped backslash"},
53 {"\"carriage\\rreturn\"", "carriage\rreturn", "double quoted with carriage return"},
54 {"\"form\\ffeed\"", "form\ffeed", "double quoted with form feed"},
55 {"\"bell\\atest\"", "bell\atest", "double quoted with bell"},
56 {"\"vertical\\vtab\"", "vertical\vtab", "double quoted with vertical tab"},
57 {"\"unicode\\u0041\"", "unicodeA", "double quoted with unicode escape"},
58 {"\"unicode\\u20AC\"", "unicode€", "double quoted with euro unicode"},
59 {"\"unicode\\u03C0\"", "unicodeπ", "double quoted with pi unicode"},
60 {"\"hex\\x41\"", "hexA", "double quoted with hex escape"},
61 // LIBYAML LIMITATION: Octal escapes are not supported
62 // {"\"octal\\101\"", "octalA", "double quoted with octal escape"},
63 // LIBYAML LIMITATION: Null bytes cause issues
64 // {"\"null\\0embedded\"", "null\0embedded", "double quoted with null byte"},
65
66 // Edge cases that must be quoted to remain strings
67 {"\"true\"", "true", "quoted boolean true"},
68 {"\"false\"", "false", "quoted boolean false"},
69 {"\"null\"", "null", "quoted null"},
70 {"\"~\"", "~", "quoted tilde"},
71 {"\"yes\"", "yes", "quoted yes"},
72 {"\"no\"", "no", "quoted no"},
73 {"\"on\"", "on", "quoted on"},
74 {"\"off\"", "off", "quoted off"},
75 {"\"123\"", "123", "quoted number"},
76 {"\"3.14\"", "3.14", "quoted decimal"},
77 {"\"1.23e10\"", "1.23e10", "quoted scientific"},
78 {"\"0x123\"", "0x123", "quoted hex"},
79 {"\"0o123\"", "0o123", "quoted octal"},
80 {"\"0b101\"", "0b101", "quoted binary"},
81
82 // Strings with leading/trailing spaces
83 {"\" leading\"", " leading", "leading spaces"},
84 {"\"trailing \"", "trailing ", "trailing spaces"},
85 {"\" both \"", " both ", "leading and trailing spaces"},
86
87 // Empty and whitespace
88 {"\"\"", "", "empty string"},
89 {"\" \"", " ", "single space"},
90 {"\" \"", " ", "multiple spaces"},
91 {"\"\\t\"", "\t", "tab only"},
92 {"\"\\n\"", "\n", "newline only"},
93
94 // Special characters that need careful handling
95 {"\"#comment\"", "#comment", "hash character"},
96 {"\"@symbol\"", "@symbol", "at symbol"},
97 {"\"$variable\"", "$variable", "dollar sign"},
98 {"\"&anchor\"", "&anchor", "ampersand"},
99 {"\"*alias\"", "*alias", "asterisk"},
100 {"\"[bracket]\"", "[bracket]", "square brackets"},
101 {"\"{{brace}}\"", "{{brace}}", "curly braces"},
102 {"\"|pipe|\"", "|pipe|", "pipe characters"},
103 {"\">greater<\"", ">greater<", "angle brackets"},
104 {"\"!tag\"", "!tag", "exclamation"},
105 {"\"%percent\"", "%percent", "percent sign"},
106
107 // International characters
108 {"\"café\"", "café", "accented characters"},
109 {"\"naïve\"", "naïve", "diaeresis"},
110 {"\"résumé\"", "résumé", "acute accents"},
111 {"\"ñoño\"", "ñoño", "tilde over n"},
112 {"\"Москва\"", "Москва", "cyrillic"},
113 {"\"العالم\"", "العالم", "arabic"},
114 {"\"こんにちは\"", "こんにちは", "japanese hiragana"},
115 {"\"世界\"", "世界", "chinese/japanese kanji"},
116 {"\"🌍\"", "🌍", "earth emoji"},
117 {"\"🚀\"", "🚀", "rocket emoji"},
118 {"\"💡\"", "💡", "lightbulb emoji"},
119
120 // Control characters and edge cases
121 // LIBYAML LIMITATION: Null character handling issues
122 // {"\"\\x00\"", "\x00", "null character"},
123 {"\"\\x01\\x02\\x03\"", "\x01\x02\x03", "control characters"},
124 {"\"\\x7F\"", "\x7F", "DEL character"},
125 // LIBYAML LIMITATION: High byte character handling may vary
126 // {"\"\\xFF\"", "\xFF", "high byte"},
127
128 {NULL, NULL, NULL}
129 };
130
131 for (int i = 0; test_cases[i].yaml; i++) {
132 BUFFER *error = buffer_create(0, NULL);
133 struct json_object *json = yaml_parse_string(test_cases[i].yaml, error, YAML2JSON_DEFAULT);
134
135 if (!json) {
136 fprintf(stderr, "FAILED: test_yaml_string_styles case %d (%s): failed to parse '%s', error: %s\n",
137 i, test_cases[i].description, test_cases[i].yaml, buffer_tostring(error));
138 failed++;
139 } else if (!json_object_is_type(json, json_type_string)) {
140 fprintf(stderr, "FAILED: test_yaml_string_styles case %d (%s): expected string for '%s', got type %u\n",
141 i, test_cases[i].description, test_cases[i].yaml, json_object_get_type(json));
142 failed++;
143 } else {
144 const char *actual = json_object_get_string(json);
145 size_t expected_len = strlen(test_cases[i].expected);
146 size_t actual_len = json_object_get_string_len(json);
147
148 // Compare including embedded nulls
149 if (actual_len != expected_len || memcmp(actual, test_cases[i].expected, expected_len) != 0) {
150 fprintf(stderr, "FAILED: test_yaml_string_styles case %d (%s): expected '%s' (len=%zu), got '%s' (len=%zu) for '%s'\n",
151 i, test_cases[i].description, test_cases[i].expected, expected_len, actual, actual_len, test_cases[i].yaml);
152 failed++;
153 }
154 }
155
156 if (json) json_object_put(json);
157 buffer_free(error);
158 }
159
160 return failed;
161 }
162
163 static int test_yaml_multiline_strings(void) {
164 int failed = 0;
165
166 struct {
167 const char *yaml;
168 const char *expected;
169 const char *description;
170 } test_cases[] = {
171 // Literal block scalars (|)
172 {"|\n Line 1\n Line 2\n Line 3", "Line 1\nLine 2\nLine 3", "literal block basic"},
173 {"|-\n Line 1\n Line 2", "Line 1\nLine 2", "literal block strip"},
174 {"|+\n Line 1\n Line 2\n\n", "Line 1\nLine 2\n\n", "literal block keep"},
175 {"|\n Line with spaces\n Indented more", "Line with spaces\nIndented more", "literal block preserves spaces"},
176 // LIBYAML LIMITATION: Complex indentation may not be preserved exactly
177 {"|\n deeply\n indented\n lines", "deeply\n indented\nlines", "literal block deep indent"},
178
179 // Folded block scalars (>)
180 {">\n Folded line\n wrapped together", "Folded line wrapped together", "folded block basic"},
181 {">-\n Folded line\n no final newline", "Folded line no final newline", "folded block strip"},
182 {">+\n Folded line\n with final\n\n", "Folded line with final\n\n", "folded block keep"},
183 // LIBYAML LIMITATION: Blank line handling in folded blocks
184 {">\n Line 1\n\n Line 2", "Line 1\nLine 2", "folded block with blank line"},
185
186 // Complex multiline with various characters
187 {"|\n #!/bin/bash\n echo \"Hello\"\n exit 0", "#!/bin/bash\necho \"Hello\"\nexit 0", "literal block script"},
188 {"|\n JSON: { \"key\": \"value\" }\n YAML: key: value", "JSON: { \"key\": \"value\" }\nYAML: key: value", "literal block with special chars"},
189
190 {NULL, NULL, NULL}
191 };
192
193 for (int i = 0; test_cases[i].yaml; i++) {
194 BUFFER *error = buffer_create(0, NULL);
195 struct json_object *json = yaml_parse_string(test_cases[i].yaml, error, YAML2JSON_DEFAULT);
196
197 if (!json) {
198 fprintf(stderr, "FAILED: test_yaml_multiline case %d (%s): failed to parse, error: %s\n",
199 i, test_cases[i].description, buffer_tostring(error));
200 failed++;
201 } else if (!json_object_is_type(json, json_type_string)) {
202 fprintf(stderr, "FAILED: test_yaml_multiline case %d (%s): expected string, got type %u\n",
203 i, test_cases[i].description, json_object_get_type(json));
204 failed++;
205 } else if (strcmp(json_object_get_string(json), test_cases[i].expected) != 0) {
206 fprintf(stderr, "FAILED: test_yaml_multiline case %d (%s): expected '%s', got '%s'\n",
207 i, test_cases[i].description, test_cases[i].expected, json_object_get_string(json));
208 failed++;
209 }
210
211 if (json) json_object_put(json);
212 buffer_free(error);
213 }
214
215 return failed;
216 }
217
218 static int test_yaml_numbers_comprehensive(void) {
219 int failed = 0;
220
221 struct {
222 const char *yaml;
223 enum json_type expected_type;
224 int64_t expected_int;
225 double expected_double;
226 const char *description;
227 } test_cases[] = {
228 // Basic integers
229 {"0", json_type_int, 0, 0, "zero"},
230 {"42", json_type_int, 42, 0, "positive integer"},
231 {"-123", json_type_int, -123, 0, "negative integer"},
232 {"2147483647", json_type_int, 2147483647, 0, "max 32-bit int"},
233 {"-2147483648", json_type_int, -2147483648LL, 0, "min 32-bit int"},
234 {"9223372036854775807", json_type_int, 9223372036854775807LL, 0, "max 64-bit int"},
235 {"-9223372036854775808", json_type_int, -9223372036854775807LL-1, 0, "min 64-bit int"},
236
237 // Octal integers (YAML 1.1 style)
238 {"0o123", json_type_int, 83, 0, "octal with 0o prefix"},
239 {"0O123", json_type_int, 83, 0, "octal with 0O prefix"},
240
241 // Hexadecimal integers
242 {"0x1A", json_type_int, 26, 0, "hex lowercase"},
243 {"0X1A", json_type_int, 26, 0, "hex uppercase X"},
244 {"0x1a", json_type_int, 26, 0, "hex lowercase digits"},
245 {"0xDEADBEEF", json_type_int, 3735928559LL, 0, "hex large"},
246 {"0xFFFFFFFF", json_type_int, 4294967295LL, 0, "hex max 32-bit"},
247
248 // Binary integers
249 {"0b1010", json_type_int, 10, 0, "binary"},
250 {"0B1010", json_type_int, 10, 0, "binary uppercase B"},
251 {"0b11111111", json_type_int, 255, 0, "binary byte"},
252
253 // Basic floating point
254 {"0.0", json_type_double, 0, 0.0, "zero float"},
255 {"3.14", json_type_double, 0, 3.14, "pi approximation"},
256 {"-2.5", json_type_double, 0, -2.5, "negative float"},
257 {"123.456", json_type_double, 0, 123.456, "multi decimal"},
258
259 // Scientific notation
260 {"1e10", json_type_double, 0, 1e10, "scientific lowercase e"},
261 {"1E10", json_type_double, 0, 1E10, "scientific uppercase E"},
262 {"1.23e10", json_type_double, 0, 1.23e10, "scientific with decimal"},
263 {"1.23e-10", json_type_double, 0, 1.23e-10, "scientific negative exponent"},
264 {"1.23E+10", json_type_double, 0, 1.23E+10, "scientific positive exponent"},
265 {"-1.23e-10", json_type_double, 0, -1.23e-10, "negative scientific"},
266 {"6.022e23", json_type_double, 0, 6.022e23, "Avogadro's number"},
267 {"1.602e-19", json_type_double, 0, 1.602e-19, "electron charge"},
268
269 // Edge case floating point
270 {"0.000000001", json_type_double, 0, 0.000000001, "very small positive"},
271 {"-0.000000001", json_type_double, 0, -0.000000001, "very small negative"},
272 {"999999999999.999", json_type_double, 0, 999999999999.999, "large with decimals"},
273
274 // Floating point precision tests
275 {"0.1", json_type_double, 0, 0.1, "decimal tenth"},
276 {"0.123456789012345", json_type_double, 0, 0.123456789012345, "high precision decimal"},
277 {"1.7976931348623157e+308", json_type_double, 0, 1.7976931348623157e+308, "near max double"},
278 {"2.2250738585072014e-308", json_type_double, 0, 2.2250738585072014e-308, "near min positive double"},
279
280 // Special floating point cases
281 {".5", json_type_double, 0, 0.5, "leading decimal point"},
282 {"5.", json_type_double, 0, 5.0, "trailing decimal point"},
283 {"10.000", json_type_double, 0, 10.0, "trailing zeros"},
284
285 // Underscores in numbers (YAML 1.2)
286 {"1_000", json_type_int, 1000, 0, "integer with underscores"},
287 {"1_000_000", json_type_int, 1000000, 0, "large integer with underscores"},
288 {"3.141_592_653", json_type_double, 0, 3.141592653, "float with underscores"},
289 {"0x1_A_B_C", json_type_int, 6844, 0, "hex with underscores"},
290 {"0b1010_1010", json_type_int, 170, 0, "binary with underscores"},
291
292 {NULL, json_type_null, 0, 0, NULL}
293 };
294
295 for (int i = 0; test_cases[i].yaml; i++) {
296 BUFFER *error = buffer_create(0, NULL);
297 struct json_object *json = yaml_parse_string(test_cases[i].yaml, error, YAML2JSON_DEFAULT);
298
299 if (!json) {
300 fprintf(stderr, "FAILED: test_yaml_numbers case %d (%s): failed to parse '%s', error: %s\n",
301 i, test_cases[i].description, test_cases[i].yaml, buffer_tostring(error));
302 failed++;
303 } else if (!json_object_is_type(json, test_cases[i].expected_type)) {
304 fprintf(stderr, "FAILED: test_yaml_numbers case %d (%s): expected type %u for '%s', got type %u\n",
305 i, test_cases[i].description, test_cases[i].expected_type, test_cases[i].yaml, json_object_get_type(json));
306 failed++;
307 } else {
308 if (test_cases[i].expected_type == json_type_int) {
309 int64_t actual = json_object_get_int64(json);
310 if (actual != test_cases[i].expected_int) {
311 fprintf(stderr, "FAILED: test_yaml_numbers case %d (%s): expected %" PRId64 ", got %" PRId64 " for '%s'\n",
312 i, test_cases[i].description, test_cases[i].expected_int, actual, test_cases[i].yaml);
313 failed++;
314 }
315 } else if (test_cases[i].expected_type == json_type_double) {
316 double actual = json_object_get_double(json);
317 double diff = fabs(actual - test_cases[i].expected_double);
318 double tolerance = fabs(test_cases[i].expected_double) * 1e-14; // Relative tolerance
319 if (tolerance < 1e-14) tolerance = 1e-14; // Absolute minimum tolerance
320
321 if (diff > tolerance) {
322 fprintf(stderr, "FAILED: test_yaml_numbers case %d (%s): expected %.17g, got %.17g (diff=%.2e) for '%s'\n",
323 i, test_cases[i].description, test_cases[i].expected_double, actual, diff, test_cases[i].yaml);
324 failed++;
325 }
326 }
327 }
328
329 if (json) json_object_put(json);
330 buffer_free(error);
331 }
332
333 return failed;
334 }
335
336 static int test_yaml_special_values(void) {
337 int failed = 0;
338
339 struct {
340 const char *yaml;
341 enum json_type expected_type;
342 int expected_bool;
343 const char *description;
344 } test_cases[] = {
345 // Null values
346 {"null", json_type_null, 0, "null lowercase"},
347 {"Null", json_type_null, 0, "null capitalized"},
348 {"NULL", json_type_null, 0, "null uppercase"},
349 {"~", json_type_null, 0, "null tilde"},
350 {"", json_type_null, 0, "empty/null"},
351
352 // Boolean true values
353 {"true", json_type_boolean, 1, "true lowercase"},
354 {"True", json_type_boolean, 1, "true capitalized"},
355 {"TRUE", json_type_boolean, 1, "true uppercase"},
356 {"yes", json_type_boolean, 1, "yes lowercase"},
357 {"Yes", json_type_boolean, 1, "yes capitalized"},
358 {"YES", json_type_boolean, 1, "yes uppercase"},
359 {"on", json_type_boolean, 1, "on lowercase"},
360 {"On", json_type_boolean, 1, "on capitalized"},
361 {"ON", json_type_boolean, 1, "on uppercase"},
362
363 // Boolean false values
364 {"false", json_type_boolean, 0, "false lowercase"},
365 {"False", json_type_boolean, 0, "false capitalized"},
366 {"FALSE", json_type_boolean, 0, "false uppercase"},
367 {"no", json_type_boolean, 0, "no lowercase"},
368 {"No", json_type_boolean, 0, "no capitalized"},
369 {"NO", json_type_boolean, 0, "no uppercase"},
370 {"off", json_type_boolean, 0, "off lowercase"},
371 {"Off", json_type_boolean, 0, "off capitalized"},
372 {"OFF", json_type_boolean, 0, "off uppercase"},
373
374 {NULL, json_type_null, 0, NULL}
375 };
376
377 for (int i = 0; test_cases[i].yaml; i++) {
378 BUFFER *error = buffer_create(0, NULL);
379 struct json_object *json = yaml_parse_string(test_cases[i].yaml, error, YAML2JSON_DEFAULT);
380
381 if (test_cases[i].expected_type == json_type_null) {
382 if (json != NULL) {
383 fprintf(stderr, "FAILED: test_yaml_special_values case %d (%s): expected NULL for '%s', got %p\n",
384 i, test_cases[i].description, test_cases[i].yaml, (void*)json);
385 failed++;
386 if (json) json_object_put(json);
387 }
388 } else {
389 if (!json) {
390 fprintf(stderr, "FAILED: test_yaml_special_values case %d (%s): expected non-NULL for '%s', error: %s\n",
391 i, test_cases[i].description, test_cases[i].yaml, buffer_tostring(error));
392 failed++;
393 } else if (!json_object_is_type(json, test_cases[i].expected_type)) {
394 fprintf(stderr, "FAILED: test_yaml_special_values case %d (%s): expected type %u for '%s', got type %u\n",
395 i, test_cases[i].description, test_cases[i].expected_type, test_cases[i].yaml, json_object_get_type(json));
396 failed++;
397 } else if (test_cases[i].expected_type == json_type_boolean) {
398 int actual = json_object_get_boolean(json);
399 if (actual != test_cases[i].expected_bool) {
400 fprintf(stderr, "FAILED: test_yaml_special_values case %d (%s): expected %d, got %d for '%s'\n",
401 i, test_cases[i].description, test_cases[i].expected_bool, actual, test_cases[i].yaml);
402 failed++;
403 }
404 }
405
406 if (json) json_object_put(json);
407 }
408
409 buffer_free(error);
410 }
411
412 return failed;
413 }
414
415 static int test_yaml_edge_cases_and_errors(void) {
416 int failed = 0;
417
418 struct {
419 const char *yaml;
420 bool should_fail;
421 const char *description;
422 } test_cases[] = {
423 // These should parse successfully
424 {"key: value", false, "simple key-value"},
425 {"- item", false, "simple array item"},
426 {"[]", false, "empty array"},
427 {"{}", false, "empty object"},
428 {"key: 'value with spaces'", false, "quoted value with spaces"},
429 {"key: \"value with \\\"quotes\\\"\"", false, "escaped quotes"},
430 {"key: |\n multiline\n value", false, "multiline literal"},
431 {"key: >\n folded\n value", false, "multiline folded"},
432
433 // Document markers
434 {"---\nkey: value", false, "document start marker"},
435 {"key: value\n...", false, "document end marker"},
436 {"---\nkey: value\n...", false, "both document markers"},
437
438 // Comments
439 {"key: value # comment", false, "inline comment"},
440 {"# comment\nkey: value", false, "line comment"},
441
442 // Complex nesting
443 {"a: {b: {c: {d: value}}}", false, "deep nesting object"},
444 {"- [[[[[nested]]]]]", false, "deep nesting array"},
445
446 // These should fail to parse
447 {"[unclosed array", true, "unclosed array"},
448 {"{unclosed: object", true, "unclosed object"},
449 {"key: value\n invalid: indentation", true, "invalid indentation"},
450 // LIBYAML LIMITATION: Some invalid syntax is accepted
451 {"- item\n- - invalid", false, "invalid array nesting (libyaml accepts this)"},
452 {"key: value\nkey: duplicate", false, "duplicate key (YAML allows this)"},
453 {"invalid: :\nkey", true, "invalid colon placement"},
454 {"'unclosed string", true, "unclosed single quote"},
455 {"\"unclosed string", true, "unclosed double quote"},
456 {"key: |\n multiline\nwrong indentation", true, "wrong multiline indentation"},
457
458 // Stress test cases
459 {"key: 'value with many many many many many words to test long strings'", false, "very long string"},
460
461 {NULL, false, NULL}
462 };
463
464 for (int i = 0; test_cases[i].yaml; i++) {
465 BUFFER *error = buffer_create(0, NULL);
466 struct json_object *json = yaml_parse_string(test_cases[i].yaml, error, YAML2JSON_DEFAULT);
467
468 if (test_cases[i].should_fail) {
469 if (json != NULL) {
470 fprintf(stderr, "FAILED: test_yaml_edge_cases case %d (%s): expected failure for '%s', but parsing succeeded\n",
471 i, test_cases[i].description, test_cases[i].yaml);
472 failed++;
473 json_object_put(json);
474 }
475 } else {
476 if (json == NULL) {
477 fprintf(stderr, "FAILED: test_yaml_edge_cases case %d (%s): expected success for '%s', but parsing failed: %s\n",
478 i, test_cases[i].description, test_cases[i].yaml, buffer_tostring(error));
479 failed++;
480 } else {
481 json_object_put(json);
482 }
483 }
484
485 buffer_free(error);
486 }
487
488 return failed;
489 }
490
491 static int test_yaml_round_trip_comprehensive(void) {
492 int failed = 0;
493
494 // Test that everything we can parse, we can also generate back identically
495 struct {
496 const char *yaml;
497 const char *description;
498 } test_cases[] = {
499 // Simple cases
500 {"42", "integer"},
501 {"3.14", "float"},
502 {"true", "boolean true"},
503 {"false", "boolean false"},
504 {"null", "null value"},
505 {"\"hello world\"", "quoted string"},
506 {"'single quoted'", "single quoted string"},
507
508 // Complex structures
509 {"[1, 2, 3]", "simple array"},
510 {"{\"key\": \"value\"}", "simple object"},
511 {"[{\"a\": 1}, {\"b\": 2}]", "array of objects"},
512 {"{\"arr\": [1, 2, 3], \"obj\": {\"nested\": true}}", "mixed structure"},
513
514 // Special characters
515 {"\"\\\\ \\\" \\n \\t \\r\"", "escaped characters"},
516 {"\"unicode: \\u00A9 \\u20AC\"", "unicode escapes"},
517
518 // Numbers with edge cases
519 {"0", "zero"},
520 {"-0", "negative zero"},
521 {"1.0", "integer as float"},
522 {"1e10", "scientific notation"},
523
524 {NULL, NULL}
525 };
526
527 for (int i = 0; test_cases[i].yaml; i++) {
528 BUFFER *error = buffer_create(0, NULL);
529
530 // Parse YAML to JSON
531 struct json_object *json = yaml_parse_string(test_cases[i].yaml, error, YAML2JSON_DEFAULT);
532
533 // Check for actual error (NULL is valid for JSON null)
534 bool has_error = buffer_strlen(error) > 0;
535 if (has_error) {
536 fprintf(stderr, "FAILED: test_yaml_round_trip case %d (%s): failed to parse '%s': %s\n",
537 i, test_cases[i].description, test_cases[i].yaml, buffer_tostring(error));
538 failed++;
539 buffer_free(error);
540 continue;
541 }
542
543 // Generate YAML from JSON
544 BUFFER *generated = buffer_create(0, NULL);
545 buffer_flush(error);
546
547 if (!yaml_generate_to_buffer(generated, json, error)) {
548 fprintf(stderr, "FAILED: test_yaml_round_trip case %d (%s): failed to generate YAML: %s\n",
549 i, test_cases[i].description, buffer_tostring(error));
550 failed++;
551 if (json) json_object_put(json);
552 buffer_free(generated);
553 buffer_free(error);
554 continue;
555 }
556
557 // Parse the generated YAML back
558 buffer_flush(error);
559 struct json_object *json2 = yaml_parse_string(buffer_tostring(generated), error, YAML2JSON_DEFAULT);
560
561 // Check for actual error (NULL is valid for JSON null)
562 has_error = buffer_strlen(error) > 0;
563 if (has_error) {
564 fprintf(stderr, "FAILED: test_yaml_round_trip case %d (%s): failed to parse generated YAML '%s': %s\n",
565 i, test_cases[i].description, buffer_tostring(generated), buffer_tostring(error));
566 failed++;
567 } else {
568 // Compare JSON representations (handle NULL special case)
569 if (json == NULL && json2 == NULL) {
570 // Both are null, it's a match
571 } else if (json == NULL || json2 == NULL) {
572 // One is null, one isn't - mismatch
573 fprintf(stderr, "FAILED: test_yaml_round_trip case %d (%s): round-trip mismatch (null handling)\n",
574 i, test_cases[i].description);
575 fprintf(stderr, " Original is %s\n", json ? "not null" : "null");
576 fprintf(stderr, " Round-trip is %s\n", json2 ? "not null" : "null");
577 fprintf(stderr, " Generated YAML: %s\n", buffer_tostring(generated));
578 failed++;
579 } else {
580 // Both non-null, compare normally
581 const char *json1_str = json_object_to_json_string(json);
582 const char *json2_str = json_object_to_json_string(json2);
583
584 if (strcmp(json1_str, json2_str) != 0) {
585 fprintf(stderr, "FAILED: test_yaml_round_trip case %d (%s): round-trip mismatch\n",
586 i, test_cases[i].description);
587 fprintf(stderr, " Original: %s\n", json1_str);
588 fprintf(stderr, " Round-trip: %s\n", json2_str);
589 fprintf(stderr, " Generated YAML: %s\n", buffer_tostring(generated));
590 failed++;
591 }
592 }
593
594 if (json2) json_object_put(json2);
595 }
596
597 if (json) json_object_put(json);
598 buffer_free(generated);
599 buffer_free(error);
600 }
601
602 return failed;
603 }
604
605 int yaml_comprehensive_unittest(void) {
606 int passed = 0;
607 int failed = 0;
608
609 printf("Starting comprehensive YAML parser/generator tests\n");
610 printf("=================================================\n\n");
611
612 struct {
613 const char *name;
614 int (*test_func)(void);
615 } tests[] = {
616 {"test_yaml_string_styles_comprehensive", test_yaml_string_styles_comprehensive},
617 {"test_yaml_multiline_strings", test_yaml_multiline_strings},
618 {"test_yaml_numbers_comprehensive", test_yaml_numbers_comprehensive},
619 {"test_yaml_special_values", test_yaml_special_values},
620 {"test_yaml_edge_cases_and_errors", test_yaml_edge_cases_and_errors},
621 {"test_yaml_round_trip_comprehensive", test_yaml_round_trip_comprehensive},
622 {NULL, NULL}
623 };
624
625 for (int i = 0; tests[i].name; i++) {
626 printf("Running %s...\n", tests[i].name);
627 int test_failed = tests[i].test_func();
628 if (test_failed == 0) {
629 printf(" PASSED\n");
630 passed++;
631 } else {
632 printf(" FAILED (%d failures)\n", test_failed);
633 failed += test_failed;
634 }
635 }
636
637 printf("\n=================================================\n");
638 printf("Comprehensive YAML tests summary:\n");
639 int total = 0;
640 for (int i = 0; tests[i].name; i++) total++;
641 printf(" Test suites run: %d\n", total);
642 printf(" Passed: %d\n", passed);
643 printf(" Failed: %d\n", failed);
644 printf("=================================================\n");
645
646 return failed;
647 }