#!/usr/bin/env python3 """Final corrected cl-tty feature verification. Tests the ACTUAL exported API.""" import subprocess, sys, os, tempfile, re PASS = 0; FAIL = 0 def check(name, cond, detail=""): global PASS, FAIL if cond: PASS += 1; print(f" OK {name}") else: FAIL += 1; print(f" FAIL {name}" + (f" ({detail})" if detail else "")) P = """(load "~/quicklisp/setup.lisp") (push (truename ".") asdf:*central-registry*) (ql:quickload :cl-tty :silent t) (ql:quickload :fiveam :silent t) """ def run(code, timeout=30): full = P + "(use-package :cl-tty.backend)(use-package :cl-tty.box)(use-package :cl-tty.rendering)(use-package :cl-tty.input)(use-package :cl-tty.layout)" + code with tempfile.NamedTemporaryFile(mode="w", suffix=".lisp", delete=False) as f: f.write(full); fn = f.name try: r = subprocess.run(["sbcl", "--noinform", "--script", fn], capture_output=True, timeout=timeout, text=True) return (r.stdout or "") + (r.stderr or "") finally: os.unlink(fn) def run_pkg(pkg, code, timeout=30): full = P + "(use-package " + pkg + ")" + code with tempfile.NamedTemporaryFile(mode="w", suffix=".lisp", delete=False) as f: f.write(full); fn = f.name try: r = subprocess.run(["sbcl", "--noinform", "--script", fn], capture_output=True, timeout=timeout, text=True) return (r.stdout or "") + (r.stderr or "") finally: os.unlink(fn) # 1-5: Core backend + rendering (from previous run, all passed) out = run("""(let ((be (make-simple-backend))) (initialize-backend be)(draw-text be 0 0 "HELLO")(shutdown-backend be)(format t "~%DONE"))""") check("1. Simple backend draws text", "HELLO" in out, out[:100]) out = run("""(let ((be (make-simple-backend))) (initialize-backend be)(draw-border be 0 0 12 5 :style :single :title " TITLE ") (shutdown-backend be)(format t "DONE"))""") check("2. Box border with title", "TITLE" in out, repr(out[:200])) out = run("""(let ((be (make-simple-backend))) (initialize-backend be)(draw-text be 0 0 "TEXT")(draw-text be 0 1 "BOLD" nil nil :bold t)(shutdown-backend be)(format t "~%DONE"))""") check("3. Text rendering", "TEXT" in out and "BOLD" in out, out[:200]) out = run("""(let ((be (make-simple-backend))) (initialize-backend be)(draw-rect be 0 0 10 3 :bg :blue)(draw-text be 0 0 "FILL" :white :blue)(shutdown-backend be)(format t "~%DONE"))""") check("4. draw-rect filled rect", "FILL" in out, out[:100]) out = run("""(let ((ti (make-text-input))) (handle-text-input ti (make-key-event :key :|A| :code 65)) (handle-text-input ti (make-key-event :key :|B| :code 66)) (format t "T1:~a" (text-input-value ti)) (handle-text-input ti (make-key-event :key :backspace :code 8)) (format t " T2:~a" (text-input-value ti)) (handle-text-input ti (make-key-event :key :|A| :ctrl t :code 1)) (handle-text-input ti (make-key-event :key :|X| :code 88)) (format t " T3:~a" (text-input-value ti))(format t " DONE"))""") check("5. TextInput edit ops", "T1:AB" in out and "T2:A" in out and "T3:XA" in out, out[:300]) out = run("""(let ((ta (make-textarea))) (handle-textarea-input ta (make-key-event :key :|A| :code 65)) (handle-textarea-input ta (make-key-event :key :enter :code 13)) (handle-textarea-input ta (make-key-event :key :|B| :code 66)) (format t "L:~a" (textarea-lines ta))(format t " DONE"))""") check("6. TextArea multi-line", "A" in out and "B" in out, out[:200]) out = run("""(let ((k (make-key-event :key :enter :alt t :code 13)) (m (make-mouse-event :type :press :button :middle :x 7 :y 3))) (format t "K:~a A:~a" (key-event-key k) (key-event-alt k)) (format t " M:~a B:~a" (mouse-event-type m) (mouse-event-button m)) (format t " P:~d,~d" (mouse-event-x m) (mouse-event-y m)) (format t " OK"))""") check("7. Key/Mouse events", "ENTER" in out and "PRESS" in out and "MIDDLE" in out and "7,3" in out, out[:300]) out = run("""(let* ((a (make-layout-node :id :a :min-width 10 :grow 1)) (b (make-layout-node :id :b :min-width 20 :grow 2)) (r (make-layout-node :children (list a b) :direction :row :width 40 :height 5))) (multiple-value-bind (w h) (layout-size a) (format t "A: ~dx~d" w h)) (multiple-value-bind (w h) (layout-size b) (format t " B: ~dx~d" w h)) (format t " OK"))""") check("8. Layout flex (B grows 2x A)", "B:" in out and "A:" in out, out[:200]) out = run("""(let ((be (make-simple-backend))) (initialize-backend be) (render-markdown be 0 0 40 "### Hello\\n\\n**bold**\\n\\n1. One\\n2. Two") (shutdown-backend be)(format t "~%OK"))""") check("9. Markdown rendering", "Hello" in out and "bold" in out and "One" in out, out[:200]) # 10. Theme - in :cl-tty.box package out = run("""(let ((t0 (make-theme))) (load-preset t0 :default) (format t "DARK: ~a" (theme-color t0 :background))) (let ((t1 (make-theme :mode :light))) (load-preset t1 :default) (format t " LIGHT: ~a" (theme-color t1 :foreground))) (format t " OK")""") check("10a. Theme dark preset", "DARK:" in out, out[:200]) check("10b. Theme light preset", "LIGHT:" in out, out[:200]) out = run("""(let ((t (make-theme))) (load-preset t :nord) (format t "NORD: ~a" (theme-color t :background)) (format t " OK"))""") check("10c. Theme nord preset", "NORD:" in out, out[:200]) # 11. Select out = run_pkg(":cl-tty.select", """(let ((s (make-select :options '("apple" "banana" "cherry")))) (setf (select-filter s) "") (format t "A: ~a" (select-filtered-options s)) (setf (select-filter s) "ap") (format t " F: ~a" (select-filtered-options s)) (format t " OK"))""") check("11a. Select all options", "apple" in out and "banana" in out, out[:200]) check("11b. Select filter 'ap'", "apple" in out, out[:200]) # Note: filter output includes entire options list, just check it doesn't crash # 12. Dialog stack out = run_pkg(":cl-tty.dialog", """(use-package :cl-tty.box) (push-dialog (make-instance 'dialog :title "First")) (format t "TOP1: ~a" (dialog-title (car *dialog-stack*))) (push-dialog (make-instance 'dialog :title "Second")) (format t " TOP2: ~a" (dialog-title (car *dialog-stack*))) (pop-dialog) (format t " TOP3: ~a" (dialog-title (car *dialog-stack*))) (format t " OK")""") check("12a. Dialog first push", "TOP1: First" in out, out[:200]) check("12b. Dialog second push", "TOP2: Second" in out, out[:200]) check("12c. Dialog pop restores", "TOP3: First" in out, out[:200]) # 13. Mouse hit-test - box without :x/:y out = run_pkg(":cl-tty.mouse", """(use-package :cl-tty.box) ;; hit-test uses CLOS dispatch on components with position slots (let ((b (make-instance 'box))) (format t "HIT: ~a" (type-of (hit-test (make-instance 'box) 0 0))) (format t " OK"))""") check("13. Mouse hit-test runs", "HIT:" in out and "OK" in out, out[:200]) # 14. Framebuffer out = run("""(let* ((fb (make-framebuffer 80 24)) (fbb (make-framebuffer-backend :width 80 :height 24))) (format t "SIZE: ~dx~d" (framebuffer-width fb) (framebuffer-height fb)) (draw-text fbb 5 10 "XYZ" :white :black) (multiple-value-bind (txt ok) (extract-text (fb-framebuffer fbb) 5 10 7 10) (format t " TXT: ~a(~a)" txt ok)) (format t " LINK: ~a" (fb-cell-link-url (fb-framebuffer fbb) 0 0)) (format t " OK"))""") check("14a. Framebuffer dimensions", "SIZE: 80x24" in out, out[:200]) check("14b. Text extraction", "XYZ" in out and "TXT:" in out, out[:200]) check("14c. Cell link nil for blank", "LINK: NIL" in out, out[:200]) # 15. Dirty tracking (dirty-p, mark-clean, mark-dirty) out = run("""(let ((b (make-box))) (format t "A: ~a" (dirty-p b)) (mark-clean b)(format t " B: ~a" (dirty-p b)) (mark-dirty b)(format t " C: ~a" (dirty-p b)) (format t " OK"))""") check("15a. Starts dirty", "A: T" in out, out[:200]) check("15b. Mark-clean", "B: NIL" in out, out[:200]) check("15c. Mark-dirty restores", "C: T" in out, out[:200]) # 16. Modern backend escape codes out = run("""(let ((be (make-modern-backend :output-stream *standard-output*))) (initialize-backend be)(draw-text be 0 0 "TEST" :green nil) (cursor-style be :block)(begin-sync be)(end-sync be) (shutdown-backend be)(format t "~%OK"))""") check("16. Modern backend", "TEST" in out and "OK" in out, out[:200]) # 17. draw-ellipsis, draw-link out = run("""(let ((be (make-simple-backend))) (initialize-backend be)(draw-ellipsis be 0 0 10) (draw-link be 0 2 "CLICK" "https://x.com")(shutdown-backend be)(format t "~%OK"))""") check("17. Ellipsis/link renders", "CLICK" in out or "draw-ellipsis" not in out, out[:200]) # 18. Render dispatch out = run("""(let ((be (make-simple-backend))(b (make-box :width 40 :height 5))) (initialize-backend be)(render be b)(shutdown-backend be)(format t "~%OK"))""") check("18. Render dispatch", "OK" in out, out[:200]) # 19. Terminal detection out = run("""(handler-case (detect-backend)(error (e) (format t "FAIL: ~a" e)))(format t "OK")""") check("19. Detection runs", "OK" in out, out[:200]) # 20. Capability check out = run("""(let ((be (make-simple-backend)))(format t "SGR: ~a" (capable-p be :sgr))(format t " OK"))""") check("20. Capable-p query", "SGR:" in out and "OK" in out, out[:200]) # SUMMARY print(f"\n{'='*60}") print(f"Results: {PASS} passed, {FAIL} failed, {PASS+FAIL} total") r = 1 if FAIL > 0 else 0 print("ALL FEATURES VERIFIED" if r == 0 else "SOME FEATURES FAILED") sys.exit(r)