racket/collects/tests/units/test-unit-contracts.rktl
2012-05-16 11:44:09 -04:00

893 lines
22 KiB
Racket

(require "test-harness.rkt"
scheme/unit
scheme/contract)
(define temp-unit-blame-re "\\(unit temp[0-9]*\\)")
(define top-level "top-level")
(define (match-blame re msg)
(or (regexp-match? (format "blaming: ~a" re) msg)
(regexp-match? (format "broke its contract:.*blaming: ~a" re) msg)))
(define (match-obj re msg)
(or (regexp-match? (format "~a: contract violation" re) msg)
(regexp-match? (format "~a: broke its contract" re) msg)))
(define (get-ctc-err msg)
(cond
[(regexp-match #rx"contract violation\n *([^\n]*)\n" msg)
=>
(λ (x) (cadr x))]
[(regexp-match #rx"broke its contract\n *([^\n]*)\n" msg)
=>
(lambda (x) (cadr x))]
[else (error 'test-contract-error
(format "no specific error in message: \"~a\"" msg))]))
(define-syntax-rule (test-contract-error blame obj err expr)
(test-contract-error/regexp
(regexp-quote blame) (regexp-quote obj) (regexp-quote err)
expr))
(define-syntax test-contract-error/regexp
(syntax-rules ()
((_ blame obj err expr)
(with-handlers ((exn:fail:contract?
(lambda (exn)
(let ([msg (exn-message exn)])
(cond
[(not (match-blame blame msg))
(error 'test-contract-error
"blame \"~a\" not found in:\n\"~a\""
blame msg)]
[(not (match-obj obj msg))
(error 'test-contract-error
"object \"~a\" not found in:\n\"~a\""
obj msg)]
[else
(printf "contract error \"~a\" on ~a blaming ~a: ok\n\t\"~a\"\n\n"
err obj blame (get-ctc-err msg))])))))
expr
(error 'test-contract-error
"expected contract error \"~a\" on ~a, got none"
err 'expr)))))
(define-signature sig1
((contracted [x number?])))
(define-signature sig2
((contracted [f (-> number? number?)])))
(define-signature sig3 extends sig2
((contracted [g (-> number? boolean?)])))
(define-signature sig4
((contracted [a number?] [b (-> boolean? number?)])))
(define-signature sig5
((contracted [c string?])
(contracted [d symbol?])))
(define-unit unit1
(import)
(export sig1)
(define x #f))
(define-unit unit2
(import sig1)
(export sig2)
(define (f n) x))
(define-unit unit3
(import sig3 sig4)
(export)
(b (g a)))
(define-unit unit4
(import sig3 sig4)
(export)
(g (b a)))
(define-unit unit5
(import)
(export sig5)
(define-values (c d) (values "foo" 3)))
(test-syntax-error "misuse of contracted"
contracted)
(test-syntax-error "invalid forms after contracted in signature"
(define-signature x ((contracted x y))))
(test-syntax-error "identifier not first part of pair after contracted in signature"
(define-signature x ((contracted [(-> number? number?) x]))))
(test-syntax-error "identifier h? not bound anywhere"
(module h?-test scheme
(define-signature s^
((define-values (f?) (values number?))
(define-syntaxes (g?) (make-rename-transformer #'number?))
(contracted [f (-> f? (and/c g? h?))])))))
(test-syntax-error "f not defined in unit exporting sig3"
(unit (import) (export sig3 sig4)
(define a #t)
(define g zero?)
(define (b t) (if t 3 0))))
(test-contract-error "(unit unit1)" "x" "not a number"
(invoke-unit unit1))
(test-contract-error "(unit unit1)" "x" "not a number"
(invoke-unit (compound-unit (import) (export)
(link (((S1 : sig1)) unit1)
(() unit2 S1)))))
(test-contract-error/regexp temp-unit-blame-re "a" "not a number"
(invoke-unit (compound-unit (import) (export)
(link (((S3 : sig3) (S4 : sig4))
(unit (import) (export sig3 sig4)
(define a #t)
(define f add1)
(define g zero?)
(define (b t) (if t 3 0))))
(() unit3 S3 S4)))))
(test-contract-error/regexp temp-unit-blame-re "g" "not a boolean"
(invoke-unit (compound-unit (import) (export)
(link (((S3 : sig3) (S4 : sig4))
(unit (import) (export sig3 sig4)
(define a 3)
(define f add1)
(define g values)
(define (b t) (if t 3 0))))
(() unit3 S3 S4)))))
(test-contract-error "(unit unit4)" "b" "not a boolean"
(invoke-unit (compound-unit (import) (export)
(link (((S3 : sig3) (S4 : sig4))
(unit (import) (export sig3 sig4)
(define a 3)
(define f add1)
(define g zero?)
(define (b t) (if t 3 0))))
(() unit4 S3 S4)))))
(test-contract-error "(unit unit5)" "d" "not a symbol"
(invoke-unit unit5))
(define-unit unit6
(import)
(export sig1)
(define-unit unit6-1
(import)
(export sig1)
(define x 3))
(define-values/invoke-unit unit6-1
(import)
(export sig1)))
(invoke-unit unit6)
(define-signature sig6
((contracted [x boolean?])))
(define-unit unit7
(import)
(export sig6)
(define-unit unit7-1
(import)
(export sig1)
(define x 3))
(define-values/invoke-unit unit7-1
(import)
(export sig1)))
(test-contract-error "(unit unit7)" "x" "not a boolean"
(invoke-unit unit7))
(define-unit unit8
(import)
(export)
(define-unit unit8-1
(import)
(export sig2)
(define f values))
(define-values/invoke-unit unit8-1
(import)
(export sig2))
(f #t))
(test-contract-error "(unit unit8)" "f" "not a number"
(invoke-unit unit8))
(define-unit unit9
(import)
(export)
(define-unit unit9-1
(import)
(export sig2)
(define f zero?))
(define-values/invoke-unit unit9-1
(import)
(export sig2))
(f 3))
(test-contract-error "(unit unit9-1)" "f" "not a number"
(invoke-unit unit9))
(define-values/invoke-unit
(unit
(import) (export sig2)
(define f values))
(import)
(export sig2))
(test-contract-error top-level "f" "not a number"
(f #t))
(define-unit unit10
(import sig1 sig2) (export)
(if (zero? x)
(f 3)
(f #t)))
(let ()
(define x 0)
(define f (lambda (x) #t))
(test-contract-error "(unit u)" "f" "not a number"
(invoke-unit unit10 (import sig1 sig2))))
(let ()
(define x 1)
(define f values)
(test-contract-error "(unit unit10)" "f" "not a number"
(invoke-unit unit10 (import sig1 sig2))))
;; testing that contracts from extended signatures are checked properly
(define-unit unit11
(import) (export sig3)
(define (f n) #t)
(define (g n) 3))
(let ()
(define-values/invoke-unit unit11
(import)
(export sig3))
(test-contract-error "(unit unit11)" "f" "not a number"
(f 3))
(test-contract-error top-level "f" "not a number"
(f #t)))
;; unit/new-import-export tests
(define-signature sig7 (f))
(define-signature sig8 ((contracted [f (-> number? number?)])))
(define-signature sig9 ((contracted [f (-> number? number?)])))
;; All units that play nicely
(define-unit unit12
(import sig7)
(export)
(f 3))
(define-unit unit13
(import sig8)
(export)
(f 3))
(define-unit unit14
(import)
(export sig7)
(define f (λ (n) 3)))
(define-unit unit15
(import)
(export sig8)
(define f (λ (n) 3)))
;; All units that don't play nicely (or won't after converted)
(define-unit unit16
(import sig7)
(export)
(f #t))
(define-unit unit17
(import sig8)
(export)
(f #t))
(define-unit unit18
(import)
(export sig7)
(define f (λ (n) #t)))
(define-unit unit19
(import)
(export sig8)
(define f (λ (n) #t)))
;; Converting units without internal contract violations
;; uncontracted import -> contracted import
(define-unit/new-import-export unit20
(import sig8)
(export)
(() unit12 sig7))
(let ()
(define-compound-unit unit21
(import)
(export)
(link [((S : sig8)) unit15]
[() unit20 S]))
(invoke-unit unit21))
(let ()
(define-compound-unit unit22
(import)
(export)
(link [((S : sig8)) unit19]
[() unit20 S]))
(test-contract-error "(unit unit19)" "f" "not a number"
(invoke-unit unit22)))
;; contracted import -> uncontracted import
(define-unit/new-import-export unit23
(import sig7)
(export)
(() unit13 sig8))
(let ()
(define-compound-unit unit24
(import)
(export)
(link [((S : sig7)) unit14]
[() unit23 S]))
(invoke-unit unit24))
(let ()
(define-compound-unit unit25
(import)
(export)
(link [((S : sig7)) unit18]
[() unit23 S]))
(test-contract-error "(unit unit23)" "f" "not a number"
(invoke-unit unit25)))
;; contracted import -> contracted import
(define-unit/new-import-export unit26
(import sig9)
(export)
(() unit13 sig8))
(let ()
(define-unit unit27-1
(import)
(export sig9)
(define (f n) 3))
(define-compound-unit unit27-2
(import)
(export)
(link [((S : sig9)) unit27-1]
[() unit26 S]))
(invoke-unit unit27-2))
(let ()
(define-unit unit28-1
(import)
(export sig9)
(define (f n) #f))
(define-compound-unit unit28-2
(import)
(export)
(link [((S : sig9)) unit28-1]
[() unit26 S]))
(test-contract-error "(unit unit28-1)" "f" "not a number"
(invoke-unit unit28-2)))
;; uncontracted export -> contracted export
(define-unit/new-import-export unit29
(import)
(export sig8)
((sig7) unit14))
(let ()
(define-compound-unit unit30
(import)
(export)
(link [((S : sig8)) unit29]
[() unit13 S]))
(invoke-unit unit30))
(let ()
(define-compound-unit unit31
(import)
(export)
(link [((S : sig8)) unit29]
[() unit17 S]))
(test-contract-error "(unit unit17)" "f" "not a number"
(invoke-unit unit31)))
;; contracted export -> uncontracted export
(define-unit/new-import-export unit32
(import)
(export sig7)
((sig8) unit15))
(let ()
(define-compound-unit unit33
(import)
(export)
(link [((S : sig7)) unit32]
[() unit14 S]))
(invoke-unit unit33))
(let ()
(define-compound-unit unit34
(import)
(export)
(link [((S : sig7)) unit32]
[() unit16 S]))
(test-contract-error "(unit unit32)" "f" "not a number"
(invoke-unit unit34)))
;; contracted export -> contracted export
(define-unit/new-import-export unit35
(import)
(export sig9)
((sig8) unit15))
(let ()
(define-unit unit36-1
(import sig9)
(export)
(f 3))
(define-compound-unit unit36-2
(import)
(export)
(link [((S : sig9)) unit35]
[() unit36-1 S]))
(invoke-unit unit36-2))
(let ()
(define-unit unit37-1
(import sig9)
(export)
(f #f))
(define-compound-unit unit37-2
(import)
(export)
(link [((S : sig9)) unit35]
[() unit37-1 S]))
(test-contract-error "(unit unit37-1)" "f" "not a number"
(invoke-unit unit37-2)))
;; Converting units with internal contract violations
;; uncontracted import -> contracted import
(define-unit/new-import-export unit38
(import sig8)
(export)
(() unit16 sig7))
(let ()
(define-compound-unit unit39
(import)
(export)
(link [((S : sig8)) unit15]
[() unit38 S]))
(test-contract-error "(unit unit38)" "f" "not a number"
(invoke-unit unit39)))
(let ()
(define-compound-unit unit40
(import)
(export)
(link [((S : sig8)) unit19]
[() unit38 S]))
(test-contract-error "(unit unit38)" "f" "not a number"
(invoke-unit unit40)))
;; contracted import -> uncontracted import
(define-unit/new-import-export unit41
(import sig7)
(export)
(() unit17 sig8))
(let ()
(define-compound-unit unit42
(import)
(export)
(link [((S : sig7)) unit14]
[() unit41 S]))
(test-contract-error "(unit unit17)" "f" "not a number"
(invoke-unit unit42)))
(let ()
(define-compound-unit unit43
(import)
(export)
(link [((S : sig7)) unit18]
[() unit41 S]))
(test-contract-error "(unit unit17)" "f" "not a number"
(invoke-unit unit43)))
;; contracted import -> contracted import
(define-unit/new-import-export unit44
(import sig9)
(export)
(() unit17 sig8))
(let ()
(define-unit unit45-1
(import)
(export sig9)
(define (f n) 3))
(define-compound-unit unit45-2
(import)
(export)
(link [((S : sig9)) unit45-1]
[() unit44 S]))
(test-contract-error "(unit unit17)" "f" "not a number"
(invoke-unit unit45-2)))
(let ()
(define-unit unit46-1
(import)
(export sig9)
(define (f n) #t))
(define-compound-unit unit46-2
(import)
(export)
(link [((S : sig9)) unit46-1]
[() unit44 S]))
(test-contract-error "(unit unit17)" "f" "not a number"
(invoke-unit unit46-2)))
;; uncontracted export -> contracted export
(define-unit/new-import-export unit47
(import)
(export sig8)
((sig7) unit18))
(let ()
(define-compound-unit unit48
(import)
(export)
(link [((S : sig8)) unit47]
[() unit13 S]))
(test-contract-error "(unit unit47)" "f" "not a number"
(invoke-unit unit48)))
(let ()
(define-compound-unit unit49
(import)
(export)
(link [((S : sig8)) unit47]
[() unit17 S]))
(test-contract-error "(unit unit17)" "f" "not a number"
(invoke-unit unit49)))
;; contracted import -> uncontracted import
(define-unit/new-import-export unit50
(import)
(export sig7)
((sig8) unit19))
(let ()
(define-compound-unit unit51
(import)
(export)
(link [((S : sig7)) unit50]
[() unit12 S]))
(test-contract-error "(unit unit19)" "f" "not a number"
(invoke-unit unit51)))
(let ()
(define-compound-unit unit52
(import)
(export)
(link [((S : sig7)) unit50]
[() unit16 S]))
(test-contract-error "(unit unit50)" "f" "not a number"
(invoke-unit unit52)))
;; contracted export -> contracted export
(define-unit/new-import-export unit53
(import)
(export sig9)
((sig8) unit19))
(let ()
(define-unit unit54-1
(import sig9)
(export)
(f 3))
(define-compound-unit unit54-2
(import)
(export)
(link [((S : sig9)) unit53]
[() unit54-1 S]))
(test-contract-error "(unit unit19)" "f" "not a number"
(invoke-unit unit54-2)))
(let ()
(define-unit unit55-1
(import sig9)
(export)
(f #t))
(define-compound-unit unit55-2
(import)
(export)
(link [((S : sig9)) unit53]
[() unit55-1 S]))
(test-contract-error "(unit unit55-1)" "f" "not a number"
(invoke-unit unit55-2)))
(module m1 scheme
(define-signature foo^ (x))
(define-signature bar^ (y))
(provide foo^ bar^)
(define-unit U@
(import foo^)
(export bar^)
(define (y s)
(if (eq? s 'bork)
3
(string-append (symbol->string s) " " (if (x 3) "was true on 3" "was not true on 3")))))
(provide/contract [U@ (unit/c (import (foo^ [x (-> number? boolean?)]))
(export (bar^ [y (-> symbol? string?)])))]))
(module m2 scheme
(require 'm1)
(define x zero?)
(define-values/invoke-unit U@
(import foo^)
(export bar^))
(define (z)
(y 'a))
(define (w)
(y "foo"))
(define (v)
(y 'bork))
(provide z w v))
(require (prefix-in m2: 'm2))
(m2:z)
(test-contract-error "m2" "U@" "not a symbol" (m2:w))
(test-contract-error "m1" "U@" "not a string" (m2:v))
(test-syntax-error "no y in sig1"
(unit/c (import (sig1 [y number?]))
(export)))
(test-syntax-error "two xs for sig1"
(unit/c (import)
(export (sig1 [x string?] [x number?]))))
(test-syntax-error "no sig called faux^, so import description matching fails"
(unit/c (import faux^) (export)))
(test-contract-error "(definition bad-export@)" "bad-export@" "unit must export sig1"
(let ()
(define/contract bad-export@
(unit/c (import) (export sig1))
(unit (import) (export)))
bad-export@))
(test-contract-error "(definition bad-import@)" "bad-import@" "contract must import sig1"
(let ()
(define/contract bad-import@
(unit/c (import) (export))
(unit (import sig1) (export) (+ x 1)))
bad-import@))
(test-contract-error "(definition not-a-unit)" "not-a-unit" "not a unit"
(let ()
(define/contract not-a-unit
(unit/c (import) (export))
3)
not-a-unit))
;; Adding a test to make sure that contracts can refer
;; to other parts of the signature.
(module m3 scheme
(define-signature toy-factory^
((contracted
[build-toys (-> integer? (listof toy?))]
[repaint (-> toy? symbol? toy?)]
[toy? (-> any/c boolean?)]
[toy-color (-> toy? symbol?)])))
(define-unit simple-factory@
(import)
(export toy-factory^)
(define-struct toy (color) #:transparent)
(define (build-toys n)
(for/list ([i (in-range n)])
(make-toy 'blue)))
(define (repaint t col)
(make-toy col)))
(provide toy-factory^ simple-factory@))
(module m4 scheme
(define-signature foo^ (x? (contracted [f (-> x? boolean?)])))
(define-unit U@
(import)
(export foo^)
(define (x? x) #f)
(define (f x) (x? x)))
(define-values/invoke-unit/infer U@)
(provide f x?))
(require (prefix-in m4: 'm4))
(test-contract-error "m4" "f" "not an x"
(m4:f 3))
(require (prefix-in m3: 'm3))
(test-contract-error top-level "build-toys" "not a integer"
(let ()
(define-values/invoke-unit/infer m3:simple-factory@)
(build-toys #f)))
(module m5 scheme
(define-signature foo^ (f (contracted [x? (-> any/c boolean?)])))
(define-unit U@
(import)
(export foo^)
(define (x? n) (zero? n))
(define (f x) (x? x)))
(provide foo^)
(provide/contract
[U@
(unit/c (import)
(export (foo^ [f (-> x? boolean?)])))]))
(require (prefix-in m5: 'm5))
(define-values/invoke-unit m5:U@
(import)
(export (prefix m5: m5:foo^)))
(m5:f 0)
(test-contract-error top-level "U@" "not an x"
(m5:f 3))
(let ()
(define-signature foo^ (x? f))
(define-signature bar^ ((contracted [x? (-> number? boolean?)]
[f (-> x? number?)])))
(define-unit U@
(import)
(export bar^)
(define x? zero?)
(define f values))
(define-unit/new-import-export V@
(import)
(export bar^)
((bar^) U@))
(define-values/invoke-unit/infer V@)
(f 0)
(test-contract-error top-level "f" "not an x"
(f 3)))
(let ()
(define-signature foo^ ((contracted [x? (-> number? boolean?)]
[f (-> x? number?)])))
(define-signature bar^ (f (contracted [x? (-> any/c boolean?)])))
(define-unit U@
(import)
(export foo^)
(define x? zero?)
(define f values))
(define-unit/new-import-export V@
(import)
(export bar^)
((foo^) U@))
(define-values/invoke-unit/infer V@)
(f 0)
(test-contract-error "(unit V@)" "f" "not an x"
(f 3)))
(let ()
(define-signature foo^ (x y))
(define-unit/contract U@
(import)
(export (foo^ [x (-> number? number?)]))
(define (x n) (zero? n))
(define y 4))
(define-unit V@
(import foo^)
(export)
(x 4))
(define-compound-unit/infer W@
(import) (export) (link U@ V@))
(define-values/invoke-unit/infer U@)
y
(test-contract-error top-level "U@" "not a number"
(x #t))
(test-contract-error "(unit U@)" "U@" "not a number"
(x 3))
(test-contract-error "(unit U@)" "U@" "not a number"
(invoke-unit W@)))
(let ()
(define-signature foo^ (x? f))
(define-unit/contract U@
(import)
(export (foo^ [f (-> x? number?)]))
(define (x? n) (or (= n 3)
(zero? n)))
(define (f n) (if (= n 3) #t n)))
(define-unit V@
(import foo^)
(export)
(test-contract-error top-level "U@" "not an x"
(f 2))
(test-contract-error "(unit U@)" "U@" "not an number"
(f 3)))
(define-compound-unit/infer W@
(import) (export) (link U@ V@))
(define-values/invoke-unit/infer U@)
(test-contract-error top-level "U@" "not an x"
(f 4))
(test-contract-error "(unit U@)" "U@" "not a number"
(f 3))
(invoke-unit W@))
(let ()
(define-signature foo^
((contracted
[x? (-> number? boolean?)]
[f (-> x? number?)])))
(define-unit/contract foo@
(import)
(export (foo^ [x? (-> any/c boolean?)]))
(define (x? n) (zero? n))
(define (f x) 3))
(define-values/invoke-unit/infer foo@)
(f 0)
(test-contract-error top-level "f" "not an x"
(f 4))
;; This is a weird one. The definition for foo@ has two conflicting
;; contracts. Who gets blamed? Still the top-level, since foo@ can't
;; get blamed for breaking its own contract. In theory you could say
;; that perhaps the top-level shouldn't be blamed, and that it should
;; just be an "overriding" contract, but a) that won't really work and
;; b) what about other units that might link with foo@, that expect
;; the stronger contract?
(test-contract-error top-level "x?" "not a number"
(f #t)))
(let ()
(define-signature student^
((struct/ctc student ([name string?] [id number?]))))
(define-unit student@
(import)
(export student^)
(define-struct student (name id)))
(define-values/invoke-unit/infer student@)
(make-student "foo" 3)
(test-contract-error top-level "make-student" "not a string"
(make-student 4 3))
(test-contract-error top-level "student-id" "not a student"
(student-id 'a)))
;; Test that prefixing doesn't cause issues.
(let ()
(define-signature t^
((contracted (t? (any/c . -> . boolean?))
(make-t (-> t?)))))
(define-unit t@
(import)
(export t^)
(define-struct t ()))
(define-signature s^ (new-make-t))
(define-unit s@
(import (prefix pre: t^))
(export s^)
(define new-make-t pre:make-t))
(define c@ (compound-unit (import)
(export S)
(link [((T : t^)) t@]
[((S : s^)) s@ T])))
(define-values/invoke-unit c@ (import) (export s^))
(new-make-t))