diff options
Diffstat (limited to 'lesstest/run.c')
-rw-r--r-- | lesstest/run.c | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/lesstest/run.c b/lesstest/run.c new file mode 100644 index 000000000000..fcca179129f9 --- /dev/null +++ b/lesstest/run.c @@ -0,0 +1,227 @@ +#include <time.h> +#include <errno.h> +#include <setjmp.h> +#include <errno.h> +#include <signal.h> +#include <sys/wait.h> +#include "lesstest.h" + +extern int verbose; +extern int less_quit; +extern int details; +extern int err_only; +extern TermInfo terminfo; + +static pid_t less_pid; +static jmp_buf run_catch; + +static void set_signal(int signum, void (*handler)(int)) { + struct sigaction sa; + sa.sa_handler = handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(signum, &sa, NULL); +} + +static void child_handler(int signum) { + int status; + pid_t child = wait(&status); + if (verbose) fprintf(stderr, "child %d died, status 0x%x\n", child, status); + if (child == less_pid) { + if (verbose) fprintf(stderr, "less died\n"); + less_quit = 1; + } +} + +static void set_signal_handlers(int set) { + set_signal(SIGINT, set ? SIG_IGN : SIG_DFL); + set_signal(SIGQUIT, set ? SIG_IGN : SIG_DFL); + set_signal(SIGKILL, set ? SIG_IGN : SIG_DFL); + set_signal(SIGPIPE, set ? SIG_IGN : SIG_DFL); + set_signal(SIGCHLD, set ? child_handler : SIG_DFL); +} + +// Send a command char to a LessPipeline. +static void send_char(LessPipeline* pipeline, wchar ch) { + if (verbose) fprintf(stderr, "lt.send %lx\n", ch); + byte cbuf[UNICODE_MAX_BYTES]; + byte* cp = cbuf; + store_wchar(&cp, ch); + write(pipeline->less_in, cbuf, cp-cbuf); +} + +// Read the screen image from the lt_screen in a LessPipeline. +static int read_screen(LessPipeline* pipeline, byte* buf, int buflen) { + if (verbose) fprintf(stderr, "lt.gen: read screen\n"); + send_char(pipeline, LESS_DUMP_CHAR); + int rn = 0; + for (; rn <= buflen; ++rn) { + byte ch; + if (read(pipeline->screen_out, &ch, 1) != 1) + break; + if (ch == '\n') + break; + if (buf != NULL) buf[rn] = ch; + } + return rn; +} + +// Read screen image from a LessPipeline and display it. +static void read_and_display_screen(LessPipeline* pipeline) { + byte rbuf[MAX_SCREENBUF_SIZE]; + int rn = read_screen(pipeline, rbuf, sizeof(rbuf)); + if (rn == 0) return; + printf("%s", terminfo.clear_screen); + display_screen(rbuf, rn, pipeline->screen_width, pipeline->screen_height); + log_screen(rbuf, rn); +} + +// Is the screen image in a LessPipeline equal to a given buffer? +static int curr_screen_match(LessPipeline* pipeline, const byte* img, int imglen) { + byte curr[MAX_SCREENBUF_SIZE]; + int currlen = read_screen(pipeline, curr, sizeof(curr)); + if (currlen == imglen && memcmp(img, curr, imglen) == 0) + return 1; + if (details) { + fprintf(stderr, "lt: mismatch: expect:\n"); + display_screen_debug(img, imglen, pipeline->screen_width, pipeline->screen_height); + fprintf(stderr, "lt: got:\n"); + display_screen_debug(curr, currlen, pipeline->screen_width, pipeline->screen_height); + } + return 0; +} + +// Run an interactive lesstest session to create an lt file. +// Read individual chars from stdin and send them to a LessPipeline. +// After each char, read the LessPipeline screen and display it +// on the user's screen. +// Also log the char and the screen image in the lt file. +int run_interactive(char* const* argv, int argc, char* const* prog_envp) { + setup_term(); + char* const* envp = less_envp(prog_envp, 1); + LessPipeline* pipeline = create_less_pipeline(argv, argc, envp); + if (pipeline == NULL) + return 0; + less_pid = pipeline->less_pid; + const char* textfile = (pipeline->tempfile != NULL) ? pipeline->tempfile : argv[argc-1]; + if (!log_test_header(argv, argc, textfile)) { + destroy_less_pipeline(pipeline); + return 0; + } + set_signal_handlers(1); + less_quit = 0; + int ttyin = 0; // stdin + raw_mode(ttyin, 1); + printf("%s%s", terminfo.init_term, terminfo.enter_keypad); + read_and_display_screen(pipeline); + while (!less_quit) { + wchar ch = read_wchar(ttyin); + if (ch == terminfo.backspace_key) + ch = '\b'; + if (verbose) fprintf(stderr, "tty %c (%lx)\n", pr_ascii(ch), ch); + log_tty_char(ch); + send_char(pipeline, ch); + read_and_display_screen(pipeline); + } + log_test_footer(); + printf("%s%s%s", terminfo.clear_screen, terminfo.exit_keypad, terminfo.deinit_term); + raw_mode(ttyin, 0); + destroy_less_pipeline(pipeline); + set_signal_handlers(0); + return 1; +} + +// Run a test of less, as directed by an open lt file. +// Read a logged char and screen image from the lt file. +// Send the char to a LessPipeline, then read the LessPipeline screen image +// and compare it to the screen image from the lt file. +// Report an error if they differ. +static int run_test(TestSetup* setup, FILE* testfd) { + const char* setup_name = setup->argv[setup->argc-1]; + //fprintf(stderr, "RUN %s\n", setup_name); + LessPipeline* pipeline = create_less_pipeline(setup->argv, setup->argc, + less_envp(setup->env.env_list, 0)); + if (pipeline == NULL) + return 0; + less_quit = 0; + wchar last_char = 0; + int ok = 1; + int cmds = 0; + if (setjmp(run_catch)) { + fprintf(stderr, "\nINTR test interrupted\n"); + ok = 0; + } else { + set_signal_handlers(1); + (void) read_screen(pipeline, NULL, MAX_SCREENBUF_SIZE); // wait until less is running + while (!less_quit) { + char line[10000]; + int line_len = read_zline(testfd, line, sizeof(line)); + if (line_len < 0) + break; + if (line_len < 1) + continue; + switch (line[0]) { + case '+': + last_char = (wchar) strtol(line+1, NULL, 16); + send_char(pipeline, last_char); + ++cmds; + break; + case '=': + if (!curr_screen_match(pipeline, (byte*)line+1, line_len-1)) { + ok = 0; + less_quit = 1; + fprintf(stderr, "DIFF %s on cmd #%d (%c %lx)\n", + setup_name, cmds, pr_ascii(last_char), last_char); + } + break; + case 'Q': + less_quit = 1; + break; + case '\n': + case '!': + break; + default: + fprintf(stderr, "unrecognized char at start of \"%s\"\n", line); + return 0; + } + } + set_signal_handlers(0); + } + destroy_less_pipeline(pipeline); + if (!ok) + printf("FAIL: %s (%d steps)\n", setup_name, cmds); + else if (!err_only) + printf("PASS: %s (%d steps)\n", setup_name, cmds); + return ok; +} + +// Run a test of less, as directed by a named lt file. +// Should be run in an empty temp directory; +// it creates its own files in the current directory. +int run_testfile(const char* ltfile, const char* less) { + FILE* testfd = fopen(ltfile, "r"); + if (testfd == NULL) { + fprintf(stderr, "cannot open %s\n", ltfile); + return 0; + } + int tests = 0; + int fails = 0; + // This for loop is to handle multiple tests in one file. + for (;;) { + TestSetup* setup = read_test_setup(testfd, less); + if (setup == NULL) + break; + ++tests; + int ok = run_test(setup, testfd); + free_test_setup(setup); + if (!ok) ++fails; + } +#if 0 + fprintf(stderr, "DONE %d test%s", tests, tests==1?"":"s"); + if (tests > fails) fprintf(stderr, ", %d ok", tests-fails); + if (fails > 0) fprintf(stderr, ", %d failed", fails); + fprintf(stderr, "\n"); +#endif + fclose(testfd); + return (fails == 0); +} |