diff --git a/info.rkt b/info.rkt index a5346f6..d60de5a 100644 --- a/info.rkt +++ b/info.rkt @@ -12,3 +12,7 @@ (define scribblings '(("scribblings/cover.scrbl" (multi-page)))) (define test-omit-paths (list "tests/error-file.rkt")) + +(define cover-formats '(("html" cover generate-html-coverage) + ("coveralls" cover generate-coveralls-coverage) + ("raw" cover generate-raw-coverage))) diff --git a/main.rkt b/main.rkt index 0330ad6..38f3ed7 100644 --- a/main.rkt +++ b/main.rkt @@ -13,6 +13,6 @@ (->* (exact-positive-integer?) (#:byte? boolean?) (or/c 'yes 'no 'missing)))] - [generate-coveralls-coverage (->* (coverage/c) (path-string?) any)] - [generate-html-coverage (->* (coverage/c) (path-string?) any)] - [generate-raw-coverage (->* (coverage/c) (path-string?) any)])) + [generate-coveralls-coverage coverage-gen/c] + [generate-html-coverage coverage-gen/c] + [generate-raw-coverage coverage-gen/c])) diff --git a/private/contracts.rkt b/private/contracts.rkt index c8b23e5..64efa99 100644 --- a/private/contracts.rkt +++ b/private/contracts.rkt @@ -1,7 +1,8 @@ #lang racket/base -(provide coverage/c file-coverage/c) +(provide coverage/c file-coverage/c coverage-gen/c) (require racket/contract) (define file-coverage/c (listof (list/c boolean? srcloc?))) (define coverage/c (hash/c (and/c path-string? absolute-path?) file-coverage/c)) +(define coverage-gen/c (->* (coverage/c) (path-string?) any)) diff --git a/raco.rkt b/raco.rkt index 83be241..3e81501 100644 --- a/raco.rkt +++ b/raco.rkt @@ -1,6 +1,11 @@ #lang racket/base (require racket/list racket/cmdline raco/command-name + setup/getinfo + racket/match + racket/contract/base + racket/function "main.rkt" + (only-in "private/contracts.rkt" coverage-gen/c) "private/shared.rkt") (module+ test @@ -44,11 +49,8 @@ (cons file files))) (define files (expand-directories args include-exts)) (define generate-coverage - (case output-format - [("html") generate-html-coverage] - [("coveralls") generate-coveralls-coverage] - [("raw") generate-raw-coverage] - [else (error 'cover "given unknown coverage output format: ~s" output-format)])) + (hash-ref (get-formats) output-format + (lambda _ (error 'cover "given unknown coverage output format: ~s" output-format)))) (printf "generating test coverage for ~s\n" files) (define passed (keyword-apply test-files! '(#:submod) (list submod) files)) (define coverage (remove-excluded-paths (get-test-coverage) exclude-paths)) @@ -148,3 +150,37 @@ (build-path "a")) (check-equal? (->relative "/test/a/b") (build-path "a" "b")))) + + +(define (get-formats) + (define dirs (find-relevant-directories '(cover-formats) 'all-available)) + (for*/hash ([d (in-list dirs)] + [f (in-value (get-info/full/skip d))] + #:when f + [v (in-value (f 'cover-formats (const #f)))] + #:when v + [l (in-list v)]) + (with-handlers ([exn:misc:match? (make-cover-load-error d l)]) + (match-define (list (? string? name) (? module-path? path) (? symbol? ident)) l) + (define f (dynamic-require path ident (make-cover-require-error ident path))) + (values + name + (contract coverage-gen/c f 'cover ident ident #f))))) + +(define ((make-cover-load-error dir v) . _) + (error 'cover "unable to load coverage format from ~s. Found unusable value ~s" + dir v)) +(define ((make-cover-require-error ident path)) + (error 'cover "unable to load symbol ~s from ~s" ident path)) + +(define (get-info/full/skip dir) + (with-handlers ([exn:fail? (const #f)]) + (get-info/full dir))) + +(module+ test + (test-begin + ;; we expect that a standard install has "html", "coveralls", and "raw" + (define h (get-formats)) + (check-true (hash-has-key? h "html")) + (check-true (hash-has-key? h "coveralls")) + (check-true (hash-has-key? h "raw")))) diff --git a/scribblings/cover.scrbl b/scribblings/cover.scrbl index 1c7bcc9..808b2a5 100644 --- a/scribblings/cover.scrbl +++ b/scribblings/cover.scrbl @@ -11,5 +11,6 @@ Cover is a test coverage tool. It is designed to be used in addition to raco tes @include-section["basics.scrbl"] @include-section["api.scrbl"] +@include-section["plugins.scrbl"] @index-section[] diff --git a/scribblings/plugins.scrbl b/scribblings/plugins.scrbl new file mode 100644 index 0000000..e9fe4d2 --- /dev/null +++ b/scribblings/plugins.scrbl @@ -0,0 +1,18 @@ +#lang scribble/doc +@(require "base.rkt") + +@title[#:tag "plugin"]{Creating Custom Output Formats} + +Any package may add an output format to cover. A format is, roughly, a function that takes +coverage information and transforms it into some other format. To add a format +put a definition for @racket[cover-formats] into a packages @filepath["info.rkt"]. This should be a +@racket[(listof (list _command-name _module-path _function-name))]: +@itemize{ + @item{@racket[_command-name] is a string which will be used as the argument to @exec{-c} to + use this format.} + @item{@racket[_module-path] should be the path to a racket file providing this format.} + @item{@@racket[_function-name] should be a symbol that is + bound to a function in @racket[_module-math]. It should match the contract + @racket[(->* (coverage/c) (path-string?) any)], and is the implementation of + the format.} + }