racket/collects/redex/examples/delim-cont/randomized-tests.rkt
Casey Klein 1ccd6e7a2c Randomized tests for the delimited control model.
Tests in randomized-tests.rkt are deliberately not run by DrDr at the
moment because they fail too consistently.
2010-11-26 13:24:21 -06:00

385 lines
12 KiB
Racket
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#lang racket
(require "grammar.ss"
"reduce.rkt"
(except-in redex/reduction-semantics plug)
racket/runtime-path)
(provide (all-defined-out))
(define (main [seed-arg #f])
(define seed
(if seed-arg
(string->number seed-arg)
(add1 (random (sub1 (expt 2 31))))))
(printf "Test seed: ~s\n" seed)
(parameterize ([current-pseudo-random-generator test-prg])
(random-seed seed))
(parameterize ([redex-pseudo-random-generator test-prg])
(time (test #:attempts 3000))
(time (test #:source :-> #:attempts 3000))))
(define-syntax-rule (test . kw-args)
(redex-check grammar p (same-behavior? (term p))
#:prepare fix-prog . kw-args))
(define fix-prog
(match-lambda
[`(<> ,s ,_ ,e)
(match-let ([`([,xs ,vs] ...) (remove-duplicates s #:key first)])
`(<> ,(map list xs (map (fix-expr xs) vs)) [] ,((fix-expr xs) e)))]))
(define (fix-expr top-vars)
(compose drop-duplicate-binders
proper-wcms
consistent-dws
(curry close top-vars '())))
(struct error (cause) #:transparent)
(struct answer (output result) #:transparent)
(struct bad-test (reason) #:transparent)
(struct timeout ())
(define (same-behavior? prog)
(let ([impl-behavior (timeout-kill 15 (impl-eval (impl-program (transform-intermediate prog))))])
(or (bad-test? impl-behavior)
(timeout? impl-behavior)
(let ([model-behavior (timeout-warn 30 (model-eval prog) (pretty-write prog))])
(or (timeout? model-behavior)
(if (error? impl-behavior)
(error? model-behavior)
(and (answer? model-behavior)
(equal? impl-behavior model-behavior))))))))
(define impl-program
(match-lambda
[`(<> ,s [] ,e)
`(letrec ,s ,e)]
[e e]))
(define-runtime-module-path model-impl "model-impl.rkt")
(define impl-eval
(let ([ns (make-base-empty-namespace)])
(parameterize ([current-namespace ns])
(namespace-require 'racket)
(namespace-require (resolved-module-path-name model-impl)))
(define show
(match-lambda
[(? procedure?) 'procedure]
[(? list? vs) (map show vs)]
[v v]))
(λ (test)
(define output (open-output-string))
(define result
(with-handlers ([exn:fail?
(λ (e)
(match (exn-message e)
[(regexp #rx"%: expected argument of type <non-procedure>")
(bad-test "procedure as tag")]
[_ (error e)]))])
(parameterize ([current-output-port output])
(eval test ns))))
(if (or (error? result) (bad-test? result))
result
(answer (get-output-string output)
(show result))))))
(define model-eval-steps (make-parameter +inf.0))
(define (model-eval prog)
(let/ec return
(define show
(match-lambda
[(? number? n) n]
[(? boolean? b) b]
[`(list . ,vs) (map show vs)]
[v 'procedure]))
(define (eval prog steps)
(define ns (set))
(let recur ([p prog] [d steps] [s (set)])
(define qs (apply-reduction-relation :-> p))
(if (empty? qs)
(set! ns (set-add ns p))
(if (< d 0)
(return (timeout))
(for ([q qs])
(if (set-member? s q)
(return (timeout))
(recur q (sub1 d) (set-add s p)))))))
(set-map ns values))
(match (eval prog (model-eval-steps))
[(list (and p `(<> ,_ ,output ,result)))
(if (v? result)
(answer
(apply string-append (map (curry format "~v") output))
(show result))
(error p))])))
(define (with-timeout thunk timeout on-timeout)
(let ([c (make-channel)])
(struct raised (value))
(let ([t (thread
(λ ()
(channel-put
c (with-handlers ([exn:fail? raised])
(thunk)))))])
(match (sync/timeout timeout c)
[#f (on-timeout t c)]
[(raised v) (raise v)]
[x x]))))
(define-syntax-rule (timeout-kill time expr)
(with-timeout (λ () expr) time
(λ (t _)
(kill-thread t)
(timeout))))
(define-syntax-rule (timeout-warn time expr warn)
(with-timeout (λ () expr) time
(λ (_ c)
warn
(sync c))))
(define (close top-vars loc-vars expr)
(match expr
[(? x? x)
(let ([bound (append top-vars loc-vars)])
(cond [(memq x bound) x]
[(not (empty? bound))
(random-member bound)]
[else (random-literal)]))]
[`(set! ,x ,e)
(if (empty? top-vars)
(close top-vars loc-vars e)
`(set! ,(random-member top-vars)
,(close top-vars loc-vars e)))]
[`(λ ,xs ,e)
`(λ ,xs
,(close (filter (negate (curryr member xs)) top-vars)
(append xs loc-vars)
e))]
[`(dw ,x ,e_1 ,e_2 ,e_3)
`(dw ,x
,(close top-vars loc-vars e_1)
,(close top-vars loc-vars e_2)
,(close top-vars loc-vars e_3))]
; substitution does not recur inside continuation values
; (not sure why it bothers to recur within dw expression)
[`(cont ,v ,E)
`(cont ,(close top-vars '() v)
,(close top-vars '() E))]
[`(cont ,E)
`(comp ,(close top-vars '() E))]
[(? list?)
(map (curry close top-vars loc-vars) expr)]
[_ expr]))
(define drop-duplicate-binders
(match-lambda
[`(λ ,xs ,e)
`(λ ,(remove-duplicates xs) ,(drop-duplicate-binders e))]
[(? list? es)
(map drop-duplicate-binders es)]
[e e]))
(define (consistent-dws p)
(define pre-post
(let ([h (make-hash)])
(λ (id pre post)
(match (hash-ref h id #f)
[#f
(hash-set! h id (list pre post))
(list pre post)]
[x x]))))
(let recur ([x p] [excluded '()])
(match x
[`(dw ,x ,e1 ,e2 ,e3)
(if (member x excluded)
(recur e2 excluded)
(match-let ([(list e1 e3) (pre-post x e1 e3)])
`(dw ,x
,(recur e1 (cons x excluded))
,(recur e2 excluded)
,(recur e3 (cons x excluded)))))]
[(? list?) (map (curryr recur excluded) x)]
[_ x])))
(define (proper-wcms e)
(let fix ([ok? #t] [e e])
(match e
[`(wcm ,w ,e)
(if ok?
`(wcm ,(remove-duplicates (fix #t w) #:key first)
,(fix #f e))
(fix #f e))]
[`(list . ,vs)
`(list . ,(map (curry fix #t) vs))]
[`(λ ,xs ,e)
; #f in case applied with a continuation that's already marked
`(λ ,xs ,(fix #f e))]
[`(cont ,v ,E)
`(cont ,(fix #t v) ,(fix #t E))]
[`(comp ,E)
`(comp ,(fix #t E))]
[`(begin ,e1 ,e2)
`(begin ,(fix #t e1)
,(fix ok? e2))]
[`(% ,e1 ,e2 ,e3)
`(% ,(fix #t e1) ,(fix ok? e2) ,(fix #t e3))]
[`(dw ,x ,e1 ,e2 ,e3)
`(dw ,x ,(fix #t e1) ,(fix ok? e2) ,(fix #t e3))]
[`(if ,e1 ,e2 ,e3)
`(if ,(fix #t e1)
,(fix ok? e2)
,(fix ok? e3))]
[`(set! ,x ,e)
`(set! ,x ,(fix #t e))]
[(? list?)
(map (curry fix #t) e)]
[_ e])))
(define transform-intermediate
(match-lambda
[(and p `(<> ,s ,o ,e))
(define fresh (make-fresh p))
(define allocated (map first s))
(define (alloc-cell prefix)
(define cell (fresh prefix))
(set! allocated (cons cell allocated))
cell)
(define no-dw? (alloc-cell "handlers-disabled?"))
(define dw-frame-locs
(let ([locs (make-hash)])
(λ (x)
(hash-ref
locs x
(λ () (let ([ys (list (alloc-cell (format "~s-allocated?" x))
(alloc-cell (format "~s-skip-pre?" x))
(alloc-cell (format "~s-comp-cont" x)))])
(hash-set! locs x ys)
ys))))))
(define transform
(match-lambda
[`(wcm () ,m)
(transform m)]
[`(wcm ([,k ,v] . ,w) ,m)
`(call/cm ,(transform k) ,(transform v)
(λ () ,(transform `(wcm ,w ,m))))]
[(and e `(dw ,x ,e1 ,e2 ,e3))
(match-let ([(list a? s? c) (dw-frame-locs x)]
[t (fresh "t")])
`((λ (,t)
(if ,a?
(begin (if ,no-dw? #f (set! ,s? #t)) (,c ,t))
(% 1
(dynamic-wind
(λ ()
(if ,no-dw?
#f
(if ,a?
(if ,s? (set! ,s? #f) ,(transform e1))
#f)))
(λ ()
((call/comp
(λ (k)
(begin
(set! ,c k)
(abort 1 k)))
1)))
(λ ()
(if ,no-dw?
(set! ,a? #t)
(if ,a?
,(transform e3)
(set! ,a? #t)))))
(λ (k) (begin (if ,no-dw? #f (set! ,s? #t)) (k ,t))))))
(λ () ,(transform e2))))]
[`(cont ,v ,E)
(let ([x (fresh "v")])
`(begin
(set! ,no-dw? #t)
((λ (,x)
(% ,x
,(transform
(term (plug ,E (call/cc (λ (k) (abort ,x k)) ,x))))
(λ (x) (begin (set! ,no-dw? #f) x))))
,(transform v))))]
[`(comp ,E)
(define numbers
(match-lambda
[(? integer? n) (list n)]
[(? list? l) (append-map numbers l)]
[_ (list)]))
(define t (add1 (apply max 0 (numbers E))))
`(begin
(set! ,no-dw? #t)
(% ,t
,(transform
(term (plug ,E (call/comp (λ (k) (abort ,t k)) ,t))))
(λ (x) (begin (set! ,no-dw? #f) x))))]
[`(list ,vs ...)
`(list ,@(map transform-value vs))]
[(? list? xs)
(map transform xs)]
[e e]))
(define transform-value
(match-lambda
[(and e (or `(cont ,_ ,_) `(comp ,_)))
`(λ (x) (,(transform e) x))]
[e (transform e)]))
(define e (transform e))
(define s (map (match-lambda [(list x v) (list x (transform-value v))]) s))
`(<> ,(map (λ (x) (match (assoc x s)
[#f (list x #f)]
[(list _ v) (list x v)]))
allocated)
,o
,e)]))
;; The built-in `plug' sometimes chooses the wrong hole.
(define-metafunction grammar
[(plug hole any) any]
[(plug (in-hole W (dw x e_1 E e_2)) any)
(in-hole W (dw x e_1 (plug E any) e_2))]
[(plug (wcm w M) any)
(wcm w (plug M any))]
[(plug (v ... W e ...) any)
(v ... (plug W any) e ...)]
[(plug (begin W e) any)
(begin (plug W any) e)]
[(plug (% W e_1 e_2) any)
(% (plug W any) e_1 e_2)]
[(plug (% v e W) any)
(% v e (plug W any))]
[(plug (% v_1 W v_2) any)
(% v_1 (plug W any) v_2)]
[(plug (set! x W) any)
(set! x (plug W any))]
[(plug (if W e_1 e_2) any)
(if (plug W any) e_1 e_2)])
(define (make-fresh p)
(define suffix
(let recur ([x p] [s 0])
(cond [(symbol? x)
(match (regexp-match #rx"_(.+)$" (symbol->string x))
[(list _ n) (max s (add1 (string->number n)))]
[#f s])]
[(pair? x) (recur (cdr x) (recur (car x) s))]
[else s])))
(λ (prefix)
(begin0 (string->symbol (format "~a_~a" prefix suffix))
(set! suffix (add1 suffix)))))
(define (random-literal)
(random-member
'(dynamic-wind abort current-marks cons
-inf.0 +inf.0 -1 0 1 1/3 -1/4 .33 -.25 4-3i 3+4i
call/cc call/comp call/cm
#f #t zero? print + first rest)))
(define (random-member xs)
(parameterize ([current-pseudo-random-generator test-prg])
(list-ref xs (random (length xs)))))
(define test-prg (make-pseudo-random-generator))