#!/bin/bash # # modulepath_test.sh — @eval pathname resolution (Klammermachine engine # tier): module resolution and the :cwd option. # # A Python module lives NEXT TO THE FILE whose @eval names it: the directory # of every file the Machine reads — input files, klammer sets, @read targets # — joins the Python sys.path (after the SKS directories), so resolution is # independent of the cwd. Before 2026-07-27 modules were found only via # PYTHONPATH or the cwd, so `ktext /path/doc.kt` worked from the document's # directory and failed from anywhere else. # # The same directories are searched for a relative `:cpp` library not found # from the cwd (Eval::eval_command); that path is not exercised here because # it would mean compiling a DSO inside the test — the directory list is the # same one these cases prove. # # Pass-ordering caveat (deliberate, untested here): at one nesting level # @eval is processed before @read expands, so a TOP-LEVEL @eval sees only # files read earlier. A klammer DEFINED in a read file is applied later and # always sees its own directory — the realistic case, tested below. # # Engine tier: --klammersets none, fixtures created inline in a temp tree, no SKS. # Usage: ./modulepath_test.sh (ktext on PATH) PASS=0 FAIL=0 red=$'\033[31m' green=$'\033[32m' bold=$'\033[1m' reset=$'\033[0m' # Resolve the temporary directory to its physical path. On macOS /var is a # symlink to /private/var, so `mktemp -d` hands back an unresolved # /var/folders/... while Python's os.getcwd() reports the resolved # /private/var/folders/... — case 5 would then compare two spellings of the # same directory and fail on the Mac only. `pwd -P` is POSIX; macOS has no # GNU realpath by default. T="$(cd "$(mktemp -d)" && pwd -P)" trap 'rm -rf "$T"' EXIT mkdir -p "$T/seta" "$T/setb" "$T/elsewhere" printf 'def hello():\n return "module-next-to-read-file"\n' > "$T/seta/pmod.py" printf '@@desc : @eval pmod.hello() @ @@#-\n' > "$T/seta/defs.k" printf 'def here():\n return "module-next-to-input"\n' > "$T/setb/imod.py" printf '@read %s/seta/defs.k @#-@desc@ / @eval imod.here() @\n' "$T" > "$T/setb/doc.kt" printf '@eval nosuch_module_xyz.f() @\n' > "$T/setb/missing.kt" check() { # check NAME EXPECTED ARGS... local name="$1" expected="$2"; shift 2 local out out=$(ktext "$@" --klammersets none -d 2>/dev/null) if [ "$out" = "$expected" ]; then echo "${green}PASS${reset} $name"; PASS=$((PASS + 1)) else echo "${red}FAIL${reset} $name" echo " expected: [$expected]" echo " got: [$out]"; FAIL=$((FAIL + 1)) fi } echo "${bold}@eval module resolution tests${reset}" echo "=======================" echo cd "$T/elsewhere" check " 1. modules beside the @read file and the input file (absolute path)" \ "module-next-to-read-file / module-next-to-input" \ "$T/setb/doc.kt" check " 2. the same, input given as a relative path" \ "module-next-to-read-file / module-next-to-input" \ "../setb/doc.kt" name=" 3. an unknown module still reports cleanly" err=$(ktext "$T/setb/missing.kt" --klammersets none -d 2>&1 >/dev/null) case "$err" in *'Cannot import module "nosuch_module_xyz"'*) echo "${green}PASS${reset} $name"; PASS=$((PASS + 1)) ;; *) echo "${red}FAIL${reset} $name" echo " stderr: [$(echo "$err" | head -3)]"; FAIL=$((FAIL + 1)) ;; esac # --- the :cwd option -------------------------------------------------------- # @eval :cwd DIR runs the eval (any mode) with DIR as the working directory # and restores the process cwd afterwards; *K_input_dir* names the # document's directory; whitespace-separated tokens are joined until they # name an existing directory (the filenames-with-spaces convention). printf 'hello from data file\n' > "$T/setb/data.txt" printf '@eval :cwd *K_input_dir* :shell cat data.txt @\n' > "$T/setb/shellcwd.kt" check " 4. :cwd *K_input_dir* for :shell, run from elsewhere" \ "hello from data file" "$T/setb/shellcwd.kt" printf '@eval :cwd *K_input_dir* os.getcwd() @\n' > "$T/setb/pycwd.kt" check " 5. :cwd changes the Python working directory" \ "$T/setb" "$T/setb/pycwd.kt" printf '@eval :cwd *K_input_dir* :shell true @ / @eval :shell pwd @\n' > "$T/setb/restore.kt" check " 6. the cwd is restored after the eval" \ "/ $T/elsewhere" "$T/setb/restore.kt" mkdir -p "$T/sp ace" printf 'spaced\n' > "$T/sp ace/f.txt" printf '@eval :cwd %s/sp ace :shell cat f.txt @\n' "$T" > "$T/setb/spaces.kt" check " 7. a :cwd directory containing spaces" \ "spaced" "$T/setb/spaces.kt" name=" 8. a nonexistent :cwd is a clean argument error" printf '@eval :cwd /nonexistent_kt_dir :shell true @\n' > "$T/setb/bad.kt" err=$(ktext "$T/setb/bad.kt" --klammersets none -d 2>&1 >/dev/null) case "$err" in *'The :cwd directory does not exist'*) echo "${green}PASS${reset} $name"; PASS=$((PASS + 1)) ;; *) echo "${red}FAIL${reset} $name" echo " stderr: [$(echo "$err" | head -2)]"; FAIL=$((FAIL + 1)) ;; esac echo echo "=======================" echo "Results: ${green}$PASS passed${reset}, ${red}$FAIL failed${reset}" [ $FAIL -eq 0 ]