diff --git a/.travis.yml b/.travis.yml index 58327d1..4fcb672 100644 --- 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 index 24bcc0a..9a5069a 100644 --- 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 index a83a1aa..11bb6a1 100644 --- 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 deleted file mode 100644 index aca1794..0000000 --- a/private/modbg.rkt +++ /dev/null @@ -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 index be0ec62..8ce4e5d 100644 --- 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 index 54be906..0aae078 100644 --- 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 new file mode 100644 index 0000000..4189753 --- /dev/null +++ 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 new file mode 100644 index 0000000..60b73fb --- /dev/null +++ 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 new file mode 100644 index 0000000..87875a3 --- /dev/null +++ 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 new file mode 100644 index 0000000..b55af83 --- /dev/null +++ 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 new file mode 100644 index 0000000..f2412a3 --- /dev/null +++ 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 new file mode 100644 index 0000000..a1d6a19 --- /dev/null +++ b/test/doc5.rkt @@ -0,0 +1,5 @@ +#lang repltest typed/racket +(define x 0) +> x +- : Integer [more precisely: Zero] +0