test-specs.c (12272B)
1 /* 2 Author: José Bollo <jobol@nonadev.net> 3 4 https://gitlab.com/jobol/mustach 5 6 SPDX-License-Identifier: ISC 7 */ 8 9 #ifndef _GNU_SOURCE 10 #define _GNU_SOURCE 11 #endif 12 13 #include <stdlib.h> 14 #include <stdio.h> 15 #include <sys/stat.h> 16 #include <unistd.h> 17 #include <fcntl.h> 18 #include <string.h> 19 #include <errno.h> 20 21 #include "mustach-wrap.h" 22 23 #define TEST_JSON_C 1 24 #define TEST_JANSSON 2 25 #define TEST_CJSON 3 26 27 static const char *errors[] = { 28 "??? unreferenced ???", 29 "system", 30 "unexpected end", 31 "empty tag", 32 "tag too long", 33 "bad separators", 34 "too depth", 35 "closing", 36 "bad unescape tag", 37 "invalid interface", 38 "item not found", 39 "partial not found" 40 }; 41 42 const char *mustach_error_string(int status) 43 { 44 return status >= 0 ? "no error" 45 : errors[status <= -(int)(sizeof errors / sizeof * errors) ? 0 : -status]; 46 } 47 48 static const char *errmsg = 0; 49 static int flags = 0; 50 static FILE *output = 0; 51 52 static void help(char *prog) 53 { 54 char *name = basename(prog); 55 #define STR(x) #x 56 printf("%s version %s\n", name, STR(VERSION)); 57 #undef STR 58 printf("usage: %s test-files...\n", name); 59 exit(0); 60 } 61 62 #if TEST == TEST_CJSON 63 64 static const size_t BLOCKSIZE = 8192; 65 66 static char *readfile(const char *filename, size_t *length) 67 { 68 int f; 69 struct stat s; 70 char *result; 71 size_t size, pos; 72 ssize_t rc; 73 74 result = NULL; 75 if (filename[0] == '-' && filename[1] == 0) 76 f = dup(0); 77 else 78 f = open(filename, O_RDONLY); 79 if (f < 0) { 80 fprintf(stderr, "Can't open file: %s\n", filename); 81 exit(1); 82 } 83 84 fstat(f, &s); 85 switch (s.st_mode & S_IFMT) { 86 case S_IFREG: 87 size = s.st_size; 88 break; 89 case S_IFSOCK: 90 case S_IFIFO: 91 size = BLOCKSIZE; 92 break; 93 default: 94 fprintf(stderr, "Bad file: %s\n", filename); 95 exit(1); 96 } 97 98 pos = 0; 99 result = malloc(size + 1); 100 do { 101 if (result == NULL) { 102 fprintf(stderr, "Out of memory\n"); 103 exit(1); 104 } 105 rc = read(f, &result[pos], (size - pos) + 1); 106 if (rc < 0) { 107 fprintf(stderr, "Error while reading %s\n", filename); 108 exit(1); 109 } 110 if (rc > 0) { 111 pos += (size_t)rc; 112 if (pos > size) { 113 size = pos + BLOCKSIZE; 114 result = realloc(result, size + 1); 115 } 116 } 117 } while(rc > 0); 118 119 close(f); 120 if (length != NULL) 121 *length = pos; 122 result[pos] = 0; 123 return result; 124 } 125 #endif 126 127 typedef struct { 128 unsigned nerror; 129 unsigned ndiffers; 130 unsigned nsuccess; 131 unsigned ninvalid; 132 } counters; 133 134 static int load_json(const char *filename); 135 static int process(counters *c); 136 static void close_json(); 137 static int get_partial(const char *name, struct mustach_sbuf *sbuf); 138 139 int main(int ac, char **av) 140 { 141 char *f; 142 char *prog = *av; 143 int s; 144 counters c; 145 146 (void)ac; /* unused */ 147 flags = Mustach_With_SingleDot | Mustach_With_IncPartial; 148 output = stdout; 149 mustach_wrap_get_partial = get_partial; 150 151 memset(&c, 0, sizeof c); 152 while (*++av) { 153 if (!strcmp(*av, "-h") || !strcmp(*av, "--help")) 154 help(prog); 155 f = (av[0][0] == '-' && !av[0][1]) ? "/dev/stdin" : av[0]; 156 fprintf(output, "\nloading %s\n", f); 157 s = load_json(f); 158 if (s < 0) { 159 fprintf(stderr, "error when loading %s!\n", f); 160 if(errmsg) 161 fprintf(stderr, " reason: %s\n", errmsg); 162 exit(1); 163 } 164 fprintf(output, "processing file %s\n", f); 165 s = process(&c); 166 if (s < 0) { 167 fprintf(stderr, "error bad test file %s!\n", f); 168 exit(1); 169 } 170 close_json(); 171 } 172 fprintf(output, "\nsummary:\n"); 173 if (c.ninvalid) 174 fprintf(output, " invalid %u\n", c.ninvalid); 175 fprintf(output, " error %u\n", c.nerror); 176 fprintf(output, " differ %u\n", c.ndiffers); 177 fprintf(output, " success %u\n", c.nsuccess); 178 if (c.nerror) 179 return 2; 180 if (c.ndiffers) 181 return 1; 182 return 0; 183 } 184 185 void emit(FILE *f, const char *s) 186 { 187 for(;;s++) { 188 switch(*s) { 189 case 0: return; 190 case '\\': fprintf(f, "\\\\"); break; 191 case '\t': fprintf(f, "\\t"); break; 192 case '\n': fprintf(f, "\\n"); break; 193 case '\r': fprintf(f, "\\r"); break; 194 default: fprintf(f, "%c", *s); break; 195 } 196 } 197 } 198 199 #if TEST == TEST_JSON_C 200 201 #include "mustach-json-c.h" 202 203 static struct json_object *o; 204 205 static struct json_object *partials; 206 static int get_partial(const char *name, struct mustach_sbuf *sbuf) 207 { 208 struct json_object *x; 209 if (partials == NULL || !json_object_object_get_ex(partials, name, &x)) 210 return MUSTACH_ERROR_PARTIAL_NOT_FOUND; 211 sbuf->value = json_object_get_string(x); 212 return MUSTACH_OK; 213 } 214 215 static int load_json(const char *filename) 216 { 217 o = json_object_from_file(filename); 218 #if JSON_C_VERSION_NUM >= 0x000D00 219 errmsg = json_util_get_last_err(); 220 if (errmsg != NULL) 221 return -1; 222 #endif 223 if (o == NULL) { 224 errmsg = "null json"; 225 return -1; 226 } 227 return 0; 228 } 229 static int process(counters *c) 230 { 231 const char *t, *e; 232 char *got; 233 unsigned i, n; 234 size_t length; 235 int s; 236 json_object *tests, *unit, *name, *desc, *data, *template, *expected; 237 238 if (!json_object_object_get_ex(o, "tests", &tests) || json_object_get_type(tests) != json_type_array) 239 return -1; 240 241 i = 0; 242 n = (unsigned)json_object_array_length(tests); 243 while (i < n) { 244 unit = json_object_array_get_idx(tests, i); 245 if (json_object_get_type(unit) != json_type_object 246 || !json_object_object_get_ex(unit, "name", &name) 247 || !json_object_object_get_ex(unit, "desc", &desc) 248 || !json_object_object_get_ex(unit, "data", &data) 249 || !json_object_object_get_ex(unit, "template", &template) 250 || !json_object_object_get_ex(unit, "expected", &expected) 251 || json_object_get_type(name) != json_type_string 252 || json_object_get_type(desc) != json_type_string 253 || json_object_get_type(template) != json_type_string 254 || json_object_get_type(expected) != json_type_string) { 255 fprintf(stderr, "invalid test %u\n", i); 256 c->ninvalid++; 257 } 258 else { 259 fprintf(output, "[%u] %s\n", i, json_object_get_string(name)); 260 fprintf(output, "\t%s\n", json_object_get_string(desc)); 261 if (!json_object_object_get_ex(unit, "partials", &partials)) 262 partials = NULL; 263 t = json_object_get_string(template); 264 e = json_object_get_string(expected); 265 s = mustach_json_c_mem(t, 0, data, flags, &got, &length); 266 if (s == 0 && strcmp(got, e) == 0) { 267 fprintf(output, "\t=> SUCCESS\n"); 268 c->nsuccess++; 269 } 270 else { 271 if (s < 0) { 272 fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s)); 273 c->nerror++; 274 } 275 else { 276 fprintf(output, "\t=> DIFFERS\n"); 277 c->ndiffers++; 278 } 279 if (partials) 280 fprintf(output, "\t.. PARTIALS[%s]\n", json_object_to_json_string_ext(partials, 0)); 281 fprintf(output, "\t.. DATA[%s]\n", json_object_to_json_string_ext(data, 0)); 282 fprintf(output, "\t.. TEMPLATE["); 283 emit(output, t); 284 fprintf(output, "]\n"); 285 fprintf(output, "\t.. EXPECTED["); 286 emit(output, e); 287 fprintf(output, "]\n"); 288 if (s == 0) { 289 fprintf(output, "\t.. GOT["); 290 emit(output, got); 291 fprintf(output, "]\n"); 292 } 293 } 294 free(got); 295 } 296 i++; 297 } 298 return 0; 299 } 300 static void close_json() 301 { 302 json_object_put(o); 303 } 304 305 #elif TEST == TEST_JANSSON 306 307 #include "mustach-jansson.h" 308 309 static json_t *o; 310 static json_error_t e; 311 312 static json_t *partials; 313 static int get_partial(const char *name, struct mustach_sbuf *sbuf) 314 { 315 json_t *x; 316 if (partials == NULL || !(x = json_object_get(partials, name))) 317 return MUSTACH_ERROR_PARTIAL_NOT_FOUND; 318 sbuf->value = json_string_value(x); 319 return MUSTACH_OK; 320 } 321 322 static int load_json(const char *filename) 323 { 324 o = json_load_file(filename, JSON_DECODE_ANY, &e); 325 if (o == NULL) { 326 errmsg = e.text; 327 return -1; 328 } 329 return 0; 330 } 331 static int process(counters *c) 332 { 333 const char *t, *e; 334 char *got, *tmp; 335 int i, n; 336 size_t length; 337 int s; 338 json_t *tests, *unit, *name, *desc, *data, *template, *expected; 339 340 tests = json_object_get(o, "tests"); 341 if (!tests || json_typeof(tests) != JSON_ARRAY) 342 return -1; 343 344 i = 0; 345 n = json_array_size(tests); 346 while (i < n) { 347 unit = json_array_get(tests, i); 348 if (!unit || json_typeof(unit) != JSON_OBJECT 349 || !(name = json_object_get(unit, "name")) 350 || !(desc = json_object_get(unit, "desc")) 351 || !(data = json_object_get(unit, "data")) 352 || !(template = json_object_get(unit, "template")) 353 || !(expected = json_object_get(unit, "expected")) 354 || json_typeof(name) != JSON_STRING 355 || json_typeof(desc) != JSON_STRING 356 || json_typeof(template) != JSON_STRING 357 || json_typeof(expected) != JSON_STRING) { 358 fprintf(stderr, "invalid test %u\n", i); 359 c->ninvalid++; 360 } 361 else { 362 fprintf(output, "[%u] %s\n", i, json_string_value(name)); 363 fprintf(output, "\t%s\n", json_string_value(desc)); 364 partials = json_object_get(unit, "partials"); 365 t = json_string_value(template); 366 e = json_string_value(expected); 367 s = mustach_jansson_mem(t, 0, data, flags, &got, &length); 368 if (s == 0 && strcmp(got, e) == 0) { 369 fprintf(output, "\t=> SUCCESS\n"); 370 c->nsuccess++; 371 } 372 else { 373 if (s < 0) { 374 fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s)); 375 c->nerror++; 376 } 377 else { 378 fprintf(output, "\t=> DIFFERS\n"); 379 c->ndiffers++; 380 } 381 if (partials) { 382 tmp = json_dumps(partials, JSON_ENCODE_ANY | JSON_COMPACT); 383 fprintf(output, "\t.. PARTIALS[%s]\n", tmp); 384 free(tmp); 385 } 386 tmp = json_dumps(data, JSON_ENCODE_ANY | JSON_COMPACT); 387 fprintf(output, "\t.. DATA[%s]\n", tmp); 388 free(tmp); 389 fprintf(output, "\t.. TEMPLATE["); 390 emit(output, t); 391 fprintf(output, "]\n"); 392 fprintf(output, "\t.. EXPECTED["); 393 emit(output, e); 394 fprintf(output, "]\n"); 395 if (s == 0) { 396 fprintf(output, "\t.. GOT["); 397 emit(output, got); 398 fprintf(output, "]\n"); 399 } 400 } 401 free(got); 402 } 403 i++; 404 } 405 return 0; 406 } 407 static void close_json() 408 { 409 json_decref(o); 410 } 411 412 #elif TEST == TEST_CJSON 413 414 #include "mustach-cjson.h" 415 416 static cJSON *o; 417 static cJSON *partials; 418 static int get_partial(const char *name, struct mustach_sbuf *sbuf) 419 { 420 cJSON *x; 421 if (partials == NULL || !(x = cJSON_GetObjectItemCaseSensitive(partials, name))) 422 return MUSTACH_ERROR_PARTIAL_NOT_FOUND; 423 sbuf->value = x->valuestring; 424 return MUSTACH_OK; 425 } 426 427 static int load_json(const char *filename) 428 { 429 char *t; 430 size_t length; 431 432 t = readfile(filename, &length); 433 o = t ? cJSON_ParseWithLength(t, length) : NULL; 434 free(t); 435 return -!o; 436 } 437 static int process(counters *c) 438 { 439 const char *t, *e; 440 char *got, *tmp; 441 int i, n; 442 size_t length; 443 int s; 444 cJSON *tests, *unit, *name, *desc, *data, *template, *expected; 445 446 tests = cJSON_GetObjectItemCaseSensitive(o, "tests"); 447 if (!tests || tests->type != cJSON_Array) 448 return -1; 449 450 i = 0; 451 n = cJSON_GetArraySize(tests); 452 while (i < n) { 453 unit = cJSON_GetArrayItem(tests, i); 454 if (!unit || unit->type != cJSON_Object 455 || !(name = cJSON_GetObjectItemCaseSensitive(unit, "name")) 456 || !(desc = cJSON_GetObjectItemCaseSensitive(unit, "desc")) 457 || !(data = cJSON_GetObjectItemCaseSensitive(unit, "data")) 458 || !(template = cJSON_GetObjectItemCaseSensitive(unit, "template")) 459 || !(expected = cJSON_GetObjectItemCaseSensitive(unit, "expected")) 460 || name->type != cJSON_String 461 || desc->type != cJSON_String 462 || template->type != cJSON_String 463 || expected->type != cJSON_String) { 464 fprintf(stderr, "invalid test %u\n", i); 465 c->ninvalid++; 466 } 467 else { 468 fprintf(output, "[%u] %s\n", i, name->valuestring); 469 fprintf(output, "\t%s\n", desc->valuestring); 470 partials = cJSON_GetObjectItemCaseSensitive(unit, "partials"); 471 t = template->valuestring; 472 e = expected->valuestring; 473 s = mustach_cJSON_mem(t, 0, data, flags, &got, &length); 474 if (s == 0 && strcmp(got, e) == 0) { 475 fprintf(output, "\t=> SUCCESS\n"); 476 c->nsuccess++; 477 } 478 else { 479 if (s < 0) { 480 fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s)); 481 c->nerror++; 482 } 483 else { 484 fprintf(output, "\t=> DIFFERS\n"); 485 c->ndiffers++; 486 } 487 if (partials) { 488 tmp = cJSON_PrintUnformatted(partials); 489 fprintf(output, "\t.. PARTIALS[%s]\n", tmp); 490 free(tmp); 491 } 492 tmp = cJSON_PrintUnformatted(data); 493 fprintf(output, "\t.. DATA[%s]\n", tmp); 494 free(tmp); 495 fprintf(output, "\t.. TEMPLATE["); 496 emit(output, t); 497 fprintf(output, "]\n"); 498 fprintf(output, "\t.. EXPECTED["); 499 emit(output, e); 500 fprintf(output, "]\n"); 501 if (s == 0) { 502 fprintf(output, "\t.. GOT["); 503 emit(output, got); 504 fprintf(output, "]\n"); 505 } 506 } 507 free(got); 508 } 509 i++; 510 } 511 return 0; 512 } 513 static void close_json() 514 { 515 cJSON_Delete(o); 516 } 517 518 #else 519 #error "no defined json library" 520 #endif