1#include "ctwm.h" 2 3#include <assert.h> 4#include <stdio.h> 5#include <string.h> 6#include <stdlib.h> 7#include <errno.h> 8 9#include "r_layout.h" 10#include "r_area_list.h" 11#include "r_area.h" 12 13static char * 14trim_prefix(char *str, char *prefix) 15{ 16 int prefix_len = strlen(prefix); 17 18 if(strncmp(str, prefix, prefix_len) == 0) { 19 str += prefix_len; 20 } 21 22 return str; 23} 24 25static char * 26trim_spaces(char *str) 27{ 28 char *end; 29 30 while(*str == ' ' || *str == '\t') { 31 str++; 32 } 33 34 end = str + strlen(str) - 1; 35 36 while(end >= str && (*end == '\n' || *end == ' ' || *end == '\t')) { 37 end--; 38 } 39 end[1] = '\0'; 40 41 // translate \t to space 42 for(end = str; *end; end++) 43 if(*end == '\t') { 44 *end = ' '; 45 } 46 47 return str; 48} 49 50static void 51print_rarea_list(RAreaList *list) 52{ 53 printf("[len=%d", list->len); 54 55 for(int i = 0 ; i < list->len ; i++) { 56 RArea area = list->areas[i]; 57 printf(" %dx%d+%d+%d", area.width, area.height, area.x, area.y); 58 } 59 60 printf("]"); 61} 62 63static int 64cmp_rarea_list(char *filename, int linenum, char *function, 65 RAreaList *got, RAreaList *expected) 66{ 67 if(got->len != expected->len 68 || memcmp(got->areas, expected->areas, got->len * sizeof(got->areas[0]))) { 69 printf("%s:%d: %s failed\n" 70 "\t got: ", filename, linenum, function); 71 print_rarea_list(got); 72 printf("\n" 73 "\texpected: "); 74 print_rarea_list(expected); 75 printf("\n"); 76 } 77 78 return 1; 79} 80 81static int 82extract_geometry(char *buf, char ***names, int *num_names, 83 unsigned int *x, unsigned int *y, 84 unsigned int *width, unsigned int *height) 85{ 86 char name[32]; 87 88 // {monitor_name}:{width}x{height}+{x}+{y} 89 if(sscanf(buf, "%31[^:]:%ux%u+%u+%u", name, width, height, x, y) == 5) { 90 if(names == NULL) { 91 *names = malloc(2 * sizeof(char *)); 92 } 93 else { 94 (*num_names)++; 95 *names = realloc(*names, (*num_names + 1) * sizeof(char *)); 96 } 97 98 if(*names == NULL) { 99 perror("{m,re}alloc failed"); 100 exit(1); 101 } 102 103 (*names)[*num_names] = strdup(name); 104 if((*names)[*num_names] == NULL) { 105 perror("strdup failed"); 106 exit(1); 107 } 108 } 109 // {width}x{height}+{x}+{y} 110 else if(sscanf(buf, "%ux%u+%u+%u", width, height, x, y) != 4) { 111 return 0; 112 } 113 114 return 1; 115} 116 117static RLayout * 118extract_layout(char *filename, int linenum, char *line) 119{ 120 char **names = NULL, *geom; 121 RAreaList *list; 122 int num_names = 0; 123 unsigned int width, height, x, y; 124 125 list = RAreaListNew(10, NULL); 126 names = NULL; 127 128 while((geom = strsep(&line, " ")) != NULL) { 129 if(geom[0] == '\0') { 130 continue; 131 } 132 133 switch(extract_geometry(geom, &names, &num_names, 134 &x, &y, &width, &height)) { 135 case 1: 136 break; 137 138 case 0: 139 fprintf(stderr, "%s:%d: layout unrecognized geometry (%s)\n", 140 filename, linenum, geom); 141 // fallthrough 142 default: 143 free(names); 144 return NULL; 145 } 146 147 RAreaListAdd(list, RAreaNewStatic((int)x, (int)y, (int)width, (int)height)); 148 } 149 150 return RLayoutSetMonitorsNames(RLayoutNew(list), names); 151} 152 153static RLayout * 154read_layout_file(FILE *file, char *filename) 155{ 156 char buf[128], *line, **names; 157 RAreaList *list; 158 RLayout *layout; 159 int num, num_names; 160 bool comment = false; 161 unsigned int width, height, x, y; 162 163 list = RAreaListNew(10, NULL); 164 names = NULL; 165 num_names = 0; 166 for(num = 1; fgets(buf, sizeof(buf), file) != NULL; num++) { 167 line = trim_spaces(buf); 168 169 // Multiline comments: =comment -> =end 170 if(comment) { 171 if(strcmp(line, "=end") == 0) { 172 comment = false; 173 } 174 continue; 175 } 176 177 if(strcmp(line, "=comment") == 0) { 178 comment = true; 179 continue; 180 } 181 182 if(line[0] == '#' || line[0] == '\0') { 183 continue; 184 } 185 186 if(strcmp(line, "__END__") == 0) { 187 break; 188 } 189 190 switch(extract_geometry(line, &names, &num_names, 191 &x, &y, &width, &height)) { 192 case 1: 193 break; 194 195 case 0: 196 fprintf(stderr, "%s:%d: layout unrecognized line (%s)\n", filename, num, line); 197 // fallthrough 198 default: 199 free(names); 200 return NULL; 201 } 202 203 RAreaListAdd(list, RAreaNewStatic((int)x, (int)y, (int)width, (int)height)); 204 } 205 206 layout = RLayoutSetMonitorsNames(RLayoutNew(list), names); 207 208 //RLayoutPrint(layout); 209 210 return layout; 211} 212 213static int 214read_test_from_file(char *filename) 215{ 216 char buf[1024], *cur_buf, sub_buf[1024], func_buf[32], *line; 217 FILE *file; 218 RLayout *layout = NULL; 219 RArea win = { 0 }; 220 int linenum, buf_size, expected1, expected2, errors; 221 bool comment = false; 222 unsigned int width, height, x, y; 223 224 file = fopen(filename, "r"); 225 if(file == NULL) { 226 fprintf(stderr, "Cannot open %s: %s\n", filename, strerror(errno)); 227 return 1; 228 } 229 230 errors = 0; 231 232 cur_buf = buf; 233 buf_size = sizeof(buf); 234 235 for(linenum = 1; fgets(cur_buf, buf_size, file) != NULL; linenum++) { 236 int last_chr_idx = strlen(cur_buf) - 1; 237 if(last_chr_idx >= 0) { 238 if(cur_buf[last_chr_idx] == '\n') { 239 last_chr_idx--; 240 } 241 242 if(last_chr_idx >= 0 && cur_buf[last_chr_idx] == '\\') { 243 cur_buf += last_chr_idx; 244 buf_size -= last_chr_idx; 245 246 if(buf_size > 0) { 247 continue; 248 } 249 250 fprintf(stderr, "%s:%d: line too long\n", filename, linenum); 251 errors++; 252 break; 253 } 254 } 255 256 cur_buf = buf; 257 buf_size = sizeof(buf); 258 259 line = trim_spaces(buf); 260 261 // Multiline comments: =comment -> =end 262 if(comment) { 263 if(strcmp(line, "=end") == 0) { 264 comment = false; 265 } 266 continue; 267 } 268 269 if(strcmp(line, "=comment") == 0) { 270 comment = true; 271 continue; 272 } 273 274 if(line[0] == '#' || line[0] == '\0') { 275 continue; 276 } 277 278 // layout FILENAME|area ... 279 if(sscanf(line, "layout %1023s", sub_buf) == 1) { 280 FILE *file_layout; 281 282 if((file_layout = fopen(sub_buf, "r")) != NULL) { 283 layout = read_layout_file(file_layout, sub_buf); 284 fclose(file_layout); 285 } 286 else if(errno == ENOENT) { 287 line = trim_spaces(&line[7]); 288 289 layout = extract_layout(filename, linenum, line); 290 } 291 292 if(layout != NULL) { 293 continue; 294 } 295 296 fprintf(stderr, "%s:%d: layout error\n", filename, linenum); 297 errors++; 298 break; 299 } 300 301 // Gotta have a layout by now, right? 302 assert(layout != NULL); 303 304 // check_horizontal_layout area ... 305 if(strncmp(line, "check_horizontal_layout ", 24) == 0) { 306 RLayout *check_layout; 307 308 line += 24; 309 line = trim_spaces(line); 310 311 check_layout = extract_layout(filename, linenum, line); 312 if(check_layout == NULL) { 313 break; 314 } 315 316 if(cmp_rarea_list(filename, linenum, 317 "check_horizontal_layout", 318 layout->horiz, check_layout->monitors)) { 319 continue; 320 } 321 break; 322 } 323 324 // check_vertical_layout area ... 325 if(strncmp(line, "check_vertical_layout ", 22) == 0) { 326 RLayout *check_layout; 327 328 line += 22; 329 line = trim_spaces(line); 330 331 check_layout = extract_layout(filename, linenum, line); 332 if(check_layout == NULL) { 333 break; 334 } 335 336 if(cmp_rarea_list(filename, linenum, 337 "check_vertical_layout", 338 layout->vert, check_layout->monitors)) { 339 continue; 340 } 341 break; 342 } 343 344 // window area 345 if(sscanf(line, "window %dx%d+%d+%d", &width, &height, &x, &y) == 4) { 346 win = RAreaNew((int)x, (int)y, (int)width, (int)height); 347 if(RAreaIsValid(&win)) { 348 continue; 349 } 350 351 fprintf(stderr, "%s:%d: bad window geometry\n", filename, linenum); 352 errors++; 353 break; 354 } 355 356 if(layout == NULL) { 357 fprintf(stderr, 358 "%s:%d: cannot continue as `layout ...' line not found\n", 359 filename, linenum); 360 errors++; 361 break; 362 } 363 364 if(!RAreaIsValid(&win)) { 365 fprintf(stderr, 366 "%s:%d: cannot continue as `window ...' line not found\n", 367 filename, linenum); 368 errors++; 369 break; 370 } 371 372 // Function area 373 // RLayoutFull area 374 // RLayoutFullHoriz area 375 // RLayoutFullVert area 376 // RLayoutFull1 area 377 // RLayoutFullHoriz1 area 378 // RLayoutFullVert1 area 379 if(sscanf(line, "%31s %dx%d+%d+%d", 380 func_buf, &width, &height, &x, &y) == 5) { 381 RArea got_area, expected_area; 382 char *function = trim_prefix(func_buf, "RLayoutFull"); 383 384 expected_area = RAreaNew((int)x, (int)y, (int)width, (int)height); 385 386 if(function[0] == '\0') { 387 got_area = RLayoutFull(layout, &win); 388 } 389 else if(strcmp(function, "Horiz") == 0) { 390 got_area = RLayoutFullHoriz(layout, &win); 391 } 392 else if(strcmp(function, "Vert") == 0) { 393 got_area = RLayoutFullVert(layout, &win); 394 } 395 else if(strcmp(function, "1") == 0) { 396 got_area = RLayoutFull1(layout, &win); 397 } 398 else if(strcmp(function, "Horiz1") == 0) { 399 got_area = RLayoutFullHoriz1(layout, &win); 400 } 401 else if(strcmp(function, "Vert1") == 0) { 402 got_area = RLayoutFullVert1(layout, &win); 403 } 404 else { 405 fprintf(stderr, "%s:%d: bad function name bound to area\n", 406 filename, linenum); 407 errors++; 408 break; 409 } 410 411 if(memcmp(&got_area, &expected_area, sizeof(got_area)) != 0) { 412 printf("%s:%d: %s failed\n" 413 "\t got: %dx%d+%d+%d\n" 414 "\texpected: %dx%d+%d+%d\n", 415 filename, linenum, func_buf, 416 got_area.width, got_area.height, got_area.x, got_area.y, 417 expected_area.width, expected_area.height, 418 expected_area.x, expected_area.y); 419 errors++; 420 } 421 } 422 // Function num1 num2 423 // RLayoutFindTopBottomEdges top bottom 424 // RLayoutFindLeftRightEdges left right 425 else if(sscanf(line, "%31s %d %d", 426 func_buf, &expected1, &expected2) == 3) { 427 int got1, got2; 428 char *function = trim_prefix(func_buf, "RLayoutFind"); 429 430 if(strcmp(function, "TopBottomEdges") == 0) { 431 RLayoutFindTopBottomEdges(layout, &win, &got1, &got2); 432 } 433 else if(strcmp(function, "LeftRightEdges") == 0) { 434 RLayoutFindLeftRightEdges(layout, &win, &got1, &got2); 435 } 436 else { 437 fprintf(stderr, 438 "%s:%d: bad function name bound to 2 expected results\n", 439 filename, linenum); 440 errors++; 441 break; 442 } 443 444 if(got1 != expected1 || got2 != expected2) { 445 printf("%s:%d: %s failed\n" 446 "\t got: (%d, %d)\n" 447 "\texpected: (%d, %d)\n", 448 filename, linenum, func_buf, 449 got1, got2, 450 expected1, expected2); 451 errors++; 452 } 453 } 454 // Function num 455 // RLayoutFindMonitorBottomEdge bottom 456 // RLayoutFindMonitorTopEdge top 457 // RLayoutFindMonitorLeftEdge left 458 // RLayoutFindMonitorRightEdge right 459 else if(sscanf(line, "%31s %d", func_buf, &expected1) == 2) { 460 int got; 461 char *function = trim_prefix(func_buf, "RLayoutFindMonitor"); 462 463 if(strcmp(function, "BottomEdge") == 0) { 464 got = RLayoutFindMonitorBottomEdge(layout, &win); 465 } 466 else if(strcmp(function, "TopEdge") == 0) { 467 got = RLayoutFindMonitorTopEdge(layout, &win); 468 } 469 else if(strcmp(function, "LeftEdge") == 0) { 470 got = RLayoutFindMonitorLeftEdge(layout, &win); 471 } 472 else if(strcmp(function, "RightEdge") == 0) { 473 got = RLayoutFindMonitorRightEdge(layout, &win); 474 } 475 else { 476 fprintf(stderr, 477 "%s:%d: bad function name bound to one expected result\n", 478 filename, linenum); 479 errors++; 480 break; 481 } 482 483 if(got != expected1) { 484 printf("%s:%d: %s failed\n" 485 "\t got: %d\n" 486 "\texpected: %d\n", 487 filename, linenum, func_buf, 488 got, 489 expected1); 490 errors++; 491 } 492 } 493 else { 494 fprintf(stderr, "%s:%d: test unrecognized line\n", filename, linenum); 495 errors++; 496 break; 497 } 498 } 499 500 fclose(file); 501 502 return errors; 503} 504 505int 506main(int argc, char **argv) 507{ 508 int i, error; 509 510 if(argc < 2) { 511 fprintf(stderr, "usage: %s TEST_FILE ...\n", argv[0]); 512 return 1; 513 } 514 515 error = 0; 516 for(i = 1; i < argc; i++) { 517 printf("Testing %s\n", argv[i]); 518 error = read_test_from_file(argv[i]) || error; 519 } 520 521 return error; 522} 523