-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpractical_tlaplus_chap1.py
84 lines (69 loc) · 2.31 KB
/
practical_tlaplus_chap1.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import timewinder
from timewinder.generators import Set # noqa
# This model intends to check the correctness of bank
# accounts and moving money between them, with different
# constraints.
#
# This is adapted from Chapter 1 of _Practical_TLA+_ by
# Hillel Wayne
# Start by defining an account.
@timewinder.object
class Account:
def __init__(self, name, amt):
self.name = name
self.acc = amt
# The simplest transfer withdraws from the sender, and then,
# sometime later, puts that amount in the recieving account.
@timewinder.process
def withdraw(sender, reciever, amount):
sender.acc = sender.acc - amount
yield "deposit"
reciever.acc = reciever.acc + amount
# This is a more intelligent transfer, that checks that the
# amount to transfer isn't more than the account sender has
# in their account before continuing.
@timewinder.process
def check_and_withdraw(sender, reciever, amt):
if amt <= sender.acc:
sender.acc = sender.acc - amt
yield "deposit"
reciever.acc = reciever.acc + amt
# Instantiate our example account objects
alice = Account("alice", 5)
bob = Account("bob", 5)
# Create a predicate that says, at every stage, all Account
# objects must carry positive balances.
no_overdrafts = timewinder.ForAll(Account, lambda a: a.acc >= 0)
# Run the evaluator.
ev = timewinder.Evaluator(
# Pass our two objects
objects=[alice, bob],
# Declare the predicates we want to check.
specs=[no_overdrafts],
# Only have one thread do a withdrawal of too much
# money, and it should fail.
#
threads=[withdraw(alice, bob, 6)],
# Alternately, run two threads, both withdrawing from Alice and depositing
# to Bob's account. The `Set` function is a generator that will
# try every transfer amount from 1 to 5, as per Python's `range`
# builtin.
#
# This should fail, as both must complete and will in some cases
# (both amounts at 1) but will fail if too much is transferred.
#
# threads=[
# check_and_withdraw(alice, bob, Set(range(1, 6))),
# check_and_withdraw(alice, bob, Set(range(1, 6))),
# ],
)
err = None
try:
ev.evaluate()
except timewinder.ConstraintError as e:
err = e
if err is None:
print(ev.stats)
else:
print(f"\nConstraint Violated: {err.name}\n")
ev.replay_thunk(err.thunk)