Reputation: 113
Using the fiveam testing package, I would like to test the parsing of CLI arguments which is handled by the main.lisp script. However the main function which is parsing the arguments does not take any arguments. I am therefore wondering the following, What is the best practice to test command line parsing in my case ?
If so, how should I implement it ? How can I test the main function in main-missing-arg-p() as if I would execute the program with "--interface wlo1" as an argument ?
main.lisp,
(in-package :cl-user)
(defpackage kafar
(:use :cl)
(:export main))
(in-package :kafar)
;;; CLI Parser
(opts:define-opts
(:name :port
:description "port on the loopback interface on which the proxy server shall run. Recommended: 1080"
:short #\p
:long "port"
:required t
:arg-parser #'parse-integer
)
(:name :interface
:description "name of the interface kafar shall listen on"
:short #\i
:required t
:long "interface"
:arg-parser #'identity)
(:name :help
:description "print this help text"
:short #\h
:long "help"))
(defun unknown-option (condition)
...)
(defun missing-arg (condition)
...)
(defmacro when-option ((options opt) &body body)
`...)
(defun main ()
(multiple-value-bind (options free-args)
(handler-case
(handler-bind ((opts:unknown-option #'unknown-option))
(opts:get-opts))
(opts:missing-arg (condition)
(format t "fatal: option ~s needs an argument!~%"
(opts:option condition))
(uiop:quit))
(opts:arg-parser-failed (condition)
(format t "fatal: cannot parse ~s as argument of ~s.~%"
(opts:raw-arg condition)
(opts:option condition))
(uiop:quit))
...
(when-option (options :port)
(let ((interfacei (getf options :interface))
(porti (getf options :port)))
(kafar/proxy:proxy-server porti interfacei))
(uiop:quit)
)))
test-main.lisp:
(in-package :kafar/tests)
;;; Testing Proxy server
(def-suite* parse-suite
:description "Test parsing of CLI arguments"
:in kafar-suite)
;; test the stdout without arguments
(test main-help
; call main function without aguments
(print (unix-opts:argv)) ; => ("sbcl")
(let ((output (with-output-to-string (*error-output*) (kafar:main))))
(is (string= "warning: missing required options: \"--port\", \"--interface\"" output))))
;; test stderr missing port argument
(test main-missing-arg-p
; define interface argument, not define port argument
;Potentially modify (unix-opts:argv) (sb-ext:*posix-argv*) such that they would be equal to ("sbcl" "--interface 80")
; call main function (handling CLI argument parsing)
)
I am executing the test as follows in the shell,
sbcl --non-interactive --eval "(progn (ql:quickload '(fiveam usocket nibbles unix-opts trivial-coverage)) (asdf:load-asd (merge-pathnames \"kafar.asd\" (uiop:getcwd))) (asdf:test-system 'kafar/coverage))"
Upvotes: 1
Views: 121
Reputation: 38799
Using your opts:define-opts
clause, you can test arguments by giving them in a list to get-opts
:
CL-USER> (opts:get-opts '("--interface" "wlo1"))
; Evaluation aborted on #<UNIX-OPTS:MISSING-REQUIRED-OPTION {10024637C3}>.
So I would let main
be split in run
and entry
functions, the first one accepting a list of arguments, and the second one being the entry point of your program, which calls run
with (opts:argv)
.
Upvotes: 2
Reputation: 517
You could use the humble object pattern (https://martinfowler.com/bliki/HumbleObject.html) which effectively would involve passing the received cmd line arguments to another function you are able to test. You could determine whether to pass the parameters as is, or define a contract to transform the parameters. In any case it would be important to know how 'unix-opts' will deliver the parameters.
Upvotes: 2