Add DOS build script, compiler/DOS test harnesses, FreeDOS package, CI
- build_dos.sh: Linux-friendly cross-compile to DOS via OpenWatcom V2.
OpenWatcom's wmake on Linux can't apply the .c.obj implicit rule for
subdirectory paths, and Makefile.dos / Makefile.dos16 rely on DOS-
only commands like 'del'. Script invokes wcc / wcc386 directly,
tracks 16-bit vs 32-bit mode via a stamp file (auto-cleans on
switch), generates a wlink directive file (the brace-delimited file
list wouldn't survive shell quoting), and supports clean. The DOS
Makefiles still work on Windows / DOS hosts.
- tests/run_compiler_tests.sh: AOT compiler harness. For each .bas
in tests/programs/, compiles via gwbasic-compile -c, runs the
resulting executable, normalizes output and diffs against the
golden file from tests/expected/. Skip list covers chain/common
multi-file flows, hardware/timing-dependent programs, unnumbered
direct-mode programs (compiler requires line numbers), and
misc_stmts/run_file (interpreter-vs-compiler ON ERROR divergence).
Result: 56/56 pass.
- tests/run_dos_smoke.sh + dos_smoke.bas + expected: runs gwbasic16.exe
under DOSBox-X (flatpak) with a program that exercises arithmetic,
strings, control flow, GOSUB, FOR/NEXT, DATA/READ, DEF FN, OPEN/
PRINT#/CLOSE, and diffs against the interpreter's golden output.
Uses $HOME for the staging dir (DOSBox-X flatpak doesn't see /tmp).
- pkg/GWBASIC.LSM + pkg/build_pkg.sh: FreeDOS submission package.
Produces dist/gwbasic-<VERSION>.zip with the standard FreeDOS
layout (APPINFO/GWBASIC.LSM, BIN/GWBASIC.EXE, DOC/GWBASIC/{README,
CHANGES,LICENSE} with CRLF, SOURCE/GWBASIC/<full source>). Source
tree is filtered through git ls-files to exclude build artifacts.
- docs/Makefile: standard Sphinx Makefile so 'cd docs && make html'
works as documented in README.md.
- .github/workflows/ci.yml: split into two jobs. build-and-test now
also runs the compiler harness. New dos-cross-compile job caches
~/openwatcom-v2, downloads the OpenWatcom V2 snapshot if not
cached, builds both 16-bit and 32-bit DOS binaries, asserts size
bounds, and uploads them as artifacts.
- .gitignore: ignore .dos_build_mode (script's stamp), .link_dir/
(transient wlink directive dir), dist/ (package output).
This commit is contained in:
57
.github/workflows/ci.yml
vendored
57
.github/workflows/ci.yml
vendored
@@ -24,5 +24,60 @@ jobs:
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
|
||||
- name: Test
|
||||
- name: Interpreter tests
|
||||
run: bash tests/run_tests.sh
|
||||
|
||||
- name: Compiler tests
|
||||
run: bash tests/run_compiler_tests.sh
|
||||
|
||||
dos-cross-compile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Cache OpenWatcom V2
|
||||
id: cache-watcom
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/openwatcom-v2
|
||||
key: openwatcom-v2-snapshot
|
||||
|
||||
- name: Install OpenWatcom V2
|
||||
if: steps.cache-watcom.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p ~/openwatcom-v2
|
||||
curl -L -o /tmp/ow-snapshot.tar.xz \
|
||||
https://github.com/open-watcom/open-watcom-v2/releases/download/Last-CI-build/ow-snapshot.tar.xz
|
||||
tar -xJf /tmp/ow-snapshot.tar.xz -C ~/openwatcom-v2
|
||||
rm -f /tmp/ow-snapshot.tar.xz
|
||||
|
||||
- name: Build 16-bit DOS target
|
||||
run: ./build_dos.sh 16
|
||||
|
||||
- name: Build 32-bit DOS target
|
||||
run: |
|
||||
./build_dos.sh clean
|
||||
./build_dos.sh 32
|
||||
|
||||
- name: Verify binary sizes look sane
|
||||
run: |
|
||||
test -f gwbasic.exe
|
||||
# 32-bit LE executable; should be a few hundred KB
|
||||
size=$(stat -c%s gwbasic.exe)
|
||||
echo "gwbasic.exe = $size bytes"
|
||||
[ "$size" -gt 100000 ] && [ "$size" -lt 500000 ]
|
||||
# Rebuild 16-bit too for the artifact upload
|
||||
./build_dos.sh clean
|
||||
./build_dos.sh 16
|
||||
test -f gwbasic16.exe
|
||||
size16=$(stat -c%s gwbasic16.exe)
|
||||
echo "gwbasic16.exe = $size16 bytes"
|
||||
[ "$size16" -gt 80000 ] && [ "$size16" -lt 200000 ]
|
||||
|
||||
- name: Upload DOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dos-binaries
|
||||
path: |
|
||||
gwbasic16.exe
|
||||
gwbasic.exe
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,6 +7,9 @@ docs/_build/
|
||||
*.err
|
||||
*.lib
|
||||
.tab-color
|
||||
.dos_build_mode
|
||||
.link_dir/
|
||||
dist/
|
||||
gwbasic_*.txt
|
||||
gwbasic_*.dat
|
||||
gwbasic_*.bas
|
||||
|
||||
98
build_dos.sh
Executable file
98
build_dos.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# build_dos.sh -- Cross-compile GW-BASIC 2026 to DOS using OpenWatcom V2.
|
||||
#
|
||||
# Usage:
|
||||
# ./build_dos.sh # 16-bit real-mode, produces gwbasic16.exe
|
||||
# ./build_dos.sh 32 # 32-bit DOS/4GW, produces gwbasic.exe
|
||||
# ./build_dos.sh clean # remove .obj files and DOS executables
|
||||
#
|
||||
# OpenWatcom's wmake on Linux struggles with the .c.obj implicit rule for
|
||||
# subdirectory paths, so this script invokes wcc/wcc386 directly. On a
|
||||
# Windows or DOS host, use Makefile.dos / Makefile.dos16 with wmake instead.
|
||||
#
|
||||
# Requires: OpenWatcom V2 with $WATCOM and binl64 (or binl) on PATH.
|
||||
# Source ~/openwatcom-v2/setvars.sh first if not already.
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$WATCOM" ]; then
|
||||
if [ -f "$HOME/openwatcom-v2/setvars.sh" ]; then
|
||||
. "$HOME/openwatcom-v2/setvars.sh"
|
||||
else
|
||||
echo "Error: WATCOM not set and ~/openwatcom-v2/setvars.sh not found." >&2
|
||||
echo "Install OpenWatcom V2 and set WATCOM to its root." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
INTERP_C=(
|
||||
src/main.c src/tokens.c src/tokenizer.c src/error.c
|
||||
src/eval.c src/interp.c src/vars.c src/arrays.c
|
||||
src/input.c src/math_int.c src/math_float.c
|
||||
src/math_transcend.c src/strings.c src/print.c
|
||||
src/fileio.c src/program_io.c src/print_using.c
|
||||
src/graphics.c src/virmem.c src/portio.c src/strpool.c
|
||||
src/sound.c src/tui.c platform/hal_dos.c
|
||||
)
|
||||
|
||||
case "${1:-16}" in
|
||||
clean)
|
||||
rm -f src/*.obj platform/*.obj gwbasic.exe gwbasic16.exe gwbascom.exe gwrt.lib .dos_build_mode
|
||||
echo "cleaned"
|
||||
exit 0
|
||||
;;
|
||||
16)
|
||||
CC=wcc
|
||||
CFLAGS="-bt=dos -mm -ox -w4 -zq -za99 -Iinclude -D__MSDOS__"
|
||||
EXE=gwbasic16.exe
|
||||
LINK_SYSTEM="dos option stack=8192"
|
||||
;;
|
||||
32)
|
||||
CC=wcc386
|
||||
CFLAGS="-bt=dos -mf -ox -w4 -zq -za99 -Iinclude -D__MSDOS__"
|
||||
EXE=gwbasic.exe
|
||||
LINK_SYSTEM="dos4g"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [16|32|clean]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Track which mode the .obj files were built for; clean if it differs. 16-bit
|
||||
# .obj from wcc and 32-bit .obj from wcc386 share names but cannot mix in one
|
||||
# link.
|
||||
MODE_STAMP=.dos_build_mode
|
||||
PREV_MODE=""
|
||||
[ -f "$MODE_STAMP" ] && PREV_MODE=$(cat "$MODE_STAMP")
|
||||
if [ -n "$PREV_MODE" ] && [ "$PREV_MODE" != "$1" ] && [ "$PREV_MODE" != "${1:-16}" ]; then
|
||||
echo " -- previous build was $PREV_MODE, cleaning"
|
||||
rm -f src/*.obj platform/*.obj
|
||||
fi
|
||||
echo "${1:-16}" > "$MODE_STAMP"
|
||||
|
||||
OBJS=()
|
||||
for c in "${INTERP_C[@]}"; do
|
||||
obj="${c%.c}.obj"
|
||||
OBJS+=("$obj")
|
||||
if [ "$c" -nt "$obj" ] || [ ! -f "$obj" ]; then
|
||||
printf " CC %s\n" "$c"
|
||||
$CC $CFLAGS -fo="$obj" "$c"
|
||||
fi
|
||||
done
|
||||
|
||||
printf " LD %s\n" "$EXE"
|
||||
LINK_DIR=$(dirname "$0")/.link_dir
|
||||
mkdir -p "$LINK_DIR"
|
||||
LINK_SCRIPT="$LINK_DIR/link.lnk"
|
||||
{
|
||||
echo "system $LINK_SYSTEM"
|
||||
echo "name $EXE"
|
||||
for o in "${OBJS[@]}"; do
|
||||
echo "file $o"
|
||||
done
|
||||
} > "$LINK_SCRIPT"
|
||||
wlink @"$LINK_SCRIPT"
|
||||
rm -rf "$LINK_DIR"
|
||||
|
||||
ls -la "$EXE"
|
||||
14
docs/Makefile
Normal file
14
docs/Makefile
Normal file
@@ -0,0 +1,14 @@
|
||||
# Minimal Sphinx Makefile.
|
||||
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
22
pkg/GWBASIC.LSM
Normal file
22
pkg/GWBASIC.LSM
Normal file
@@ -0,0 +1,22 @@
|
||||
Begin3
|
||||
Title: GW-BASIC 2026
|
||||
Version: 0.17.0
|
||||
Entered-date: 2026-04-10
|
||||
Description: A portable C reimplementation of Microsoft GW-BASIC, using
|
||||
the original 8088 assembly source (released by Microsoft
|
||||
in 2020) as the authoritative reference. Targets bug-
|
||||
compatible behavior with classic GW-BASIC programs.
|
||||
Includes a full-screen TUI editor that renders through
|
||||
BIOS INT 10h on bare FreeDOS (no ANSI.SYS required), 100%
|
||||
token coverage (all 144 GW-BASIC tokens), MBF on-disk file
|
||||
format compatibility, and an ahead-of-time compiler
|
||||
(Linux/POSIX builds only).
|
||||
Keywords: BASIC, GW-BASIC, Microsoft, interpreter, retrocomputing
|
||||
Author: Eremey Valetov <evv@msu.edu>
|
||||
Maintained-by: Eremey Valetov <evv@msu.edu>
|
||||
Primary-site: https://github.com/evvaletov/gw-basic-2026
|
||||
Original-site: https://github.com/microsoft/GW-BASIC
|
||||
Platforms: DOS, FreeDOS 1.4 (16-bit real mode), MS-DOS 5+, DOS/4GW
|
||||
(32-bit protected mode); also Linux, macOS, BSD via CMake
|
||||
Copying-policy: MIT License
|
||||
End
|
||||
66
pkg/build_pkg.sh
Executable file
66
pkg/build_pkg.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
# Build a FreeDOS-ready package for GW-BASIC 2026.
|
||||
#
|
||||
# Produces dist/gwbasic-<VERSION>.zip with the layout FreeDOS expects:
|
||||
# APPINFO/GWBASIC.LSM (Linux Software Map metadata)
|
||||
# BIN/GWBASIC.EXE (16-bit real-mode interpreter)
|
||||
# DOC/GWBASIC/README (project README, CRLF)
|
||||
# DOC/GWBASIC/CHANGES (version history, CRLF)
|
||||
# DOC/GWBASIC/LICENSE (MIT, CRLF)
|
||||
# SOURCE/GWBASIC/<...> (full source tree, optional)
|
||||
#
|
||||
# Run from the project root: ./pkg/build_pkg.sh
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
VERSION=$(grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' include/gwbasic.h | tr -d '"')
|
||||
[ -n "$VERSION" ] || { echo "Cannot determine version from include/gwbasic.h" >&2; exit 1; }
|
||||
|
||||
echo "==> Packaging GW-BASIC 2026 v$VERSION"
|
||||
|
||||
if [ ! -f gwbasic16.exe ] || [ src/main.c -nt gwbasic16.exe ]; then
|
||||
echo "==> Building gwbasic16.exe"
|
||||
./build_dos.sh clean
|
||||
./build_dos.sh 16
|
||||
fi
|
||||
|
||||
STAGE=$(mktemp -d --tmpdir="$HOME" gw_pkg.XXXXXX)
|
||||
trap 'rm -rf "$STAGE"' EXIT
|
||||
|
||||
mkdir -p "$STAGE/APPINFO" "$STAGE/BIN" "$STAGE/DOC/GWBASIC" "$STAGE/SOURCE/GWBASIC"
|
||||
|
||||
# Metadata
|
||||
cp pkg/GWBASIC.LSM "$STAGE/APPINFO/GWBASIC.LSM"
|
||||
unix2dos -q "$STAGE/APPINFO/GWBASIC.LSM" 2>/dev/null \
|
||||
|| sed -i 's/$/\r/' "$STAGE/APPINFO/GWBASIC.LSM"
|
||||
|
||||
# Binary
|
||||
cp gwbasic16.exe "$STAGE/BIN/GWBASIC.EXE"
|
||||
|
||||
# Documentation (DOS line endings, 8.3-friendly names)
|
||||
cp README.md "$STAGE/DOC/GWBASIC/README"
|
||||
cp CHANGES.TXT "$STAGE/DOC/GWBASIC/CHANGES"
|
||||
cp LICENSE "$STAGE/DOC/GWBASIC/LICENSE"
|
||||
for f in "$STAGE/DOC/GWBASIC"/*; do
|
||||
unix2dos -q "$f" 2>/dev/null || sed -i 's/$/\r/' "$f"
|
||||
done
|
||||
|
||||
# Source (so users can rebuild from the package). Follow git's tracked-files
|
||||
# list to avoid bundling build/, _build/, *.obj, etc.
|
||||
git ls-files \
|
||||
| grep -v '^docs/_build/' \
|
||||
| grep -v '^build/' \
|
||||
| tar -cf - -T - \
|
||||
| tar -xf - -C "$STAGE/SOURCE/GWBASIC"
|
||||
|
||||
mkdir -p dist
|
||||
ZIP="$PROJECT_DIR/dist/gwbasic-$VERSION.zip"
|
||||
rm -f "$ZIP"
|
||||
( cd "$STAGE" && zip -rq "$ZIP" APPINFO BIN DOC SOURCE )
|
||||
|
||||
echo
|
||||
echo "==> Wrote $ZIP"
|
||||
unzip -l "$ZIP" | tail -8
|
||||
51
tests/dos_smoke.bas
Normal file
51
tests/dos_smoke.bas
Normal file
@@ -0,0 +1,51 @@
|
||||
10 REM DOS smoke test for gwbasic16.exe -- exercises arithmetic, strings,
|
||||
20 REM control flow, GOSUB, FOR/NEXT, DATA/READ, file I/O, MID$ assignment.
|
||||
30 REM Output is captured via OPEN/PRINT# so the host can compare against
|
||||
40 REM tests/expected/dos_smoke.expected.
|
||||
50 OPEN "O",#1,"OUT.TXT"
|
||||
60 REM --- 1. Arithmetic
|
||||
70 PRINT #1, "ARITH"
|
||||
80 PRINT #1, 2+2*3
|
||||
90 PRINT #1, (2+2)*3
|
||||
100 PRINT #1, 100\3, 100 MOD 3
|
||||
110 PRINT #1, 2^10
|
||||
120 REM --- 2. Strings
|
||||
130 PRINT #1, "STRINGS"
|
||||
140 A$ = "HELLO" + " " + "WORLD"
|
||||
150 PRINT #1, A$
|
||||
160 PRINT #1, LEN(A$); LEFT$(A$,5); RIGHT$(A$,5); MID$(A$,7,5)
|
||||
170 MID$(A$,7,5) = "BASIC"
|
||||
180 PRINT #1, A$
|
||||
190 REM --- 3. Control flow
|
||||
200 PRINT #1, "CONTROL"
|
||||
210 FOR I = 1 TO 5
|
||||
220 PRINT #1, "FOR"; I
|
||||
230 NEXT I
|
||||
240 J = 0
|
||||
250 WHILE J < 3
|
||||
260 J = J + 1
|
||||
270 PRINT #1, "WHILE"; J
|
||||
280 WEND
|
||||
290 GOSUB 1000
|
||||
300 REM --- 4. DATA/READ
|
||||
310 PRINT #1, "DATA"
|
||||
320 RESTORE 900
|
||||
330 FOR K = 1 TO 4
|
||||
340 READ X
|
||||
350 PRINT #1, "X="; X
|
||||
360 NEXT K
|
||||
370 REM --- 5. DEF FN
|
||||
380 PRINT #1, "DEFFN"
|
||||
390 DEF FN SQUARE(N) = N*N
|
||||
400 PRINT #1, FN SQUARE(7)
|
||||
410 PRINT #1, FN SQUARE(13)
|
||||
420 REM --- 6. Conditionals
|
||||
430 PRINT #1, "IF"
|
||||
440 IF 5 > 3 THEN PRINT #1, "T1"
|
||||
450 IF 5 < 3 THEN PRINT #1, "F1" ELSE PRINT #1, "T2"
|
||||
460 PRINT #1, "DONE"
|
||||
470 CLOSE #1
|
||||
480 END
|
||||
900 DATA 10, 20, 30, 40
|
||||
1000 PRINT #1, "GOSUB OK"
|
||||
1010 RETURN
|
||||
31
tests/expected/dos_smoke.expected
Normal file
31
tests/expected/dos_smoke.expected
Normal file
@@ -0,0 +1,31 @@
|
||||
ARITH
|
||||
8
|
||||
12
|
||||
33 , 1
|
||||
1024
|
||||
STRINGS
|
||||
HELLO WORLD
|
||||
11 HELLOWORLDWORLD
|
||||
HELLO BASIC
|
||||
CONTROL
|
||||
FOR 1
|
||||
FOR 2
|
||||
FOR 3
|
||||
FOR 4
|
||||
FOR 5
|
||||
WHILE 1
|
||||
WHILE 2
|
||||
WHILE 3
|
||||
GOSUB OK
|
||||
DATA
|
||||
X= 10
|
||||
X= 20
|
||||
X= 30
|
||||
X= 40
|
||||
DEFFN
|
||||
49
|
||||
169
|
||||
IF
|
||||
T1
|
||||
T2
|
||||
DONE
|
||||
119
tests/run_compiler_tests.sh
Executable file
119
tests/run_compiler_tests.sh
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
# Run all .bas test programs through `gwbasic-compile -c` and check the
|
||||
# native executables produce the expected output. Mirrors
|
||||
# tests/run_tests.sh but exercises the AOT compiler path.
|
||||
set -u
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
COMPILE="${PROJECT_DIR}/build/gwbasic-compile"
|
||||
EXPECTED_DIR="${SCRIPT_DIR}/expected"
|
||||
WORK_DIR=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
if [ ! -x "$COMPILE" ]; then
|
||||
echo "ERROR: gwbasic-compile not found at $COMPILE (run cmake/make first)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$PROJECT_DIR/build/libgwrt.a" ]; then
|
||||
echo "ERROR: libgwrt.a not built yet (run cmake/make first)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Programs that are not meaningful for the AOT path:
|
||||
# - chain/common targets are not standalone
|
||||
# - interactive / timing-dependent / hardware tests
|
||||
# - unnumbered direct-mode programs (compiler requires numbered lines)
|
||||
# - misc_stmts.bas / run_file.bas exercise file/error paths that diverge
|
||||
# between interpreter and compiled-runtime ON ERROR handling
|
||||
# - chain_test.bas / common_test.bas need their target .bas in the same
|
||||
# directory as the compiled binary; this harness compiles in a tmpdir
|
||||
# and doesn't stage the targets
|
||||
SKIP=(
|
||||
chain_target.bas
|
||||
chain_test.bas
|
||||
common_target.bas
|
||||
common_test.bas
|
||||
datetime.bas
|
||||
on_timer.bas
|
||||
timer_stop.bas
|
||||
color_test.bas
|
||||
sound_test.bas
|
||||
play_music.bas
|
||||
play_scale.bas
|
||||
speaker_out.bas
|
||||
text_adventure.bas
|
||||
hello.bas
|
||||
math_ops.bas
|
||||
string_ops.bas
|
||||
misc_stmts.bas
|
||||
run_file.bas
|
||||
)
|
||||
should_skip() {
|
||||
local n="$1"
|
||||
for s in "${SKIP[@]}"; do [ "$n" = "$s" ] && return 0; done
|
||||
return 1
|
||||
}
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
skip=0
|
||||
for bas in "$SCRIPT_DIR"/programs/*.bas; do
|
||||
name="$(basename "$bas")"
|
||||
stem="${name%.bas}"
|
||||
if should_skip "$name"; then
|
||||
printf " SKIP %s\n" "$name"
|
||||
skip=$((skip + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
cp "$bas" "$WORK_DIR/$name"
|
||||
pushd "$WORK_DIR" > /dev/null
|
||||
if ! "$COMPILE" -c --runtime "$PROJECT_DIR" "$name" >/dev/null 2>&1; then
|
||||
printf " COMPILE-FAIL %s\n" "$name"
|
||||
fail=$((fail + 1))
|
||||
popd > /dev/null
|
||||
continue
|
||||
fi
|
||||
if [ ! -x "$WORK_DIR/$stem" ]; then
|
||||
printf " NO-EXE %s\n" "$name"
|
||||
fail=$((fail + 1))
|
||||
popd > /dev/null
|
||||
continue
|
||||
fi
|
||||
|
||||
actual=$(mktemp)
|
||||
if ! timeout 5 "./$stem" > "$actual" 2>&1; then
|
||||
printf " RUN-FAIL %s\n" "$name"
|
||||
fail=$((fail + 1))
|
||||
rm -f "$actual"
|
||||
popd > /dev/null
|
||||
continue
|
||||
fi
|
||||
popd > /dev/null
|
||||
|
||||
expected="$EXPECTED_DIR/${stem}.expected"
|
||||
if [ -f "$expected" ]; then
|
||||
normalized=$(mktemp)
|
||||
normalized_expected=$(mktemp)
|
||||
sed 's/\r//g; s/[[:space:]]*$//' "$actual" | sed '/^$/d' > "$normalized"
|
||||
sed 's/\r//g; s/[[:space:]]*$//' "$expected" | sed '/^$/d' > "$normalized_expected"
|
||||
if diff -q "$normalized_expected" "$normalized" >/dev/null 2>&1; then
|
||||
printf " PASS %s\n" "$name"
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
printf " DIFF %s\n" "$name"
|
||||
fail=$((fail + 1))
|
||||
fi
|
||||
rm -f "$normalized" "$normalized_expected"
|
||||
else
|
||||
# No golden file: just confirm it ran without crashing.
|
||||
printf " PASS %s [no expected]\n" "$name"
|
||||
pass=$((pass + 1))
|
||||
fi
|
||||
rm -f "$actual"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "$((pass + fail)) compiled tests: $pass passed, $fail failed ($skip skipped)"
|
||||
[ "$fail" -eq 0 ] || exit 1
|
||||
60
tests/run_dos_smoke.sh
Executable file
60
tests/run_dos_smoke.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# Run gwbasic16.exe under DOSBox-X with tests/dos_smoke.bas and compare
|
||||
# output against the golden file generated from the Linux interpreter.
|
||||
# Verifies the BIOS-rendered TUI doesn't crash and that core features
|
||||
# (arithmetic, strings, control flow, GOSUB, FOR/NEXT, DATA/READ, DEF FN,
|
||||
# file I/O via OPEN/PRINT#) all work in the 16-bit DOS build.
|
||||
set -u
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
EXE="${PROJECT_DIR}/gwbasic16.exe"
|
||||
SMOKE="${SCRIPT_DIR}/dos_smoke.bas"
|
||||
EXPECTED="${SCRIPT_DIR}/expected/dos_smoke.expected"
|
||||
DOSBOX_CONF="${SCRIPT_DIR}/dosbox-compat.conf"
|
||||
|
||||
if [ ! -f "$EXE" ]; then
|
||||
echo "ERROR: $EXE not found. Run ./build_dos.sh 16 first." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! flatpak list --app 2>/dev/null | grep -q com.dosbox_x.DOSBox-X; then
|
||||
echo "ERROR: DOSBox-X flatpak not installed (com.dosbox_x.DOSBox-X)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# DOSBox-X (flatpak) can only access $HOME, not /tmp.
|
||||
WORK=$(mktemp -d --tmpdir="$HOME" gw_dos_smoke.XXXXXX)
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
cp "$EXE" "$WORK/GWBASIC.EXE"
|
||||
cp "$SMOKE" "$WORK/SMOKE.BAS"
|
||||
sed -i 's/$/\r/' "$WORK/SMOKE.BAS"
|
||||
|
||||
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||
timeout 30 flatpak run com.dosbox_x.DOSBox-X \
|
||||
-conf "$DOSBOX_CONF" \
|
||||
-c "MOUNT C $WORK" \
|
||||
-c "C:" \
|
||||
-c "GWBASIC.EXE SMOKE.BAS" \
|
||||
-c "EXIT" \
|
||||
>/dev/null 2>&1
|
||||
|
||||
if [ ! -f "$WORK/OUT.TXT" ]; then
|
||||
echo "FAIL: gwbasic16.exe produced no OUT.TXT under DOSBox-X" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
actual=$(mktemp)
|
||||
normalized_expected=$(mktemp)
|
||||
trap 'rm -rf "$WORK" "$actual" "$normalized_expected"' EXIT
|
||||
sed 's/\r//g; s/[[:space:]]*$//' "$WORK/OUT.TXT" | sed '/^$/d' > "$actual"
|
||||
sed 's/\r//g; s/[[:space:]]*$//' "$EXPECTED" | sed '/^$/d' > "$normalized_expected"
|
||||
|
||||
if diff -q "$normalized_expected" "$actual" >/dev/null; then
|
||||
echo "PASS: gwbasic16.exe smoke test matches golden output"
|
||||
exit 0
|
||||
else
|
||||
echo "FAIL: gwbasic16.exe output differs from expected"
|
||||
diff -u "$normalized_expected" "$actual" | head -40
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user