|
11 | 11 | (or/c exact-positive-integer? #f) (or/c exact-positive-integer? #f)
|
12 | 12 | (or/c exact-positive-integer? #f) (or/c exact-positive-integer? #f)
|
13 | 13 | void?)]))
|
| 14 | +(module+ test (require rackunit)) |
14 | 15 |
|
15 | 16 | (struct dont-stop (val) #:transparent)
|
16 | 17 |
|
|
81 | 82 | " lexer: ~e\n"
|
82 | 83 | " pseudo-random state: ~s\n"
|
83 | 84 | " latest input string: ~s\n"
|
84 |
| - " error message: ~s") |
| 85 | + " ~a\n" |
| 86 | + " error message: ~a") |
85 | 87 | lexer
|
86 | 88 | initial-state
|
87 | 89 | latest-input-string
|
88 |
| - (exn-message exn)) |
| 90 | + (try-to-shrink 3ary-lexer latest-input-string) |
| 91 | + (format-as-here-string (exn-message exn))) |
89 | 92 | (exn-continuation-marks exn))))])
|
90 | 93 | (for ([x (in-range 10)])
|
91 |
| - (define size (random 100)) |
92 |
| - |
93 |
| - (define opens '()) |
94 |
| - (define (update-opens c) |
95 |
| - (define (update-open c) (set! opens (cons c opens))) |
96 |
| - (case c |
97 |
| - [(#\") (update-open #\")] |
98 |
| - [(#\|) (update-open #\|)] |
99 |
| - [(#\() (update-open #\))] |
100 |
| - [(#\[) (update-open #\])] |
101 |
| - [(#\{) (update-open #\})]) |
102 |
| - c) |
103 |
| - |
104 |
| - (define (quash-backslash-r c) |
105 |
| - ;; it isn't clear the spec is right in |
106 |
| - ;; the case of \r\n combinations, so we |
107 |
| - ;; punt for now |
108 |
| - (if (equal? c #\return) #\newline c)) |
109 |
| - |
110 |
| - (define (char-at-random) |
111 |
| - (update-opens |
112 |
| - (quash-backslash-r |
113 |
| - (case (random 3) |
114 |
| - [(0) |
115 |
| - (define s " ()@{}\"λΣ\0|") |
116 |
| - (string-ref s (random (string-length s)))] |
117 |
| - [(1 2) |
118 |
| - (integer->char (random 255))])))) |
119 |
| - |
120 |
| - (define (pick-a-char) |
121 |
| - (cond |
122 |
| - [(null? opens) |
123 |
| - (char-at-random)] |
124 |
| - [else |
125 |
| - (case (random 4) |
126 |
| - [(0) |
127 |
| - (begin0 (car opens) |
128 |
| - (set! opens (cdr opens)))] |
129 |
| - [else (char-at-random)])])) |
130 |
| - |
131 |
| - (define s (build-string size (λ (c) (pick-a-char)))) |
| 94 | + (define s (make-a-string (random 100))) |
132 | 95 | (set! latest-input-string s)
|
133 |
| - (define in (open-input-string s)) |
134 |
| - (port-count-lines! in) |
135 |
| - (let loop ([mode #f][offset 0]) |
136 |
| - (define-values (txt type paren start end backup new-mode) |
137 |
| - (3ary-lexer in offset mode)) |
138 |
| - (cond |
139 |
| - [(equal? type 'eof) #t] |
140 |
| - [(< end size) (loop new-mode end)] |
141 |
| - [else #f]))))) |
| 96 | + (try 3ary-lexer s)))) |
| 97 | + |
| 98 | +;; make-a-string : natural -> string |
| 99 | +;; tries to make an interesting random string of the given size |
| 100 | +(define (make-a-string size) |
| 101 | + (define opens '()) |
| 102 | + (define (update-opens c) |
| 103 | + (define (update-open c) (set! opens (cons c opens))) |
| 104 | + (case c |
| 105 | + [(#\") (update-open #\")] |
| 106 | + [(#\|) (update-open #\|)] |
| 107 | + [(#\() (update-open #\))] |
| 108 | + [(#\[) (update-open #\])] |
| 109 | + [(#\{) (update-open #\})]) |
| 110 | + c) |
| 111 | + |
| 112 | + (define (quash-backslash-r c) |
| 113 | + ;; it isn't clear the spec is right in |
| 114 | + ;; the case of \r\n combinations, so we |
| 115 | + ;; punt for now |
| 116 | + (if (equal? c #\return) #\newline c)) |
| 117 | + |
| 118 | + (define (char-at-random) |
| 119 | + (update-opens |
| 120 | + (quash-backslash-r |
| 121 | + (case (random 3) |
| 122 | + [(0) |
| 123 | + (define s " ()@{}\"λΣ\0|") |
| 124 | + (string-ref s (random (string-length s)))] |
| 125 | + [(1 2) |
| 126 | + (integer->char (random 255))])))) |
| 127 | + |
| 128 | + (define (pick-a-char) |
| 129 | + (cond |
| 130 | + [(null? opens) |
| 131 | + (char-at-random)] |
| 132 | + [else |
| 133 | + (case (random 4) |
| 134 | + [(0) |
| 135 | + (begin0 (car opens) |
| 136 | + (set! opens (cdr opens)))] |
| 137 | + [else (char-at-random)])])) |
| 138 | + |
| 139 | + (build-string size (λ (c) (pick-a-char)))) |
| 140 | + |
| 141 | +;; try-to-shrink : lexer string -> string |
| 142 | +;; tries to shrink the counterexample, returning a |
| 143 | +;; string to include in the error message |
| 144 | +(define (try-to-shrink 3ary-lexer s) |
| 145 | + (define failed? |
| 146 | + (with-handlers ([exn:fail? (λ (x) #t)]) |
| 147 | + (try 3ary-lexer s))) |
| 148 | + (cond |
| 149 | + [failed? |
| 150 | + (define shrunk (shrink 3ary-lexer s)) |
| 151 | + (cond |
| 152 | + [shrunk |
| 153 | + (format "shrunk to: ~s" shrunk)] |
| 154 | + [else |
| 155 | + "could not shrink, but it did reproduce"])] |
| 156 | + [else |
| 157 | + "could not reproduce with just the latest input string, so didn't shrink"])) |
| 158 | + |
| 159 | +;; shrink : lexer string -> string or #f |
| 160 | +;; tries to shrink the counterexample s, returns the smaller one |
| 161 | +;; or #f if a shorter one could not be found |
| 162 | +(define (shrink 3ary-lexer s) |
| 163 | + (let loop ([s s]) |
| 164 | + (define failed? |
| 165 | + (with-handlers ([exn:fail? (λ (x) #t)]) |
| 166 | + (try 3ary-lexer s))) |
| 167 | + (cond |
| 168 | + [failed? |
| 169 | + (or (for/or ([candidate (in-list (get-shrink-candidates s))]) |
| 170 | + (loop candidate)) |
| 171 | + s)] |
| 172 | + [else #f]))) |
| 173 | + |
| 174 | +;; get-shrink-candidates : string -> (listof string) |
| 175 | +;; returns a list of shorter strings to try to see if they also fail |
| 176 | +(define (get-shrink-candidates s) |
| 177 | + (append |
| 178 | + (for/list ([i (in-range (string-length s))]) |
| 179 | + (string-append (substring s 0 i) |
| 180 | + (substring s (+ i 1) (string-length s)))) |
| 181 | + (for/list ([i (in-range (string-length s))] |
| 182 | + #:unless (equal? (string-ref s i) #\a)) |
| 183 | + (string-append (substring s 0 i) |
| 184 | + "a" |
| 185 | + (substring s (+ i 1) (string-length s)))))) |
| 186 | + |
| 187 | +;; try : lexer string -> boolean? |
| 188 | +;; runs `3ary-lexer` on `s` to see if it fails |
| 189 | +(define (try 3ary-lexer s) |
| 190 | + (define size (string-length s)) |
| 191 | + (define in (open-input-string s)) |
| 192 | + (port-count-lines! in) |
| 193 | + (let loop ([mode #f][offset 0]) |
| 194 | + (define-values (txt type paren start end backup new-mode) |
| 195 | + (3ary-lexer in offset mode)) |
| 196 | + (cond |
| 197 | + [(equal? type 'eof) #t] |
| 198 | + [(< end size) (loop new-mode end)] |
| 199 | + [else #f]))) |
| 200 | + |
| 201 | +(define (format-as-here-string s) |
| 202 | + (unless (regexp-match? #rx"\n$" s) (set! s (string-append s "\n"))) |
| 203 | + (let loop ([n 0]) |
| 204 | + (define terminator (if (= n 0) "--" (format "--~a" n))) |
| 205 | + (cond |
| 206 | + [(regexp-match? (string-append "\n" terminator "\n") s) |
| 207 | + (loop (+ n 1))] |
| 208 | + [else |
| 209 | + (string-append |
| 210 | + "#<<" terminator "\n" |
| 211 | + s |
| 212 | + terminator "\n")]))) |
| 213 | +(module+ test |
| 214 | + (check-equal? (format-as-here-string "abc") |
| 215 | + "#<<--\nabc\n--\n") |
| 216 | + (check-equal? (format-as-here-string "abc\n--\ndef") |
| 217 | + "#<<--1\nabc\n--\ndef\n--1\n")) |
142 | 218 |
|
143 | 219 | (define (end/c start type)
|
144 | 220 | (cond
|
|
0 commit comments