Simple test harness

This commit is contained in:
Tony Garnock-Jones 2018-04-27 23:53:31 +01:00
parent d6c0241c57
commit 290170f450
4 changed files with 531 additions and 235 deletions

View File

@ -10,7 +10,8 @@
bag-member?
in-bag
in-bag/count
set->bag)
set->bag
bag->set)
(require racket/set)
@ -55,3 +56,6 @@
(define (set->bag s [count 1])
(for/hash [(e (in-set s))]
(values e count)))
(define (bag->set b)
(list->set (hash-keys b)))

View File

@ -8,6 +8,7 @@
(struct-out observe)
dataspace?
dataspace-assertions ;; TODO: shouldn't be provided - needed by test.rkt
generate-id! ;; TODO: shouldn't be provided - inline syntax.rkt??
actor?
@ -25,6 +26,8 @@
field-handle-owner
field-handle-value
current-actor-crash-logger
current-actor
current-facet
in-script? ;; TODO: shouldn't be provided - inline syntax.rkt??
@ -169,6 +172,12 @@
(field-handle-owner f)
(current-actor)))
;; Parameterof (Actor Exn -> Void)
(define current-actor-crash-logger
(make-parameter
(lambda (a e)
(log-error "Actor ~a died with exception:\n~a" a (exn->string e)))))
;; Parameterof Actor
(define current-actor (make-parameter #f))
@ -259,7 +268,7 @@
(in-script? script?))
(with-handlers ([(lambda (e) (not (exn:break? e)))
(lambda (e)
(log-error "Actor ~a died with exception:\n~a" a (exn->string e))
((current-actor-crash-logger) a e)
(abandon-queued-work! a)
(terminate-actor! a))]) ;; TODO: tracing
(call-with-syndicate-prompt

View File

@ -581,267 +581,354 @@
;;---------------------------------------------------------------------------
(module+ test
(message-struct set-box (new-value))
(assertion-struct box-state (value))
(require "test.rkt")
(define ds
(make-dataspace
(lambda ()
(schedule-script!
(current-actor)
(test-case
;; See .../syndicate/examples/actor/example-during-criterion-shapshotting.rkt
[(struct foo (x y) #:prefab)
(spawn (field [x 123])
(assert (foo (x) 999))
(during (foo (x) $v)
(printf "x=~a v=~a\n" (x) v)
(when (= (x) 123) (x 124))
(on-stop (printf "finally for x=~a v=~a\n" (x) v))))]
no-crashes
(expected-output "x=123 v=999"
"x=124 v=999"
"finally for x=124 v=999"))
#;(lambda ()
;; See .../syndicate/examples/actor/example-during-criterion-shapshotting.rkt
(test-case
;; Goal: no matter the circumstances (e.g. exception in a
;; stop script), we will never retract an assertion more or
;; fewer than the correct number of times.
[(spawn #:name 'supply
#:assertions ['marker]
(assert 'marker) ;; NB this is the change wrt the test case immediately below
(error 'test-case "Deliberate error"))
(struct foo (x y) #:prefab)
(spawn (on (asserted 'marker) (printf "marker appeared\n"))
(on (retracted 'marker) (printf "marker disappeared\n")))]
(it "should crash deliberately" (actor-died? 'supply "Deliberate error"))
(expected-output "marker appeared"
"marker disappeared"))
(spawn (field [x 123])
(assert (foo (x) 999))
(during (foo (x) $v)
(log-info "x=~a v=~a" (x) v)
(when (= (x) 123) (x 124))
(on-stop
(log-info "finally for x=~a v=~a" (x) v))))
)
(test-case
;; Goal: no matter the circumstances (e.g. exception in a
;; stop script), we will never retract an assertion more or
;; fewer than the correct number of times.
[(spawn #:name 'supply
#:assertions ['marker]
(error 'test-case "Deliberate error"))
#;(lambda ()
;; Goal: no matter the circumstances (e.g. exception in a
;; stop script), we will never retract an assertion more or
;; fewer than the correct number of times.
(spawn (on (asserted 'marker) (printf "marker appeared\n"))
(on (retracted 'marker) (printf "marker disappeared\n")))]
(it "should crash deliberately" (actor-died? 'supply "Deliberate error"))
(expected-output "marker appeared"
"marker disappeared"))
;; Expected output:
;;
;; marker appeared
;; marker disappeared
;;
;; (plus the exception report for "Deliberate error")
(test-case
;; Goal: no matter the circumstances (e.g. exception in a
;; stop script), we will never retract an assertion more or
;; fewer than the correct number of times.
;;
;; Test cleanup after exception in stop script
[(assertion-struct layer (name))
(spawn #:name 'crasher
(define root-facet (current-facet))
(assert (layer 'outer))
(on-start (react (assert (layer 'middle))
(on-start (flush!) (flush!) (stop-facet root-facet))
(on-stop (/ 1 0))
(on-start (react (assert (layer 'inner)))))))
(spawn (on (retracted (layer $x)) (printf "~a gone\n" x)))]
(it "should crash with division by zero" (actor-died? 'crasher "division by zero"))
;; a permutation of these lines is acceptable:
(expected-output "middle gone"
"inner gone"
"outer gone"))
(spawn #:assertions ['marker]
(assert 'marker) ;; NB this is the change wrt the test case immediately below
(error 'test-case "Deliberate error")
(void))
(begin ;; Suite of four related tests.
;; See .../syndicate/examples/actor/example-responsibility-transfer-2.rkt
(spawn (on (asserted 'marker) (printf "marker appeared\n"))
(on (retracted 'marker) (printf "marker disappeared\n")))
)
(test-case
[(spawn #:name 'factory-1
(on (asserted (list 'X 1))
(spawn #:name 'service-1
#:assertions [(observe (list 'X 1))] ;; (A)
(stop-when (retracted (list 'X 1))) ;; (B)
(on (message 'dummy)))
(stop-current-facet)))
(spawn (on (asserted (observe (list 'X $supplier)))
(printf "Supply ~v asserted.\n" supplier)
(assert! (list 'X supplier)))
(on (retracted (observe (list 'X $supplier)))
(printf "Supply ~v retracted.\n" supplier)))]
no-crashes
(expected-output "Supply 1 asserted."))
#;(lambda ()
;; Goal: no matter the circumstances (e.g. exception in a
;; stop script), we will never retract an assertion more or
;; fewer than the correct number of times.
(test-case
[(spawn #:name 'factory-1
(on (asserted (list 'X 1))
(spawn #:name 'service-1
;; #:assertions [(observe (list 'X 1))] ;; (A)
(stop-when (retracted (list 'X 1))) ;; (B)
(on (message 'dummy)))
(stop-current-facet)))
(spawn (on (asserted (observe (list 'X $supplier)))
(printf "Supply ~v asserted.\n" supplier)
(assert! (list 'X supplier)))
(on (retracted (observe (list 'X $supplier)))
(printf "Supply ~v retracted.\n" supplier)))]
no-crashes
(expected-output "Supply 1 asserted."
"Supply 1 retracted."
"Supply 1 asserted."))
;; Expected output:
;;
;; marker appeared
;; marker disappeared
;;
;; (plus the exception report for "Deliberate error")
(test-case
[(spawn #:name 'factory-1
(on (asserted (list 'X 1))
(spawn #:name 'service-1
#:assertions [(observe (list 'X 1))] ;; (A)
;; (stop-when (retracted (list 'X 1))) ;; (B)
(on (message 'dummy)))
(stop-current-facet)))
(spawn (on (asserted (observe (list 'X $supplier)))
(printf "Supply ~v asserted.\n" supplier)
(assert! (list 'X supplier)))
(on (retracted (observe (list 'X $supplier)))
(printf "Supply ~v retracted.\n" supplier)))]
no-crashes
(expected-output "Supply 1 asserted."
"Supply 1 retracted."))
(spawn #:assertions ['marker]
(error 'test-case "Deliberate error")
(void))
(test-case
[(spawn #:name 'factory-1
(on (asserted (list 'X 1))
(spawn #:name 'service-1
;; #:assertions [(observe (list 'X 1))] ;; (A)
;; (stop-when (retracted (list 'X 1))) ;; (B)
(on (message 'dummy)))
(stop-current-facet)))
(spawn (on (asserted (observe (list 'X $supplier)))
(printf "Supply ~v asserted.\n" supplier)
(assert! (list 'X supplier)))
(on (retracted (observe (list 'X $supplier)))
(printf "Supply ~v retracted.\n" supplier)))]
no-crashes
(expected-output "Supply 1 asserted."
"Supply 1 retracted."))
(spawn (on (asserted 'marker) (printf "marker appeared\n"))
(on (retracted 'marker) (printf "marker disappeared\n")))
)
)
#;(lambda ()
;; Goal: no matter the circumstances (e.g. exception in a
;; stop script), we will never retract an assertion more or
;; fewer than the correct number of times.
(test-case
;; See .../syndicate/examples/actor/example-responsibility-transfer-1.rkt
[(spawn #:name 'demand-watcher
(during/spawn 'demand
#:name (gensym 'intermediate-demand-asserter)
(assert 'intermediate-demand)))
;; Test cleanup after exception in stop script
;;
;; Correct output:
;;
;; inner gone
;; outer gone
;; middle gone
;;
;; (or some permutation of these) plus an exception report for the division by zero.
(spawn #:name 'intermediate-demand-watcher
(during/spawn 'intermediate-demand
#:name (gensym 'supply-asserter)
(assert 'supply)))
(assertion-struct layer (name))
(spawn* #:name 'driver
(react (on (asserted 'supply) (displayln "Supply asserted."))
(on (retracted 'supply) (displayln "Supply retracted.")))
(until (asserted (observe 'demand)))
(displayln "Asserting demand.")
(assert! 'demand)
(until (asserted 'supply))
(displayln "Glitching demand.")
(retract! 'demand)
(flush!)
(assert! 'demand)
(displayln "Demand now steady."))]
no-crashes
(expected-output "Asserting demand."
"Supply asserted."
"Glitching demand."
"Demand now steady."
"Supply retracted."
"Supply asserted."))
(spawn (define root-facet (current-facet))
(assert (layer 'outer))
(on-start (react (assert (layer 'middle))
(on-start (flush!) (flush!) (stop-facet root-facet))
(on-stop (/ 1 0))
(on-start (react (assert (layer 'inner)))))))
(test-case
[(assertion-struct one-plus (n m))
(spawn #:name 'add1-server
(during/spawn (observe (one-plus $n _))
#:name (list 'solving 'one-plus n)
(assert (one-plus n (+ n 1)))))
(spawn #:name 'client-process
(stop-when (asserted (one-plus 3 $value))
(printf "1 + 3 = ~a\n" value)))]
no-crashes
(expected-output "1 + 3 = 4"))
(spawn (on (retracted (layer $x)) (printf "~a gone\n" x))))
(test-case
;; .../racket/syndicate/examples/actor/example-partial-retraction.rkt
[(struct ready (what) #:prefab)
(struct entry (key val) #:prefab)
#;(lambda ()
;; See .../syndicate/examples/actor/example-responsibility-transfer-2.rkt
(spawn (assert (ready 'listener))
(on (asserted (entry $key _))
(printf "key ~v asserted\n" key)
(until (retracted (entry key _))
(on (asserted (entry key $value))
(printf "add binding: ~v -> ~v\n" key value))
(on (retracted (entry key $value))
(printf "del binding: ~v -> ~v\n" key value)))
(printf "key ~v retracted\n" key)))
(spawn #:name 'factory-1
(on (asserted (list 'X 1))
(spawn #:name 'service-1
#:assertions [(observe (list 'X 1))] ;; (A)
(stop-when (retracted (list 'X 1))) ;; (B)
(on (message 'dummy))) ;; exists just to keep the service alive if
;; there are no other endpoints
;; spawn executes *before* teardown of this on-asserted endpoint, and thus
;; before the patch withdrawing (observe (list 'X 1)).
(stop-current-facet)))
(spawn (assert (ready 'other-listener))
(during (entry $key _)
(printf "(other-listener) key ~v asserted\n" key)
(on-stop (printf "(other-listener) key ~v retracted\n" key))
(during (entry key $value)
(printf "(other-listener) ~v ---> ~v\n" key value)
(on-stop (printf "(other-listener) ~v -/-> ~v\n" key value)))))
(spawn (on (asserted (observe (list 'X $supplier)))
(printf "Supply ~v asserted.\n" supplier)
(assert! (list 'X supplier)))
(on (retracted (observe (list 'X $supplier)))
(printf "Supply ~v retracted.\n" supplier)))
)
(define (pause)
(displayln "pause")
(define token (gensym 'pause)) ;; FIXME:: If we use the same token every time, need epochs!
(until (asserted (ready token))
(assert (ready token))))
#;(lambda ()
;; See .../syndicate/examples/actor/example-responsibility-transfer-1.rkt
(spawn* (until (asserted (ready 'listener)))
(until (asserted (ready 'other-listener)))
(assert! (entry 'a 1))
(assert! (entry 'a 2))
(assert! (entry 'b 3))
(assert! (entry 'c 33))
(assert! (entry 'a 4))
(assert! (entry 'a 5))
(pause)
(retract! (entry 'a 2))
(retract! (entry 'c 33))
(assert! (entry 'a 9))
(pause)
(local-require "bag.rkt")
(for [(a (in-bag (current-adhoc-assertions)))]
(local-require racket/match)
(match a
[(entry 'a _) (retract! a)]
[_ (void)]))
;; ^ (retract! (entry 'a ?))
(pause))]
no-crashes
;; Within the following, some permutations are acceptable:
#;(expected-output
"pause"
"pause"
"(other-listener) key 'a asserted"
"(other-listener) key 'c asserted"
"(other-listener) key 'b asserted"
"(other-listener) 'a ---> 4"
"(other-listener) 'a ---> 1"
"(other-listener) 'a ---> 2"
"(other-listener) 'a ---> 5"
"(other-listener) 'c ---> 33"
"(other-listener) 'b ---> 3"
"key 'a asserted"
"key 'c asserted"
"key 'b asserted"
"add binding: 'a -> 4"
"add binding: 'a -> 1"
"add binding: 'a -> 2"
"add binding: 'a -> 5"
"add binding: 'c -> 33"
"add binding: 'b -> 3"
"pause"
"del binding: 'a -> 2"
"del binding: 'c -> 33"
"add binding: 'a -> 9"
"key 'c retracted"
"(other-listener) 'a ---> 9"
"(other-listener) 'a -/-> 2"
"(other-listener) 'c -/-> 33"
"(other-listener) key 'c retracted"
"del binding: 'a -> 1"
"del binding: 'a -> 9"
"del binding: 'a -> 5"
"del binding: 'a -> 4"
"key 'a retracted"
"(other-listener) 'a -/-> 1"
"(other-listener) 'a -/-> 9"
"(other-listener) 'a -/-> 5"
"(other-listener) 'a -/-> 4"
"(other-listener) key 'a retracted"
"del binding: 'b -> 3"
"key 'b retracted"
"(other-listener) 'b -/-> 3"
"(other-listener) key 'b retracted"
))
(spawn #:name 'demand-watcher
(during/spawn 'demand
#:name (gensym 'intermediate-demand-asserter)
(assert 'intermediate-demand)))
(let ((N 100000))
(test-case
;; Rough message send speed test
[(spawn (on (message $v)
(if (= v N)
(stop-current-facet)
(send! (+ v 1))))
(on-start (send! 0)))]
no-crashes
(it "should be fairly quick"
(log-info "Rough message send speed: ~a msgs in ~a ms = ~a Hz"
N
(test-run-time)
(/ N (/ (test-run-time) 1000.0))))))
(spawn #:name 'intermediate-demand-watcher
(during/spawn 'intermediate-demand
#:name (gensym 'supply-asserter)
(assert 'supply)))
(test-case
;; Tests that pending actions are abandoned during a turn in which there is an exception
[(message-struct stage (n))
(spawn* #:name 'driver
(react (on (asserted 'supply) (log-info "Supply asserted."))
(on (retracted 'supply) (log-info "Supply retracted.")))
(until (asserted (observe 'demand)))
(log-info "Asserting demand.")
(assert! 'demand)
(until (asserted 'supply))
(log-info "Glitching demand.")
(retract! 'demand)
(flush!)
(assert! 'demand)
(log-info "Demand now steady."))
)
(spawn #:name 'actor0
(on (message (stage 0))
(send! (stage 1)))
#;(lambda ()
;; Trivial example program to demonstrate tracing
(on (message (stage 2))
(send! (stage 3))
(/ 1 0)
(send! (stage 3))))
(assertion-struct one-plus (n m))
(spawn #:name 'main
(on (message (stage $v))
(printf "Got message ~v\n" v))
(on-start
(until (asserted (observe (stage 0))))
(send! (stage 0))
(until (message (stage 1)))
(send! (stage 2))))]
(it "should involve one crash" (actor-died? 'actor0 "division by zero"))
(expected-output "Got message 0"
"Got message 1"
"Got message 2"))
(spawn #:name 'add1-server
(during/spawn (observe (one-plus $n _))
#:name (list 'solving 'one-plus n)
(assert (one-plus n (+ n 1)))))
(test-case
;; Box-and-client
[(message-struct set-box (new-value))
(assertion-struct box-state (value))
(spawn #:name 'client-process
(stop-when (asserted (one-plus 3 $value))
(printf "1 + 3 = ~a\n" value)))
)
(spawn (field [current-value 0])
(assert (box-state (current-value)))
(stop-when-true (= (current-value) 3)
(displayln "box: terminating"))
(on (message (set-box $new-value))
(printf "box: taking on new-value ~v\n" new-value)
(current-value new-value)))
#;(lambda ()
;; .../racket/syndicate/examples/actor/example-partial-retraction.rkt
;;
(struct ready (what) #:prefab)
(struct entry (key val) #:prefab)
(spawn (stop-when (retracted (observe (set-box _)))
(displayln "client: box has gone"))
(on (asserted (box-state $v))
(printf "client: learned that box's value is now ~v\n" v)
(send! (set-box (+ v 1)))))]
no-crashes
(expected-output "client: learned that box's value is now 0"
"box: taking on new-value 1"
"client: learned that box's value is now 1"
"box: taking on new-value 2"
"client: learned that box's value is now 2"
"box: taking on new-value 3"
"box: terminating"
"client: box has gone"))
(spawn (assert (ready 'listener))
(on (asserted (entry $key _))
(log-info "key ~v asserted" key)
(until (retracted (entry key _))
(on (asserted (entry key $value))
(log-info "add binding: ~v -> ~v" key value))
(on (retracted (entry key $value))
(log-info "del binding: ~v -> ~v" key value)))
(log-info "key ~v retracted" key)))
(spawn (assert (ready 'other-listener))
(during (entry $key _)
(log-info "(other-listener) key ~v asserted" key)
(on-stop (log-info "(other-listener) key ~v retracted" key))
(during (entry key $value)
(log-info "(other-listener) ~v ---> ~v" key value)
(on-stop (log-info "(other-listener) ~v -/-> ~v" key value)))))
(define (pause)
(log-info "pause")
(define token (gensym 'pause)) ;; FIXME:: If we use the same token every time, need epochs!
(until (asserted (ready token))
(assert (ready token))))
(spawn* (until (asserted (ready 'listener)))
(until (asserted (ready 'other-listener)))
(assert! (entry 'a 1))
(assert! (entry 'a 2))
(assert! (entry 'b 3))
(assert! (entry 'c 33))
(assert! (entry 'a 4))
(assert! (entry 'a 5))
(pause)
(retract! (entry 'a 2))
(retract! (entry 'c 33))
(assert! (entry 'a 9))
(pause)
(local-require "bag.rkt")
(for [(a (in-bag (current-adhoc-assertions)))]
(local-require racket/match)
(match a
[(entry 'a _) (retract! a)]
[_ (void)]))
;; ^ (retract! (entry 'a ?))
(pause))
)
#;(lambda ()
(spawn (on (message $v)
(if (= v 10000000)
(stop-current-facet)
(send! (+ v 1))))
(on-start (send! 0)))
)
#;(lambda ()
(message-struct stage (n))
(spawn #:name 'actor0
(on (message (stage 0))
(send! (stage 1)))
(on (message (stage 2))
(send! (stage 3))
(/ 1 0)
(send! (stage 3))))
(spawn #:name 'main
(on (message (stage $v))
(printf "Got message ~v\n" v))
(on-start
(until (asserted (observe (stage 0))))
(send! (stage 0))
(until (message (stage 1)))
(send! (stage 2))))
)
(lambda ()
(spawn (field [current-value 0])
(assert (box-state (current-value)))
(stop-when-true (= (current-value) 10)
(log-info "box: terminating"))
(on (message (set-box $new-value))
(log-info "box: taking on new-value ~v" new-value)
(current-value new-value)))
(spawn (stop-when (retracted (observe (set-box _)))
(log-info "client: box has gone"))
(on (asserted (box-state $v))
(log-info "client: learned that box's value is now ~v" v)
(send! (set-box (+ v 1)))))
)
))))
(require racket/pretty)
;; (pretty-print ds)
(#;time values
(let loop ((i 0))
;; (printf "--- i = ~v\n" i)
(when (run-scripts! ds)
;; (pretty-print ds)
(loop (+ i 1)))))
;; (pretty-print ds)
)

196
syndicate/test.rkt Normal file
View File

@ -0,0 +1,196 @@
#lang racket/base
;; Test drivers for Syndicate implementation.
(provide collected-events
collected-output
collected-lines
final-dataspace
final-assertions
test-run-time
test-gc-time
asserted?
emitted?
actor-died?
emit!
test-case
it
no-crashes
expected-output
run-syndicate-test!
log-test-result!)
(require racket/exn)
(require racket/match)
(require (only-in racket/string string-split string-join string-contains?))
(require "bag.rkt")
(require "dataspace.rkt")
(require (for-syntax racket/base))
(require (for-syntax syntax/srcloc))
(define-logger syndicate/test)
(define event-accumulator (make-parameter #f))
(define exn-accumulator (make-parameter #f))
(define failure-detected? (make-parameter #f))
(define collected-events (make-parameter '()))
(define collected-exns (make-parameter '()))
(define collected-output (make-parameter ""))
(define collected-lines (make-parameter '()))
(define final-dataspace (make-parameter #f))
(define test-run-time (make-parameter 0))
(define test-gc-time (make-parameter 0))
(define (asserted? v)
(bag-member? (dataspace-assertions (final-dataspace)) v))
(define (final-assertions)
(bag->set (dataspace-assertions (final-dataspace))))
(define (emitted? v)
(member v (collected-events)))
(define (actor-died? name [substr-or-regex ""])
(define entry (findf (lambda (e) (equal? name (actor-name (car e)))) (collected-exns)))
(and entry
(let ((message (exn-message (cadr entry))))
(match substr-or-regex
[(? string? substr) (string-contains? message substr)]
[(? regexp? re) (regexp-match? re message)]))))
(define (emit! event)
(define b (event-accumulator))
(set-box! b (cons event (unbox b))))
(define-syntax (test-case stx)
(syntax-case stx ()
[(_ [body ...] checks ...)
(quasisyntax/loc stx
(run-syndicate-test!
#,(source-location->string stx)
(lambda () body ...)
(list checks ...)))]))
(define (not-break? e)
(not (exn:break? e)))
(struct check (location description thunk) #:prefab)
(define (run-syndicate-test! location-str body-thunk list-of-checks)
(parameterize ((failure-detected? #f))
(define events-box (box '()))
(define exns-box (box '()))
(define (get-items b) (reverse (unbox b)))
(define op (open-output-string))
(with-handlers ([not-break? (lambda (e) (log-test-result! location-str #f e))])
(log-syndicate/test-info "~a--- Running test at ~a~a"
(color YELLOW)
location-str
(color NORMAL))
(define ds (make-dataspace
(lambda ()
(schedule-script! (current-actor) body-thunk))))
(define-values (_results cpu-ms _wall-ms gc-ms)
(parameterize ((current-output-port op)
(current-actor-crash-logger
(lambda (a e)
(set-box! exns-box (cons (list a e) (unbox exns-box)))))
(event-accumulator events-box))
(time-apply (lambda () (let loop () (when (run-scripts! ds) (loop))))
'())))
(define op-string (get-output-string op))
(parameterize ((collected-events (get-items events-box))
(collected-exns (get-items exns-box))
(collected-output op-string)
(collected-lines (string-split op-string "\n"))
(final-dataspace ds)
(test-run-time cpu-ms)
(test-gc-time gc-ms))
(for [(check (in-list list-of-checks))]
(with-handlers ([not-break? (lambda (e) (log-test-result! location-str check e))])
(match check
[(? procedure?)
(log-test-result! location-str check (check))]
[(? check?)
(log-test-result! location-str check ((check-thunk check)))])))))
(when (failure-detected?)
(log-syndicate/test-debug "Collected events:")
(for [(e (get-items events-box))]
(log-syndicate/test-debug " ~v" e))
(log-syndicate/test-debug "Collected output:")
(for [(l (string-split (get-output-string op) "\n"))]
(log-syndicate/test-debug " ~a" l))
(log-syndicate/test-debug "Crashed actors:")
(for [(entry (get-items exns-box))]
(match-define (list a e) entry)
(log-syndicate/test-debug " ~a\n ~a"
a
(string-join (string-split (exn->string e) "\n")
" \n"))))))
(define-syntax (it stx)
(syntax-case stx ()
[(_ description body ...)
(quasisyntax/loc stx
(check #,(source-location->string stx)
description
(lambda ()
(and body ...))))]))
(define no-crashes (it "shouldn't involve any crashing actors"
(null? (collected-exns))))
(define-syntax (expected-output stx)
(syntax-case stx ()
[(_ line ...)
(quasisyntax/loc stx
(it "should produce correct output"
(equal? (collected-lines)
(list line ...))))]))
(define RED ";31")
(define BRIGHT-RED ";1;31")
(define GREEN ";32")
(define BRIGHT-GREEN ";1;32")
(define YELLOW ";33")
(define NORMAL "")
(define (color c) (format "\e[0~am" c))
(define (log-test-result! test-loc maybe-check result)
(if (not maybe-check)
(begin
(failure-detected? #t)
(log-syndicate/test-error "~a ✗ Exception running program under test:\n~a~a"
(color BRIGHT-RED)
(exn->string result)
(color NORMAL)))
(let ((description
(match maybe-check
[(? procedure?) (format "~a" check)]
[(check #f description _thunk) (format "~a" description)]
[(check check-loc description _thunk) (format "~a (~a)" description check-loc)])))
(match result
[(? exn?)
(failure-detected? #t)
(log-syndicate/test-error "~a ✗ ... ~a:\n~a~a"
(color RED)
description
(exn->string result)
(color NORMAL))]
[#f
(failure-detected? #t)
(log-syndicate/test-error "~a ✗ ... ~a~a"
(color RED)
description
(color NORMAL))]
[_
(log-syndicate/test-info "~a ✓ ... ~a~a"
(color GREEN)
description
(color NORMAL))]))))