Skip to content

Commit

Permalink
Introduced extended specs to allow wrapping command sequences
Browse files Browse the repository at this point in the history
The motivating use case is to make it nicer to test commands that perform
effects.
  • Loading branch information
polytypic committed Jan 14, 2025
1 parent 9fc194a commit 45ae1ad
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## NEXT RELEASE

- #509: Change/Fix to use a symmetric barrier to synchronize domains
- #511: Introduce extended specs to allow wrapping command sequences

## 0.6

Expand Down
13 changes: 13 additions & 0 deletions lib/STM.ml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,19 @@ sig
Note: [s] is in this case the model's state prior to command execution. *)
end

module type SpecExt =
sig
include Spec

val wrap_cmd_seq : (unit -> 'a) -> 'a
end

module SpecDefaults =
struct
let cleanup = ignore
let precond _ _ = true
let wrap_cmd_seq th = th ()
end

module Internal =
struct
Expand Down
48 changes: 47 additions & 1 deletion lib/STM.mli
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ type res =
val show_res : res -> string


(** The specification of a state machine. *)
(** The specification of a state machine.
See also {!SpecExt} and {!SpecDefaults}. *)
module type Spec =
sig
type cmd
Expand Down Expand Up @@ -126,6 +128,50 @@ sig
This is helpful to model, e.g., a [remove] [cmd] that returns the removed element. *)
end

module type SpecExt =
sig
(** Extended specification of a state machine.
This signature may be extended in the future with new specifications that
can be given defaults via {!SpecDefaults}. *)

include Spec

val wrap_cmd_seq : (unit -> 'a) -> 'a
(** [wrap_cmd_seq thunk] must call [thunk ()] and return or raise whatever
[thunk ()] returned or raised. [wrap_cmd_seq] is used to wrap the
execution of the generated command sequences. [wrap_cmd_seq] is useful,
e.g., to handle effects performed by blocking primitives. *)
end

module SpecDefaults :
sig
(** Default implementations for state machine specifications that can be given
useful defaults.
The intention is that extended spec modules would [include] the defaults:
{[
module MySpec = struct
include SpecDefaults
(* ... *)
end
]}
This way the spec module can usually just continue working after new
specifications have been added to {!SpecExt} with defaults in
{!SpecDefaults}. *)

val cleanup : 'sut -> unit
(** [cleanup sut] just return [()]. *)

val precond : 'cmd -> 'state -> bool
(** [precond cmd state] just returns [true]. *)

val wrap_cmd_seq : (unit -> 'a) -> 'a
(** [wrap_cmd_seq thunk] is equivalent to [thunk ()]. *)
end

module Internal : sig
open QCheck
Expand Down
22 changes: 16 additions & 6 deletions lib/STM_domain.ml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
open STM

module Make (Spec: Spec) = struct
module MakeExt (Spec: SpecExt) = struct

open Util
open QCheck
Expand All @@ -24,9 +24,10 @@ module Make (Spec: Spec) = struct

let run_par seq_pref cmds1 cmds2 =
let sut = Spec.init_sut () in
let pref_obs = interp_sut_res sut seq_pref in
let pref_obs = Spec.wrap_cmd_seq @@ fun () -> interp_sut_res sut seq_pref in
let barrier = Atomic.make 2 in
let main cmds () =
Spec.wrap_cmd_seq @@ fun () ->
Atomic.decr barrier;
while Atomic.get barrier <> 0 do Domain.cpu_relax() done;
try Ok (interp_sut_res sut cmds) with exn -> Error exn
Expand Down Expand Up @@ -54,17 +55,20 @@ module Make (Spec: Spec) = struct

let agree_prop_par_asym (seq_pref, cmds1, cmds2) =
let sut = Spec.init_sut () in
let pref_obs = interp_sut_res sut seq_pref in
let pref_obs = Spec.wrap_cmd_seq @@ fun () -> interp_sut_res sut seq_pref in
let wait = Atomic.make 2 in
let child_dom =
Domain.spawn (fun () ->
Spec.wrap_cmd_seq @@ fun () ->
Atomic.decr wait;
while Atomic.get wait <> 0 do Domain.cpu_relax() done;
try Ok (interp_sut_res sut cmds2) with exn -> Error exn)
in
Atomic.decr wait;
while Atomic.get wait <> 0 do Domain.cpu_relax() done;
let parent_obs = try Ok (interp_sut_res sut cmds1) with exn -> Error exn in
let parent_obs =
Spec.wrap_cmd_seq @@ fun () ->
Atomic.decr wait;
while Atomic.get wait <> 0 do Domain.cpu_relax() done;
try Ok (interp_sut_res sut cmds1) with exn -> Error exn in
let child_obs = Domain.join child_dom in
let () = Spec.cleanup sut in
let parent_obs = match parent_obs with Ok v -> v | Error exn -> raise exn in
Expand Down Expand Up @@ -125,3 +129,9 @@ module Make (Spec: Spec) = struct
assume (all_interleavings_ok triple);
repeat rep_count agree_prop_par_asym triple) (* 25 times each, then 25 * 10 times when shrinking *)
end

module Make (Spec: Spec) =
MakeExt (struct
include SpecDefaults
include Spec
end)
3 changes: 3 additions & 0 deletions lib/STM_domain.mli
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,6 @@ module Make : functor (Spec : STM.Spec) ->
interleaving search like {!agree_test_par} and {!neg_agree_test_par}. *)

end

module MakeExt : functor (Spec : STM.SpecExt) ->
module type of Make (Spec)
10 changes: 8 additions & 2 deletions lib/STM_sequential.ml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
open STM

module Make (Spec: Spec) = struct
module MakeExt (Spec: SpecExt) = struct

open QCheck
open Internal.Make(Spec)
Expand All @@ -18,7 +18,7 @@ module Make (Spec: Spec) = struct
let agree_prop cs =
assume (cmds_ok Spec.init_state cs);
let sut = Spec.init_sut () in (* reset system's state *)
let res = try Ok (check_disagree Spec.init_state sut cs) with exn -> Error exn in
let res = try Ok (Spec.wrap_cmd_seq @@ fun () -> check_disagree Spec.init_state sut cs) with exn -> Error exn in
let () = Spec.cleanup sut in
let res = match res with Ok res -> res | Error exn -> raise exn in
match res with
Expand All @@ -34,3 +34,9 @@ module Make (Spec: Spec) = struct
Test.make_neg ~name ~count (arb_cmds Spec.init_state) agree_prop

end

module Make (Spec : Spec) =
MakeExt (struct
include SpecDefaults
include Spec
end)
3 changes: 3 additions & 0 deletions lib/STM_sequential.mli
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ module Make : functor (Spec : STM.Spec) ->
(** A negative agreement test (for convenience). Accepts two labeled parameters:
[count] is the test count and [name] is the printed test name. *)
end

module MakeExt : functor (Spec : STM.SpecExt) ->
module type of Make (Spec)
14 changes: 10 additions & 4 deletions lib/STM_thread.ml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
open STM

module Make (Spec: Spec) = struct
module MakeExt (Spec: SpecExt) = struct

open Util
open QCheck
Expand All @@ -23,10 +23,10 @@ module Make (Spec: Spec) = struct
let agree_prop_conc (seq_pref,cmds1,cmds2) =
let sut = Spec.init_sut () in
let obs1,obs2 = ref (Error ThreadNotFinished), ref (Error ThreadNotFinished) in
let pref_obs = interp_sut_res sut seq_pref in
let pref_obs = Spec.wrap_cmd_seq @@ fun () -> interp_sut_res sut seq_pref in
let wait = ref true in
let th1 = Thread.create (fun () -> while !wait do Thread.yield () done; obs1 := try Ok (interp_sut_res sut cmds1) with exn -> Error exn) () in
let th2 = Thread.create (fun () -> wait := false; obs2 := try Ok (interp_sut_res sut cmds2) with exn -> Error exn) () in
let th1 = Thread.create (fun () -> Spec.wrap_cmd_seq @@ fun () -> while !wait do Thread.yield () done; obs1 := try Ok (interp_sut_res sut cmds1) with exn -> Error exn) () in
let th2 = Thread.create (fun () -> Spec.wrap_cmd_seq @@ fun () -> wait := false; obs2 := try Ok (interp_sut_res sut cmds2) with exn -> Error exn) () in
let () = Thread.join th1 in
let () = Thread.join th2 in
let () = Spec.cleanup sut in
Expand Down Expand Up @@ -59,3 +59,9 @@ module Make (Spec: Spec) = struct
assume (all_interleavings_ok seq_pref cmds1 cmds2 Spec.init_state);
repeat rep_count agree_prop_conc triple) (* 100 times each, then 100 * 15 times when shrinking *)
end

module Make (Spec: Spec) =
MakeExt (struct
include SpecDefaults
include Spec
end)
4 changes: 4 additions & 0 deletions lib/STM_thread.mli
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ module Make : functor (Spec : STM.Spec) ->

end
[@@alert experimental "This module is experimental: It may fail to trigger concurrency issues that are present."]

module MakeExt : functor (Spec : STM.SpecExt) ->
module type of Make (Spec) [@@alert "-experimental"]
[@@alert experimental "This module is experimental: It may fail to trigger concurrency issues that are present."]

0 comments on commit 45ae1ad

Please sign in to comment.