Class: Roby::Test::ValidateStateMachine

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize(test, task_or_action) ⇒ ValidateStateMachine

Returns a new instance of ValidateStateMachine.

Raises:

  • (ArgumentError)


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_taskRoby::Task (readonly)

The task that holds the state machine

Returns:



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

Parameters:

  • state_name (String, Symbol)

    the name of the target state

  • timeout (Numeric) (defaults to: 5)

Yield Parameters:



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_taskRoby::Task

The toplevel task of the current state

It raises if the toplevel task (and thus, the state machine) are not running

Returns:



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

Returns:

  • (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

Returns:

  • (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

#startObject

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