roby 0.7.3 → 0.8.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.
- data/History.txt +7 -5
- data/Manifest.txt +91 -16
- data/README.txt +24 -24
- data/Rakefile +92 -64
- data/app/config/app.yml +42 -43
- data/app/config/init.rb +26 -0
- data/benchmark/alloc_misc.rb +123 -0
- data/benchmark/discovery_latency.rb +67 -0
- data/benchmark/garbage_collection.rb +48 -0
- data/benchmark/genom.rb +31 -0
- data/benchmark/transactions.rb +62 -0
- data/bin/roby +1 -1
- data/bin/roby-log +16 -6
- data/doc/guide/.gitignore +2 -0
- data/doc/guide/config.yaml +34 -0
- data/doc/guide/ext/init.rb +14 -0
- data/doc/guide/ext/previous_next.rb +40 -0
- data/doc/guide/ext/rdoc_links.rb +33 -0
- data/doc/guide/index.rdoc +16 -0
- data/doc/guide/overview.rdoc +62 -0
- data/doc/guide/plan_modifications.rdoc +67 -0
- data/doc/guide/src/abstraction/achieve_with.page +8 -0
- data/doc/guide/src/abstraction/forwarding.page +8 -0
- data/doc/guide/src/abstraction/hierarchy.page +19 -0
- data/doc/guide/src/abstraction/index.page +28 -0
- data/doc/guide/src/abstraction/task_models.page +13 -0
- data/doc/guide/src/basics.template +6 -0
- data/doc/guide/src/basics/app.page +139 -0
- data/doc/guide/src/basics/code_examples.page +33 -0
- data/doc/guide/src/basics/dry.page +69 -0
- data/doc/guide/src/basics/errors.page +443 -0
- data/doc/guide/src/basics/events.page +179 -0
- data/doc/guide/src/basics/hierarchy.page +275 -0
- data/doc/guide/src/basics/index.page +11 -0
- data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_4.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
- data/doc/guide/src/basics/log_replay/hierarchy_error_1.png +0 -0
- data/doc/guide/src/basics/log_replay/hierarchy_error_2.png +0 -0
- data/doc/guide/src/basics/log_replay/hierarchy_error_3.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_1.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_2.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_3.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_log_relation_window.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_replay_event_representation.png +0 -0
- data/doc/guide/src/basics/plan_objects.page +71 -0
- data/doc/guide/src/basics/relations_display.page +203 -0
- data/doc/guide/src/basics/roby_cycle_overview.png +0 -0
- data/doc/guide/src/basics/shell.page +102 -0
- data/doc/guide/src/basics/summary.page +32 -0
- data/doc/guide/src/basics/tasks.page +357 -0
- data/doc/guide/src/basics_shell_header.txt +16 -0
- data/doc/guide/src/cycle/cycle-overview.png +0 -0
- data/doc/guide/src/cycle/cycle-overview.svg +208 -0
- data/doc/guide/src/cycle/error_handling.page +168 -0
- data/doc/guide/src/cycle/error_instantaneous_repair.png +0 -0
- data/doc/guide/src/cycle/error_instantaneous_repair.svg +1224 -0
- data/doc/guide/src/cycle/garbage_collection.page +10 -0
- data/doc/guide/src/cycle/index.page +23 -0
- data/doc/guide/src/cycle/propagation.page +154 -0
- data/doc/guide/src/cycle/propagation_diamond.png +0 -0
- data/doc/guide/src/cycle/propagation_diamond.svg +1279 -0
- data/doc/guide/src/default.css +319 -0
- data/doc/guide/src/default.template +74 -0
- data/doc/guide/src/htmldoc.metainfo +20 -0
- data/doc/guide/src/htmldoc.virtual +18 -0
- data/doc/guide/src/images/bodybg.png +0 -0
- data/doc/guide/src/images/contbg.png +0 -0
- data/doc/guide/src/images/footerbg.png +0 -0
- data/doc/guide/src/images/gradient1.png +0 -0
- data/doc/guide/src/images/gradient2.png +0 -0
- data/doc/guide/src/index.page +7 -0
- data/doc/guide/src/introduction/index.page +29 -0
- data/doc/guide/src/introduction/install.page +133 -0
- data/doc/{papers.rdoc → guide/src/introduction/publications.page} +5 -2
- data/doc/{videos.rdoc → guide/src/introduction/videos.page} +4 -2
- data/doc/guide/src/plugins/fault_tolerance.page +44 -0
- data/doc/guide/src/plugins/index.page +11 -0
- data/doc/guide/src/plugins/subsystems.page +45 -0
- data/doc/guide/src/relations/dependency.page +89 -0
- data/doc/guide/src/relations/index.page +12 -0
- data/doc/misc/update_github +24 -0
- data/doc/tutorials/02-GoForward.rdoc +3 -3
- data/ext/graph/graph.cc +46 -0
- data/lib/roby.rb +57 -22
- data/lib/roby/app.rb +132 -112
- data/lib/roby/app/plugins/rake.rb +21 -0
- data/lib/roby/app/rake.rb +0 -7
- data/lib/roby/app/run.rb +1 -1
- data/lib/roby/app/scripts/distributed.rb +1 -2
- data/lib/roby/app/scripts/generate/bookmarks.rb +1 -1
- data/lib/roby/app/scripts/results.rb +2 -1
- data/lib/roby/app/scripts/run.rb +6 -2
- data/lib/roby/app/scripts/shell.rb +11 -11
- data/lib/roby/config.rb +1 -1
- data/lib/roby/decision_control.rb +62 -3
- data/lib/roby/distributed.rb +4 -0
- data/lib/roby/distributed/base.rb +8 -0
- data/lib/roby/distributed/communication.rb +12 -8
- data/lib/roby/distributed/connection_space.rb +61 -44
- data/lib/roby/distributed/distributed_object.rb +1 -1
- data/lib/roby/distributed/notifications.rb +22 -30
- data/lib/roby/distributed/peer.rb +13 -8
- data/lib/roby/distributed/proxy.rb +5 -5
- data/lib/roby/distributed/subscription.rb +4 -4
- data/lib/roby/distributed/transaction.rb +3 -3
- data/lib/roby/event.rb +176 -110
- data/lib/roby/exceptions.rb +12 -4
- data/lib/roby/execution_engine.rb +1604 -0
- data/lib/roby/external_process_task.rb +225 -0
- data/lib/roby/graph.rb +0 -6
- data/lib/roby/interface.rb +221 -137
- data/lib/roby/log/console.rb +5 -3
- data/lib/roby/log/data_stream.rb +94 -16
- data/lib/roby/log/dot.rb +8 -8
- data/lib/roby/log/event_stream.rb +13 -3
- data/lib/roby/log/file.rb +43 -18
- data/lib/roby/log/gui/basic_display_ui.rb +89 -0
- data/lib/roby/log/gui/chronicle_view_ui.rb +90 -0
- data/lib/roby/log/gui/data_displays.rb +4 -5
- data/lib/roby/log/gui/data_displays_ui.rb +146 -0
- data/lib/roby/log/gui/relations.rb +18 -18
- data/lib/roby/log/gui/relations_ui.rb +120 -0
- data/lib/roby/log/gui/relations_view_ui.rb +144 -0
- data/lib/roby/log/gui/replay.rb +41 -13
- data/lib/roby/log/gui/replay_controls.rb +3 -0
- data/lib/roby/log/gui/replay_controls.ui +133 -110
- data/lib/roby/log/gui/replay_controls_ui.rb +249 -0
- data/lib/roby/log/hooks.rb +19 -18
- data/lib/roby/log/logger.rb +7 -6
- data/lib/roby/log/notifications.rb +4 -4
- data/lib/roby/log/plan_rebuilder.rb +20 -22
- data/lib/roby/log/relations.rb +44 -16
- data/lib/roby/log/server.rb +1 -4
- data/lib/roby/log/timings.rb +88 -19
- data/lib/roby/plan-object.rb +135 -11
- data/lib/roby/plan.rb +408 -224
- data/lib/roby/planning/loops.rb +32 -25
- data/lib/roby/planning/model.rb +157 -51
- data/lib/roby/planning/task.rb +47 -20
- data/lib/roby/query.rb +128 -92
- data/lib/roby/relations.rb +254 -136
- data/lib/roby/relations/conflicts.rb +6 -9
- data/lib/roby/relations/dependency.rb +358 -0
- data/lib/roby/relations/ensured.rb +0 -1
- data/lib/roby/relations/error_handling.rb +0 -1
- data/lib/roby/relations/events.rb +0 -2
- data/lib/roby/relations/executed_by.rb +26 -11
- data/lib/roby/relations/planned_by.rb +14 -14
- data/lib/roby/robot.rb +46 -0
- data/lib/roby/schedulers/basic.rb +34 -0
- data/lib/roby/standalone.rb +4 -0
- data/lib/roby/standard_errors.rb +21 -15
- data/lib/roby/state/events.rb +5 -4
- data/lib/roby/support.rb +107 -6
- data/lib/roby/task-operations.rb +23 -19
- data/lib/roby/task.rb +522 -148
- data/lib/roby/task_index.rb +80 -0
- data/lib/roby/test/common.rb +283 -44
- data/lib/roby/test/distributed.rb +53 -37
- data/lib/roby/test/testcase.rb +9 -204
- data/lib/roby/test/tools.rb +3 -3
- data/lib/roby/transactions.rb +154 -111
- data/lib/roby/transactions/proxy.rb +40 -7
- data/manifest.xml +20 -0
- data/plugins/fault_injection/README.txt +0 -3
- data/plugins/fault_injection/Rakefile +2 -8
- data/plugins/fault_injection/app.rb +1 -1
- data/plugins/fault_injection/fault_injection.rb +3 -3
- data/plugins/fault_injection/test/test_fault_injection.rb +19 -25
- data/plugins/subsystems/README.txt +0 -3
- data/plugins/subsystems/Rakefile +2 -7
- data/plugins/subsystems/app.rb +27 -16
- data/plugins/subsystems/test/app/config/init.rb +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +1 -1
- data/plugins/subsystems/test/app/tasks/services.rb +1 -1
- data/plugins/subsystems/test/test_subsystems.rb +23 -16
- data/test/distributed/test_communication.rb +32 -15
- data/test/distributed/test_connection.rb +28 -26
- data/test/distributed/test_execution.rb +59 -54
- data/test/distributed/test_mixed_plan.rb +34 -34
- data/test/distributed/test_plan_notifications.rb +26 -26
- data/test/distributed/test_protocol.rb +57 -48
- data/test/distributed/test_query.rb +11 -7
- data/test/distributed/test_remote_plan.rb +71 -71
- data/test/distributed/test_transaction.rb +50 -47
- data/test/mockups/external_process +28 -0
- data/test/planning/test_loops.rb +163 -119
- data/test/planning/test_model.rb +3 -3
- data/test/planning/test_task.rb +27 -7
- data/test/relations/test_conflicts.rb +3 -3
- data/test/relations/test_dependency.rb +324 -0
- data/test/relations/test_ensured.rb +2 -2
- data/test/relations/test_executed_by.rb +94 -19
- data/test/relations/test_planned_by.rb +11 -9
- data/test/suite_core.rb +6 -3
- data/test/suite_distributed.rb +1 -0
- data/test/suite_planning.rb +1 -0
- data/test/suite_relations.rb +2 -2
- data/test/tasks/test_external_process.rb +126 -0
- data/test/{test_thread_task.rb → tasks/test_thread_task.rb} +17 -20
- data/test/test_bgl.rb +21 -1
- data/test/test_event.rb +229 -155
- data/test/test_exceptions.rb +79 -80
- data/test/test_execution_engine.rb +987 -0
- data/test/test_gui.rb +1 -1
- data/test/test_interface.rb +11 -5
- data/test/test_log.rb +18 -7
- data/test/test_log_server.rb +1 -0
- data/test/test_plan.rb +229 -395
- data/test/test_query.rb +193 -35
- data/test/test_relations.rb +88 -8
- data/test/test_state.rb +55 -37
- data/test/test_support.rb +1 -1
- data/test/test_task.rb +371 -218
- data/test/test_testcase.rb +32 -16
- data/test/test_transactions.rb +211 -170
- data/test/test_transactions_proxy.rb +37 -19
- metadata +169 -71
- data/.gitignore +0 -29
- data/doc/styles/allison.css +0 -314
- data/doc/styles/allison.js +0 -316
- data/doc/styles/allison.rb +0 -276
- data/doc/styles/jamis.rb +0 -593
- data/lib/roby/control.rb +0 -746
- data/lib/roby/executives/simple.rb +0 -30
- data/lib/roby/propagation.rb +0 -562
- data/lib/roby/relations/hierarchy.rb +0 -239
- data/lib/roby/transactions/updates.rb +0 -139
- data/test/relations/test_hierarchy.rb +0 -158
- data/test/test_control.rb +0 -399
- data/test/test_propagation.rb +0 -210
data/test/test_exceptions.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
$LOAD_PATH.unshift File.expand_path('..', File.dirname(__FILE__))
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.join('..', 'lib'), File.dirname(__FILE__))
|
|
2
2
|
require 'roby/test/common'
|
|
3
3
|
require 'flexmock'
|
|
4
4
|
require 'roby/test/tasks/simple_task'
|
|
@@ -10,7 +10,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
10
10
|
class SpecializedError < LocalizedError; end
|
|
11
11
|
|
|
12
12
|
def test_execution_exception_initialize
|
|
13
|
-
plan.
|
|
13
|
+
plan.add(task = Task.new)
|
|
14
14
|
error = ExecutionException.new(LocalizedError.new(task))
|
|
15
15
|
assert_equal(task, error.task)
|
|
16
16
|
assert_equal([task], error.trace)
|
|
@@ -24,7 +24,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def test_execution_exception_fork
|
|
27
|
-
task, t1, t2, t3 = prepare_plan :
|
|
27
|
+
task, t1, t2, t3 = prepare_plan :add => 5
|
|
28
28
|
e = ExecutionException.new(LocalizedError.new(task))
|
|
29
29
|
s = e.fork
|
|
30
30
|
|
|
@@ -51,7 +51,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
51
51
|
|
|
52
52
|
e = ExecutionException.new(LocalizedError.new(task))
|
|
53
53
|
s = e.fork
|
|
54
|
-
t1, t2 = prepare_plan :
|
|
54
|
+
t1, t2 = prepare_plan :add => 2
|
|
55
55
|
s.trace << t1 << t2
|
|
56
56
|
e.merge(s)
|
|
57
57
|
assert_equal([task, t2], e.task)
|
|
@@ -87,7 +87,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
plan.
|
|
90
|
+
plan.add(task = klass.new)
|
|
91
91
|
error = ExecutionException.new(SpecializedError.new(task))
|
|
92
92
|
mock.should_receive(:handler2).with(error, task, task).once.ordered
|
|
93
93
|
mock.should_receive(:handler1).with(error, task, task).once.ordered
|
|
@@ -104,8 +104,8 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
104
104
|
def test_exception_in_handler
|
|
105
105
|
Roby.logger.level = Logger::FATAL
|
|
106
106
|
|
|
107
|
-
Roby.
|
|
108
|
-
Roby.
|
|
107
|
+
Roby.app.abort_on_exception = true
|
|
108
|
+
Roby.app.abort_on_application_exception = false
|
|
109
109
|
FlexMock.use do |mock|
|
|
110
110
|
klass = Class.new(SimpleTask) do
|
|
111
111
|
define_method(:mock) { mock }
|
|
@@ -120,19 +120,19 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
120
120
|
end
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
plan.on_exception(RuntimeError) do |task, exception|
|
|
124
124
|
mock.global_handler_called
|
|
125
125
|
raise
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
t1, t2 = klass.new, klass.new
|
|
129
|
-
t1.
|
|
130
|
-
plan.
|
|
129
|
+
t1.depends_on t2
|
|
130
|
+
plan.add_mission(t1)
|
|
131
131
|
|
|
132
132
|
mock.should_receive(:event_called).once.ordered
|
|
133
133
|
mock.should_receive(:task_handler_called).once.ordered
|
|
134
134
|
mock.should_receive(:global_handler_called).once.ordered
|
|
135
|
-
|
|
135
|
+
engine.once { t2.start! }
|
|
136
136
|
assert_raises(SpecializedError) { process_events }
|
|
137
137
|
end
|
|
138
138
|
end
|
|
@@ -145,28 +145,28 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
145
145
|
mock.handler(exception, exception.task, self)
|
|
146
146
|
end
|
|
147
147
|
end.new
|
|
148
|
-
plan.
|
|
149
|
-
t0.
|
|
150
|
-
t1.
|
|
148
|
+
plan.add(t0)
|
|
149
|
+
t0.depends_on t1
|
|
150
|
+
t1.depends_on t2
|
|
151
151
|
|
|
152
152
|
error = ExecutionException.new(SpecializedError.new(t2))
|
|
153
153
|
mock.should_receive(:handler).with(error, t1, t0).once
|
|
154
|
-
assert_equal([],
|
|
154
|
+
assert_equal([], engine.propagate_exceptions([error]))
|
|
155
155
|
assert_equal([error], error.siblings)
|
|
156
156
|
assert_equal([t2, t1], error.trace)
|
|
157
157
|
|
|
158
158
|
error = ExecutionException.new(CodeError.new(nil, t2))
|
|
159
|
-
assert_equal([error],
|
|
159
|
+
assert_equal([error], engine.propagate_exceptions([error]))
|
|
160
160
|
assert_equal(t0, error.task)
|
|
161
161
|
assert_equal([t2, t1, t0], error.trace)
|
|
162
162
|
|
|
163
163
|
# Redo that but this time define a global exception handler
|
|
164
164
|
error = ExecutionException.new(CodeError.new(nil, t2))
|
|
165
|
-
|
|
165
|
+
plan.on_exception(CodeError) do |mod, exception|
|
|
166
166
|
mock.global_handler(exception, exception.task, mod)
|
|
167
167
|
end
|
|
168
|
-
mock.should_receive(:global_handler).with(error, t0,
|
|
169
|
-
assert_equal([],
|
|
168
|
+
mock.should_receive(:global_handler).with(error, t0, plan).once
|
|
169
|
+
assert_equal([], engine.propagate_exceptions([error]))
|
|
170
170
|
end
|
|
171
171
|
end
|
|
172
172
|
|
|
@@ -175,7 +175,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
175
175
|
# 0 being able to handle the exception and 1, 3 not
|
|
176
176
|
|
|
177
177
|
FlexMock.use do |mock|
|
|
178
|
-
t1, t2, t3 = prepare_plan :
|
|
178
|
+
t1, t2, t3 = prepare_plan :add => 3
|
|
179
179
|
t0 = Class.new(Task) do
|
|
180
180
|
attr_accessor :handled_exception
|
|
181
181
|
on_exception(CodeError) do |exception|
|
|
@@ -183,10 +183,10 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
183
183
|
mock.handler(exception, exception.task, self)
|
|
184
184
|
end
|
|
185
185
|
end.new
|
|
186
|
-
plan.
|
|
187
|
-
t0.
|
|
188
|
-
t1.
|
|
189
|
-
t3.
|
|
186
|
+
plan.add(t0)
|
|
187
|
+
t0.depends_on t1
|
|
188
|
+
t1.depends_on t2
|
|
189
|
+
t3.depends_on t2
|
|
190
190
|
|
|
191
191
|
error = ExecutionException.new(CodeError.new(nil, t2))
|
|
192
192
|
mock.should_receive(:handler).with(ExecutionException, t1, t0).once
|
|
@@ -196,7 +196,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
196
196
|
# never tested on t3
|
|
197
197
|
# 2/ propagation begins with t3, in which case +error+ is a sibling of
|
|
198
198
|
# t0.handled_exception
|
|
199
|
-
assert_equal([],
|
|
199
|
+
assert_equal([], engine.propagate_exceptions([error]))
|
|
200
200
|
assert_equal([t2, t1], t0.handled_exception.trace)
|
|
201
201
|
if t0.handled_exception != error
|
|
202
202
|
assert_equal([t2, t3], error.trace)
|
|
@@ -204,7 +204,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
204
204
|
end
|
|
205
205
|
|
|
206
206
|
error = ExecutionException.new(LocalizedError.new(t2))
|
|
207
|
-
assert(fatal =
|
|
207
|
+
assert(fatal = engine.propagate_exceptions([error]))
|
|
208
208
|
assert_equal(1, fatal.size)
|
|
209
209
|
e = *fatal
|
|
210
210
|
assert_equal(t2, e.origin)
|
|
@@ -217,7 +217,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
217
217
|
# 0 being able to handle the exception and 1, 3 not
|
|
218
218
|
|
|
219
219
|
FlexMock.use do |mock|
|
|
220
|
-
t1, t2, t3 = prepare_plan :
|
|
220
|
+
t1, t2, t3 = prepare_plan :add => 3
|
|
221
221
|
|
|
222
222
|
found_exception = nil
|
|
223
223
|
t0 = Class.new(Task) do
|
|
@@ -226,14 +226,14 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
226
226
|
mock.handler(exception, exception.task.to_set, self)
|
|
227
227
|
end
|
|
228
228
|
end.new
|
|
229
|
-
plan.
|
|
230
|
-
t0.
|
|
231
|
-
t0.
|
|
229
|
+
plan.add(t0)
|
|
230
|
+
t0.depends_on t1 ; t1.depends_on t2
|
|
231
|
+
t0.depends_on t3 ; t3.depends_on t2
|
|
232
232
|
|
|
233
233
|
|
|
234
234
|
error = ExecutionException.new(LocalizedError.new(t2))
|
|
235
235
|
mock.should_receive(:handler).with(ExecutionException, [t1, t3].to_set, t0).once
|
|
236
|
-
assert_equal([],
|
|
236
|
+
assert_equal([], engine.propagate_exceptions([error]))
|
|
237
237
|
assert_equal(2, found_exception.trace.size, found_exception.trace)
|
|
238
238
|
assert_equal(t2, found_exception.origin)
|
|
239
239
|
assert_equal([t3, t1].to_set, found_exception.task.to_set)
|
|
@@ -245,7 +245,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
245
245
|
raise RuntimeError
|
|
246
246
|
ev.emit(context)
|
|
247
247
|
end
|
|
248
|
-
plan.
|
|
248
|
+
plan.add(ev)
|
|
249
249
|
assert_original_error(RuntimeError, CommandFailed) { ev.call(nil) }
|
|
250
250
|
assert(!ev.happened?)
|
|
251
251
|
|
|
@@ -254,7 +254,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
254
254
|
ev.emit(context)
|
|
255
255
|
raise RuntimeError
|
|
256
256
|
end
|
|
257
|
-
plan.
|
|
257
|
+
plan.add(ev)
|
|
258
258
|
assert_original_error(RuntimeError, CommandFailed) { ev.call(nil) }
|
|
259
259
|
assert(ev.happened?)
|
|
260
260
|
|
|
@@ -263,9 +263,9 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
263
263
|
ev.emit(context)
|
|
264
264
|
raise RuntimeError
|
|
265
265
|
end
|
|
266
|
-
plan.
|
|
266
|
+
plan.add(ev)
|
|
267
267
|
ev2 = EventGenerator.new(true)
|
|
268
|
-
ev.
|
|
268
|
+
ev.signals ev2
|
|
269
269
|
|
|
270
270
|
assert_original_error(RuntimeError, CommandFailed) { ev.call(nil) }
|
|
271
271
|
assert(ev.happened?)
|
|
@@ -274,7 +274,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
274
274
|
# Check event handlers
|
|
275
275
|
FlexMock.use do |mock|
|
|
276
276
|
ev = EventGenerator.new(true)
|
|
277
|
-
plan.
|
|
277
|
+
plan.add(ev)
|
|
278
278
|
ev.on { mock.handler ; raise RuntimeError }
|
|
279
279
|
ev.on { mock.handler }
|
|
280
280
|
mock.should_receive(:handler).twice
|
|
@@ -284,7 +284,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
284
284
|
|
|
285
285
|
# Tests exception handling mechanism during event propagation
|
|
286
286
|
def test_task_propagation_with_exception
|
|
287
|
-
Roby.
|
|
287
|
+
Roby.app.abort_on_exception = true
|
|
288
288
|
Roby.logger.level = Logger::FATAL
|
|
289
289
|
|
|
290
290
|
task = Class.new(SimpleTask) do
|
|
@@ -303,15 +303,15 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
303
303
|
end.new
|
|
304
304
|
mock.should_receive(:exception).once
|
|
305
305
|
|
|
306
|
-
parent.
|
|
307
|
-
plan.
|
|
306
|
+
parent.depends_on task
|
|
307
|
+
plan.add_mission(parent)
|
|
308
308
|
|
|
309
|
-
|
|
309
|
+
engine.once { task.start! }
|
|
310
310
|
|
|
311
311
|
mock.should_receive(:other_once_handler).once
|
|
312
312
|
mock.should_receive(:other_event_processing).once
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
engine.once { mock.other_once_handler }
|
|
314
|
+
engine.add_propagation_handler { |plan| mock.other_event_processing }
|
|
315
315
|
|
|
316
316
|
begin
|
|
317
317
|
process_events
|
|
@@ -338,15 +338,15 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
338
338
|
end
|
|
339
339
|
|
|
340
340
|
assert_raises(ArgumentError) do
|
|
341
|
-
|
|
341
|
+
plan.on_exception(RuntimeError) do ||
|
|
342
342
|
end
|
|
343
343
|
end
|
|
344
344
|
assert_raises(ArgumentError) do |a, b|
|
|
345
|
-
|
|
345
|
+
plan.on_exception(RuntimeError) do |_|
|
|
346
346
|
end
|
|
347
347
|
end
|
|
348
348
|
assert_nothing_raised do
|
|
349
|
-
|
|
349
|
+
plan.on_exception(RuntimeError) do |_, _|
|
|
350
350
|
end
|
|
351
351
|
end
|
|
352
352
|
end
|
|
@@ -364,20 +364,20 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
364
364
|
mock.caught(exception.task)
|
|
365
365
|
end
|
|
366
366
|
end.new(:id => 'root')
|
|
367
|
-
plan.
|
|
368
|
-
root.
|
|
369
|
-
root.
|
|
370
|
-
root.
|
|
367
|
+
plan.add(root)
|
|
368
|
+
root.depends_on(t11)
|
|
369
|
+
root.depends_on(t12)
|
|
370
|
+
root.depends_on(t13)
|
|
371
371
|
|
|
372
|
-
t11.
|
|
373
|
-
t12.
|
|
372
|
+
t11.depends_on(t21 = Task.new(:id => '21'))
|
|
373
|
+
t12.depends_on(t21)
|
|
374
374
|
|
|
375
|
-
t13.
|
|
376
|
-
t22.
|
|
377
|
-
t31.
|
|
375
|
+
t13.depends_on(t22 = Task.new(:id => '22'))
|
|
376
|
+
t22.depends_on(t31 = Task.new(:id => '31'))
|
|
377
|
+
t31.depends_on(t21)
|
|
378
378
|
|
|
379
379
|
mock.should_receive(:caught).once
|
|
380
|
-
|
|
380
|
+
engine.propagate_exceptions([ExecutionException.new(LocalizedError.new(t21))])
|
|
381
381
|
end
|
|
382
382
|
end
|
|
383
383
|
|
|
@@ -388,7 +388,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
388
388
|
end
|
|
389
389
|
|
|
390
390
|
# First, check methods located in Plan
|
|
391
|
-
plan.
|
|
391
|
+
plan.add(task = model.new)
|
|
392
392
|
r1, r2 = SimpleTask.new, SimpleTask.new
|
|
393
393
|
|
|
394
394
|
task.start!
|
|
@@ -416,21 +416,21 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
416
416
|
|
|
417
417
|
def test_exception_inhibition
|
|
418
418
|
parent, child = prepare_plan :tasks => 2, :model => SimpleTask
|
|
419
|
-
plan.
|
|
420
|
-
parent.
|
|
421
|
-
parent.
|
|
419
|
+
plan.add_mission(parent)
|
|
420
|
+
parent.depends_on child
|
|
421
|
+
parent.signals :start, child, :start
|
|
422
422
|
parent.start!
|
|
423
423
|
child.failed!
|
|
424
424
|
|
|
425
|
-
exceptions =
|
|
425
|
+
exceptions = plan.check_structure
|
|
426
426
|
|
|
427
|
-
plan.
|
|
427
|
+
plan.add(repairing_task = SimpleTask.new)
|
|
428
428
|
repairing_task.start!
|
|
429
|
-
assert_equal(exceptions.to_a,
|
|
430
|
-
assert_equal(exceptions.keys,
|
|
429
|
+
assert_equal(exceptions.to_a, engine.remove_inhibited_exceptions(exceptions))
|
|
430
|
+
assert_equal(exceptions.keys, engine.propagate_exceptions(exceptions))
|
|
431
431
|
plan.add_repair(child.terminal_event, repairing_task)
|
|
432
|
-
assert_equal([],
|
|
433
|
-
assert_equal([],
|
|
432
|
+
assert_equal([], engine.remove_inhibited_exceptions(exceptions))
|
|
433
|
+
assert_equal([], engine.propagate_exceptions(exceptions))
|
|
434
434
|
|
|
435
435
|
ensure
|
|
436
436
|
# Remove the child so that the test's plan cleanup does not complain
|
|
@@ -444,8 +444,8 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
444
444
|
end
|
|
445
445
|
|
|
446
446
|
parent, child = prepare_plan :tasks => 2, :model => task_model
|
|
447
|
-
plan.
|
|
448
|
-
parent.
|
|
447
|
+
plan.add_mission(parent)
|
|
448
|
+
parent.depends_on child
|
|
449
449
|
repairing_task = SimpleTask.new
|
|
450
450
|
child.event(:failed).handle_with repairing_task
|
|
451
451
|
|
|
@@ -453,20 +453,20 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
453
453
|
child.start!
|
|
454
454
|
child.emit error_event
|
|
455
455
|
|
|
456
|
-
exceptions =
|
|
456
|
+
exceptions = plan.check_structure
|
|
457
457
|
|
|
458
|
-
assert_equal([],
|
|
458
|
+
assert_equal([], engine.propagate_exceptions(exceptions))
|
|
459
459
|
assert_equal({ child.terminal_event => repairing_task },
|
|
460
460
|
plan.repairs_for(child.terminal_event), [plan.repairs, child.terminal_event])
|
|
461
461
|
|
|
462
|
-
Roby.
|
|
462
|
+
Roby.app.abort_on_exception = false
|
|
463
463
|
process_events
|
|
464
464
|
assert(repairing_task.running?)
|
|
465
465
|
|
|
466
466
|
# Make the "repair task" finish, but do not repair the plan.
|
|
467
467
|
# propagate_exceptions must not add a new repair
|
|
468
468
|
repairing_task.success!
|
|
469
|
-
assert_equal(exceptions.keys,
|
|
469
|
+
assert_equal(exceptions.keys, engine.propagate_exceptions(exceptions))
|
|
470
470
|
|
|
471
471
|
ensure
|
|
472
472
|
parent.remove_child child if child
|
|
@@ -476,7 +476,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
476
476
|
test_error_handling_relation(:blocked)
|
|
477
477
|
end
|
|
478
478
|
|
|
479
|
-
def
|
|
479
|
+
def test_mission_exceptions
|
|
480
480
|
mission = prepare_plan :missions => 1, :model => SimpleTask
|
|
481
481
|
repairing_task = SimpleTask.new
|
|
482
482
|
mission.event(:failed).handle_with repairing_task
|
|
@@ -484,15 +484,15 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
484
484
|
mission.start!
|
|
485
485
|
mission.emit :failed
|
|
486
486
|
|
|
487
|
-
exceptions =
|
|
487
|
+
exceptions = plan.check_structure
|
|
488
488
|
assert_equal(1, exceptions.size)
|
|
489
489
|
assert_kind_of(Roby::MissionFailedError, exceptions.to_a[0][0].exception, exceptions)
|
|
490
490
|
|
|
491
|
-
assert_equal([],
|
|
491
|
+
assert_equal([], engine.propagate_exceptions(exceptions))
|
|
492
492
|
assert_equal({ mission.terminal_event => repairing_task },
|
|
493
493
|
plan.repairs_for(mission.terminal_event), [plan.repairs, mission.terminal_event])
|
|
494
494
|
|
|
495
|
-
Roby.
|
|
495
|
+
Roby.app.abort_on_exception = false
|
|
496
496
|
process_events
|
|
497
497
|
assert(plan.mission?(mission))
|
|
498
498
|
assert(repairing_task.running?)
|
|
@@ -500,13 +500,14 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
500
500
|
# Make the "repair task" finish, but do not repair the plan.
|
|
501
501
|
# propagate_exceptions must not add a new repair
|
|
502
502
|
repairing_task.success!
|
|
503
|
-
assert_equal(exceptions.keys,
|
|
503
|
+
assert_equal(exceptions.keys, engine.propagate_exceptions(exceptions))
|
|
504
504
|
|
|
505
505
|
# Discard the mission so that the test teardown does not complain
|
|
506
|
-
plan.
|
|
506
|
+
plan.unmark_mission(mission)
|
|
507
507
|
end
|
|
508
508
|
|
|
509
509
|
def test_filter_command_errors
|
|
510
|
+
Roby.app.filter_backtraces = true
|
|
510
511
|
model = Class.new(SimpleTask) do
|
|
511
512
|
event :start do
|
|
512
513
|
raise ArgumentError
|
|
@@ -565,8 +566,6 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
565
566
|
end
|
|
566
567
|
|
|
567
568
|
def test_filter_polling_errors
|
|
568
|
-
#Roby.control.fatal_exceptions = false
|
|
569
|
-
|
|
570
569
|
model = Class.new(SimpleTask) do
|
|
571
570
|
poll do
|
|
572
571
|
raise ArgumentError, "bla"
|
|
@@ -575,7 +574,7 @@ class TC_Exceptions < Test::Unit::TestCase
|
|
|
575
574
|
|
|
576
575
|
parent = prepare_plan :permanent => 1, :model => SimpleTask
|
|
577
576
|
child = prepare_plan :permanent => 1, :model => model
|
|
578
|
-
parent.
|
|
577
|
+
parent.depends_on child
|
|
579
578
|
parent.start!
|
|
580
579
|
child.start!
|
|
581
580
|
child.failed!
|
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.join('..', 'lib'), File.dirname(__FILE__))
|
|
2
|
+
require 'roby/test/common'
|
|
3
|
+
require 'flexmock'
|
|
4
|
+
require 'roby/test/tasks/simple_task'
|
|
5
|
+
require 'roby/test/tasks/empty_task'
|
|
6
|
+
require 'mockups/tasks'
|
|
7
|
+
require 'flexmock'
|
|
8
|
+
require 'utilrb/hash/slice'
|
|
9
|
+
require 'roby/log'
|
|
10
|
+
|
|
11
|
+
class TC_ExecutionEngine < Test::Unit::TestCase
|
|
12
|
+
include Roby::Test
|
|
13
|
+
|
|
14
|
+
def setup
|
|
15
|
+
super
|
|
16
|
+
Roby::Log.add_logger(@finalized_tasks_recorder = FinalizedTaskRecorder.new)
|
|
17
|
+
end
|
|
18
|
+
def teardown
|
|
19
|
+
Roby::Log.remove_logger @finalized_tasks_recorder
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_gather_propagation
|
|
24
|
+
e1, e2, e3 = EventGenerator.new(true), EventGenerator.new(true), EventGenerator.new(true)
|
|
25
|
+
plan.add [e1, e2, e3]
|
|
26
|
+
|
|
27
|
+
set = engine.gather_propagation do
|
|
28
|
+
e1.call(1)
|
|
29
|
+
e1.call(4)
|
|
30
|
+
e2.emit(2)
|
|
31
|
+
e2.emit(3)
|
|
32
|
+
e3.call(5)
|
|
33
|
+
e3.emit(6)
|
|
34
|
+
end
|
|
35
|
+
assert_equal({ e1 => [nil, [nil, [1], nil, nil, [4], nil]], e2 => [[nil, [2], nil, nil, [3], nil], nil], e3 => [[nil, [6], nil], [nil, [5], nil]] }, set)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_emission_is_forbidden_outside_propagation_phase
|
|
39
|
+
# Temporarily disable logging as we are going to generate a fatal error
|
|
40
|
+
# ..
|
|
41
|
+
Roby.logger.level = Logger::FATAL
|
|
42
|
+
|
|
43
|
+
plan.add_permanent(task = SimpleTask.new)
|
|
44
|
+
|
|
45
|
+
error = nil
|
|
46
|
+
failure = lambda do
|
|
47
|
+
begin
|
|
48
|
+
task.emit(:start)
|
|
49
|
+
rescue Exception => e
|
|
50
|
+
error = e
|
|
51
|
+
raise
|
|
52
|
+
end
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
plan.structure_checks << failure
|
|
56
|
+
|
|
57
|
+
engine.run
|
|
58
|
+
engine.join
|
|
59
|
+
assert_kind_of(PhaseMismatch, error)
|
|
60
|
+
|
|
61
|
+
ensure
|
|
62
|
+
plan.structure_checks.delete_if { |v| v == failure }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_propagation_handlers
|
|
66
|
+
test_obj = Object.new
|
|
67
|
+
def test_obj.mock_handler(plan)
|
|
68
|
+
@mockup.called(plan)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
FlexMock.use do |mock|
|
|
72
|
+
test_obj.instance_variable_set :@mockup, mock
|
|
73
|
+
id = engine.add_propagation_handler test_obj.method(:mock_handler)
|
|
74
|
+
|
|
75
|
+
mock.should_receive(:called).with(plan).twice
|
|
76
|
+
process_events
|
|
77
|
+
process_events
|
|
78
|
+
engine.remove_propagation_handler id
|
|
79
|
+
process_events
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
FlexMock.use do |mock|
|
|
83
|
+
test_obj.instance_variable_set :@mockup, mock
|
|
84
|
+
id = engine.add_propagation_handler { |plan| mock.called(plan) }
|
|
85
|
+
|
|
86
|
+
mock.should_receive(:called).with(plan).twice
|
|
87
|
+
process_events
|
|
88
|
+
process_events
|
|
89
|
+
engine.remove_propagation_handler id
|
|
90
|
+
process_events
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
assert_raises(ArgumentError) do
|
|
94
|
+
engine.add_propagation_handler { |plan, failure| mock.called(plan) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
assert_nothing_raised { process_events }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_prepare_propagation
|
|
101
|
+
g1, g2 = EventGenerator.new(true), EventGenerator.new(true)
|
|
102
|
+
ev = Event.new(g2, 0, nil)
|
|
103
|
+
|
|
104
|
+
step = [nil, [1], nil, nil, [4], nil]
|
|
105
|
+
source_events, source_generators, context = engine.prepare_propagation(nil, false, step)
|
|
106
|
+
assert_equal(ValueSet.new, source_events)
|
|
107
|
+
assert_equal(ValueSet.new, source_generators)
|
|
108
|
+
assert_equal([1, 4], context)
|
|
109
|
+
|
|
110
|
+
step = [nil, [], nil, nil, [4], nil]
|
|
111
|
+
source_events, source_generators, context = engine.prepare_propagation(nil, false, step)
|
|
112
|
+
assert_equal(ValueSet.new, source_events)
|
|
113
|
+
assert_equal(ValueSet.new, source_generators)
|
|
114
|
+
assert_equal([4], context)
|
|
115
|
+
|
|
116
|
+
step = [g1, [], nil, ev, [], nil]
|
|
117
|
+
source_events, source_generators, context = engine.prepare_propagation(nil, false, step)
|
|
118
|
+
assert_equal([g1, g2].to_value_set, source_generators)
|
|
119
|
+
assert_equal([ev].to_value_set, source_events)
|
|
120
|
+
assert_equal(nil, context)
|
|
121
|
+
|
|
122
|
+
step = [g2, [], nil, ev, [], nil]
|
|
123
|
+
source_events, source_generators, context = engine.prepare_propagation(nil, false, step)
|
|
124
|
+
assert_equal([g2].to_value_set, source_generators)
|
|
125
|
+
assert_equal([ev].to_value_set, source_events)
|
|
126
|
+
assert_equal(nil, context)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_precedence_graph
|
|
130
|
+
e1, e2 = EventGenerator.new(true), EventGenerator.new(true)
|
|
131
|
+
engine.event_ordering << :bla
|
|
132
|
+
plan.add e1
|
|
133
|
+
assert(engine.event_ordering.empty?)
|
|
134
|
+
plan.add e2
|
|
135
|
+
|
|
136
|
+
engine.event_ordering << :bla
|
|
137
|
+
task = Roby::Task.new
|
|
138
|
+
plan.add(task)
|
|
139
|
+
assert(engine.event_ordering.empty?)
|
|
140
|
+
assert(EventStructure::Precedence.linked?(task.event(:start), task.event(:updated_data)))
|
|
141
|
+
|
|
142
|
+
engine.event_ordering << :bla
|
|
143
|
+
e1.signals e2
|
|
144
|
+
assert(EventStructure::Precedence.linked?(e1, e2))
|
|
145
|
+
assert(engine.event_ordering.empty?)
|
|
146
|
+
|
|
147
|
+
engine.event_ordering << :bla
|
|
148
|
+
e1.remove_signal e2
|
|
149
|
+
assert(engine.event_ordering.empty?)
|
|
150
|
+
assert(!EventStructure::Precedence.linked?(e1, e2))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def test_next_step
|
|
154
|
+
# For the test to be valid, we need +pending+ to have a deterministic ordering
|
|
155
|
+
# Fix that here
|
|
156
|
+
e1, e2 = EventGenerator.new(true), EventGenerator.new(true)
|
|
157
|
+
plan.add [e1, e2]
|
|
158
|
+
pending = [ [e1, [true, nil, nil, nil]], [e2, [false, nil, nil, nil]] ]
|
|
159
|
+
def pending.each_key; each { |(k, v)| yield(k) } end
|
|
160
|
+
def pending.delete(ev); delete_if { |(k, v)| k == ev } end
|
|
161
|
+
|
|
162
|
+
e1.add_precedence e2
|
|
163
|
+
assert_equal(e1, engine.next_event(pending).first)
|
|
164
|
+
|
|
165
|
+
e1.remove_precedence e2
|
|
166
|
+
e2.add_precedence e1
|
|
167
|
+
assert_equal(e2, engine.next_event(pending).first)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def test_delay
|
|
171
|
+
FlexMock.use(Time) do |time_proxy|
|
|
172
|
+
current_time = Time.now + 5
|
|
173
|
+
time_proxy.should_receive(:now).and_return { current_time }
|
|
174
|
+
|
|
175
|
+
plan.add_mission(t = SimpleTask.new)
|
|
176
|
+
e = EventGenerator.new(true)
|
|
177
|
+
t.event(:start).signals e, :delay => 0.1
|
|
178
|
+
engine.once { t.start! }
|
|
179
|
+
process_events
|
|
180
|
+
assert(!e.happened?)
|
|
181
|
+
current_time += 0.1
|
|
182
|
+
process_events
|
|
183
|
+
assert(e.happened?)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def test_duplicate_signals
|
|
188
|
+
plan.add_mission(t = SimpleTask.new)
|
|
189
|
+
|
|
190
|
+
FlexMock.use do |mock|
|
|
191
|
+
t.on(:start) { |event| t.emit(:success, *event.context) }
|
|
192
|
+
t.on(:start) { |event| t.emit(:success, *event.context) }
|
|
193
|
+
|
|
194
|
+
t.on(:success) { |event| mock.success(event.context) }
|
|
195
|
+
t.on(:stop) { |event| mock.stop(event.context) }
|
|
196
|
+
mock.should_receive(:success).with([42, 42]).once.ordered
|
|
197
|
+
mock.should_receive(:stop).with([42, 42]).once.ordered
|
|
198
|
+
t.start!(42)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
def test_diamond_structure
|
|
202
|
+
a = Class.new(SimpleTask) do
|
|
203
|
+
event :child_success
|
|
204
|
+
event :child_stop
|
|
205
|
+
forward :child_success => :child_stop
|
|
206
|
+
end.new(:id => 'a')
|
|
207
|
+
|
|
208
|
+
plan.add_mission(a)
|
|
209
|
+
a.depends_on(b = SimpleTask.new(:id => 'b'))
|
|
210
|
+
|
|
211
|
+
b.forward_to(:success, a, :child_success)
|
|
212
|
+
b.forward_to(:stop, a, :child_stop)
|
|
213
|
+
|
|
214
|
+
FlexMock.use do |mock|
|
|
215
|
+
a.on(:child_stop) { mock.stopped }
|
|
216
|
+
mock.should_receive(:stopped).once.ordered
|
|
217
|
+
a.start!
|
|
218
|
+
b.start!
|
|
219
|
+
b.success!
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def test_signal_forward
|
|
224
|
+
forward = EventGenerator.new(true)
|
|
225
|
+
signal = EventGenerator.new(true)
|
|
226
|
+
plan.add [forward, signal]
|
|
227
|
+
|
|
228
|
+
FlexMock.use do |mock|
|
|
229
|
+
sink = EventGenerator.new do |context|
|
|
230
|
+
mock.command_called(context)
|
|
231
|
+
sink.emit(42)
|
|
232
|
+
end
|
|
233
|
+
sink.on { |event| mock.handler_called(event.context) }
|
|
234
|
+
|
|
235
|
+
forward.forward_to sink
|
|
236
|
+
signal.signals sink
|
|
237
|
+
|
|
238
|
+
seed = lambda do
|
|
239
|
+
forward.call(24)
|
|
240
|
+
signal.call(42)
|
|
241
|
+
end
|
|
242
|
+
mock.should_receive(:command_called).with([42]).once.ordered
|
|
243
|
+
mock.should_receive(:handler_called).with([42, 24]).once.ordered
|
|
244
|
+
engine.propagate_events([seed])
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
module LogEventGathering
|
|
249
|
+
class << self
|
|
250
|
+
attr_accessor :mockup
|
|
251
|
+
def handle(name, obj)
|
|
252
|
+
mockup.send(name, obj, obj.engine.propagation_sources) if mockup
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def signalling(event, to)
|
|
257
|
+
super if defined? super
|
|
258
|
+
LogEventGathering.handle(:signalling, self)
|
|
259
|
+
end
|
|
260
|
+
def forwarding(event, to)
|
|
261
|
+
super if defined? super
|
|
262
|
+
LogEventGathering.handle(:forwarding, self)
|
|
263
|
+
end
|
|
264
|
+
def emitting(context)
|
|
265
|
+
super if defined? super
|
|
266
|
+
LogEventGathering.handle(:emitting, self)
|
|
267
|
+
end
|
|
268
|
+
def calling(context)
|
|
269
|
+
super if defined? super
|
|
270
|
+
LogEventGathering.handle(:calling, self)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
EventGenerator.include LogEventGathering
|
|
274
|
+
|
|
275
|
+
def test_log_events
|
|
276
|
+
FlexMock.use do |mock|
|
|
277
|
+
LogEventGathering.mockup = mock
|
|
278
|
+
dst = EventGenerator.new { }
|
|
279
|
+
src = EventGenerator.new { dst.call }
|
|
280
|
+
plan.add [src, dst]
|
|
281
|
+
|
|
282
|
+
mock.should_receive(:signalling).never
|
|
283
|
+
mock.should_receive(:forwarding).never
|
|
284
|
+
mock.should_receive(:calling).with(src, [].to_value_set).once
|
|
285
|
+
mock.should_receive(:calling).with(dst, [src].to_value_set).once
|
|
286
|
+
src.call
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
ensure
|
|
290
|
+
LogEventGathering.mockup = nil
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def test_add_framework_errors
|
|
294
|
+
# Shut up the logger in this test
|
|
295
|
+
Roby.logger.level = Logger::FATAL
|
|
296
|
+
exception = begin; raise RuntimeError
|
|
297
|
+
rescue; $!
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
Roby.app.abort_on_application_exception = false
|
|
301
|
+
assert_nothing_raised { engine.add_framework_error(exception, :exceptions) }
|
|
302
|
+
|
|
303
|
+
Roby.app.abort_on_application_exception = true
|
|
304
|
+
assert_raises(RuntimeError) { engine.add_framework_error(exception, :exceptions) }
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def test_event_loop
|
|
308
|
+
plan.add_mission(start_node = EmptyTask.new)
|
|
309
|
+
next_event = [ start_node, :start ]
|
|
310
|
+
plan.add_mission(if_node = ChoiceTask.new)
|
|
311
|
+
start_node.on(:stop) { next_event = [if_node, :start] }
|
|
312
|
+
if_node.on(:stop) { }
|
|
313
|
+
|
|
314
|
+
engine.propagation_handlers << lambda do |plan|
|
|
315
|
+
next unless next_event
|
|
316
|
+
task, event = *next_event
|
|
317
|
+
next_event = nil
|
|
318
|
+
task.event(event).call(nil)
|
|
319
|
+
end
|
|
320
|
+
process_events
|
|
321
|
+
assert(start_node.finished?)
|
|
322
|
+
|
|
323
|
+
process_events
|
|
324
|
+
assert(if_node.finished?)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def test_every
|
|
328
|
+
# Check that every(cycle_length) works fine
|
|
329
|
+
engine.run
|
|
330
|
+
|
|
331
|
+
samples = []
|
|
332
|
+
id = engine.every(0.1) do
|
|
333
|
+
samples << engine.cycle_start
|
|
334
|
+
end
|
|
335
|
+
sleep(1)
|
|
336
|
+
engine.remove_periodic_handler(id)
|
|
337
|
+
size = samples.size
|
|
338
|
+
assert(size > 2, samples.map { |t| t.to_hms })
|
|
339
|
+
|
|
340
|
+
samples.each_cons(2) do |a, b|
|
|
341
|
+
assert_in_delta(0.1, b - a, 0.001)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Check that no samples have been added after the 'remove_periodic_handler'
|
|
345
|
+
assert_equal(size, samples.size)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def test_once
|
|
349
|
+
FlexMock.use do |mock|
|
|
350
|
+
engine.once { mock.called }
|
|
351
|
+
mock.should_receive(:called).once
|
|
352
|
+
process_events
|
|
353
|
+
end
|
|
354
|
+
FlexMock.use do |mock|
|
|
355
|
+
engine.once { mock.called }
|
|
356
|
+
mock.should_receive(:called).once
|
|
357
|
+
process_events
|
|
358
|
+
process_events
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def test_failing_once
|
|
363
|
+
Roby.logger.level = Logger::FATAL
|
|
364
|
+
Roby.app.abort_on_exception = true
|
|
365
|
+
engine.run
|
|
366
|
+
|
|
367
|
+
FlexMock.use do |mock|
|
|
368
|
+
engine.once { mock.called; raise }
|
|
369
|
+
mock.should_receive(:called).once
|
|
370
|
+
|
|
371
|
+
assert_raises(ExecutionQuitError) do
|
|
372
|
+
engine.wait_one_cycle
|
|
373
|
+
engine.join
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
class SpecificException < RuntimeError; end
|
|
379
|
+
def test_unhandled_event_exceptions
|
|
380
|
+
Roby.app.abort_on_exception = true
|
|
381
|
+
|
|
382
|
+
# Test that the event is not pending if the command raises
|
|
383
|
+
model = Class.new(SimpleTask) do
|
|
384
|
+
event :start do |context|
|
|
385
|
+
raise SpecificException, "bla"
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
plan.add_permanent(t = model.new)
|
|
389
|
+
|
|
390
|
+
assert_original_error(SpecificException, CommandFailed) { t.start! }
|
|
391
|
+
assert(!t.event(:start).pending?)
|
|
392
|
+
|
|
393
|
+
# Check that the propagation is pruned if the command raises
|
|
394
|
+
t = nil
|
|
395
|
+
FlexMock.use do |mock|
|
|
396
|
+
t = Class.new(SimpleTask) do
|
|
397
|
+
event :start do |context|
|
|
398
|
+
mock.command_called
|
|
399
|
+
raise SpecificException, "bla"
|
|
400
|
+
emit :start
|
|
401
|
+
end
|
|
402
|
+
on(:start) { |ev| mock.handler_called }
|
|
403
|
+
end.new
|
|
404
|
+
plan.add_permanent(t)
|
|
405
|
+
|
|
406
|
+
mock.should_receive(:command_called).once
|
|
407
|
+
mock.should_receive(:handler_called).never
|
|
408
|
+
|
|
409
|
+
engine.once { t.start!(nil) }
|
|
410
|
+
assert_original_error(SpecificException, CommandFailed) { process_events }
|
|
411
|
+
assert(!t.event(:start).pending)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Check that the task has been garbage collected in the process
|
|
415
|
+
assert(! plan.include?(t))
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def apply_check_structure(&block)
|
|
419
|
+
Plan.structure_checks.clear
|
|
420
|
+
Plan.structure_checks << lambda(&block)
|
|
421
|
+
process_events
|
|
422
|
+
ensure
|
|
423
|
+
Plan.structure_checks.clear
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def test_check_structure
|
|
427
|
+
Roby.logger.level = Logger::FATAL
|
|
428
|
+
Roby.app.abort_on_exception = false
|
|
429
|
+
|
|
430
|
+
# Check on a single task
|
|
431
|
+
plan.add_mission(t = SimpleTask.new)
|
|
432
|
+
apply_check_structure { LocalizedError.new(t) }
|
|
433
|
+
assert(! plan.include?(t))
|
|
434
|
+
|
|
435
|
+
# Make sure that a task which has been repaired will not be killed
|
|
436
|
+
plan.add_mission(t = SimpleTask.new)
|
|
437
|
+
did_once = false
|
|
438
|
+
apply_check_structure do
|
|
439
|
+
unless did_once
|
|
440
|
+
did_once = true
|
|
441
|
+
LocalizedError.new(t)
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
assert(plan.include?(t))
|
|
445
|
+
|
|
446
|
+
# Check that whole task trees are killed
|
|
447
|
+
t0, t1, t2, t3 = prepare_plan :discover => 4
|
|
448
|
+
t0.depends_on t2
|
|
449
|
+
t1.depends_on t2
|
|
450
|
+
t2.depends_on t3
|
|
451
|
+
|
|
452
|
+
plan.add_mission(t0)
|
|
453
|
+
plan.add_mission(t1)
|
|
454
|
+
FlexMock.use do |mock|
|
|
455
|
+
mock.should_receive(:checking).twice
|
|
456
|
+
apply_check_structure do
|
|
457
|
+
mock.checking
|
|
458
|
+
LocalizedError.new(t2)
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
assert(!plan.include?(t0))
|
|
462
|
+
assert(!plan.include?(t1))
|
|
463
|
+
assert(!plan.include?(t2))
|
|
464
|
+
process_events
|
|
465
|
+
assert(!plan.include?(t3))
|
|
466
|
+
|
|
467
|
+
# Check that we can kill selectively by returning a hash
|
|
468
|
+
t0, t1, t2 = prepare_plan :discover => 3
|
|
469
|
+
t0.depends_on t2
|
|
470
|
+
t1.depends_on t2
|
|
471
|
+
plan.add_mission(t0)
|
|
472
|
+
plan.add_mission(t1)
|
|
473
|
+
apply_check_structure { { LocalizedError.new(t2) => t0 } }
|
|
474
|
+
assert(!plan.include?(t0))
|
|
475
|
+
assert(plan.include?(t1))
|
|
476
|
+
assert(plan.include?(t2))
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def test_at_cycle_end
|
|
480
|
+
# Shut up the logger in this test
|
|
481
|
+
Roby.logger.level = Logger::FATAL
|
|
482
|
+
Roby.app.abort_on_application_exception = false
|
|
483
|
+
|
|
484
|
+
FlexMock.use do |mock|
|
|
485
|
+
mock.should_receive(:before_error).at_least.once
|
|
486
|
+
mock.should_receive(:after_error).never
|
|
487
|
+
mock.should_receive(:called).at_least.once
|
|
488
|
+
|
|
489
|
+
engine.at_cycle_end do
|
|
490
|
+
mock.before_error
|
|
491
|
+
raise
|
|
492
|
+
mock.after_error
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
engine.at_cycle_end do
|
|
496
|
+
mock.called
|
|
497
|
+
unless engine.quitting?
|
|
498
|
+
engine.quit
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
engine.run
|
|
502
|
+
engine.join
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def test_inside_outside_control
|
|
507
|
+
# First, no control thread
|
|
508
|
+
assert(engine.inside_control?)
|
|
509
|
+
assert(engine.outside_control?)
|
|
510
|
+
|
|
511
|
+
# Add a fake control thread
|
|
512
|
+
begin
|
|
513
|
+
engine.thread = Thread.main
|
|
514
|
+
assert(engine.inside_control?)
|
|
515
|
+
assert(!engine.outside_control?)
|
|
516
|
+
|
|
517
|
+
t = Thread.new do
|
|
518
|
+
assert(!engine.inside_control?)
|
|
519
|
+
assert(engine.outside_control?)
|
|
520
|
+
end
|
|
521
|
+
t.value
|
|
522
|
+
ensure
|
|
523
|
+
engine.thread = nil
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# .. and test with the real one
|
|
527
|
+
engine.run
|
|
528
|
+
engine.execute do
|
|
529
|
+
assert(engine.inside_control?)
|
|
530
|
+
assert(!engine.outside_control?)
|
|
531
|
+
end
|
|
532
|
+
assert(!engine.inside_control?)
|
|
533
|
+
assert(engine.outside_control?)
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def test_execute
|
|
537
|
+
# Set a fake control thread
|
|
538
|
+
engine.thread = Thread.main
|
|
539
|
+
|
|
540
|
+
FlexMock.use do |mock|
|
|
541
|
+
mock.should_receive(:thread_before).once.ordered
|
|
542
|
+
mock.should_receive(:main_before).once.ordered
|
|
543
|
+
mock.should_receive(:execute).once.ordered.with(Thread.current).and_return(42)
|
|
544
|
+
mock.should_receive(:main_after).once.ordered(:finish)
|
|
545
|
+
mock.should_receive(:thread_after).once.ordered(:finish)
|
|
546
|
+
|
|
547
|
+
returned_value = nil
|
|
548
|
+
t = Thread.new do
|
|
549
|
+
mock.thread_before
|
|
550
|
+
returned_value = engine.execute do
|
|
551
|
+
mock.execute(Thread.current)
|
|
552
|
+
end
|
|
553
|
+
mock.thread_after
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Wait for the thread to block
|
|
557
|
+
while !t.stop?; sleep(0.1) end
|
|
558
|
+
mock.main_before
|
|
559
|
+
assert(t.alive?)
|
|
560
|
+
process_events
|
|
561
|
+
mock.main_after
|
|
562
|
+
t.join
|
|
563
|
+
|
|
564
|
+
assert_equal(42, returned_value)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
ensure
|
|
568
|
+
engine.thread = nil
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
def test_execute_error
|
|
572
|
+
assert(!engine.thread)
|
|
573
|
+
# Set a fake control thread
|
|
574
|
+
engine.thread = Thread.main
|
|
575
|
+
assert(!engine.quitting?)
|
|
576
|
+
|
|
577
|
+
returned_value = nil
|
|
578
|
+
t = Thread.new do
|
|
579
|
+
returned_value = begin
|
|
580
|
+
engine.execute do
|
|
581
|
+
raise ArgumentError
|
|
582
|
+
end
|
|
583
|
+
rescue ArgumentError => e
|
|
584
|
+
e
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# Wait for the thread to block
|
|
589
|
+
while !t.stop?; sleep(0.1) end
|
|
590
|
+
process_events
|
|
591
|
+
t.join
|
|
592
|
+
|
|
593
|
+
assert_kind_of(ArgumentError, returned_value)
|
|
594
|
+
assert(!engine.quitting?)
|
|
595
|
+
|
|
596
|
+
ensure
|
|
597
|
+
engine.thread = nil
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def test_wait_until
|
|
601
|
+
# Set a fake control thread
|
|
602
|
+
engine.thread = Thread.main
|
|
603
|
+
|
|
604
|
+
plan.add_permanent(task = SimpleTask.new)
|
|
605
|
+
t = Thread.new do
|
|
606
|
+
engine.wait_until(task.event(:start)) do
|
|
607
|
+
task.start!
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
while !t.stop?; sleep(0.1) end
|
|
612
|
+
process_events
|
|
613
|
+
assert_nothing_raised { t.value }
|
|
614
|
+
|
|
615
|
+
ensure
|
|
616
|
+
engine.thread = nil
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
def test_wait_until_unreachable
|
|
620
|
+
# Set a fake control thread
|
|
621
|
+
engine.thread = Thread.main
|
|
622
|
+
|
|
623
|
+
plan.add_permanent(task = SimpleTask.new)
|
|
624
|
+
t = Thread.new do
|
|
625
|
+
begin
|
|
626
|
+
engine.wait_until(task.event(:success)) do
|
|
627
|
+
task.start!
|
|
628
|
+
task.stop!
|
|
629
|
+
end
|
|
630
|
+
rescue Exception => e
|
|
631
|
+
e
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
while !t.stop?; sleep(0.1) end
|
|
636
|
+
process_events
|
|
637
|
+
|
|
638
|
+
result = t.value
|
|
639
|
+
assert_kind_of(UnreachableEvent, result)
|
|
640
|
+
assert_equal(task.event(:success), result.failed_generator)
|
|
641
|
+
|
|
642
|
+
ensure
|
|
643
|
+
engine.thread = nil
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
class CaptureLastStats
|
|
647
|
+
attr_reader :last_stats
|
|
648
|
+
def splat?; true end
|
|
649
|
+
def logs_message?(m); m == :cycle_end end
|
|
650
|
+
def close; end
|
|
651
|
+
def cycle_end(time, stats)
|
|
652
|
+
@last_stats = stats
|
|
653
|
+
end
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
def test_stats
|
|
657
|
+
require 'roby/log'
|
|
658
|
+
engine.run
|
|
659
|
+
|
|
660
|
+
capture = CaptureLastStats.new
|
|
661
|
+
Roby::Log.add_logger capture
|
|
662
|
+
|
|
663
|
+
time_events = [:real_start, :events, :structure_check, :exception_propagation, :exception_fatal, :garbage_collect, :application_errors, :ruby_gc, :sleep, :end]
|
|
664
|
+
10.times do
|
|
665
|
+
engine.wait_one_cycle
|
|
666
|
+
next unless capture.last_stats
|
|
667
|
+
|
|
668
|
+
Roby.synchronize do
|
|
669
|
+
timepoints = capture.last_stats.slice(*time_events)
|
|
670
|
+
assert(timepoints.all? { |name, d| d > 0 })
|
|
671
|
+
|
|
672
|
+
sorted_by_time = timepoints.sort_by { |name, d| d }
|
|
673
|
+
sorted_by_name = timepoints.sort_by { |name, d| time_events.index(name) }
|
|
674
|
+
sorted_by_time.each_with_index do |(name, d), i|
|
|
675
|
+
assert(sorted_by_name[i][1] == d)
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
ensure
|
|
681
|
+
Roby::Log.remove_logger capture if capture
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
def clear_finalized
|
|
685
|
+
Roby::Log.flush
|
|
686
|
+
@finalized_tasks_recorder.clear
|
|
687
|
+
end
|
|
688
|
+
# Returns the RemoteID for tasks that have been finalized since the last
|
|
689
|
+
# call to #clear_finalized.
|
|
690
|
+
def finalized_tasks; @finalized_tasks_recorder.tasks end
|
|
691
|
+
# Returns the RemoteID for events that have been finalized since the last
|
|
692
|
+
# call to #clear_finalized.
|
|
693
|
+
def finalized_events; @finalized_tasks_recorder.events end
|
|
694
|
+
class FinalizedTaskRecorder
|
|
695
|
+
attribute(:tasks) { Array.new }
|
|
696
|
+
attribute(:events) { Array.new }
|
|
697
|
+
def logs_message?(m); m == :finalized_task || m == :finalized_event end
|
|
698
|
+
def finalized_task(time, plan, task)
|
|
699
|
+
tasks << task
|
|
700
|
+
end
|
|
701
|
+
def finalized_event(time, plan, event)
|
|
702
|
+
events << event unless event.respond_to?(:task)
|
|
703
|
+
end
|
|
704
|
+
def clear
|
|
705
|
+
tasks.clear
|
|
706
|
+
events.clear
|
|
707
|
+
end
|
|
708
|
+
def close; end
|
|
709
|
+
def splat?; true end
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
def assert_finalizes(plan, unneeded, finalized = nil)
|
|
713
|
+
finalized ||= unneeded
|
|
714
|
+
finalized = finalized.map { |obj| obj.remote_id }
|
|
715
|
+
clear_finalized
|
|
716
|
+
|
|
717
|
+
yield if block_given?
|
|
718
|
+
|
|
719
|
+
assert_equal(unneeded.to_set, plan.unneeded_tasks.to_set)
|
|
720
|
+
engine.garbage_collect
|
|
721
|
+
process_events
|
|
722
|
+
engine.garbage_collect
|
|
723
|
+
|
|
724
|
+
# !!! We are actually relying on the logging queue for this to work.
|
|
725
|
+
# make sure it is empty before testing anything
|
|
726
|
+
Roby::Log.flush
|
|
727
|
+
|
|
728
|
+
assert_equal(finalized.to_set, (finalized_tasks.to_set | finalized_events.to_set) )
|
|
729
|
+
assert(! finalized.any? { |t| plan.include?(t) })
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
def test_garbage_collect_tasks
|
|
733
|
+
klass = Class.new(Task) do
|
|
734
|
+
attr_accessor :delays
|
|
735
|
+
|
|
736
|
+
event(:start, :command => true)
|
|
737
|
+
event(:stop) do |context|
|
|
738
|
+
if delays
|
|
739
|
+
return
|
|
740
|
+
else
|
|
741
|
+
emit(:stop)
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
t1, t2, t3, t4, t5, t6, t7, t8, p1 = (1..9).map { |i| klass.new(:id => i) }
|
|
747
|
+
t1.depends_on t3
|
|
748
|
+
t2.depends_on t3
|
|
749
|
+
t3.depends_on t4
|
|
750
|
+
t5.depends_on t4
|
|
751
|
+
t5.planned_by p1
|
|
752
|
+
p1.depends_on t6
|
|
753
|
+
|
|
754
|
+
t7.depends_on t8
|
|
755
|
+
|
|
756
|
+
[t1, t2, t5].each { |t| plan.add_mission(t) }
|
|
757
|
+
plan.add_permanent(t7)
|
|
758
|
+
|
|
759
|
+
assert_finalizes(plan, [])
|
|
760
|
+
assert_finalizes(plan, [t1]) { plan.unmark_mission(t1) }
|
|
761
|
+
assert_finalizes(plan, [t2, t3]) do
|
|
762
|
+
t2.start!(nil)
|
|
763
|
+
plan.unmark_mission(t2)
|
|
764
|
+
end
|
|
765
|
+
assert_finalizes(plan, [t5, t4, p1, t6], []) do
|
|
766
|
+
t5.delays = true
|
|
767
|
+
t5.start!(nil)
|
|
768
|
+
plan.unmark_mission(t5)
|
|
769
|
+
end
|
|
770
|
+
assert(t5.event(:stop).pending?)
|
|
771
|
+
assert_finalizes(plan, [t5, t4, p1, t6]) do
|
|
772
|
+
t5.event(:stop).emit(nil)
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
def test_force_garbage_collect_tasks
|
|
777
|
+
t1 = Class.new(Task) do
|
|
778
|
+
event(:stop) { |context| }
|
|
779
|
+
end.new
|
|
780
|
+
t2 = Task.new
|
|
781
|
+
t1.depends_on t2
|
|
782
|
+
|
|
783
|
+
plan.add_mission(t1)
|
|
784
|
+
t1.start!
|
|
785
|
+
assert_finalizes(plan, []) do
|
|
786
|
+
engine.garbage_collect([t1])
|
|
787
|
+
end
|
|
788
|
+
assert(t1.event(:stop).pending?)
|
|
789
|
+
|
|
790
|
+
assert_finalizes(plan, [t1, t2], [t1, t2]) do
|
|
791
|
+
# This stops the mission, which will be automatically discarded
|
|
792
|
+
t1.event(:stop).emit(nil)
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
def test_gc_ignores_incoming_events
|
|
797
|
+
Roby::Plan.logger.level = Logger::WARN
|
|
798
|
+
a, b = prepare_plan :discover => 2, :model => SimpleTask
|
|
799
|
+
a.signals(:stop, b, :start)
|
|
800
|
+
a.start!
|
|
801
|
+
|
|
802
|
+
process_events
|
|
803
|
+
process_events
|
|
804
|
+
assert(!a.plan)
|
|
805
|
+
assert(!b.plan)
|
|
806
|
+
assert(!b.event(:start).happened?)
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
# Test a setup where there is both pending tasks and running tasks. This
|
|
810
|
+
# checks that #stop! is called on all the involved tasks. This tracks
|
|
811
|
+
# problems related to bindings in the implementation of #garbage_collect:
|
|
812
|
+
# the killed task bound to the Roby.once block must remain the same.
|
|
813
|
+
def test_gc_stopping
|
|
814
|
+
Roby::Plan.logger.level = Logger::WARN
|
|
815
|
+
running_task = nil
|
|
816
|
+
FlexMock.use do |mock|
|
|
817
|
+
task_model = Class.new(Task) do
|
|
818
|
+
event :start, :command => true
|
|
819
|
+
event :stop do
|
|
820
|
+
mock.stop(self)
|
|
821
|
+
end
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
running_tasks = (1..5).map do
|
|
825
|
+
task_model.new
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
plan.add(running_tasks)
|
|
829
|
+
t1, t2 = Roby::Task.new, Roby::Task.new
|
|
830
|
+
t1.depends_on t2
|
|
831
|
+
plan.add(t1)
|
|
832
|
+
|
|
833
|
+
running_tasks.each do |t|
|
|
834
|
+
t.start!
|
|
835
|
+
mock.should_receive(:stop).with(t).once
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
engine.garbage_collect
|
|
839
|
+
process_events
|
|
840
|
+
|
|
841
|
+
assert(!plan.include?(t1))
|
|
842
|
+
assert(!plan.include?(t2))
|
|
843
|
+
running_tasks.each do |t|
|
|
844
|
+
assert(t.finishing?)
|
|
845
|
+
t.emit(:stop)
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
engine.garbage_collect
|
|
849
|
+
running_tasks.each do |t|
|
|
850
|
+
assert(!plan.include?(t))
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
ensure
|
|
855
|
+
running_task.emit(:stop) if running_task && !running_task.finished?
|
|
856
|
+
end
|
|
857
|
+
|
|
858
|
+
def test_garbage_collect_events
|
|
859
|
+
t = SimpleTask.new
|
|
860
|
+
e1 = EventGenerator.new(true)
|
|
861
|
+
|
|
862
|
+
plan.add_mission(t)
|
|
863
|
+
plan.add(e1)
|
|
864
|
+
assert_equal([e1], plan.unneeded_events.to_a)
|
|
865
|
+
t.event(:start).signals e1
|
|
866
|
+
assert_equal([], plan.unneeded_events.to_a)
|
|
867
|
+
|
|
868
|
+
e2 = EventGenerator.new(true)
|
|
869
|
+
plan.add(e2)
|
|
870
|
+
assert_equal([e2], plan.unneeded_events.to_a)
|
|
871
|
+
e1.forward_to e2
|
|
872
|
+
assert_equal([], plan.unneeded_events.to_a)
|
|
873
|
+
|
|
874
|
+
plan.remove_object(t)
|
|
875
|
+
assert_equal([e1, e2].to_value_set, plan.unneeded_events)
|
|
876
|
+
|
|
877
|
+
plan.add_permanent(e1)
|
|
878
|
+
assert_equal([], plan.unneeded_events.to_a)
|
|
879
|
+
plan.unmark_permanent(e1)
|
|
880
|
+
assert_equal([e1, e2].to_value_set, plan.unneeded_events)
|
|
881
|
+
plan.add_permanent(e2)
|
|
882
|
+
assert_equal([], plan.unneeded_events.to_a)
|
|
883
|
+
plan.unmark_permanent(e2)
|
|
884
|
+
assert_equal([e1, e2].to_value_set, plan.unneeded_events)
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
def test_garbage_collect_weak_relations
|
|
888
|
+
engine.run
|
|
889
|
+
|
|
890
|
+
engine.execute do
|
|
891
|
+
planning, planned, influencing = prepare_plan :discover => 3, :model => SimpleTask
|
|
892
|
+
|
|
893
|
+
planned.planned_by planning
|
|
894
|
+
influencing.depends_on planned
|
|
895
|
+
planning.influenced_by influencing
|
|
896
|
+
|
|
897
|
+
planned.start!
|
|
898
|
+
planning.start!
|
|
899
|
+
influencing.start!
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
engine.wait_one_cycle
|
|
903
|
+
engine.wait_one_cycle
|
|
904
|
+
engine.wait_one_cycle
|
|
905
|
+
|
|
906
|
+
assert(plan.known_tasks.empty?)
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
def test_mission_failed
|
|
910
|
+
model = Class.new(SimpleTask) do
|
|
911
|
+
event :specialized_failure, :command => true
|
|
912
|
+
forward :specialized_failure => :failed
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
task = prepare_plan :missions => 1, :model => model
|
|
916
|
+
task.start!
|
|
917
|
+
task.specialized_failure!
|
|
918
|
+
|
|
919
|
+
error = Roby::Plan.check_failed_missions(plan).first.exception
|
|
920
|
+
assert_kind_of(Roby::MissionFailedError, error)
|
|
921
|
+
assert_equal(task.event(:specialized_failure).last, error.failure_point)
|
|
922
|
+
assert_nothing_raised do
|
|
923
|
+
Roby.format_exception error
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
# Makes teardown happy
|
|
927
|
+
plan.remove_object(task)
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
def test_check_relations_structure
|
|
931
|
+
r_t = TaskStructure.relation :TestRT
|
|
932
|
+
r_e = EventStructure.relation :TestRE
|
|
933
|
+
|
|
934
|
+
FlexMock.use do |mock|
|
|
935
|
+
r_t.singleton_class.class_eval do
|
|
936
|
+
define_method :check_structure do |plan|
|
|
937
|
+
mock.checked_task_relation(plan)
|
|
938
|
+
[]
|
|
939
|
+
end
|
|
940
|
+
end
|
|
941
|
+
r_e.singleton_class.class_eval do
|
|
942
|
+
define_method :check_structure do |plan|
|
|
943
|
+
mock.checked_event_relation(plan)
|
|
944
|
+
[]
|
|
945
|
+
end
|
|
946
|
+
end
|
|
947
|
+
|
|
948
|
+
plan = Plan.new
|
|
949
|
+
assert plan.relations.include?(r_t)
|
|
950
|
+
assert plan.relations.include?(r_e)
|
|
951
|
+
|
|
952
|
+
mock.should_receive(:checked_task_relation).with(plan).once
|
|
953
|
+
mock.should_receive(:checked_event_relation).with(plan).once
|
|
954
|
+
assert_equal(Hash.new, plan.check_structure)
|
|
955
|
+
end
|
|
956
|
+
ensure
|
|
957
|
+
TaskStructure.remove_relation r_t if r_t
|
|
958
|
+
EventStructure.remove_relation r_e if r_e
|
|
959
|
+
end
|
|
960
|
+
|
|
961
|
+
def test_forward_signal_ordering
|
|
962
|
+
100.times do
|
|
963
|
+
stop_called = false
|
|
964
|
+
source = SimpleTask.new(:id => 'source')
|
|
965
|
+
target = Class.new(SimpleTask) do
|
|
966
|
+
event :start do
|
|
967
|
+
if !stop_called
|
|
968
|
+
raise ArgumentError, "ordering failed"
|
|
969
|
+
end
|
|
970
|
+
emit :start
|
|
971
|
+
end
|
|
972
|
+
end.new(:id => 'target')
|
|
973
|
+
plan.add_permanent(source)
|
|
974
|
+
plan.add_permanent(target)
|
|
975
|
+
|
|
976
|
+
source.signals :success, target, :start
|
|
977
|
+
source.on :stop do
|
|
978
|
+
stop_called = true
|
|
979
|
+
end
|
|
980
|
+
source.start!
|
|
981
|
+
source.emit :success
|
|
982
|
+
assert(target.running?)
|
|
983
|
+
target.stop!
|
|
984
|
+
end
|
|
985
|
+
end
|
|
986
|
+
end
|
|
987
|
+
|