Commit | Line | Data |
---|---|---|
ce4c9a6d AW |
1 | ;;;; -*- scheme -*- |
2 | ;;;; control.test --- test suite for delimited continuations | |
3 | ;;;; | |
55e26a49 | 4 | ;;;; Copyright (C) 2010, 2011, 2013 Free Software Foundation, Inc. |
ce4c9a6d AW |
5 | ;;;; |
6 | ;;;; This library is free software; you can redistribute it and/or | |
7 | ;;;; modify it under the terms of the GNU Lesser General Public | |
8 | ;;;; License as published by the Free Software Foundation; either | |
9 | ;;;; version 3 of the License, or (at your option) any later version. | |
10 | ;;;; | |
11 | ;;;; This library is distributed in the hope that it will be useful, | |
12 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | ;;;; Lesser General Public License for more details. | |
15 | ;;;; | |
16 | ;;;; You should have received a copy of the GNU Lesser General Public | |
17 | ;;;; License along with this library; if not, write to the Free Software | |
18 | ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | ||
20 | (define-module (test-suite test-control) | |
21 | #:use-module (ice-9 control) | |
8684029d | 22 | #:use-module (system vm vm) |
55e26a49 | 23 | #:use-module (srfi srfi-1) |
da7497e0 | 24 | #:use-module (srfi srfi-11) |
ce4c9a6d AW |
25 | #:use-module (test-suite lib)) |
26 | ||
27 | ||
da7fa082 AW |
28 | ;; For these, the compiler should be able to prove that "k" is not referenced, |
29 | ;; so it avoids reifying the continuation. Since that's a slightly different | |
30 | ;; codepath, we test them both. | |
f5fc7e57 | 31 | (with-test-prefix/c&e "escape-only continuations" |
ce4c9a6d AW |
32 | (pass-if "no values, normal exit" |
33 | (equal? '() | |
34 | (call-with-values | |
35 | (lambda () | |
8fc43b12 | 36 | (% (values) |
ce4c9a6d AW |
37 | (lambda (k . args) |
38 | (error "unexpected exit" args)))) | |
39 | list))) | |
da7fa082 AW |
40 | |
41 | (pass-if "no values, abnormal exit" | |
ce4c9a6d | 42 | (equal? '() |
8fc43b12 AW |
43 | (% (begin |
44 | (abort) | |
ce4c9a6d | 45 | (error "unexpected exit")) |
da7fa082 AW |
46 | (lambda (k . args) |
47 | args)))) | |
48 | ||
49 | (pass-if "single value, normal exit" | |
50 | (equal? '(foo) | |
51 | (call-with-values | |
52 | (lambda () | |
8fc43b12 | 53 | (% 'foo |
da7fa082 AW |
54 | (lambda (k . args) |
55 | (error "unexpected exit" args)))) | |
56 | list))) | |
57 | ||
58 | (pass-if "single value, abnormal exit" | |
59 | (equal? '(foo) | |
8fc43b12 AW |
60 | (% (begin |
61 | (abort 'foo) | |
da7fa082 AW |
62 | (error "unexpected exit")) |
63 | (lambda (k . args) | |
64 | args)))) | |
65 | ||
66 | (pass-if "multiple values, normal exit" | |
67 | (equal? '(foo bar baz) | |
68 | (call-with-values | |
69 | (lambda () | |
8fc43b12 | 70 | (% (values 'foo 'bar 'baz) |
da7fa082 AW |
71 | (lambda (k . args) |
72 | (error "unexpected exit" args)))) | |
73 | list))) | |
74 | ||
75 | (pass-if "multiple values, abnormal exit" | |
76 | (equal? '(foo bar baz) | |
8fc43b12 AW |
77 | (% (begin |
78 | (abort 'foo 'bar 'baz) | |
da7fa082 | 79 | (error "unexpected exit")) |
ce4c9a6d | 80 | (lambda (k . args) |
55e26a49 LC |
81 | args)))) |
82 | ||
83 | (pass-if-equal "call/ec" '(0 1 2) ; example from the manual | |
84 | (let ((prefix | |
85 | (lambda (x lst) | |
86 | (call/ec | |
87 | (lambda (return) | |
88 | (fold (lambda (element prefix) | |
89 | (if (equal? element x) | |
90 | (return (reverse prefix)) | |
91 | (cons element prefix))) | |
92 | '() | |
93 | lst)))))) | |
94 | (prefix 'a '(0 1 2 a 3 4 5)))) | |
95 | ||
96 | (pass-if-equal "let/ec" '(0 1 2) | |
97 | (let ((prefix | |
98 | (lambda (x lst) | |
99 | (let/ec return | |
100 | (fold (lambda (element prefix) | |
101 | (if (equal? element x) | |
102 | (return (reverse prefix)) | |
103 | (cons element prefix))) | |
104 | '() | |
105 | lst))))) | |
106 | (prefix 'a '(0 1 2 a 3 4 5))))) | |
ce4c9a6d | 107 | |
da7fa082 | 108 | ;;; And the case in which the compiler has to reify the continuation. |
f5fc7e57 | 109 | (with-test-prefix/c&e "reified continuations" |
da7fa082 AW |
110 | (pass-if "no values, normal exit" |
111 | (equal? '() | |
112 | (call-with-values | |
113 | (lambda () | |
8fc43b12 | 114 | (% (values) |
da7fa082 AW |
115 | (lambda (k . args) |
116 | (error "unexpected exit" k args)))) | |
117 | list))) | |
118 | ||
119 | (pass-if "no values, abnormal exit" | |
120 | (equal? '() | |
121 | (cdr | |
8fc43b12 AW |
122 | (% (begin |
123 | (abort) | |
da7fa082 AW |
124 | (error "unexpected exit")) |
125 | (lambda args | |
126 | args))))) | |
127 | ||
128 | (pass-if "single value, normal exit" | |
129 | (equal? '(foo) | |
130 | (call-with-values | |
131 | (lambda () | |
8fc43b12 | 132 | (% 'foo |
da7fa082 AW |
133 | (lambda (k . args) |
134 | (error "unexpected exit" k args)))) | |
135 | list))) | |
136 | ||
137 | (pass-if "single value, abnormal exit" | |
138 | (equal? '(foo) | |
139 | (cdr | |
8fc43b12 AW |
140 | (% (begin |
141 | (abort 'foo) | |
da7fa082 AW |
142 | (error "unexpected exit")) |
143 | (lambda args | |
144 | args))))) | |
145 | ||
146 | (pass-if "multiple values, normal exit" | |
147 | (equal? '(foo bar baz) | |
148 | (call-with-values | |
149 | (lambda () | |
8fc43b12 | 150 | (% (values 'foo 'bar 'baz) |
da7fa082 AW |
151 | (lambda (k . args) |
152 | (error "unexpected exit" k args)))) | |
153 | list))) | |
154 | ||
155 | (pass-if "multiple values, abnormal exit" | |
156 | (equal? '(foo bar baz) | |
157 | (cdr | |
8fc43b12 AW |
158 | (% (begin |
159 | (abort 'foo 'bar 'baz) | |
da7fa082 AW |
160 | (error "unexpected exit")) |
161 | (lambda args | |
f5fc7e57 AW |
162 | args))))) |
163 | ||
164 | (pass-if "reified pending call frames, instantiated elsewhere on the stack" | |
165 | (equal? 'foo | |
166 | ((call-with-prompt | |
167 | 'p0 | |
168 | (lambda () | |
169 | (identity ((abort-to-prompt 'p0) 'foo))) | |
170 | (lambda (c) c)) | |
171 | (lambda (x) x))))) | |
172 | ||
da7fa082 | 173 | |
da7497e0 | 174 | ;; The variants check different cases in the compiler. |
f5fc7e57 | 175 | (with-test-prefix/c&e "restarting partial continuations" |
da7497e0 | 176 | (pass-if "in side-effect position" |
8fc43b12 | 177 | (let ((k (% (begin (abort) 'foo) |
da7fa082 AW |
178 | (lambda (k) k)))) |
179 | (eq? (k) | |
da7497e0 AW |
180 | 'foo))) |
181 | ||
182 | (pass-if "passing values to side-effect abort" | |
8fc43b12 | 183 | (let ((k (% (begin (abort) 'foo) |
da7497e0 AW |
184 | (lambda (k) k)))) |
185 | (eq? (k 'qux 'baz 'hello) | |
186 | 'foo))) | |
187 | ||
188 | (pass-if "called for one value" | |
8fc43b12 | 189 | (let ((k (% (+ (abort) 3) |
da7497e0 AW |
190 | (lambda (k) k)))) |
191 | (eqv? (k 39) | |
192 | 42))) | |
193 | ||
194 | (pass-if "called for multiple values" | |
8fc43b12 | 195 | (let ((k (% (let-values (((a b . c) (abort))) |
da7497e0 AW |
196 | (list a b c)) |
197 | (lambda (k) k)))) | |
198 | (equal? (k 1 2 3 4) | |
199 | '(1 2 (3 4))))) | |
200 | ||
201 | (pass-if "in tail position" | |
8fc43b12 | 202 | (let ((k (% (abort) |
da7497e0 AW |
203 | (lambda (k) k)))) |
204 | (eq? (k 'xyzzy) | |
205 | 'xyzzy)))) | |
da7fa082 | 206 | |
9dadfa47 AW |
207 | ;; Here we test different cases for the `prompt'. |
208 | (with-test-prefix/c&e "prompt in different contexts" | |
209 | (pass-if "push, normal exit" | |
210 | (car (call-with-prompt | |
211 | 'foo | |
212 | (lambda () '(#t)) | |
213 | (lambda (k) '(#f))))) | |
214 | ||
215 | (pass-if "push, nonlocal exit" | |
216 | (car (call-with-prompt | |
217 | 'foo | |
218 | (lambda () (abort-to-prompt 'foo) '(#f)) | |
219 | (lambda (k) '(#t))))) | |
220 | ||
221 | (pass-if "push with RA, normal exit" | |
222 | (car (letrec ((test (lambda () | |
223 | (call-with-prompt | |
224 | 'foo | |
225 | (lambda () '(#t)) | |
226 | (lambda (k) '(#f)))))) | |
227 | (test)))) | |
228 | ||
229 | (pass-if "push with RA, nonlocal exit" | |
230 | (car (letrec ((test (lambda () | |
231 | (call-with-prompt | |
232 | 'foo | |
233 | (lambda () (abort-to-prompt 'foo) '(#f)) | |
234 | (lambda (k) '(#t)))))) | |
235 | (test)))) | |
236 | ||
237 | (pass-if "tail, normal exit" | |
238 | (call-with-prompt | |
239 | 'foo | |
240 | (lambda () #t) | |
241 | (lambda (k) #f))) | |
242 | ||
243 | (pass-if "tail, nonlocal exit" | |
244 | (call-with-prompt | |
245 | 'foo | |
246 | (lambda () (abort-to-prompt 'foo) #f) | |
247 | (lambda (k) #t))) | |
248 | ||
249 | (pass-if "tail with RA, normal exit" | |
250 | (letrec ((test (lambda () | |
251 | (call-with-prompt | |
252 | 'foo | |
253 | (lambda () #t) | |
254 | (lambda (k) #f))))) | |
255 | (test))) | |
256 | ||
257 | (pass-if "tail with RA, nonlocal exit" | |
258 | (letrec ((test (lambda () | |
259 | (call-with-prompt | |
260 | 'foo | |
261 | (lambda () (abort-to-prompt 'foo) #f) | |
262 | (lambda (k) #t))))) | |
263 | (test))) | |
264 | ||
265 | (pass-if "drop, normal exit" | |
266 | (begin | |
267 | (call-with-prompt | |
268 | 'foo | |
269 | (lambda () #f) | |
270 | (lambda (k) #f)) | |
271 | #t)) | |
272 | ||
273 | (pass-if "drop, nonlocal exit" | |
274 | (begin | |
275 | (call-with-prompt | |
276 | 'foo | |
277 | (lambda () (abort-to-prompt 'foo)) | |
278 | (lambda (k) #f)) | |
279 | #t)) | |
280 | ||
281 | (pass-if "drop with RA, normal exit" | |
282 | (begin | |
283 | (letrec ((test (lambda () | |
284 | (call-with-prompt | |
285 | 'foo | |
286 | (lambda () #f) | |
287 | (lambda (k) #f))))) | |
288 | (test)) | |
289 | #t)) | |
290 | ||
291 | (pass-if "drop with RA, nonlocal exit" | |
292 | (begin | |
293 | (letrec ((test (lambda () | |
294 | (call-with-prompt | |
295 | 'foo | |
296 | (lambda () (abort-to-prompt 'foo) #f) | |
297 | (lambda (k) #f))))) | |
298 | (test)) | |
299 | #t))) | |
300 | ||
301 | ||
da7fa082 AW |
302 | (define fl (make-fluid)) |
303 | (fluid-set! fl 0) | |
304 | ||
f5fc7e57 AW |
305 | ;; Not c&e as it assumes this block executes once. |
306 | ;; | |
da7fa082 AW |
307 | (with-test-prefix "suspend/resume with fluids" |
308 | (pass-if "normal" | |
8fc43b12 | 309 | (zero? (% (fluid-ref fl) |
da7fa082 AW |
310 | error))) |
311 | (pass-if "with-fluids normal" | |
8fc43b12 | 312 | (equal? (% (with-fluids ((fl (1+ (fluid-ref fl)))) |
da7fa082 AW |
313 | (fluid-ref fl)) |
314 | error) | |
315 | 1)) | |
316 | (pass-if "normal (post)" | |
317 | (zero? (fluid-ref fl))) | |
318 | (pass-if "with-fluids and fluid-set!" | |
8fc43b12 | 319 | (equal? (% (with-fluids ((fl (1+ (fluid-ref fl)))) |
da7fa082 AW |
320 | (fluid-set! fl (1+ (fluid-ref fl))) |
321 | (fluid-ref fl)) | |
322 | error) | |
323 | 2)) | |
324 | (pass-if "normal (post2)" | |
325 | (zero? (fluid-ref fl))) | |
326 | (pass-if "normal fluid-set!" | |
327 | (equal? (begin | |
328 | (fluid-set! fl (1+ (fluid-ref fl))) | |
329 | (fluid-ref fl)) | |
330 | 1)) | |
331 | (pass-if "reset fluid-set!" | |
332 | (equal? (begin | |
333 | (fluid-set! fl (1- (fluid-ref fl))) | |
334 | (fluid-ref fl)) | |
335 | 0)) | |
336 | ||
8fc43b12 AW |
337 | (let ((k (% (with-fluids ((fl (1+ (fluid-ref fl)))) |
338 | (abort) | |
da7fa082 AW |
339 | (fluid-ref fl)) |
340 | (lambda (k) k)))) | |
341 | (pass-if "pre" | |
342 | (equal? (fluid-ref fl) 0)) | |
343 | (pass-if "res" | |
344 | (equal? (k) 1)) | |
345 | (pass-if "post" | |
346 | (equal? (fluid-ref fl) 0)))) | |
adbdfd6d | 347 | |
f5fc7e57 | 348 | (with-test-prefix/c&e "rewinding prompts" |
adbdfd6d AW |
349 | (pass-if "nested prompts" |
350 | (let ((k (% 'a | |
351 | (% 'b | |
352 | (begin | |
8fc43b12 AW |
353 | (abort-to-prompt 'a) |
354 | (abort-to-prompt 'b #t)) | |
adbdfd6d AW |
355 | (lambda (k x) x)) |
356 | (lambda (k) k)))) | |
357 | (k)))) | |
9f074518 | 358 | |
f5fc7e57 | 359 | (with-test-prefix/c&e "abort to unknown prompt" |
9f074518 AW |
360 | (pass-if-exception "foo" '(misc-error . "^Abort to unknown prompt") |
361 | (abort-to-prompt 'does-not-exist))) | |
8684029d | 362 | |
f5fc7e57 | 363 | (with-test-prefix/c&e "the-vm" |
8684029d LC |
364 | |
365 | (pass-if "unwind changes VMs" | |
366 | (let ((new-vm (make-vm)) | |
367 | (prev-vm (the-vm)) | |
368 | (proc (lambda (x y) | |
369 | (expt x y))) | |
370 | (call (lambda (p x y) | |
371 | (p x y)))) | |
372 | (catch 'foo | |
373 | (lambda () | |
ea9f4f4b | 374 | (call-with-vm new-vm (lambda () (throw 'foo (the-vm))))) |
8684029d LC |
375 | (lambda (key vm) |
376 | (and (eq? key 'foo) | |
377 | (eq? vm new-vm) | |
70057f34 LC |
378 | (eq? (the-vm) prev-vm)))))) |
379 | ||
380 | (pass-if "stack overflow reinstates stack reserve" | |
381 | ;; In Guile <= 2.0.9, only the first overflow would be gracefully | |
382 | ;; handle; subsequent overflows would lead to an abort. See | |
383 | ;; <http://lists.gnu.org/archive/html/guile-user/2013-12/msg00017.html>. | |
384 | (letrec ((foo (lambda () (+ 1 (foo))))) | |
385 | (define (overflows?) | |
386 | (catch 'vm-error foo | |
387 | (lambda (key proc msg . rest) | |
388 | (and (eq? 'vm-run proc) | |
389 | (->bool (string-contains msg "overflow")))))) | |
390 | ||
391 | (and (overflows?) (overflows?) (overflows?))))) | |
18e444b4 AW |
392 | |
393 | ;; These tests from Oleg Kiselyov's delim-control-n.scm, available at | |
394 | ;; http://okmij.org/ftp/Scheme/delim-control-n.scm. Public domain. | |
395 | ;; | |
396 | (with-test-prefix "shift and reset" | |
397 | (pass-if (equal? | |
398 | 117 | |
399 | (+ 10 (reset (+ 2 (shift k (+ 100 (k (k 3))))))))) | |
400 | ||
401 | (pass-if (equal? | |
402 | 60 | |
403 | (* 10 (reset (* 2 (shift g (* 5 (shift f (+ (f 1) 1))))))))) | |
404 | ||
405 | (pass-if (equal? | |
406 | 121 | |
407 | (let ((f (lambda (x) (shift k (k (k x)))))) | |
408 | (+ 1 (reset (+ 10 (f 100))))))) | |
409 | ||
410 | (pass-if (equal? | |
411 | 'a | |
412 | (car (reset | |
413 | (let ((x (shift f | |
414 | (shift f1 (f1 (cons 'a (f '()))))))) | |
415 | (shift g x)))))) | |
416 | ||
417 | ;; Example by Olivier Danvy | |
418 | (pass-if (equal? | |
419 | '(1 2 3 4 5) | |
420 | (let () | |
421 | (define (traverse xs) | |
422 | (define (visit xs) | |
423 | (if (null? xs) | |
424 | '() | |
425 | (visit (shift* | |
426 | (lambda (k) | |
427 | (cons (car xs) (k (cdr xs)))))))) | |
428 | (reset* (lambda () (visit xs)))) | |
429 | (traverse '(1 2 3 4 5)))))) |