state_machine_checker 0.1.0

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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +0 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +67 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +102 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/lib/state_machine_checker/adapters/state_machines.rb +40 -0
  15. data/lib/state_machine_checker/check_result.rb +40 -0
  16. data/lib/state_machine_checker/ctl/a_f.rb +20 -0
  17. data/lib/state_machine_checker/ctl/a_g.rb +20 -0
  18. data/lib/state_machine_checker/ctl/a_u.rb +23 -0
  19. data/lib/state_machine_checker/ctl/a_x.rb +20 -0
  20. data/lib/state_machine_checker/ctl/and.rb +42 -0
  21. data/lib/state_machine_checker/ctl/api.rb +61 -0
  22. data/lib/state_machine_checker/ctl/atom.rb +62 -0
  23. data/lib/state_machine_checker/ctl/binary_formula.rb +23 -0
  24. data/lib/state_machine_checker/ctl/e_f.rb +34 -0
  25. data/lib/state_machine_checker/ctl/e_g.rb +139 -0
  26. data/lib/state_machine_checker/ctl/e_u.rb +42 -0
  27. data/lib/state_machine_checker/ctl/e_x.rb +40 -0
  28. data/lib/state_machine_checker/ctl/formula.rb +42 -0
  29. data/lib/state_machine_checker/ctl/implication.rb +19 -0
  30. data/lib/state_machine_checker/ctl/not.rb +36 -0
  31. data/lib/state_machine_checker/ctl/or.rb +40 -0
  32. data/lib/state_machine_checker/ctl/unary_operator.rb +22 -0
  33. data/lib/state_machine_checker/finite_state_machine.rb +120 -0
  34. data/lib/state_machine_checker/labeled_machine.rb +51 -0
  35. data/lib/state_machine_checker/labeling.rb +48 -0
  36. data/lib/state_machine_checker/rspec_matchers.rb +60 -0
  37. data/lib/state_machine_checker/state_result.rb +57 -0
  38. data/lib/state_machine_checker/transition.rb +49 -0
  39. data/lib/state_machine_checker/version.rb +3 -0
  40. data/lib/state_machine_checker.rb +43 -0
  41. data/state_machine_checker.gemspec +47 -0
  42. metadata +186 -0
@@ -0,0 +1,60 @@
1
+ require "state_machine_checker"
2
+
3
+ module StateMachineChecker
4
+ module RspecMatchers
5
+ def satisfy(formula)
6
+ SatisfyMatcher.new(formula)
7
+ end
8
+
9
+ class SatisfyMatcher
10
+ include StateMachineChecker
11
+
12
+ def initialize(formula)
13
+ @formula = formula
14
+ end
15
+
16
+ def description
17
+ "satisfy \"#{formula}\""
18
+ end
19
+
20
+ def diffable?
21
+ false
22
+ end
23
+
24
+ def matches?(instance_generator)
25
+ @instance_generator = instance_generator
26
+ @result = check_satisfied(formula, instance_generator)
27
+ @result.satisfied?
28
+ end
29
+
30
+ def failure_message
31
+ <<~MESSAGE
32
+ Expected state machine for #{machine_name} to satisfy "#{formula}" but it does not.
33
+ Counterexample: #{result.counterexample}
34
+ MESSAGE
35
+ end
36
+
37
+ def failure_message_when_negated
38
+ <<~MESSAGE
39
+ Expected state machine for #{machine_name} not to satisfy "#{formula}" but it does.
40
+ Witness: #{result.witness}
41
+ MESSAGE
42
+ end
43
+
44
+ def supports_block_expectations?
45
+ true
46
+ end
47
+
48
+ private
49
+
50
+ attr_reader :instance_generator, :formula, :result
51
+
52
+ def machine_name
53
+ # TODO: This assumes state_machines gem
54
+ klass = instance_generator.call.class
55
+ gem_machine = klass.state_machine
56
+ "#{klass}##{gem_machine.name}"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,57 @@
1
+ module StateMachineChecker
2
+ # The result of checking whether this state satisfies a formula.
3
+ class StateResult
4
+ # @param [Boolean] satisfied
5
+ # @param [Array<Symbol>] path
6
+ def initialize(satisfied, path)
7
+ @satisfied = satisfied
8
+ @path = path
9
+ end
10
+
11
+ # Whether the formula is satisfied from this state.
12
+ #
13
+ # @return [true, false]
14
+ def satisfied?
15
+ satisfied
16
+ end
17
+
18
+ # A witness that the formula is satisfied from this state.
19
+ #
20
+ # @return [Array<Symbol>] an array of the names of transitions.
21
+ def witness
22
+ if satisfied?
23
+ path
24
+ end
25
+ end
26
+
27
+ # A counterexample demonstrating that the formula is not satisfied from this
28
+ # state.
29
+ #
30
+ # @return [Array<Symbol>] an array of the names of transitions.
31
+ def counterexample
32
+ unless satisfied?
33
+ path
34
+ end
35
+ end
36
+
37
+ def or(other)
38
+ if satisfied?
39
+ self
40
+ else
41
+ other
42
+ end
43
+ end
44
+
45
+ def and(other)
46
+ if !other.satisfied?
47
+ other
48
+ else
49
+ self
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :satisfied, :path
56
+ end
57
+ end
@@ -0,0 +1,49 @@
1
+ module StateMachineChecker
2
+ # A transition from one state to another
3
+ class Transition
4
+ attr_reader :from, :to, :name
5
+
6
+ # @param [Symbol] from the starting state.
7
+ # @param [Symbol] to the ending state.
8
+ # @param [Symbol] name the name of the transition.
9
+ def initialize(from, to, name)
10
+ @from = from
11
+ @to = to
12
+ @name = name
13
+ end
14
+
15
+ # Execute the transition on an instance.
16
+ #
17
+ # This assumes that the instance has a method corresponding to the
18
+ # transition name, and that that will return a boolean representing whether
19
+ # the transition was successful.
20
+ #
21
+ # @param instance the instance to execute the transition on.
22
+ def execute(instance)
23
+ # TODO: calling the "bang" version (to raise on failure) is specific to
24
+ # the state_machines gem.
25
+ instance.public_send("#{name}!")
26
+ end
27
+
28
+ def ==(other)
29
+ hash_attributes.all? { |attr|
30
+ other.respond_to?(attr) &&
31
+ other.public_send(attr) == public_send(attr)
32
+ }
33
+ end
34
+
35
+ def eql?(other)
36
+ other == self
37
+ end
38
+
39
+ def hash
40
+ hash_attributes.map(&:hash).hash
41
+ end
42
+
43
+ private
44
+
45
+ def hash_attributes
46
+ [:from, :to, :name]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module StateMachineChecker
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,43 @@
1
+ require "state_machine_checker/version"
2
+ require "state_machine_checker/finite_state_machine"
3
+ require "state_machine_checker/labeled_machine"
4
+ require "state_machine_checker/labeling"
5
+ require "state_machine_checker/adapters/state_machines"
6
+ require "state_machine_checker/ctl/api"
7
+
8
+ # The main entrypoint is {#check_satisfied}. The other methods are provided
9
+ # primarily for debugging and transparency.
10
+ module StateMachineChecker
11
+ # Check whether a formula is satisfied by the state machine of a given class.
12
+ #
13
+ # @param [CTL::Formula] formula the formula that should be satisfied.
14
+ # @param instance_generator a thunk (zero-argument function) that must return
15
+ # an instance of an object containing a state machine. The instance must be
16
+ # in the initial state.
17
+ # @return [StateResult] the result for the initial state.
18
+ def check_satisfied(formula, instance_generator)
19
+ labeled_machine = build_labeled_machine(formula, instance_generator)
20
+ check = formula.check(labeled_machine)
21
+ check.for_state(labeled_machine.initial_state)
22
+ end
23
+
24
+ # Build a labeled machine (Kripke structure) over the atomic propositions
25
+ # contained in the given formula.
26
+ #
27
+ # @param [CTL::Formula] formula the formula from which to extract atomic
28
+ # propositions.
29
+ # @param instance_generator a thunk (zero-argument function) that must return
30
+ # an instance of an object containing a state machine. The instance must be
31
+ # in the initial state.
32
+ # @return [LabeledMachine]
33
+ def build_labeled_machine(formula, instance_generator)
34
+ # TODO: Assumes state_machines gem.
35
+ gem_machine = instance_generator.call.class.state_machine
36
+ adapter = Adapters::StateMachines.new(gem_machine)
37
+ fsm = FiniteStateMachine.new(adapter.initial_state, adapter.transitions)
38
+ LabeledMachine.new(
39
+ fsm,
40
+ Labeling.new(formula.atoms, fsm, instance_generator)
41
+ )
42
+ end
43
+ end
@@ -0,0 +1,47 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "state_machine_checker/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "state_machine_checker"
7
+ spec.version = StateMachineChecker::VERSION
8
+ spec.authors = ["Chris Stadler"]
9
+ spec.email = ["chrisstadler@gmail.com"]
10
+
11
+ spec.summary = "Model checking for state machines using CTL."
12
+ spec.description = <<~DESC
13
+ Verify that your state machines have the expected properties. Properties are
14
+ specified using CTL.
15
+ DESC
16
+ spec.homepage = "https://github.com/CJStadler/state_machine_checker"
17
+ spec.license = "MIT"
18
+
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata["homepage_uri"] = spec.homepage
21
+ spec.metadata["source_code_uri"] = "https://github.com/CJStadler/state_machine_checker"
22
+ spec.metadata["changelog_uri"] =
23
+ "https://github.com/CJStadler/state_machine_checker/blob/master/CHANGELOG.md"
24
+ else
25
+ raise "RubyGems 2.0 or newer is required to protect against " \
26
+ "public gem pushes."
27
+ end
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
32
+ `git ls-files -z`.split("\x0").
33
+ reject { |f| f.match(%r{^(test|spec|features)/}) }.
34
+ reject { |f| f.match('paper.pdf')}
35
+ end
36
+ spec.bindir = "exe"
37
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
38
+ spec.require_paths = ["lib"]
39
+
40
+ spec.add_development_dependency "bundler", "~> 1.17"
41
+ spec.add_development_dependency "rake", "~> 10.0"
42
+ spec.add_development_dependency "rspec", "~> 3.0"
43
+ spec.add_development_dependency "state_machines", "~> 0.5.0"
44
+ spec.add_development_dependency "standard", "~> 0.0.36"
45
+ spec.add_development_dependency "pry"
46
+ spec.add_development_dependency "yard", "~> 0.9.18"
47
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: state_machine_checker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Stadler
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-07-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: state_machines
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.5.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.5.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: standard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.0.36
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.36
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.9.18
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.9.18
111
+ description: |
112
+ Verify that your state machines have the expected properties. Properties are
113
+ specified using CTL.
114
+ email:
115
+ - chrisstadler@gmail.com
116
+ executables: []
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - ".gitignore"
121
+ - ".rspec"
122
+ - ".travis.yml"
123
+ - CHANGELOG.md
124
+ - CODE_OF_CONDUCT.md
125
+ - Gemfile
126
+ - Gemfile.lock
127
+ - LICENSE.txt
128
+ - README.md
129
+ - Rakefile
130
+ - bin/console
131
+ - bin/setup
132
+ - lib/state_machine_checker.rb
133
+ - lib/state_machine_checker/adapters/state_machines.rb
134
+ - lib/state_machine_checker/check_result.rb
135
+ - lib/state_machine_checker/ctl/a_f.rb
136
+ - lib/state_machine_checker/ctl/a_g.rb
137
+ - lib/state_machine_checker/ctl/a_u.rb
138
+ - lib/state_machine_checker/ctl/a_x.rb
139
+ - lib/state_machine_checker/ctl/and.rb
140
+ - lib/state_machine_checker/ctl/api.rb
141
+ - lib/state_machine_checker/ctl/atom.rb
142
+ - lib/state_machine_checker/ctl/binary_formula.rb
143
+ - lib/state_machine_checker/ctl/e_f.rb
144
+ - lib/state_machine_checker/ctl/e_g.rb
145
+ - lib/state_machine_checker/ctl/e_u.rb
146
+ - lib/state_machine_checker/ctl/e_x.rb
147
+ - lib/state_machine_checker/ctl/formula.rb
148
+ - lib/state_machine_checker/ctl/implication.rb
149
+ - lib/state_machine_checker/ctl/not.rb
150
+ - lib/state_machine_checker/ctl/or.rb
151
+ - lib/state_machine_checker/ctl/unary_operator.rb
152
+ - lib/state_machine_checker/finite_state_machine.rb
153
+ - lib/state_machine_checker/labeled_machine.rb
154
+ - lib/state_machine_checker/labeling.rb
155
+ - lib/state_machine_checker/rspec_matchers.rb
156
+ - lib/state_machine_checker/state_result.rb
157
+ - lib/state_machine_checker/transition.rb
158
+ - lib/state_machine_checker/version.rb
159
+ - state_machine_checker.gemspec
160
+ homepage: https://github.com/CJStadler/state_machine_checker
161
+ licenses:
162
+ - MIT
163
+ metadata:
164
+ homepage_uri: https://github.com/CJStadler/state_machine_checker
165
+ source_code_uri: https://github.com/CJStadler/state_machine_checker
166
+ changelog_uri: https://github.com/CJStadler/state_machine_checker/blob/master/CHANGELOG.md
167
+ post_install_message:
168
+ rdoc_options: []
169
+ require_paths:
170
+ - lib
171
+ required_ruby_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ required_rubygems_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ requirements: []
182
+ rubygems_version: 3.0.1
183
+ signing_key:
184
+ specification_version: 4
185
+ summary: Model checking for state machines using CTL.
186
+ test_files: []