xref: /netbsd-src/external/bsd/less/dist/lesstest/run.c (revision e4a6e799a67c2028562d75b4e61407b22434aa36)
1 #include <time.h>
2 #include <errno.h>
3 #include <setjmp.h>
4 #include <errno.h>
5 #include <signal.h>
6 #include <sys/wait.h>
7 #include "lesstest.h"
8 
9 extern int verbose;
10 extern int less_quit;
11 extern int details;
12 extern int err_only;
13 extern TermInfo terminfo;
14 
15 static pid_t less_pid;
16 static jmp_buf run_catch;
17 
set_signal(int signum,void (* handler)(int))18 static void set_signal(int signum, void (*handler)(int)) {
19 	struct sigaction sa;
20 	sa.sa_handler = handler;
21 	sa.sa_flags = 0;
22 	sigemptyset(&sa.sa_mask);
23 	sigaction(signum, &sa, NULL);
24 }
25 
child_handler(int signum)26 static void child_handler(int signum) {
27 	int status;
28 	pid_t child = wait(&status);
29 	if (verbose) fprintf(stderr, "child %d died, status 0x%x\n", child, status);
30 	if (child == less_pid) {
31 		if (verbose) fprintf(stderr, "less died\n");
32 		less_quit = 1;
33 	}
34 }
35 
set_signal_handlers(int set)36 static void set_signal_handlers(int set) {
37 	set_signal(SIGINT,  set ? SIG_IGN : SIG_DFL);
38 	set_signal(SIGQUIT, set ? SIG_IGN : SIG_DFL);
39 	set_signal(SIGKILL, set ? SIG_IGN : SIG_DFL);
40 	set_signal(SIGPIPE, set ? SIG_IGN : SIG_DFL);
41 	set_signal(SIGCHLD, set ? child_handler : SIG_DFL);
42 }
43 
44 // Send a command char to a LessPipeline.
send_char(LessPipeline * pipeline,wchar ch)45 static void send_char(LessPipeline* pipeline, wchar ch) {
46 	if (verbose) fprintf(stderr, "lt.send %lx\n", ch);
47 	byte cbuf[UNICODE_MAX_BYTES];
48 	byte* cp = cbuf;
49 	store_wchar(&cp, ch);
50 	write(pipeline->less_in, cbuf, cp-cbuf);
51 }
52 
53 // Read the screen image from the lt_screen in a LessPipeline.
read_screen(LessPipeline * pipeline,byte * buf,int buflen)54 static int read_screen(LessPipeline* pipeline, byte* buf, int buflen) {
55 	if (verbose) fprintf(stderr, "lt.gen: read screen\n");
56 	send_char(pipeline, LESS_DUMP_CHAR);
57 	int rn = 0;
58 	for (; rn <= buflen; ++rn) {
59 		byte ch;
60 		if (read(pipeline->screen_out, &ch, 1) != 1)
61 			break;
62 		if (ch == '\n')
63 			break;
64 		if (buf != NULL) buf[rn] = ch;
65 	}
66 	return rn;
67 }
68 
69 // Read screen image from a LessPipeline and display it.
read_and_display_screen(LessPipeline * pipeline)70 static void read_and_display_screen(LessPipeline* pipeline) {
71 	byte rbuf[MAX_SCREENBUF_SIZE];
72 	int rn = read_screen(pipeline, rbuf, sizeof(rbuf));
73 	if (rn == 0) return;
74 	printf("%s", terminfo.clear_screen);
75 	display_screen(rbuf, rn, pipeline->screen_width, pipeline->screen_height);
76 	log_screen(rbuf, rn);
77 }
78 
79 // Is the screen image in a LessPipeline equal to a given buffer?
curr_screen_match(LessPipeline * pipeline,const byte * img,int imglen)80 static int curr_screen_match(LessPipeline* pipeline, const byte* img, int imglen) {
81 	byte curr[MAX_SCREENBUF_SIZE];
82 	int currlen = read_screen(pipeline, curr, sizeof(curr));
83 	if (currlen == imglen && memcmp(img, curr, imglen) == 0)
84 		return 1;
85 	if (details) {
86 		fprintf(stderr, "lt: mismatch: expect:\n");
87 		display_screen_debug(img, imglen, pipeline->screen_width, pipeline->screen_height);
88 		fprintf(stderr, "lt: got:\n");
89 		display_screen_debug(curr, currlen, pipeline->screen_width, pipeline->screen_height);
90 	}
91 	return 0;
92 }
93 
94 // Run an interactive lesstest session to create an lt file.
95 // Read individual chars from stdin and send them to a LessPipeline.
96 // After each char, read the LessPipeline screen and display it
97 // on the user's screen.
98 // Also log the char and the screen image in the lt file.
run_interactive(char * const * argv,int argc,char * const * prog_envp)99 int run_interactive(char* const* argv, int argc, char* const* prog_envp) {
100 	setup_term();
101 	char* const* envp = less_envp(prog_envp, 1);
102 	LessPipeline* pipeline = create_less_pipeline(argv, argc, envp);
103 	if (pipeline == NULL)
104 		return 0;
105 	less_pid = pipeline->less_pid;
106 	const char* textfile = (pipeline->tempfile != NULL) ? pipeline->tempfile : argv[argc-1];
107 	if (!log_test_header(argv, argc, textfile)) {
108 		destroy_less_pipeline(pipeline);
109 		return 0;
110 	}
111 	set_signal_handlers(1);
112 	less_quit = 0;
113 	int ttyin = 0; // stdin
114 	raw_mode(ttyin, 1);
115 	printf("%s%s", terminfo.init_term, terminfo.enter_keypad);
116 	read_and_display_screen(pipeline);
117 	while (!less_quit) {
118 		wchar ch = read_wchar(ttyin);
119 		if (ch == terminfo.backspace_key)
120 			ch = '\b';
121 		if (verbose) fprintf(stderr, "tty %c (%lx)\n", pr_ascii(ch), ch);
122 		log_tty_char(ch);
123 		send_char(pipeline, ch);
124 		read_and_display_screen(pipeline);
125 	}
126 	log_test_footer();
127 	printf("%s%s%s", terminfo.clear_screen, terminfo.exit_keypad, terminfo.deinit_term);
128 	raw_mode(ttyin, 0);
129 	destroy_less_pipeline(pipeline);
130 	set_signal_handlers(0);
131 	return 1;
132 }
133 
134 // Run a test of less, as directed by an open lt file.
135 // Read a logged char and screen image from the lt file.
136 // Send the char to a LessPipeline, then read the LessPipeline screen image
137 // and compare it to the screen image from the lt file.
138 // Report an error if they differ.
run_test(TestSetup * setup,FILE * testfd)139 static int run_test(TestSetup* setup, FILE* testfd) {
140 	const char* setup_name = setup->argv[setup->argc-1];
141 	//fprintf(stderr, "RUN  %s\n", setup_name);
142 	LessPipeline* pipeline = create_less_pipeline(setup->argv, setup->argc,
143 			less_envp(setup->env.env_list, 0));
144 	if (pipeline == NULL)
145 		return 0;
146 	less_quit = 0;
147 	wchar last_char = 0;
148 	int ok = 1;
149 	int cmds = 0;
150 	if (setjmp(run_catch)) {
151 		fprintf(stderr, "\nINTR test interrupted\n");
152 		ok = 0;
153 	} else {
154 		set_signal_handlers(1);
155 		(void) read_screen(pipeline, NULL, MAX_SCREENBUF_SIZE); // wait until less is running
156 		while (!less_quit) {
157 			char line[10000];
158 			int line_len = read_zline(testfd, line, sizeof(line));
159 			if (line_len < 0)
160 				break;
161 			if (line_len < 1)
162 				continue;
163 			switch (line[0]) {
164 			case '+':
165 				last_char = (wchar) strtol(line+1, NULL, 16);
166 				send_char(pipeline, last_char);
167 				++cmds;
168 				break;
169 			case '=':
170 				if (!curr_screen_match(pipeline, (byte*)line+1, line_len-1)) {
171 					ok = 0;
172 					less_quit = 1;
173 					fprintf(stderr, "DIFF %s on cmd #%d (%c %lx)\n",
174 						setup_name, cmds, pr_ascii(last_char), last_char);
175 				}
176 				break;
177 			case 'Q':
178 				less_quit = 1;
179 				break;
180 			case '\n':
181 			case '!':
182 				break;
183 			default:
184 				fprintf(stderr, "unrecognized char at start of \"%s\"\n", line);
185 				return 0;
186 			}
187 		}
188 		set_signal_handlers(0);
189 	}
190 	destroy_less_pipeline(pipeline);
191 	if (!ok)
192 		printf("FAIL: %s (%d steps)\n", setup_name, cmds);
193 	else if (!err_only)
194 		printf("PASS: %s (%d steps)\n", setup_name, cmds);
195 	return ok;
196 }
197 
198 // Run a test of less, as directed by a named lt file.
199 // Should be run in an empty temp directory;
200 // it creates its own files in the current directory.
run_testfile(const char * ltfile,const char * less)201 int run_testfile(const char* ltfile, const char* less) {
202 	FILE* testfd = fopen(ltfile, "r");
203 	if (testfd == NULL) {
204 		fprintf(stderr, "cannot open %s\n", ltfile);
205 		return 0;
206 	}
207 	int tests = 0;
208 	int fails = 0;
209 	// This for loop is to handle multiple tests in one file.
210 	for (;;) {
211 		TestSetup* setup = read_test_setup(testfd, less);
212 		if (setup == NULL)
213 			break;
214 		++tests;
215 		int ok = run_test(setup, testfd);
216 		free_test_setup(setup);
217 		if (!ok) ++fails;
218 	}
219 #if 0
220 	fprintf(stderr, "DONE %d test%s", tests, tests==1?"":"s");
221 	if (tests > fails)  fprintf(stderr, ", %d ok",  tests-fails);
222 	if (fails > 0)      fprintf(stderr, ", %d failed", fails);
223 	fprintf(stderr, "\n");
224 #endif
225 	fclose(testfd);
226 	return (fails == 0);
227 }
228