Search code examples
testingcommon-lispfiveam

Testing with fiveam


I can't figure out how to test a function with fiveam. I setup a project using cl-project. My project name is my-projects/proj1:

~/quicklisp% tree local-projects 
local-projects
├── dtrace
│   ├── dtrace.asd
│   └── dtrace.lisp
├── my-projects
│   └── proj1
│       ├── #proj1.asd#
│       ├── README.markdown
│       ├── README.org
│       ├── proj1.asd
│       ├── proj1.asd~
│       ├── src
│       │   ├── main.fasl
│       │   ├── main.lisp
│       │   └── main.lisp~
│       └── tests
│           ├── main.fasl
│           ├── main.lisp
│           └── main.lisp~
└── system-index.txt

And:

~/quicklisp/local-projects% cat system-index.txt 
dtrace/dtrace.asd
my-projects/proj1/proj1.asd

Here is proj1/proj1.asd:

(defsystem "proj1"
  :version "0.0.1"
  :author "7stud"
  :license "any"
  :depends-on ()
  :components ((:module "src"
                :components
                ((:file "main"))))
  :description ""
  :in-order-to ((test-op (test-op "proj1/tests"))))

(defsystem "proj1/tests"
  :author "7stud"
  :license "any"
  :depends-on ("proj1"
               "fiveam")
  :components ((:module "tests"
                :components
                ((:file "main"))))
  :description "Test system for proj1"
  :perform (test-op (op c) (symbol-call :fiveam '#:run! :proj1)))

proj1/src/main.lisp:

(defpackage proj1
  (:use :cl))
(in-package :proj1)

;; blah blah blah.

(defun anyoddp (number-list)
  (cond ((null number-list) nil)
        ((oddp (first number-list)) t)
        (t (anyoddp (rest number-list)))))

proj1/tests/main.lisp:

(defpackage proj1/tests/main
  (:use :cl
        :proj1
        :fiveam))
(in-package :proj1/tests/main)

;; Note: To run this test file, execute `(asdf:test-system :proj1)' in your Lisp.

;; (deftest test-target-1
;;   (testing "should (= 1 1) to be true"
;;            (ok (= 1 1))))

(def-suite master-suite
  :description "Test my system.")

(def-suite anyoddp-tests
  :description "Test anyoddp"
  :in master-suite)

(in-suite anyoddp-tests)

(test simple-maths
  (is (= 3 (+ 1 1))))

(test anyoddp-with-odds
  (let ((result (anyoddp '(1 3 4))))
    (is (equal result t))
    "True expected but got ~a" result))

I've tried every permutation of commands that I've read about in an attempt to test my proj1 "system", and I either get an error or Didn't run anything...huh?. Here's my latest attempt:

CL-USER> (ql:quickload "fiveam")
To load "fiveam":
  Load 1 ASDF system:
    fiveam
; Loading "fiveam"

("fiveam")
CL-USER> (asdf:load-system "proj1")
T
CL-USER> (asdf:test-system :proj1)
; compiling file "/Users/7stud/quicklisp/local-projects/my-projects/proj1/tests/main.lisp" (written 20 MAR 2024 10:04:40 AM):

; wrote /Users/7stud/.cache/common-lisp/sbcl-2.4.0-macosx-arm64/Users/7stud/quicklisp/local-projects/my-projects/proj1/tests/main-tmpUSHT8RIL.fasl
; compilation finished in 0:00:00.004
 Didn't run anything...huh?; in: ALEXANDRIA:NAMED-LAMBDA PROJ1/TESTS/MAIN::%TEST-ANYODDP-WITH-ODDS
;     (PROJ1/TESTS/MAIN::ANYODDP '(1 3 4))
; 
; caught STYLE-WARNING:
;   undefined function: PROJ1/TESTS/MAIN::ANYODDP
; 
; compilation unit finished
;   Undefined function:
;     PROJ1/TESTS/MAIN::ANYODDP
;   caught 1 STYLE-WARNING condition
T
CL-USER> 

Why am I getting

undefined function: PROJ1/TESTS/MAIN::ANYODDP

??? At the top of proj1/tests/main.lisp it says:

(defpackage proj1/tests/main
  (:use :cl
        :proj1
        :fiveam))

Isn't use :proj1 supposed to let me call the functions defined in proj1 without the package name? Okay, here is proj1/tests/main.lisp with the package name added to the function name anyoddp:

(defpackage proj1/tests/main
  (:use :cl
        :proj1
        :fiveam))
(in-package :proj1/tests/main)

(def-suite master-suite
  :description "Test my system.")

(def-suite anyoddp-tests
  :description "Test anyoddp"
  :in master-suite)

(in-suite anyoddp-tests)

(test simple-maths
  (is (= 3 (+ 1 1))))

(test anyoddp-with-odds
  (let ((result (proj1::anyoddp '(1 3 4))))   ;; **CHANGE HERE**
    (is (equal result t))
    "True expected but got ~a" result))

Then:

CL-USER> (ql:quickload "fiveam")
To load "fiveam":
  Load 1 ASDF system:
    fiveam
; Loading "fiveam"

("fiveam")
CL-USER> (asdf:load-system "proj1")
T
CL-USER> (asdf:test-system :proj1)
; compiling file "/Users/7stud/quicklisp/local-projects/my-projects/proj1/tests/main.lisp" (written 20 MAR 2024 10:33:11 AM):

; wrote /Users/7stud/.cache/common-lisp/sbcl-2.4.0-macosx-arm64/Users/7stud/quicklisp/local-projects/my-projects/proj1/tests/main-tmp7YCPD44Y.fasl
; compilation finished in 0:00:00.004
 Didn't run anything...huh?
T
CL-USER> 

I'm also wondering if there is a way to use fiveam to test a function in a file that is not in a project. Suppose I have this file:

;;  a.lisp

(defun anyoddp (number-list)
  (cond ((null number-list) nil)
        ((oddp (first number-list)) t)
        (t (anyoddp (rest number-list)))))

How can I add some tests to a.lisp, then run those tests?


Solution

  • I made some progress running tests in a project. I was able to execute my tests with the following code:

    proj1/src/main.lisp:

    (defpackage proj1
      (:use :cl)
      (:export :anyoddp))
    
    (in-package :proj1)
    
    ;; blah blah blah.n
    
    (defun anyoddp (number-list)
      (cond
        ((null number-list) nil)
        ((oddp (first number-list)) t)
        (t (anyoddp (rest number-list)))))
    

    proj1/tests/main.lisp:

    (defpackage proj1/tests/main
      (:use :cl
            :proj1
            :fiveam)
      (:export #:test-proj1))
    (in-package :proj1/tests/main)
    
    (defun test-proj1 ()
      (run! 'master-suite))
    
    (def-suite master-suite
      :description "Test my system.")
    (in-suite master-suite)
    
    (test simple-maths
      (is (= 3 (+ 1 1))))
    
    
    (def-suite anyoddp-tests
      :description "Test anyoddp"
      :in master-suite)
    (in-suite anyoddp-tests)
    
    (test anyoddp-with-odds
      (let ((result (anyoddp '(1 3 4))))   
        (is (equal result t))
        "True expected but got ~a" result))
    
    (test anyoddp-with-evens
      (let ((result (anyoddp '(2 4 6))))
        (is (equal result nil))
        "Nil expected but got ~a" result))
    
    

    Then in slime:

    CL-USER> (ql:quickload 'proj1/tests)
    ; Evaluation aborted on #<QUICKLISP-CLIENT:SYSTEM-NOT-FOUND {7007278153}>.
    CL-USER> (ql:register-local-projects)
    NIL
    CL-USER> (ql:quickload 'proj1/tests)
    To load "proj1/tests":
      Load 1 ASDF system:
        proj1/tests
    ; Loading "proj1/tests"
    
    (PROJ1/TESTS)
    CL-USER> (proj1/tests/main:test-proj1)
    
    Running test suite MASTER-SUITE
     Running test SIMPLE-MATHS f
     Running test suite ANYODDP-TESTS
      Running test ANYODDP-WITH-ODDS .
      Running test ANYODDP-WITH-EVENS .
     Did 3 checks.
        Pass: 2 (66%)
        Skip: 0 ( 0%)
        Fail: 1 (33%)
    
     Failure Details:
     --------------------------------
     SIMPLE-MATHS in MASTER-SUITE []: 
          
    (+ 1 1)
    
     evaluated to 
    
    2
    
     which is not 
    
    =
    
     to 
    
    3
    
    
     --------------------------------
    
    NIL
    (#<IT.BESE.FIVEAM::TEST-FAILURE {700832DF43}>)
    NIL
    CL-USER> 
    

    I have no idea why I got this error:

    CL-USER> (ql:quickload 'proj1/tests)
    ; Evaluation aborted on #<QUICKLISP-CLIENT:SYSTEM-NOT-FOUND {7007278153}>.
    

    I, or rather cl-project, defined the "system" proj1/tests in the file ~/quicklisp/local-projects/my-projects/proj1/proj1.asd:

    (defsystem "proj1"
      :version "0.0.1"
      :author "7stud"
      :license "any"
      :depends-on ()
      :components ((:module "src"
                    :components
                    ((:file "main"))))
      :description "Some functions"
      :in-order-to ((test-op (test-op "proj1/tests"))))
    
    (defsystem "proj1/tests"
      :author ""
      :license ""
      :depends-on ("proj1"
                   "fiveam")
      :components ((:module "tests"
                    :components
                    ((:file "main"))))
      :description "Test system for proj1"
      :perform (test-op (op c) (symbol-call :fiveam '#:run!)))
    

    In any case, the command:

    (ql:register-local-projects)
    

    causes quickslip to look through the directories under ~/quicklisp/local-projects and write the paths to all the .asd files it finds into ~/quicklisp/local-projects/system-index.txt. After I evaluated that command, then (ql:quickload 'proj1/tests) was successful.

    Essentially, I loaded a system, then I used a package name that was defined somewhere in the system to call a function defined in that package:

    (proj1/tests/main:test-proj1)
     |              | |        |
     +-------+------+ +---+----+
             |            |
          package      function 
           name          name
    

    And, here are the changes I made to proj1.asd to integrate with asdf:

    (defsystem "proj1"
      :version "0.0.1"
      :author "7stud"
      :license "any"
      :depends-on ()
      :components ((:module "src"
                    :components
                    ((:file "main"))))
      :description "Some functions"
      :in-order-to ((test-op (test-op "proj1/tests"))))
    
    (defsystem "proj1/tests"
      :author ""
      :license ""
      :depends-on ("proj1"
                   "fiveam")
      :components ((:module "tests"
                    :components
                    ((:file "main"))))
      :description "Test system for proj1"
      :perform (test-op (o s)
           (symbol-call :fiveam '#:run!
              (find-symbol* '#:master-suite
                            :proj1/tests/main))))
    

    Then:

    CL-USER> (ql:quickload 'proj1/tests)
    To load "proj1/tests":
      Load 1 ASDF system:
        proj1/tests
    ; Loading "proj1/tests"
    [package proj1]...................................
    [package proj1/tests/main]
    (PROJ1/TESTS)
    CL-USER> (asdf:test-system 'proj1/tests)
    
    Running test suite MASTER-SUITE
     Running test SIMPLE-MATHS f
     Running test suite ANYODDP-TESTS
      Running test ANYODDP-WITH-ODDS .
      Running test ANYODDP-WITH-EVENS .
     Did 3 checks.
        Pass: 2 (66%)
        Skip: 0 ( 0%)
        Fail: 1 (33%)
    
     Failure Details:
     --------------------------------
     SIMPLE-MATHS in MASTER-SUITE []: 
          
    (+ 1 1)
    
     evaluated to 
    
    2
    
     which is not 
    
    =
    
     to 
    
    3
    
    
     --------------------------------
    
    T
    

    And:

    CL-USER> (asdf:test-system 'proj1)
    
    Running test suite MASTER-SUITE
     Running test SIMPLE-MATHS f
     Running test suite ANYODDP-TESTS
      Running test ANYODDP-WITH-ODDS .
      Running test ANYODDP-WITH-EVENS .
     Did 3 checks.
        Pass: 2 (66%)
        Skip: 0 ( 0%)
        Fail: 1 (33%)
    
     Failure Details:
     --------------------------------
     SIMPLE-MATHS in MASTER-SUITE []: 
          
    (+ 1 1)
    
     evaluated to 
    
    2
    
     which is not 
    
    =
    
     to 
    
    3
    
    
     --------------------------------
    
    T
    CL-USER> 
    
    

    Whenever I made changes to the files in ~/quicklisp/local-projects/my-projects/proj1/, I usually tried to execute:

    CL-USER> (ql:quickload 'proj1)
    

    or

    CL-USER> (ql:quickload 'proj1/tests)
    

    in the slime repl. I found that the slime repl would often get out of sync with the current files in proj1, which would cause errors.

    As far as I can figure out, this line:

      :perform (test-op (o s)
           (symbol-call :fiveam '#:run!
              (find-symbol* '#:master-suite
                            :proj1/tests/main))))
    

    has to use some trickery because it is read before the packages/functions in proj1 are created. symbol-call is in the uiop package, and it is used to specify a function call that is read before the package containing the function definition exists. You specify the package name, the function name, and the args. In symbol-call above, the package name is :fiveam, the function name is '#:run! and the arg for the function is whatever is returned by:

    (find-symbol* '#:master-suite :proj1/tests/main)
    

    find-symbol* is a function in the uiop package which comes with asdf which comes with sbcl, which is the lisp implementation I'm using. find-symbol* takes a symbol and a package name as arguments and is used when the package isn't present.

    The result is, I am specifying a function call something like:

    (fiveam:run! proj1/tests/main:master_suite)
    

    But, instead of dealing with all that crazy syntax, I found I can do this:

     :perform (test-op (o s)
           (symbol-call :proj1/tests/main :test-proj1)))
    

    test-proj1 is this function:

    (defun test-proj1 ()
      (run! 'master-suite))
    

    which is defined in the package proj1/tests/main, which is defined in the file .../proj1/test/main.lisp. Then I only needed to export the function test-proj1:

    ...proj1/tests/main.lisp:

    (defpackage proj1/tests/main
      (:use :cl
            :proj1
            :fiveam)
      (:export #:test-proj1))
    (in-package :proj1/tests/main)
    
    (defun test-proj1 ()
      (run! 'master-suite))
    

    I found nothing about what test-op (o s) does.

    Resources:

    1. ASDF manual: test-op

    2. Tutorial: Working with FiveAM