changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/lib/cli/tests.lisp

changeset 651: af486e0a40c9
parent: 74e563ed4537
child: 328e1ff73938
author: Richard Westhaver <ellis@rwest.io>
date: Sat, 14 Sep 2024 22:13:06 -0400
permissions: -rw-r--r--
description: multi-binaries, working on removing x.lisp
1 ;;; cli/tests.lisp --- CLI Tests
2 
3 ;;
4 
5 ;;; Code:
6 (defpackage :cli/tests
7  (:use :cl :std :rt :cli :cli/shell :cli/progress :cli/spark :cli/repl :cli/ansi :cli/prompt :cli/clap :cli/tools/sbcl :dat/sxp))
8 
9 (in-package :cli/tests)
10 (declaim (optimize (debug 3) (safety 3)))
11 (defsuite :cli)
12 (in-suite :cli)
13 
14 (defun ansi-t01 ()
15  (erase)
16  (cursor-position 0 0)
17  (princ "0")
18  (cursor-position 2 2)
19  (princ "1")
20  (cursor-position 5 15)
21  (princ "test")
22  (cursor-position 10 15)
23  (force-output)
24  (with-input-from-string (in (format nil "test~%~%"))
25  (let ((a (read-line in)))
26  (cursor-position 12 15)
27  (princ a)
28  (force-output))))
29 
30 (defun ansi-t02 ()
31  (print "normal")
32  (.sgr 1)
33  (print "bold")
34  (.sgr 4)
35  (print "bold underline")
36  (.sgr 7)
37  (print "bold underline reverse")
38  (.sgr 22)
39  (print "underline reverse")
40  (.sgr 24)
41  (print "reverse")
42  (.sgr 27)
43  (print "normal")
44  (.sgr 1 4 7)
45  (print "bold underline reverse")
46  (.sgr 0)
47  (print "normal")
48  (force-output))
49 
50 (defun ansi-t03 ()
51  "Display the 256 color palette."
52  (clear)
53  (loop for i from 0 to 255 do
54  (.sgr 48 5 i)
55  (princ #\space))
56  (terpri)
57  (.sgr 0)
58  (loop for i from 0 to 255 do
59  (.sgr 38 5 i)
60  (princ "X"))
61  (.sgr 0)
62  (force-output)
63  ;; (sleep 3)
64  (.ris)
65  (force-output))
66 
67 (defun ansi-t04 ()
68  "Hide and show the cursor."
69  (princ "Cursor visible:")
70  (force-output)
71  ;; (sleep 2)
72  (terpri)
73  (princ "Cursor invisible:")
74  (hide-cursor)
75  (force-output)
76  ;; (sleep 2)
77  (terpri)
78  (princ "Cursor visible:")
79  (show-cursor)
80  (force-output)
81  ;; (sleep 2)
82  )
83 
84 (defun ansi-t05 ()
85  "Switch to and back from the alternate screen buffer."
86  (princ "Normal screen buffer. ")
87  (force-output)
88  ;; (sleep 2)
89  (save-cursor-position)
90  (use-alternate-screen-buffer)
91  (clear)
92  (princ "Alternate screen buffer.")
93  (force-output)
94  ;; (sleep 2)
95  (use-normal-screen-buffer)
96  (restore-cursor-position)
97  (princ "Back to Normal screen buffer.")
98  (force-output)
99  ;; (sleep 1)
100  )
101 
102 (defun ansi-t06 ()
103  "Set individual termios flags to enable raw and disable echo mode.
104 
105 Enabling raw mode allows read-char to return immediately after a key is pressed.
106 
107 In the default cooked mode, the entry has to be confirmed by pressing enter."
108  (set-tty-mode t :ignbrk nil
109  :brkint nil
110  :parmrk nil
111  :istrip nil
112  :inlcr nil
113  :igncr nil
114  :icrnl nil
115  :ixon nil
116  :opost nil
117  :echo nil
118  :echonl nil
119  :icanon nil
120  :isig nil
121  :iexten nil
122  :csize nil
123  :parenb nil
124  :vmin 1
125  :vtime 0)
126  (erase)
127  (cursor-position 1 1)
128  (force-output)
129  (let ((a (read-char)))
130  (cursor-position 10 5)
131  (princ a)
132  (force-output))
133 
134  (set-tty-mode t :echo t
135  :brkint t
136  :ignpar t
137  :istrip t
138  :icrnl t
139  :ixon t
140  :opost t
141  :isig t
142  :icanon t
143  :veol 0))
144 
145 (defun ansi-t07 ()
146  "Use combination modes that consist of several individual flags.
147 
148 Cooked and raw are opposite modes. Enabling cooked disbles raw and vice versa."
149  (set-tty-mode t :cooked nil)
150  (erase)
151  (cursor-position 1 1)
152  (force-output)
153  (let ((a (read-char)))
154  (cursor-position 3 1)
155  (princ a)
156  (force-output))
157  (set-tty-mode t :raw nil))
158 
159 (defun ansi-t08 ()
160  "Why doesnt calling the stty utility work?"
161  (uiop:run-program "stty raw -echo" :ignore-error-status t)
162  (erase)
163  (cursor-position 1 1)
164  (force-output)
165  (let ((a (read-char)))
166  (cursor-position 2 1)
167  (princ a)
168  (force-output))
169  (uiop:run-program "stty -raw echo" :ignore-error-status t))
170 
171 (defun ansi-t09 ()
172  "Query terminal size with ANSI escape sequences."
173  ;; Put the terminal into raw mode so we can read the "user input"
174  ;; of the reply char by char
175  ;; Turn off the echo or the sequence will be displayed
176  (set-tty-mode t :cooked nil :echo nil)
177  (save-cursor-position)
178  ;; Go to the bottom right corner of the terminal by attempting
179  ;; to go to some high value of row and column
180  (cursor-position 999 999)
181  (let (chars)
182  ;; The terminal returns an escape sequence to the standard input
183  (device-status-report)
184  (force-output)
185  ;; The reply isnt immediately available, the terminal does need
186  ;; some time to answer
187  (sleep 0.1)
188  ;; The reply has to be read as if the user typed an escape sequence
189  (loop for i = (read-char-no-hang *standard-input* nil)
190  until (null i)
191  do (push i chars))
192  ;; Put the terminal back into its initial cooked state
193  (set-tty-mode t :raw nil :echo t)
194  (restore-cursor-position)
195  ;; Return the read sequence as a list of characters.
196  (nreverse chars)))
197 
198 (deftest ansi ()
199  (with-input-from-string (in (format nil "~%~%"))
200  (ansi-t01)
201  (ansi-t02)
202  (ansi-t03)
203  (ansi-t04)
204  (ansi-t05)))
205 
206 ;; TODO: needs to be compiled outside scope of test - contender for
207 ;; fixture API
208 (defprompt tpfoo :prompt "testing:")
209 
210 (deftest cli-prompt (:skip t)
211  "Test CLI prompts"
212  (defvar tcoll nil)
213  (defvar thist nil)
214  (let ((*standard-input* (make-string-input-stream
215  (format nil "~A~%~A~%~%" "foobar" "foobar"))))
216  ;; prompts
217  (is (string= (tpfoo-prompt) "foobar"))
218  (is (string= "foobar"
219  (completing-read "nothing: " tcoll :history thist :default "foobar")))))
220 
221 (defparameter *opts* '((:name "foo" :global t :description "bar" :kind string)
222  (:name "bar" :description "foo" :kind string)))
223 
224 (defparameter *cmd1* (make-cli :cmd :name "holla" :opts *opts* :description "cmd1 description"))
225 (defparameter *cmd2* (make-cli :cmd :name "ayo" :cmds (vector *cmd1*) :opts *opts* :description "cmd1 description"))
226 (defparameter *cmds* (make-cmds (list `(:name "baz" :description "baz" :opts ,*opts*) *cmd1* *cmd2*)))
227 
228 (defparameter *cli* (make-cli :cli :opts *opts* :cmds *cmds* :description "test cli"))
229 
230 
231 (deftest clap-basic (:skip t)
232  "test basic CLAP functionality."
233  (is (eq (make-shorty "test") #\t))
234  (is (equalp (proc-args *cli* '("-f" "baz" "--bar=fax")) ;; not eql
235  (make-cli-ast
236  (list (make-cli-node 'opt (find-short-opts cli #\f))
237  (make-cli-node 'cmd (find-cmd cli "baz"))
238  (make-cli-node 'opt (find-opts cli "bar"))
239  (make-cli-node 'arg "fax")))))
240  (is (parse-args cli '("--bar" "baz" "-f" "yaks")))
241  (is (stringp
242  (with-output-to-string (s)
243  (print-version *cli* s)
244  (print-usage *cli* s)
245  (print-help *cli* s))))
246  (is (string= "foobar" (cli/clap:parse-string-opt "foobar"))))
247 
248 (make-opt-parser thing *arg*)
249 
250 (deftest clap-opts ()
251  "CLAP opt tests."
252  (is (reduce (lambda (x y) (and x y))
253  (loop for k across *cli-opt-kinds* collect (cli-opt-kind-p k))))
254  (is (parse-thing-opt t))
255  (is (null (parse-thing-opt nil))))
256 
257 (deftest progress ()
258  (flet ((%step () (cli/progress::update 1)))
259  (let ((*progress-bar-enabled* t)
260  (n 100))
261  (with-progress-bar (n "TEST: # of steps = ~a" n)
262  (dotimes (i n) (%step))))))
263 
264 (deftest spark ()
265  (is (string=
266  (spark '(1 5 22 13 5))
267  "▁▂█▅▂"))
268  (is (string=
269  (spark '(5.5 20))
270  "▁█"))
271  (is (string=
272  (spark '(1 2 3 4 100 5 10 20 50 300))
273  "▁▁▁▁▃▁▁▁▂█"))
274  (is (string=
275  (spark '(1 50 100))
276  "▁▄█"))
277  (is (string=
278  (spark '(2 4 8))
279  "▁▃█"))
280  (is (string=
281  (spark '(1 2 3 4 5))
282  "▁▂▄▆█"))
283  (is (string=
284  (spark '(0 30 55 80 33 150))
285  "▁▂▃▄▂█"))
286  ;; null
287  (is (string=
288  (spark '())
289  ""))
290  ;; singleton
291  (is (string=
292  (spark '(42))
293  "▁"))
294  ;; constant
295  (is (string=
296  (spark '(42 42))
297  "▁▁"))
298  ;; min/max
299  (is (string=
300  (spark '(0 30 55 80 33 150) :min -100)
301  "▃▄▅▆▄█"))
302  (is (string=
303  (spark '(0 30 55 80 33 150) :max 50)
304  "▁▅██▅█"))
305  (is (string=
306  (spark '(0 30 55 80 33 150) :min 30 :max 80)
307  "▁▁▄█▁█"))
308  ;; double-float, minus
309  (is (string=
310  (spark '(1.000000000005d0 0.000000000005d0 1.0d0))
311  "█▁▇"))
312  (is (string=
313  (spark '(-1 0 -1))
314  "▁█▁"))
315  (is (string=
316  (spark '(-1.000000000005d0 0.000000000005d0 -1.0d0))
317  "▁█▁"))
318  ;; *ticks*
319  (let ((ternary '(-1 0 1 -1 1 0 0 -1 1 1 0)))
320  (is (string=
321  (spark ternary)
322  "▁▄█▁█▄▄▁██▄"))
323  (is (string=
324  (let ((*ticks* #(#\_ #\- #\¯)))
325  (spark ternary))
326  "_-¯_¯--_¯¯-"))
327  (is (string=
328  (let ((*ticks* #(#\▄ #\⎯ #\▀)))
329  (spark ternary))
330  "▄⎯▀▄▀⎯⎯▄▀▀⎯"))
331  (is (string=
332  (let ((*ticks* #(#\E #\O)))
333  (spark '(4 8 15 22 42) :key (lambda (n) (mod n 2))))
334  "EEOEE")))
335  ;; key
336  (flet ((range (start end) (loop for i from start below end collect i))
337  (fib (n) (loop for x = 0 then y
338  and y = 1 then (+ x y)
339  repeat n
340  finally (return x)))
341  (fac (n) (labels ((rec (n acc) (if (<= n 1) acc (rec (1- n) (* n acc)))))
342  (rec n 1))))
343  (is (string=
344  (spark (range 0 51)
345  :key (lambda (x) (sin (* x pi 1/4))))
346  "▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█"))
347  (is (string=
348  (spark (range 0 51)
349  :key (lambda (x) (cos (* x pi 1/4))))
350  "█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄▂▁▂▄▆█▆▄"))
351 
352  (is (string=
353  (spark (range 0 51)
354  :key (lambda (x) (abs (cis (* x pi 1/4)))))
355  "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁"))
356 
357  (is (string=
358  (spark (range 0 51)
359  :key (lambda (x) (float (phase (cis (* x pi 1/4))) 1.0)))
360  "▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆▇█▁▁▂▄▅▆"))
361 
362  (is (string=
363  (spark (range 1 7) :key #'log)
364  "▁▃▅▆▇█"))
365 
366  (is (string=
367  (spark (range 1 7) :key #'sqrt)
368  "▁▃▄▅▆█"))
369  (is (string=
370  (spark (range 1 7))
371  "▁▂▃▅▆█"))
372  (is (string=
373  (spark (range 1 7) :key #'fib)
374  "▁▁▂▃▅█"))
375  (is (string=
376  (spark (range 1 7) :key #'exp)
377  "▁▁▁▁▃█"))
378  (is (string=
379  (spark (range 1 7) :key #'fac)
380  "▁▁▁▁▂█"))
381  (is (string=
382  (spark (range 1 7) :key #'isqrt)
383  "▁▁▁███"))
384  ;; misc
385  (flet ((lbits (n) (spark (map 'list #'digit-char-p (write-to-string n :base 2)))))
386  (is (string=
387  (lbits 42)
388  "█▁█▁█▁"))
389  (is (string=
390  (lbits 43)
391  "█▁█▁██"))
392  (is (string=
393  (lbits 44)
394  "█▁██▁▁"))
395  (is (string=
396  (lbits 45)
397  "█▁██▁█")))
398 
399  ;; VSPARK
400  (is (string=
401  (vspark '())
402  ""))
403  ;; singleton
404  (is (string=
405  (vspark '(1))
406  "
407 1 1.5 2
408 ˫-----------------------+------------------------˧
409 
410 "))
411 
412  ;; constant
413  (is (string=
414  (vspark '(1 1))
415  "
416 1 1.5 2
417 ˫-----------------------+------------------------˧
418 
419 
420 "))
421 
422 
423  (is (string=
424  (vspark '(0 30 55 80 33 150))
425  "
426 0 75 150
427 ˫-----------------------+------------------------˧
428 
429 ██████████▏
430 ██████████████████▍
431 ██████████████████████████▋
432 ███████████▏
433 ██████████████████████████████████████████████████
434 "))
435 
436 
437  ;; min, max
438 
439  (is (string=
440  (vspark '(0 30 55 80 33 150) :min -100)
441  "
442 -100 25 150
443 ˫-----------------------+------------------------˧
444 ████████████████████▏
445 ██████████████████████████▏
446 ███████████████████████████████▏
447 ████████████████████████████████████▏
448 ██████████████████████████▋
449 ██████████████████████████████████████████████████
450 "))
451 
452  (is (string=
453  (vspark '(0 30 55 80 33 150) :max 50)
454  "
455 0 25 50
456 ˫-----------------------+------------------------˧
457 
458 ██████████████████████████████▏
459 ██████████████████████████████████████████████████
460 ██████████████████████████████████████████████████
461 █████████████████████████████████▏
462 ██████████████████████████████████████████████████
463 "))
464 
465 
466  (is (string=
467  (vspark '(0 30 55 80 33 150) :min 30 :max 80)
468  "
469 30 55 80
470 ˫-----------------------+------------------------˧
471 
472 
473 █████████████████████████▏
474 ██████████████████████████████████████████████████
475 ███▏
476 ██████████████████████████████████████████████████
477 "))
478 
479  ;; labels
480  (is (string=
481  (vspark '(1 0 .5) :labels '("on" "off" "unknown")
482  :size 1
483  :scale? nil)
484  "
485  on █
486  off ▏
487 unknown ▌
488 "))
489 
490  (is (string=
491  (vspark '(1 0 .5) :labels '("on" "off")
492  :size 1
493  :scale? nil)
494  "
495  on █
496 off ▏
497 
498 "))
499 
500  (is (string=
501  (vspark '(1 0) :labels '("on" "off" "unknown")
502  :size 1
503  :scale? nil)
504  "
505  on █
506 off ▏
507 "))
508 
509  ;; key
510  (is (string=
511  (vspark '(0 1 2 3 4 5 6 7 8) :key (lambda (x) (sin (* x pi 1/4))))
512  "
513 -1.0 0.0 1.0
514 ˫-----------------------+------------------------˧
515 █████████████████████████▏
516 ██████████████████████████████████████████▋
517 ██████████████████████████████████████████████████
518 ██████████████████████████████████████████▋
519 █████████████████████████▏
520 ███████▍
521 
522 ███████▍
523 ████████████████████████▉
524 "))
525 
526  ;; size
527  (is (string=
528  (vspark '(0 1 2 3 4 5 6 7 8) :key (lambda (x) (sin (* x pi 1/4)))
529  :size 10)
530  "
531 -1.0 1.0
532 ˫--------˧
533 █████▏
534 ████████▌
535 ██████████
536 ████████▌
537 █████▏
538 █▌
539 
540 █▌
541 ████▉
542 "))
543 
544  ;; scale (mid-point)
545  (is (string=
546  (vspark '(0 1 2 3 4 5 6 7 8) :key (lambda (x) (sin (* x pi 1/4)))
547  :size 20)
548  "
549 -1.0 0.0 1.0
550 ˫--------+---------˧
551 ██████████▏
552 █████████████████▏
553 ████████████████████
554 █████████████████▏
555 ██████████▏
556 ██▉
557 
558 ██▉
559 █████████▉
560 "))
561 
562  (let ((life-expectancies '(("Africa" 56)
563  ("Americans" 76)
564  ("South-East Asia" 67)
565  ("Europe" 76)
566  ("Eastern Mediterranean" 68)
567  ("Western Pacific" 76)
568  ("Global" 70))))
569 
570  (is (string=
571  (vspark life-expectancies :key #'second)
572  "
573 56 66 76
574 ˫-----------------------+------------------------˧
575 
576 ██████████████████████████████████████████████████
577 ███████████████████████████▌
578 ██████████████████████████████████████████████████
579 ██████████████████████████████▏
580 ██████████████████████████████████████████████████
581 ███████████████████████████████████▏
582 "))
583 
584  ;; newline?
585  (is (string=
586  (vspark life-expectancies :key #'second :scale? nil :newline? nil)
587  "▏
588 ██████████████████████████████████████████████████
589 ███████████████████████████▌
590 ██████████████████████████████████████████████████
591 ██████████████████████████████▏
592 ██████████████████████████████████████████████████
593 ███████████████████████████████████▏"))
594 
595  ;; scale?
596  (is (string=
597  (vspark life-expectancies :key #'second :scale? nil)
598  "
599 
600 ██████████████████████████████████████████████████
601 ███████████████████████████▌
602 ██████████████████████████████████████████████████
603 ██████████████████████████████▏
604 ██████████████████████████████████████████████████
605 ███████████████████████████████████▏
606 "))
607 
608  ;; labels
609  (is (string=
610  (vspark life-expectancies
611  :key #'second
612  :labels (mapcar #'first life-expectancies))
613  "
614  56 66 76
615  ˫------------+-------------˧
616  Africa ▏
617  Americans ████████████████████████████
618  South-East Asia ███████████████▍
619  Europe ████████████████████████████
620 Eastern Mediterranean ████████████████▊
621  Western Pacific ████████████████████████████
622  Global ███████████████████▋
623 "))
624 
625  ;; title
626  (is (string=
627  (vspark life-expectancies
628  :min 50 :max 80
629  :key #'second
630  :labels (mapcar #'first life-expectancies)
631  :title "Life Expectancy")
632  "
633  Life Expectancy
634  50 65 80
635  ˫------------+-------------˧
636  Africa █████▋
637  Americans ████████████████████████▎
638  South-East Asia ███████████████▉
639  Europe ████████████████████████▎
640 Eastern Mediterranean ████████████████▊
641  Western Pacific ████████████████████████▎
642  Global ██████████████████▋
643 "))
644 
645  (is (string=
646  (spark (range 0 15) :key #'fib)
647  "▁▁▁▁▁▁▁▁▁▁▂▂▃▅█"))
648 
649  (is (string=
650  (vspark (range 0 15) :key #'fib)
651  "
652 0 188.5 377
653 ˫-----------------------+------------------------˧
654 
655 
656 
657 
658 
659 
660 █▏
661 █▊
662 ██▊
663 ████▌
664 ███████▍
665 ███████████▊
666 ███████████████████▏
667 ██████████████████████████████▉
668 ██████████████████████████████████████████████████
669 ")))))
670 
671 (deftest repl ())
672 
673 (deftest env ()
674  (is (ld-library-path-list))
675  (is (exec-path-list))
676  (is (find-exe "sbcl")))
677 
678 (deftest cli-ast ()
679  "Validate the CLI/CLAP/AST parser."
680  (is (string= (cli-opt-name (cli-node-form (car (ast (proc-args *cli* '("--foo" "1"))))))
681  "foo"))
682  (signals clap-unknown-argument
683  (proc-args *cli* '("--log" "default" "--foo=11"))))
684 
685 (defmain foo-main (:exit nil :export nil)
686  (with-cli (*cli*) ()
687  (log:trace! "defmain is OK")
688  t))
689 
690 (deftest clap-main ()
691  (is (null (funcall #'foo-main))))
692 
693 (deftest sbcl-tools ()
694  (with-sbcl (:noinform t :quit t)
695  (print 1)))