From 00c2aff7a8d6cbd4740011ffefa7a7429217ffe6 Mon Sep 17 00:00:00 2001 From: Kyle Serrecchia Date: Wed, 8 Jul 2026 01:02:06 -0700 Subject: [PATCH] add CI test harness and tests for first six languages Co-Authored-By: Claude Fable 5 --- .github/workflows/test.yml | 23 ++++++++ ROADMAP.md | 67 ++++++++++++++++++++++ languages/c/c/expected-output.txt | 2 + languages/c/c/test.sh | 5 ++ languages/j/javascript/expected-output.txt | 30 ++++++++++ languages/j/javascript/test.sh | 1 + languages/p/perl/expected-output.txt | 24 ++++++++ languages/p/perl/test.sh | 1 + languages/p/python/expected-output.txt | 30 ++++++++++ languages/p/python/test.sh | 1 + languages/r/racket/expected-output.txt | 30 ++++++++++ languages/r/racket/test.sh | 1 + languages/t/typescript/expected-output.txt | 8 +++ languages/t/typescript/test.sh | 1 + run-tests.sh | 35 +++++++++++ 15 files changed, 259 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 ROADMAP.md create mode 100644 languages/c/c/expected-output.txt create mode 100644 languages/c/c/test.sh create mode 100644 languages/j/javascript/expected-output.txt create mode 100644 languages/j/javascript/test.sh create mode 100644 languages/p/perl/expected-output.txt create mode 100644 languages/p/perl/test.sh create mode 100644 languages/p/python/expected-output.txt create mode 100644 languages/p/python/test.sh create mode 100644 languages/r/racket/expected-output.txt create mode 100644 languages/r/racket/test.sh create mode 100644 languages/t/typescript/expected-output.txt create mode 100644 languages/t/typescript/test.sh create mode 100755 run-tests.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..8e5b8ff --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: test + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Python, Node.js, Perl, GCC/G++ 14 come preinstalled on the runner. + - name: Install toolchains not preinstalled on the runner + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends racket + + - uses: oven-sh/setup-bun@v2 + + - name: Run all language tests + run: ./run-tests.sh diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..bfd0cc4 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,67 @@ +# Roadmap + +Working agreement: every step is sized to be completed (and verified) in one +sitting. An implementation only gets an `expected-output.txt` captured from an +actual observed run — never written from reading the source. Anything that +can't be run locally gets verified on the CI runner before merge. + +## Milestone 1 — CI backfill: every existing language tested on every PR + +The harness: `run-tests.sh` loops over `languages/*/*/test.sh`, runs each, and +diffs stdout against that folder's `expected-output.txt`. The GitHub Actions +workflow (`.github/workflows/test.yml`) runs it on every PR and push to main. +Folders without a `test.sh` are skipped until backfilled. + +- [x] **Step 1 — harness + locally verifiable languages.** Workflow file, + `run-tests.sh`, and test.sh/expected-output.txt for Python, JavaScript, + TypeScript (Bun), Perl, Racket, and C. All six verified locally. +- [ ] **Step 2 — C++ and Go.** C++ needs GCC 14 (`` header) — not on + the dev machine, so capture its output from the CI runner log on the PR, + then commit it as the expected file. Go is preinstalled on runners. +- [ ] **Step 3 — Ruby, Lua, Haskell.** All installable on the runner via apt + (`ruby`, `lua5.4`, `ghc`); add installs to the workflow's toolchain step. +- [ ] **Step 4 — OCaml, Elixir, Clojure, F#.** apt/preinstalled on runners + (`ocaml`, `elixir`, `clojure`; F# via preinstalled dotnet). Clojure runs + through its own deps.edn test runner — wrap it in test.sh. +- [ ] **Step 5 — Java and Kotlin.** Java: PR #29 (the `java` branch) fixes the + numeral typing so PRED works; add test.sh + expected output to that + branch, let CI validate it, then merge #29. Kotlin: runner has a JDK; + install kotlinc in the workflow. +- [ ] **Step 6 — exotic languages: ArkScript, FatScript, bruijn, Language 84.** + Each needs its own toolchain acquisition (GitHub releases, cargo/stack + installs, or building from source). If one is genuinely unobtainable in + CI, document that in its folder instead of leaving it silently untested. +- [ ] **Step 7 — flip the default and update the front door.** Once all + folders have tests: make `run-tests.sh` fail on folders *missing* a + test.sh, update README contribution instructions to require + test.sh + expected-output.txt in new-language PRs, and enable branch + protection so PRs need a green check to merge. + +## Milestone 2 — fill out the missing major languages + +One language per step, each landing as its own PR with implementation, README, +test.sh, and expected output, CI-green before merge. Order roughly by reach: + +- [ ] Rust +- [ ] C# +- [ ] Swift +- [ ] Scala +- [ ] Zig +- [ ] PHP +- [ ] Dart +- [ ] Gleam (reopens the slot from closed PR #28) + +Keep a "wanted" list in the README for languages left open to contributors +(Prolog, Erlang, APL, Idris, …). + +## Milestone 3 — housekeeping + +- [ ] Remove the committed binary `languages/c/c/app` and add a root + `.gitignore` for build artifacts. +- [ ] TypeScript implementation only exercises Church numerals at runtime — + booleans exist at the type level only. Decide whether that satisfies the + spec or needs a runtime supplement. +- [ ] Comment on merged PR #6 noting the `Function` fix, closing + the loop on the thread there. +- [ ] Java README still describes an Oracle JDK manual install — point it at + apt/Temurin and the test.sh instead. diff --git a/languages/c/c/expected-output.txt b/languages/c/c/expected-output.txt new file mode 100644 index 0000000..6ed281c --- /dev/null +++ b/languages/c/c/expected-output.txt @@ -0,0 +1,2 @@ +1 +1 diff --git a/languages/c/c/test.sh b/languages/c/c/test.sh new file mode 100644 index 0000000..bd039e5 --- /dev/null +++ b/languages/c/c/test.sh @@ -0,0 +1,5 @@ +gcc lambda-core.c -std=c89 -pedantic -o lambda-core.test || exit 1 +./lambda-core.test +status=$? +rm -f lambda-core.test +exit $status diff --git a/languages/j/javascript/expected-output.txt b/languages/j/javascript/expected-output.txt new file mode 100644 index 0000000..1b055f1 --- /dev/null +++ b/languages/j/javascript/expected-output.txt @@ -0,0 +1,30 @@ +LOGIC +--------------- +TRUE/FALSE +t +f +NOT +f +t +AND +t +f +f +f +OR +t +t +t +f + +CHURCH NUMERALS +--------------- +ZERO/SUCC +0 +1 +2 +3 +PRED +0 +1 +2 diff --git a/languages/j/javascript/test.sh b/languages/j/javascript/test.sh new file mode 100644 index 0000000..a8371cf --- /dev/null +++ b/languages/j/javascript/test.sh @@ -0,0 +1 @@ +node lambda-core.js diff --git a/languages/p/perl/expected-output.txt b/languages/p/perl/expected-output.txt new file mode 100644 index 0000000..619efb9 --- /dev/null +++ b/languages/p/perl/expected-output.txt @@ -0,0 +1,24 @@ +LOGIC +------------- + TRUE: true + FALSE: false + NOT TRUE: false + NOT FALSE: true + TRUE AND TRUE: true + TRUE AND FALSE: false + FALSE AND TRUE: false + FALSE AND FALSE: false + TRUE OR TRUE: true + TRUE OR FALSE: true + FALSE OR TRUE: true + FALSE OR FALSE: false + +CHURCH NUMERALS +------------- + ZERO: 0 + ONE: 1 + SUCC ONE: 2 + SUCC SUCC ONE: 3 + PRED ONE: 0 + PRED SUCC ONE: 1 + PRED SUCC SUCC ONE: 2 \ No newline at end of file diff --git a/languages/p/perl/test.sh b/languages/p/perl/test.sh new file mode 100644 index 0000000..823eaea --- /dev/null +++ b/languages/p/perl/test.sh @@ -0,0 +1 @@ +perl lambda-core.pl diff --git a/languages/p/python/expected-output.txt b/languages/p/python/expected-output.txt new file mode 100644 index 0000000..6a993e2 --- /dev/null +++ b/languages/p/python/expected-output.txt @@ -0,0 +1,30 @@ +LOGIC +------------- +TRUE/FALSE +t +f +NOT +f +t +AND +t +f +f +f +OR +t +t +t +f + +CHURCH NUMERALS +------------- +ZERO/SUCC +0 +1 +2 +3 +PRED +0 +1 +2 diff --git a/languages/p/python/test.sh b/languages/p/python/test.sh new file mode 100644 index 0000000..deb68f3 --- /dev/null +++ b/languages/p/python/test.sh @@ -0,0 +1 @@ +python3 lambda-core.py diff --git a/languages/r/racket/expected-output.txt b/languages/r/racket/expected-output.txt new file mode 100644 index 0000000..e39dc20 --- /dev/null +++ b/languages/r/racket/expected-output.txt @@ -0,0 +1,30 @@ +LOGIC +-------------- +TRUE/FALSE +t +f +NOT +f +t +AND +t +f +f +f +OR +t +t +t +f + +CHURCH NUMERALS +-------------- +ZERO/SUCC +0 +1 +2 +3 +PRED +0 +1 +2 diff --git a/languages/r/racket/test.sh b/languages/r/racket/test.sh new file mode 100644 index 0000000..a1d57df --- /dev/null +++ b/languages/r/racket/test.sh @@ -0,0 +1 @@ +racket lambda-core.rkt diff --git a/languages/t/typescript/expected-output.txt b/languages/t/typescript/expected-output.txt new file mode 100644 index 0000000..5d390c1 --- /dev/null +++ b/languages/t/typescript/expected-output.txt @@ -0,0 +1,8 @@ +Zero: 0 +One: 1 +Two: 2 +Three: 3 +Pred Zero: 0 +Pred One: 0 +Pred Two: 1 +Pred Three: 2 diff --git a/languages/t/typescript/test.sh b/languages/t/typescript/test.sh new file mode 100644 index 0000000..333ac84 --- /dev/null +++ b/languages/t/typescript/test.sh @@ -0,0 +1 @@ +bun run lambda-core.ts diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 0000000..705f109 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# Runs every language folder that has a test.sh and compares its stdout +# against that folder's expected-output.txt. Exits nonzero if any differ. +# Folders without a test.sh are skipped (not yet backfilled). +set -u + +root=$(cd "$(dirname "$0")" && pwd) +fail=0 +ran=0 + +for script in "$root"/languages/*/*/test.sh; do + [ -e "$script" ] || continue + dir=$(dirname "$script") + name=${dir#"$root"/languages/} + ran=$((ran + 1)) + + actual=$( (cd "$dir" && sh test.sh 2>/dev/null) ) + status=$? + expected=$(cat "$dir/expected-output.txt") + + if [ "$status" -eq 0 ] && [ "$actual" = "$expected" ]; then + echo "PASS $name" + else + echo "FAIL $name (exit $status)" + echo "--- expected ---" + echo "$expected" + echo "--- actual ---" + echo "$actual" + echo "---" + fail=1 + fi +done + +echo "$ran folder(s) tested" +exit $fail