Skip to content

Commit e2552fa

Browse files
committed
small improvements to random tester for lexers
1) try to shrink the latest-line as a counterexample 2) print the original error message in a here string 3) tidy up the code a little bit
1 parent 907cfde commit e2552fa

File tree

1 file changed

+128
-52
lines changed

1 file changed

+128
-52
lines changed

syntax-color-lib/syntax-color/lexer-contract.rkt

Lines changed: 128 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
(or/c exact-positive-integer? #f) (or/c exact-positive-integer? #f)
1212
(or/c exact-positive-integer? #f) (or/c exact-positive-integer? #f)
1313
void?)]))
14+
(module+ test (require rackunit))
1415

1516
(struct dont-stop (val) #:transparent)
1617

@@ -81,64 +82,139 @@
8182
" lexer: ~e\n"
8283
" pseudo-random state: ~s\n"
8384
" latest input string: ~s\n"
84-
" error message: ~s")
85+
" ~a\n"
86+
" error message: ~a")
8587
lexer
8688
initial-state
8789
latest-input-string
88-
(exn-message exn))
90+
(try-to-shrink 3ary-lexer latest-input-string)
91+
(format-as-here-string (exn-message exn)))
8992
(exn-continuation-marks exn))))])
9093
(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)))
13295
(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"))
142218

143219
(define (end/c start type)
144220
(cond

0 commit comments

Comments
 (0)