-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun-tests.rkt
More file actions
215 lines (175 loc) · 7.68 KB
/
run-tests.rkt
File metadata and controls
215 lines (175 loc) · 7.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#lang racket
(provide json-summary run-test-specs-from-s-exp
serialize-test-suite run-tests results-summary simple-summary [struct-out Result])
(require racket/set)
(struct Result (name timeout? expected-out actual-out expected-err actual-err)
#:transparent)
;; (TestSpec string string string string)
(struct TestSpec (program-name program-src output error) #:transparent)
(define INTERP-TIMEOUT-SECONDS 3)
;; run-for-n-seconds :: (-> (pairof string string)) number ->
;; (pairof string string) U #f
(define (run-for-n-seconds thnk n)
(define current (current-thread))
(define (wrapped-thunk)
(define result (thnk))
(thread-send current result))
(define handle (thread wrapped-thunk))
(define receive (thread-receive-evt))
(define got-result (sync/timeout n receive))
(define result (if got-result (thread-receive) #f))
(when (not got-result) (break-thread handle))
result)
(define (clean s) (regexp-replace* "\r" s ""))
;; run-test-spec : (string port -> (pairof string string)) TestSpec -> Result
(define (run-test-spec interp test-spec)
(define interp-thunk
(λ ()
(define srcport (open-input-string (TestSpec-program-src test-spec)))
(define result (interp (TestSpec-program-name test-spec) srcport))
(close-input-port srcport)
result))
(define interp-output
(run-for-n-seconds interp-thunk INTERP-TIMEOUT-SECONDS))
;; Check for a false return from run-for-n-seconds, which indicates
;; a timeout happened
(define-values (stdout stderr)
(cond [(false? interp-output) (values "" "")]
[(cons? interp-output)
(values (car interp-output) (cdr interp-output))]))
(define timed-out? (not interp-output))
(Result (TestSpec-program-name test-spec)
timed-out?
(TestSpec-output test-spec)
stdout
(TestSpec-error test-spec)
stderr))
;; path->test-spec : some-system-path -> TestSpec
(define (path->test-spec path)
(define strpath (some-system-path->string path))
(define expected-path (string-append strpath ".expected"))
(define err-path (string-append strpath ".error"))
(define (contents-or-empty f)
(define p (if (file-exists? f) (open-input-file f) (open-input-string "")))
(define contents (port->string p))
(close-input-port p)
(clean contents))
(define expected-out (contents-or-empty expected-path))
(define expected-err (contents-or-empty err-path))
(define src-input (open-input-file (some-system-path->string path)))
(define src (port->string src-input))
(close-input-port src-input)
(TestSpec strpath src expected-out expected-err))
(define (test-spec->s-exp test-spec)
;; we ditch path information
(define-values (_ name __) (split-path (TestSpec-program-name test-spec)))
(list (path->string name)
(TestSpec-program-src test-spec)
(TestSpec-output test-spec)
(TestSpec-error test-spec)))
(define (s-exp->test-spec s-exp)
(TestSpec (first s-exp) (second s-exp) (third s-exp) (fourth s-exp)))
(define (test-specs->racket-str test-specs)
(define out (open-output-string))
(write (map test-spec->s-exp test-specs) out)
(get-output-string out))
(define (serialize-test-specs test-specs)
(define template "#lang racket/base\n\n(provide TESTSPECS)\n\n(define TESTSPECS '~a)\n")
(format template (test-specs->racket-str test-specs)))
(define (serialize-test-suite dirname)
(serialize-test-specs (get-test-specs dirname)))
(define (deserialize-test-suite s-exps)
(map s-exp->test-spec s-exps))
;; get-test-specs : path -> (listof test-spec)
(define (get-test-specs dirname)
(define EXTENSION ".py")
(define (directory-list-with-name dirname)
(map (λ (p) (path->complete-path p dirname))
(directory-list (some-system-path->string dirname))))
(define (syspath-dir-exists? dirname)
(directory-exists? (some-system-path->string dirname)))
(define (parseltongue-paths dirname)
(define paths (directory-list-with-name dirname))
(define (plt? path)
(define strpath (some-system-path->string path))
(and
(not (directory-exists? (some-system-path->string path)))
(equal? EXTENSION (substring strpath (- (string-length strpath)
(string-length EXTENSION))))))
(append* (cons (filter plt? paths)
(map parseltongue-paths (filter syspath-dir-exists? paths)))))
(when (not (directory-exists? dirname))
(error (format "Directory not found: ~a" dirname)))
(for/list ([path (parseltongue-paths (path->complete-path dirname))])
(path->test-spec path)))
(define (run-test-specs-from-s-exp interp s-exp)
(define specs (deserialize-test-suite s-exp))
(for/list ([spec specs])
(run-test-spec interp spec)))
;; run-tests : (string port -> (pairof string string)) string ->
;; (listof Result)
#|
interp - An interpreter that accepts a filename and a port to read a
source file from, and yields the "stdout" and "stderr" of
the evaluator
dir-name - A root directory full of tests to run that looks like:
dir-name/
test1.psl
test1.psl.expected
test1.psl.error
test3.psl
test3.psl.expected
subdir/
test2.psl
test2.psl.expected
subdir2/
subdir3/
test4.psl
test4.psl.error
run-tests will use interp to run test1.psl, test2.psl, etc,
and check the standard out/error of the interpreter against
the corresponding .expected and .error files. If a
.expected or .error file is omitted, it is assumed to be the
empty string.
|#
(define (run-tests interp dirname)
(define specs (get-test-specs dirname))
(for/list ([spec specs])
(run-test-spec interp spec)))
(define (successful-result r)
(and (Result? r)
(equal? (Result-actual-out r) (Result-expected-out r))
(equal? (Result-actual-err r) (Result-expected-err r))))
(define (partition-results results)
(partition successful-result results))
(define (results-summary results)
(define report-string (open-output-string "report"))
(define (report str . args)
(display (apply format (cons str args)) report-string))
(define-values (passed failed) (partition-results results))
(report "~a tests succeeded.\n" (length passed))
(report "~a tests failed.\n" (length failed))
(when (not (empty? failed)) (report "== Output of failed tests ==\n"))
(for ((f failed))
(define (report-to s . args)
(when (not (Result-timeout? f)) (apply report (cons s args))))
(report "=====================================================\n")
(report "= Results for ~a =\n" (Result-name f))
(report "=====================================================\n")
(when (Result-timeout? f) (report-to "TEST TIMED OUT"))
(report "=== Expected stdout ===\n~a\n" (Result-expected-out f))
(report-to "=== Actual stdout ===\n~a\n" (Result-actual-out f))
(report "=== Expected stderr ===\n~a\n" (Result-expected-err f))
(report-to "=== Actual stderr ===\n~a\n" (Result-actual-err f)))
(get-output-string report-string))
(define (simple-summary results)
(define report-string (open-output-string "report"))
(define (report str . args)
(display (apply format (cons str args)) report-string))
(define-values (passed failed) (partition-results results))
(for/fold ((str "Differences in:\n"))
((r failed))
(string-append str (format "~a\n" (Result-name r)))))
(define (json-summary results)
(for/hash ((result results))
(values (Result-name result) (successful-result result))))