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 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 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 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. 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. 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. 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? 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. 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. 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. 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