236 lines
7.2 KiB
Racket
Executable File
236 lines
7.2 KiB
Racket
Executable File
#!/bin/sh
|
|
#|
|
|
exec racket -t "$0" -- -s -t 60 -v -R $*
|
|
|#
|
|
|
|
#lang racket
|
|
(require setup/dirs
|
|
racket/runtime-path
|
|
racket/future
|
|
compiler/find-exe
|
|
"zo-test-util.rkt")
|
|
|
|
(define ((make-recorder! ht) file phase)
|
|
(hash-update! ht phase (curry list* file) empty))
|
|
|
|
(define stop-on-first-error (make-parameter #f))
|
|
(define verbose-mode (make-parameter #f))
|
|
(define care-about-nonserious? (make-parameter #t))
|
|
(define invariant-output (make-parameter #f))
|
|
(define time-limit (make-parameter +inf.0))
|
|
(define randomize (make-parameter #f))
|
|
(define num-processes (make-parameter (processor-count)))
|
|
|
|
(define errors (make-hash))
|
|
(define (record-common-error! exn-msg)
|
|
(hash-update! errors (common-message exn-msg) add1 0))
|
|
|
|
(define (common-message exn-msg)
|
|
(define given-messages (regexp-match #rx".*given" exn-msg))
|
|
(if (and given-messages (not (empty? given-messages)))
|
|
(first given-messages)
|
|
exn-msg))
|
|
|
|
(define success-ht (make-hasheq))
|
|
(define success! (make-recorder! success-ht))
|
|
(define failure-ht (make-hasheq))
|
|
(define failure! (make-recorder! failure-ht))
|
|
|
|
(define debugging? (make-parameter #f))
|
|
|
|
(define (randomize-list l)
|
|
(define ll (length l))
|
|
(define seen? (make-hasheq))
|
|
(let loop ([t 0])
|
|
(if (= t ll)
|
|
empty
|
|
(let ([i (random ll)])
|
|
(if (hash-has-key? seen? i)
|
|
(loop t)
|
|
(begin (hash-set! seen? i #t)
|
|
(list* (list-ref l i)
|
|
(loop (add1 t)))))))))
|
|
|
|
(define (maybe-randomize-list l)
|
|
(if (randomize) (randomize-list l) l))
|
|
|
|
(define (for-zos ! p)
|
|
(define p-str (if (path? p) (path->string p) p))
|
|
(cond
|
|
[(directory-exists? p)
|
|
(for ([sp (in-list (maybe-randomize-list (directory-list p)))])
|
|
(for-zos ! (build-path p sp)))]
|
|
[(regexp-match #rx"\\.zo$" p-str)
|
|
(! p-str)]))
|
|
|
|
(define-runtime-path zo-test-worker-path "zo-test-worker.rkt")
|
|
(define racket-path (path->string (find-exe)))
|
|
|
|
(define p
|
|
(command-line #:program "zo-test"
|
|
#:once-each
|
|
[("-D")
|
|
"Enable debugging output"
|
|
(debugging? #t)]
|
|
[("-s" "--stop-on-first-error")
|
|
"Stop testing when first error is encountered"
|
|
(stop-on-first-error #t)]
|
|
[("-S")
|
|
"Don't take some errors seriously"
|
|
(care-about-nonserious? #f)]
|
|
[("-v" "--verbose")
|
|
"Display verbose error messages"
|
|
(verbose-mode #t)]
|
|
[("-I")
|
|
"Invariant output"
|
|
(invariant-output #t)]
|
|
[("-R")
|
|
"Randomize"
|
|
(randomize #t)]
|
|
[("-t")
|
|
number
|
|
"Limit the run to a given amount of time"
|
|
(time-limit (string->number number))]
|
|
[("-j")
|
|
n
|
|
"Run <n> in parallel"
|
|
(num-processes (string->number n))]
|
|
#:args p
|
|
(if (empty? p)
|
|
(list (find-collects-dir))
|
|
p)))
|
|
|
|
(define to-worker-ch (make-channel))
|
|
(define stop-ch (make-channel))
|
|
(define from-worker-ch (make-channel))
|
|
|
|
(define worker-threads
|
|
(for/list ([i (in-range (num-processes))])
|
|
(thread
|
|
(λ ()
|
|
(let loop ()
|
|
(sync
|
|
(handle-evt to-worker-ch
|
|
(λ (p)
|
|
(when (debugging?)
|
|
(printf "~a\n" p))
|
|
(define-values
|
|
(sp stdout stdin stderr)
|
|
(subprocess #f #f #f racket-path (path->string zo-test-worker-path) p))
|
|
(define r
|
|
(dynamic-wind
|
|
void
|
|
(λ ()
|
|
(read stdout))
|
|
(λ ()
|
|
(close-input-port stdout)
|
|
(close-input-port stderr)
|
|
(close-output-port stdin)
|
|
(subprocess-kill sp #t))))
|
|
(channel-put from-worker-ch (cons p r))
|
|
(loop)))
|
|
(handle-evt stop-ch
|
|
(λ (die)
|
|
(void)))))))))
|
|
|
|
(define (process-result p r)
|
|
(match r
|
|
[(success phase)
|
|
(success! p phase)]
|
|
[(failure phase serious? exn-msg)
|
|
(record-common-error! exn-msg)
|
|
(failure! p phase)
|
|
|
|
(unless (and (not (care-about-nonserious?)) (not serious?))
|
|
(when (or (verbose-mode) (stop-on-first-error))
|
|
(eprintf "~a -- ~a: ~a\n" p phase exn-msg))
|
|
(when (stop-on-first-error)
|
|
(stop!)))]))
|
|
|
|
(define timing-thread
|
|
(thread
|
|
(λ ()
|
|
(sync
|
|
(alarm-evt (+ (current-inexact-milliseconds)
|
|
(* 1000 (time-limit)))))
|
|
(stop!))))
|
|
|
|
(define server-thread
|
|
(thread
|
|
(λ ()
|
|
(let loop ([ts worker-threads])
|
|
(if (empty? ts)
|
|
(stop!)
|
|
(apply
|
|
sync
|
|
(handle-evt from-worker-ch
|
|
(match-lambda
|
|
[(cons p rs)
|
|
(for-each (curry process-result p) rs)
|
|
(loop ts)]))
|
|
(for/list ([t (in-list ts)])
|
|
(handle-evt t (λ _ (loop (remq t ts)))))))))))
|
|
|
|
(define (spawn-worker p)
|
|
(channel-put to-worker-ch p))
|
|
|
|
(define (zo-test paths)
|
|
(for-each (curry for-zos spawn-worker) paths)
|
|
|
|
(for ([i (in-range (processor-count))])
|
|
(channel-put stop-ch #t)))
|
|
|
|
(define root-thread
|
|
(thread
|
|
(λ ()
|
|
(zo-test p))))
|
|
|
|
(define final-sema (make-semaphore 0))
|
|
(define (stop!)
|
|
(semaphore-post final-sema))
|
|
|
|
(define (hash-keys ht)
|
|
(hash-map ht (λ (k v) k)))
|
|
|
|
(define final-thread
|
|
(thread
|
|
(λ ()
|
|
(semaphore-wait final-sema)
|
|
(for-each kill-thread
|
|
(list* root-thread server-thread worker-threads))
|
|
(unless (invariant-output)
|
|
(newline)
|
|
(for ([kind-name
|
|
(remove-duplicates
|
|
(append
|
|
(hash-keys failure-ht)
|
|
(hash-keys success-ht)))])
|
|
(define fails (length (hash-ref failure-ht kind-name empty)))
|
|
(define succs (length (hash-ref success-ht kind-name empty)))
|
|
(define all (+ fails succs))
|
|
(unless (zero? all)
|
|
(printf "~S\n"
|
|
`(,kind-name
|
|
(#f ,fails)
|
|
(#t ,succs)
|
|
,all))))
|
|
(newline)
|
|
(printf "~a tests passed\n" (length (hash-ref success-ht 'everything empty)))
|
|
|
|
(let ([common-errors
|
|
(sort (filter (λ (p) ((car p) . > . 10))
|
|
(hash-map errors (λ (k v) (cons v k))))
|
|
> #:key car)])
|
|
(unless (empty? common-errors)
|
|
(printf "Common Errors:\n")
|
|
(for ([p (in-list common-errors)])
|
|
(printf "~a:\n~a\n\n" (car p) (cdr p)))))))))
|
|
|
|
(thread-wait final-thread)
|
|
|
|
;; Test mode:
|
|
(module test racket/base
|
|
(require syntax/location)
|
|
(parameterize ([current-command-line-arguments (vector "-I" "-S" "-t" "60" "-v" "-R")])
|
|
(dynamic-require (quote-module-path "..") #f)))
|