Antoine Kalmbach

A Guile test runner with an exit code

The other day I was playing around with the Meson build system for building GNU Guile projects. It was actually a lot of fun, Meson build files are in my opinion much easier to manage than Autoconf, although Autoconf the de facto build system for Guile.

It was all fun and nice until I ran into a problem with running unit tests using meson test. Meson expects unit tests to return exit code 1 to signal a failure, but the standard Guile test runner that implements SRFI-64 does not have an exit code.

So I wrote the following Scheme snippet to make a small script that you can invoke to run tests from an arbitrary Scheme file and it returns 1 if the tests fail. That way it can be used in Meson builds to signal unit test failures.

(define-module (test-driver)
  #:declarative? #f
  #:use-module (srfi srfi-64))

(define* (main _ #:rest files)
  "Load each file in FILES while evaluating them within the context of a test
runner."
  (unless (= 1 (length files))
    (display "Usage: test-driver.scm <TEST-FILE>\n"
             (current-error-port))
    (exit 1))
  
  (let ((file (car files)))
    (unless (file-exists? file)
      (display (format #f "File ~A does not exist!~%" file)
               (current-error-port))
      (exit 1))

    (let* ((full (string-append (getcwd) "/" file))
           (runner (test-runner-simple)))
      (test-with-runner runner
        (load full))
      (if (< 0 (test-runner-fail-count runner))
          (exit 1)
          (exit 0)))))

(apply main (command-line))

Invoking it on a file like simple.scm as follows:

(define-module (simple-tests)
  #:use-module (srfi srfi-64))

(test-begin "simple")

(test-equal "ok" 3 3)

(test-equal "bad" 3 4)

(test-end "simple")

produces the exit code 1 as is expected:

$ guile --no-auto-compile test-driver.scm simple.scm
%%%% Starting test simple  (Writing full log to "simple.log")
/Users/akalmbach/code/guile/simple.scm:8: FAIL bad
# of expected passes      1
# of unexpected failures  1

$ echo $?
1
Previous: Working with Git and patches in Emacs Next: Recutils, GOOPS and virtual slots