generalize the random contract generation APIs to allow

a more dynamic notion of failure

use that to try harder with and/c contracts. In particular,
the contract system now tries to generate the arguments
on their own and then uses the other arguments to filter
(when they are all flat contracts, of course)

closes PR 14832
This commit is contained in:
Robby Findler 2014-11-16 20:41:37 -06:00
parent 43229abf05
commit 5495595535
11 changed files with 328 additions and 105 deletions

View File

@ -2357,12 +2357,13 @@ is expected to be the blame record for the contract on the value).
#f] #f]
[#:generate [#:generate
generate generate
(->i ([c contract?]) (or/c (->i ([c contract?])
([generator ([generator
(c) (c)
(-> (and/c positive? real?) (-> (and/c positive? real?)
(or/c #f (or/c (-> (or/c contract-random-generate-fail? c))
(-> c)))])) #f))]))
#f)
#f] #f]
[#:exercise [#:exercise
exercise exercise
@ -2405,12 +2406,13 @@ is expected to be the blame record for the contract on the value).
#f] #f]
[#:generate [#:generate
generate generate
(->i ([c contract?]) (or/c (->i ([c contract?])
([generator ([generator
(c) (c)
(-> (and/c positive? real?) (-> (and/c positive? real?)
(or/c #f (or/c (-> (or/c contract-random-generate-fail? c))
(-> c)))])) #f))]))
#f)
#f] #f]
[#:exercise [#:exercise
exercise exercise
@ -2453,12 +2455,13 @@ is expected to be the blame record for the contract on the value).
#f] #f]
[#:generate [#:generate
generate generate
(->i ([c contract?]) (or/c (->i ([c contract?])
([generator ([generator
(c) (c)
(-> (and/c positive? real?) (-> (and/c positive? real?)
(or/c #f (or/c (-> (or/c contract-random-generate-fail? c))
(-> c)))])) #f))]))
#f)
#f] #f]
[#:exercise [#:exercise
exercise exercise
@ -2491,7 +2494,8 @@ produces a blame-tracking projection defining the behavior of the contract;
@racket[stronger], which is a predicate that determines whether this contract @racket[stronger], which is a predicate that determines whether this contract
(passed in the first argument) is stronger than some other contract (passed (passed in the first argument) is stronger than some other contract (passed
in the second argument); @racket[generate], which returns a thunk in the second argument); @racket[generate], which returns a thunk
that generates random values matching the contract or @racket[#f], indicating that generates random values matching the contract (using @racket[contract-random-generate-fail])
to indicate failure) or @racket[#f] to indicate
that random generation for this contract isn't supported; @racket[exercise], that random generation for this contract isn't supported; @racket[exercise],
which returns a function that exercises values matching the contract (e.g., which returns a function that exercises values matching the contract (e.g.,
if it is a function contract, it may call the function) and a list of contracts if it is a function contract, it may call the function) and a list of contracts
@ -2519,7 +2523,9 @@ projection accessor is expected not to wrap its argument in a higher-order
fashion, analogous to the constraint on projections in fashion, analogous to the constraint on projections in
@racket[make-flat-contract]. @racket[make-flat-contract].
@history[#:changed "6.0.1.13" @list{Added the @racket[#:list-contract?] argument.}] @history[#:changed "6.0.1.13" @list{Added the @racket[#:list-contract?] argument.}
#:changed "6.1.1.4"
@list{Allow @racket[generate] to return @racket[contract-random-generate-fail]}]
} }
@deftogether[( @deftogether[(
@ -2919,7 +2925,7 @@ parts of the contract system.
@defproc[(contract-random-generate [ctc contract?] @defproc[(contract-random-generate [ctc contract?]
[fuel 5 exact-nonnegative-integer?] [fuel 5 exact-nonnegative-integer?]
[fail (or/c #f (-> any)) #f]) [fail (or/c #f (-> any) (-> boolean? any)) #f])
any/c]{ any/c]{
Attempts to randomly generate a value which will match the contract. The fuel Attempts to randomly generate a value which will match the contract. The fuel
argument limits how hard the generator tries to generate a value matching the argument limits how hard the generator tries to generate a value matching the
@ -2928,7 +2934,13 @@ contract and is a rough limit of the size of the resulting value.
The generator may fail to generate a value, either because some contracts The generator may fail to generate a value, either because some contracts
do not have corresponding generators (for example, not all predicates have do not have corresponding generators (for example, not all predicates have
generators) or because there is not enough fuel. In either case, the generators) or because there is not enough fuel. In either case, the
thunk @racket[fail] is invoked. function @racket[fail] is invoked. If @racket[fail] accepts an argument,
it is called with @racket[#t] when there is no generator for @racket[ctc]
and called with @racket[#f] when there is a generator, but the generator
ended up returning @racket[contract-random-generate-fail].
@history[#:changed "6.1.1.5" @list{Allow @racket[fail] to accept a boolean.}]
} }
@defproc[(contract-exercise [val any/c] ...+) void?]{ @defproc[(contract-exercise [val any/c] ...+) void?]{
@ -2939,3 +2951,62 @@ thunk @racket[fail] is invoked.
to poke and prod at the value. For example, if the value is function, it will to poke and prod at the value. For example, if the value is function, it will
use the contract to tell it what arguments to supply to the value. use the contract to tell it what arguments to supply to the value.
} }
@defproc[(contract-random-generate/choose [c contract?] [fuel exact-nonnegative-integer?])
(or/c #f (-> c))]{
This function is like @racket[contract-random-generate], but it is intended to
be used with combinators that generate values based on sub-contracts
they have. It cannot be called, except during contract
generation. It will never fail, but it might escape back to an enclosing
call or to the original call to @racket[contract-random-generate].
It chooses one of several possible generation strategies, and thus it may not
actually use the generator associated with @racket[c], but might instead
use a stashed value that matches @racket[c] that it knows about via
@racket[contract-random-generate-stash].
@history[#:added "6.1.1.5"]
}
@defthing[contract-random-generate-fail contract-random-generate-fail?]{
An atomic value that is used to indicate that a generator
failed to generate a value.
@history[#:added "6.1.1.5"]
}
@defproc[(contract-random-generate-fail? [v any/c]) boolean?]{
A predicate to recognize @racket[contract-random-generate-fail].
@history[#:added "6.1.1.5"]
}
@defproc[(contract-random-generate-env? [v any/c]) boolean?]{
Recognizes contract generation environments.
@history[#:added "6.1.1.5"]
}
@defproc[(contract-random-generate-stash [env contract-random-generate-env?]
[c contract?]
[v c]) void?]{
This should be called with values that the program under
test supplies during contract generation. For example, when
@racket[(-> (-> integer? integer?) integer?)] is generated,
it may call its argument function. That argument function may
return an integer and, if so, that integer should be saved by
calling @racket[contract-random-generate-stash], so it can
be used by other integer generators.
@history[#:added "6.1.1.5"]
}
@defproc[(contract-random-generate-get-current-environment) contract-random-generate-env?]{
Returns the environment currently being for generation. This function
can be called only during the dynamic extent of contract generation.
It is intended to be grabbed during the construction of a contract
generator and then used with @racket[contract-random-generate-stash]
while generation is happening.
@history[#:added "6.1.1.5"]
}

View File

@ -9,8 +9,16 @@
(define (some-crazy-predicate? x) (and (number? x) (= x 11))) (define (some-crazy-predicate? x) (and (number? x) (= x 11)))
(define (test-contract-generation ctc #:size [size 10]) (define (test-contract-generation ctc #:size [size 10])
(define example-val (contract-random-generate ctc size)) (let/ec k
(contract ctc example-val 'pos 'neg)) (define example-val (contract-random-generate
ctc size
(λ (no-generator?)
(if no-generator?
(error 'test-contract-generation
"unable to construct any generator for contract: ~e"
ctc)
(k 'we-tried-but-could-not-generate-anything)))))
(contract ctc example-val 'pos 'neg)))
(for ([(k v) (in-hash predicate-generator-table)]) (for ([(k v) (in-hash predicate-generator-table)])
(check-not-exn (λ () (test-contract-generation k)))) (check-not-exn (λ () (test-contract-generation k))))
@ -38,12 +46,17 @@
(check-not-exn (λ () (test-contract-generation (</c 0.0)))) (check-not-exn (λ () (test-contract-generation (</c 0.0))))
(check-not-exn (λ () (test-contract-generation (=/c 0)))) (check-not-exn (λ () (test-contract-generation (=/c 0))))
(check-not-exn (λ () (test-contract-generation (=/c 0.0)))) (check-not-exn (λ () (test-contract-generation (=/c 0.0))))
(check-not-exn (λ () (test-contract-generation (and/c real? (not/c negative?)))))
(check-not-exn (λ () (test-contract-generation (and/c rational? (not/c negative?)))))
(check-not-exn (λ () (test-contract-generation (or/c boolean? boolean?)))) (check-not-exn (λ () (test-contract-generation (or/c boolean? boolean?))))
(check-not-exn (λ () (test-contract-generation (cons/c integer? boolean?)))) (check-not-exn (λ () (test-contract-generation (cons/c integer? boolean?))))
(check-not-exn (λ () (test-contract-generation any/c))) (check-not-exn (λ () (test-contract-generation any/c)))
(check-not-exn (λ () (test-contract-generation (and/c real? (not/c negative?)))))
(check-not-exn (λ () (test-contract-generation (and/c rational? (not/c negative?)))))
(check-not-exn (λ () (test-contract-generation (and/c integer? even?))))
(check-not-exn (λ () (test-contract-generation (and/c procedure? (-> integer? integer?)))))
(check-not-exn (λ () (test-contract-generation (and/c integer? even?))))
(check-not-exn (λ () (test-contract-generation (or/c (and/c real? positive? (</c 0)) boolean?))))
(check-not-exn (λ () (test-contract-generation (listof boolean?)))) (check-not-exn (λ () (test-contract-generation (listof boolean?))))
(check-not-exn (λ () (test-contract-generation (listof some-crazy-predicate?)))) (check-not-exn (λ () (test-contract-generation (listof some-crazy-predicate?))))
(check-not-exn (λ () (test-contract-generation (non-empty-listof boolean?)))) (check-not-exn (λ () (test-contract-generation (non-empty-listof boolean?))))
@ -105,7 +118,7 @@
(define (cannot-generate-exn? x) (define (cannot-generate-exn? x)
(and (exn:fail? x) (and (exn:fail? x)
(regexp-match #rx"contract-random-generate: unable to construct" (regexp-match #rx"test-contract-generation: unable to construct"
(exn-message x)))) (exn-message x))))
(check-exn cannot-generate-exn? (λ () (test-contract-generation some-crazy-predicate?))) (check-exn cannot-generate-exn? (λ () (test-contract-generation some-crazy-predicate?)))
(check-exn cannot-generate-exn? (λ () (test-contract-generation (list/c some-crazy-predicate?)))) (check-exn cannot-generate-exn? (λ () (test-contract-generation (list/c some-crazy-predicate?))))
@ -127,6 +140,14 @@
(or/c some-crazy-predicate? (or/c some-crazy-predicate?
some-crazy-predicate?)))) some-crazy-predicate?))))
;; testing a bunch of impossible and/c's inside some or/c doesn't crash
(check-not-exn (λ () (test-contract-generation
(or/c (or/c (and/c integer? boolean?)
(and/c (listof integer?) string?))
(and/c (-> number? number?)
any/c
number?)))))
(check-not-exn (check-not-exn
(λ () (λ ()
(define eleven (define eleven

View File

@ -13,4 +13,9 @@
"contract/private/legacy.rkt" "contract/private/legacy.rkt"
"contract/private/ds.rkt") "contract/private/ds.rkt")
contract-random-generate contract-random-generate
contract-random-generate-stash
contract-random-generate-get-current-environment
contract-random-generate/choose
contract-random-generate-fail
contract-random-generate-fail?
contract-exercise) contract-exercise)

View File

@ -118,18 +118,18 @@
(define gens (for/list ([arg-ctc (in-list (->i-arg-ctcs ctc))] (define gens (for/list ([arg-ctc (in-list (->i-arg-ctcs ctc))]
#:when (and (not (->i-arg-optional? arg-ctc)) #:when (and (not (->i-arg-optional? arg-ctc))
(not (->i-arg-kwd arg-ctc)))) (not (->i-arg-kwd arg-ctc))))
(generate/choose (->i-arg-contract arg-ctc) fuel))) (contract-random-generate/choose (->i-arg-contract arg-ctc) fuel)))
(define kwd-gens (for/list ([arg-ctc (in-list (->i-arg-ctcs ctc))] (define kwd-gens (for/list ([arg-ctc (in-list (->i-arg-ctcs ctc))]
#:when (and (not (->i-arg-optional? arg-ctc)) #:when (and (not (->i-arg-optional? arg-ctc))
(->i-arg-kwd arg-ctc))) (->i-arg-kwd arg-ctc)))
(generate/choose (->i-arg-contract arg-ctc) fuel))) (contract-random-generate/choose (->i-arg-contract arg-ctc) fuel)))
(define dom-kwds (for/list ([arg-ctc (in-list (->i-arg-ctcs ctc))] (define dom-kwds (for/list ([arg-ctc (in-list (->i-arg-ctcs ctc))]
#:when (and (not (->i-arg-optional? arg-ctc)) #:when (and (not (->i-arg-optional? arg-ctc))
(->i-arg-kwd arg-ctc))) (->i-arg-kwd arg-ctc)))
(->i-arg-kwd arg-ctc))) (->i-arg-kwd arg-ctc)))
(cond (cond
[(andmap values gens) [(andmap values gens)
(define env (generate-env)) (define env (contract-random-generate-get-current-environment))
(values (λ (f) (values (λ (f)
(call-with-values (call-with-values
(λ () (λ ()
@ -150,7 +150,7 @@
;; better: if we did actually stash the results we knew about. ;; better: if we did actually stash the results we knew about.
'(for ([res-ctc (in-list rng-ctcs)] '(for ([res-ctc (in-list rng-ctcs)]
[result (in-list results)]) [result (in-list results)])
(env-stash env res-ctc result))))) (contract-random-generate-stash env res-ctc result)))))
;; better here: if we promised the results we knew we could deliver ;; better here: if we promised the results we knew we could deliver
'())] '())]
[else [else

View File

@ -728,19 +728,19 @@
addl-available addl-available
(λ () (λ ()
(for/list ([c (in-list (base->-rngs ctc))]) (for/list ([c (in-list (base->-rngs ctc))])
(generate/choose c fuel)))) (contract-random-generate/choose c fuel))))
'())) '()))
(cond (cond
[(for/and ([rng-gen (in-list rngs-gens)]) [(for/and ([rng-gen (in-list rngs-gens)])
rng-gen) rng-gen)
(define env (generate-env)) (define env (contract-random-generate-get-current-environment))
(λ () (λ ()
(procedure-reduce-arity (procedure-reduce-arity
(λ args (λ args
; stash the arguments for use by other generators ; stash the arguments for use by other generators
(for ([ctc (in-list dom-ctcs)] (for ([ctc (in-list dom-ctcs)]
[arg (in-list args)]) [arg (in-list args)])
(env-stash env ctc arg)) (contract-random-generate-stash env ctc arg))
; exercise the arguments ; exercise the arguments
(for ([arg (in-list args)] (for ([arg (in-list args)]
[dom-exer (in-list dom-exers)]) [dom-exer (in-list dom-exers)])
@ -769,11 +769,11 @@
(λ (fuel) (λ (fuel)
(define gens (define gens
(for/list ([dom-ctc (in-list dom-ctcs)]) (for/list ([dom-ctc (in-list dom-ctcs)])
(generate/choose dom-ctc fuel))) (contract-random-generate/choose dom-ctc fuel)))
(define kwd-gens (define kwd-gens
(for/list ([kwd-info (in-list dom-kwd-infos)]) (for/list ([kwd-info (in-list dom-kwd-infos)])
(generate/choose (kwd-info-ctc kwd-info) fuel))) (contract-random-generate/choose (kwd-info-ctc kwd-info) fuel)))
(define env (generate-env)) (define env (contract-random-generate-get-current-environment))
(cond (cond
[(and (andmap values gens) [(and (andmap values gens)
(andmap values kwd-gens)) (andmap values kwd-gens))
@ -792,7 +792,7 @@
(when rng-ctcs (when rng-ctcs
(for ([res-ctc (in-list rng-ctcs)] (for ([res-ctc (in-list rng-ctcs)]
[result (in-list results)]) [result (in-list results)])
(env-stash env res-ctc result)))))) (contract-random-generate-stash env res-ctc result))))))
(or rng-ctcs '()))] (or rng-ctcs '()))]
[else [else
(values void '())]))] (values void '())]))]

View File

@ -38,7 +38,7 @@
(cond (cond
[(∀∃/c-neg? ctc) [(∀∃/c-neg? ctc)
(λ (fuel) (λ (fuel)
(define env (generate-env)) (define env (contract-random-generate-get-current-environment))
(λ () (random-any/c env fuel)))] (λ () (random-any/c env fuel)))]
[else [else
(λ (fuel) #f)])))) (λ (fuel) #f)]))))

View File

@ -2,8 +2,9 @@
(require "rand.rkt") (require "rand.rkt")
(provide (provide
(rename-out [sngleton-maker make-generate-ctc-fail]) contract-random-generate-fail
generate-ctc-fail? contract-random-generate-fail?
fail-escape
find-generate find-generate
get-arg-names-space get-arg-names-space
@ -19,13 +20,11 @@
;; generate ;; generate
(define-struct env-item (ctc name)) (define-struct env-item (ctc name))
;; generate failure type (define fail-escape (make-parameter 'fail-escape-not-set))
(define-struct generate-ctc-fail ()) (define-values (contract-random-generate-fail contract-random-generate-fail?)
(define a-generate-ctc-fail (make-generate-ctc-fail)) (let ()
(define sngleton-maker (struct contract-random-generate-fail ())
(let ([make-generate-contract-fail (values (contract-random-generate-fail) contract-random-generate-fail?)))
(λ () a-generate-ctc-fail)])
make-generate-contract-fail))
(define (gen-char fuel) (define (gen-char fuel)
(let* ([gen (oneof (list (rand-range 0 55295) (let* ([gen (oneof (list (rand-range 0 55295)
@ -133,7 +132,7 @@
;; thread-cell ;; thread-cell
(define arg-names-count (make-thread-cell 0)) (define arg-names-count (make-thread-cell 0))
;; given a predicate returns a generate for this predicate or generate-ctc-fail ;; given a predicate returns a generate for this predicate or contract-random-generate-fail
(define (find-generate func [name "internal"]) (define (find-generate func [name "internal"])
(hash-ref predicate-generator-table func #f)) (hash-ref predicate-generator-table func #f))

View File

@ -6,18 +6,21 @@
"prop.rkt" "prop.rkt"
racket/list) racket/list)
(provide generate-env (provide contract-random-generate
env-stash contract-random-generate-stash
contract-random-generate contract-random-generate-get-current-environment
contract-random-generate/choose
contract-random-generate-env-hash
contract-random-generate-env?
contract-exercise contract-exercise
generate/direct generate/direct
generate/choose contract-random-generate-fail
make-generate-ctc-fail contract-random-generate-fail?
generate-ctc-fail?
with-definitely-available-contracts with-definitely-available-contracts
can-generate/env? can-generate/env?
try/env try/env
multi-exercise) multi-exercise
fail-escape)
(define (contract-exercise #:fuel [fuel 10] v1 . vs) (define (contract-exercise #:fuel [fuel 10] v1 . vs)
(define vals (define vals
@ -26,11 +29,18 @@
val)) val))
(define ctcs (map value-contract vals)) (define ctcs (map value-contract vals))
(define-values (go _) (define-values (go _)
(parameterize ([generate-env (make-hash)]) (parameterize ([generate-env (contract-random-generate-env (make-hash))])
((multi-exercise ctcs) fuel))) ((multi-exercise ctcs) fuel)))
(for ([x (in-range fuel)]) (for ([x (in-range fuel)])
(go vals))) (go vals)))
(define (contract-random-generate-get-current-environment)
(define env (generate-env))
(unless (contract-random-generate-env? env)
(error 'get-current-contract-generation-environment
"expected to be called only during generation"))
env)
;; multi-exercise : (listof contract?) -> fuel -> (values (listof ctc) (-> (listof val[ctcs]) void) ;; multi-exercise : (listof contract?) -> fuel -> (values (listof ctc) (-> (listof val[ctcs]) void)
(define (multi-exercise orig-ctcs) (define (multi-exercise orig-ctcs)
(λ (fuel) (λ (fuel)
@ -88,7 +98,8 @@
;; a stash of values and the contracts that they correspond to ;; a stash of values and the contracts that they correspond to
;; that generation has produced earlier in the process ;; that generation has produced earlier in the process
(define generate-env (make-parameter 'generate-env-not-currently-set)) (define generate-env (make-parameter #f))
(struct contract-random-generate-env (hash))
;; (parameter/c (listof contract?)) ;; (parameter/c (listof contract?))
;; contracts in this will definitely have values available ;; contracts in this will definitely have values available
@ -98,9 +109,20 @@
; Adds a new contract and value to the environment if ; Adds a new contract and value to the environment if
; they don't already exist ; they don't already exist
(define (env-stash env ctc val) (define (contract-random-generate-stash env ctc val)
(define curvals (hash-ref env ctc '())) (unless (contract-random-generate-env? env)
(hash-set! env ctc (cons val curvals))) (raise-argument-error 'contract-random-generate-stash
"contract-random-generate-env?"
0
env ctc val))
(unless (contract-struct? ctc)
(raise-argument-error 'contract-random-generate-stash
"contract?"
1
env ctc val))
(define env-hash (contract-random-generate-env-hash env))
(define curvals (hash-ref env-hash ctc '()))
(hash-set! env-hash ctc (cons val curvals)))
(define (with-definitely-available-contracts ctcs thunk) (define (with-definitely-available-contracts ctcs thunk)
(parameterize ([definitely-available-contracts (parameterize ([definitely-available-contracts
@ -113,36 +135,66 @@
(raise-argument-error 'contract-random-generate (raise-argument-error 'contract-random-generate
"exact-nonnegative-integer?" "exact-nonnegative-integer?"
fuel)) fuel))
(unless (or (not _fail) (and (procedure? _fail) (procedure-arity-includes? _fail 0))) (unless (or (not _fail)
(and (procedure? _fail)
(or (procedure-arity-includes? _fail 0)
(procedure-arity-includes? _fail 1))))
(raise-argument-error 'contract-random-generate (raise-argument-error 'contract-random-generate
(format "~s" '(or/c #f (-> any))) (format "~s" '(or/c #f (-> any) (-> boolean? any)))
3 3
ctc fuel _fail)) ctc fuel _fail))
(define fail
(cond
[(not _fail) #f]
[(procedure-arity-includes? _fail 1) _fail]
[else (λ (x) (_fail))]))
(define proc (define proc
(parameterize ([generate-env (make-hash)]) (parameterize ([generate-env (contract-random-generate-env (make-hash))])
(generate/choose def-ctc fuel))) (contract-random-generate/choose def-ctc fuel)))
(define-values (success? value)
(cond
[proc
(let/ec k
(parameterize ([fail-escape (λ () (k #f #f))])
(values #t (proc))))]
[else (values #f #f)]))
(cond (cond
[proc (proc)] [(and success?
[_fail (_fail)] (not (contract-random-generate-fail? value)))
value]
[fail (fail (not success?))]
[else [else
(error 'contract-random-generate (if success?
"unable to construct any generator for contract: ~e" (error 'contract-random-generate
def-ctc)])) "unable generate a value satisfying: ~e"
def-ctc)
(error 'contract-random-generate
"unable to construct any generator for contract: ~e"
def-ctc))]))
;; generate/choose : contract? nonnegative-int -> (or/c #f (-> any/c)) ;; generate/choose : contract? nonnegative-int -> (or/c #f (-> any/c))
; Iterates through generation methods until failure. Returns ; Iterates through generation methods until failure. Returns
; #f if no value could be generated ; #f if no value could be generated
(define (generate/choose ctc fuel) ;; if it returns a thunk, the thunk will not return contract-random-generate-fail?
(define (contract-random-generate/choose ctc fuel)
(define direct (generate/direct ctc fuel)) (define direct (generate/direct ctc fuel))
(define env-can? (can-generate/env? ctc)) (define env-can? (can-generate/env? ctc))
(define env (generate-env)) (define env (generate-env))
(unless (contract-random-generate-env? env)
(error 'contract-random-generate/choose
"expected to be called only during generation"))
(cond (cond
[direct [direct
(λ () (λ ()
(define use-direct? (zero? (rand 2))) (define use-direct? (zero? (rand 2)))
(if use-direct? (cond
(direct) [use-direct?
(try/env ctc env direct)))] (define candidate (direct))
(if (contract-random-generate-fail? candidate)
(try/env ctc env direct)
candidate)]
[else (try/env ctc env direct)]))]
[env-can? [env-can?
(λ () (λ ()
(try/env (try/env
@ -150,19 +202,27 @@
(λ () (error 'generate/choose "internal generation failure"))))] (λ () (error 'generate/choose "internal generation failure"))))]
[else #f])) [else #f]))
; generate/direct :: contract nonnegative-int -> (or/c #f (-> val)) ;; generate/direct :: contract nonnegative-int -> (or/c #f (-> val))
;; generate directly via the contract's built-in generator, if possible ;; generate directly via the contract's built-in generator, if possible
(define (generate/direct ctc fuel) ((contract-struct-generate ctc) fuel)) ;; if it returns a thunk, the thunk will not return contract-random-generate-fail?
(define (generate/direct ctc fuel)
(define candidate ((contract-struct-generate ctc) fuel))
(cond
[(contract-random-generate-fail? candidate) ((fail-escape))]
[else candidate]))
(define (try/env ctc env fail) (define (try/env ctc env fail)
(define env-hash (contract-random-generate-env-hash env))
(define available (define available
(for/list ([(avail-ctc vs) (in-hash env)] (for/list ([(avail-ctc vs) (in-hash env-hash)]
#:when (contract-stronger? avail-ctc ctc) #:when (contract-stronger? avail-ctc ctc)
[v (in-list vs)]) [v (in-list vs)])
v)) v))
(cond (cond
[(null? available) (fail)] [(null? available) (fail)]
[else (oneof available)])) [else
(oneof available)]))
(define (can-generate/env? ctc) (define (can-generate/env? ctc)
(for/or ([avail-ctc (in-list (definitely-available-contracts))]) (for/or ([avail-ctc (in-list (definitely-available-contracts))])

View File

@ -179,7 +179,65 @@
(cond (cond
[(and/c-check-nonneg ctc real?) => values] [(and/c-check-nonneg ctc real?) => values]
[(and/c-check-nonneg ctc rational?) => values] [(and/c-check-nonneg ctc rational?) => values]
[else (λ (fuel) #f)])) [(null? (base-and/c-ctcs ctc)) => (λ (fuel) #f)]
[else
(define flat (filter flat-contract? (base-and/c-ctcs ctc)))
(define ho (filter (λ (x) (not (flat-contract? x))) (base-and/c-ctcs ctc)))
(cond
[(null? ho)
(λ (fuel)
(define candidates
(let loop ([sub-contracts-after (cdr (base-and/c-ctcs ctc))]
[sub-contract (car (base-and/c-ctcs ctc))]
[sub-contracts-before '()]
[candidates '()])
(define sub-gen (contract-random-generate/choose sub-contract fuel))
(define new-candidates
(cond
[sub-gen
(cons (cons sub-gen (append (reverse sub-contracts-before) sub-contracts-after))
candidates)]
[else candidates]))
(cond
[(null? sub-contracts-after) new-candidates]
[else (loop (cdr sub-contracts-after)
(car sub-contracts-after)
(cons sub-contract sub-contracts-before)
new-candidates)])))
(cond
[(null? candidates) #f]
[else
(λ ()
(let loop ([attempts 10])
(cond
[(zero? attempts) contract-random-generate-fail]
[else
(define which (oneof candidates))
(define val ((car which)))
(cond
[(andmap (λ (p?) (p? val)) (cdr which))
val]
[else
(loop (- attempts 1))])])))]))]
[(null? (cdr ho))
(λ (fuel)
(define ho-gen (contract-random-generate/choose (car ho) fuel))
(cond
[ho-gen
(λ ()
(let loop ([attempts 10])
(cond
[(zero? attempts) contract-random-generate-fail]
[else
(define val (ho-gen))
(cond
[(andmap (λ (p?) (p? val)) flat)
val]
[else
(loop (- attempts 1))])])))]
[else #f]))]
[else
(λ (fuel) #f)])]))
(define (and/c-check-nonneg ctc pred) (define (and/c-check-nonneg ctc pred)
(define sub-contracts (base-and/c-ctcs ctc)) (define sub-contracts (base-and/c-ctcs ctc))
@ -476,7 +534,7 @@
(define (listof-generate ctc) (define (listof-generate ctc)
(λ (fuel) (λ (fuel)
(define eg (generate/choose (listof-ctc-elem-c ctc) fuel)) (define eg (contract-random-generate/choose (listof-ctc-elem-c ctc) fuel))
(if eg (if eg
(λ () (λ ()
(let loop ([so-far (cond (let loop ([so-far (cond
@ -501,14 +559,15 @@
[else [else
(define elem-ctc (listof-ctc-elem-c ctc)) (define elem-ctc (listof-ctc-elem-c ctc))
(λ (fuel) (λ (fuel)
(define env (generate-env)) (define env (contract-random-generate-get-current-environment))
(values (values
(λ (lst) (λ (lst)
(env-stash env elem-ctc (contract-random-generate-stash
(oneof env elem-ctc
(if (im-listof-ctc? ctc) (oneof
(improper-list->list lst) (if (im-listof-ctc? ctc)
lst)))) (improper-list->list lst)
lst))))
(list elem-ctc)))])) (list elem-ctc)))]))
(define (improper-list->list l) (define (improper-list->list l)
@ -853,8 +912,8 @@
(define ctc-car (the-cons/c-hd-ctc ctc)) (define ctc-car (the-cons/c-hd-ctc ctc))
(define ctc-cdr (the-cons/c-tl-ctc ctc)) (define ctc-cdr (the-cons/c-tl-ctc ctc))
(λ (fuel) (λ (fuel)
(define car-gen (generate/choose ctc-car fuel)) (define car-gen (contract-random-generate/choose ctc-car fuel))
(define cdr-gen (generate/choose ctc-cdr fuel)) (define cdr-gen (contract-random-generate/choose ctc-cdr fuel))
(and car-gen (and car-gen
cdr-gen cdr-gen
(λ () (cons (car-gen) (cdr-gen)))))) (λ () (cons (car-gen) (cdr-gen))))))
@ -940,7 +999,7 @@
(define elem-ctcs (generic-list/c-args ctc)) (define elem-ctcs (generic-list/c-args ctc))
(λ (fuel) (λ (fuel)
(define gens (for/list ([elem-ctc (in-list elem-ctcs)]) (define gens (for/list ([elem-ctc (in-list elem-ctcs)])
(generate/choose elem-ctc fuel))) (contract-random-generate/choose elem-ctc fuel)))
(cond (cond
[(andmap values gens) [(andmap values gens)
(λ () (λ ()
@ -1327,18 +1386,19 @@
(define any/c-neg-party-fn (λ (val) (λ (neg-party) val))) (define any/c-neg-party-fn (λ (val) (λ (neg-party) val)))
(define (random-any/c env fuel) (define (random-any/c env fuel)
(define env-hash (contract-random-generate-env-hash env))
(cond (cond
[(zero? (hash-count env)) [(zero? (hash-count env-hash))
(rand-choice (rand-choice
[1/3 (any/c-simple-value)] [1/3 (any/c-simple-value)]
[1/3 (any/c-procedure env fuel)] [1/3 (any/c-procedure env-hash fuel)]
[else (any/c-from-predicate-generator env fuel)])] [else (any/c-from-predicate-generator env-hash fuel)])]
[else [else
(rand-choice (rand-choice
[1/4 (oneof (hash-ref env (oneof (hash-keys env))))] [1/4 (oneof (hash-ref env-hash (oneof (hash-keys env-hash))))]
[1/4 (any/c-simple-value)] [1/4 (any/c-simple-value)]
[1/4 (any/c-procedure env fuel)] [1/4 (any/c-procedure env-hash fuel)]
[else (any/c-from-predicate-generator env fuel)])])) [else (any/c-from-predicate-generator env-hash fuel)])]))
(define (any/c-simple-value) (define (any/c-simple-value)
(oneof '(0 #f "" () #() -1 1 #t elephant))) (oneof '(0 #f "" () #() -1 1 #t elephant)))
@ -1368,7 +1428,7 @@
#:name (λ (ctc) 'any/c) #:name (λ (ctc) 'any/c)
#:generate (λ (ctc) #:generate (λ (ctc)
(λ (fuel) (λ (fuel)
(define env (generate-env)) (define env (contract-random-generate-get-current-environment))
(λ () (random-any/c env fuel)))) (λ () (random-any/c env fuel))))
#:first-order get-any?)) #:first-order get-any?))

View File

@ -121,7 +121,7 @@
(define (or/c-exercise ho-contracts) (define (or/c-exercise ho-contracts)
(λ (fuel) (λ (fuel)
(define env (generate-env)) (define env (contract-random-generate-get-current-environment))
(values (λ (val) (values (λ (val)
(let loop ([ho-contracts ho-contracts]) (let loop ([ho-contracts ho-contracts])
(unless (null? ho-contracts) (unless (null? ho-contracts)
@ -130,7 +130,7 @@
[((contract-first-order ctc) val) [((contract-first-order ctc) val)
(define-values (exercise ctcs) ((contract-struct-exercise ctc) fuel)) (define-values (exercise ctcs) ((contract-struct-exercise ctc) fuel))
(exercise val) (exercise val)
(env-stash env ctc val)] (contract-random-generate-stash env ctc val)]
[else [else
(loop (cdr ho-contracts))])))) (loop (cdr ho-contracts))]))))
'()))) '())))
@ -149,11 +149,11 @@
[can-generate? [can-generate?
;; #f => try to use me in the env. ;; #f => try to use me in the env.
(define options (cons #f (append directs ctcs))) (define options (cons #f (append directs ctcs)))
(define env (generate-env)) (define env (contract-random-generate-get-current-environment))
(λ () (λ ()
(let loop ([options (permute options)]) (let loop ([options (permute options)])
(cond (cond
[(null? options) (error 'or/c-generate "shouldn't fail!")] [(null? options) contract-random-generate-fail]
[else [else
(define option (car options)) (define option (car options))
(cond (cond
@ -165,7 +165,14 @@
(try/env (try/env
option env option env
(λ () (loop (cdr options))))] (λ () (loop (cdr options))))]
[else (option)])])))] [else
(define-values (succ? val)
(let/ec k
(parameterize ([fail-escape (λ () (k #f #f))])
(k #t (option)))))
(if succ?
val
(loop (cdr options)))])])))]
[else #f])) [else #f]))
(define (single-or/c-list-contract? c) (define (single-or/c-list-contract? c)
@ -482,7 +489,7 @@
(λ (fuel) (λ (fuel)
(if (zero? fuel) (if (zero? fuel)
#f #f
(generate/choose (get-flat-rec-me ctc) (- fuel 1))))))) (contract-random-generate/choose (get-flat-rec-me ctc) (- fuel 1)))))))
(define-syntax (_flat-rec-contract stx) (define-syntax (_flat-rec-contract stx)
(syntax-case stx () (syntax-case stx ()

View File

@ -214,7 +214,7 @@
(cond (cond
[(invariant? subc) #f] [(invariant? subc) #f]
[(indep? subc) [(indep? subc)
(define sgen (generate/choose (indep-ctc subc) fuel)) (define sgen (contract-random-generate/choose (indep-ctc subc) fuel))
(cond (cond
[sgen (loop (cdr subcs) (cons sgen gens))] [sgen (loop (cdr subcs) (cons sgen gens))]
[else #f])] [else #f])]
@ -671,7 +671,7 @@
(define (struct/dc-exercise stct) (define (struct/dc-exercise stct)
(λ (fuel) (λ (fuel)
(define env (generate-env)) (define env (contract-random-generate-get-current-environment))
(values (values
(λ (val) (λ (val)
;; need to extract the fields and do it in ;; need to extract the fields and do it in