pg-verify 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -20
  3. data/README.md +1 -1
  4. data/data/banner.txt +5 -0
  5. data/data/project-template/README.md +81 -0
  6. data/doc/examples/vending_machine/checkpoint_1.rb +29 -0
  7. data/doc/examples/vending_machine/checkpoint_2.rb +47 -0
  8. data/doc/examples/vending_machine/checkpoint_3.rb +68 -0
  9. data/doc/examples/vending_machine/checkpoint_4.rb +202 -0
  10. data/integration_tests/ruby_dsl/001_states.rb +2 -1
  11. data/integration_tests/ruby_dsl/002_transitions.rb +2 -1
  12. data/integration_tests/ruby_dsl/017_ctl_specifications.rb +19 -0
  13. data/integration_tests/ruby_dsl/018_non_atomic_hazard.rb +17 -0
  14. data/integration_tests/ruby_dsl/019_multiple_actions.rb +21 -0
  15. data/integration_tests/ruby_dsl/020_vending_machine.rb +188 -0
  16. data/lib/pg-verify/cli/cli.rb +94 -24
  17. data/lib/pg-verify/cli/cli_utils.rb +61 -0
  18. data/lib/pg-verify/interpret/component_context.rb +1 -1
  19. data/lib/pg-verify/interpret/interpret.rb +1 -1
  20. data/lib/pg-verify/interpret/pg_script.rb +1 -0
  21. data/lib/pg-verify/model/parsed_expression.rb +10 -5
  22. data/lib/pg-verify/model/simulation/trace.rb +15 -4
  23. data/lib/pg-verify/model/validation/errors.rb +21 -2
  24. data/lib/pg-verify/nusmv/runner.rb +69 -17
  25. data/lib/pg-verify/puml/puml.rb +1 -0
  26. data/lib/pg-verify/transform/hash_transformation.rb +46 -14
  27. data/lib/pg-verify/transform/nusmv_transformation.rb +4 -3
  28. data/lib/pg-verify/transform/puml_transformation.rb +27 -8
  29. data/lib/pg-verify/version.rb +1 -1
  30. data/pg-verify.gemspec +0 -1
  31. data/vscript.rb +64 -0
  32. metadata +14 -18
  33. data/data/project-template/program-graph.rb.resource +0 -103
  34. /data/{data/project-template/.pg-verify.yml → lib/pg-verify/puml/runner.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0582b670fd565351fe5f6f01f34e5dd7358f4fd6f2385ced16d7a487c0b65a8d'
4
- data.tar.gz: ea5556b7831154f0330e96e145be74fba25a31d5a775dc5e6abc5d32eaba9b03
3
+ metadata.gz: 61be58b97dd5e5beb6546f87ef6b2f19b2d372e983fb3b93332c88a0f0b489ad
4
+ data.tar.gz: 6fc1e14c40748353219ca58d69b034b2970c57da5d19f2ad27ee842c66847035
5
5
  SHA512:
6
- metadata.gz: fb81fb19377bda7a3c109a5dfb7ee4174c5d529a3133223b784ff814bc68636487664ed07ce8cc6f39eed1cdf99ebd3e20ac027ab5a19da9416f32f6977a5105
7
- data.tar.gz: 5453f990cda9252b0be7b9e6c0a71ce8f1d8fc77f064ef5f3f6eb29c03dbb1a5a44f370e54b8137398c02c0a3643a9135558ea5c20066684545618ab60f5de94
6
+ metadata.gz: cbf7170ad7ebff0f939a1ed3309d82ae45246cfbc670475be37f7c0f72f17e516d5e9a73427ebb23dc5403de48693f4dc26db194ac299523af0c119063c75cc4
7
+ data.tar.gz: c0b0b0c88b8e4a54465c857f5daaaa06826e69c2d9c2149e248863b2d8e627ab6348b59e842352ea24a3c453a0319d9d6054205337ab6de6c5bd88fa6f395047
data/Gemfile.lock CHANGED
@@ -1,9 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pg-verify (0.1.0)
4
+ pg-verify (0.1.2)
5
5
  config (~> 4.2.1)
6
- ebnf (~> 2.3.4)
7
6
  plantuml_builder (~> 0.3.0)
8
7
  rainbow (~> 3.0.0)
9
8
  thor (~> 1.2.1)
@@ -11,7 +10,7 @@ PATH
11
10
  GEM
12
11
  remote: https://rubygems.org/
13
12
  specs:
14
- concurrent-ruby (1.2.2)
13
+ concurrent-ruby (1.2.3)
15
14
  config (4.2.1)
16
15
  deep_merge (~> 1.2, >= 1.2.1)
17
16
  dry-validation (~> 1.0, >= 1.0.0)
@@ -50,21 +49,10 @@ GEM
50
49
  dry-core (~> 0.5, >= 0.5)
51
50
  dry-initializer (~> 3.0)
52
51
  dry-schema (~> 1.8, >= 1.8.0)
53
- ebnf (2.3.4)
54
- htmlentities (~> 4.3)
55
- rdf (~> 3.2)
56
- scanf (~> 1.0)
57
- sxp (~> 1.2)
58
- unicode-types (~> 1.8)
59
- htmlentities (4.3.4)
60
- link_header (0.0.8)
61
- matrix (0.4.2)
62
52
  plantuml_builder (0.3.0)
63
53
  docopt (~> 0.5.0)
64
54
  rainbow (3.0.0)
65
55
  rake (13.0.6)
66
- rdf (3.2.11)
67
- link_header (~> 0.0, >= 0.0.8)
68
56
  rspec (3.12.0)
69
57
  rspec-core (~> 3.12.0)
70
58
  rspec-expectations (~> 3.12.0)
@@ -78,12 +66,7 @@ GEM
78
66
  diff-lcs (>= 1.2.0, < 2.0)
79
67
  rspec-support (~> 3.12.0)
80
68
  rspec-support (3.12.0)
81
- scanf (1.0.0)
82
- sxp (1.2.4)
83
- matrix (~> 0.4)
84
- rdf (~> 3.2)
85
- thor (1.2.1)
86
- unicode-types (1.8.0)
69
+ thor (1.2.2)
87
70
 
88
71
  PLATFORMS
89
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/[USERNAME]/pg-verify.
29
+ Bug reports and pull requests are welcome on GitHub at https://github.com/aweber/pg-verify.
data/data/banner.txt ADDED
@@ -0,0 +1,5 @@
1
+ ___ _____ _ __ _ ___
2
+ / _ \/ ___/ | | / /__ ____(_) _/_ __
3
+ / ___/ (_ / | |/ / -_) __/ / _/ // /
4
+ /_/ \___/ |___/\__/_/ /_/_/ \_, /
5
+ version 0.1.0 /___/
@@ -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
@@ -5,6 +5,7 @@ model :TestTransitions do
5
5
  end
6
6
  specify "The state" do
7
7
  it "starts in one" => :"Test == one"
8
- it "transitions to two" => :"X Test == two"
8
+ it "transitions to two (LTL)" => :"X Test == two"
9
+ it "transitions to two (CTL)" => :"AX Test == two"
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