| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "../libnetdata.h" |
| 4 | #include "yaml.h" |
| 5 | |
| 6 | static int test_yaml_parse_null(void) { |
| 7 | int failed = 0; |
| 8 | |
| 9 | const char *yaml_inputs[] = { |
| 10 | "null", |
| 11 | "~", |
| 12 | "---\nnull", |
| 13 | NULL |
| 14 | }; |
| 15 | |
| 16 | for (int i = 0; yaml_inputs[i]; i++) { |
| 17 | BUFFER *error = buffer_create(0, NULL); |
| 18 | struct json_object *json = yaml_parse_string(yaml_inputs[i], error, YAML2JSON_DEFAULT); |
| 19 | |
| 20 | if (json != NULL) { |
| 21 | fprintf(stderr, "FAILED: test_yaml_parse_null case %d: expected NULL, got %p\n", |
| 22 | i, (void*)json); |
| 23 | failed++; |
| 24 | json_object_put(json); |
| 25 | } |
| 26 | |
| 27 | buffer_free(error); |
| 28 | } |
| 29 | |
| 30 | return failed; |
| 31 | } |
| 32 | |
| 33 | static int test_yaml_parse_boolean(void) { |
| 34 | int failed = 0; |
| 35 | |
| 36 | struct { |
| 37 | const char *yaml; |
| 38 | int expected; |
| 39 | } test_cases[] = { |
| 40 | {"true", 1}, |
| 41 | {"false", 0}, |
| 42 | {"yes", 1}, |
| 43 | {"no", 0}, |
| 44 | {"on", 1}, |
| 45 | {"off", 0}, |
| 46 | {"True", 1}, |
| 47 | {"False", 0}, |
| 48 | {"YES", 1}, |
| 49 | {"NO", 0}, |
| 50 | {NULL, 0} |
| 51 | }; |
| 52 | |
| 53 | for (int i = 0; test_cases[i].yaml; i++) { |
| 54 | BUFFER *error = buffer_create(0, NULL); |
| 55 | struct json_object *json = yaml_parse_string(test_cases[i].yaml, error, YAML2JSON_DEFAULT); |
| 56 | |
| 57 | if (!json || !json_object_is_type(json, json_type_boolean)) { |
| 58 | fprintf(stderr, "FAILED: test_yaml_parse_boolean case %d: expected boolean for '%s'\n", |
| 59 | i, test_cases[i].yaml); |
| 60 | failed++; |
| 61 | } else if (json_object_get_boolean(json) != test_cases[i].expected) { |
| 62 | fprintf(stderr, "FAILED: test_yaml_parse_boolean case %d: expected %d, got %d for '%s'\n", |
| 63 | i, test_cases[i].expected, json_object_get_boolean(json), test_cases[i].yaml); |
| 64 | failed++; |
| 65 | } |
| 66 | |
| 67 | if (json) json_object_put(json); |
| 68 | buffer_free(error); |
| 69 | } |
| 70 | |
| 71 | return failed; |
| 72 | } |
| 73 | |
| 74 | static int test_yaml_parse_numbers(void) { |
| 75 | int failed = 0; |
| 76 | |
| 77 | struct { |
| 78 | const char *yaml; |
| 79 | enum json_type expected_type; |
| 80 | int64_t expected_int; |
| 81 | double expected_double; |
| 82 | } test_cases[] = { |
| 83 | {"42", json_type_int, 42, 0}, |
| 84 | {"-123", json_type_int, -123, 0}, |
| 85 | {"0", json_type_int, 0, 0}, |
| 86 | {"3.14", json_type_double, 0, 3.14}, |
| 87 | {"-0.5", json_type_double, 0, -0.5}, |
| 88 | {"1.23e10", json_type_double, 0, 1.23e10}, |
| 89 | {"1.23e-10", json_type_double, 0, 1.23e-10}, |
| 90 | {NULL, 0, 0, 0} |
| 91 | }; |
| 92 | |
| 93 | for (int i = 0; test_cases[i].yaml; i++) { |
| 94 | BUFFER *error = buffer_create(0, NULL); |
| 95 | struct json_object *json = yaml_parse_string(test_cases[i].yaml, error, YAML2JSON_DEFAULT); |
| 96 | |
| 97 | if (!json || !json_object_is_type(json, test_cases[i].expected_type)) { |
| 98 | fprintf(stderr, "FAILED: test_yaml_parse_numbers case %d: wrong type for '%s'\n", |
| 99 | i, test_cases[i].yaml); |
| 100 | failed++; |
| 101 | } else { |
| 102 | if (test_cases[i].expected_type == json_type_int) { |
| 103 | if (json_object_get_int64(json) != test_cases[i].expected_int) { |
| 104 | fprintf(stderr, "FAILED: test_yaml_parse_numbers case %d: expected %" PRId64 ", got %" PRId64 " for '%s'\n", |
| 105 | i, test_cases[i].expected_int, json_object_get_int64(json), test_cases[i].yaml); |
| 106 | failed++; |
| 107 | } |
| 108 | } else { |
| 109 | double diff = fabs(json_object_get_double(json) - test_cases[i].expected_double); |
| 110 | if (diff > 0.000001) { |
| 111 | fprintf(stderr, "FAILED: test_yaml_parse_numbers case %d: expected %f, got %f for '%s'\n", |
| 112 | i, test_cases[i].expected_double, json_object_get_double(json), test_cases[i].yaml); |
| 113 | failed++; |
| 114 | } |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | if (json) json_object_put(json); |
| 119 | buffer_free(error); |
| 120 | } |
| 121 | |
| 122 | return failed; |
| 123 | } |
| 124 | |
| 125 | static int test_yaml_parse_strings(void) { |
| 126 | int failed = 0; |
| 127 | |
| 128 | struct { |
| 129 | const char *yaml; |
| 130 | const char *expected; |
| 131 | } test_cases[] = { |
| 132 | {"hello", "hello"}, |
| 133 | {"\"hello world\"", "hello world"}, |
| 134 | {"'hello world'", "hello world"}, |
| 135 | {"\"true\"", "true"}, |
| 136 | {"\"123\"", "123"}, |
| 137 | {"\"null\"", "null"}, |
| 138 | {"multi\\nline", "multi\\nline"}, |
| 139 | {"\"multi\\nline\"", "multi\nline"}, |
| 140 | {"\" spaces \"", " spaces "}, |
| 141 | {NULL, NULL} |
| 142 | }; |
| 143 | |
| 144 | for (int i = 0; test_cases[i].yaml; i++) { |
| 145 | BUFFER *error = buffer_create(0, NULL); |
| 146 | struct json_object *json = yaml_parse_string(test_cases[i].yaml, error, YAML2JSON_DEFAULT); |
| 147 | |
| 148 | if (!json || !json_object_is_type(json, json_type_string)) { |
| 149 | fprintf(stderr, "FAILED: test_yaml_parse_strings case %d: expected string for '%s'\n", |
| 150 | i, test_cases[i].yaml); |
| 151 | failed++; |
| 152 | } else if (strcmp(json_object_get_string(json), test_cases[i].expected) != 0) { |
| 153 | fprintf(stderr, "FAILED: test_yaml_parse_strings case %d: expected '%s', got '%s' for '%s'\n", |
| 154 | i, test_cases[i].expected, json_object_get_string(json), test_cases[i].yaml); |
| 155 | failed++; |
| 156 | } |
| 157 | |
| 158 | if (json) json_object_put(json); |
| 159 | buffer_free(error); |
| 160 | } |
| 161 | |
| 162 | return failed; |
| 163 | } |
| 164 | |
| 165 | static int test_yaml_parse_arrays(void) { |
| 166 | int failed = 0; |
| 167 | |
| 168 | // Try both flow and block style arrays |
| 169 | const char *yaml = "- 1\n- 2\n- three\n- true\n- null\n- 4.5"; |
| 170 | |
| 171 | BUFFER *error = buffer_create(0, NULL); |
| 172 | struct json_object *json = yaml_parse_string(yaml, error, YAML2JSON_DEFAULT); |
| 173 | |
| 174 | if (!json) { |
| 175 | fprintf(stderr, "FAILED: test_yaml_parse_arrays: json is NULL, error: %s\n", buffer_tostring(error)); |
| 176 | failed++; |
| 177 | goto cleanup; |
| 178 | } |
| 179 | |
| 180 | if (!json_object_is_type(json, json_type_array)) { |
| 181 | fprintf(stderr, "FAILED: test_yaml_parse_arrays: expected array but got type %u\n", |
| 182 | (unsigned int)json_object_get_type(json)); |
| 183 | failed++; |
| 184 | goto cleanup; |
| 185 | } |
| 186 | |
| 187 | if (json_object_array_length(json) != 6) { |
| 188 | fprintf(stderr, "FAILED: test_yaml_parse_arrays: expected 6 elements, got %zu\n", |
| 189 | json_object_array_length(json)); |
| 190 | failed++; |
| 191 | goto cleanup; |
| 192 | } |
| 193 | |
| 194 | // Check array elements |
| 195 | struct json_object *elem; |
| 196 | |
| 197 | elem = json_object_array_get_idx(json, 0); |
| 198 | if (!elem || !json_object_is_type(elem, json_type_int) || json_object_get_int64(elem) != 1) { |
| 199 | fprintf(stderr, "FAILED: test_yaml_parse_arrays: element 0 check failed\n"); |
| 200 | failed++; |
| 201 | } |
| 202 | |
| 203 | elem = json_object_array_get_idx(json, 2); |
| 204 | if (!elem || !json_object_is_type(elem, json_type_string) || |
| 205 | strcmp(json_object_get_string(elem), "three") != 0) { |
| 206 | fprintf(stderr, "FAILED: test_yaml_parse_arrays: element 2 check failed\n"); |
| 207 | failed++; |
| 208 | } |
| 209 | |
| 210 | elem = json_object_array_get_idx(json, 3); |
| 211 | if (!elem || !json_object_is_type(elem, json_type_boolean) || !json_object_get_boolean(elem)) { |
| 212 | fprintf(stderr, "FAILED: test_yaml_parse_arrays: element 3 check failed\n"); |
| 213 | failed++; |
| 214 | } |
| 215 | |
| 216 | elem = json_object_array_get_idx(json, 4); |
| 217 | if (elem != NULL) { |
| 218 | fprintf(stderr, "FAILED: test_yaml_parse_arrays: element 4 should be NULL (json-c represents null array elements as NULL)\n"); |
| 219 | failed++; |
| 220 | } |
| 221 | |
| 222 | cleanup: |
| 223 | if (json) json_object_put(json); |
| 224 | buffer_free(error); |
| 225 | |
| 226 | return failed; |
| 227 | } |
| 228 | |
| 229 | static int test_yaml_parse_objects(void) { |
| 230 | int failed = 0; |
| 231 | |
| 232 | const char *yaml = |
| 233 | "name: John Doe\n" |
| 234 | "age: 30\n" |
| 235 | "active: true\n" |
| 236 | "salary: 50000.50\n" |
| 237 | "address:\n" |
| 238 | " street: 123 Main St\n" |
| 239 | " city: Anytown\n" |
| 240 | "tags:\n" |
| 241 | " - developer\n" |
| 242 | " - team-lead\n"; |
| 243 | |
| 244 | BUFFER *error = buffer_create(0, NULL); |
| 245 | struct json_object *json = yaml_parse_string(yaml, error, YAML2JSON_DEFAULT); |
| 246 | |
| 247 | if (!json || !json_object_is_type(json, json_type_object)) { |
| 248 | fprintf(stderr, "FAILED: test_yaml_parse_objects: expected object\n"); |
| 249 | failed++; |
| 250 | goto cleanup; |
| 251 | } |
| 252 | |
| 253 | // Check object properties |
| 254 | struct json_object *prop; |
| 255 | |
| 256 | if (!json_object_object_get_ex(json, "name", &prop) || |
| 257 | !json_object_is_type(prop, json_type_string) || |
| 258 | strcmp(json_object_get_string(prop), "John Doe") != 0) { |
| 259 | fprintf(stderr, "FAILED: test_yaml_parse_objects: name property check failed\n"); |
| 260 | failed++; |
| 261 | } |
| 262 | |
| 263 | if (!json_object_object_get_ex(json, "age", &prop) || |
| 264 | !json_object_is_type(prop, json_type_int) || |
| 265 | json_object_get_int64(prop) != 30) { |
| 266 | fprintf(stderr, "FAILED: test_yaml_parse_objects: age property check failed\n"); |
| 267 | failed++; |
| 268 | } |
| 269 | |
| 270 | if (!json_object_object_get_ex(json, "active", &prop) || |
| 271 | !json_object_is_type(prop, json_type_boolean) || |
| 272 | !json_object_get_boolean(prop)) { |
| 273 | fprintf(stderr, "FAILED: test_yaml_parse_objects: active property check failed\n"); |
| 274 | failed++; |
| 275 | } |
| 276 | |
| 277 | // Check nested object |
| 278 | if (!json_object_object_get_ex(json, "address", &prop) || |
| 279 | !json_object_is_type(prop, json_type_object)) { |
| 280 | fprintf(stderr, "FAILED: test_yaml_parse_objects: address property check failed\n"); |
| 281 | failed++; |
| 282 | } else { |
| 283 | struct json_object *street; |
| 284 | if (!json_object_object_get_ex(prop, "street", &street) || |
| 285 | strcmp(json_object_get_string(street), "123 Main St") != 0) { |
| 286 | fprintf(stderr, "FAILED: test_yaml_parse_objects: street property check failed\n"); |
| 287 | failed++; |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | // Check array |
| 292 | if (!json_object_object_get_ex(json, "tags", &prop) || |
| 293 | !json_object_is_type(prop, json_type_array) || |
| 294 | json_object_array_length(prop) != 2) { |
| 295 | fprintf(stderr, "FAILED: test_yaml_parse_objects: tags property check failed\n"); |
| 296 | failed++; |
| 297 | } |
| 298 | |
| 299 | cleanup: |
| 300 | if (json) json_object_put(json); |
| 301 | buffer_free(error); |
| 302 | |
| 303 | return failed; |
| 304 | } |
| 305 | |
| 306 | static int test_yaml_generation(void) { |
| 307 | int failed = 0; |
| 308 | |
| 309 | // Create a more comprehensive JSON object |
| 310 | struct json_object *root = json_object_new_object(); |
| 311 | json_object_object_add(root, "name", json_object_new_string("Test")); |
| 312 | json_object_object_add(root, "version", json_object_new_int(1)); |
| 313 | json_object_object_add(root, "enabled", json_object_new_boolean(1)); |
| 314 | json_object_object_add(root, "pi", json_object_new_double(3.14159)); |
| 315 | // Test with NULL value |
| 316 | json_object_object_add(root, "nothing", NULL); |
| 317 | |
| 318 | struct json_object *array = json_object_new_array(); |
| 319 | json_object_array_add(array, json_object_new_string("item1")); |
| 320 | json_object_array_add(array, json_object_new_int(2)); |
| 321 | json_object_array_add(array, json_object_new_boolean(0)); |
| 322 | json_object_object_add(root, "items", array); |
| 323 | |
| 324 | struct json_object *nested = json_object_new_object(); |
| 325 | json_object_object_add(nested, "key", json_object_new_string("value")); |
| 326 | json_object_object_add(root, "nested", nested); |
| 327 | |
| 328 | // Generate YAML |
| 329 | BUFFER *output = buffer_create(0, NULL); |
| 330 | BUFFER *error = buffer_create(0, NULL); |
| 331 | |
| 332 | if (!yaml_generate_to_buffer(output, root, error)) { |
| 333 | fprintf(stderr, "FAILED: test_yaml_generation: failed to generate YAML: %s\n", |
| 334 | buffer_tostring(error)); |
| 335 | failed++; |
| 336 | goto cleanup; |
| 337 | } |
| 338 | |
| 339 | const char *yaml_str = buffer_tostring(output); |
| 340 | if (!yaml_str || strlen(yaml_str) == 0) { |
| 341 | fprintf(stderr, "FAILED: test_yaml_generation: generated empty YAML\n"); |
| 342 | failed++; |
| 343 | goto cleanup; |
| 344 | } |
| 345 | |
| 346 | // Parse the generated YAML back |
| 347 | buffer_flush(error); |
| 348 | struct json_object *parsed = yaml_parse_string(yaml_str, error, YAML2JSON_DEFAULT); |
| 349 | if (!parsed) { |
| 350 | const char *err_msg = buffer_tostring(error); |
| 351 | if (!err_msg || strlen(err_msg) == 0) { |
| 352 | err_msg = "(no error message but result is NULL)"; |
| 353 | } |
| 354 | fprintf(stderr, "FAILED: test_yaml_generation: failed to parse generated YAML: %s\nYAML was:\n%s\n", |
| 355 | err_msg, yaml_str); |
| 356 | failed++; |
| 357 | goto cleanup; |
| 358 | } |
| 359 | |
| 360 | // Verify the parsed object matches the original |
| 361 | struct json_object *prop; |
| 362 | |
| 363 | if (!json_object_object_get_ex(parsed, "name", &prop) || |
| 364 | strcmp(json_object_get_string(prop), "Test") != 0) { |
| 365 | fprintf(stderr, "FAILED: test_yaml_generation: name property mismatch\n"); |
| 366 | failed++; |
| 367 | } |
| 368 | |
| 369 | if (!json_object_object_get_ex(parsed, "version", &prop) || |
| 370 | json_object_get_int64(prop) != 1) { |
| 371 | fprintf(stderr, "FAILED: test_yaml_generation: version property mismatch\n"); |
| 372 | failed++; |
| 373 | } |
| 374 | |
| 375 | if (!json_object_object_get_ex(parsed, "enabled", &prop) || |
| 376 | !json_object_get_boolean(prop)) { |
| 377 | fprintf(stderr, "FAILED: test_yaml_generation: enabled property mismatch\n"); |
| 378 | failed++; |
| 379 | } |
| 380 | |
| 381 | if (!json_object_object_get_ex(parsed, "pi", &prop) || |
| 382 | !json_object_is_type(prop, json_type_double)) { |
| 383 | fprintf(stderr, "FAILED: test_yaml_generation: pi property mismatch\n"); |
| 384 | failed++; |
| 385 | } |
| 386 | |
| 387 | // Check NULL value - in json-c, JSON null is represented as C NULL |
| 388 | if (!json_object_object_get_ex(parsed, "nothing", &prop)) { |
| 389 | fprintf(stderr, "FAILED: test_yaml_generation: nothing property should exist\n"); |
| 390 | failed++; |
| 391 | } else if (prop != NULL) { |
| 392 | fprintf(stderr, "FAILED: test_yaml_generation: nothing property should be NULL\n"); |
| 393 | failed++; |
| 394 | } |
| 395 | |
| 396 | if (!json_object_object_get_ex(parsed, "items", &prop) || |
| 397 | !json_object_is_type(prop, json_type_array) || |
| 398 | json_object_array_length(prop) != 3) { |
| 399 | fprintf(stderr, "FAILED: test_yaml_generation: items property mismatch\n"); |
| 400 | failed++; |
| 401 | } |
| 402 | |
| 403 | if (!json_object_object_get_ex(parsed, "nested", &prop) || |
| 404 | !json_object_is_type(prop, json_type_object)) { |
| 405 | fprintf(stderr, "FAILED: test_yaml_generation: nested property mismatch\n"); |
| 406 | failed++; |
| 407 | } else { |
| 408 | struct json_object *nested_key; |
| 409 | if (!json_object_object_get_ex(prop, "key", &nested_key) || |
| 410 | strcmp(json_object_get_string(nested_key), "value") != 0) { |
| 411 | fprintf(stderr, "FAILED: test_yaml_generation: nested.key property mismatch\n"); |
| 412 | failed++; |
| 413 | } |
| 414 | } |
| 415 | |
| 416 | json_object_put(parsed); |
| 417 | |
| 418 | cleanup: |
| 419 | json_object_put(root); |
| 420 | buffer_free(output); |
| 421 | buffer_free(error); |
| 422 | |
| 423 | return failed; |
| 424 | } |
| 425 | |
| 426 | static int test_yaml_parse_errors(void) { |
| 427 | int failed = 0; |
| 428 | |
| 429 | const char *invalid_yaml[] = { |
| 430 | "[unclosed array", |
| 431 | "{ unclosed: object", |
| 432 | NULL |
| 433 | }; |
| 434 | |
| 435 | for (int i = 0; invalid_yaml[i]; i++) { |
| 436 | BUFFER *error = buffer_create(0, NULL); |
| 437 | struct json_object *json = yaml_parse_string(invalid_yaml[i], error, YAML2JSON_DEFAULT); |
| 438 | |
| 439 | if (json != NULL || buffer_strlen(error) == 0) { |
| 440 | fprintf(stderr, "FAILED: test_yaml_parse_errors case %d: expected parse error\n", i); |
| 441 | failed++; |
| 442 | } |
| 443 | |
| 444 | if (json) json_object_put(json); |
| 445 | buffer_free(error); |
| 446 | } |
| 447 | |
| 448 | return failed; |
| 449 | } |
| 450 | |
| 451 | static int test_yaml_file_operations(void) { |
| 452 | int failed = 0; |
| 453 | |
| 454 | const char *test_file = "/tmp/netdata_yaml_test.yaml"; |
| 455 | |
| 456 | // Create a test JSON object |
| 457 | struct json_object *root = json_object_new_object(); |
| 458 | json_object_object_add(root, "test", json_object_new_string("file operations")); |
| 459 | json_object_object_add(root, "number", json_object_new_int(42)); |
| 460 | |
| 461 | BUFFER *error = buffer_create(0, NULL); |
| 462 | |
| 463 | // Write to file |
| 464 | if (!yaml_generate_to_filename(test_file, root, error)) { |
| 465 | fprintf(stderr, "FAILED: test_yaml_file_operations: failed to write file: %s\n", |
| 466 | buffer_tostring(error)); |
| 467 | failed++; |
| 468 | goto cleanup; |
| 469 | } |
| 470 | |
| 471 | // Read from file |
| 472 | struct json_object *parsed = yaml_parse_filename(test_file, error, YAML2JSON_DEFAULT); |
| 473 | if (!parsed) { |
| 474 | fprintf(stderr, "FAILED: test_yaml_file_operations: failed to read file: %s\n", |
| 475 | buffer_tostring(error)); |
| 476 | failed++; |
| 477 | goto cleanup; |
| 478 | } |
| 479 | |
| 480 | // Verify content |
| 481 | struct json_object *prop; |
| 482 | if (!json_object_object_get_ex(parsed, "test", &prop) || |
| 483 | strcmp(json_object_get_string(prop), "file operations") != 0) { |
| 484 | fprintf(stderr, "FAILED: test_yaml_file_operations: test property mismatch\n"); |
| 485 | failed++; |
| 486 | } |
| 487 | |
| 488 | if (!json_object_object_get_ex(parsed, "number", &prop) || |
| 489 | json_object_get_int64(prop) != 42) { |
| 490 | fprintf(stderr, "FAILED: test_yaml_file_operations: number property mismatch\n"); |
| 491 | failed++; |
| 492 | } |
| 493 | |
| 494 | json_object_put(parsed); |
| 495 | |
| 496 | cleanup: |
| 497 | // Cleanup |
| 498 | unlink(test_file); |
| 499 | json_object_put(root); |
| 500 | buffer_free(error); |
| 501 | |
| 502 | return failed; |
| 503 | } |
| 504 | |
| 505 | static int test_yaml_edge_cases(void) { |
| 506 | int failed = 0; |
| 507 | |
| 508 | BUFFER *error = buffer_create(0, NULL); |
| 509 | |
| 510 | // Empty string |
| 511 | struct json_object *json = yaml_parse_string("", error, YAML2JSON_DEFAULT); |
| 512 | if (json != NULL) { |
| 513 | fprintf(stderr, "FAILED: test_yaml_edge_cases: empty string should return NULL\n"); |
| 514 | failed++; |
| 515 | json_object_put(json); |
| 516 | } |
| 517 | |
| 518 | // NULL input |
| 519 | buffer_flush(error); |
| 520 | json = yaml_parse_string(NULL, error, YAML2JSON_DEFAULT); |
| 521 | if (json != NULL || buffer_strlen(error) == 0) { |
| 522 | fprintf(stderr, "FAILED: test_yaml_edge_cases: NULL input should fail\n"); |
| 523 | failed++; |
| 524 | if (json) json_object_put(json); |
| 525 | } |
| 526 | |
| 527 | // Empty object |
| 528 | buffer_flush(error); |
| 529 | json = yaml_parse_string("{}", error, YAML2JSON_DEFAULT); |
| 530 | if (!json || !json_object_is_type(json, json_type_object) || |
| 531 | json_object_object_length(json) != 0) { |
| 532 | fprintf(stderr, "FAILED: test_yaml_edge_cases: empty object parse failed\n"); |
| 533 | failed++; |
| 534 | } |
| 535 | if (json) json_object_put(json); |
| 536 | |
| 537 | // Empty array |
| 538 | buffer_flush(error); |
| 539 | json = yaml_parse_string("[]", error, YAML2JSON_DEFAULT); |
| 540 | if (!json || !json_object_is_type(json, json_type_array) || |
| 541 | json_object_array_length(json) != 0) { |
| 542 | fprintf(stderr, "FAILED: test_yaml_edge_cases: empty array parse failed\n"); |
| 543 | failed++; |
| 544 | } |
| 545 | if (json) json_object_put(json); |
| 546 | |
| 547 | buffer_free(error); |
| 548 | |
| 549 | return failed; |
| 550 | } |
| 551 | |
| 552 | static int test_yaml_special_strings(void) { |
| 553 | int failed = 0; |
| 554 | |
| 555 | // Create JSON with special strings |
| 556 | struct json_object *root = json_object_new_object(); |
| 557 | json_object_object_add(root, "str_null", json_object_new_string("null")); |
| 558 | json_object_object_add(root, "str_true", json_object_new_string("true")); |
| 559 | json_object_object_add(root, "str_false", json_object_new_string("false")); |
| 560 | json_object_object_add(root, "str_yes", json_object_new_string("yes")); |
| 561 | json_object_object_add(root, "str_no", json_object_new_string("no")); |
| 562 | json_object_object_add(root, "str_on", json_object_new_string("on")); |
| 563 | json_object_object_add(root, "str_off", json_object_new_string("off")); |
| 564 | json_object_object_add(root, "str_spaces", json_object_new_string(" spaces ")); |
| 565 | json_object_object_add(root, "str_newline", json_object_new_string("line1\nline2")); |
| 566 | json_object_object_add(root, "str_empty", json_object_new_string("")); |
| 567 | |
| 568 | BUFFER *output = buffer_create(0, NULL); |
| 569 | BUFFER *error = buffer_create(0, NULL); |
| 570 | |
| 571 | // Generate YAML |
| 572 | if (!yaml_generate_to_buffer(output, root, error)) { |
| 573 | fprintf(stderr, "FAILED: test_yaml_special_strings: failed to generate YAML: %s\n", |
| 574 | buffer_tostring(error)); |
| 575 | failed++; |
| 576 | goto cleanup; |
| 577 | } |
| 578 | |
| 579 | // Parse back |
| 580 | struct json_object *parsed = yaml_parse_string(buffer_tostring(output), error, YAML2JSON_DEFAULT); |
| 581 | if (!parsed) { |
| 582 | fprintf(stderr, "FAILED: test_yaml_special_strings: failed to parse YAML: %s\n", |
| 583 | buffer_tostring(error)); |
| 584 | failed++; |
| 585 | goto cleanup; |
| 586 | } |
| 587 | |
| 588 | // Verify all strings are preserved correctly |
| 589 | struct json_object *prop; |
| 590 | |
| 591 | if (!json_object_object_get_ex(parsed, "str_null", &prop) || |
| 592 | !json_object_is_type(prop, json_type_string) || |
| 593 | strcmp(json_object_get_string(prop), "null") != 0) { |
| 594 | fprintf(stderr, "FAILED: test_yaml_special_strings: str_null mismatch\n"); |
| 595 | failed++; |
| 596 | } |
| 597 | |
| 598 | if (!json_object_object_get_ex(parsed, "str_true", &prop) || |
| 599 | !json_object_is_type(prop, json_type_string) || |
| 600 | strcmp(json_object_get_string(prop), "true") != 0) { |
| 601 | fprintf(stderr, "FAILED: test_yaml_special_strings: str_true mismatch\n"); |
| 602 | failed++; |
| 603 | } |
| 604 | |
| 605 | if (!json_object_object_get_ex(parsed, "str_spaces", &prop) || |
| 606 | !json_object_is_type(prop, json_type_string) || |
| 607 | strcmp(json_object_get_string(prop), " spaces ") != 0) { |
| 608 | fprintf(stderr, "FAILED: test_yaml_special_strings: str_spaces mismatch\n"); |
| 609 | failed++; |
| 610 | } |
| 611 | |
| 612 | if (!json_object_object_get_ex(parsed, "str_empty", &prop) || |
| 613 | !json_object_is_type(prop, json_type_string) || |
| 614 | strcmp(json_object_get_string(prop), "") != 0) { |
| 615 | fprintf(stderr, "FAILED: test_yaml_special_strings: str_empty mismatch\n"); |
| 616 | failed++; |
| 617 | } |
| 618 | |
| 619 | json_object_put(parsed); |
| 620 | |
| 621 | cleanup: |
| 622 | json_object_put(root); |
| 623 | buffer_free(output); |
| 624 | buffer_free(error); |
| 625 | |
| 626 | return failed; |
| 627 | } |
| 628 | |
| 629 | // Forward declaration |
| 630 | int yaml_comprehensive_unittest(void); |
| 631 | |
| 632 | int yaml_unittest(void) { |
| 633 | int passed = 0; |
| 634 | int failed = 0; |
| 635 | |
| 636 | printf("Starting YAML parser/generator unit tests\n"); |
| 637 | printf("=========================================\n\n"); |
| 638 | |
| 639 | // Run all tests |
| 640 | struct { |
| 641 | const char *name; |
| 642 | int (*test_func)(void); |
| 643 | } tests[] = { |
| 644 | {"test_yaml_parse_null", test_yaml_parse_null}, |
| 645 | {"test_yaml_parse_boolean", test_yaml_parse_boolean}, |
| 646 | {"test_yaml_parse_numbers", test_yaml_parse_numbers}, |
| 647 | {"test_yaml_parse_strings", test_yaml_parse_strings}, |
| 648 | {"test_yaml_parse_arrays", test_yaml_parse_arrays}, |
| 649 | {"test_yaml_parse_objects", test_yaml_parse_objects}, |
| 650 | {"test_yaml_generation", test_yaml_generation}, |
| 651 | {"test_yaml_parse_errors", test_yaml_parse_errors}, |
| 652 | {"test_yaml_file_operations", test_yaml_file_operations}, |
| 653 | {"test_yaml_edge_cases", test_yaml_edge_cases}, |
| 654 | {"test_yaml_special_strings", test_yaml_special_strings}, |
| 655 | {NULL, NULL} |
| 656 | }; |
| 657 | |
| 658 | for (int i = 0; tests[i].name; i++) { |
| 659 | printf("Running %s...\n", tests[i].name); |
| 660 | int test_failed = tests[i].test_func(); |
| 661 | if (test_failed == 0) { |
| 662 | printf(" PASSED\n"); |
| 663 | passed++; |
| 664 | } else { |
| 665 | printf(" FAILED (%d failures)\n", test_failed); |
| 666 | failed += test_failed; |
| 667 | } |
| 668 | } |
| 669 | |
| 670 | printf("\n=========================================\n"); |
| 671 | printf("YAML unit tests summary:\n"); |
| 672 | int total = 0; |
| 673 | for (int i = 0; tests[i].name; i++) total++; |
| 674 | printf(" Tests run: %d\n", total); |
| 675 | printf(" Passed: %d\n", passed); |
| 676 | printf(" Failed: %d\n", failed); |
| 677 | printf("=========================================\n"); |
| 678 | |
| 679 | // Run comprehensive tests |
| 680 | int comprehensive_failed = yaml_comprehensive_unittest(); |
| 681 | failed += comprehensive_failed; |
| 682 | |
| 683 | return failed; |
| 684 | } |