pg-verify 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,9 +5,17 @@ module PgVerify
5
5
  class Runner
6
6
 
7
7
  def run_specs(program_graph)
8
- nusmv_s = Transform::NuSmvTransformation.new.transform_graph(program_graph)
9
- output = eval_nusmv(nusmv_s)
8
+ transformer = Transform::NuSmvTransformation.new
9
+ nusmv_s = transformer.transform_graph(program_graph, include_specs: false)
10
+ commands = [ "go" ]
10
11
  specs = program_graph.specification.flatten()
12
+ transform_map = specs.each { |spec|
13
+ transformed_spec_expr = transformer.transform_expression(spec.expression, program_graph.all_variables)
14
+ cmd = { ltl: "check_ltlspec", ctl: "check_ctlspec" }[spec.expression.predict_type]
15
+ commands << "#{cmd} -p \"#{transformed_spec_expr}\""
16
+ }
17
+ output = eval_nusmv(nusmv_s, commands: commands)
18
+
11
19
  return parse_spec_results(program_graph, specs, output)
12
20
  end
13
21
 
@@ -20,8 +28,8 @@ module PgVerify
20
28
  nusmv_output.split("\n").each { |line|
21
29
  result = line[/-- specification .* is (true|false)/, 1]
22
30
  if !result.nil?
23
- blocks << current_block unless current_block.nil?
24
- current_block = block.new(result == "true", [])
31
+ blocks << current_block unless current_block.nil? # Complete the last block
32
+ current_block = block.new(result == "true", []) # Start a new block
25
33
  next
26
34
  end
27
35
  current_block.lines << line unless current_block.nil?
@@ -41,10 +49,7 @@ module PgVerify
41
49
  # variable to the value of that variable in that state
42
50
  def run_simulation(program_graph, steps, random: false)
43
51
  commands = []
44
- commands << "read_model"
45
- commands << "flatten_hierarchy"
46
- commands << "encode_variables"
47
- commands << "build_model"
52
+ commands << "go"
48
53
  commands << "pick_state #{random ? '-r' : ''}"
49
54
  commands << "simulate -k #{steps.to_s.to_i} -v #{random ? '-r' : ''}"
50
55
  commands << "quit"
@@ -53,12 +58,48 @@ module PgVerify
53
58
  return parse_trace(program_graph, output)
54
59
  end
55
60
 
61
+ def run_check!(program_graph)
62
+ deadlock_state = run_check(program_graph)
63
+ return if deadlock_state.nil?
64
+ raise Model::Validation::DeadlockInFSMError.new(program_graph, deadlock_state)
65
+ end
66
+
67
+ def run_check(program_graph)
68
+ commands = [ "go", "check_fsm", "quit" ]
69
+ nusmv_s = Transform::NuSmvTransformation.new.transform_graph(program_graph, include_specs: false)
70
+ output = eval_nusmv(nusmv_s, commands: commands)
71
+
72
+ # Return "ok" if the FSM has no deadlocks
73
+ return nil if output.include?("The transition relation is total: No deadlock state exists")
74
+
75
+ # Otherwise compute and return the deadlock state
76
+ lines = output.split("\n").drop_while { |str|
77
+ !(str.start_with?("A deadlock state is:") || str.start_with?("successors is:"))
78
+ }
79
+ lines = lines[1, lines.length - 1]
80
+
81
+ var_state = {}
82
+ lines.each { |line|
83
+ key, val = parse_var_assignment_in_trace(line)
84
+ next if key.nil? || val.nil?
85
+ var_state[key] = val
86
+ }
87
+
88
+ return var_state
89
+ end
90
+
56
91
  def parse_trace(program_graph, nusmv_output)
57
92
  var_states, current_var_state = [], nil
58
93
 
59
94
  loop_index = -1
60
95
 
61
96
  nusmv_output.split("\n").each { |line|
97
+ # Mark a loop for the following state
98
+ if line.include?("Loop starts here")
99
+ loop_index = var_states.length
100
+ next
101
+ end
102
+
62
103
  # Wait for heading of new state
63
104
  if line.match(/\s*-> State: .+ <-/)
64
105
  # Complete and store the old state if any
@@ -76,17 +117,28 @@ module PgVerify
76
117
  # Skip lines before the first state
77
118
  next if current_var_state.nil?
78
119
 
79
- if line.include?("Loop starts here")
80
- loop_index == var_states.length - 1
81
- next
82
- end
83
-
84
- key_val = line.split("=").map(&:strip)
85
- key = key_val[0].gsub("v.V_", "").to_sym
86
- val = key_val[1].gsub("L_", "")
120
+ # Parse the variable state
121
+ key, val = parse_var_assignment_in_trace(line)
122
+ next if key.nil? || val.nil?
87
123
  current_var_state[key] = val
88
124
  }
89
- return Model::Trace.new(program_graph, var_states)
125
+ # Finish up
126
+ unless current_var_state.nil?
127
+ missing_keys = var_states.empty? ? [] : var_states[-1].keys - current_var_state.keys
128
+ missing_keys.each { |key|
129
+ current_var_state[key] = var_states[-1][key]
130
+ }
131
+ var_states << current_var_state
132
+ end
133
+ return Model::Trace.new(program_graph, var_states, loop_index: loop_index)
134
+ end
135
+
136
+ def parse_var_assignment_in_trace(line)
137
+ return nil, nil unless line.include?("=")
138
+ key_val = line.split("=").map(&:strip)
139
+ key = key_val[0].gsub("v.V_", "").to_sym
140
+ val = key_val[1].gsub("L_", "")
141
+ return key, val
90
142
  end
91
143
 
92
144
  def eval_nusmv(nusmv_string, commands: [])
@@ -15,6 +15,7 @@ module PgVerify
15
15
  end
16
16
 
17
17
  def self.convert_file(in_path, out_path)
18
+ # CALL WITH java -Djava.awt.headless=true -jar addon/plantuml-1.2023.13.jar --help
18
19
  # TODO: The PlantUML jar switches focus to the desktop is if it would
19
20
  # attempt to open a window each time it is invoked.
20
21
  end
File without changes
@@ -4,16 +4,24 @@ module PgVerify
4
4
  class HashTransformation
5
5
 
6
6
  def transform_graph(graph)
7
- return { graph.name.to_s =>
8
- graph.components.map { |c| transform_component(graph, c) }
9
- }
7
+ components = graph.components.map { |c| transform_component(graph, c) }
8
+ hazards = graph.hazards.map { |h| transform_hazard(h) }
9
+ sub_hash = {}
10
+ sub_hash["components"] = components
11
+ sub_hash["hazards"] = hazards unless hazards.empty?
12
+ sub_hash["specification"] = transform_specification(graph.specification)
13
+ return { graph.name.to_s => sub_hash }
10
14
  end
11
15
 
12
16
  def parse_graph(hash)
13
17
  name = hash.keys.first.to_sym
14
18
  graph = Model::Graph.new(name)
15
- components = hash[hash.keys.first].map { |c| parse_component(graph, c) }
19
+ components = hash[hash.keys.first]["components"].map { |c| parse_component(graph, c) }
20
+ hazards = hash[hash.keys.first]["hazards"]&.map { |h| parse_hazard(h) } || []
21
+ specification = parse_specification(hash[hash.keys.first]["specification"])
16
22
  graph.components = components
23
+ graph.hazards = hazards
24
+ graph.specification = specification
17
25
  return graph
18
26
  end
19
27
 
@@ -22,6 +30,7 @@ module PgVerify
22
30
  return { component.name.to_s => {
23
31
  "states" => component.states.map(&:to_s),
24
32
  "variables" => variables.map { |v| transform_variable(v) },
33
+ "init" => transform_expression(component.init_expression),
25
34
  "transitions" => component.transitions.map { |t| transform_transition(t) },
26
35
  "represents_fault" => component.represents_fault
27
36
  }}
@@ -31,13 +40,14 @@ module PgVerify
31
40
  name = hash.keys.first
32
41
  states = hash[name]["states"].map(&:to_sym)
33
42
  variables = hash[name]["variables"].map { |v| parse_variable(name, v) }
43
+ init_expression = parse_expression(hash[name]["init"])
34
44
  transitions = hash[name]["transitions"].map { |t| parse_transition(t) }
35
45
  represents_fault = hash[name]["represents_fault"]
36
46
 
37
47
  graph.variables += variables
38
48
 
39
49
  return Model::Component.new(name: name.to_sym, states: states,
40
- transitions: transitions, represents_fault: represents_fault)
50
+ transitions: transitions, represents_fault: represents_fault, init_expression: init_expression)
41
51
  end
42
52
 
43
53
  def transform_variable(variable)
@@ -49,7 +59,7 @@ module PgVerify
49
59
 
50
60
  def parse_variable(owner_name, hash)
51
61
  name = hash.keys.first
52
- range = hash[name]["range"]
62
+ range = parse_variable_range(hash[name]["range"])
53
63
  init_expression = parse_expression(hash[name]["init"])
54
64
  return Model::Variable.new(name.to_sym, range, owner_name.to_sym, nil, init: init_expression)
55
65
  end
@@ -60,9 +70,10 @@ module PgVerify
60
70
  end
61
71
 
62
72
  def parse_variable_range(range)
63
- if range.is_a?(String) && range.match(/\A\d+\.\.\d+\z/)
73
+ # Matches ranges like 1..12 or -1..23
74
+ if range.is_a?(String) && range.match(/\A\-?\d+\.\.\-?\d+\z/)
64
75
  first, last = range.split("..")[0], range.split("..")[-1]
65
- return Range.new(first, last)
76
+ return Range.new(first.to_i, last.to_i)
66
77
  end
67
78
  return range
68
79
  end
@@ -85,20 +96,41 @@ module PgVerify
85
96
  def transform_transition(transition)
86
97
  name = "#{transition.src_state} -> #{transition.tgt_state}"
87
98
  return { name => {
88
- "precon" => transition.precon&.to_s,
89
- "guard" => transition.guard&.to_s,
90
- "action" => transition.action&.to_s,
99
+ "precon" => transform_expression(transition.precon),
100
+ "guard" => transform_expression(transition.guard),
101
+ "action" => transform_expression(transition.action),
91
102
  }}
92
103
  end
93
104
 
94
105
  def parse_transition(hash)
95
106
  src_state, tgt_state = hash.keys.first.split("->").map(&:strip).map(&:to_sym)
96
- precon = hash.values.first["precon"]
97
- guard = hash.values.first["guard"]
98
- action = hash.values.first["action"]
107
+ precon = parse_expression(hash.values.first["precon"])
108
+ guard = parse_expression(hash.values.first["guard"] )
109
+ action = parse_expression(hash.values.first["action"])
99
110
  return Model::Transition.new(src_state, tgt_state, precon: precon, guard: guard, action: action)
100
111
  end
101
112
 
113
+ def transform_hazard(hazard)
114
+ return { "label" => hazard.text, "expression" => hazard.expression }
115
+ end
116
+
117
+ def parse_hazard(hash)
118
+ return Model::Hazard.new(hash["label"], hash["expression"])
119
+ end
120
+
121
+ def transform_specification(specification)
122
+ return specification.flatten().map { |spec|
123
+ { "label" => spec.text.strip, "expression" => transform_expression(spec.expression) }
124
+ }
125
+ end
126
+
127
+ def parse_specification(array)
128
+ specs = array.map { |hash|
129
+ Model::Spec.new(hash["label"], parse_expression(hash["expression"]), nil)
130
+ }
131
+ return Model::Specification.new([Model::SpecSet.wrap(specs)])
132
+ end
133
+
102
134
  end
103
135
  end
104
136
  end
@@ -3,7 +3,7 @@ module PgVerify
3
3
 
4
4
  class NuSmvTransformation
5
5
 
6
- def transform_graph(program_graph)
6
+ def transform_graph(program_graph, include_specs: true)
7
7
  variables = program_graph.all_variables
8
8
  components = program_graph.components
9
9
 
@@ -11,7 +11,7 @@ module PgVerify
11
11
  cmp_s = transform_components(components, variables)
12
12
  main_s = transform_main_module(components)
13
13
 
14
- specs = transform_specification(program_graph.specification, variables)
14
+ specs = transform_specification(program_graph.specification, variables) if include_specs
15
15
 
16
16
  return "#{var_s}\n\n#{cmp_s}\n\n#{main_s}\n\n#{specs}\n"
17
17
  end
@@ -251,7 +251,8 @@ module PgVerify
251
251
  def transform_spec(spec, varset)
252
252
  expression_s = "-- #{spec.text}\n"
253
253
  expression_s += "-- Defined in: #{spec.source_location.to_s}\n"
254
- expression_s += "LTLSPEC " + transform_expression(spec.expression, varset)
254
+ keyword = { ltl: "LTLSPEC", ctl: "CTLSPEC" }[spec.expression.predict_type]
255
+ expression_s += keyword + " " + transform_expression(spec.expression, varset)
255
256
  return expression_s
256
257
  end
257
258
 
@@ -5,13 +5,25 @@ module PgVerify
5
5
 
6
6
  class PumlTransformation
7
7
 
8
+ attr_accessor :render_labels
9
+ attr_accessor :render_precons
10
+ attr_accessor :render_guards
11
+ attr_accessor :render_actions
12
+
13
+ def initialize(render_options = {})
14
+ @render_labels = render_options[:render_labels].nil? ? true : render_options[:render_labels]
15
+ @render_precons = render_options[:render_precons].nil? ? true : render_options[:render_precons]
16
+ @render_guards = render_options[:render_guards].nil? ? true : render_options[:render_guards]
17
+ @render_actions = render_options[:render_actions].nil? ? true : render_options[:render_actions]
18
+ end
19
+
8
20
  def transform_graph(graph, variable_state: nil, only: nil)
9
21
 
10
22
  components = graph.components
11
23
  components = components.select { |c| only.map(&:to_s).include?(c.name.to_s) } unless only.nil?
12
24
 
13
25
  parts = []
14
- parts << components.map { |c| transform_component(graph, c) }.join("\n\n")
26
+ parts << components.map { |c| transform_component(graph, c, variable_state) }.join("\n\n")
15
27
  parts << transform_variable_state(graph, variable_state) unless variable_state.nil?
16
28
  parts = parts.compact.join("\n\n")
17
29
  return "@startuml Programmgraph\n#{parts}\n@enduml\n"
@@ -25,14 +37,14 @@ module PgVerify
25
37
  }.join("\n")
26
38
  end
27
39
 
28
- def transform_component(graph, component)
40
+ def transform_component(graph, component, variable_state)
29
41
  # Transform component states
30
42
  states_s = component.states.map { |s| transform_state(component, s) }.join("\n")
31
43
  # Transform component transitions
32
44
  trans_s = component.transitions.map { |t| transform_transition(component, t) }.join("\n")
33
45
  # Transform component variables
34
46
  vars = graph.variables.select_by_owner(component.name)
35
- vars_s = transform_variables(component, vars)
47
+ vars_s = transform_variables(component, vars, variable_state)
36
48
  # Transform the initial state
37
49
  initial_s = transform_initial(graph, component)
38
50
 
@@ -59,10 +71,14 @@ module PgVerify
59
71
  end
60
72
 
61
73
  def transform_transition(component, transition)
62
- label = [ transition.precon, transition.guard ].map(&:to_s).reject(&:empty?).join(" && ")
63
- label += "/ " + transition.action.to_s unless transition.action.nil?
74
+ precon = @render_precons ? transition.precon : nil
75
+ guard = @render_guards ? transition.guard : nil
76
+ action = @render_actions ? transition.action : nil
64
77
 
78
+ label = [ precon, guard ].map(&:to_s).reject(&:empty?).join(" && ")
79
+ label += " / " + action.to_s unless action.nil?
65
80
  label = ": #{label}" unless label.strip.empty?
81
+ label = "" unless @render_labels
66
82
  return "#{component.name}_#{transition.src_state} --> #{component.name}_#{transition.tgt_state} #{label}"
67
83
  end
68
84
 
@@ -70,9 +86,12 @@ module PgVerify
70
86
  str.split("\n").map { |s| "\t#{s}" }.join("\n")
71
87
  end
72
88
 
73
- def transform_variables(component, variables)
74
- return nil if variables.empty?
75
- body = variables.map { |v| "#{v.name} => #{transform_range(v.range)}" }.join("\n")
89
+ def transform_variables(component, variables, variable_state)
90
+ return nil if variables.empty?
91
+ body = variables.map { |var|
92
+ value = variable_state.nil? ? transform_range(var.range) : variable_state[var.name]
93
+ "#{var.name} => #{value}"
94
+ }.join("\n")
76
95
  return "map #{component.name}Variables {\n#{body}\n}"
77
96
  end
78
97
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgVerify
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
data/vscript.rb ADDED
@@ -0,0 +1,64 @@
1
+
2
+ task :gen_examples do
3
+ files = Dir["integration_tests/ruby_dsl/*.rb"].select(&:file?)
4
+
5
+ target_dir = "examples"
6
+ FileUtils.rm_rf(target_dir); FileUtils.mkdir(target_dir)
7
+
8
+ files.each { |file|
9
+ json = x "./devpg show json --script #{file}", verbose: false
10
+ yaml = x "./devpg show yaml --script #{file}", verbose: false
11
+ json_file = File.join(target_dir, File.basename(file, '.rb') + ".json")
12
+ yaml_file = File.join(target_dir, File.basename(file, '.rb') + ".yaml")
13
+ FileUtils.cp(file, File.join(target_dir, File.basename(file)))
14
+ File.write(json_file, json)
15
+ File.write(yaml_file, yaml)
16
+
17
+ x "tar -czf examples.tar.gz examples/"
18
+ }
19
+ FileUtils.rm_rf(target_dir)
20
+
21
+ done "Generated examples.tar.gz"
22
+ end
23
+
24
+ task :integration_test do
25
+
26
+ work_dir = ".pg-work"
27
+ FileUtils.rm_rf(work_dir); FileUtils.mkdir(work_dir)
28
+
29
+ test_files = Dir["integration_tests/ruby_dsl/*.rb"].sort.select(&:file?)
30
+
31
+
32
+ # Create json files for all tests
33
+ test_files.each { |source|
34
+ log "JSON #{File.basename(source)}"
35
+ target = File.join(work_dir, File.basename(source, '.rb') + ".json")
36
+ content = x "./devpg show json --script #{source}", verbose: false
37
+ File.write(target, content)
38
+ }
39
+
40
+ # Create json files for all tests
41
+ test_files.each { |source|
42
+ log "YAML #{File.basename(source)}"
43
+ target = File.join(work_dir, File.basename(source, '.rb') + ".yaml")
44
+ content = x "./devpg show yaml --script #{source}", verbose: false
45
+ File.write(target, content)
46
+ }
47
+
48
+ # Run tests for all files
49
+ test_files.each { |source|
50
+ args = {
51
+ "script" => source,
52
+ "json-file" => File.join(work_dir, File.basename(source, '.rb') + ".json"),
53
+ }
54
+ commands = [ "show puml", "test", "dcca", "show png", "simulate" ]
55
+ args.each { |argument, file|
56
+ commands.each { |command|
57
+ log "pgv #{command} #{argument} | #{File.basename(file)}"
58
+ x "./devpg #{command} --#{argument} #{file}", verbose: false
59
+ }
60
+ }
61
+ }
62
+
63
+ done "Great success!"
64
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg-verify
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arian Weber
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-12 00:00:00.000000000 Z
11
+ date: 2024-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -92,6 +92,10 @@ files:
92
92
  - devpg
93
93
  - doc/examples/railroad_crossing.rb
94
94
  - doc/examples/train-tree.rb
95
+ - doc/examples/vending_machine/checkpoint_1.rb
96
+ - doc/examples/vending_machine/checkpoint_2.rb
97
+ - doc/examples/vending_machine/checkpoint_3.rb
98
+ - doc/examples/vending_machine/checkpoint_4.rb
95
99
  - doc/examples/weidezaun.rb
96
100
  - doc/examples/weidezaun.txt
97
101
  - doc/expose/definition.png
@@ -115,8 +119,13 @@ files:
115
119
  - integration_tests/ruby_dsl/014_tau_transitions.rb
116
120
  - integration_tests/ruby_dsl/015_basic_dcca.rb
117
121
  - integration_tests/ruby_dsl/016_pressure_tank.rb
122
+ - integration_tests/ruby_dsl/017_ctl_specifications.rb
123
+ - integration_tests/ruby_dsl/018_non_atomic_hazard.rb
124
+ - integration_tests/ruby_dsl/019_multiple_actions.rb
125
+ - integration_tests/ruby_dsl/020_vending_machine.rb
118
126
  - lib/pg-verify.rb
119
127
  - lib/pg-verify/cli/cli.rb
128
+ - lib/pg-verify/cli/cli_utils.rb
120
129
  - lib/pg-verify/core/cmd_runner.rb
121
130
  - lib/pg-verify/core/core.rb
122
131
  - lib/pg-verify/core/extensions/array_extensions.rb
@@ -170,6 +179,7 @@ files:
170
179
  - lib/pg-verify/nusmv/nusmv.rb
171
180
  - lib/pg-verify/nusmv/runner.rb
172
181
  - lib/pg-verify/puml/puml.rb
182
+ - lib/pg-verify/puml/runner.rb
173
183
  - lib/pg-verify/shell/loading/line_animation.rb
174
184
  - lib/pg-verify/shell/loading/loading_animation.rb
175
185
  - lib/pg-verify/shell/loading/loading_prompt.rb
@@ -185,6 +195,7 @@ files:
185
195
  - lib/pg-verify/version.rb
186
196
  - pg-verify.gemspec
187
197
  - sig/pg-verify.rbs
198
+ - vscript.rb
188
199
  homepage: https://github.com/ArianWeber/pg-verify
189
200
  licenses: []
190
201
  metadata: