pg-verify 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -3
- data/README.md +1 -1
- data/data/project-template/README.md +81 -0
- data/doc/examples/vending_machine/checkpoint_1.rb +29 -0
- data/doc/examples/vending_machine/checkpoint_2.rb +47 -0
- data/doc/examples/vending_machine/checkpoint_3.rb +68 -0
- data/doc/examples/vending_machine/checkpoint_4.rb +202 -0
- data/integration_tests/ruby_dsl/001_states.rb +2 -1
- data/integration_tests/ruby_dsl/002_transitions.rb +2 -1
- data/integration_tests/ruby_dsl/017_ctl_specifications.rb +19 -0
- data/integration_tests/ruby_dsl/018_non_atomic_hazard.rb +17 -0
- data/integration_tests/ruby_dsl/019_multiple_actions.rb +21 -0
- data/integration_tests/ruby_dsl/020_vending_machine.rb +188 -0
- data/lib/pg-verify/cli/cli.rb +80 -24
- data/lib/pg-verify/cli/cli_utils.rb +61 -0
- data/lib/pg-verify/interpret/component_context.rb +1 -1
- data/lib/pg-verify/interpret/interpret.rb +1 -1
- data/lib/pg-verify/interpret/pg_script.rb +1 -0
- data/lib/pg-verify/model/parsed_expression.rb +10 -5
- data/lib/pg-verify/model/simulation/trace.rb +15 -4
- data/lib/pg-verify/model/validation/errors.rb +21 -2
- data/lib/pg-verify/nusmv/runner.rb +69 -17
- data/lib/pg-verify/puml/puml.rb +1 -0
- data/lib/pg-verify/puml/runner.rb +0 -0
- data/lib/pg-verify/transform/hash_transformation.rb +46 -14
- data/lib/pg-verify/transform/nusmv_transformation.rb +4 -3
- data/lib/pg-verify/transform/puml_transformation.rb +27 -8
- data/lib/pg-verify/version.rb +1 -1
- data/vscript.rb +64 -0
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61be58b97dd5e5beb6546f87ef6b2f19b2d372e983fb3b93332c88a0f0b489ad
|
4
|
+
data.tar.gz: 6fc1e14c40748353219ca58d69b034b2970c57da5d19f2ad27ee842c66847035
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbf7170ad7ebff0f939a1ed3309d82ae45246cfbc670475be37f7c0f72f17e516d5e9a73427ebb23dc5403de48693f4dc26db194ac299523af0c119063c75cc4
|
7
|
+
data.tar.gz: c0b0b0c88b8e4a54465c857f5daaaa06826e69c2d9c2149e248863b2d8e627ab6348b59e842352ea24a3c453a0319d9d6054205337ab6de6c5bd88fa6f395047
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pg-verify (0.1.
|
4
|
+
pg-verify (0.1.2)
|
5
5
|
config (~> 4.2.1)
|
6
6
|
plantuml_builder (~> 0.3.0)
|
7
7
|
rainbow (~> 3.0.0)
|
@@ -10,7 +10,7 @@ PATH
|
|
10
10
|
GEM
|
11
11
|
remote: https://rubygems.org/
|
12
12
|
specs:
|
13
|
-
concurrent-ruby (1.2.
|
13
|
+
concurrent-ruby (1.2.3)
|
14
14
|
config (4.2.1)
|
15
15
|
deep_merge (~> 1.2, >= 1.2.1)
|
16
16
|
dry-validation (~> 1.0, >= 1.0.0)
|
@@ -66,7 +66,7 @@ GEM
|
|
66
66
|
diff-lcs (>= 1.2.0, < 2.0)
|
67
67
|
rspec-support (~> 3.12.0)
|
68
68
|
rspec-support (3.12.0)
|
69
|
-
thor (1.2.
|
69
|
+
thor (1.2.2)
|
70
70
|
|
71
71
|
PLATFORMS
|
72
72
|
x86_64-darwin-19
|
data/README.md
CHANGED
@@ -26,4 +26,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
26
26
|
|
27
27
|
## Contributing
|
28
28
|
|
29
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
29
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/aweber/pg-verify.
|
@@ -7,6 +7,7 @@ To get started you can run `pg-verify doctor` to verify your installation and gu
|
|
7
7
|
you through the steps needed to install external addons like the NuSMV model checker.
|
8
8
|
|
9
9
|
You can always run `pg-verify help` to get a list of available commands.
|
10
|
+
Run `pg-verify help <command>` to get more information about a specific command and its options.
|
10
11
|
For example try running `pg-verify show png` to render a PNG image of your program graph
|
11
12
|
and save that to your working directory.
|
12
13
|
|
@@ -15,4 +16,84 @@ and save that to your working directory.
|
|
15
16
|
There are a couple of prelude files and directories in your project to get you started:
|
16
17
|
|
17
18
|
- `program-graph.rb`: This file defines the default program graph you will be working on
|
19
|
+
- `.pg-verify.yml`: This file can be used to configure pg-verify
|
18
20
|
- `addon/`: This is the directory where you will place addon resources like the NuSMV executable.
|
21
|
+
|
22
|
+
## Writing specifications
|
23
|
+
|
24
|
+
The specification framework for the Ruby DSL is inspired by the popular
|
25
|
+
testing library [rspec](https://rspec.info/). Here is an example of the syntax:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# Wrap two specifications which are related to some "Car" model.
|
29
|
+
specify "The car" do
|
30
|
+
it "won't crash" => :"G distance_to_tree > 0" # Becomes: The car won't crash
|
31
|
+
it "will drive" => :"F velocity > 0" # Becomes: The car will drive
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
You wrap your specification using one or more `specify` blocks.
|
36
|
+
Within those blocks you can declare a *specification* using the `it` keyword.
|
37
|
+
Each specification consists of two parts:
|
38
|
+
- A **text** which is used to describe the spec in natural language
|
39
|
+
- An **expression** given in LTL or CTL. This will actually be checked
|
40
|
+
|
41
|
+
The outer block serves as a wrapper for specifications which are related.
|
42
|
+
In the example above we declare two specifications which are both concerning
|
43
|
+
some "Car" model.
|
44
|
+
|
45
|
+
You can read those specifications from outside to inside.
|
46
|
+
The `it` semantically refers to the outer block (the car in our case).
|
47
|
+
So when reading `it "won't crash"`, **it** refers to **the car**.
|
48
|
+
Thus this specification becomes "The car won't crash" when it is expanded.
|
49
|
+
|
50
|
+
### Assumption blocks
|
51
|
+
|
52
|
+
You can consolidate multiple preconditions for your specifications into
|
53
|
+
an `assuming` block. Here's the syntax:
|
54
|
+
|
55
|
+
```Ruby
|
56
|
+
specify "The car" do
|
57
|
+
assuming "the breaks don't fail" => :"G BreakFailure == No" do
|
58
|
+
it "won't crash" => :"G distance_to_tree > 0" # Becomes: The car (assuming the breaks don't fail) won't crash
|
59
|
+
it "will drive" => :"F velocity > 0" # Becomes: The car (assuming the breaks don't fail) will drive
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
Much like the specifications themselves, the assuming block expects a `text` and an `expression`.
|
65
|
+
For each contained specification, the assumption expression will be prepended to the spec
|
66
|
+
while expanding. Thus for the example above we get two expanded specifications:
|
67
|
+
|
68
|
+
```
|
69
|
+
1) The car (assuming the breaks don't fail) won't crash
|
70
|
+
( G BreakFailure == No ) => G distance_to_tree > 0
|
71
|
+
|
72
|
+
2) The car (assuming the breaks don't fail) will drive
|
73
|
+
( G BreakFailure == No ) => F velocity > 0
|
74
|
+
```
|
75
|
+
|
76
|
+
### Assuming no errors
|
77
|
+
|
78
|
+
The imagined use case for the `assume` block is to specify model properties under exclusion of errors.
|
79
|
+
|
80
|
+
```Ruby
|
81
|
+
specify "The car" do
|
82
|
+
assuming no_errors do
|
83
|
+
it "won't crash" => :"G distance_to_tree > 0" # Becomes: The car (assuming the breaks don't fail) won't crash
|
84
|
+
it "will drive" => :"F velocity > 0" # Becomes: The car (assuming the breaks don't fail) will drive
|
85
|
+
end
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
The thing that's new here is the `no_errors` keyword.
|
90
|
+
All this does is generate a *text* and *expression* to be used by the `assuming` block.
|
91
|
+
Say you had three error graphs: `BreakFailure`, `UsageFailure` and `MotorFailure`.
|
92
|
+
Then `no_errors` would produce this expression:
|
93
|
+
|
94
|
+
```
|
95
|
+
G ( BreakFailure == No && UsageFailure == No && MotorFailure == No )
|
96
|
+
```
|
97
|
+
|
98
|
+
Instead of `no_errors` you can also use `only(...)` to limit the possibility of errors to the
|
99
|
+
ones you provide. e.g: `assuming only(:BreakFailure, :UsageFailure) do ...`.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Create graphs with default states
|
2
|
+
|
3
|
+
model :VendingMachine do
|
4
|
+
|
5
|
+
graph :User do
|
6
|
+
states :todo
|
7
|
+
end
|
8
|
+
|
9
|
+
graph :CoinReader do
|
10
|
+
states :todo
|
11
|
+
end
|
12
|
+
|
13
|
+
graph :Controller do
|
14
|
+
states :todo
|
15
|
+
end
|
16
|
+
|
17
|
+
graph :LED do
|
18
|
+
states :todo
|
19
|
+
end
|
20
|
+
|
21
|
+
graph :Dispenser do
|
22
|
+
states :todo
|
23
|
+
end
|
24
|
+
|
25
|
+
graph :CoinDispenser do
|
26
|
+
states :todo
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Add products and coins
|
2
|
+
|
3
|
+
model :VendingMachine do
|
4
|
+
|
5
|
+
products = {
|
6
|
+
cola: 1.2,
|
7
|
+
chips: 2.5
|
8
|
+
}
|
9
|
+
products = products.map { |c, v| [c, (v * 10).to_i] }.to_h
|
10
|
+
|
11
|
+
coins = {
|
12
|
+
ten_cents: 10,
|
13
|
+
twenty_cents: 20,
|
14
|
+
fifty_cents: 50,
|
15
|
+
one_euro: 100,
|
16
|
+
two_euro: 200
|
17
|
+
}
|
18
|
+
coins = coins.map { |c, v| [c, (v / 10).to_i] }.to_h
|
19
|
+
|
20
|
+
MAX_MONEY = 100
|
21
|
+
START_MONEY = 20
|
22
|
+
|
23
|
+
graph :User do
|
24
|
+
states :todo
|
25
|
+
end
|
26
|
+
|
27
|
+
graph :CoinReader do
|
28
|
+
states :todo
|
29
|
+
end
|
30
|
+
|
31
|
+
graph :Controller do
|
32
|
+
states :todo
|
33
|
+
end
|
34
|
+
|
35
|
+
graph :LED do
|
36
|
+
states :todo
|
37
|
+
end
|
38
|
+
|
39
|
+
graph :Dispenser do
|
40
|
+
states :todo
|
41
|
+
end
|
42
|
+
|
43
|
+
graph :CoinDispenser do
|
44
|
+
states :todo
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Add states and variables
|
2
|
+
|
3
|
+
model :VendingMachine do
|
4
|
+
|
5
|
+
products = {
|
6
|
+
cola: 1.2,
|
7
|
+
chips: 2.5
|
8
|
+
}
|
9
|
+
products = products.map { |c, v| [c, (v * 10).to_i] }.to_h
|
10
|
+
|
11
|
+
coins = {
|
12
|
+
ten_cents: 10,
|
13
|
+
twenty_cents: 20,
|
14
|
+
fifty_cents: 50,
|
15
|
+
one_euro: 100,
|
16
|
+
two_euro: 200
|
17
|
+
}
|
18
|
+
coins = coins.map { |c, v| [c, (v / 10).to_i] }.to_h
|
19
|
+
|
20
|
+
|
21
|
+
MAX_MONEY = 100
|
22
|
+
START_MONEY = 20
|
23
|
+
|
24
|
+
graph :User do
|
25
|
+
press_states = products.keys.map { |product| :"press_#{product}" }
|
26
|
+
grab_states = products.keys.map { |product| :"grab_#{product}" }
|
27
|
+
insert_states = coins.keys.map { |coin| :"insert_#{coin}" }
|
28
|
+
|
29
|
+
var pocket_money: (0..MAX_MONEY), init: START_MONEY
|
30
|
+
var value_in_products: (0..MAX_MONEY), init: 0
|
31
|
+
|
32
|
+
states :inserting, *insert_states, \
|
33
|
+
:pressing, *press_states, \
|
34
|
+
:waiting, :grab_product, \
|
35
|
+
:grab_change, \
|
36
|
+
:done, \
|
37
|
+
init: :inserting
|
38
|
+
end
|
39
|
+
|
40
|
+
graph :CoinReader do
|
41
|
+
states :reading
|
42
|
+
|
43
|
+
var read_value: (0..coins.values.max), init: 0
|
44
|
+
end
|
45
|
+
|
46
|
+
graph :Controller do
|
47
|
+
var budget: (0..MAX_MONEY), init: 0
|
48
|
+
|
49
|
+
dispense_states = products.keys.map { |product| :"dispense_#{product}" }
|
50
|
+
states :accepting, *dispense_states, :rejected, :done, init: :accepting
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
graph :LED do
|
55
|
+
states :idle, :red, :green, init: :idle
|
56
|
+
end
|
57
|
+
|
58
|
+
graph :Dispenser do
|
59
|
+
dispensed_states = products.keys.map { |product| :"dispensed_#{product}" }
|
60
|
+
states :empty, *dispensed_states, init: :empty
|
61
|
+
end
|
62
|
+
|
63
|
+
graph :CoinDispenser do
|
64
|
+
states :idle, :dispensed_change, init: :idle
|
65
|
+
var change: (0..MAX_MONEY), init: 0
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# Finish it all up
|
2
|
+
|
3
|
+
# CONFIG
|
4
|
+
# trace:
|
5
|
+
# colors:
|
6
|
+
# insert_*: green
|
7
|
+
# press_*: yellow
|
8
|
+
# dispense_*: yellow
|
9
|
+
# accepting: sidenote
|
10
|
+
# idle: sidenote
|
11
|
+
# empty: sidenote
|
12
|
+
# reading: sidenote
|
13
|
+
|
14
|
+
model :VendingMachine do
|
15
|
+
|
16
|
+
products = {
|
17
|
+
cola: 1.2,
|
18
|
+
chips: 2.5
|
19
|
+
}.map { |c, v| [c, (v * 10).to_i] }.to_h
|
20
|
+
|
21
|
+
coins = {
|
22
|
+
ten_cents: 10,
|
23
|
+
twenty_cents: 20,
|
24
|
+
fifty_cents: 50,
|
25
|
+
one_euro: 100,
|
26
|
+
two_euro: 200
|
27
|
+
}.map { |c, v| [c, (v / 10).to_i] }.to_h
|
28
|
+
|
29
|
+
MAX_MONEY = 100
|
30
|
+
START_MONEY = 20
|
31
|
+
|
32
|
+
transient error :CoinReadFails
|
33
|
+
|
34
|
+
graph :User do
|
35
|
+
press_states = products.keys.map { |product| :"press_#{product}" }
|
36
|
+
grab_states = products.keys.map { |product| :"grab_#{product}" }
|
37
|
+
insert_states = coins.keys.map { |coin| :"insert_#{coin}" }
|
38
|
+
|
39
|
+
var pocket_money: (0..MAX_MONEY), init: START_MONEY
|
40
|
+
var value_in_products: (0..MAX_MONEY), init: 0
|
41
|
+
|
42
|
+
states :inserting, *insert_states, \
|
43
|
+
:pressing, *press_states, \
|
44
|
+
:waiting, :grab_product, \
|
45
|
+
:grab_change, \
|
46
|
+
:done, \
|
47
|
+
init: :inserting
|
48
|
+
|
49
|
+
# The user can insert coins until the money runs out
|
50
|
+
coins.each { |coin, value|
|
51
|
+
transition :inserting => :"insert_#{coin}" do
|
52
|
+
guard "pocket_money - #{value} >= 0"
|
53
|
+
action "pocket_money := pocket_money - #{value}"
|
54
|
+
end
|
55
|
+
transition :"insert_#{coin}" => :inserting
|
56
|
+
}
|
57
|
+
# The user can decide at any point to start pressing buttons
|
58
|
+
transition :inserting => :pressing
|
59
|
+
|
60
|
+
# Press one button and wait for the result
|
61
|
+
products.each { |product, value|
|
62
|
+
transition :pressing => :"press_#{product}"
|
63
|
+
transition :"press_#{product}" => :waiting
|
64
|
+
}
|
65
|
+
|
66
|
+
# Grab a product and take note of the value
|
67
|
+
products.each { |product, value|
|
68
|
+
transition :waiting => :grab_product do
|
69
|
+
precon "value_in_products + #{value} < #{MAX_MONEY}"
|
70
|
+
guard "LED == green && Dispenser == dispensed_#{product}"
|
71
|
+
action "value_in_products := value_in_products + #{value}"
|
72
|
+
end
|
73
|
+
}
|
74
|
+
transition :grab_product => :grab_change
|
75
|
+
|
76
|
+
# Grab change directly on fail
|
77
|
+
transition :waiting => :grab_change do
|
78
|
+
guard "LED == red"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Grab the change and be done
|
82
|
+
transition :grab_change => :done do
|
83
|
+
precon "pocket_money + change <= #{MAX_MONEY}"
|
84
|
+
action "pocket_money := pocket_money + change"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
graph :CoinReader do
|
89
|
+
states :reading
|
90
|
+
|
91
|
+
var read_value: (0..coins.values.max), init: 0
|
92
|
+
|
93
|
+
coins.each { |coin, value|
|
94
|
+
transition :reading => :reading do
|
95
|
+
guard "User == insert_#{coin} && CoinReadFails == No"
|
96
|
+
action "read_value := #{value}"
|
97
|
+
end
|
98
|
+
}
|
99
|
+
|
100
|
+
transition :reading => :reading do
|
101
|
+
guard coins.keys.map { |coin| "User != insert_#{coin}" }.join(" && ")
|
102
|
+
action "read_value := 0"
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
graph :Controller do
|
108
|
+
var budget: (0..MAX_MONEY), init: 0
|
109
|
+
|
110
|
+
dispense_states = products.keys.map { |product| :"dispense_#{product}" }
|
111
|
+
|
112
|
+
states :accepting, *dispense_states, :rejected, :done, init: :accepting
|
113
|
+
|
114
|
+
# Accept coin reads from the reader
|
115
|
+
transition :accepting => :accepting do
|
116
|
+
precon "budget + read_value <= #{MAX_MONEY}"
|
117
|
+
guard "read_value > 0"
|
118
|
+
action "budget := budget + read_value"
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
products.each { |product, value|
|
123
|
+
|
124
|
+
# Accept button presses when enough money
|
125
|
+
transition :accepting => :"dispense_#{product}" do
|
126
|
+
guard "User == press_#{product} && budget >= #{value}"
|
127
|
+
action "budget := budget - #{value}"
|
128
|
+
end
|
129
|
+
transition :"dispense_#{product}" => :done
|
130
|
+
|
131
|
+
# Reject button presses when not enough money
|
132
|
+
transition :accepting => :rejected do
|
133
|
+
guard "User == press_#{product} && budget < #{value}"
|
134
|
+
end
|
135
|
+
}
|
136
|
+
|
137
|
+
# Reset when the coin dispenser has dispensed
|
138
|
+
transition :accepting => :accepting do
|
139
|
+
guard "CoinDispenser == dispensed_change"
|
140
|
+
action "budget := 0"
|
141
|
+
end
|
142
|
+
transition :done => :accepting do
|
143
|
+
guard "CoinDispenser == dispensed_change"
|
144
|
+
action "budget := 0"
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
graph :LED do
|
151
|
+
states :idle, :red, :green, init: :idle
|
152
|
+
|
153
|
+
transition :idle => :green do
|
154
|
+
guard "Controller == done"
|
155
|
+
end
|
156
|
+
transition :idle => :red do
|
157
|
+
guard "Controller == rejected"
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
graph :Dispenser do
|
163
|
+
dispensed_states = products.keys.map { |product| :"dispensed_#{product}" }
|
164
|
+
states :empty, *dispensed_states, init: :empty
|
165
|
+
|
166
|
+
# Dispense the product the Controller tells us to
|
167
|
+
products.each { |product, value|
|
168
|
+
transition :empty => :"dispensed_#{product}" do
|
169
|
+
guard "Controller == dispense_#{product}"
|
170
|
+
end
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
graph :CoinDispenser do
|
175
|
+
states :idle, :dispensed_change, init: :idle
|
176
|
+
var change: (0..MAX_MONEY), init: 0
|
177
|
+
|
178
|
+
# Eject the change money
|
179
|
+
transition :idle => :dispensed_change do
|
180
|
+
guard "Controller == rejected || Controller == done"
|
181
|
+
action "change := budget"
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
specify "The vending machine" do
|
187
|
+
|
188
|
+
it "allows the user to buy something" => :"EF value_in_products > 0"
|
189
|
+
it "always completes the transaction" => :"F User == done"
|
190
|
+
|
191
|
+
products.each { |product, value|
|
192
|
+
assuming "the product can be bough" => :"pocket_money >= #{value}" do
|
193
|
+
it "allows the user to buy #{product}" => :"EF Dispenser == dispensed_#{product}"
|
194
|
+
end
|
195
|
+
}
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
hazard "The user looses money" => :"User == done && pocket_money + value_in_products < #{START_MONEY}"
|
200
|
+
hazard "The machine looses money" => :"User == done && pocket_money + value_in_products > #{START_MONEY}"
|
201
|
+
|
202
|
+
end
|
@@ -4,7 +4,8 @@ model :TestTransitions do
|
|
4
4
|
end
|
5
5
|
specify "The state" do
|
6
6
|
it "starts in initial" => :"Test == initial"
|
7
|
-
it "stays in initial" => :"G Test == initial"
|
7
|
+
it "stays in initial (LTL)" => :"G Test == initial"
|
8
|
+
it "stays in initial (CTL)" => :"AG Test == initial"
|
8
9
|
it "never leaves initial" => :"! F Test != initial"
|
9
10
|
end
|
10
11
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
model :CtlSpecifications do
|
2
|
+
|
3
|
+
graph :Example do
|
4
|
+
states :one, :two, :three, init: :one
|
5
|
+
|
6
|
+
transition :one => :two
|
7
|
+
transition :two => :one
|
8
|
+
transition :one => :three
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "The model" do
|
13
|
+
it "starts in state one" => :"Example == one"
|
14
|
+
it "cannot stay in one" => :"! G Example == one"
|
15
|
+
it "can stay in three" => :"EX AG Example == three"
|
16
|
+
it "cannot go back to one from three" => :"AG ( (Example == three) => ! EF Example == one)"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
model :NonAtomicHazard do
|
2
|
+
|
3
|
+
persistent error :KeysForgotten
|
4
|
+
|
5
|
+
graph :Person do
|
6
|
+
states :inside, :outside, init: :inside
|
7
|
+
transition :inside => :outside
|
8
|
+
transition :outside => :inside do
|
9
|
+
guard "KeysForgotten == No"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# This does not work as at any point the fault could not have occurred
|
14
|
+
# (KeysForgotten == No) already but then it happens at a later point
|
15
|
+
# Expected Cut Sets: { :"" }
|
16
|
+
# hazard "The person can't get in" => :"G Person == outside"
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
model :MultipleActions do
|
2
|
+
|
3
|
+
graph :GraphWithMultipleActions do
|
4
|
+
states :one, :two, init: :one
|
5
|
+
|
6
|
+
var a: (0..10), init: 0
|
7
|
+
var b: (0..10), init: 0
|
8
|
+
var c: (0..10), init: 0
|
9
|
+
|
10
|
+
transition :one => :two do
|
11
|
+
action "a := 1 + 1 | b := 0 | c := 25 - (2 * 10)"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
specify "The graph" do
|
16
|
+
it "set a = 2" => :"X a == 2"
|
17
|
+
it "set b = 0" => :"X b == 0"
|
18
|
+
it "set c = 5" => :"X c == 5"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|