Skip to content

Lin: Print commands that trigger unexpected exceptions #324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changes

## Next version

- #324: When a command causes an unexpectedly uncaught exception,
display that command along with the exception itself in both
`Lin` and `STM`

## 0.2

- #342: Add two submodules of combinators in `Util`:
Expand Down
4 changes: 2 additions & 2 deletions lib/STM.ml
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,15 @@ struct
let rec interp_agree s sut cs = match cs with
| [] -> true
| c::cs ->
let res = Spec.run c sut in
let res = try Spec.run c sut with e -> Util.wrap_uncaught_exn Spec.show_cmd c e in
let b = Spec.postcond c s res in
let s' = Spec.next_state c s in
b && interp_agree s' sut cs

let rec check_disagree s sut cs = match cs with
| [] -> None
| c::cs ->
let res = Spec.run c sut in
let res = try Spec.run c sut with e -> Util.wrap_uncaught_exn Spec.show_cmd c e in
let b = Spec.postcond c s res in
let s' = Spec.next_state c s in
if b
Expand Down
6 changes: 4 additions & 2 deletions lib/lin.ml
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ module MakeCmd (ApiSpec : Spec) : Internal.CmdSpec = struct
apply_f (f arg) rem state

let run cmd state =
let Cmd { args ; rty ; f ; _ } = cmd in
Res (rty, apply_f f args state)
try
let (Cmd { args; rty; f; _ }) = cmd in
Res (rty, apply_f f args state)
with e -> Util.wrap_uncaught_exn show_cmd cmd e
end
17 changes: 17 additions & 0 deletions lib/util.ml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ let protect (f : 'a -> 'b) (a : 'a) : ('b, exn) result =
try Result.Ok (f a)
with e -> Result.Error e

exception Uncaught_exception of string * exn

let print_uncaught_exception e =
match e with
| Uncaught_exception (cmd, exc) ->
Some
(Format.sprintf "%s raised but not caught while running %s"
(Printexc.to_string exc) cmd)
| _ -> None

let _ =
Printexc.register_printer print_uncaught_exception

let wrap_uncaught_exn show x e =
let bt = Printexc.get_raw_backtrace () in
Printexc.raise_with_backtrace (Uncaught_exception (show x, e)) bt

module Pp = struct
open Format

Expand Down
14 changes: 14 additions & 0 deletions lib/util.mli
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ val print_triple_vertical :
val protect : ('a -> 'b) -> 'a -> ('b, exn) result
(** [protect f] turns an [exception] throwing function into a [result] returning function. *)

exception Uncaught_exception of string * exn
(** [Uncaught_exception (cmd, exc)] is used to signal that the
exception [exc] was not caught (but should have been) during the
execution of [cmd]. That exception will be printed as:
["... raised but not caught while running " ^ cmd]. *)

val wrap_uncaught_exn : ('a -> string) -> 'a -> exn -> 'b
(** [wrap_uncaught_exn show x e] wraps the exception [e] into an
[Uncaught_exception] with the command as pretty-printed by
[show x] and raises that [Uncaught_exception].
[wrap_uncaught_exn] preserves the current backtrace so it
must be called from the exception handler, like so:
[try ... with e -> wrap_uncaught_exn show x e] *)

module Pp : sig
(** Pretty-printing combinators that generate valid OCaml syntax for common
types along with combinators for user-defined types *)
Expand Down
27 changes: 27 additions & 0 deletions test/dune
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,30 @@
(libraries qcheck-stm.sequential threads.posix)
(action
(with-accepted-exit-codes 1 (run ./%{test} --verbose --seed 229109553))))

(test
(name uncaught_lin)
(package qcheck-lin)
(modules uncaught_lin)
(libraries qcheck-lin.domain)
(enabled_if (>= %{ocaml_version} 5))
(action
(pipe-outputs
(with-accepted-exit-codes 1
(run %{test} --no-colors --verbose --seed 260395858))
; filter out output that is not deterministic enough across
; versions and platforms
(run sed -e "/^[[]/d" -e "s/ ([0-9]* shrink steps)//"))))

(test
(name uncaught_stm)
(package qcheck-stm)
(modules uncaught_stm)
(libraries qcheck-stm.sequential)
(action
(pipe-outputs
(with-accepted-exit-codes 1
(run %{test} --no-colors --verbose --seed 260395858))
; filter out output that is not deterministic enough across
; versions and platforms
(run sed -e "/^[[]/d" -e "s/ ([0-9]* shrink steps)//"))))
32 changes: 32 additions & 0 deletions test/uncaught_lin.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
random seed: 260395858
generated error fail pass / total time test name

=== Error ======================================================================

Test Lin DSL test of uncaught exceptions errored on:

|
always_fail t
|
.---------------------.
| |


exception Failure("unexpected") raised but not caught while running always_fail t


=== Error ======================================================================

Test neg Lin DSL test of uncaught exceptions errored on:

|
always_fail t
|
.---------------------.
| |


exception Failure("unexpected") raised but not caught while running always_fail t

================================================================================
failure (0 tests failed, 2 tests errored, ran 2 tests)
24 changes: 24 additions & 0 deletions test/uncaught_lin.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(* Test of the behaviour of Lin tests with uncaught exceptions *)

let always_fail () = failwith "unexpected"

module UncaughtExcConf = struct
open Lin

type t = unit

let init () = ()
let cleanup _ = ()
let api = [ val_ "always_fail" always_fail (t @-> returning unit) ]
end

module UncaughtExc_domain = Lin_domain.Make (UncaughtExcConf)

let _ =
QCheck_base_runner.run_tests_main
[
UncaughtExc_domain.lin_test ~count:10
~name:"Lin DSL test of uncaught exceptions";
UncaughtExc_domain.neg_lin_test ~count:10
~name:"neg Lin DSL test of uncaught exceptions";
]
24 changes: 24 additions & 0 deletions test/uncaught_stm.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
random seed: 260395858
generated error fail pass / total time test name

=== Error ======================================================================

Test STM test of uncaught exceptions errored on:

AlwaysFail ()


exception Failure("unexpected") raised but not caught while running AlwaysFail ()


=== Error ======================================================================

Test neg STM test of uncaught exceptions errored on:

AlwaysFail ()


exception Failure("unexpected") raised but not caught while running AlwaysFail ()

================================================================================
failure (0 tests failed, 2 tests errored, ran 2 tests)
32 changes: 32 additions & 0 deletions test/uncaught_stm.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
(* Test of the behaviour of STM tests with uncaught exceptions *)

let always_fail () = failwith "unexpected"

module UncaughtExcConf : STM.Spec = struct
open STM

type sut = unit
type state = unit
type cmd = AlwaysFail of sut

let show_cmd = function AlwaysFail () -> "AlwaysFail ()"
let arb_cmd _ = QCheck.(make ~print:show_cmd (Gen.pure (AlwaysFail ())))
let init_state = ()
let next_state _ _ = ()
let init_sut _ = ()
let cleanup _ = ()
let precond _ _ = true
let run c s = match c with AlwaysFail () -> Res (unit, always_fail s)

let postcond c _ r =
match (c, r) with AlwaysFail (), Res ((Unit, _), _) -> true | _ -> false
end

module UE = STM_sequential.Make (UncaughtExcConf)

let _ =
QCheck_base_runner.run_tests_main
[
UE.agree_test ~count:10 ~name:"STM test of uncaught exceptions";
UE.neg_agree_test ~count:10 ~name:"neg STM test of uncaught exceptions";
]