From 009f59b0ee0a39fdffb4f89a63ed8351bb801268 Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 18:58:22 -0300 Subject: [PATCH 01/15] testsuite: Update mini65 to full E: editor emulation Update the mini65 submodule and build the new editor module. Add atedit.c to the test build source list, and make editor tests depend on the IDE binaries. Full editor mode is now the default, which changes E: semantics in existing tests: - CLS homes the cursor to row 0 / left margin. The test harness pins LMARGN to 0, so stmt-cls now reports the plot at (0,0). - After E: input, the editor moves the cursor to the next line at the left margin, whereas the old stream mode left COLCRS after the "?" prompt. Update the two INPUT expectations accordingly. All 83 non-editor tests pass. --- tests.mak | 3 +++ testsuite/mini65 | 2 +- testsuite/tests/fp-input.chk | 2 +- testsuite/tests/stmt-cls.chk | 2 +- testsuite/tests/stmt-input.chk | 10 +++++----- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tests.mak b/tests.mak index 94870ce..d1a7af1 100644 --- a/tests.mak +++ b/tests.mak @@ -26,6 +26,7 @@ MINI65_SRC=\ atari.c\ ataridos.c\ atcio.c\ + atedit.c\ atsio.c\ dosfname.c\ hw.c\ @@ -37,6 +38,8 @@ TESTS := $(sort $(wildcard testsuite/tests/*.chk)) # The tests need to be rerun if any of this files change: TESTS_DEPS=\ + build/bin/fb.xex\ + build/bin/fbi.xex\ build/bin/fbc.xex\ build/bin/fbci.xex\ build/bin/fastbasic$(HOST_EXT)\ diff --git a/testsuite/mini65 b/testsuite/mini65 index 30b85d1..8d20189 160000 --- a/testsuite/mini65 +++ b/testsuite/mini65 @@ -1 +1 @@ -Subproject commit 30b85d1ee940b546ca7b5737be371ff6c7f99ed1 +Subproject commit 8d20189a87e9e23b70b2a991adbfecfa723f2e79 diff --git a/testsuite/tests/fp-input.chk b/testsuite/tests/fp-input.chk index 1bae856..f3df58c 100644 --- a/testsuite/tests/fp-input.chk +++ b/testsuite/tests/fp-input.chk @@ -6,6 +6,6 @@ Input: . Output: Start -?1 1 +?1 1 1 2 ?18 diff --git a/testsuite/tests/stmt-cls.chk b/testsuite/tests/stmt-cls.chk index 08f6f4c..c38c20e 100644 --- a/testsuite/tests/stmt-cls.chk +++ b/testsuite/tests/stmt-cls.chk @@ -3,5 +3,5 @@ Test: run Output: Start SCREEN: set graphics 1 with text window -}SCREEN: plot 1,1 color 125 +}SCREEN: plot 0,0 color 125 Ok diff --git a/testsuite/tests/stmt-input.chk b/testsuite/tests/stmt-input.chk index 722fa4f..1db502f 100644 --- a/testsuite/tests/stmt-input.chk +++ b/testsuite/tests/stmt-input.chk @@ -11,11 +11,11 @@ hello . Output: Start -?1 1234 +?1 1234 1 2345 Prompt: 1 3456 -?18 0 -?1 -1 -?18 0 -?1 hello +?18 0 +?1 -1 +?18 0 +?1 hello ?136 From 7eebfbd0a52832fed4da249d57156b21e496732e Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 19:01:26 -0300 Subject: [PATCH 02/15] fbtest: Add editor test mode with explicit document fixtures Editor tests are defined by their .chk file and need no paired .bas source. The initial editor buffer is declared with a Document: key: Document: none empty HELP.TXT (default) Document: inline section, terminated by "." Document: ASCII fixture from the test directory Document: is rejected for non-editor tests. Each editor run gets its own DOS root, build/tests//, and all generated files stay under build/. The directory is created by fbtest and checked files are removed before the run. The harness also gains: - readable Input notation (^X, , \xHH, UTF-8 ATASCII glyphs); - editor-only trimming of the final Input EOL, so normal run tests keep their exact input bytes; - Screen: assertions, compared byte-exactly against the emulated E: screen buffer; - direct use of atari_screen_get(), which now includes the visible cursor glyph, instead of XORing the cursor cell again; - build-only FB_DUMP_SCREEN and FB_DUMP_OUTPUT debug dumps; - input EOF termination for editor scripts. Add the first editor tests: line split and two screen tests. The status line now correctly fills all 40 columns, so the expected first row gains its last inverse-video cell. --- tests.mak | 5 +- testsuite/src/fbtest.c | 790 +++++++++++++++++++++++++++-- testsuite/tests/ed-split.chk | 35 ++ testsuite/tests/screen-basic.chk | 36 ++ testsuite/tests/screen-initial.chk | 34 ++ 5 files changed, 864 insertions(+), 36 deletions(-) create mode 100644 testsuite/tests/ed-split.chk create mode 100644 testsuite/tests/screen-basic.chk create mode 100644 testsuite/tests/screen-initial.chk diff --git a/tests.mak b/tests.mak index d1a7af1..031d8e0 100644 --- a/tests.mak +++ b/tests.mak @@ -61,7 +61,10 @@ RUNTEST_OBJS=build/obj/tests/fbtest.o $(MINI65_SRC:%.c=build/obj/tests/%.o) .PHONY: test test: $(TESTS_STAMP) $(RUNTEST) -build/tests/%.stamp: testsuite/tests/%.chk testsuite/tests/%.bas $(RUNTEST) $(TESTS_DEPS) | build/tests +.SECONDEXPANSION: +build/tests/%.stamp: testsuite/tests/%.chk $$(wildcard testsuite/tests/$$*.bas) \ + $(wildcard testsuite/tests/fixtures/*) \ + $(RUNTEST) $(TESTS_DEPS) | build/tests $(Q)$(RUNTEST) $< @touch $@ diff --git a/testsuite/src/fbtest.c b/testsuite/src/fbtest.c index 2e49031..3ca8ba7 100644 --- a/testsuite/src/fbtest.c +++ b/testsuite/src/fbtest.c @@ -21,13 +21,17 @@ #define _GNU_SOURCE // for asprintf #include "atari.h" #include "sim65.h" +#include "atcio.h" #include #include #include #include +#include #include #ifdef _WIN32 +# include +# define MKDIR(path) _mkdir(path) # define PATH_SEP "\\" // Implement missing "strndup" static char *strndup(const char *s, size_t n) @@ -38,6 +42,7 @@ static char *strndup(const char *s, size_t n) return ret; }; #else +# define MKDIR(path) mkdir(path, 0777) # define PATH_SEP "/" #endif @@ -45,6 +50,8 @@ static char *strndup(const char *s, size_t n) static int verbose; static const char *fb_atari_comp_int = "build/bin/fbci.xex"; static const char *fb_atari_comp_fp = "build/bin/fbc.xex"; +static const char *fb_atari_ide_int = "build/bin/fbi.xex"; +static const char *fb_atari_ide_fp = "build/bin/fb.xex"; static const char *fb_compiler = "build" PATH_SEP "bin" PATH_SEP "fastbasic"; static const char *ca65_path = "build" PATH_SEP "bin" PATH_SEP "ca65"; static const char *ld65_path = "build" PATH_SEP "bin" PATH_SEP "ld65"; @@ -69,6 +76,9 @@ static const char *output_dir = "build/tests"; static size_t str_out_pos, str_out_len, str_in_pos, str_in_len; static char *str_out; static const char *str_in; +static sim65 str_s; +static int str_stop_on_eof; +static int input_eof; static void str_put_char(int c) { if (c == 0x9b) @@ -83,14 +93,76 @@ static int str_get_char(void) { if (str_in_pos < str_in_len) return 0xFF & str_in[str_in_pos++]; - else - return EOF; + // Input exhausted + input_eof = 1; + if (str_stop_on_eof) + sim65_set_cycle_limit(str_s, 1); + return EOF; +} + +// ATASCII→UTF-8 table for visual screen diagnostics. +// Adapted from conv-atascii-utf8.c — indexed by ATASCII byte value. +static const char *const atascii_utf8[256] = { + "♥", "├", "🮇", "┘", "┤", "┐", "╱", "╲", + "◢", "▗", "◣", "▝", "▘", "🮂", "▂", "▖", + "♣", "┌", "─", "┼", "●", "▄", "▎", "┬", + "┴", "▌", "└", "␛", "↑", "↓", "←", "→", + " ", "!", "\"", "#", "$", "%", "&", "'", + "(", ")", "*", "+", ",", "-", ".", "/", + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", ":", ";", "<", "=", ">", "?", + "@", "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "[", "\\", "]", "^", "_", + "♦", "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "♠", "|", "⇱", "◀", "▶", + "♥", "┣", "▊", "┛", "┫", "┓", "╱", "╲", + "◤", "▛", "◥", "▙", "▟", "▆", "🮅", "▜", + "♣", "┏", "━", "╋", "●", "▀", "🮊", "┳", + "┻", "▐", "┗", "\n", "⇑", "⇓", "⇐", "⇒", + "█", "!", "\"", "#", "$", "%", "&", "'", + "(", ")", "*", "+", ",", "-", ".", "/", + "𝟬", "𝟭", "𝟮", "𝟯", "𝟰", "𝟱", "𝟲", "𝟳", + "𝟴", "𝟵", ":", ";", "<", "=", ">", "?", + "@", "𝗔", "𝗕", "𝗖", "𝗗", "𝗘", "𝗙", "𝗚", + "𝗛", "𝗜", "𝗝", "𝗞", "𝗟", "𝗠", "𝗡", "𝗢", + "𝗣", "𝗤", "𝗥", "𝗦", "𝗧", "𝗨", "𝗩", "𝗪", + "𝗫", "𝗬", "𝗭", "[", "\\", "]", "^", "_", + "♦", "𝗮", "𝗯", "𝗰", "𝗱", "𝗲", "𝗳", "𝗴", + "𝗵", "𝗶", "𝗷", "𝗸", "𝗹", "𝗺", "𝗻", "𝗼", + "𝗽", "𝗾", "𝗿", "𝘀", "𝘁", "𝘂", "𝘃", "𝘄", + "𝘅", "𝘆", "𝘇", "♠", "|", "⇱", "◀", "▶" +}; + +// Print one screen row as UTF-8 with ANSI inverse-video escapes. +// atari_screen_get() already contains the visible cursor glyph, so the +// character must not be inverted again here. +static void print_screen_row_utf8(FILE *fp, int row) +{ + int inv = 0; + for (int c = 0; c < 40; c++) + { + int ch = atari_screen_get(row, c); + int is_inv = ch & 0x80; + if (is_inv != inv) + { + fputs(inv ? "\033[27m" : "\033[7m", fp); + inv = is_inv; + } + fputs(atascii_utf8[ch & 0x7F], fp); + } + if (inv) + fputs("\033[27m", fp); } // Runs atari XEX file capturing the output static int run_atari_prog(const char *progname, char *output, size_t *output_len, const char *input, size_t input_len, uint64_t max_cycles, - int is_rom, const char *cmdline) + int is_rom, const char *cmdline, int fix_margins, + const char *dos_root) { // Init input/output str_in = input; @@ -100,11 +172,17 @@ static int run_atari_prog(const char *progname, char *output, size_t *output_len str_out_pos = 0; str_out_len = *output_len - 1; memset(str_out, 0, *output_len); + input_eof = 0; // Init emulator sim65 s = sim65_new(); if (!s) return -1; + // NOTE: never enable full instruction tracing here, a single run can + // log billions of lines and fill the disk. // sim65_set_debug(s, sim65_debug_trace); + if (getenv("FBTEST_DEBUG")) + // Log CIO and device activity, useful to debug editor scripts + sim65_set_debug(s, sim65_debug_messages); emu_options opts = { .get_char = str_get_char, .put_char = str_put_char, .flags = atari_opt_cycletime }; @@ -114,18 +192,27 @@ static int run_atari_prog(const char *progname, char *output, size_t *output_len if (cmdline) atari_dos_add_cmdline(s, cmdline); // Set DOS path - atari_dos_set_root(s, output_dir); - // Use 0 as left-margin value, to normalize results - unsigned char val = 0; - sim65_add_data_ram(s, 0x52, &val, 1); // LMARGN - sim65_add_data_ram(s, 0x55, &val, 1); // COLCRS + atari_dos_set_root(s, dos_root ? dos_root : output_dir); + // Use 0 as left-margin value, to normalize results. This is not + // done when running the IDE, that expects the default margins. + if (fix_margins) + { + unsigned char val = 0; + sim65_add_data_ram(s, 0x52, &val, 1); // LMARGN + sim65_add_data_ram(s, 0x55, &val, 1); // COLCRS + } sim65_set_cycle_limit(s, max_cycles); + str_s = s; enum sim65_error e; if( is_rom ) e = atari_rom_load(s, 0xA000, progname); else e = atari_xex_load(s, progname, 1); + str_s = NULL; + // Cycle limit hit after input exhausted is the normal editor exit path + if (e == sim65_err_cycle_limit && str_stop_on_eof && input_eof) + e = sim65_err_none; if (e == sim65_err_user) { fprintf(stderr, "%s: error reading XEX/ROM file\n", progname); @@ -153,6 +240,30 @@ static int run_atari_prog(const char *progname, char *output, size_t *output_len return x; } +// Checks that the captured output matches the expected output +static int check_output(const char *fname, const char *expected_out, const char *out) +{ + if (!strcmp(expected_out, out)) + return 0; + size_t l = 0; + for (l = 0; out[l] == expected_out[l] && out[l]; l++); + + size_t l1 = l > 20 ? l - 20 : 0; + size_t l2 = l + 20; + fprintf(stderr, "%s: output does not match:\n", fname); + fprintf(stderr, "expected: "); + for(const char *x = expected_out + l1; x<(expected_out+l2) && *x; x++) + putc(*x > 31 && *x < 127 ? *x : '.', stderr); + fprintf(stderr, "\ngot: "); + for(const char *x = out + l1; x<(out+l2) && *x; x++) + putc(*x > 31 && *x < 127 ? *x : '.', stderr); + fprintf(stderr, "\n "); + for(size_t x = l1; x 20 ? l - 20 : 0; - size_t l2 = l + 20; - fprintf(stderr, "%s: output does not match:\n", fname); - fprintf(stderr, "expected: "); - for(const char *x = expected_out + l1; x<(expected_out+l2) && *x; x++) - putc(*x > 31 && *x < 127 ? *x : '.', stderr); - fprintf(stderr, "\ngot: "); - for(const char *x = out + l1; x<(out+l2) && *x; x++) - putc(*x > 31 && *x < 127 ? *x : '.', stderr); - fprintf(stderr, "\n "); - for(size_t x = l1; x 0) fprintf(stderr,"%s: unexpected stack value.\n", fname); @@ -193,6 +285,40 @@ int run_test_xex(const char *fname, const char *input, const char *expected_out, return e; } +// Copies a file from the test directory into a DOS root, converting to +// ATASCII. The destination is always under build/. +static int atascii_convert(const char *infile, const char *outfile); +static int copy_fixture_to(const char *srcdir, const char *name, const char *dst) +{ + char *src; + if (asprintf(&src, "%s/%s", srcdir, name) < 0) + return -1; + int e = atascii_convert(src, dst); + free(src); + return e; +} + +static int dos_copy_file(const char *srcdir, const char *name, const char *dstdir) +{ + char *dst; + if (asprintf(&dst, "%s/%s", dstdir, name) < 0) + return -1; + int e = copy_fixture_to(srcdir, name, dst); + free(dst); + return e; +} + +// Removes a file from a directory, ignoring errors. +static void unlink_in(const char *dir, const char *name) +{ + char *path; + if (asprintf(&path, "%s/%s", dir, name) >= 0) + { + unlink(path); + free(path); + } +} + // Converts ASCII file to ATASCII static int atascii_convert(const char *infile, const char *outfile) { @@ -237,7 +363,7 @@ static int compile_native(const char *atbname, const char *xexname, const char * sprintf(cmd, "%s %s", atbname, xexname); else sprintf(cmd, "%s %s", atbname + l, xexname + l); - int e = run_atari_prog(compiler, out, &len, 0, 0, MAX_FPC_CYCLES, 0, cmd); + int e = run_atari_prog(compiler, out, &len, 0, 0, MAX_FPC_CYCLES, 0, cmd, 1, NULL); if (e) fprintf(stderr, "%s: can't execute compiler.\n", compiler); @@ -429,9 +555,157 @@ enum tests { test_int = 4, test_cross = 8, test_native = 16, - test_compile_error = 32 + test_compile_error = 32, + test_editor = 64 +}; + +// Initial document specification for editor tests. +enum document_kind { + doc_default = 0, // no Document: key, start with an empty file + doc_none, + doc_inline, + doc_file }; +// Expands control-key, named-key, and UTF-8 notation in an input buffer. +// ^X (0x41-0x5A) -> Ctrl-A..Z ($01-$1A) +// -> single byte from the Atari special-character table +// \xHH -> hex byte +// UTF-8 multi-byte sequences -> ATASCII byte from atascii_utf8 table +// All other bytes pass through literally. +// Returns a malloc'ed buffer; caller must free the original. +static char *expand_notation(char *input, size_t *len_out) +{ + static const struct { const char *name; int code; } keys[] = { + { "ESC", 0x1B }, { "UP", 0x1C }, { "DOWN", 0x1D }, + { "LEFT", 0x1E }, { "RIGHT", 0x1F }, { "CLS", 0x7D }, + { "BACKSP", 0x7E }, { "TAB", 0x7F }, { "DELLN", 0x9C }, + { "INSLN", 0x9D }, { "DELTAB", 0x9E }, { "SETTAB", 0x9F }, + { "BELL", 0xFD }, { "DEL", 0xFE }, { "INS", 0xFF }, + { NULL, 0 } + }; + size_t in_len = strlen(input); + char *out = malloc(in_len + 1); + size_t oi = 0; + for (size_t i = 0; i < in_len; ) + { + unsigned char c = (unsigned char)input[i]; + if (c == '^' && i + 1 < in_len) + { + unsigned char next = (unsigned char)input[i + 1]; + if (next >= 'A' && next <= 'Z') + { + out[oi++] = next - 'A' + 1; + i += 2; + continue; + } + if (next >= 'a' && next <= 'z') + { + out[oi++] = next - 'a' + 1; + i += 2; + continue; + } + } + if (c == '<') + { + size_t j = i + 1; + while (j < in_len && input[j] != '>') + j++; + if (j < in_len) + { + size_t nlen = j - i - 1; + for (int k = 0; keys[k].name; k++) + { + if (strlen(keys[k].name) == nlen && + !strncasecmp(input + i + 1, keys[k].name, nlen)) + { + out[oi++] = (unsigned char)keys[k].code; + i = j + 1; + goto next; + } + } + } + } + if (c == '\\' && i + 3 < in_len && input[i + 1] == 'x') + { + int hi = -1, lo = -1; + unsigned char h = (unsigned char)input[i + 2]; + unsigned char l = (unsigned char)input[i + 3]; + if (h >= '0' && h <= '9') hi = h - '0'; + else if (h >= 'A' && h <= 'F') hi = h - 'A' + 10; + else if (h >= 'a' && h <= 'f') hi = h - 'a' + 10; + if (l >= '0' && l <= '9') lo = l - '0'; + else if (l >= 'A' && l <= 'F') lo = l - 'A' + 10; + else if (l >= 'a' && l <= 'f') lo = l - 'a' + 10; + if (hi >= 0 && lo >= 0) + { + out[oi++] = (unsigned char)(hi * 16 + lo); + i += 4; + continue; + } + } + // UTF-8: scan atascii_utf8 table for a match at current position. + if (c >= 0xC0) + { + for (int k = 0; k < 256; k++) + { + size_t ulen = strlen(atascii_utf8[k]); + if (i + ulen <= in_len && + !memcmp(input + i, atascii_utf8[k], ulen)) + { + out[oi++] = (unsigned char)k; + i += ulen; + goto next; + } + } + } + out[oi++] = c; + i++; + next:; + } + out[oi] = 0; + *len_out = oi; + free(input); + return out; +} + +// Reads a section of lines, terminated by a lone ".", converting newlines +// to ATASCII EOL. Returns a malloc'ed buffer and its size, or NULL if the +// section is unterminated. +static char *read_section(FILE *f, int *line, size_t *len_out) +{ + size_t cap = 4096, size = 0; + char *buf = calloc(cap, 1), lbuf[256]; + while (fgets(lbuf, sizeof(lbuf)-1, f)) + { + (*line)++; + if (!strcmp(lbuf, ".") || !strcmp(lbuf, ".\n")) + { + buf[size] = 0; + *len_out = size; + return buf; + } + size_t l = strlen(lbuf); + if (l && lbuf[l-1] == '\n') + lbuf[l-1] = 0x9B; + while (size + l + 1 > cap) + { + cap *= 2; + char *nb = realloc(buf, cap); + if (!nb) + { + free(buf); + return NULL; + } + buf = nb; + } + memcpy(buf + size, lbuf, l); + size += l; + } + free(buf); + return NULL; +} + // Runs one test from the test-file int fbtest(const char *fname) { @@ -454,6 +728,30 @@ int fbtest(const char *fname) * The expected output, up to the end of the file. */ char *name = 0, *expected_out = 0, *input_buf = 0, *error_data = 0; + char *files[16]; + int nfiles = 0; + // Initial editor document + int doc_kind = doc_default, doc_set = 0; + char *doc_filename = 0, *doc_data = 0; + size_t doc_len = 0; + size_t input_len = 0; + // Expected content of files written during an editor session + struct + { + char *name; + char *data; + size_t len; + } checks[16]; + int nchecks = 0; + // Names of files that must exist after the run + char *exists[16]; + int nexists = 0; + // Substrings that must appear in the captured output stream + char *expects[8]; + int nexpects = 0; + // Expected screen content (24 rows of up to 40 cols) + char *expected_screen[24]; + int screen_lines = 0; int error_pos_line = 0, error_pos_column = 0; int test = 0, n = 0, line = 0; uint64_t max_cycles = 20000000; @@ -464,7 +762,7 @@ int fbtest(const char *fname) if ( !*lbuf || *lbuf == '\n' ) continue; - char key[16], buf[256]; + char key[48], buf[256]; n = sscanf(lbuf, "%15[^:]: %255[^\n\r]", key, buf); if (n < 1) { @@ -490,6 +788,9 @@ int fbtest(const char *fname) test = test & ~test_cross; else if (!strcasecmp(tok, "compile")) test = test_cross | test_native | test_fp | test_int; + else if (!strcasecmp(tok, "editor")) + // Runs the IDE; the initial buffer comes from Document: + test = test_editor | test_fp; else if (!strcasecmp(tok, "error")) test |= test_compile_error; else @@ -522,6 +823,150 @@ int fbtest(const char *fname) return -1; } } + else if (!strcasecmp(key, "document")) + { + // Initial editor document, only valid for editor tests. + // Document: none start with an empty HELP.TXT + // Document: install an ASCII fixture + // Document: read the inline section below + if (doc_set) + { + fprintf(stderr, "%s:%d: more than one DOCUMENT entry.\n", fname, line); + return -1; + } + doc_set = 1; + if (n < 2 || !*buf) + { + doc_kind = doc_inline; + doc_data = read_section(f, &line, &doc_len); + if (!doc_data) + { + fprintf(stderr, "%s:%d: unfinished DOCUMENT section.\n", fname, line); + return -1; + } + } + else if (!strcasecmp(buf, "none")) + doc_kind = doc_none; + else + { + doc_kind = doc_file; + doc_filename = strdup(buf); + } + } + else if (!strcasecmp(key, "files")) + { + // Files to copy into the DOS root before the run + while ( 0 != fgets(lbuf, sizeof(lbuf)-1, f) ) + { + line ++; + size_t l = strlen(lbuf); + while (l && (lbuf[l-1] == '\n' || lbuf[l-1] == ' ' || lbuf[l-1] == '\t')) + lbuf[--l] = 0; + if (!strcmp(lbuf, ".")) + break; + if (!l || nfiles >= 16) + { + fprintf(stderr, "%s:%d: error, invalid FILES entry\n", fname, line); + return -1; + } + files[nfiles++] = strdup(lbuf); + } + if (feof(f)) + { + fprintf(stderr, "%s:%d: unfinished FILES section.\n", fname, line); + return -1; + } + } + else if (!strcasecmp(key, "check")) + { + // Compare a file written during the session against this content + if (n < 2 || !*buf) + { + fprintf(stderr, "%s:%d: error, missing file name in CHECK\n", fname, line); + return -1; + } + if (nchecks >= 16) + { + fprintf(stderr, "%s:%d: error, too many CHECK sections.\n", fname, line); + return -1; + } + checks[nchecks].name = strdup(buf); + checks[nchecks].data = read_section(f, &line, &checks[nchecks].len); + if (!checks[nchecks].data) + { + fprintf(stderr, "%s:%d: unfinished CHECK section.\n", fname, line); + return -1; + } + nchecks++; + } + else if (!strcasecmp(key, "exists")) + { + if (n < 2 || !*buf) + { + fprintf(stderr, "%s:%d: error, missing file name in EXISTS\n", fname, line); + return -1; + } + if (nexists >= 16) + { + fprintf(stderr, "%s:%d: error, too many EXISTS entries.\n", fname, line); + return -1; + } + exists[nexists++] = strdup(buf); + } + else if (!strcasecmp(key, "expect-out")) + { + if (n < 2 || !*buf) + { + fprintf(stderr, "%s:%d: error, missing text in EXPECT-OUT\n", fname, line); + return -1; + } + if (nexpects >= 8) + { + fprintf(stderr, "%s:%d: error, too many EXPECT-OUT entries.\n", fname, line); + return -1; + } + expects[nexpects++] = strdup(buf); + } + else if (!strcasecmp(key, "screen")) + { + if (screen_lines) + { + fprintf(stderr, "%s:%d: more than one SCREEN section.\n", fname, line); + return -1; + } + if (n > 1 && *buf) + { + fprintf(stderr, "%s:%d: error, extra characters in SCREEN '%s'\n", fname, line, buf); + return -1; + } + while ( 0 != fgets(lbuf, sizeof(lbuf)-1, f) ) + { + line++; + if ( !strcmp(lbuf, ".") || !strcmp(lbuf, ".\n") ) + break; + if (screen_lines >= 24) + { + fprintf(stderr, "%s:%d: error, too many SCREEN lines (max 24).\n", fname, line); + return -1; + } + size_t l = strlen(lbuf); + // Trim trailing newline + if (l && lbuf[l-1] == '\n') + lbuf[--l] = 0; + // Trim trailing spaces + while (l && lbuf[l-1] == ' ') + lbuf[--l] = 0; + // Expand notation + size_t elen; + char *line_buf = expand_notation(strdup(lbuf), &elen); + expected_screen[screen_lines++] = line_buf; + } + if (feof(f)) + { + fprintf(stderr, "%s:%d: unfinished SCREEN section.\n", fname, line); + return -1; + } + } else if (!strcasecmp(key, "input")) { size_t input_size = 0; @@ -557,6 +1002,7 @@ int fbtest(const char *fname) input_size += l; } input_buf[input_size] = 0; + input_len = input_size; if (feof(f)) { fprintf(stderr, "%s:%d: unfinished INPUT section.\n", fname, line); @@ -581,7 +1027,8 @@ int fbtest(const char *fname) free(input_buf); return -1; } - else if(len < 1) + // Editor tests compare the screen and files, not the output + else if (len < 1 && !(test & test_editor)) { fprintf(stderr, "%s: error, no output.\n", fname); free(input_buf); @@ -606,10 +1053,29 @@ int fbtest(const char *fname) } if (!name) name = "unnamed"; + if (doc_set && !(test & test_editor)) + { + fprintf(stderr, "%s: error, DOCUMENT is only valid for editor tests\n", fname); + return -1; + } if (!expected_out) expected_out = strdup(""); if (!input_buf) + { input_buf = strdup(""); + input_len = 0; + } + // Expand ^X, , \xHH notation after the whole file is parsed, so + // header keys may appear in any order. In editor tests, the final EOL + // of the Input section is a section separator, not a RETURN keystroke. + { + size_t expanded_len; + input_buf = expand_notation(input_buf, &expanded_len); + input_len = expanded_len; + } + if ((test & test_editor) && input_len && + (unsigned char)input_buf[input_len - 1] == 0x9B) + input_len--; if (error_data && !(test & test_compile_error)) { fprintf(stderr, "%s: error data but no compile error expected\n", fname); @@ -633,6 +1099,10 @@ int fbtest(const char *fname) else tag_name = tag_name + 1; + // srcdir: directory containing the test files + char *srcdir = (tag_name != base_name) ? + strndup(fname, tag_name - base_name - 1) : strdup("."); + // basname: input basic file name char *basname = source_fname(base_name, "bas"); // atbname: BASIC file converted to ATASCII @@ -648,8 +1118,10 @@ int fbtest(const char *fname) // objname: Object file name char *objname = build_fname(tag_name, "o"); - // Generate ATASCII file - atascii_convert(basname, atbname); + // Generate ATASCII file for normal (non-editor) tests. Editor tests + // use the Document: section instead of the paired .bas file. + if (!(test & test_editor)) + atascii_convert(basname, atbname); // Ok, do tests int test_ok = 0; @@ -657,6 +1129,238 @@ int fbtest(const char *fname) do { + if (0 != (test & test_editor)) + { + const char *ide = (test & test_int) ? fb_atari_ide_int : fb_atari_ide_fp; + if (verbose) + fprintf(stderr, "%s: run editor '%s'\n", fname, ide); + + // Each editor test gets its own DOS root, always under build/. + MKDIR(output_dir); + char *test_root; + if (asprintf(&test_root, "%s/%s", output_dir, tag_name) < 0) + exit(1); + MKDIR(test_root); + char *help_txt; + if (asprintf(&help_txt, "%s/HELP.TXT", test_root) < 0) + exit(1); + + // Setup the initial document and extra files in the DOS root. + int e = 0; + unlink(help_txt); + for (int i = 0; i < nfiles; i++) + unlink_in(test_root, files[i]); + for (int i = 0; i < nchecks; i++) + unlink_in(test_root, checks[i].name); + for (int i = 0; i < nexists; i++) + unlink_in(test_root, exists[i]); + + if (doc_kind == doc_file) + { + if (copy_fixture_to(srcdir, doc_filename, help_txt)) + { + fprintf(stderr, "%s: can't install document '%s'.\n", + fname, doc_filename); + e = -1; + } + } + else + { + FILE *fh = fopen(help_txt, "wb"); + if (!fh) + { + fprintf(stderr, "%s: can't create '%s'.\n", fname, help_txt); + e = -1; + } + else + { + if (doc_kind == doc_inline && doc_len) + { + // The newline before the section terminator is not + // part of the document. Add an empty line before the + // terminator to keep a trailing EOL. + size_t len = doc_len; + if ((unsigned char)doc_data[len - 1] == 0x9B) + len--; + if (fwrite(doc_data, 1, len, fh) != len) + e = -1; + } + fclose(fh); + } + } + for (int i = 0; !e && i < nfiles; i++) + { + unlink_in(test_root, files[i]); + if (dos_copy_file(srcdir, files[i], test_root)) + { + fprintf(stderr, "%s: can't install file '%s'.\n", + fname, files[i]); + e = -1; + } + } + + // Run the IDE with the keystroke script + size_t len = 65536; + char *out = calloc(len + 1, 1); + if (!e) + { + str_stop_on_eof = 1; + e = run_atari_prog(ide, out, &len, input_buf, input_len, + max_cycles, 0, 0, 0, test_root); + str_stop_on_eof = 0; + } + // Cursor state is exposed for diagnostics only: the screen + // accessor already includes the visible cursor glyph. + int cur_row, cur_col; + atario_cursor_get(&cur_row, &cur_col); + + // Dump final E: screen state as raw ATASCII bytes for hex analysis. + if (getenv("FB_DUMP_SCREEN")) + { + char *dump; + if (asprintf(&dump, "%s/%s.screen", output_dir, tag_name) >= 0) + { + FILE *fd = fopen(dump, "wb"); + if (fd) + { + for (int r = 0; r < 24; r++) + { + for (int c = 0; c < 40; c++) + putc(atari_screen_get(r, c), fd); + putc('\n', fd); + } + fclose(fd); + } + free(dump); + } + } + if (!e && screen_lines) + { + // Compare expected screen against the actual screen buffer. + // atari_screen_get() includes the cursor glyph, so it must + // not be XORed again here. + for (int r = 0; !e && r < screen_lines; r++) + { + char actual[41]; + for (int c = 0; c < 40; c++) + actual[c] = atari_screen_get(r, c); + actual[40] = 0; + int alen = 40; + while (alen > 0 && actual[alen - 1] == ' ') + alen--; + actual[alen] = 0; + if (strcmp(expected_screen[r], actual)) + { + fprintf(stderr, "%s: SCREEN row %d mismatch:\n", fname, r); + fprintf(stderr, " expected: '%s'\n", expected_screen[r]); + fprintf(stderr, " actual: '%s'\n", actual); + fprintf(stderr, " cursor: %d,%d\n", cur_row, cur_col); + fprintf(stderr, " screen: "); + print_screen_row_utf8(stderr, r); + fprintf(stderr, "\n"); + e = -1; + } + } + } + if (!e && *expected_out) + e = check_output(fname, expected_out, out); + + // Dump captured output stream, to debug editor test scripts. + if (getenv("FB_DUMP_OUTPUT")) + { + char *dumpo; + if (asprintf(&dumpo, "%s/%s.output", output_dir, tag_name) >= 0) + { + FILE *fd = fopen(dumpo, "wb"); + if (fd) + { + fwrite(out, 1, strlen(out), fd); + fclose(fd); + } + free(dumpo); + } + } + // Check expected substrings of the captured output stream + for (int i = 0; !e && i < nexpects; i++) + { + if (!strstr(out, expects[i])) + { + fprintf(stderr, "%s: '%s' not found on output.\n", + fname, expects[i]); + e = -1; + } + } + + // Check the contents of files written during the session + for (int i = 0; !e && i < nchecks; i++) + { + char *path; + if (asprintf(&path, "%s/%s", test_root, checks[i].name) < 0) + exit(1); + FILE *fc = fopen(path, "rb"); + free(path); + char *got = 0; + size_t rlen = 0; + if (fc) + { + fseek(fc, 0, SEEK_END); + long sz = ftell(fc); + fseek(fc, 0, SEEK_SET); + got = calloc(sz + 1, 1); + rlen = fread(got, 1, sz, fc); + fclose(fc); + } + if (!got) + { + fprintf(stderr, "%s: can't open checked file '%s'.\n", + fname, checks[i].name); + e = -1; + } + else if (rlen != checks[i].len || + memcmp(got, checks[i].data, rlen)) + { + size_t d = 0; + while (d < rlen && d < checks[i].len && + got[d] == checks[i].data[d]) + d++; + fprintf(stderr, + "%s: file '%s' does not match: %lu bytes vs %lu " + "expected, first difference at byte %lu.\n", + fname, checks[i].name, (unsigned long)rlen, + (unsigned long)checks[i].len, (unsigned long)d); + e = -1; + } + free(got); + } + + // Check that files were created during the session + for (int i = 0; !e && i < nexists; i++) + { + char *path; + if (asprintf(&path, "%s/%s", test_root, exists[i]) < 0) + exit(1); + FILE *fx = fopen(path, "rb"); + free(path); + if (!fx) + { + fprintf(stderr, "%s: file '%s' not created.\n", + fname, exists[i]); + e = -1; + } + else + fclose(fx); + } + + if (e > 0) + fprintf(stderr, "%s: unexpected stack value.\n", fname); + else if (e < 0) + fprintf(stderr, "%s: error on editor execution.\n", fname); + free(out); + free(help_txt); + free(test_root); + if (e) + break; + } if (0 != (test & test_native) && 0 != (test & test_fp)) { if (verbose) @@ -779,6 +1483,8 @@ int fbtest(const char *fname) free(input_buf); free(expected_out); free(name); + free(doc_filename); + free(doc_data); free(basname); free(atbname); free(romname); @@ -786,6 +1492,20 @@ int fbtest(const char *fname) free(asmname); free(objname); free(base_name); + free(srcdir); + for (int i = 0; i < nfiles; i++) + free(files[i]); + for (int i = 0; i < nchecks; i++) + { + free(checks[i].name); + free(checks[i].data); + } + for (int i = 0; i < nexists; i++) + free(exists[i]); + for (int i = 0; i < nexpects; i++) + free(expects[i]); + for (int i = 0; i < screen_lines; i++) + free(expected_screen[i]); return !test_ok; } diff --git a/testsuite/tests/ed-split.chk b/testsuite/tests/ed-split.chk new file mode 100644 index 0000000..e4961dc --- /dev/null +++ b/testsuite/tests/ed-split.chk @@ -0,0 +1,35 @@ +Name: ed split +Test: editor +Max-cycles: 800000000 +Document: +' split test +. +Input: +abcdefghij\x9b +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\x92\x92\x92\x92\x92\x92\x92 +abcdefg +\xe8ij' split test +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/screen-basic.chk b/testsuite/tests/screen-basic.chk new file mode 100644 index 0000000..748cfdf --- /dev/null +++ b/testsuite/tests/screen-basic.chk @@ -0,0 +1,36 @@ +Name: screen basic +Test: editor +Max-cycles: 800000000 +Document: +' screen test + +. +Input: +HELLO WORLD +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +HELLO WORLD\xa7 screen test +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/screen-initial.chk b/testsuite/tests/screen-initial.chk new file mode 100644 index 0000000..585635d --- /dev/null +++ b/testsuite/tests/screen-initial.chk @@ -0,0 +1,34 @@ +Name: screen initial +Test: editor +Max-cycles: 800000000 +Document: +screen initial test +. +Input: +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xf3creen initial test +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. From 30ca8686b4ed4b1c9e09d05d33916be87e478e2a Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 19:01:41 -0300 Subject: [PATCH 03/15] testsuite: Add editor assert-plumbing test Exercise the editor output and file assertions: type a program at the top of the initial document, compile and run it with Ctrl-R, save the XEX with Ctrl-W, and quit discarding changes. Check for the compiler "Parsing" message, the program marker on the captured output, and the saved T2.XEX in the per-test DOS root. --- testsuite/tests/ed-assert.chk | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 testsuite/tests/ed-assert.chk diff --git a/testsuite/tests/ed-assert.chk b/testsuite/tests/ed-assert.chk new file mode 100644 index 0000000..f22e6c1 --- /dev/null +++ b/testsuite/tests/ed-assert.chk @@ -0,0 +1,15 @@ +Name: assert plumbing +Test: editor +Max-cycles: 800000000 +Document: +' assert-plumbing help document + +. +Input: +? "MARKERXYZ" +^R ^WT2.XEX +^Q^C +. +Expect-out: Parsing +Expect-out: MARKERXYZ +Exists: T2.XEX From 5fe96724f2cef6fcb57699ef2c381cf30b1a6923 Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 19:02:12 -0300 Subject: [PATCH 04/15] testsuite: Add goto and scroll editor tests Add tests for Ctrl-G to lines 0, 1, 5, 30 and 99, including the RETURN that executes the jump. They share one 33-line document fixture instead of duplicating the same text in five source files. Add the horizontal scroll test, which types Ctrl-E and moves the cursor left over a 50-character line. --- testsuite/tests/ed-goto0.chk | 33 ++++++++++++++++++++++++++ testsuite/tests/ed-goto1.chk | 33 ++++++++++++++++++++++++++ testsuite/tests/ed-goto30.chk | 33 ++++++++++++++++++++++++++ testsuite/tests/ed-goto5.chk | 33 ++++++++++++++++++++++++++ testsuite/tests/ed-goto99.chk | 33 ++++++++++++++++++++++++++ testsuite/tests/ed-scroll.chk | 35 ++++++++++++++++++++++++++++ testsuite/tests/fixtures/lines33.txt | 33 ++++++++++++++++++++++++++ 7 files changed, 233 insertions(+) create mode 100644 testsuite/tests/ed-goto0.chk create mode 100644 testsuite/tests/ed-goto1.chk create mode 100644 testsuite/tests/ed-goto30.chk create mode 100644 testsuite/tests/ed-goto5.chk create mode 100644 testsuite/tests/ed-goto99.chk create mode 100644 testsuite/tests/ed-scroll.chk create mode 100644 testsuite/tests/fixtures/lines33.txt diff --git a/testsuite/tests/ed-goto0.chk b/testsuite/tests/ed-goto0.chk new file mode 100644 index 0000000..b2c6ade --- /dev/null +++ b/testsuite/tests/ed-goto0.chk @@ -0,0 +1,33 @@ +Name: ed goto 0 +Test: editor +Max-cycles: 800000000 +Document: fixtures/lines33.txt +Input: +^G0\x9b +. +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\x92\x92\x92\x92\x92\x92\x92 +\xecine 02 +line 03 +line 04 +line 05 +line 06 +line 07 +line 08 +line 09 +line 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 +line 21 +line 22 +line 23 +line 24 +. diff --git a/testsuite/tests/ed-goto1.chk b/testsuite/tests/ed-goto1.chk new file mode 100644 index 0000000..fdf2330 --- /dev/null +++ b/testsuite/tests/ed-goto1.chk @@ -0,0 +1,33 @@ +Name: ed goto 1 +Test: editor +Max-cycles: 800000000 +Document: fixtures/lines33.txt +Input: +^G1\x9b +. +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xecine 01 +line 02 +line 03 +line 04 +line 05 +line 06 +line 07 +line 08 +line 09 +line 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 +line 21 +line 22 +line 23 +. diff --git a/testsuite/tests/ed-goto30.chk b/testsuite/tests/ed-goto30.chk new file mode 100644 index 0000000..ef42e30 --- /dev/null +++ b/testsuite/tests/ed-goto30.chk @@ -0,0 +1,33 @@ +Name: ed goto 30 +Test: editor +Max-cycles: 800000000 +Document: fixtures/lines33.txt +Input: +^G30\x9b +. +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb3\xb0\x92\x92\x92\x92\x92\x92 +\xecine 30 +line 31 +line 32 +line 33 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-goto5.chk b/testsuite/tests/ed-goto5.chk new file mode 100644 index 0000000..285ac1a --- /dev/null +++ b/testsuite/tests/ed-goto5.chk @@ -0,0 +1,33 @@ +Name: ed goto 5 +Test: editor +Max-cycles: 800000000 +Document: fixtures/lines33.txt +Input: +^G5\x9b +. +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb5\x92\x92\x92\x92\x92\x92\x92 +\xecine 05 +line 06 +line 07 +line 08 +line 09 +line 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 +line 21 +line 22 +line 23 +line 24 +line 25 +line 26 +line 27 +. diff --git a/testsuite/tests/ed-goto99.chk b/testsuite/tests/ed-goto99.chk new file mode 100644 index 0000000..2e58236 --- /dev/null +++ b/testsuite/tests/ed-goto99.chk @@ -0,0 +1,33 @@ +Name: ed goto 99 +Test: editor +Max-cycles: 800000000 +Document: fixtures/lines33.txt +Input: +^G99\x9b +. +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb3\xb3\x92\x92\x92\x92\x92\x92 +\xecine 33 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-scroll.chk b/testsuite/tests/ed-scroll.chk new file mode 100644 index 0000000..8e1f809 --- /dev/null +++ b/testsuite/tests/ed-scroll.chk @@ -0,0 +1,35 @@ +Name: ed scroll +Test: editor +Max-cycles: 800000000 +Document: +AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEE +. +Input: +\x05\x1e\x1e\x1e\x1e\x1e +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\x9eBBBBCCCCCCCCCCDDDDDDDDDDEEEEE\xc5EEEE +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/fixtures/lines33.txt b/testsuite/tests/fixtures/lines33.txt new file mode 100644 index 0000000..dea6e12 --- /dev/null +++ b/testsuite/tests/fixtures/lines33.txt @@ -0,0 +1,33 @@ +line 01 +line 02 +line 03 +line 04 +line 05 +line 06 +line 07 +line 08 +line 09 +line 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 +line 21 +line 22 +line 23 +line 24 +line 25 +line 26 +line 27 +line 28 +line 29 +line 30 +line 31 +line 32 +line 33 From 099676802432ef4e6724b047b95c9eb50c750a1a Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 19:06:07 -0300 Subject: [PATCH 05/15] testsuite: Add editing operation editor tests Add editor tests for split/join, delete and insert line, delete to EOF, copy/paste, undo, backspace at the start, an empty final line and typing at the 255-character limit. Use the readable Input notation for control keys, so the intent of each script is visible. The wrapped-line tests now expect the right-arrow marker that the FastBasic editor places in the last column to show that the line continues. --- testsuite/tests/ed-backspace-nop.chk | 35 +++++++++++++++++++++++++ testsuite/tests/ed-copy-paste.chk | 36 +++++++++++++++++++++++++ testsuite/tests/ed-del-eof.chk | 35 +++++++++++++++++++++++++ testsuite/tests/ed-del-line.chk | 36 +++++++++++++++++++++++++ testsuite/tests/ed-end-empty.chk | 33 +++++++++++++++++++++++ testsuite/tests/ed-join-long.chk | 39 ++++++++++++++++++++++++++++ testsuite/tests/ed-join.chk | 39 ++++++++++++++++++++++++++++ testsuite/tests/ed-typing-at-255.chk | 35 +++++++++++++++++++++++++ testsuite/tests/ed-undo.chk | 36 +++++++++++++++++++++++++ 9 files changed, 324 insertions(+) create mode 100644 testsuite/tests/ed-backspace-nop.chk create mode 100644 testsuite/tests/ed-copy-paste.chk create mode 100644 testsuite/tests/ed-del-eof.chk create mode 100644 testsuite/tests/ed-del-line.chk create mode 100644 testsuite/tests/ed-end-empty.chk create mode 100644 testsuite/tests/ed-join-long.chk create mode 100644 testsuite/tests/ed-join.chk create mode 100644 testsuite/tests/ed-typing-at-255.chk create mode 100644 testsuite/tests/ed-undo.chk diff --git a/testsuite/tests/ed-backspace-nop.chk b/testsuite/tests/ed-backspace-nop.chk new file mode 100644 index 0000000..67b3d33 --- /dev/null +++ b/testsuite/tests/ed-backspace-nop.chk @@ -0,0 +1,35 @@ +Name: ed backspace nop +Test: editor +Max-cycles: 800000000 +Document: +simple test for editor +. +Input: + +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xf3imple test for editor +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-copy-paste.chk b/testsuite/tests/ed-copy-paste.chk new file mode 100644 index 0000000..56f1218 --- /dev/null +++ b/testsuite/tests/ed-copy-paste.chk @@ -0,0 +1,36 @@ +Name: ed copy paste +Test: editor +Max-cycles: 800000000 +Document: +abc +def +. +Input: +^C^V^V +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb4\x92\x92\x92\x92\x92\x92\x92 +abc +def +abc +\xe4ef +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-del-eof.chk b/testsuite/tests/ed-del-eof.chk new file mode 100644 index 0000000..35216a8 --- /dev/null +++ b/testsuite/tests/ed-del-eof.chk @@ -0,0 +1,35 @@ +Name: ed del eof +Test: editor +Max-cycles: 800000000 +Document: +simple test for editor +. +Input: + +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +simple test for editor\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-del-line.chk b/testsuite/tests/ed-del-line.chk new file mode 100644 index 0000000..e68bbfe --- /dev/null +++ b/testsuite/tests/ed-del-line.chk @@ -0,0 +1,36 @@ +Name: ed del line +Test: editor +Max-cycles: 800000000 +Document: +abc +def +. +Input: + +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xe1bc +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-end-empty.chk b/testsuite/tests/ed-end-empty.chk new file mode 100644 index 0000000..32ff4af --- /dev/null +++ b/testsuite/tests/ed-end-empty.chk @@ -0,0 +1,33 @@ +Name: ed end empty +Test: editor +Max-cycles: 800000000 +Document: none +Input: +^E +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-join-long.chk b/testsuite/tests/ed-join-long.chk new file mode 100644 index 0000000..592e22d --- /dev/null +++ b/testsuite/tests/ed-join-long.chk @@ -0,0 +1,39 @@ +Name: ed join long +Test: editor +Max-cycles: 800000000 +Document: +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +. +Input: +^G1\x9b^E^ST.BAS\x9b +. +Screen: +\x92\x92\xc4\xba\xd4\xae\xc2\xc1\xd3\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92 +\x9eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xc2BBBBB\x9f +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. +check: T.BAS +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +. diff --git a/testsuite/tests/ed-join.chk b/testsuite/tests/ed-join.chk new file mode 100644 index 0000000..aefe44b --- /dev/null +++ b/testsuite/tests/ed-join.chk @@ -0,0 +1,39 @@ +Name: ed join +Test: editor +Max-cycles: 800000000 +Document: +abcde +fghij +klmno +pqrst +uvwxy +. +Input: +^E^E +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +abcde\xa0 +fghij +klmnopqrstuvwxy +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-typing-at-255.chk b/testsuite/tests/ed-typing-at-255.chk new file mode 100644 index 0000000..50e8b17 --- /dev/null +++ b/testsuite/tests/ed-typing-at-255.chk @@ -0,0 +1,35 @@ +Name: ed typing at 255 +Test: editor +Max-cycles: 800000000 +Document: +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +. +Input: +Y +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xd8XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\x9f +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-undo.chk b/testsuite/tests/ed-undo.chk new file mode 100644 index 0000000..972be58 --- /dev/null +++ b/testsuite/tests/ed-undo.chk @@ -0,0 +1,36 @@ +Name: ed undo +Test: editor +Max-cycles: 800000000 +Document: +' undo test + +. +Input: +XXXX^Z^EYYYY +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +' undo testYYYY\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. From 43537263b2dd14c230c485599488537c39d8e493 Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 19:06:22 -0300 Subject: [PATCH 06/15] testsuite: Add compile-error editor tests Add tests for a parse error on Ctrl-R, both at the start of the file and after scrolling to the error line. --- testsuite/tests/ed-syntax-error.chk | 64 +++++++++++++++++++++++++++++ testsuite/tests/ed-syntax-msg.chk | 36 ++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 testsuite/tests/ed-syntax-error.chk create mode 100644 testsuite/tests/ed-syntax-msg.chk diff --git a/testsuite/tests/ed-syntax-error.chk b/testsuite/tests/ed-syntax-error.chk new file mode 100644 index 0000000..8971dbf --- /dev/null +++ b/testsuite/tests/ed-syntax-error.chk @@ -0,0 +1,64 @@ +Name: ed syntax error +Test: editor +Max-cycles: 800000000 +Document: +PRINT "LINE 01" +PRINT "LINE 02" +PRINT "LINE 03" +PRINT "LINE 04" +PRINT "LINE 05" +PRINT "LINE 06" +PRINT "LINE 07" +PRINT "LINE 08" +PRINT "LINE 09" +PRINT "LINE 10" +PRINT "LINE 11" +PRINT "LINE 12" +PRINT "LINE 13" +PRINT "LINE 14" +PRINT "LINE 15" +PRINT "LINE 16" +PRINT "LINE 17" +PRINT "LINE 18" +PRINT "LINE 19" +? B +PRINT "LINE 21" +PRINT "LINE 22" +PRINT "LINE 23" +PRINT "LINE 24" +PRINT "LINE 25" +PRINT "LINE 26" +PRINT "LINE 27" +PRINT "LINE 28" +PRINT "LINE 29" +PRINT "LINE 30" +. +Input: +^R\x9b +. +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\xb0\x92\x92\x92\x92\x92\x92 +PRINT "LINE 10" +PRINT "LINE 11" +PRINT "LINE 12" +PRINT "LINE 13" +PRINT "LINE 14" +PRINT "LINE 15" +PRINT "LINE 16" +PRINT "LINE 17" +PRINT "LINE 18" +PRINT "LINE 19" +? B\xa0 +PRINT "LINE 21" +PRINT "LINE 22" +PRINT "LINE 23" +PRINT "LINE 24" +PRINT "LINE 25" +PRINT "LINE 26" +PRINT "LINE 27" +PRINT "LINE 28" +PRINT "LINE 29" +PRINT "LINE 30" +\xfd +\xfd +. diff --git a/testsuite/tests/ed-syntax-msg.chk b/testsuite/tests/ed-syntax-msg.chk new file mode 100644 index 0000000..f49e2de --- /dev/null +++ b/testsuite/tests/ed-syntax-msg.chk @@ -0,0 +1,36 @@ +Name: ed syntax msg +Test: editor +Max-cycles: 800000000 +Document: +A = 1 +? B +. +Input: +^R +. +Screen: +Parsing: parse error\xa0 +A = 1 +? B +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. From fa829c7d99141d2ecf18f3c196fc1a67e213b8ff Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 19:07:30 -0300 Subject: [PATCH 07/15] testsuite: Document the editor test format Describe .chk-only editor tests and the Document: key, per-test DOS roots, input notation, editor-only EOF handling, Screen: assertions, cursor rendering, wrap markers, and build-only debug dumps. --- testsuite/README.md | 232 +++++++++++++++++++++++- testsuite/tests/fixtures/.gitattributes | 1 + 2 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 testsuite/tests/fixtures/.gitattributes diff --git a/testsuite/README.md b/testsuite/README.md index b1122a5..0187ccb 100644 --- a/testsuite/README.md +++ b/testsuite/README.md @@ -1,6 +1,232 @@ FastBasic test suite --------------------- +==================== -To run the testsuite, you need "git" to download the 6502 simulator and type -`make test` from the parent directory. +To run the testsuite, you need `git` to download the 6502 simulator and type +`make test` from the parent directory. To run a single test: + build/bin/fbtest testsuite/tests/.chk + + +File format +----------- + +Normal tests are a pair of files: `tests/.chk` (the test definition) +and `tests/.bas` (the source program). + +Editor tests (`Test: editor`) are different: the `.chk` file is +self-contained and **no `.bas` file is needed**. The initial editor buffer is +declared explicitly with the `Document:` key, so the same `.bas` file is never +silently used as both program source and editor buffer. + +The `.chk` file is a sequence of `Key: value` lines, followed by multi-line +sections terminated by a line containing only `.`. Recognised keys: + +| Key | Value | +|:----------------|:---------------------------------------------------------------| +| `Name:` | Human-readable test name (shown in pass/fail output). | +| `Test:` | comma/space/hyphen separated list of test modifiers (below). | +| `Max-cycles:` | Cycle limit for the simulator (default 20 000 000). | +| `Error:` | Expected compiler error message (for `error` tests). | +| `Error-pos:` | Expected error location as `line : column`. | +| `Document:` | Editor tests only: initial document (see below). | +| `Input:` | Keystrokes fed to the running program (section, terminated by `.`). | +| `Output:` | Expected captured output stream (rest of file, exact match). | +| `Screen:` | Expected screen state, up to 24 lines of up to 40 chars (section). | +| `Expect-out:` | Substring that must appear in the captured output. | +| `Exists:` | Filename that must exist after the run (in the test's DOS root).| +| `Check :` | Section with expected content of a file written during the run.| +| `Files:` | Filenames to copy into the DOS root before the run (section). | + +### Test modifiers (`Test:`) + +| Modifier | Meaning | +|:----------|:--------------------------------------------------------------| +| `run` | Compile as XEX and run under mini65 (default for most tests). | +| `fp` | Use the floating-point compiler (default). | +| `int` | Use the integer compiler. | +| `cross` | Cross-compile (build/bin/fastbasic). | +| `native` | Native compile (build/bin/fbc / fbci). | +| `compile` | Compile only, do not run. | +| `error` | Expect a compile error; `Error:` gives the message. | +| `editor` | Run the IDE (fb.xex) with a keystroke script. Implies `fp`. | + +Multiple modifiers can be combined: `Test: run, int` runs the integer +compiler, `Test: cross, compile` cross-compiles without running. + +When neither `cross` nor `native` is given, both are used. When neither `fp` +nor `int` is given, both are used. A single test may be compiled and run up to +six ways: native FP/INT and cross FP/INT, each as XEX and cart ROM. + + +Input notation +-------------- + +The `Input:` section supports readable notation for control and special +characters. All other bytes pass through literally (newlines in the file are +converted to ATASCII EOL `$9B`). + +| Notation | Maps to | +|:-------------|:------------------------------------------------------| +| `^A` .. `^Z` | Ctrl-A (`$01`) .. Ctrl-Z (`$1A`). Case-insensitive. | +| `` | Escape (`$1B`). | +| `` | Cursor up (`$1C`). | +| `` | Cursor down (`$1D`). | +| `` | Cursor left (`$1E`). | +| `` | Cursor right (`$1F`). | +| `` | Clear screen (`$7D`). | +| `` | Backspace (`$7E`). | +| `` | Tab (`$7F`). | +| `` | Delete line (`$9C`). | +| `` | Insert line (`$9D`). | +| `` | Clear tab stop (`$9E`). | +| `` | Set tab stop (`$9F`). | +| `` | Bell (`$FD`). | +| `` | Delete character at cursor (`$FE`). | +| `` | Insert character at cursor (`$FF`). | +| `\xHH` | Explicit hex byte (e.g. `\x9B` for ATASCII EOL). | +| UTF-8 chars | Multi-byte UTF-8 sequences from the ATASCII character set (e.g. `⇱` for CLS, `▄` for block graphic, `𝗔` for inverse A). | + +Example — the following types "HELLO", runs the program, saves as +`T2.XEX` and quits: + + Input: + HELLO + ^R ^WT2.XEX + ^Q^C + . + +For normal (non-editor) tests, every line break in `Input:` is passed to the +program. In editor tests, the line break immediately before the terminating +`.` is treated as a section separator, not as a RETURN key press. Use an +explicit `\x9b` or an empty line before `.` when the script needs a final +RETURN. + + +Editor tests +------------ + +Editor tests (`Test: editor`) run the FastBasic IDE (`fb.xex`) under the +mini65 simulator with a scripted keystroke sequence. + +### Initial document + +The IDE opens `D:HELP.TXT` on startup, and that file is the initial editor +buffer. The `Document:` key describes how to create it: + + Document: none + +creates an empty `HELP.TXT`. This is the default when `Document:` is omitted. + + Document: + ' split test + . + +uses the lines between `Document:` and `.` as the initial document. Lines +are converted from LF to ATASCII EOL. The newline before the final `.` is not +part of the document; add an empty line before `.` if the document must end +with an EOL. + + Document: fixtures/lines33.txt + +copies an ASCII fixture from the test directory, converting LF to ATASCII. +Fixtures are read-only and are never modified by a test. + +`Document:` is only valid for editor tests. + +### Per-test DOS root + +Every editor test runs with its own emulated `D:` root: + + build/tests// + +`HELP.TXT`, files from `Files:`, files written by the IDE, and the targets of +`Check:` and `Exists:` all live in that directory. The harness creates the +directory and removes known output files before each run, so `Exists:` cannot +pass because of a file left by an earlier run. + +All generated files stay under `build/`. Tests never write into +`testsuite/tests/`. + +### EOF termination + +When the `Input:` feeder is exhausted, `str_get_char()` returns EOF and the +simulator's cycle limit is set to 1, causing it to stop on the next cycle +check. This avoids wasting cycles waiting for more input when the editor is +idle. This only applies to editor tests (the `str_stop_on_eof` flag); regular +tests are not affected. + + +Screen assertions +----------------- + +The `Screen:` section allows editor tests to check the final state of the +emulated 24×40 text screen. It contains up to 24 lines, each up to 40 +characters, terminated by a line with only `.`. + +Trailing spaces on each line are trimmed before comparison. The screen is +read after the simulation ends and compared byte-exactly against the expected +content — the ATASCII inverse bit (`$80`) and all control characters are +preserved. Notation is expanded in expected lines, so you can use `\x92` for +inverse space, `` for `$7D`, UTF-8 characters like `𝗔` for inverse A, etc. + +When a comparison fails, the actual screen row is also printed using UTF-8 +characters with ANSI inverse-video escapes, so you can visually see what the +Atari screen looks like. + +The simulation is terminated when the input feeder reaches EOF, so the screen +snapshot captures the state at that point. + +### Cursor rendering + +The real Atari OS renders the cursor by XOR'ing `$80` at the cursor position +(`CRSINH`/`OLDCHR` mechanism). The mini65 screen accessor includes the visible +cursor glyph, so the test harness reads the screen buffer directly and does +**not** invert the cursor cell again. Goldens must account for the cursor +glyph — e.g., a normal space (`$20`) at the cursor position becomes inverse +space (`$A0`). + +### Last-column and wrap markers + +The editor writes continuation markers in the last column of a wrapped +logical line. Tests must not write to (23, 39) unless they intend to trigger +a scroll, and screen goldens must include these marker cells when a line +overflows. + +### Screen state after quit + +If the editor quits (Ctrl-Q Ctrl-C), the screen is typically cleared. +`Screen:` assertions are meaningful only while the editor is still running +(e.g. after typing text or running a program). Do not assert the post-quit +screen. + + +Debugging +--------- + +Set environment variables before running `make test` or `build/bin/fbtest`: + +| Variable | Effect | +|:-----------------|:-------------------------------------------------------------| +| `FBTEST_DEBUG` | Enable sim65 debug messages (CIO activity, device I/O). | +| `FB_DUMP_SCREEN` | Write the final 24×40 screen as raw ATASCII bytes to `build/tests/.screen`. | +| `FB_DUMP_OUTPUT` | Write the captured output stream to `build/tests/.output`. | + +Example: + + FBTEST_DEBUG=1 FB_DUMP_SCREEN=1 \ + build/bin/fbtest testsuite/tests/ed-assert.chk + +Both dump variables are switches; the files are always written under +`build/tests/`. + + +Architecture +------------ + +- `testsuite/src/fbtest.c` — the test harness (compilation, execution, + output comparison, screen assertions, notation expansion). +- `testsuite/mini65/` — the mini65 6502 simulator with Atari BIOS + emulation (submodule). +- `testsuite/tests/*.chk` — test definitions. +- `testsuite/tests/*.bas` — source programs for non-editor tests. +- `testsuite/tests/fixtures/` — shared read-only editor documents. diff --git a/testsuite/tests/fixtures/.gitattributes b/testsuite/tests/fixtures/.gitattributes new file mode 100644 index 0000000..fa1385d --- /dev/null +++ b/testsuite/tests/fixtures/.gitattributes @@ -0,0 +1 @@ +* -text From 2c05f8f2f09a7bf857a8f448581792d0df26bb51 Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 20:27:55 -0300 Subject: [PATCH 08/15] fbtest: Add input notation Add a named notation for the ATASCII EOL/RETURN byte and use it in editor scripts that explicitly press RETURN. Newlines in Input still imply RETURN; the named form makes explicit key presses visible. Use the readable notation for the scroll test's Ctrl-E and arrow keys as well. --- testsuite/README.md | 3 ++- testsuite/src/fbtest.c | 3 ++- testsuite/tests/ed-goto0.chk | 2 +- testsuite/tests/ed-goto1.chk | 2 +- testsuite/tests/ed-goto30.chk | 2 +- testsuite/tests/ed-goto5.chk | 2 +- testsuite/tests/ed-goto99.chk | 2 +- testsuite/tests/ed-join-long.chk | 2 +- testsuite/tests/ed-scroll.chk | 2 +- testsuite/tests/ed-split.chk | 2 +- testsuite/tests/ed-syntax-error.chk | 2 +- 11 files changed, 13 insertions(+), 11 deletions(-) diff --git a/testsuite/README.md b/testsuite/README.md index 0187ccb..22b79ea 100644 --- a/testsuite/README.md +++ b/testsuite/README.md @@ -73,6 +73,7 @@ converted to ATASCII EOL `$9B`). | `` | Cursor down (`$1D`). | | `` | Cursor left (`$1E`). | | `` | Cursor right (`$1F`). | +| `` | Return / ATASCII EOL (`$9B`). | | `` | Clear screen (`$7D`). | | `` | Backspace (`$7E`). | | `` | Tab (`$7F`). | @@ -98,7 +99,7 @@ Example — the following types "HELLO", runs the program, saves as For normal (non-editor) tests, every line break in `Input:` is passed to the program. In editor tests, the line break immediately before the terminating `.` is treated as a section separator, not as a RETURN key press. Use an -explicit `\x9b` or an empty line before `.` when the script needs a final +`` or an empty line before `.` when the script needs a final RETURN. diff --git a/testsuite/src/fbtest.c b/testsuite/src/fbtest.c index 3ca8ba7..c207282 100644 --- a/testsuite/src/fbtest.c +++ b/testsuite/src/fbtest.c @@ -578,7 +578,8 @@ static char *expand_notation(char *input, size_t *len_out) { static const struct { const char *name; int code; } keys[] = { { "ESC", 0x1B }, { "UP", 0x1C }, { "DOWN", 0x1D }, - { "LEFT", 0x1E }, { "RIGHT", 0x1F }, { "CLS", 0x7D }, + { "LEFT", 0x1E }, { "RIGHT", 0x1F }, { "RETURN", 0x9B }, + { "CLS", 0x7D }, { "BACKSP", 0x7E }, { "TAB", 0x7F }, { "DELLN", 0x9C }, { "INSLN", 0x9D }, { "DELTAB", 0x9E }, { "SETTAB", 0x9F }, { "BELL", 0xFD }, { "DEL", 0xFE }, { "INS", 0xFF }, diff --git a/testsuite/tests/ed-goto0.chk b/testsuite/tests/ed-goto0.chk index b2c6ade..4b673bf 100644 --- a/testsuite/tests/ed-goto0.chk +++ b/testsuite/tests/ed-goto0.chk @@ -3,7 +3,7 @@ Test: editor Max-cycles: 800000000 Document: fixtures/lines33.txt Input: -^G0\x9b +^G0 . Screen: \x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\x92\x92\x92\x92\x92\x92\x92 diff --git a/testsuite/tests/ed-goto1.chk b/testsuite/tests/ed-goto1.chk index fdf2330..072bf98 100644 --- a/testsuite/tests/ed-goto1.chk +++ b/testsuite/tests/ed-goto1.chk @@ -3,7 +3,7 @@ Test: editor Max-cycles: 800000000 Document: fixtures/lines33.txt Input: -^G1\x9b +^G1 . Screen: \x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 diff --git a/testsuite/tests/ed-goto30.chk b/testsuite/tests/ed-goto30.chk index ef42e30..e68017d 100644 --- a/testsuite/tests/ed-goto30.chk +++ b/testsuite/tests/ed-goto30.chk @@ -3,7 +3,7 @@ Test: editor Max-cycles: 800000000 Document: fixtures/lines33.txt Input: -^G30\x9b +^G30 . Screen: \x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb3\xb0\x92\x92\x92\x92\x92\x92 diff --git a/testsuite/tests/ed-goto5.chk b/testsuite/tests/ed-goto5.chk index 285ac1a..781d5ec 100644 --- a/testsuite/tests/ed-goto5.chk +++ b/testsuite/tests/ed-goto5.chk @@ -3,7 +3,7 @@ Test: editor Max-cycles: 800000000 Document: fixtures/lines33.txt Input: -^G5\x9b +^G5 . Screen: \x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb5\x92\x92\x92\x92\x92\x92\x92 diff --git a/testsuite/tests/ed-goto99.chk b/testsuite/tests/ed-goto99.chk index 2e58236..d803528 100644 --- a/testsuite/tests/ed-goto99.chk +++ b/testsuite/tests/ed-goto99.chk @@ -3,7 +3,7 @@ Test: editor Max-cycles: 800000000 Document: fixtures/lines33.txt Input: -^G99\x9b +^G99 . Screen: \x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb3\xb3\x92\x92\x92\x92\x92\x92 diff --git a/testsuite/tests/ed-join-long.chk b/testsuite/tests/ed-join-long.chk index 592e22d..5da8ce3 100644 --- a/testsuite/tests/ed-join-long.chk +++ b/testsuite/tests/ed-join-long.chk @@ -6,7 +6,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB . Input: -^G1\x9b^E^ST.BAS\x9b +^G1^E^ST.BAS . Screen: \x92\x92\xc4\xba\xd4\xae\xc2\xc1\xd3\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92 diff --git a/testsuite/tests/ed-scroll.chk b/testsuite/tests/ed-scroll.chk index 8e1f809..2ea09d2 100644 --- a/testsuite/tests/ed-scroll.chk +++ b/testsuite/tests/ed-scroll.chk @@ -5,7 +5,7 @@ Document: AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEE . Input: -\x05\x1e\x1e\x1e\x1e\x1e +^E . Screen: \x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 diff --git a/testsuite/tests/ed-split.chk b/testsuite/tests/ed-split.chk index e4961dc..c355139 100644 --- a/testsuite/tests/ed-split.chk +++ b/testsuite/tests/ed-split.chk @@ -5,7 +5,7 @@ Document: ' split test . Input: -abcdefghij\x9b +abcdefghij . Screen: \x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\x92\x92\x92\x92\x92\x92\x92 diff --git a/testsuite/tests/ed-syntax-error.chk b/testsuite/tests/ed-syntax-error.chk index 8971dbf..8ef940c 100644 --- a/testsuite/tests/ed-syntax-error.chk +++ b/testsuite/tests/ed-syntax-error.chk @@ -34,7 +34,7 @@ PRINT "LINE 29" PRINT "LINE 30" . Input: -^R\x9b +^R . Screen: \x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\xb0\x92\x92\x92\x92\x92\x92 From 700e415122a2b3438b9308e6774f96bf2cdbdc16 Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 21:20:07 -0300 Subject: [PATCH 09/15] fbtest: Add comments and Cursor assertions Allow # comments in .chk headers and add a Cursor: row,col assertion, read from the emulated ROWCRS/COLCRS variables after the run. Use it in a few existing tests, and document the known Ctrl-G 0 bug and the intent of ed-del-eof. --- testsuite/README.md | 9 ++++- testsuite/src/fbtest.c | 57 +++++++++++++++++++++++++----- testsuite/tests/ed-del-eof.chk | 1 + testsuite/tests/ed-goto0.chk | 3 ++ testsuite/tests/ed-split.chk | 1 + testsuite/tests/screen-initial.chk | 1 + 6 files changed, 63 insertions(+), 9 deletions(-) diff --git a/testsuite/README.md b/testsuite/README.md index 22b79ea..ff3604a 100644 --- a/testsuite/README.md +++ b/testsuite/README.md @@ -19,7 +19,8 @@ declared explicitly with the `Document:` key, so the same `.bas` file is never silently used as both program source and editor buffer. The `.chk` file is a sequence of `Key: value` lines, followed by multi-line -sections terminated by a line containing only `.`. Recognised keys: +sections terminated by a line containing only `.`. Lines starting with `#` in +the header are comments. Recognised keys: | Key | Value | |:----------------|:---------------------------------------------------------------| @@ -32,6 +33,7 @@ sections terminated by a line containing only `.`. Recognised keys: | `Input:` | Keystrokes fed to the running program (section, terminated by `.`). | | `Output:` | Expected captured output stream (rest of file, exact match). | | `Screen:` | Expected screen state, up to 24 lines of up to 40 chars (section). | +| `Cursor:` | Expected cursor position as `row,col` (editor tests, from ROWCRS/COLCRS). | | `Expect-out:` | Substring that must appear in the captured output. | | `Exists:` | Filename that must exist after the run (in the test's DOS root).| | `Check :` | Section with expected content of a file written during the run.| @@ -174,6 +176,11 @@ When a comparison fails, the actual screen row is also printed using UTF-8 characters with ANSI inverse-video escapes, so you can visually see what the Atari screen looks like. +A `Cursor: row,col` line asserts the cursor position after the run; coordinates +are zero-based and are read from the emulated `ROWCRS` and `COLCRS` OS +variables. This complements the cursor glyph in `Screen:` and is useful for +navigation tests. + The simulation is terminated when the input feeder reaches EOF, so the screen snapshot captures the state at that point. diff --git a/testsuite/src/fbtest.c b/testsuite/src/fbtest.c index c207282..9331c1e 100644 --- a/testsuite/src/fbtest.c +++ b/testsuite/src/fbtest.c @@ -162,7 +162,7 @@ static void print_screen_row_utf8(FILE *fp, int row) static int run_atari_prog(const char *progname, char *output, size_t *output_len, const char *input, size_t input_len, uint64_t max_cycles, int is_rom, const char *cmdline, int fix_margins, - const char *dos_root) + const char *dos_root, int *out_row, int *out_col) { // Init input/output str_in = input; @@ -231,6 +231,12 @@ static int run_atari_prog(const char *progname, char *output, size_t *output_len // TODO: this should depend on actual compiled value, we currently expect the // value to be constant. int x = 0x28 != (sim65_get_byte(s, 0x8E) & 0xFF); + // Read the OS cursor variables before freeing the simulator. + if (out_row) + *out_row = sim65_get_byte(s, ROWCRS); + if (out_col) + *out_col = sim65_get_byte(s, COLCRS) | + (sim65_get_byte(s, COLCRS + 1) << 8); // Update output length *output_len = str_out_pos; free(s); @@ -271,7 +277,7 @@ int run_test_xex(const char *fname, const char *input, const char *expected_out, size_t len = strlen(expected_out) + 128; char *out = calloc(len + 1, 1); - int e = run_atari_prog(fname, out, &len, input, strlen(input), max_cycles, rom, 0, 1, NULL); + int e = run_atari_prog(fname, out, &len, input, strlen(input), max_cycles, rom, 0, 1, NULL, NULL, NULL); if (!e) { // Check @@ -363,7 +369,7 @@ static int compile_native(const char *atbname, const char *xexname, const char * sprintf(cmd, "%s %s", atbname, xexname); else sprintf(cmd, "%s %s", atbname + l, xexname + l); - int e = run_atari_prog(compiler, out, &len, 0, 0, MAX_FPC_CYCLES, 0, cmd, 1, NULL); + int e = run_atari_prog(compiler, out, &len, 0, 0, MAX_FPC_CYCLES, 0, cmd, 1, NULL, NULL, NULL); if (e) fprintf(stderr, "%s: can't execute compiler.\n", compiler); @@ -753,6 +759,8 @@ int fbtest(const char *fname) // Expected screen content (24 rows of up to 40 cols) char *expected_screen[24]; int screen_lines = 0; + // Expected cursor position, read from ROWCRS/COLCRS after the run + int cursor_set = 0, cursor_row = 0, cursor_col = 0; int error_pos_line = 0, error_pos_column = 0; int test = 0, n = 0, line = 0; uint64_t max_cycles = 20000000; @@ -762,6 +770,9 @@ int fbtest(const char *fname) line ++; if ( !*lbuf || *lbuf == '\n' ) continue; + // Header comments + if (lbuf[0] == '#') + continue; char key[48], buf[256]; n = sscanf(lbuf, "%15[^:]: %255[^\n\r]", key, buf); @@ -824,6 +835,23 @@ int fbtest(const char *fname) return -1; } } + else if (!strcasecmp(key, "cursor")) + { + if (cursor_set) + { + fprintf(stderr, "%s:%d: more than one CURSOR entry.\n", fname, line); + return -1; + } + if (2 != sscanf(buf, " %i , %i", &cursor_row, &cursor_col) || + cursor_row < 0 || cursor_row >= 24 || + cursor_col < 0 || cursor_col >= 40) + { + fprintf(stderr, "%s:%d: error, invalid value for CURSOR '%s'\n", + fname, line, buf); + return -1; + } + cursor_set = 1; + } else if (!strcasecmp(key, "document")) { // Initial editor document, only valid for editor tests. @@ -1059,6 +1087,11 @@ int fbtest(const char *fname) fprintf(stderr, "%s: error, DOCUMENT is only valid for editor tests\n", fname); return -1; } + if (cursor_set && !(test & test_editor)) + { + fprintf(stderr, "%s: error, CURSOR is only valid for editor tests\n", fname); + return -1; + } if (!expected_out) expected_out = strdup(""); if (!input_buf) @@ -1203,17 +1236,18 @@ int fbtest(const char *fname) // Run the IDE with the keystroke script size_t len = 65536; char *out = calloc(len + 1, 1); + int cur_row = 0, cur_col = 0; if (!e) { str_stop_on_eof = 1; e = run_atari_prog(ide, out, &len, input_buf, input_len, - max_cycles, 0, 0, 0, test_root); + max_cycles, 0, 0, 0, test_root, + &cur_row, &cur_col); str_stop_on_eof = 0; } - // Cursor state is exposed for diagnostics only: the screen - // accessor already includes the visible cursor glyph. - int cur_row, cur_col; - atario_cursor_get(&cur_row, &cur_col); + // Cursor state from ROWCRS/COLCRS is used for Cursor: checks and + // diagnostics. The screen accessor already includes the visible + // cursor glyph, so it must not be XORed again. // Dump final E: screen state as raw ATASCII bytes for hex analysis. if (getenv("FB_DUMP_SCREEN")) @@ -1263,6 +1297,13 @@ int fbtest(const char *fname) } } } + if (!e && cursor_set && + (cur_row != cursor_row || cur_col != cursor_col)) + { + fprintf(stderr, "%s: CURSOR mismatch: expected %d,%d, got %d,%d\n", + fname, cursor_row, cursor_col, cur_row, cur_col); + e = -1; + } if (!e && *expected_out) e = check_output(fname, expected_out, out); diff --git a/testsuite/tests/ed-del-eof.chk b/testsuite/tests/ed-del-eof.chk index 35216a8..7765b94 100644 --- a/testsuite/tests/ed-del-eof.chk +++ b/testsuite/tests/ed-del-eof.chk @@ -1,3 +1,4 @@ +# DEL at EOF must not crash. The screen is intentionally unchanged. Name: ed del eof Test: editor Max-cycles: 800000000 diff --git a/testsuite/tests/ed-goto0.chk b/testsuite/tests/ed-goto0.chk index 4b673bf..823d097 100644 --- a/testsuite/tests/ed-goto0.chk +++ b/testsuite/tests/ed-goto0.chk @@ -1,3 +1,5 @@ +# TODO: Ctrl-G 0 currently lands on line 02 because of abs(topLine - 1). +# Expected is line 01; keep this test documenting the bug for now. Name: ed goto 0 Test: editor Max-cycles: 800000000 @@ -31,3 +33,4 @@ line 22 line 23 line 24 . +Cursor: 1,0 diff --git a/testsuite/tests/ed-split.chk b/testsuite/tests/ed-split.chk index c355139..ab9808c 100644 --- a/testsuite/tests/ed-split.chk +++ b/testsuite/tests/ed-split.chk @@ -33,3 +33,4 @@ abcdefg \xfd \xfd . +Cursor: 2,0 diff --git a/testsuite/tests/screen-initial.chk b/testsuite/tests/screen-initial.chk index 585635d..98ad5a8 100644 --- a/testsuite/tests/screen-initial.chk +++ b/testsuite/tests/screen-initial.chk @@ -32,3 +32,4 @@ Screen: \xfd \xfd . +Cursor: 1,0 From c1e1a1c29c6d93f2129c2f3be8f2d8598c86ae6f Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 21:20:47 -0300 Subject: [PATCH 10/15] testsuite: Add cursor navigation editor tests Add tests for Ctrl-A, Ctrl-E, left/right boundaries, page up/down and scrolling at the top and bottom of the screen. Assert both the screen and the cursor via ROWCRS/COLCRS. The page tests document the current 20-line movement; the manual says 19 lines, which is left for a later decision. --- testsuite/tests/ed-cursor-down-scroll.chk | 34 ++++++++++++++++++ testsuite/tests/ed-cursor-up-scroll.chk | 34 ++++++++++++++++++ testsuite/tests/ed-end.chk | 36 +++++++++++++++++++ testsuite/tests/ed-home.chk | 36 +++++++++++++++++++ testsuite/tests/ed-left-right-boundary.chk | 36 +++++++++++++++++++ testsuite/tests/ed-page-down.chk | 35 +++++++++++++++++++ testsuite/tests/ed-page-up.chk | 35 +++++++++++++++++++ testsuite/tests/fixtures/lines40.txt | 40 ++++++++++++++++++++++ 8 files changed, 286 insertions(+) create mode 100644 testsuite/tests/ed-cursor-down-scroll.chk create mode 100644 testsuite/tests/ed-cursor-up-scroll.chk create mode 100644 testsuite/tests/ed-end.chk create mode 100644 testsuite/tests/ed-home.chk create mode 100644 testsuite/tests/ed-left-right-boundary.chk create mode 100644 testsuite/tests/ed-page-down.chk create mode 100644 testsuite/tests/ed-page-up.chk create mode 100644 testsuite/tests/fixtures/lines40.txt diff --git a/testsuite/tests/ed-cursor-down-scroll.chk b/testsuite/tests/ed-cursor-down-scroll.chk new file mode 100644 index 0000000..7857630 --- /dev/null +++ b/testsuite/tests/ed-cursor-down-scroll.chk @@ -0,0 +1,34 @@ +Name: ed-cursor-down-scroll +Test: editor +Max-cycles: 800000000 +Document: fixtures/lines33.txt +Input: +^G1^I +. +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\xb4\x92\x92\x92\x92\x92\x92 +line 02 +line 03 +line 04 +line 05 +line 06 +line 07 +line 08 +line 09 +line 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 +line 21 +line 22 +line 23 +\xecine 24 +. +Cursor: 23,0 diff --git a/testsuite/tests/ed-cursor-up-scroll.chk b/testsuite/tests/ed-cursor-up-scroll.chk new file mode 100644 index 0000000..0c142a0 --- /dev/null +++ b/testsuite/tests/ed-cursor-up-scroll.chk @@ -0,0 +1,34 @@ +Name: ed-cursor-up-scroll +Test: editor +Max-cycles: 800000000 +Document: fixtures/lines33.txt +Input: +^G10 +. +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb9\x92\x92\x92\x92\x92\x92\x92 +\xecine 09 +line 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 +line 21 +line 22 +line 23 +line 24 +line 25 +line 26 +line 27 +line 28 +line 29 +line 30 +line 31 +. +Cursor: 1,0 diff --git a/testsuite/tests/ed-end.chk b/testsuite/tests/ed-end.chk new file mode 100644 index 0000000..76552f5 --- /dev/null +++ b/testsuite/tests/ed-end.chk @@ -0,0 +1,36 @@ +Name: ed-end +Test: editor +Max-cycles: 800000000 +Document: +abcdef +. +Input: +^E +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +abcdef\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. +Cursor: 1,6 diff --git a/testsuite/tests/ed-home.chk b/testsuite/tests/ed-home.chk new file mode 100644 index 0000000..8e7c5c9 --- /dev/null +++ b/testsuite/tests/ed-home.chk @@ -0,0 +1,36 @@ +Name: ed-home +Test: editor +Max-cycles: 800000000 +Document: +abcdef +. +Input: +^A +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xe1bcdef +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. +Cursor: 1,0 diff --git a/testsuite/tests/ed-left-right-boundary.chk b/testsuite/tests/ed-left-right-boundary.chk new file mode 100644 index 0000000..4435492 --- /dev/null +++ b/testsuite/tests/ed-left-right-boundary.chk @@ -0,0 +1,36 @@ +Name: ed-left-right-boundary +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: + +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +abc\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. +Cursor: 1,3 diff --git a/testsuite/tests/ed-page-down.chk b/testsuite/tests/ed-page-down.chk new file mode 100644 index 0000000..e04c51c --- /dev/null +++ b/testsuite/tests/ed-page-down.chk @@ -0,0 +1,35 @@ +# Current code moves 20 lines; manual.md says 19. Document current behavior. +Name: ed-page-down +Test: editor +Max-cycles: 800000000 +Document: fixtures/lines40.txt +Input: +^I +. +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\xb1\x92\x92\x92\x92\x92\x92 +line 01 +line 02 +line 03 +line 04 +line 05 +line 06 +line 07 +line 08 +line 09 +line 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 +\xecine 21 +line 22 +line 23 +. +Cursor: 21,0 diff --git a/testsuite/tests/ed-page-up.chk b/testsuite/tests/ed-page-up.chk new file mode 100644 index 0000000..d48a335 --- /dev/null +++ b/testsuite/tests/ed-page-up.chk @@ -0,0 +1,35 @@ +# Current code moves 20 lines; manual.md says 19. Document current behavior. +Name: ed-page-up +Test: editor +Max-cycles: 800000000 +Document: fixtures/lines40.txt +Input: +^G30^U +. +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\xb0\x92\x92\x92\x92\x92\x92 +\xecine 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 +line 21 +line 22 +line 23 +line 24 +line 25 +line 26 +line 27 +line 28 +line 29 +line 30 +line 31 +line 32 +. +Cursor: 1,0 diff --git a/testsuite/tests/fixtures/lines40.txt b/testsuite/tests/fixtures/lines40.txt new file mode 100644 index 0000000..f8a8ac7 --- /dev/null +++ b/testsuite/tests/fixtures/lines40.txt @@ -0,0 +1,40 @@ +line 01 +line 02 +line 03 +line 04 +line 05 +line 06 +line 07 +line 08 +line 09 +line 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 +line 21 +line 22 +line 23 +line 24 +line 25 +line 26 +line 27 +line 28 +line 29 +line 30 +line 31 +line 32 +line 33 +line 34 +line 35 +line 36 +line 37 +line 38 +line 39 +line 40 From 4c843ef0840da94583800cfe36450be1a0990061 Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 21:21:13 -0300 Subject: [PATCH 11/15] testsuite: Add line-editing boundary tests Add tests for RETURN at column 0, at EOL and at the bottom row, real backspace and delete-character edits, deleting first/last/only lines, and accepting one character on a 254-character line. Assert both the screen and the cursor position. --- testsuite/tests/ed-backspace.chk | 36 ++++++++++++++++++++++++ testsuite/tests/ed-del-char.chk | 36 ++++++++++++++++++++++++ testsuite/tests/ed-del-line-first.chk | 38 ++++++++++++++++++++++++++ testsuite/tests/ed-del-line-last.chk | 38 ++++++++++++++++++++++++++ testsuite/tests/ed-del-line-only.chk | 36 ++++++++++++++++++++++++ testsuite/tests/ed-return-end.chk | 36 ++++++++++++++++++++++++ testsuite/tests/ed-return-last-row.chk | 34 +++++++++++++++++++++++ testsuite/tests/ed-return-start.chk | 36 ++++++++++++++++++++++++ testsuite/tests/ed-typing-254.chk | 36 ++++++++++++++++++++++++ 9 files changed, 326 insertions(+) create mode 100644 testsuite/tests/ed-backspace.chk create mode 100644 testsuite/tests/ed-del-char.chk create mode 100644 testsuite/tests/ed-del-line-first.chk create mode 100644 testsuite/tests/ed-del-line-last.chk create mode 100644 testsuite/tests/ed-del-line-only.chk create mode 100644 testsuite/tests/ed-return-end.chk create mode 100644 testsuite/tests/ed-return-last-row.chk create mode 100644 testsuite/tests/ed-return-start.chk create mode 100644 testsuite/tests/ed-typing-254.chk diff --git a/testsuite/tests/ed-backspace.chk b/testsuite/tests/ed-backspace.chk new file mode 100644 index 0000000..3adb3e0 --- /dev/null +++ b/testsuite/tests/ed-backspace.chk @@ -0,0 +1,36 @@ +Name: ed-backspace +Test: editor +Max-cycles: 800000000 +Document: +abcdef +. +Input: + +. +Cursor: 1,2 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +ab\xe4ef +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-del-char.chk b/testsuite/tests/ed-del-char.chk new file mode 100644 index 0000000..8d897fa --- /dev/null +++ b/testsuite/tests/ed-del-char.chk @@ -0,0 +1,36 @@ +Name: ed-del-char +Test: editor +Max-cycles: 800000000 +Document: +abcdef +. +Input: + +. +Cursor: 1,3 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +abc\xe5f +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-del-line-first.chk b/testsuite/tests/ed-del-line-first.chk new file mode 100644 index 0000000..ddaebdb --- /dev/null +++ b/testsuite/tests/ed-del-line-first.chk @@ -0,0 +1,38 @@ +Name: ed-del-line-first +Test: editor +Max-cycles: 800000000 +Document: +abc +def +ghi +. +Input: + +. +Cursor: 1,0 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xe4ef +ghi +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-del-line-last.chk b/testsuite/tests/ed-del-line-last.chk new file mode 100644 index 0000000..18e236f --- /dev/null +++ b/testsuite/tests/ed-del-line-last.chk @@ -0,0 +1,38 @@ +Name: ed-del-line-last +Test: editor +Max-cycles: 800000000 +Document: +abc +def +ghi +. +Input: +^G3 +. +Cursor: 1,0 +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\x92\x92\x92\x92\x92\x92\x92 +\xe4ef +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-del-line-only.chk b/testsuite/tests/ed-del-line-only.chk new file mode 100644 index 0000000..f47a564 --- /dev/null +++ b/testsuite/tests/ed-del-line-only.chk @@ -0,0 +1,36 @@ +Name: ed-del-line-only +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: + +. +Cursor: 1,0 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-return-end.chk b/testsuite/tests/ed-return-end.chk new file mode 100644 index 0000000..9483a11 --- /dev/null +++ b/testsuite/tests/ed-return-end.chk @@ -0,0 +1,36 @@ +Name: ed-return-end +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: +^E +. +Cursor: 2,0 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\x92\x92\x92\x92\x92\x92\x92 +abc +\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-return-last-row.chk b/testsuite/tests/ed-return-last-row.chk new file mode 100644 index 0000000..7208b74 --- /dev/null +++ b/testsuite/tests/ed-return-last-row.chk @@ -0,0 +1,34 @@ +Name: ed-return-last-row +Test: editor +Max-cycles: 800000000 +Document: fixtures/lines33.txt +Input: +^G1^I +. +Cursor: 23,0 +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\xb4\x92\x92\x92\x92\x92\x92 +line 02 +line 03 +line 04 +line 05 +line 06 +line 07 +line 08 +line 09 +line 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 +line 21 +line 22 + +\xecine 23 +. diff --git a/testsuite/tests/ed-return-start.chk b/testsuite/tests/ed-return-start.chk new file mode 100644 index 0000000..894ea14 --- /dev/null +++ b/testsuite/tests/ed-return-start.chk @@ -0,0 +1,36 @@ +Name: ed-return-start +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: + +. +Cursor: 2,0 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\x92\x92\x92\x92\x92\x92\x92 + +\xe1bc +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-typing-254.chk b/testsuite/tests/ed-typing-254.chk new file mode 100644 index 0000000..553d97a --- /dev/null +++ b/testsuite/tests/ed-typing-254.chk @@ -0,0 +1,36 @@ +Name: ed-typing-254 +Test: editor +Max-cycles: 800000000 +Document: +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +. +Input: +Y +. +Cursor: 1,1 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +Y\xd8XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\x9f +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. From 0dcbdeacffcff99d61c9afb103b718d6ae9f58f8 Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 21:21:42 -0300 Subject: [PATCH 12/15] testsuite: Add editor file command tests Add tests for Ctrl-S save, Ctrl-L load, Ctrl-N new, discarding changes on new, the load error path, the quit save prompt and editing a filename in the save prompt. Save tests verify the complete source file with Check. --- testsuite/tests/ed-filename-edit.chk | 39 ++++++++++++++++++++++++++++ testsuite/tests/ed-load-error.chk | 37 ++++++++++++++++++++++++++ testsuite/tests/ed-load.chk | 37 ++++++++++++++++++++++++++ testsuite/tests/ed-new-discard.chk | 36 +++++++++++++++++++++++++ testsuite/tests/ed-new.chk | 36 +++++++++++++++++++++++++ testsuite/tests/ed-quit-prompt.chk | 36 +++++++++++++++++++++++++ testsuite/tests/ed-save-source.chk | 39 ++++++++++++++++++++++++++++ testsuite/tests/load.txt | 2 ++ 8 files changed, 262 insertions(+) create mode 100644 testsuite/tests/ed-filename-edit.chk create mode 100644 testsuite/tests/ed-load-error.chk create mode 100644 testsuite/tests/ed-load.chk create mode 100644 testsuite/tests/ed-new-discard.chk create mode 100644 testsuite/tests/ed-new.chk create mode 100644 testsuite/tests/ed-quit-prompt.chk create mode 100644 testsuite/tests/ed-save-source.chk create mode 100644 testsuite/tests/load.txt diff --git a/testsuite/tests/ed-filename-edit.chk b/testsuite/tests/ed-filename-edit.chk new file mode 100644 index 0000000..7d06876 --- /dev/null +++ b/testsuite/tests/ed-filename-edit.chk @@ -0,0 +1,39 @@ +Name: ed filename edit +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: +^ST.BAXS +. +Cursor: 1,0 +Check: T.BAS +abc +. +Screen: +\x92\x92\xc4\xba\xd4\xae\xc2\xc1\xd3\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92 +\xe1bc +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-load-error.chk b/testsuite/tests/ed-load-error.chk new file mode 100644 index 0000000..da0bf50 --- /dev/null +++ b/testsuite/tests/ed-load-error.chk @@ -0,0 +1,37 @@ +Name: ed load error +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: +^Lmissing +. +Cursor: 1,0 +Expect-out: I/O ERROR! +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-load.chk b/testsuite/tests/ed-load.chk new file mode 100644 index 0000000..23f7d31 --- /dev/null +++ b/testsuite/tests/ed-load.chk @@ -0,0 +1,37 @@ +Name: ed load +Test: editor +Max-cycles: 800000000 +Document: none +Files: +load.txt +. +Input: +^Lload.txt +. +Cursor: 1,0 +Screen: +\x92\x92\xc4\xba\xec\xef\xe1\xe4\xae\xf4\xf8\xf4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xecoaded line 1 +loaded line 2 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-new-discard.chk b/testsuite/tests/ed-new-discard.chk new file mode 100644 index 0000000..fcd02eb --- /dev/null +++ b/testsuite/tests/ed-new-discard.chk @@ -0,0 +1,36 @@ +Name: ed new discard +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: +X^N^C +. +Cursor: 1,0 +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-new.chk b/testsuite/tests/ed-new.chk new file mode 100644 index 0000000..5943232 --- /dev/null +++ b/testsuite/tests/ed-new.chk @@ -0,0 +1,36 @@ +Name: ed new +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: +^N +. +Cursor: 1,0 +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-quit-prompt.chk b/testsuite/tests/ed-quit-prompt.chk new file mode 100644 index 0000000..f356b20 --- /dev/null +++ b/testsuite/tests/ed-quit-prompt.chk @@ -0,0 +1,36 @@ +Name: ed quit prompt +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: +X^Q +. +Expect-out: Save +Screen: +Save? D:\xa0 +Xabc +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-save-source.chk b/testsuite/tests/ed-save-source.chk new file mode 100644 index 0000000..64b4c61 --- /dev/null +++ b/testsuite/tests/ed-save-source.chk @@ -0,0 +1,39 @@ +Name: ed save source +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: +^ST.BAS +. +Cursor: 1,0 +Check: T.BAS +abc +. +Screen: +\x92\x92\xc4\xba\xd4\xae\xc2\xc1\xd3\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92 +\xe1bc +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/load.txt b/testsuite/tests/load.txt new file mode 100644 index 0000000..73d01e0 --- /dev/null +++ b/testsuite/tests/load.txt @@ -0,0 +1,2 @@ +loaded line 1 +loaded line 2 From 51a9ca2d5045bef59d96649a655b0e22b00707b0 Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 21:22:09 -0300 Subject: [PATCH 13/15] testsuite: Add character and copy/undo edge tests Add tests for ESC verbatim input, inverse/graphical characters, undo after delete, copying before the mark, an 80-column scrolled line, returning the horizontal window to column 0, and a parse error on line 1. --- testsuite/tests/ed-copy-before-mark.chk | 39 +++++++++++++++++++++++++ testsuite/tests/ed-escape.chk | 36 +++++++++++++++++++++++ testsuite/tests/ed-hscroll-back.chk | 36 +++++++++++++++++++++++ testsuite/tests/ed-insert-inverse.chk | 36 +++++++++++++++++++++++ testsuite/tests/ed-syntax-line1.chk | 37 +++++++++++++++++++++++ testsuite/tests/ed-undo-delete.chk | 36 +++++++++++++++++++++++ testsuite/tests/ed-wrap-80.chk | 36 +++++++++++++++++++++++ 7 files changed, 256 insertions(+) create mode 100644 testsuite/tests/ed-copy-before-mark.chk create mode 100644 testsuite/tests/ed-escape.chk create mode 100644 testsuite/tests/ed-hscroll-back.chk create mode 100644 testsuite/tests/ed-insert-inverse.chk create mode 100644 testsuite/tests/ed-syntax-line1.chk create mode 100644 testsuite/tests/ed-undo-delete.chk create mode 100644 testsuite/tests/ed-wrap-80.chk diff --git a/testsuite/tests/ed-copy-before-mark.chk b/testsuite/tests/ed-copy-before-mark.chk new file mode 100644 index 0000000..d692cc1 --- /dev/null +++ b/testsuite/tests/ed-copy-before-mark.chk @@ -0,0 +1,39 @@ +Name: ed-copy-before-mark +Test: editor +Max-cycles: 800000000 +Document: +A +B +C +D +. +Input: +^G3^C^G1^V +. +Cursor: 2,0 +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb2\x92\x92\x92\x92\x92\x92\x92 +A +\xc3 +B +C +D +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-escape.chk b/testsuite/tests/ed-escape.chk new file mode 100644 index 0000000..8ed4d44 --- /dev/null +++ b/testsuite/tests/ed-escape.chk @@ -0,0 +1,36 @@ +Name: ed-escape +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: +^A +. +Cursor: 1,1 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\x01\xe1bc +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-hscroll-back.chk b/testsuite/tests/ed-hscroll-back.chk new file mode 100644 index 0000000..ad4a414 --- /dev/null +++ b/testsuite/tests/ed-hscroll-back.chk @@ -0,0 +1,36 @@ +Name: ed-hscroll-back +Test: editor +Max-cycles: 800000000 +Document: +AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEE +. +Input: +^E^A +. +Cursor: 1,0 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xc1AAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDD\x9f +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-insert-inverse.chk b/testsuite/tests/ed-insert-inverse.chk new file mode 100644 index 0000000..f6166f4 --- /dev/null +++ b/testsuite/tests/ed-insert-inverse.chk @@ -0,0 +1,36 @@ +Name: ed-insert-inverse +Test: editor +Max-cycles: 800000000 +Document: +abc +. +Input: +𝗔 +. +Cursor: 1,1 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\xc1\xe1bc +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-syntax-line1.chk b/testsuite/tests/ed-syntax-line1.chk new file mode 100644 index 0000000..821b750 --- /dev/null +++ b/testsuite/tests/ed-syntax-line1.chk @@ -0,0 +1,37 @@ +Name: ed-syntax-line1 +Test: editor +Max-cycles: 800000000 +Document: +? B +. +Input: +^R +. +Cursor: 1,3 +Screen: +\x92\x92\xc4\xba\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +? B\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. +Expect-out: Parsing diff --git a/testsuite/tests/ed-undo-delete.chk b/testsuite/tests/ed-undo-delete.chk new file mode 100644 index 0000000..2aeff02 --- /dev/null +++ b/testsuite/tests/ed-undo-delete.chk @@ -0,0 +1,36 @@ +Name: ed-undo-delete +Test: editor +Max-cycles: 800000000 +Document: +abcdef +. +Input: +^Z +. +Cursor: 1,3 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +abc\xe4ef +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. diff --git a/testsuite/tests/ed-wrap-80.chk b/testsuite/tests/ed-wrap-80.chk new file mode 100644 index 0000000..0adc960 --- /dev/null +++ b/testsuite/tests/ed-wrap-80.chk @@ -0,0 +1,36 @@ +Name: ed-wrap-80 +Test: editor +Max-cycles: 800000000 +Document: +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +. +Input: +^E +. +Cursor: 1,33 +Screen: +\x92\x92\xc4\xba\xc8\xc5\xcc\xd0\xae\xd4\xd8\xd4\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\xb1\x92\x92\x92\x92\x92\x92\x92 +\x9eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xa0 +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +\xfd +. From 60b35511947e8a8f3c582d37f44c8f69e7bade2b Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 21:33:35 -0300 Subject: [PATCH 14/15] testsuite: Track generated test files for make clean Each test writes build/tests/.fbtest-manifest- from the declarations in its .chk file. Editor tests list HELP.TXT, every Files: copy, every Check:/Exists: file and the test directory; normal tests list their Check:/Exists: files. The opt-in .screen and .output debug dumps are included as known harness outputs. make clean reads those manifests and removes only the listed files. Editor test directories are removed with rmdir, so user files left in them are preserved. Manifests are written in binary mode so cleanup works on Windows with the MSYS shell tools already required by the Makefile. Check: and Exists: now work for normal tests as well as editor tests. Use this to verify the complete D:XXX output of the diskio test and record it in the manifest. --- rules.mak | 13 +++ testsuite/README.md | 22 ++++ testsuite/src/fbtest.c | 230 ++++++++++++++++++++++++++----------- testsuite/tests/diskio.chk | 4 + 4 files changed, 203 insertions(+), 66 deletions(-) diff --git a/rules.mak b/rules.mak index bff91d3..b01b740 100644 --- a/rules.mak +++ b/rules.mak @@ -43,6 +43,19 @@ clean: $(Q)rm -f $(TESTS_ATB) $(Q)rm -f $(TESTS_LBL) $(Q)rm -f $(TESTS_STAMP) + $(Q)if [ -d build/tests ]; then \ + for manifest in build/tests/.fbtest-manifest-*; do \ + [ -f "$$manifest" ] || continue; \ + while read -r kind path; do \ + case "$$kind" in \ + F) rm -f "$$path" ;; \ + D) rmdir "$$path" 2>/dev/null || true ;; \ + esac; \ + done < "$$manifest"; \ + rm -f "$$manifest"; \ + done; \ + fi + $(Q)rmdir build/tests 2>/dev/null || true $(Q)rm -f $(RUNTEST_OBJS) $(RUNTEST) $(RUNTEST_OBJS:.o=.d) .PHONY: distclean diff --git a/testsuite/README.md b/testsuite/README.md index ff3604a..94c027f 100644 --- a/testsuite/README.md +++ b/testsuite/README.md @@ -228,6 +228,28 @@ Both dump variables are switches; the files are always written under `build/tests/`. +Cleanup +------- + +Each test writes a cleanup manifest derived from its `.chk` declarations: + + build/tests/.fbtest-manifest- + +For editor tests the manifest contains `HELP.TXT` (from `Document:`), every +`Files:` copy, every `Check:` and `Exists:` file, and the test directory. For +normal tests it contains the `Check:` and `Exists:` files. The opt-in +`.screen` / `.output` debug dumps are also listed. + +`make clean` reads those manifests and removes only the listed files. +Editor test directories are removed with `rmdir`, so a directory that still +contains user files is left in place. Manifests are written in binary mode so +the same cleanup works on Windows with the MSYS shell tools used by the rest of +the Makefile. + +Every file written by a test must therefore be declared with `Document:`, +`Files:`, `Check:`, or `Exists:`. + + Architecture ------------ diff --git a/testsuite/src/fbtest.c b/testsuite/src/fbtest.c index 9331c1e..e8d703f 100644 --- a/testsuite/src/fbtest.c +++ b/testsuite/src/fbtest.c @@ -325,6 +325,120 @@ static void unlink_in(const char *dir, const char *name) } } +// A manifest lists the paths created by one test. make clean reads these +// manifests and removes only the listed files; directories are removed with +// rmdir, so user files left in a test directory are preserved. +static FILE *manifest_open(const char *tag_name) +{ + char *path; + if (asprintf(&path, "%s/.fbtest-manifest-%s", output_dir, tag_name) < 0) + return NULL; + FILE *f = fopen(path, "wb"); + free(path); + return f; +} + +static void manifest_add(FILE *f, char kind, const char *path) +{ + fprintf(f, "%c %s\n", kind, path); +} + +static void manifest_add_file(FILE *f, const char *dir, const char *name) +{ + char *path; + if (asprintf(&path, "%s/%s", dir, name) >= 0) + { + manifest_add(f, 'F', path); + free(path); + } +} + +// Debug dumps use fixed names derived from the test tag. +static void manifest_add_debug(FILE *f, const char *tag_name) +{ + char *path; + if (asprintf(&path, "%s/%s.screen", output_dir, tag_name) >= 0) + { + manifest_add(f, 'F', path); + free(path); + } + if (asprintf(&path, "%s/%s.output", output_dir, tag_name) >= 0) + { + manifest_add(f, 'F', path); + free(path); + } +} + +struct file_check +{ + char *name; + char *data; + size_t len; +}; + +// Checks Check:/Exists: files in the given DOS root. +static int check_test_files(const char *fname, const char *dir, + const struct file_check *checks, int nchecks, + char **exists, int nexists) +{ + for (int i = 0; i < nchecks; i++) + { + char *path; + if (asprintf(&path, "%s/%s", dir, checks[i].name) < 0) + exit(1); + FILE *fc = fopen(path, "rb"); + free(path); + char *got = 0; + size_t rlen = 0; + if (fc) + { + fseek(fc, 0, SEEK_END); + long sz = ftell(fc); + fseek(fc, 0, SEEK_SET); + got = calloc(sz + 1, 1); + rlen = fread(got, 1, sz, fc); + fclose(fc); + } + if (!got) + { + fprintf(stderr, "%s: can't open checked file '%s'.\n", + fname, checks[i].name); + return -1; + } + else if (rlen != checks[i].len || memcmp(got, checks[i].data, rlen)) + { + size_t d = 0; + while (d < rlen && d < checks[i].len && + got[d] == checks[i].data[d]) + d++; + fprintf(stderr, + "%s: file '%s' does not match: %lu bytes vs %lu " + "expected, first difference at byte %lu.\n", + fname, checks[i].name, (unsigned long)rlen, + (unsigned long)checks[i].len, (unsigned long)d); + free(got); + return -1; + } + free(got); + } + + for (int i = 0; i < nexists; i++) + { + char *path; + if (asprintf(&path, "%s/%s", dir, exists[i]) < 0) + exit(1); + FILE *fx = fopen(path, "rb"); + free(path); + if (!fx) + { + fprintf(stderr, "%s: file '%s' not created.\n", fname, exists[i]); + return -1; + } + fclose(fx); + } + return 0; +} + // Converts ASCII file to ATASCII static int atascii_convert(const char *infile, const char *outfile) { @@ -742,13 +856,8 @@ int fbtest(const char *fname) char *doc_filename = 0, *doc_data = 0; size_t doc_len = 0; size_t input_len = 0; - // Expected content of files written during an editor session - struct - { - char *name; - char *data; - size_t len; - } checks[16]; + // Expected content of files written during the run + struct file_check checks[16]; int nchecks = 0; // Names of files that must exist after the run char *exists[16]; @@ -1161,6 +1270,21 @@ int fbtest(const char *fname) int test_ok = 0; char *cmd_out = calloc(65536, 1); + // Write the cleanup manifest from the declarations in the .chk file. + if (!(test & test_editor)) + { + FILE *manifest = manifest_open(tag_name); + if (manifest) + { + for (int i = 0; i < nchecks; i++) + manifest_add_file(manifest, output_dir, checks[i].name); + for (int i = 0; i < nexists; i++) + manifest_add_file(manifest, output_dir, exists[i]); + manifest_add_debug(manifest, tag_name); + fclose(manifest); + } + } + do { if (0 != (test & test_editor)) @@ -1179,6 +1303,23 @@ int fbtest(const char *fname) if (asprintf(&help_txt, "%s/HELP.TXT", test_root) < 0) exit(1); + // Write the cleanup manifest from the declarations in the .chk + // file, before running anything. + FILE *manifest = manifest_open(tag_name); + if (manifest) + { + manifest_add(manifest, 'F', help_txt); + for (int i = 0; i < nfiles; i++) + manifest_add_file(manifest, test_root, files[i]); + for (int i = 0; i < nchecks; i++) + manifest_add_file(manifest, test_root, checks[i].name); + for (int i = 0; i < nexists; i++) + manifest_add_file(manifest, test_root, exists[i]); + manifest_add_debug(manifest, tag_name); + manifest_add(manifest, 'D', test_root); + fclose(manifest); + } + // Setup the initial document and extra files in the DOS root. int e = 0; unlink(help_txt); @@ -1333,70 +1474,15 @@ int fbtest(const char *fname) } } - // Check the contents of files written during the session - for (int i = 0; !e && i < nchecks; i++) - { - char *path; - if (asprintf(&path, "%s/%s", test_root, checks[i].name) < 0) - exit(1); - FILE *fc = fopen(path, "rb"); - free(path); - char *got = 0; - size_t rlen = 0; - if (fc) - { - fseek(fc, 0, SEEK_END); - long sz = ftell(fc); - fseek(fc, 0, SEEK_SET); - got = calloc(sz + 1, 1); - rlen = fread(got, 1, sz, fc); - fclose(fc); - } - if (!got) - { - fprintf(stderr, "%s: can't open checked file '%s'.\n", - fname, checks[i].name); - e = -1; - } - else if (rlen != checks[i].len || - memcmp(got, checks[i].data, rlen)) - { - size_t d = 0; - while (d < rlen && d < checks[i].len && - got[d] == checks[i].data[d]) - d++; - fprintf(stderr, - "%s: file '%s' does not match: %lu bytes vs %lu " - "expected, first difference at byte %lu.\n", - fname, checks[i].name, (unsigned long)rlen, - (unsigned long)checks[i].len, (unsigned long)d); - e = -1; - } - free(got); - } - - // Check that files were created during the session - for (int i = 0; !e && i < nexists; i++) - { - char *path; - if (asprintf(&path, "%s/%s", test_root, exists[i]) < 0) - exit(1); - FILE *fx = fopen(path, "rb"); - free(path); - if (!fx) - { - fprintf(stderr, "%s: file '%s' not created.\n", - fname, exists[i]); - e = -1; - } - else - fclose(fx); - } + if (!e) + e = check_test_files(fname, test_root, checks, nchecks, + exists, nexists); if (e > 0) fprintf(stderr, "%s: unexpected stack value.\n", fname); else if (e < 0) fprintf(stderr, "%s: error on editor execution.\n", fname); + free(out); free(help_txt); free(test_root); @@ -1418,6 +1504,8 @@ int fbtest(const char *fname) // Now, runs and checks XEX if (run_test_xex(comname, input_buf, expected_out, max_cycles, 0)) break; + if (check_test_files(fname, output_dir, checks, nchecks, exists, nexists)) + break; } } if (0 != (test & test_native) && 0 != (test & test_int)) @@ -1435,6 +1523,8 @@ int fbtest(const char *fname) // Now, runs and checks XEX if (run_test_xex(comname, input_buf, expected_out, max_cycles, 0)) break; + if (check_test_files(fname, output_dir, checks, nchecks, exists, nexists)) + break; } } if (0 != (test & test_fp) && 0 != (test & test_cross)) @@ -1455,6 +1545,8 @@ int fbtest(const char *fname) // Now, runs and checks XEX if (run_test_xex(xexname, input_buf, expected_out, max_cycles, 0)) break; + if (check_test_files(fname, output_dir, checks, nchecks, exists, nexists)) + break; } // CARTRIDGE version: @@ -1473,6 +1565,8 @@ int fbtest(const char *fname) // Now, runs and checks ROM if (run_test_xex(romname, input_buf, expected_out, max_cycles, 1)) break; + if (check_test_files(fname, output_dir, checks, nchecks, exists, nexists)) + break; } } if (0 != (test & test_int) && 0 != (test & test_cross)) @@ -1493,6 +1587,8 @@ int fbtest(const char *fname) // Now, runs and checks XEX if (run_test_xex(xexname, input_buf, expected_out, max_cycles, 0)) break; + if (check_test_files(fname, output_dir, checks, nchecks, exists, nexists)) + break; } // CARTRIDGE version: @@ -1511,6 +1607,8 @@ int fbtest(const char *fname) // Now, runs and checks ROM if (run_test_xex(romname, input_buf, expected_out, max_cycles, 1)) break; + if (check_test_files(fname, output_dir, checks, nchecks, exists, nexists)) + break; } } diff --git a/testsuite/tests/diskio.chk b/testsuite/tests/diskio.chk index c7acccd..427f81e 100644 --- a/testsuite/tests/diskio.chk +++ b/testsuite/tests/diskio.chk @@ -1,5 +1,9 @@ Name: Check D: input/output Test: run +Check: XXX +Big 1: +Big 2: +. Output: Start 1 From e8fbc7ca33862c8b97c92cdbda0f9ef1701b161b Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sun, 18 Oct 2026 21:54:29 -0300 Subject: [PATCH 15/15] testsuite: Avoid strndup conflict on Windows Use a private xstrndup helper instead of defining strndup, which is now declared by newer MinGW headers. --- testsuite/src/fbtest.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/testsuite/src/fbtest.c b/testsuite/src/fbtest.c index e8d703f..2a9cf50 100644 --- a/testsuite/src/fbtest.c +++ b/testsuite/src/fbtest.c @@ -33,19 +33,26 @@ # include # define MKDIR(path) _mkdir(path) # define PATH_SEP "\\" -// Implement missing "strndup" -static char *strndup(const char *s, size_t n) -{ - char *ret = malloc(n + 1); - strncpy(ret, s, n); - ret[n] = 0; - return ret; -}; #else # define MKDIR(path) mkdir(path, 0777) # define PATH_SEP "/" #endif +// Private strndup replacement: avoid conflicting with libc/POSIX strndup, +// which newer MinGW headers now declare. +static char *xstrndup(const char *s, size_t n) +{ + size_t len = 0; + while (len < n && s[len]) + len++; + char *ret = malloc(len + 1); + if (!ret) + return NULL; + memcpy(ret, s, len); + ret[len] = 0; + return ret; +} + // Flags static int verbose; static const char *fb_atari_comp_int = "build/bin/fbci.xex"; @@ -1233,7 +1240,7 @@ int fbtest(const char *fname) ext = fname + strlen(fname); // base_name: file name without extension - char *base_name = strndup(fname, ext - fname); + char *base_name = xstrndup(fname, ext - fname); // tag_name: file name without directory and extension char *tag_name = strrchr(base_name, '/'); @@ -1244,7 +1251,7 @@ int fbtest(const char *fname) // srcdir: directory containing the test files char *srcdir = (tag_name != base_name) ? - strndup(fname, tag_name - base_name - 1) : strdup("."); + xstrndup(fname, tag_name - base_name - 1) : strdup("."); // basname: input basic file name char *basname = source_fname(base_name, "bas");