Skip to content

Commit d6619f9

Browse files
committed
fix
1 parent 4dcf927 commit d6619f9

4 files changed

Lines changed: 217 additions & 52 deletions

File tree

counter_test.mbt

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// The model is the single integer value that a correct mutable counter should
44
// contain after each prefix of the command program. `CounterIncr` advances that
55
// model by one, while `CounterGet` observes it. The property under test is the
6-
// basic state-machine lockstep property from the blogs: after each concrete
6+
// basic state-machine lockstep property : after each concrete
77
// command, the observed response must match the response predicted by the pure
88
// model.
99
fn case_config(
@@ -188,9 +188,22 @@ test "counter passes against faithful implementation" {
188188
counter_script_spec(commands),
189189
config=case_config(max_commands=commands.length()),
190190
)
191-
assert_eq(summary.cases_run, 1)
192-
assert_eq(summary.commands_run, 4)
193-
assert_eq(summary.command_distribution.length(), 2)
191+
inspect(summary.cases_run, content="1")
192+
inspect(summary.commands_run, content="4")
193+
inspect(
194+
@debug.to_string(summary.command_distribution),
195+
content="[(\"incr\", 2), (\"get\", 2)]",
196+
)
197+
inspect(
198+
@quickcheck_statemachine.format_commands(summary.commands),
199+
content=(
200+
#|0: CounterIncr -> CounterIncrDone
201+
#|1: CounterGet -> CounterGot(1)
202+
#|2: CounterIncr -> CounterIncrDone
203+
#|3: CounterGet -> CounterGot(2)
204+
#|
205+
),
206+
)
194207
}
195208

196209
///|
@@ -215,15 +228,19 @@ test "counter catches value 42 increment bug" {
215228
model~,
216229
next_model~,
217230
commands~,
231+
logic~,
218232
..
219233
)
220234
) else {
221235
fail("expected counter postcondition failure")
222236
}
223-
assert_eq(step_index, 43)
224-
assert_true(command is CounterGet)
225-
assert_true(response is CounterGot(42))
226-
assert_eq(model, 43)
227-
assert_eq(next_model, 43)
228-
assert_eq(commands.length(), 44)
237+
inspect(
238+
"\{step_index}: \{@debug.to_string(command)} -> \{@debug.to_string(response)}, model=\{model}, next=\{next_model}",
239+
content="43: CounterGet -> CounterGot(42), model=43, next=43",
240+
)
241+
inspect(commands.length(), content="44")
242+
inspect(
243+
@quickcheck_statemachine.format_counterexample(logic.counterexample()),
244+
content="counter response matches model",
245+
)
229246
}

filesystem_test.mbt

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ fn fs_sym_name(command : FsSymCommand) -> String {
835835

836836
///|
837837
// Labels used as coverage diagnostics:
838-
// The file-system example in the blog uses labels to make sure generated
838+
// The file-system example uses labels to make sure generated
839839
// programs cover meaningful behaviors, not just arbitrary command names. These
840840
// labels identify multi-open scenarios, successful reads, closed-handle writes,
841841
// and busy reads.
@@ -954,7 +954,7 @@ fn fs_unknown_handle_probe_spec() -> @quickcheck_statemachine.StateMachine[
954954
// exist, reified at execution time, and still satisfy the pure model. The
955955
// required labels assert that the scenario covers both multiple open handles and
956956
// at least one successful read.
957-
test "qsm blog filesystem open-write-read examples pass with symbolic handles" {
957+
test "qsm filesystem open-write-read examples pass with symbolic handles" {
958958
let root = fs_root_dir()
959959
let z = fs_dir(["z"])
960960
let a = fs_file(root, "a")
@@ -980,7 +980,29 @@ test "qsm blog filesystem open-write-read examples pass with symbolic handles" {
980980
"OpenTwo", "SuccessfulRead",
981981
]),
982982
)
983-
assert_eq(summary.commands_run, commands.length())
983+
inspect(summary.commands_run, content="12")
984+
inspect(
985+
@debug.to_string(summary.labels),
986+
content="[\"OpenTwo\", \"SuccessfulRead\"]",
987+
)
988+
inspect(
989+
@quickcheck_statemachine.format_commands(summary.commands),
990+
content=(
991+
#|0: FsSymOpen({ dir: { parts: [] }, name: "a" }) -> FsSymSucceeded(FsSymHandle({ id: 0 }))
992+
#|1: FsSymOpen({ dir: { parts: [] }, name: "b" }) -> FsSymSucceeded(FsSymHandle({ id: 1 }))
993+
#|2: FsSymWrite(Symbolic({ id: 0 }), "Hi") -> FsSymSucceeded(FsSymUnit)
994+
#|3: FsSymWrite(Symbolic({ id: 1 }), "Bye") -> FsSymSucceeded(FsSymUnit)
995+
#|4: FsSymClose(Symbolic({ id: 0 })) -> FsSymSucceeded(FsSymUnit)
996+
#|5: FsSymRead({ dir: { parts: [] }, name: "a" }) -> FsSymSucceeded(FsSymText("Hi"))
997+
#|6: FsSymClose(Symbolic({ id: 1 })) -> FsSymSucceeded(FsSymUnit)
998+
#|7: FsSymRead({ dir: { parts: [] }, name: "b" }) -> FsSymSucceeded(FsSymText("Bye"))
999+
#|8: FsSymMkDir({ parts: ["z"] }) -> FsSymSucceeded(FsSymUnit)
1000+
#|9: FsSymOpen({ dir: { parts: ["z"] }, name: "b" }) -> FsSymSucceeded(FsSymHandle({ id: 2 }))
1001+
#|10: FsSymClose(Symbolic({ id: 2 })) -> FsSymSucceeded(FsSymUnit)
1002+
#|11: FsSymRead({ dir: { parts: ["z"] }, name: "b" }) -> FsSymSucceeded(FsSymText(""))
1003+
#|
1004+
),
1005+
)
9841006
}
9851007

9861008
///|
@@ -989,7 +1011,7 @@ test "qsm blog filesystem open-write-read examples pass with symbolic handles" {
9891011
// introduced that variable. The property checks that generation exhausts its
9901012
// retries and reports a deadlock at step zero instead of producing a program
9911013
// that would fail later during reification.
992-
test "qsm blog precondition rejects handles that were never created" {
1014+
test "qsm precondition rejects handles that were never created" {
9931015
let result = @quickcheck_statemachine.check(
9941016
fs_unknown_handle_probe_spec(),
9951017
config=case_config(max_commands=1, max_tries=3),
@@ -1000,9 +1022,12 @@ test "qsm blog precondition rejects handles that were never created" {
10001022
) else {
10011023
fail("expected unknown handle to exhaust generation attempts")
10021024
}
1003-
assert_eq(step_index, 0)
1004-
assert_eq(model.created_handles.length(), 0)
1005-
assert_eq(commands.length(), 0)
1025+
inspect(step_index, content="0")
1026+
inspect(
1027+
@debug.to_string(model),
1028+
content="{ dirs: [{ parts: [] }], files: [], handles: [], created_handles: [] }",
1029+
)
1030+
inspect(@quickcheck_statemachine.format_commands(commands), content="")
10061031
}
10071032

10081033
///|
@@ -1012,7 +1037,7 @@ test "qsm blog precondition rejects handles that were never created" {
10121037
// the intentionally liberal precondition: closed-handle writes remain legal test
10131038
// commands so the model can verify the expected `HandleClosed` response and the
10141039
// label can record that coverage.
1015-
test "qsm blog precondition still allows closed handle errors" {
1040+
test "qsm precondition still allows closed handle errors" {
10161041
let root = fs_root_dir()
10171042
let a = fs_file(root, "a")
10181043
let commands : Array[FsSymCommand] = [
@@ -1026,7 +1051,23 @@ test "qsm blog precondition still allows closed handle errors" {
10261051
"ClosedHandleWrite",
10271052
]),
10281053
)
1029-
assert_eq(summary.commands_run, 3)
1054+
inspect(summary.commands_run, content="3")
1055+
inspect(@debug.to_string(summary.labels), content="[\"ClosedHandleWrite\"]")
1056+
inspect(
1057+
@quickcheck_statemachine.format_history(summary.history),
1058+
content=(
1059+
#|Invocation(
1060+
#| pid={ id: 0 },
1061+
#| command=FsOpen({ dir: { parts: [] }, name: "a" }),
1062+
#|)
1063+
#|Response(pid={ id: 0 }, response=FsSucceeded(FsHandle(0)))
1064+
#|Invocation(pid={ id: 1 }, command=FsClose(0))
1065+
#|Response(pid={ id: 1 }, response=FsSucceeded(FsUnit))
1066+
#|Invocation(pid={ id: 2 }, command=FsWrite(0, "late write"))
1067+
#|Response(pid={ id: 2 }, response=FsFailed(FsHandleClosed))
1068+
#|
1069+
),
1070+
)
10301071
}
10311072

10321073
///|
@@ -1036,7 +1077,7 @@ test "qsm blog precondition still allows closed handle errors" {
10361077
// The property verifies that the postcondition catches this mismatch and that
10371078
// the recorded history contains the full three-command prefix needed to explain
10381079
// the failure.
1039-
test "qsm blog filesystem catches missing busy read check" {
1080+
test "qsm filesystem catches missing busy read check" {
10401081
let root = fs_root_dir()
10411082
let a = fs_file(root, "a")
10421083
let commands : Array[FsSymCommand] = [
@@ -1057,17 +1098,50 @@ test "qsm blog filesystem catches missing busy read check" {
10571098
model~,
10581099
next_model~,
10591100
history~,
1101+
logic~,
10601102
..
10611103
)
10621104
) else {
10631105
fail("expected busy read mismatch")
10641106
}
1065-
assert_eq(step_index, 2)
1066-
assert_true(command is FsRead(_))
1067-
assert_true(response is FsSucceeded(FsText("Hi")))
1068-
assert_true(fs_postcondition(model, command, response).eval() == false)
1069-
assert_true(next_model == model)
1070-
assert_eq(@quickcheck_statemachine.make_operations(history).length(), 3)
1071-
assert_eq(model.handles.length(), 1)
1072-
assert_true(model.handles[0].file == a)
1107+
inspect(
1108+
"\{step_index}: \{@debug.to_string(command)} -> \{@debug.to_string(response)}, postcondition=\{fs_postcondition(model, command, response).eval()}",
1109+
content=(
1110+
#|2: FsRead({ dir: { parts: [] }, name: "a" }) -> FsSucceeded(FsText("Hi")), postcondition=false
1111+
),
1112+
)
1113+
inspect(
1114+
@debug.to_string(model),
1115+
content=(
1116+
#|{
1117+
#| dirs: [{ parts: [] }],
1118+
#| files: [{ file: { dir: { parts: [] }, name: "a" }, content: "Hi" }],
1119+
#| handles: [{ handle: 0, file: { dir: { parts: [] }, name: "a" } }],
1120+
#| next_handle: 1,
1121+
#|}
1122+
),
1123+
)
1124+
inspect(next_model == model, content="true")
1125+
inspect(
1126+
@quickcheck_statemachine.format_history(history),
1127+
content=(
1128+
#|Invocation(
1129+
#| pid={ id: 0 },
1130+
#| command=FsOpen({ dir: { parts: [] }, name: "a" }),
1131+
#|)
1132+
#|Response(pid={ id: 0 }, response=FsSucceeded(FsHandle(0)))
1133+
#|Invocation(pid={ id: 1 }, command=FsWrite(0, "Hi"))
1134+
#|Response(pid={ id: 1 }, response=FsSucceeded(FsUnit))
1135+
#|Invocation(
1136+
#| pid={ id: 2 },
1137+
#| command=FsRead({ dir: { parts: [] }, name: "a" }),
1138+
#|)
1139+
#|Response(pid={ id: 2 }, response=FsSucceeded(FsText("Hi")))
1140+
#|
1141+
),
1142+
)
1143+
inspect(
1144+
@quickcheck_statemachine.format_counterexample(logic.counterexample()),
1145+
content="filesystem response matches model",
1146+
)
10731147
}

jug_test.mbt

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,18 @@ fn jug_solution_spec() -> @quickcheck_statemachine.StateMachine[
143143
test "die hard jug model covers alternate commands" {
144144
let model = JugModel::{ big_jug: 1, small_jug: 2 }
145145
let fill_small = jug_next(model, JugFillSmall)
146-
assert_eq(fill_small.big_jug, 1)
147-
assert_eq(fill_small.small_jug, 3)
148146
let empty_big = jug_next(model, JugEmptyBig)
149-
assert_eq(empty_big.big_jug, 0)
150-
assert_eq(empty_big.small_jug, 2)
151147
let small_into_big = jug_next(model, JugSmallIntoBig)
152-
assert_eq(small_into_big.big_jug, 3)
153-
assert_eq(small_into_big.small_jug, 0)
148+
inspect(
149+
@debug.to_string([fill_small, empty_big, small_into_big]),
150+
content=(
151+
#|[
152+
#| { big_jug: 1, small_jug: 3 },
153+
#| { big_jug: 0, small_jug: 2 },
154+
#| { big_jug: 3, small_jug: 0 },
155+
#|]
156+
),
157+
)
154158
}
155159

156160
///|
@@ -174,18 +178,51 @@ test "die hard jug reports the 4 liter solution" {
174178
model~,
175179
next_model~,
176180
commands~,
181+
history~,
182+
logic~,
177183
..
178184
)
179185
) else {
180186
fail("expected jug solution to be reported as a postcondition failure")
181187
}
182-
assert_eq(step_index, 5)
183-
assert_true(command is JugBigIntoSmall)
184-
assert_true(response is JugDone)
185-
assert_eq(model.big_jug, 5)
186-
assert_eq(model.small_jug, 2)
187-
assert_eq(next_model.big_jug, 4)
188-
assert_eq(next_model.small_jug, 3)
189-
assert_eq(commands.length(), 6)
190-
assert_true(commands.commands[4].command is JugFillBig)
188+
inspect(
189+
"\{step_index}: \{@debug.to_string(command)} -> \{@debug.to_string(response)}, model=\{@debug.to_string(model)}, next=\{@debug.to_string(next_model)}",
190+
content=(
191+
#|5: JugBigIntoSmall -> JugDone, model={ big_jug: 5, small_jug: 2 }, next={ big_jug: 4, small_jug: 3 }
192+
),
193+
)
194+
inspect(
195+
@quickcheck_statemachine.format_commands(commands),
196+
content=(
197+
#|0: JugFillBig -> JugDone
198+
#|1: JugBigIntoSmall -> JugDone
199+
#|2: JugEmptySmall -> JugDone
200+
#|3: JugBigIntoSmall -> JugDone
201+
#|4: JugFillBig -> JugDone
202+
#|5: JugBigIntoSmall -> JugBigJugIs4
203+
#|
204+
),
205+
)
206+
inspect(
207+
@quickcheck_statemachine.format_history(history),
208+
content=(
209+
#|Invocation(pid={ id: 0 }, command=JugFillBig)
210+
#|Response(pid={ id: 0 }, response=JugDone)
211+
#|Invocation(pid={ id: 1 }, command=JugBigIntoSmall)
212+
#|Response(pid={ id: 1 }, response=JugDone)
213+
#|Invocation(pid={ id: 2 }, command=JugEmptySmall)
214+
#|Response(pid={ id: 2 }, response=JugDone)
215+
#|Invocation(pid={ id: 3 }, command=JugBigIntoSmall)
216+
#|Response(pid={ id: 3 }, response=JugDone)
217+
#|Invocation(pid={ id: 4 }, command=JugFillBig)
218+
#|Response(pid={ id: 4 }, response=JugDone)
219+
#|Invocation(pid={ id: 5 }, command=JugBigIntoSmall)
220+
#|Response(pid={ id: 5 }, response=JugDone)
221+
#|
222+
),
223+
)
224+
inspect(
225+
@quickcheck_statemachine.format_counterexample(logic.counterexample()),
226+
content="jug reports 4 liter solution",
227+
)
191228
}

0 commit comments

Comments
 (0)