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