# HG changeset patch # User Richard Westhaver # Date 1727833937 14400 # Node ID 517c65b51e6ba01f21353ed587aebc3b085c3041 # Parent c2f4e7ee921ba420687dc3bf9bf4eb6887b95d4d clap tests diff -r c2f4e7ee921b -r 517c65b51e6b lisp/lib/cli/clap/cli.lisp --- a/lisp/lib/cli/clap/cli.lisp Mon Sep 30 22:27:12 2024 -0400 +++ b/lisp/lib/cli/clap/cli.lisp Tue Oct 01 21:52:17 2024 -0400 @@ -48,8 +48,7 @@ (lambda (x) (etypecase x (string (make-cli-opt :name x)) - (list (apply #'make-cli :opt x)) - (t (make-cli :opt :name (format nil "~(~A~)" x) :global t)))) + (list (apply #'make-cli :opt x)))) opts)) (defun make-cmds (cmds) @@ -73,7 +72,7 @@ (:documentation "CLI")) (defmethod print-usage ((self cli) &optional stream) - (iprintln (format nil "usage: ~A [global] []~%" (cli-name self)) 2 stream)) + (iprintln (format nil "usage: ~A [opts] []~%" (cli-name self)) 2 stream)) (defmethod print-version ((self cli) &optional stream) (println (cli-version self) stream)) @@ -105,7 +104,7 @@ (declaim (inline debug-opts)) (defun debug-opts (cli) (let ((o (active-opts cli)) - (a (cli-cmd-args cli)) + (a (cli-args cli)) (c (active-cmds cli))) (log:debug! :pwd (cli-cd cli) :active-opts o :cmd-args a :active-cmds c))) diff -r c2f4e7ee921b -r 517c65b51e6b lisp/lib/cli/clap/cmd.lisp --- a/lisp/lib/cli/clap/cmd.lisp Mon Sep 30 22:27:12 2024 -0400 +++ b/lisp/lib/cli/clap/cmd.lisp Tue Oct 01 21:52:17 2024 -0400 @@ -19,7 +19,7 @@ (thunk :initform 'default-thunk :initarg :thunk :accessor cli-thunk :type symbol) (lock :initform nil :initarg :lock :accessor cli-lock-p :type boolean) (description :initarg :description :accessor cli-description :type string) - (args :initform nil :initarg :args :accessor cli-cmd-args)) + (args :initform nil :initarg :args :accessor cli-args)) (:documentation "CLI command class inherited by both the 'main' command which is executed when a CLI is called without arguments, and all subcommands.")) @@ -38,11 +38,12 @@ (defmethod print-object ((self cli-cmd) stream) (print-unreadable-object (self stream :type t) - (format stream "~A :opts ~A :cmds ~A :args ~A" + (format stream "~A :active ~a :opts ~A :cmds ~A :args ~A" (cli-name self) + (cli-lock-p self) (length (opts self)) (length (cmds self)) - (length (cli-cmd-args self))))) + (length (cli-args self))))) (defmethod print-usage ((self cli-cmd) &optional stream) (with-slots (opts cmds) self @@ -138,12 +139,8 @@ (let ((match (find-opt self name active))) (substitute new match (opts self) :test 'cli-equal))) -(defmethod active-opts ((self cli-cmd) &optional global) - (remove-if-not - (if global - #'active-global-opt-p - #'cli-opt-lock) - (opts self))) +(defmethod active-opts ((self cli-cmd)) + (remove-if-not 'cli-opt-lock (opts self))) (defmethod find-short-opts ((self cli-cmd) ch &key recurse) (let ((ret)) @@ -245,7 +242,7 @@ (setf (find-cmd self (cli-name form)) form) (log:trace! (format nil "installing cmd ~A" (cli-name form)))) (arg (push-arg form self))))) - (setf (cli-cmd-args self) (nreverse (cli-cmd-args self))) + (setf (cli-args self) (nreverse (cli-args self))) self)) (defmethod install-thunk ((self cli-cmd) (lambda function) &optional compile) @@ -256,7 +253,7 @@ (defmethod push-arg (arg (self cli-cmd)) "Push an ARG onto the corresponding slot of a CLI-CMD." - (push arg (cli-cmd-args self))) + (push arg (cli-args self))) (defmethod parse-args ((self cli-cmd) args &key (compile t)) "Parse ARGS and return the updated object SELF. @@ -272,8 +269,8 @@ (trace! "calling command:" args opts) (funcall (cli-thunk self) args opts)) -(defmethod do-opts ((self cli-cmd) &optional global) - (do-opts (active-opts self) global)) +(defmethod do-opts ((self cli-cmd)) + (do-opts (active-opts self))) (defmethod do-cmd ((self cli-cmd)) "Perform the active command or subcommand, recursively calling DO-CMD on @@ -281,7 +278,11 @@ evaluated with DO-OPTS along the way." (do-opts self) (if (solop self) - (call-cmd self (cli-cmd-args self) (active-opts self)) + (prog1 (call-cmd self (cli-args self) (active-opts self)) + ;; release opts + (loop for o across (active-opts self) + do (setf (cli-opt-lock o) nil))) (loop for c across (active-cmds self) - do (do-cmd c)))) + do (do-cmd c))) + (setf (cli-lock-p self) nil)) diff -r c2f4e7ee921b -r 517c65b51e6b lisp/lib/cli/clap/opt.lisp --- a/lisp/lib/cli/clap/opt.lisp Mon Sep 30 22:27:12 2024 -0400 +++ b/lisp/lib/cli/clap/opt.lisp Tue Oct 01 21:52:17 2024 -0400 @@ -38,7 +38,6 @@ (kind 'boolean :type (or symbol list)) (thunk 'identity :type symbol) (val nil) - (global nil :type boolean) (description nil :type (or null string)) (lock nil :type boolean)) @@ -48,6 +47,9 @@ (defmethod activate-opt ((self cli-opt)) (setf (cli-opt-lock self) t)) +(defmethod cli-lock-p ((self cli-opt)) + (cli-opt-lock self)) + (defun %compose-short-opt (o) (setf (cli-opt-val o) t) (make-cli-node 'opt o)) @@ -72,7 +74,7 @@ (defmethod make-load-form ((obj cli-opt) &optional env) (make-load-form-saving-slots obj - :slot-names '(name kind thunk val global description lock) + :slot-names '(name kind thunk val description lock) :environment env)) (defmethod install-thunk ((self cli-opt) (lambda function) &optional compile) @@ -83,26 +85,24 @@ (defmethod print-object ((self cli-opt) stream) (print-unreadable-object (self stream :type t) - (format stream "~A :global ~A :val ~A" + (format stream "~A :active ~A :val ~A" (cli-opt-name self) - (cli-opt-global self) + (cli-opt-lock self) (cli-opt-val self)))) (defmethod print-usage ((self cli-opt) &optional stream) - (format stream "-~(~{~A~^/--~}~)~A~A" + (format stream "-~(~{~A~^/--~}~) ~A" (let ((n (cli-opt-name self))) (declare (simple-string n)) (list (make-shorty n) n)) - (if (cli-opt-global self) "* " " ") (if-let ((d (and (slot-boundp self 'description) (cli-opt-description self)))) (format stream ": ~A" d) ""))) (defmethod cli-equal ((a cli-opt) (b cli-opt)) - (with-slots (name global kind) a - (with-slots ((bn name) (bg global) (bk kind)) b + (with-slots (name kind) a + (with-slots ((bn name) (bk kind)) b (and (equal name bn) - (eq global bg) (equal kind bk))))) (defmethod call-opt ((self cli-opt) arg) @@ -111,13 +111,6 @@ (defmethod do-opt ((self cli-opt)) (setf (cli-opt-val self) (call-opt self (cli-opt-val self)))) -(defmethod do-opts ((self vector) &optional global) +(defmethod do-opts ((self vector)) (loop for opt across self - do (if global - (when (cli-opt-global opt) - (do-opt opt)) - (do-opt opt)))) - -(defun active-global-opt-p (opt) - "Return non-nil if OPT is active at runtime and global." - (and (cli-opt-lock opt) (cli-opt-global opt))) + do (do-opt opt))) diff -r c2f4e7ee921b -r 517c65b51e6b lisp/lib/cli/clap/pkg.lisp --- a/lisp/lib/cli/clap/pkg.lisp Mon Sep 30 22:27:12 2024 -0400 +++ b/lisp/lib/cli/clap/pkg.lisp Tue Oct 01 21:52:17 2024 -0400 @@ -25,6 +25,7 @@ (defpackage :cli/clap/proto (:use :cl :std :log :sb-ext) + (:import-from :cli/clap/util :args) (:export :proc-args :clap-error :find-short-opts :find-cmd :find-opts :parse-args :print-help :print-usage :print-version :do-cmds :do-cmd @@ -45,7 +46,10 @@ :clap-invalid-argument :activate-cmd :activate-opt - :find-opt)) + :find-opt + :cli-args + :opts + :cmds)) (defpackage :cli/clap/ast (:use :cl :std :log :dat/sxp) @@ -61,10 +65,13 @@ :make-opts :make-cmds :parse-bool-opt :parse-string-opt :parse-form-opt :parse-list-op :parse-sym-op :parse-key-op :pasre-num-op :parse-file-op :parse-dir-op :cli - :cli-cd :with-cli :opts :cmds :debug-opts + :cli-cd :with-cli :debug-opts :cli-opt :cli-cmd :cli-opt-val :cli-opt-lock :cli-opt-name :active-cmds - :%compose-keyword-opt)) + :%compose-keyword-opt + :cli-cmd-args + :cli-lock-p + :cli-name)) (defpackage :cli/clap/simple (:use :cl :std :log :sb-ext) diff -r c2f4e7ee921b -r 517c65b51e6b lisp/lib/cli/clap/proto.lisp --- a/lisp/lib/cli/clap/proto.lisp Mon Sep 30 22:27:12 2024 -0400 +++ b/lisp/lib/cli/clap/proto.lisp Tue Oct 01 21:52:17 2024 -0400 @@ -25,6 +25,12 @@ (defun clap-invalid-argument (arg &key reason kind) (error 'clap-invalid-argument :name arg :kind kind :reason reason)) +(defgeneric cmds (self)) +(defgeneric opts (self)) + +(defgeneric cli-args (self) + (:method ((self null)) (args))) + (defgeneric push-cmd (cmd place)) (defgeneric push-opt (opt place)) @@ -45,7 +51,7 @@ (defgeneric active-cmds (self)) -(defgeneric active-opts (self &optional global)) +(defgeneric active-opts (self)) (defgeneric activate-opt (self)) @@ -57,7 +63,7 @@ (defgeneric do-opt (self)) -(defgeneric do-opts (self &optional global)) +(defgeneric do-opts (self)) (defgeneric call-cmd (self args opts)) diff -r c2f4e7ee921b -r 517c65b51e6b lisp/lib/cli/cli.asd --- a/lisp/lib/cli/cli.asd Mon Sep 30 22:27:12 2024 -0400 +++ b/lisp/lib/cli/cli.asd Tue Oct 01 21:52:17 2024 -0400 @@ -39,5 +39,8 @@ (defsystem :cli/tests :depends-on (:rt :cli) - :components ((:file "tests")) + :components ((:module "tests" + :components + ((:file "pkg") + (:file "clap")))) :perform (test-op (o c) (symbol-call :rt :do-tests :cli))) diff -r c2f4e7ee921b -r 517c65b51e6b lisp/lib/cli/tests.lisp --- a/lisp/lib/cli/tests.lisp Mon Sep 30 22:27:12 2024 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,695 +0,0 @@ -;;; cli/tests.lisp --- CLI Tests - -;; - -;;; Code: -(defpackage :cli/tests - (:use :cl :std :rt :cli :cli/shell :cli/progress :cli/spark :cli/repl :cli/ansi :cli/prompt :cli/clap :cli/tools/sbcl :dat/sxp)) - -(in-package :cli/tests) -(declaim (optimize (debug 3) (safety 3))) -(defsuite :cli) -(in-suite :cli) - -(defun ansi-t01 () - (erase) - (cursor-position 0 0) - (princ "0") - (cursor-position 2 2) - (princ "1") - (cursor-position 5 15) - (princ "test") - (cursor-position 10 15) - (force-output) - (with-input-from-string (in (format nil "test~%~%")) - (let ((a (read-line in))) - (cursor-position 12 15) - (princ a) - (force-output)))) - -(defun ansi-t02 () - (print "normal") - (.sgr 1) - (print "bold") - (.sgr 4) - (print "bold underline") - (.sgr 7) - (print "bold underline reverse") - (.sgr 22) - (print "underline reverse") - (.sgr 24) - (print "reverse") - (.sgr 27) - (print "normal") - (.sgr 1 4 7) - (print "bold underline reverse") - (.sgr 0) - (print "normal") - (force-output)) - -(defun ansi-t03 () - "Display the 256 color palette." - (clear) - (loop for i from 0 to 255 do - (.sgr 48 5 i) - (princ #\space)) - (terpri) - (.sgr 0) - (loop for i from 0 to 255 do - (.sgr 38 5 i) - (princ "X")) - (.sgr 0) - (force-output) - ;; (sleep 3) - (.ris) - (force-output)) - -(defun ansi-t04 () - "Hide and show the cursor." - (princ "Cursor visible:") - (force-output) - ;; (sleep 2) - (terpri) - (princ "Cursor invisible:") - (hide-cursor) - (force-output) - ;; (sleep 2) - (terpri) - (princ "Cursor visible:") - (show-cursor) - (force-output) - ;; (sleep 2) - ) - -(defun ansi-t05 () - "Switch to and back from the alternate screen buffer." - (princ "Normal screen buffer. ") - (force-output) - ;; (sleep 2) - (save-cursor-position) - (use-alternate-screen-buffer) - (clear) - (princ "Alternate screen buffer.") - (force-output) - ;; (sleep 2) - (use-normal-screen-buffer) - (restore-cursor-position) - (princ "Back to Normal screen buffer.") - (force-output) - ;; (sleep 1) - ) - -(defun ansi-t06 () - "Set individual termios flags to enable raw and disable echo mode. - -Enabling raw mode allows read-char to return immediately after a key is pressed. - -In the default cooked mode, the entry has to be confirmed by pressing enter." - (set-tty-mode t :ignbrk nil - :brkint nil - :parmrk nil - :istrip nil - :inlcr nil - :igncr nil - :icrnl nil - :ixon nil - :opost nil - :echo nil - :echonl nil - :icanon nil - :isig nil - :iexten nil - :csize nil - :parenb nil - :vmin 1 - :vtime 0) - (erase) - (cursor-position 1 1) - (force-output) - (let ((a (read-char))) - (cursor-position 10 5) - (princ a) - (force-output)) - - (set-tty-mode t :echo t - :brkint t - :ignpar t - :istrip t - :icrnl t - :ixon t - :opost t - :isig t - :icanon t - :veol 0)) - -(defun ansi-t07 () - "Use combination modes that consist of several individual flags. - -Cooked and raw are opposite modes. Enabling cooked disbles raw and vice versa." - (set-tty-mode t :cooked nil) - (erase) - (cursor-position 1 1) - (force-output) - (let ((a (read-char))) - (cursor-position 3 1) - (princ a) - (force-output)) - (set-tty-mode t :raw nil)) - -(defun ansi-t08 () - "Why doesnt calling the stty utility work?" - (uiop:run-program "stty raw -echo" :ignore-error-status t) - (erase) - (cursor-position 1 1) - (force-output) - (let ((a (read-char))) - (cursor-position 2 1) - (princ a) - (force-output)) - (uiop:run-program "stty -raw echo" :ignore-error-status t)) - -(defun ansi-t09 () - "Query terminal size with ANSI escape sequences." - ;; Put the terminal into raw mode so we can read the "user input" - ;; of the reply char by char - ;; Turn off the echo or the sequence will be displayed - (set-tty-mode t :cooked nil :echo nil) - (save-cursor-position) - ;; Go to the bottom right corner of the terminal by attempting - ;; to go to some high value of row and column - (cursor-position 999 999) - (let (chars) - ;; The terminal returns an escape sequence to the standard input - (device-status-report) - (force-output) - ;; The reply isnt immediately available, the terminal does need - ;; some time to answer - (sleep 0.1) - ;; The reply has to be read as if the user typed an escape sequence - (loop for i = (read-char-no-hang *standard-input* nil) - until (null i) - do (push i chars)) - ;; Put the terminal back into its initial cooked state - (set-tty-mode t :raw nil :echo t) - (restore-cursor-position) - ;; Return the read sequence as a list of characters. - (nreverse chars))) - -(deftest ansi () - (with-input-from-string (in (format nil "~%~%")) - (ansi-t01) - (ansi-t02) - (ansi-t03) - (ansi-t04) - (ansi-t05))) - -;; TODO: needs to be compiled outside scope of test - contender for -;; fixture API -(defprompt tpfoo :prompt "testing:") - -(deftest cli-prompt (:skip t) - "Test CLI prompts" - (defvar tcoll nil) - (defvar thist nil) - (let ((*standard-input* (make-string-input-stream - (format nil "~A~%~A~%~%" "foobar" "foobar")))) - ;; prompts - (is (string= (tpfoo-prompt) "foobar")) - (is (string= "foobar" - (completing-read "nothing: " tcoll :history thist :default "foobar"))))) - -(defparameter *opts* '((:name "foo" :global t :description "bar" :kind string) - (:name "bar" :description "foo" :kind string))) - -(defparameter *cmd1* (make-cli :cmd :name "holla" :opts *opts* :description "cmd1 description")) -(defparameter *cmd2* (make-cli :cmd :name "ayo" :cmds (vector *cmd1*) :opts *opts* :description "cmd1 description")) -(defparameter *cmds* (make-cmds (list `(:name "baz" :description "baz" :opts ,*opts*) *cmd1* *cmd2*))) - -(defparameter *cli* (make-cli :cli :opts *opts* :cmds *cmds* :description "test cli")) - - -(deftest clap-basic (:skip t) - "test basic CLAP functionality." - (is (eq (make-shorty "test") #\t)) - (is (equalp (proc-args *cli* '("-f" "baz" "--bar=fax")) ;; not eql - (make-cli-ast - (list (make-cli-node 'opt (find-short-opts cli #\f)) - (make-cli-node 'cmd (find-cmd cli "baz")) - (make-cli-node 'opt (find-opts cli "bar")) - (make-cli-node 'arg "fax"))))) - (is (parse-args cli '("--bar" "baz" "-f" "yaks"))) - (is (stringp - (with-output-to-string (s) - (print-version *cli* s) - (print-usage *cli* s) - (print-help *cli* s)))) - (is (string= "foobar" (cli/clap:parse-string-opt "foobar")))) - -(make-opt-parser thing *arg*) - -(deftest clap-opts () - "CLAP opt tests." - (is (reduce (lambda (x y) (and x y)) - (loop for k across *cli-opt-kinds* collect (cli-opt-kind-p k)))) - (is (parse-thing-opt t)) - (is (null (parse-thing-opt nil)))) - -(deftest progress () - (flet ((%step () (cli/progress::update 1))) - (let ((*progress-bar-enabled* t) - (n 100)) - (with-progress-bar (n "TEST: # of steps = ~a" n) - (dotimes (i n) (%step)))))) - -(deftest spark () - (is (string= - (spark '(1 5 22 13 5)) - "▁▂█▅▂")) - (is (string= - (spark '(5.5 20)) - "▁█")) - (is (string= - (spark '(1 2 3 4 100 5 10 20 50 300)) - "▁▁▁▁▃▁▁▁▂█")) - (is (string= - (spark '(1 50 100)) - "▁▄█")) - (is (string= - (spark '(2 4 8)) - "▁▃█")) - (is (string= - (spark '(1 2 3 4 5)) - "▁▂▄▆█")) - (is (string= - (spark '(0 30 55 80 33 150)) - "▁▂▃▄▂█")) - ;; null - (is (string= - (spark '()) - "")) - ;; singleton - (is (string= - (spark '(42)) - "▁")) - ;; constant - (is (string= - (spark '(42 42)) - "▁▁")) - ;; min/max - (is (string= - (spark '(0 30 55 80 33 150) :min -100) - "▃▄▅▆▄█")) - (is (string= - (spark '(0 30 55 80 33 150) :max 50) - "▁▅██▅█")) - (is (string= - (spark '(0 30 55 80 33 150) :min 30 :max 80) - "▁▁▄█▁█")) - ;; double-float, minus - (is (string= - (spark '(1.000000000005d0 0.000000000005d0 1.0d0)) - "█▁▇")) - (is (string= - (spark '(-1 0 -1)) - "▁█▁")) - (is (string= - (spark '(-1.000000000005d0 0.000000000005d0 -1.0d0)) - "▁█▁")) - ;; *ticks* - (let ((ternary '(-1 0 1 -1 1 0 0 -1 1 1 0))) - (is (string= - (spark ternary) - "▁▄█▁█▄▄▁██▄")) - (is (string= - (let ((*ticks* #(#\_ #\- #\¯))) - (spark ternary)) - "_-¯_¯--_¯¯-")) - (is (string= - (let ((*ticks* #(#\▄ #\⎯ #\▀))) - (spark ternary)) - "▄⎯▀▄▀⎯⎯▄▀▀⎯")) - (is (string= - (let ((*ticks* #(#\E #\O))) - (spark '(4 8 15 22 42) :key (lambda (n) (mod n 2)))) - "EEOEE"))) - ;; key - (flet ((range (start end) (loop for i from start below end collect i)) - (fib (n) (loop for x = 0 then y - and y = 1 then (+ x y) - repeat n - finally (return x))) - (fac (n) (labels ((rec (n acc) (if (<= n 1) acc (rec (1- n) (* n acc))))) - (rec n 1)))) - (is (string= - (spark (range 0 51) - :key (lambda (x) (sin (* x pi 1/4)))) - "▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█")) - (is (string= - (spark (range 0 51) - :key (lambda (x) (cos (* x pi 1/4)))) - "█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄")) - - (is (string= - (spark (range 0 51) - :key (lambda (x) (abs (cis (* x pi 1/4))))) - "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁")) - - (is (string= - (spark (range 0 51) - :key (lambda (x) (float (phase (cis (* x pi 1/4))) 1.0))) - "▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆")) - - (is (string= - (spark (range 1 7) :key #'log) - "▁▃▅▆▇█")) - - (is (string= - (spark (range 1 7) :key #'sqrt) - "▁▃▄▅▆█")) - (is (string= - (spark (range 1 7)) - "▁▂▃▅▆█")) - (is (string= - (spark (range 1 7) :key #'fib) - "▁▁▂▃▅█")) - (is (string= - (spark (range 1 7) :key #'exp) - "▁▁▁▁▃█")) - (is (string= - (spark (range 1 7) :key #'fac) - "▁▁▁▁▂█")) - (is (string= - (spark (range 1 7) :key #'isqrt) - "▁▁▁███")) - ;; misc - (flet ((lbits (n) (spark (map 'list #'digit-char-p (write-to-string n :base 2))))) - (is (string= - (lbits 42) - "█▁█▁█▁")) - (is (string= - (lbits 43) - "█▁█▁██")) - (is (string= - (lbits 44) - "█▁██▁▁")) - (is (string= - (lbits 45) - "█▁██▁█"))) - - ;; VSPARK - (is (string= - (vspark '()) - "")) - ;; singleton - (is (string= - (vspark '(1)) - " -1 1.5 2 -˫-----------------------+------------------------˧ -▏ -")) - - ;; constant - (is (string= - (vspark '(1 1)) - " -1 1.5 2 -˫-----------------------+------------------------˧ -▏ -▏ -")) - - - (is (string= - (vspark '(0 30 55 80 33 150)) - " -0 75 150 -˫-----------------------+------------------------˧ -▏ -██████████▏ -██████████████████▍ -██████████████████████████▋ -███████████▏ -██████████████████████████████████████████████████ -")) - - - ;; min, max - - (is (string= - (vspark '(0 30 55 80 33 150) :min -100) - " --100 25 150 -˫-----------------------+------------------------˧ -████████████████████▏ -██████████████████████████▏ -███████████████████████████████▏ -████████████████████████████████████▏ -██████████████████████████▋ -██████████████████████████████████████████████████ -")) - - (is (string= - (vspark '(0 30 55 80 33 150) :max 50) - " -0 25 50 -˫-----------------------+------------------------˧ -▏ -██████████████████████████████▏ -██████████████████████████████████████████████████ -██████████████████████████████████████████████████ -█████████████████████████████████▏ -██████████████████████████████████████████████████ -")) - - - (is (string= - (vspark '(0 30 55 80 33 150) :min 30 :max 80) - " -30 55 80 -˫-----------------------+------------------------˧ -▏ -▏ -█████████████████████████▏ -██████████████████████████████████████████████████ -███▏ -██████████████████████████████████████████████████ -")) - - ;; labels - (is (string= - (vspark '(1 0 .5) :labels '("on" "off" "unknown") - :size 1 - :scale? nil) - " - on █ - off ▏ -unknown ▌ -")) - - (is (string= - (vspark '(1 0 .5) :labels '("on" "off") - :size 1 - :scale? nil) - " - on █ -off ▏ - ▌ -")) - - (is (string= - (vspark '(1 0) :labels '("on" "off" "unknown") - :size 1 - :scale? nil) - " - on █ -off ▏ -")) - - ;; key - (is (string= - (vspark '(0 1 2 3 4 5 6 7 8) :key (lambda (x) (sin (* x pi 1/4)))) - " --1.0 0.0 1.0 -˫-----------------------+------------------------˧ -█████████████████████████▏ -██████████████████████████████████████████▋ -██████████████████████████████████████████████████ -██████████████████████████████████████████▋ -█████████████████████████▏ -███████▍ -▏ -███████▍ -████████████████████████▉ -")) - - ;; size - (is (string= - (vspark '(0 1 2 3 4 5 6 7 8) :key (lambda (x) (sin (* x pi 1/4))) - :size 10) - " --1.0 1.0 -˫--------˧ -█████▏ -████████▌ -██████████ -████████▌ -█████▏ -█▌ -▏ -█▌ -████▉ -")) - - ;; scale (mid-point) - (is (string= - (vspark '(0 1 2 3 4 5 6 7 8) :key (lambda (x) (sin (* x pi 1/4))) - :size 20) - " --1.0 0.0 1.0 -˫--------+---------˧ -██████████▏ -█████████████████▏ -████████████████████ -█████████████████▏ -██████████▏ -██▉ -▏ -██▉ -█████████▉ -")) - - (let ((life-expectancies '(("Africa" 56) - ("Americans" 76) - ("South-East Asia" 67) - ("Europe" 76) - ("Eastern Mediterranean" 68) - ("Western Pacific" 76) - ("Global" 70)))) - - (is (string= - (vspark life-expectancies :key #'second) - " -56 66 76 -˫-----------------------+------------------------˧ -▏ -██████████████████████████████████████████████████ -███████████████████████████▌ -██████████████████████████████████████████████████ -██████████████████████████████▏ -██████████████████████████████████████████████████ -███████████████████████████████████▏ -")) - - ;; newline? - (is (string= - (vspark life-expectancies :key #'second :scale? nil :newline? nil) - "▏ -██████████████████████████████████████████████████ -███████████████████████████▌ -██████████████████████████████████████████████████ -██████████████████████████████▏ -██████████████████████████████████████████████████ -███████████████████████████████████▏")) - - ;; scale? - (is (string= - (vspark life-expectancies :key #'second :scale? nil) - " -▏ -██████████████████████████████████████████████████ -███████████████████████████▌ -██████████████████████████████████████████████████ -██████████████████████████████▏ -██████████████████████████████████████████████████ -███████████████████████████████████▏ -")) - - ;; labels - (is (string= - (vspark life-expectancies - :key #'second - :labels (mapcar #'first life-expectancies)) - " - 56 66 76 - ˫------------+-------------˧ - Africa ▏ - Americans ████████████████████████████ - South-East Asia ███████████████▍ - Europe ████████████████████████████ -Eastern Mediterranean ████████████████▊ - Western Pacific ████████████████████████████ - Global ███████████████████▋ -")) - - ;; title - (is (string= - (vspark life-expectancies - :min 50 :max 80 - :key #'second - :labels (mapcar #'first life-expectancies) - :title "Life Expectancy") - " - Life Expectancy - 50 65 80 - ˫------------+-------------˧ - Africa █████▋ - Americans ████████████████████████▎ - South-East Asia ███████████████▉ - Europe ████████████████████████▎ -Eastern Mediterranean ████████████████▊ - Western Pacific ████████████████████████▎ - Global ██████████████████▋ -")) - - (is (string= - (spark (range 0 15) :key #'fib) - "▁▁▁▁▁▁▁▁▁▁▂▂▃▅█")) - - (is (string= - (vspark (range 0 15) :key #'fib) - " -0 188.5 377 -˫-----------------------+------------------------˧ -▏ -▏ -▏ -▎ -▍ -▋ -█▏ -█▊ -██▊ -████▌ -███████▍ -███████████▊ -███████████████████▏ -██████████████████████████████▉ -██████████████████████████████████████████████████ -"))))) - -(deftest repl ()) - -(deftest env () - (is (ld-library-path-list)) - (is (exec-path-list)) - (is (find-exe "sbcl"))) - -(deftest cli-ast () - "Validate the CLI/CLAP/AST parser." - (is (string= (cli-opt-name (cli-node-form (car (ast (proc-args *cli* '("--foo" "1")))))) - "foo")) - (signals clap-unknown-argument - (proc-args *cli* '("--log" "default" "--foo=11")))) - -(defmain foo-main (:exit nil) - (with-cli (*cli*) () - (log:trace! "defmain is OK") - t)) - -(deftest clap-main () - (is (null (funcall #'foo-main)))) - -(deftest sbcl-tools () - (with-sbcl (:noinform t :quit t) - (print 1))) diff -r c2f4e7ee921b -r 517c65b51e6b lisp/lib/cli/tests/clap.lisp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/lib/cli/tests/clap.lisp Tue Oct 01 21:52:17 2024 -0400 @@ -0,0 +1,71 @@ +;;; clap.lisp --- CLAP tests + +;; + +;;; Code: +(in-package :cli/tests) +(in-suite :cli) + +(defcmd flub-thunk + ;; FIX 2024-10-01: + (println *optc*) + (println *argc*) + (print *opts*) + (print *args*)) + +(defparameter *opts* '((:name "foo" :description "bar" :kind string) + (:name "bar" :description "foo" :kind string))) +(defparameter *cmd1* (make-cli :cmd :name "holla" :opts *opts* :description "cmd1 description")) +(defparameter *cmd2* (make-cli :cmd :name "ayo" :cmds (vector *cmd1*) :opts *opts* :description "cmd1 description")) +(defparameter *cmd3* (make-cli :cmd :name "flub" :opts *opts* :thunk 'flub-thunk)) +(defparameter *cmds* (make-cmds (list `(:name "baz" :description "baz" :opts ,*opts*) *cmd1* *cmd2* *cmd3*))) + +(defparameter *cli* (make-cli :cli :opts *opts* :cmds *cmds* :description "test cli")) + +(deftest mixed-args () + (with-cli (*cli*) '("--foo" "bar" "flub") + (is (string= "bar" (cli-opt-val (aref (opts *cli*) 0)))) + (is (null (cli-args *cli*))) + (do-cmd *cli*))) + +(deftest cli-ast () + "Validate the CLI/CLAP/AST parser." + (is (string= (cli-opt-name (cli-node-form (car (ast (proc-args *cli* '("--foo" "1")))))) + "foo")) + (signals clap-unknown-argument + (proc-args *cli* '("--log" "default" "--foo=11")))) + +(defmain foo-main (:exit nil) + (with-cli (*cli*) () + (log:trace! "defmain is OK") + t)) + +(deftest clap-main () + (is (null (funcall #'foo-main)))) + +(deftest clap-basic (:skip t) + "test basic CLAP functionality." + (with-cli (*cli* opts cmds args) *args* + (is (eq (make-shorty "test") #\t)) + (is (equalp (proc-args *cli* '("-f" "baz" "--bar=fax")) ;; not eql + (make-cli-ast + (list (make-cli-node 'opt (find-short-opts *cli* #\f)) + (make-cli-node 'cmd (find-cmd *cli* "baz")) + (make-cli-node 'opt (find-opts *cli* "bar")) + (make-cli-node 'arg "fax"))))) + (is (parse-args *cli* '("--bar" "baz" "-f" "yaks"))) + (is (stringp + (with-output-to-string (s) + (print-version *cli* s) + (print-usage *cli* s) + (print-help *cli* s)))) + (is (string= "foobar" (cli/clap:parse-string-opt "foobar"))) + (do-cmd *cli*))) + +(deftest clap-opts () + "CLAP opt tests." + (make-opt-parser trivial *arg*) + (is (reduce (lambda (x y) (and x y)) + (loop for k across *cli-opt-kinds* collect (cli-opt-kind-p k)))) + (is (parse-trivial-opt t)) + (is (null (parse-trivial-opt nil)))) diff -r c2f4e7ee921b -r 517c65b51e6b lisp/lib/cli/tests/pkg.lisp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/lib/cli/tests/pkg.lisp Tue Oct 01 21:52:17 2024 -0400 @@ -0,0 +1,644 @@ +;;; cli/tests.lisp --- CLI Tests + +;; + +;;; Code: +(defpackage :cli/tests + (:use :cl :std :rt :cli :cli/shell :cli/progress :cli/spark :cli/repl :cli/ansi :cli/prompt :cli/clap :cli/tools/sbcl :dat/sxp)) + +(in-package :cli/tests) +(declaim (optimize (debug 3) (safety 3))) +(defsuite :cli) +(in-suite :cli) + +(defun ansi-t01 () + (erase) + (cursor-position 0 0) + (princ "0") + (cursor-position 2 2) + (princ "1") + (cursor-position 5 15) + (princ "test") + (cursor-position 10 15) + (force-output) + (with-input-from-string (in (format nil "test~%~%")) + (let ((a (read-line in))) + (cursor-position 12 15) + (princ a) + (force-output)))) + +(defun ansi-t02 () + (print "normal") + (.sgr 1) + (print "bold") + (.sgr 4) + (print "bold underline") + (.sgr 7) + (print "bold underline reverse") + (.sgr 22) + (print "underline reverse") + (.sgr 24) + (print "reverse") + (.sgr 27) + (print "normal") + (.sgr 1 4 7) + (print "bold underline reverse") + (.sgr 0) + (print "normal") + (force-output)) + +(defun ansi-t03 () + "Display the 256 color palette." + (clear) + (loop for i from 0 to 255 do + (.sgr 48 5 i) + (princ #\space)) + (terpri) + (.sgr 0) + (loop for i from 0 to 255 do + (.sgr 38 5 i) + (princ "X")) + (.sgr 0) + (force-output) + ;; (sleep 3) + (.ris) + (force-output)) + +(defun ansi-t04 () + "Hide and show the cursor." + (princ "Cursor visible:") + (force-output) + ;; (sleep 2) + (terpri) + (princ "Cursor invisible:") + (hide-cursor) + (force-output) + ;; (sleep 2) + (terpri) + (princ "Cursor visible:") + (show-cursor) + (force-output) + ;; (sleep 2) + ) + +(defun ansi-t05 () + "Switch to and back from the alternate screen buffer." + (princ "Normal screen buffer. ") + (force-output) + ;; (sleep 2) + (save-cursor-position) + (use-alternate-screen-buffer) + (clear) + (princ "Alternate screen buffer.") + (force-output) + ;; (sleep 2) + (use-normal-screen-buffer) + (restore-cursor-position) + (princ "Back to Normal screen buffer.") + (force-output) + ;; (sleep 1) + ) + +(defun ansi-t06 () + "Set individual termios flags to enable raw and disable echo mode. + +Enabling raw mode allows read-char to return immediately after a key is pressed. + +In the default cooked mode, the entry has to be confirmed by pressing enter." + (set-tty-mode t :ignbrk nil + :brkint nil + :parmrk nil + :istrip nil + :inlcr nil + :igncr nil + :icrnl nil + :ixon nil + :opost nil + :echo nil + :echonl nil + :icanon nil + :isig nil + :iexten nil + :csize nil + :parenb nil + :vmin 1 + :vtime 0) + (erase) + (cursor-position 1 1) + (force-output) + (let ((a (read-char))) + (cursor-position 10 5) + (princ a) + (force-output)) + + (set-tty-mode t :echo t + :brkint t + :ignpar t + :istrip t + :icrnl t + :ixon t + :opost t + :isig t + :icanon t + :veol 0)) + +(defun ansi-t07 () + "Use combination modes that consist of several individual flags. + +Cooked and raw are opposite modes. Enabling cooked disbles raw and vice versa." + (set-tty-mode t :cooked nil) + (erase) + (cursor-position 1 1) + (force-output) + (let ((a (read-char))) + (cursor-position 3 1) + (princ a) + (force-output)) + (set-tty-mode t :raw nil)) + +(defun ansi-t08 () + "Why doesnt calling the stty utility work?" + (uiop:run-program "stty raw -echo" :ignore-error-status t) + (erase) + (cursor-position 1 1) + (force-output) + (let ((a (read-char))) + (cursor-position 2 1) + (princ a) + (force-output)) + (uiop:run-program "stty -raw echo" :ignore-error-status t)) + +(defun ansi-t09 () + "Query terminal size with ANSI escape sequences." + ;; Put the terminal into raw mode so we can read the "user input" + ;; of the reply char by char + ;; Turn off the echo or the sequence will be displayed + (set-tty-mode t :cooked nil :echo nil) + (save-cursor-position) + ;; Go to the bottom right corner of the terminal by attempting + ;; to go to some high value of row and column + (cursor-position 999 999) + (let (chars) + ;; The terminal returns an escape sequence to the standard input + (device-status-report) + (force-output) + ;; The reply isnt immediately available, the terminal does need + ;; some time to answer + (sleep 0.1) + ;; The reply has to be read as if the user typed an escape sequence + (loop for i = (read-char-no-hang *standard-input* nil) + until (null i) + do (push i chars)) + ;; Put the terminal back into its initial cooked state + (set-tty-mode t :raw nil :echo t) + (restore-cursor-position) + ;; Return the read sequence as a list of characters. + (nreverse chars))) + +(deftest ansi () + (with-input-from-string (in (format nil "~%~%")) + (ansi-t01) + (ansi-t02) + (ansi-t03) + (ansi-t04) + (ansi-t05))) + +;; TODO: needs to be compiled outside scope of test - contender for +;; fixture API +(defprompt tpfoo :prompt "testing:") + +(deftest cli-prompt (:skip t) + "Test CLI prompts" + (defvar tcoll nil) + (defvar thist nil) + (let ((*standard-input* (make-string-input-stream + (format nil "~A~%~A~%~%" "foobar" "foobar")))) + ;; prompts + (is (string= (tpfoo-prompt) "foobar")) + (is (string= "foobar" + (completing-read "nothing: " tcoll :history thist :default "foobar"))))) + +(deftest progress () + (flet ((%step () (cli/progress::update 1))) + (let ((*progress-bar-enabled* t) + (n 100)) + (with-progress-bar (n "TEST: # of steps = ~a" n) + (dotimes (i n) (%step)))))) + +(deftest spark () + (is (string= + (spark '(1 5 22 13 5)) + "▁▂█▅▂")) + (is (string= + (spark '(5.5 20)) + "▁█")) + (is (string= + (spark '(1 2 3 4 100 5 10 20 50 300)) + "▁▁▁▁▃▁▁▁▂█")) + (is (string= + (spark '(1 50 100)) + "▁▄█")) + (is (string= + (spark '(2 4 8)) + "▁▃█")) + (is (string= + (spark '(1 2 3 4 5)) + "▁▂▄▆█")) + (is (string= + (spark '(0 30 55 80 33 150)) + "▁▂▃▄▂█")) + ;; null + (is (string= + (spark '()) + "")) + ;; singleton + (is (string= + (spark '(42)) + "▁")) + ;; constant + (is (string= + (spark '(42 42)) + "▁▁")) + ;; min/max + (is (string= + (spark '(0 30 55 80 33 150) :min -100) + "▃▄▅▆▄█")) + (is (string= + (spark '(0 30 55 80 33 150) :max 50) + "▁▅██▅█")) + (is (string= + (spark '(0 30 55 80 33 150) :min 30 :max 80) + "▁▁▄█▁█")) + ;; double-float, minus + (is (string= + (spark '(1.000000000005d0 0.000000000005d0 1.0d0)) + "█▁▇")) + (is (string= + (spark '(-1 0 -1)) + "▁█▁")) + (is (string= + (spark '(-1.000000000005d0 0.000000000005d0 -1.0d0)) + "▁█▁")) + ;; *ticks* + (let ((ternary '(-1 0 1 -1 1 0 0 -1 1 1 0))) + (is (string= + (spark ternary) + "▁▄█▁█▄▄▁██▄")) + (is (string= + (let ((*ticks* #(#\_ #\- #\¯))) + (spark ternary)) + "_-¯_¯--_¯¯-")) + (is (string= + (let ((*ticks* #(#\▄ #\⎯ #\▀))) + (spark ternary)) + "▄⎯▀▄▀⎯⎯▄▀▀⎯")) + (is (string= + (let ((*ticks* #(#\E #\O))) + (spark '(4 8 15 22 42) :key (lambda (n) (mod n 2)))) + "EEOEE"))) + ;; key + (flet ((range (start end) (loop for i from start below end collect i)) + (fib (n) (loop for x = 0 then y + and y = 1 then (+ x y) + repeat n + finally (return x))) + (fac (n) (labels ((rec (n acc) (if (<= n 1) acc (rec (1- n) (* n acc))))) + (rec n 1)))) + (is (string= + (spark (range 0 51) + :key (lambda (x) (sin (* x pi 1/4)))) + "▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█")) + (is (string= + (spark (range 0 51) + :key (lambda (x) (cos (* x pi 1/4)))) + "█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄")) + + (is (string= + (spark (range 0 51) + :key (lambda (x) (abs (cis (* x pi 1/4))))) + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁")) + + (is (string= + (spark (range 0 51) + :key (lambda (x) (float (phase (cis (* x pi 1/4))) 1.0))) + "▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆")) + + (is (string= + (spark (range 1 7) :key #'log) + "▁▃▅▆▇█")) + + (is (string= + (spark (range 1 7) :key #'sqrt) + "▁▃▄▅▆█")) + (is (string= + (spark (range 1 7)) + "▁▂▃▅▆█")) + (is (string= + (spark (range 1 7) :key #'fib) + "▁▁▂▃▅█")) + (is (string= + (spark (range 1 7) :key #'exp) + "▁▁▁▁▃█")) + (is (string= + (spark (range 1 7) :key #'fac) + "▁▁▁▁▂█")) + (is (string= + (spark (range 1 7) :key #'isqrt) + "▁▁▁███")) + ;; misc + (flet ((lbits (n) (spark (map 'list #'digit-char-p (write-to-string n :base 2))))) + (is (string= + (lbits 42) + "█▁█▁█▁")) + (is (string= + (lbits 43) + "█▁█▁██")) + (is (string= + (lbits 44) + "█▁██▁▁")) + (is (string= + (lbits 45) + "█▁██▁█"))) + + ;; VSPARK + (is (string= + (vspark '()) + "")) + ;; singleton + (is (string= + (vspark '(1)) + " +1 1.5 2 +˫-----------------------+------------------------˧ +▏ +")) + + ;; constant + (is (string= + (vspark '(1 1)) + " +1 1.5 2 +˫-----------------------+------------------------˧ +▏ +▏ +")) + + + (is (string= + (vspark '(0 30 55 80 33 150)) + " +0 75 150 +˫-----------------------+------------------------˧ +▏ +██████████▏ +██████████████████▍ +██████████████████████████▋ +███████████▏ +██████████████████████████████████████████████████ +")) + + + ;; min, max + + (is (string= + (vspark '(0 30 55 80 33 150) :min -100) + " +-100 25 150 +˫-----------------------+------------------------˧ +████████████████████▏ +██████████████████████████▏ +███████████████████████████████▏ +████████████████████████████████████▏ +██████████████████████████▋ +██████████████████████████████████████████████████ +")) + + (is (string= + (vspark '(0 30 55 80 33 150) :max 50) + " +0 25 50 +˫-----------------------+------------------------˧ +▏ +██████████████████████████████▏ +██████████████████████████████████████████████████ +██████████████████████████████████████████████████ +█████████████████████████████████▏ +██████████████████████████████████████████████████ +")) + + + (is (string= + (vspark '(0 30 55 80 33 150) :min 30 :max 80) + " +30 55 80 +˫-----------------------+------------------------˧ +▏ +▏ +█████████████████████████▏ +██████████████████████████████████████████████████ +███▏ +██████████████████████████████████████████████████ +")) + + ;; labels + (is (string= + (vspark '(1 0 .5) :labels '("on" "off" "unknown") + :size 1 + :scale? nil) + " + on █ + off ▏ +unknown ▌ +")) + + (is (string= + (vspark '(1 0 .5) :labels '("on" "off") + :size 1 + :scale? nil) + " + on █ +off ▏ + ▌ +")) + + (is (string= + (vspark '(1 0) :labels '("on" "off" "unknown") + :size 1 + :scale? nil) + " + on █ +off ▏ +")) + + ;; key + (is (string= + (vspark '(0 1 2 3 4 5 6 7 8) :key (lambda (x) (sin (* x pi 1/4)))) + " +-1.0 0.0 1.0 +˫-----------------------+------------------------˧ +█████████████████████████▏ +██████████████████████████████████████████▋ +██████████████████████████████████████████████████ +██████████████████████████████████████████▋ +█████████████████████████▏ +███████▍ +▏ +███████▍ +████████████████████████▉ +")) + + ;; size + (is (string= + (vspark '(0 1 2 3 4 5 6 7 8) :key (lambda (x) (sin (* x pi 1/4))) + :size 10) + " +-1.0 1.0 +˫--------˧ +█████▏ +████████▌ +██████████ +████████▌ +█████▏ +█▌ +▏ +█▌ +████▉ +")) + + ;; scale (mid-point) + (is (string= + (vspark '(0 1 2 3 4 5 6 7 8) :key (lambda (x) (sin (* x pi 1/4))) + :size 20) + " +-1.0 0.0 1.0 +˫--------+---------˧ +██████████▏ +█████████████████▏ +████████████████████ +█████████████████▏ +██████████▏ +██▉ +▏ +██▉ +█████████▉ +")) + + (let ((life-expectancies '(("Africa" 56) + ("Americans" 76) + ("South-East Asia" 67) + ("Europe" 76) + ("Eastern Mediterranean" 68) + ("Western Pacific" 76) + ("Global" 70)))) + + (is (string= + (vspark life-expectancies :key #'second) + " +56 66 76 +˫-----------------------+------------------------˧ +▏ +██████████████████████████████████████████████████ +███████████████████████████▌ +██████████████████████████████████████████████████ +██████████████████████████████▏ +██████████████████████████████████████████████████ +███████████████████████████████████▏ +")) + + ;; newline? + (is (string= + (vspark life-expectancies :key #'second :scale? nil :newline? nil) + "▏ +██████████████████████████████████████████████████ +███████████████████████████▌ +██████████████████████████████████████████████████ +██████████████████████████████▏ +██████████████████████████████████████████████████ +███████████████████████████████████▏")) + + ;; scale? + (is (string= + (vspark life-expectancies :key #'second :scale? nil) + " +▏ +██████████████████████████████████████████████████ +███████████████████████████▌ +██████████████████████████████████████████████████ +██████████████████████████████▏ +██████████████████████████████████████████████████ +███████████████████████████████████▏ +")) + + ;; labels + (is (string= + (vspark life-expectancies + :key #'second + :labels (mapcar #'first life-expectancies)) + " + 56 66 76 + ˫------------+-------------˧ + Africa ▏ + Americans ████████████████████████████ + South-East Asia ███████████████▍ + Europe ████████████████████████████ +Eastern Mediterranean ████████████████▊ + Western Pacific ████████████████████████████ + Global ███████████████████▋ +")) + + ;; title + (is (string= + (vspark life-expectancies + :min 50 :max 80 + :key #'second + :labels (mapcar #'first life-expectancies) + :title "Life Expectancy") + " + Life Expectancy + 50 65 80 + ˫------------+-------------˧ + Africa █████▋ + Americans ████████████████████████▎ + South-East Asia ███████████████▉ + Europe ████████████████████████▎ +Eastern Mediterranean ████████████████▊ + Western Pacific ████████████████████████▎ + Global ██████████████████▋ +")) + + (is (string= + (spark (range 0 15) :key #'fib) + "▁▁▁▁▁▁▁▁▁▁▂▂▃▅█")) + + (is (string= + (vspark (range 0 15) :key #'fib) + " +0 188.5 377 +˫-----------------------+------------------------˧ +▏ +▏ +▏ +▎ +▍ +▋ +█▏ +█▊ +██▊ +████▌ +███████▍ +███████████▊ +███████████████████▏ +██████████████████████████████▉ +██████████████████████████████████████████████████ +"))))) + +(deftest repl ()) + +(deftest env () + (ld-library-path-list) + (is (exec-path-list)) + (is (find-exe "sbcl"))) + +(deftest sbcl-tools () + (with-sbcl (:noinform t :quit t) + (print 1)))