commit f57b1ecd06e82a1095a3a74cdfe9b17b75c1dd9c
parent 3049c1867be86a4c20fed4e7370a529d65e14696
Author: Georges Dupéron <georges.duperon@gmail.com>
Date: Thu, 31 Mar 2016 12:56:46 +0200
Improved documentation.
Diffstat:
12 files changed, 160 insertions(+), 120 deletions(-)
diff --git a/.travis.yml b/.travis.yml
@@ -37,8 +37,6 @@ before_install:
- cat travis-racket/install-racket.sh | bash # pipe to bash not sh!
- export PATH="${RACKET_DIR}/bin:${PATH}" #install-racket.sh can't set for us
-install:
-
before_script:
# Here supply steps such as raco make, raco test, etc. Note that you
@@ -46,7 +44,7 @@ before_script:
# `raco pkg install --deps search-auto repltest` to install any required
# packages without it getting stuck on a confirmation prompt.
script:
- - raco pkg install --deps search-auto cover
+ - raco pkg install --deps search-auto .
- raco test -x -p repltest
after_success:
diff --git a/info.rkt b/info.rkt
@@ -1,7 +1,8 @@
#lang info
(define collection "repltest")
(define deps '("base"
- "rackunit-lib"))
+ "rackunit-lib"
+ "debug"))
(define build-deps '("scribble-lib" "racket-doc"))
(define scribblings '(("scribblings/repltest.scrbl" ())))
(define pkg-desc "Copy-paste your REPL interactions, and have them run as tests")
diff --git a/lang/reader.rkt b/lang/reader.rkt
@@ -5,65 +5,25 @@
[repltest-get-info get-info]))
(require (for-template repltest/private/run-interactions)
- (for-template repltest/private/modbg)
racket/syntax
repltest/private/util
(only-in syntax/module-reader make-meta-reader)
syntax/strip-context)
(define ((wrap-reader reader) chr in src line col pos)
- (define/with-syntax (mod nm lang . body)
+ (define/with-syntax orig-mod
(reader chr (narrow-until-prompt in) src line col pos))
- ;(displayln "WARNING: skipping tests")(port->string in) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;DEBUG
- (with-syntax ([(m1 n1 l1 (mb1 . bd1))
- (eval #'(expand #`(mod nm lang . body))
+ (with-syntax ([(mod nam lang (modbeg . body))
+ (eval #'(expand #'orig-mod)
(variable-reference->namespace (#%variable-reference)))])
- #`(m1 n1 l1
- (mb1 (module* test racket/base
- (require repltest/private/run-interactions)
- (run-interactions ;#'(mod nm lang . body)
- (open-input-string #,(port->string in))
- (#%variable-reference)))
- . bd1)))
-
- ;#`(mod nm lang . body)
- #;#`(mod nm repltest/private/modbg
- require
- (module nm lang (require lang) . body)
- #;#,(port->string in)
- (module* test racket/base
- (require repltest/private/run-interactions)
- (run-interactions ;#'(mod nm lang . body)
- (open-input-string #,(port->string in))
- (#%variable-reference)))))
-#|
- #;(insert-in-module
- (module code lang . body)
- (require 'code)
- (provide (all-from-out 'code))
- (module test racket/base
- (require repltest/private/run-interactions)
- (run-interactions #'(mod nm lang . body)
- (open-input-string #,(port->string in))
- (#%variable-reference))))
-
- #;(define/with-syntax (mod2 nm2 lang2 (modbeg2 . body2))
- (local-expand #'(module nm lang . body)
- 'module
- '()))
- #;((λ (x)
- (displayln x)
- x)
- #`(mod2 nm2 lang2
- (modbeg2
- #;(module test racket/base
- (require repltest/private/run-interactions)
- (run-interactions #'(mod nm lang . body)
- (open-input-string #,(port->string in))
- (#%variable-reference)))
- . body2)))
- |#
+ #`(mod nam lang
+ (modbeg
+ (module* test racket/base
+ (require repltest/private/run-interactions)
+ (run-interactions (open-input-string #,(port->string in))
+ (#%variable-reference)))
+ . body))))
(define-values (repltest-read repltest-read-syntax repltest-get-info)
(make-meta-reader
diff --git a/private/modbg.rkt b/private/modbg.rkt
@@ -1,54 +0,0 @@
-#lang racket/base
-
-(provide (rename-out [insert-in-module #%module-begin]))
-
-(require (for-syntax racket/base
- syntax/strip-context))
-
-(define-syntax (insert-in-module stx)
- (syntax-case stx ()
- [(_ rr
- (mod1 nm1 lang1 (req lng) . bdy1);orig-mod
- submod
- ;str
- )
- (with-syntax ([(mod nm lang (modbg . body)) (expand ;#'orig-mod
- #'(mod1 nm1 lang1 . bdy1))])
- ;(with-syntax ([req (datum->syntax #'md1 'require)])
-
-
- ((λ (x)
- (displayln x)
- x)
- (syntax-local-introduce
- #`(modbg ;(require lang)
- ;(req #,(datum->syntax #'req (syntax->datum #'lang)))
- ;(rr lang)
- . body)))
-
- #;#`(modbg ;(require lang)
- ;; ok for #%top-interaction:
- (req #,(datum->syntax #'req (syntax->datum #'lang)))
- ;; not ok for #%top-interaction:
- ;(req lang)
- (rr lang)
- (define varref (#,(datum->syntax #'lang '#%variable-reference)))
- (provide varref)
- submod
- #;(module* test racket/base
- (require repltest/private/run-interactions)
- (require (submod ".."))
- #;(define res-mod
- (module-path-index-resolve
- (module-path-index-join '(submod "..")
- (variable-reference->module-path-index
- varref))))
- ;(define mod-ns (module->namespace res-mod))
- (define mod-ns (variable-reference->namespace varref))
- (displayln mod-ns)
- (run-interactions2 (open-input-string str)
- mod-ns)
- #;(run-interactions (open-input-string str)
- #,(datum->syntax #'modbg '#%variable-reference)
- #;(#%variable-reference)))
- . body))]))
-\ No newline at end of file
diff --git a/private/util.rkt b/private/util.rkt
@@ -46,9 +46,8 @@
(define (narrow-until-prompt in)
(make-limited-input-port in (peak-until-prompt-length in)))
+;; Just like the default `current-prompt-read`, but without showing the prompt.
(define silent-prompt-read
(λ ()
- ;; Default current-prompt-read, without showing
- ;; the prompt
(let ([in ((current-get-interaction-input-port))])
((current-read-interaction) (object-name in) in))))
\ No newline at end of file
diff --git a/scribblings/repltest.scrbl b/scribblings/repltest.scrbl
@@ -1,17 +1,131 @@
#lang scribble/manual
@require[@for-label[repltest
- racket/base]]
+ racket/base]
+ scriblib/footnote]
@title{REPL test: copy-paste REPL interactions to define tests}
@author{georges}
-@defmodule[repltest]
+Source code: @url{https://github.com/jsmaniac/repltest}
-This package define a meta-language which parses a REPL
-trace, and re-evaluates it, checking that the outputs
-haven't changed.
+@defmodulelang[repltest]{
+ The @racketmodname[repltest] language is a meta-language
+ that replays a copy-pasted transcript of an interactive
+ REPL (@racket[read-eval-print-loop]) session, checking that the
+ outputs have not changed.
+
+ This allows to quickly write preliminary unit tests based
+ on a debugging session. It is however not a substitute for
+ writing real tests, and these tests are more prone to the
+ “copy-pasted bogus output into the tests” problem.}
-This allows to quickly write preliminary unit tests based on
-a debugging session. It is obviously not a substitute for
-writing real tests, and these tests are more prone to the
-“copy-pasted bogus output into the tests” problem.
+@racketblock[
+ @#,hash-lang[] @#,racketmodname[repltest] @#,racketmodname[racket]
+ (define x 3)
+ @#,racketid[>] (+ x 1)
+ @#,racketresultfont{4}
+ ]
+
+The first part of the file is kept inside the top-level
+module. This module uses the language indicated just after
+@racket[@#,hash-lang[] @#,racketmodname[repltest]], for
+example:
+
+@racketblock[
+ @#,hash-lang[] @#,racketmodname[repltest] @#,racketmodname[typed/racket]]
+
+After the first occurrence of the prompt (by default
+@racket["> "], later versions of this package will allow
+customizing this) is encountered, all the remaining contents
+of the file are understood as a REPL transcript. The prompt
+is only recognized if it is outside of any s-expression,
+which means that the @racket[>] function can be used
+normally.
+
+@racketblock[
+ @#,hash-lang[] @#,racketmodname[repltest] @#,racketmodname[racket]
+ (define x (> 3 4))
+ @#,racketid[>] x
+ @#,racketresultfont{#f}
+ ]
+
+@section{The @racketid[test] submodule}
+
+This language injects a @racketid[test] submodule
+using @racket[module*]. When the @racketid[test] module
+is run, the expression after each prompt is read and
+evaluated as if it had been typed inside a real REPL, using
+@racket[read-eval-print-loop]. The result, as printed on the
+standard output and standard error, is compared with the
+text read until the next prompt. The next prompt will only
+be recognized if it is not part of an s-expression, which
+means that occurrences of @racket[>] inside an expression in
+the output are correctly handled:
+
+@racketblock[
+ @#,hash-lang[] @#,racketmodname[repltest] @#,racketmodname[racket]
+ (define x '(> 3 4))
+ @#,racketid[>] x
+ @#,racketresultfont{'(> 3 4)}
+ @#,racketid[>] '(> 5 6)
+ @#,racketresultfont{'(> 5 6)}
+ ]
+
+The fact that a real REPL is used means that any
+language-specific output will be produced as expected. For
+example @racketmodname[typed/racket] prints the type of the
+result before the result itself, so it must be included in
+the expected output:
+
+@racketblock[
+ @#,hash-lang[] @#,racketmodname[repltest] @#,racketmodname[typed/racket]
+ (define x 0)
+ @#,racketid[>] x
+ @#,racketresultfont{0}
+ ]
+
+@section{Warning concerning comments}
+
+Comments are not currently supported inside the REPL
+transcript. Also, the current version does not the first
+prompt being preceded by a comment.
+
+@section{Warning concerning spaces and newlines}
+
+The tests are space-sensitive, so care should be taken to
+include a newline at the end of the file, as in most
+languages, the REPL prints a newline after the result.
+Furthermore, extra spacing like blank lines should not be
+added after the first prompt.
+
+@section{Future improvements}
+
+Later versions of this package will allow customizing the following aspects:
+
+@itemlist[
+ @item{Flexibility of whitespace comparisons (strip leading
+ and trailing whitespace, or ignoring all whitespace
+ differences).}
+ @item{Support comments before and inside the REPL
+ transcript.}
+ @item{Specifying a regexp matching the prompt, and a
+ regexp for characters preceding the prompt which are not
+ part of it (and therefore will be part of the preceding
+ result or main module's code).}
+ @item{Disable calling @racket[read] on the output
+ expressions, which can be useful when the output contain
+ unbalanced parenthesis, or do not otherwise match the
+ language's syntax, for example:
+
+ @; TODO: include this in the tests
+ @racketblock[
+ @#,hash-lang[] @#,racketmodname[repltest] @#,racketmodname[racket]
+ @#,racketid[>] (displayln "(unbalanced")
+ @#,racketresultfont{(unbalanced}
+ @#,racketid[>] (displayln "#invalid (syntax . too . many . dots)")
+ @#,racketresultfont{#invalid (syntax . too . many . dots)}]
+
+ This will also have the side-effect of allowing the prompt
+ to be matched inside s-expressions.}
+ @item{Distinguish standard output (purple font in DrRacket),
+ printed result (blue font) and standard error (red font).}]
+\ No newline at end of file
diff --git a/test/doc1.rkt b/test/doc1.rkt
@@ -0,0 +1,4 @@
+#lang repltest racket
+(define x 3)
+> (+ x 1)
+4
diff --git a/test/doc2-newline-at-end-of-file.rkt b/test/doc2-newline-at-end-of-file.rkt
@@ -0,0 +1 @@
+#lang repltest typed/racket
diff --git a/test/doc2-no-newline-at-end-of-file.rkt b/test/doc2-no-newline-at-end-of-file.rkt
@@ -0,0 +1 @@
+#lang repltest typed/racket
+\ No newline at end of file
diff --git a/test/doc3.rkt b/test/doc3.rkt
@@ -0,0 +1,4 @@
+#lang repltest racket
+(define x (> 3 4))
+> x
+#f
diff --git a/test/doc4.rkt b/test/doc4.rkt
@@ -0,0 +1,6 @@
+#lang repltest racket
+(define x '(> 3 4))
+> x
+'(> 3 4)
+> '(> 5 6)
+'(> 5 6)
diff --git a/test/doc5.rkt b/test/doc5.rkt
@@ -0,0 +1,5 @@
+#lang repltest typed/racket
+(define x 0)
+> x
+- : Integer [more precisely: Zero]
+0