Open amirouche opened 5 years ago
What I was using in my previous work, is a macro that defines a thunk:
(define-syntax-rule (test expected actual)
(lambda ()
(let ((expected* expected)
(actual* actual))
(if (equal? expected* actual*)
(list #t)
(list #f expected* actual*)))))
Then in the test library it would be used as follow:
(library (tests-tests)
(export test-0)
(import (chezscheme)
(tests))
(define test-0
(test #t #t)))
The test runner will look up all files that ends with tests.scm
and run all the thunk of the interface of those modules. This requires to be able to lookup the public interface of a library.
As far as I know, SRFI-64 is not used much in SRFI for R7RS-large. Most (if not all) rely on chibi's test
macro which has two forms:
(test "description of the test" expected actual)
Or without a description:
(test expected actual)
A given test suite for an SRFI is wrapped inside a procedure run-tests
that is imported in a run-tests.scm
that is executed with make
. I assume they hardcode the test suite because there is no library introspection.
Here is the test runner in gambit: https://github.com/udem-dlteam/gambit/blob/master/tests/run-unit-tests.scm
My proposal is to rely on the library system to allow all the features I listed in the issue. This is a proposal that will change how the tests look, how the original tests are reworked and also must stand the test of time.
It will require to create a replacement for check-equal?
and check-tail-exn
. For the latter, I used to rely on R7RS guard
, so a test that wants to check that a given exn condition is raised will look like the following:
(define test-0
(test #t (guard (ex ((condition? ex) #t) (else #f))
(something)))
There is a problem if (something)
returns #t
, so we might need to add some sugar test-exception
to replace test
in that case.
Where test
is the following macro:
(define-syntax-rule (test expected actual)
(lambda ()
(let ((expected* expected)
(actual* actual))
(if (equal? expected* actual*)
(list #t)
(list #f expected* actual*)))))
@lassik @feeley Do you see any interest in my proposal to use the library system for creating the unit tests? Or do you prefer to keep tests as much as the original SRFI provide? Or something else?
By the way, I could not find the definition of check-equal?
and check-tail-exn
in gambit source. Any help?
Also, how can I query the public interface of a library? And retrieve the exported forms?
By the way, I could not find the definition of
check-equal?
andcheck-tail-exn
in gambit source. Any help?
They are in the _test module (the source code of the macros is in lib/_test/_test#.scm
). You get these macros:
check-equal?
check-not-equal?
check-eqv?
check-not-eqv?
check-eq?
check-not-eq?
check-=
check-true
check-false
check-not-false
check-exn
check-tail-exn
I'd like to keep tests simple and uniform across all modules. I previously had a -test
switch to the interpreter to trigger unit tests, but I simplified by adopting the convention that the unit tests for module foo
are in its test
submodule. For example, to run the unit tests of the _hamt
module you can do:
gsi _hamt/test
Which silently runs all the tests and exits with status 0. If the test submodule did not exist an error would be signalled (so you know the tests were run if you have no error). This is rather simple, but other options could be added to the _test module to add a summary of the traces, stop at the first failure, etc. This could be done by adding appropriate submodules for these options to the _test module. For example, to stop at the first failure there could ge a fail-fast
submodule:
gsi _test/fail-fast _hamt/test
The names of these options should be carefully chosen, and also the default behaviour. For example, I think the default should stop at the first failure and otherwise print the number of tests run if there are no failures, and there should be options for quiet
and all
.
For your other question, to check if a module exists you could use this code (I'm considering adding it to lib/_module.scm):
(define (module-reference string-or-source)
(let ((modref (##parse-module-ref string-or-source)))
(if modref
(##string->symbol (##modref->string modref))
(error "invalid module reference" string-or-source))))
(define (module-exists? string-or-source)
(let ((module-ref (module-reference string-or-source)))
(with-exception-catcher
(lambda (exc)
(if (module-not-found-exception? exc)
#f
(raise exc)))
(lambda ()
(##get-module module-ref)
#t))))
(pp (module-exists? '(_test))) ;;=> #t
(pp (module-exists? '(scheme fu))) ;;=> #f
(pp (module-exists? '(_define-library define-library-expand))) ;;=> #t
(pp (module-exists? '(github.com/gambit/hello test @2.1))) ;;=> #t
(pp (module-exists? '(github.com/gambit/hello test @2.0))) ;;=> #f
(pp (module-exists? "github.com/gambit/hello/test/@2.1")) ;;=> #t
That could be used to scan for modules that contain a test submodule.
Because the module system is implemented as a macro expanding to namespace and include forms, there is no easy way to determine what is exported by a module. If you limit yourself to R7RS libraries, then you could probably reuse the define-library parser in the _define-library/define-library-expand module.
I have pushed some changes to the _test
module to
1) by default give a final report of the tests run and stop at the first failure
2) if _test/all
is loaded then keep going after failures
3) if _test/quiet
is loaded then don't show final report
3) if _test/verbose
is loaded then show pass/fail message for each test
For example:
% gsi _test/quiet github.com/gambit/hello/test@2.1
% gsi github.com/gambit/hello/test@2.1
*** all tests passed out of a total of 4 tests
% gsi _test/verbose github.com/gambit/hello/test@2.1
"../../.gambit_userlib/github.com/gambit/hello/@2.1/test.scm"@10.5: PASSED (check-equal? (with-output-to-string (lambda () (hi "you"))) "hello you!\n")
"../../.gambit_userlib/github.com/gambit/hello/@2.1/test.scm"@13.5: PASSED (check-equal? (with-output-to-string (lambda () (salut "hi"))) "bonjour hi!\n")
"../../.gambit_userlib/github.com/gambit/hello/@2.1/test.scm"@16.5: PASSED (check-tail-exn wrong-number-of-arguments-exception? (lambda () (hi)))
"../../.gambit_userlib/github.com/gambit/hello/@2.1/test.scm"@19.5: PASSED (check-tail-exn wrong-number-of-arguments-exception? (lambda () (hi "foo" "bar")))
*** all tests passed out of a total of 4 tests
The tests should rely on gambit's (_test)
module
I will close the issue when I have implemented some tests.
I think srfi 175 is a good example of how things look like (once https://github.com/udem-dlteam/libs/pull/11/ is merged)
I advise against using want
as in srfi 175 for unit tests because:
1) The name want
does not convey how the check is performed (numerical equality, eq?
, equal?
, ...)
2) The second parameter of want
(i.e. check-equal?
) should be the expected value. The order is important because the second parameter is shown in the error message.
The test is a direct copy of the sample implementation, just with a few things added at the top of the file for Gambit. I can consider changing want
in the sample implementation. But first there's a more fundamental problem that prevents the current test from working at all in Gambit. I don't understand why it doesn't. PR #11 should adapt the tests for Gambit but no luck yet.
The tests work for me when I do this:
gsi/gsi -:=. . srfi/175/test
Also, you should write the unit tests as a module (e.g. test.sld) with a relative reference to srfi 175, like this:
(define-library (test)
(import (..)) ;; import parent = srfi 175
(import (scheme base)) ;; import define, lambda, etc
(import (_test)) ;; import check-equal?, etc
(begin
(define-syntax want (syntax-rules () ((_ x y) (check-equal? x y))))
(want #f (ascii-codepoint? -1))
(want #t (ascii-codepoint? 0))
(want #t (ascii-codepoint? #x7f))
(want #f (ascii-codepoint? #x80))
...
That way, if you ever start using versions for SRFI 175 the test module will import the 175 module with the same version:
gsi srfi/175/test@2.0
Works for me too, I just had a stale build of Gambit for some reason.
Should the define-library
go into a separate file srfi/175/test.sld
which includes srfi/175/test.scm
, i.e. the usual R7RS convention?
I've been thinking it could be really useful to have a big repo with up-to-date tests for all (or many) SRFIs, shared by all implementations. That way we can all leverage each other's work. The tests in each SRFI's own sample implementation are not always as full-featured as the ones that some implementations have.
The main problem here would be agreeing on a test runner API that all implementations use for SRFI tests (of course, it can be implemented as a macro that expands to call the implementation's native framework). As for the tests themselves, we'd just have to scavenge implementations' repos and merge it all into a common repo. All of that code should be pretty liberally licensed.
With a common test framework there is also the concern of different R5RS/R6RS/R7RS library filename and Scheme declaration conventions, but I've found it's surprisingly easy to write an auto-converter to do that.
For what it is worth, that is somewhat what started working on in the repository at https://github.com/amirouche/nomunofu/issues/3.
I have guile and chez on board with a handful of missing SRFI for R7RS large. The test runner does not do autodiscovery, one must pass the filenames of the library containing the tests to see https://github.com/amirouche/nomunofu/blob/wip-portable/Makefile#L170.
Autodiscovery is no brainer once the following is resolved:
I tried to reproduce a similar behavior with gambit, but I fail to express the following Chez code:
(define (library-exports* filepath library-name)
;; return the procedure defined in library LIBRARY-NAME at FILEPATH
;; XXX: hackish at best, there might be a better solution
(let ((env (interaction-environment)))
(let ((program `(begin
(import ,library-name)
(let ((exports (library-exports ',library-name)))
exports))))
(let ((exports (eval program env)))
(let ((program `(begin
(import ,library-name)
(map cons ',exports (list ,@exports)))))
(reverse (eval program env)))))))
Similar Guile code:
(define (run filename)
(let ((module-name (filename->module-name filename)))
(display "* ")
(display module-name)
(display " @ ")
(display filename)
(newline)
(let ((module (resolve-interface module-name))
(exports '()))
(module-for-each
(lambda (name variable)
(set! exports (cons (cons name variable) exports)))
module)
(set! exports
(sort exports (lambda (a b) (string<? (symbol->string (car a))
(symbol->string (car b))))))
(for-each (lambda (x) (run-one (car x) (cdr x))) exports))))
The important procedure are respectively (library-exports library-name)
and (resolve-interface module-name)
when passed the '(path to library)` will return the exported symbols (or an object referencing those exported symbols).
Using gambit I got to this point:
$ cat local/bin/run-gambit
#!/bin/sh
gsi -:r7rs $(pwd)/src/ $@
$ run-gambit
Gambit v4.9.3
> (import (gambit))
> (define-values (ld import-name) (_define-library/define-library-expand#get-libdef 'check '(arew check test)))
> ld
#(#(source1)
(#(#(source1) import "/home/amirouche/src/scheme/nomunofu/src/check.scm" 65536)
#(#(source1)
(#(#(source1) arew "/home/amirouche/src/scheme/nomunofu/src/check.scm" 589824) #(#(source1) check "/home/amirouche/src/scheme/nomunofu/src/check.scm" 917504))
"/home/amirouche/src/scheme/nomunofu/src/check.scm"
524288))
"/home/amirouche/src/scheme/nomunofu/src/check.scm"
0)
>
What I want is the module test
inside (arew check)
:
$ cat src/arew/check/test.sld
(define-library (arew check test)
(export test-0)
(import (gambit)
(scheme base)
(arew check))
(include "test.body.scm"))
When I do the following I have a segfault:
> (define-values (ld import-name) (_define-library/define-library-expand#get-libdef '(arew check test) ""))
Segmentation fault (core dumped)
I am using
commit 1b373b07814669e6051034d58ce24f3da5541fcd (HEAD -> master, origin/master, origin/HEAD)
For what it is worth, that is somewhat what started working on in the repository at amirouche/nomunofu#3.I have guile and chez on board with a handful of missing SRFI for R7RS large.
Very good progress mapping out the library support!
There should probably be a portable "all SRFI tests" repo separate from the SRFI implementations, since each Scheme implementation may want to have its own implementation of a SRFI, whereas the tests only test the public interface which ought to be the same in all Scheme implementations. So the tests are much more portable.
Of course, there's a portable implementation of many SRFIs as well, but those are not necessarily optimized for a particular implementation.
I created a test repo to try how it would work: https://github.com/srfi-explorations/srfi-test We can scrap it if we figure out that it's a bad idea.
I tried to reproduce a similar behavior with gambit, but I fail to express the following Chez code:
Nevermind, parsing the `define-library is trivial.
What I like in test runner:
lib-name/test.scm
convention.$ ./test-run lib-name/test.scm
$ ./test-run lib-name/test.scm test-0000
_test
procedurescheck-equal?
andcheck-tail-exn
(unlike python's unittest or junit).