Class: Roby::Test::ValidateStateMachine
- Includes:
- MetaRuby::DSLs::FindThroughMethodMissing
- Defined in:
- lib/roby/test/validate_state_machine.rb
Overview
DSL-like way to test an action state machine
ValidateStateMachine objects are not created directly. Use Assertions#validate_state_machine to create them
The goal of state machine validation is to verify that state transitions happen when they should in complex state machines and in state machine generators (i.e. methods that create state machines)
The general workflow is to verify some properties on the start state, and then cause state machine transitions using #assert_transitions_to_state based on e.g. event emissions. Do not fall into the trap of testing the state’s own behaviors. This should be done within separate unit tests for the state’s actions and/or task implementations.
Instance Attribute Summary collapse
-
#toplevel_task ⇒ Roby::Task
readonly
The task that holds the state machine.
Instance Method Summary collapse
-
#assert_transitions_to_state(state_name, timeout: 5) {|the| ... } ⇒ Object
Verifies that some operations cause the state machine to transition.
-
#current_state_task ⇒ Roby::Task
The toplevel task of the current state.
- #evaluate(&block) ⇒ Object
- #find_through_method_missing(m, args) ⇒ Object
- #has_through_method_missing?(m) ⇒ Boolean
-
#initialize(test, task_or_action) ⇒ ValidateStateMachine
constructor
A new instance of ValidateStateMachine.
- #method_missing(m, *args, &block) ⇒ Object
- #respond_to_missing?(m, include_private) ⇒ Boolean
-
#start ⇒ Object
Start the toplevel task.
-
#state_name_patterns(state_name) ⇒ Object
Returns possible state names based on current patterns and the actual state name.
Constructor Details
#initialize(test, task_or_action) ⇒ ValidateStateMachine
Returns a new instance of ValidateStateMachine.
36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/roby/test/validate_state_machine.rb', line 36 def initialize(test, task_or_action) @test = test @toplevel_task = @test.run_planners(task_or_action) @state_machines = @toplevel_task .each_coordination_object .find_all { |obj| obj.kind_of?(Coordination::ActionStateMachine) } return unless @state_machines.empty? raise ArgumentError, "#{task_or_action} has no state machines" end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(m, *args, &block) ⇒ Object
125 126 127 128 129 130 131 |
# File 'lib/roby/test/validate_state_machine.rb', line 125 def method_missing(m, *args, &block) if @test.respond_to?(m) @test.public_send(m, *args, &block) else super end end |
Instance Attribute Details
#toplevel_task ⇒ Roby::Task (readonly)
The task that holds the state machine
24 25 26 |
# File 'lib/roby/test/validate_state_machine.rb', line 24 def toplevel_task @toplevel_task end |
Instance Method Details
#assert_transitions_to_state(state_name, timeout: 5) {|the| ... } ⇒ Object
Verifies that some operations cause the state machine to transition
Note that one assertion may wait for more than one state transition. The given block should cause the expected transition(s) to fire, and should use normal Roby testing tools, such as e.g. ExpectExecution#expect_execution
The toplevel task MUST be active at the point of call, that is the toplevel task has been started
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/roby/test/validate_state_machine.rb', line 82 def assert_transitions_to_state(state_name, timeout: 5) matchers = state_name_patterns(state_name) done = false @state_machines.each do |m| m.on_transition do |_, new_state| done ||= matchers.any? do (_1 === new_state.name) end end end yield(current_state_task) if block_given? expect_execution.timeout(timeout).to { achieve { done } } @test.run_planners(@toplevel_task) @toplevel_task.current_task_child end |
#current_state_task ⇒ Roby::Task
The toplevel task of the current state
It raises if the toplevel task (and thus, the state machine) are not running
32 33 34 |
# File 'lib/roby/test/validate_state_machine.rb', line 32 def current_state_task @toplevel_task.current_task_child end |
#evaluate(&block) ⇒ Object
99 100 101 |
# File 'lib/roby/test/validate_state_machine.rb', line 99 def evaluate(&block) instance_eval(&block) end |
#find_through_method_missing(m, args) ⇒ Object
103 104 105 106 107 108 109 |
# File 'lib/roby/test/validate_state_machine.rb', line 103 def find_through_method_missing(m, args) MetaRuby::DSLs.find_through_method_missing( current_state_task, m, args, "_event" => :find_event, "_child" => :find_child_from_role ) || super end |
#has_through_method_missing?(m) ⇒ Boolean
111 112 113 114 115 116 117 |
# File 'lib/roby/test/validate_state_machine.rb', line 111 def has_through_method_missing?(m) MetaRuby::DSLs.has_through_method_missing?( current_state_task, m, "_event" => :has_event?, "_child" => :has_role? ) || super end |
#respond_to_missing?(m, include_private) ⇒ Boolean
121 122 123 |
# File 'lib/roby/test/validate_state_machine.rb', line 121 def respond_to_missing?(m, include_private) @test.respond_to?(m) || super end |
#start ⇒ Object
Start the toplevel task
This is done automatically by Assertions#validate_state_machine
53 54 55 56 57 58 59 |
# File 'lib/roby/test/validate_state_machine.rb', line 53 def start toplevel_task = @toplevel_task expect_execution { toplevel_task.start! } .to { emit toplevel_task.start_event } @toplevel_task = @test.run_planners(@toplevel_task) @toplevel_task.current_task_child end |
#state_name_patterns(state_name) ⇒ Object
Returns possible state names based on current patterns and the actual state name
63 64 65 66 67 |
# File 'lib/roby/test/validate_state_machine.rb', line 63 def state_name_patterns(state_name) matchers = [state_name.to_str] matchers << "#{state_name}_state" unless state_name.end_with?("_state") matchers end |