Polo
Polo

Reputation: 113

Testing command line arguments Parsing - Common Lisp

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

Answers (2)

coredump
coredump

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

Manfred
Manfred

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

Related Questions