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.
Files changed (236) hide show
  1. data/History.txt +7 -5
  2. data/Manifest.txt +91 -16
  3. data/README.txt +24 -24
  4. data/Rakefile +92 -64
  5. data/app/config/app.yml +42 -43
  6. data/app/config/init.rb +26 -0
  7. data/benchmark/alloc_misc.rb +123 -0
  8. data/benchmark/discovery_latency.rb +67 -0
  9. data/benchmark/garbage_collection.rb +48 -0
  10. data/benchmark/genom.rb +31 -0
  11. data/benchmark/transactions.rb +62 -0
  12. data/bin/roby +1 -1
  13. data/bin/roby-log +16 -6
  14. data/doc/guide/.gitignore +2 -0
  15. data/doc/guide/config.yaml +34 -0
  16. data/doc/guide/ext/init.rb +14 -0
  17. data/doc/guide/ext/previous_next.rb +40 -0
  18. data/doc/guide/ext/rdoc_links.rb +33 -0
  19. data/doc/guide/index.rdoc +16 -0
  20. data/doc/guide/overview.rdoc +62 -0
  21. data/doc/guide/plan_modifications.rdoc +67 -0
  22. data/doc/guide/src/abstraction/achieve_with.page +8 -0
  23. data/doc/guide/src/abstraction/forwarding.page +8 -0
  24. data/doc/guide/src/abstraction/hierarchy.page +19 -0
  25. data/doc/guide/src/abstraction/index.page +28 -0
  26. data/doc/guide/src/abstraction/task_models.page +13 -0
  27. data/doc/guide/src/basics.template +6 -0
  28. data/doc/guide/src/basics/app.page +139 -0
  29. data/doc/guide/src/basics/code_examples.page +33 -0
  30. data/doc/guide/src/basics/dry.page +69 -0
  31. data/doc/guide/src/basics/errors.page +443 -0
  32. data/doc/guide/src/basics/events.page +179 -0
  33. data/doc/guide/src/basics/hierarchy.page +275 -0
  34. data/doc/guide/src/basics/index.page +11 -0
  35. data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
  36. data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
  37. data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
  38. data/doc/guide/src/basics/log_replay/goForward_4.png +0 -0
  39. data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
  40. data/doc/guide/src/basics/log_replay/hierarchy_error_1.png +0 -0
  41. data/doc/guide/src/basics/log_replay/hierarchy_error_2.png +0 -0
  42. data/doc/guide/src/basics/log_replay/hierarchy_error_3.png +0 -0
  43. data/doc/guide/src/basics/log_replay/plan_repair_1.png +0 -0
  44. data/doc/guide/src/basics/log_replay/plan_repair_2.png +0 -0
  45. data/doc/guide/src/basics/log_replay/plan_repair_3.png +0 -0
  46. data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
  47. data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
  48. data/doc/guide/src/basics/log_replay/roby_log_relation_window.png +0 -0
  49. data/doc/guide/src/basics/log_replay/roby_replay_event_representation.png +0 -0
  50. data/doc/guide/src/basics/plan_objects.page +71 -0
  51. data/doc/guide/src/basics/relations_display.page +203 -0
  52. data/doc/guide/src/basics/roby_cycle_overview.png +0 -0
  53. data/doc/guide/src/basics/shell.page +102 -0
  54. data/doc/guide/src/basics/summary.page +32 -0
  55. data/doc/guide/src/basics/tasks.page +357 -0
  56. data/doc/guide/src/basics_shell_header.txt +16 -0
  57. data/doc/guide/src/cycle/cycle-overview.png +0 -0
  58. data/doc/guide/src/cycle/cycle-overview.svg +208 -0
  59. data/doc/guide/src/cycle/error_handling.page +168 -0
  60. data/doc/guide/src/cycle/error_instantaneous_repair.png +0 -0
  61. data/doc/guide/src/cycle/error_instantaneous_repair.svg +1224 -0
  62. data/doc/guide/src/cycle/garbage_collection.page +10 -0
  63. data/doc/guide/src/cycle/index.page +23 -0
  64. data/doc/guide/src/cycle/propagation.page +154 -0
  65. data/doc/guide/src/cycle/propagation_diamond.png +0 -0
  66. data/doc/guide/src/cycle/propagation_diamond.svg +1279 -0
  67. data/doc/guide/src/default.css +319 -0
  68. data/doc/guide/src/default.template +74 -0
  69. data/doc/guide/src/htmldoc.metainfo +20 -0
  70. data/doc/guide/src/htmldoc.virtual +18 -0
  71. data/doc/guide/src/images/bodybg.png +0 -0
  72. data/doc/guide/src/images/contbg.png +0 -0
  73. data/doc/guide/src/images/footerbg.png +0 -0
  74. data/doc/guide/src/images/gradient1.png +0 -0
  75. data/doc/guide/src/images/gradient2.png +0 -0
  76. data/doc/guide/src/index.page +7 -0
  77. data/doc/guide/src/introduction/index.page +29 -0
  78. data/doc/guide/src/introduction/install.page +133 -0
  79. data/doc/{papers.rdoc → guide/src/introduction/publications.page} +5 -2
  80. data/doc/{videos.rdoc → guide/src/introduction/videos.page} +4 -2
  81. data/doc/guide/src/plugins/fault_tolerance.page +44 -0
  82. data/doc/guide/src/plugins/index.page +11 -0
  83. data/doc/guide/src/plugins/subsystems.page +45 -0
  84. data/doc/guide/src/relations/dependency.page +89 -0
  85. data/doc/guide/src/relations/index.page +12 -0
  86. data/doc/misc/update_github +24 -0
  87. data/doc/tutorials/02-GoForward.rdoc +3 -3
  88. data/ext/graph/graph.cc +46 -0
  89. data/lib/roby.rb +57 -22
  90. data/lib/roby/app.rb +132 -112
  91. data/lib/roby/app/plugins/rake.rb +21 -0
  92. data/lib/roby/app/rake.rb +0 -7
  93. data/lib/roby/app/run.rb +1 -1
  94. data/lib/roby/app/scripts/distributed.rb +1 -2
  95. data/lib/roby/app/scripts/generate/bookmarks.rb +1 -1
  96. data/lib/roby/app/scripts/results.rb +2 -1
  97. data/lib/roby/app/scripts/run.rb +6 -2
  98. data/lib/roby/app/scripts/shell.rb +11 -11
  99. data/lib/roby/config.rb +1 -1
  100. data/lib/roby/decision_control.rb +62 -3
  101. data/lib/roby/distributed.rb +4 -0
  102. data/lib/roby/distributed/base.rb +8 -0
  103. data/lib/roby/distributed/communication.rb +12 -8
  104. data/lib/roby/distributed/connection_space.rb +61 -44
  105. data/lib/roby/distributed/distributed_object.rb +1 -1
  106. data/lib/roby/distributed/notifications.rb +22 -30
  107. data/lib/roby/distributed/peer.rb +13 -8
  108. data/lib/roby/distributed/proxy.rb +5 -5
  109. data/lib/roby/distributed/subscription.rb +4 -4
  110. data/lib/roby/distributed/transaction.rb +3 -3
  111. data/lib/roby/event.rb +176 -110
  112. data/lib/roby/exceptions.rb +12 -4
  113. data/lib/roby/execution_engine.rb +1604 -0
  114. data/lib/roby/external_process_task.rb +225 -0
  115. data/lib/roby/graph.rb +0 -6
  116. data/lib/roby/interface.rb +221 -137
  117. data/lib/roby/log/console.rb +5 -3
  118. data/lib/roby/log/data_stream.rb +94 -16
  119. data/lib/roby/log/dot.rb +8 -8
  120. data/lib/roby/log/event_stream.rb +13 -3
  121. data/lib/roby/log/file.rb +43 -18
  122. data/lib/roby/log/gui/basic_display_ui.rb +89 -0
  123. data/lib/roby/log/gui/chronicle_view_ui.rb +90 -0
  124. data/lib/roby/log/gui/data_displays.rb +4 -5
  125. data/lib/roby/log/gui/data_displays_ui.rb +146 -0
  126. data/lib/roby/log/gui/relations.rb +18 -18
  127. data/lib/roby/log/gui/relations_ui.rb +120 -0
  128. data/lib/roby/log/gui/relations_view_ui.rb +144 -0
  129. data/lib/roby/log/gui/replay.rb +41 -13
  130. data/lib/roby/log/gui/replay_controls.rb +3 -0
  131. data/lib/roby/log/gui/replay_controls.ui +133 -110
  132. data/lib/roby/log/gui/replay_controls_ui.rb +249 -0
  133. data/lib/roby/log/hooks.rb +19 -18
  134. data/lib/roby/log/logger.rb +7 -6
  135. data/lib/roby/log/notifications.rb +4 -4
  136. data/lib/roby/log/plan_rebuilder.rb +20 -22
  137. data/lib/roby/log/relations.rb +44 -16
  138. data/lib/roby/log/server.rb +1 -4
  139. data/lib/roby/log/timings.rb +88 -19
  140. data/lib/roby/plan-object.rb +135 -11
  141. data/lib/roby/plan.rb +408 -224
  142. data/lib/roby/planning/loops.rb +32 -25
  143. data/lib/roby/planning/model.rb +157 -51
  144. data/lib/roby/planning/task.rb +47 -20
  145. data/lib/roby/query.rb +128 -92
  146. data/lib/roby/relations.rb +254 -136
  147. data/lib/roby/relations/conflicts.rb +6 -9
  148. data/lib/roby/relations/dependency.rb +358 -0
  149. data/lib/roby/relations/ensured.rb +0 -1
  150. data/lib/roby/relations/error_handling.rb +0 -1
  151. data/lib/roby/relations/events.rb +0 -2
  152. data/lib/roby/relations/executed_by.rb +26 -11
  153. data/lib/roby/relations/planned_by.rb +14 -14
  154. data/lib/roby/robot.rb +46 -0
  155. data/lib/roby/schedulers/basic.rb +34 -0
  156. data/lib/roby/standalone.rb +4 -0
  157. data/lib/roby/standard_errors.rb +21 -15
  158. data/lib/roby/state/events.rb +5 -4
  159. data/lib/roby/support.rb +107 -6
  160. data/lib/roby/task-operations.rb +23 -19
  161. data/lib/roby/task.rb +522 -148
  162. data/lib/roby/task_index.rb +80 -0
  163. data/lib/roby/test/common.rb +283 -44
  164. data/lib/roby/test/distributed.rb +53 -37
  165. data/lib/roby/test/testcase.rb +9 -204
  166. data/lib/roby/test/tools.rb +3 -3
  167. data/lib/roby/transactions.rb +154 -111
  168. data/lib/roby/transactions/proxy.rb +40 -7
  169. data/manifest.xml +20 -0
  170. data/plugins/fault_injection/README.txt +0 -3
  171. data/plugins/fault_injection/Rakefile +2 -8
  172. data/plugins/fault_injection/app.rb +1 -1
  173. data/plugins/fault_injection/fault_injection.rb +3 -3
  174. data/plugins/fault_injection/test/test_fault_injection.rb +19 -25
  175. data/plugins/subsystems/README.txt +0 -3
  176. data/plugins/subsystems/Rakefile +2 -7
  177. data/plugins/subsystems/app.rb +27 -16
  178. data/plugins/subsystems/test/app/config/init.rb +3 -0
  179. data/plugins/subsystems/test/app/planners/main.rb +1 -1
  180. data/plugins/subsystems/test/app/tasks/services.rb +1 -1
  181. data/plugins/subsystems/test/test_subsystems.rb +23 -16
  182. data/test/distributed/test_communication.rb +32 -15
  183. data/test/distributed/test_connection.rb +28 -26
  184. data/test/distributed/test_execution.rb +59 -54
  185. data/test/distributed/test_mixed_plan.rb +34 -34
  186. data/test/distributed/test_plan_notifications.rb +26 -26
  187. data/test/distributed/test_protocol.rb +57 -48
  188. data/test/distributed/test_query.rb +11 -7
  189. data/test/distributed/test_remote_plan.rb +71 -71
  190. data/test/distributed/test_transaction.rb +50 -47
  191. data/test/mockups/external_process +28 -0
  192. data/test/planning/test_loops.rb +163 -119
  193. data/test/planning/test_model.rb +3 -3
  194. data/test/planning/test_task.rb +27 -7
  195. data/test/relations/test_conflicts.rb +3 -3
  196. data/test/relations/test_dependency.rb +324 -0
  197. data/test/relations/test_ensured.rb +2 -2
  198. data/test/relations/test_executed_by.rb +94 -19
  199. data/test/relations/test_planned_by.rb +11 -9
  200. data/test/suite_core.rb +6 -3
  201. data/test/suite_distributed.rb +1 -0
  202. data/test/suite_planning.rb +1 -0
  203. data/test/suite_relations.rb +2 -2
  204. data/test/tasks/test_external_process.rb +126 -0
  205. data/test/{test_thread_task.rb → tasks/test_thread_task.rb} +17 -20
  206. data/test/test_bgl.rb +21 -1
  207. data/test/test_event.rb +229 -155
  208. data/test/test_exceptions.rb +79 -80
  209. data/test/test_execution_engine.rb +987 -0
  210. data/test/test_gui.rb +1 -1
  211. data/test/test_interface.rb +11 -5
  212. data/test/test_log.rb +18 -7
  213. data/test/test_log_server.rb +1 -0
  214. data/test/test_plan.rb +229 -395
  215. data/test/test_query.rb +193 -35
  216. data/test/test_relations.rb +88 -8
  217. data/test/test_state.rb +55 -37
  218. data/test/test_support.rb +1 -1
  219. data/test/test_task.rb +371 -218
  220. data/test/test_testcase.rb +32 -16
  221. data/test/test_transactions.rb +211 -170
  222. data/test/test_transactions_proxy.rb +37 -19
  223. metadata +169 -71
  224. data/.gitignore +0 -29
  225. data/doc/styles/allison.css +0 -314
  226. data/doc/styles/allison.js +0 -316
  227. data/doc/styles/allison.rb +0 -276
  228. data/doc/styles/jamis.rb +0 -593
  229. data/lib/roby/control.rb +0 -746
  230. data/lib/roby/executives/simple.rb +0 -30
  231. data/lib/roby/propagation.rb +0 -562
  232. data/lib/roby/relations/hierarchy.rb +0 -239
  233. data/lib/roby/transactions/updates.rb +0 -139
  234. data/test/relations/test_hierarchy.rb +0 -158
  235. data/test/test_control.rb +0 -399
  236. data/test/test_propagation.rb +0 -210
@@ -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.discover(task = Task.new)
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 :discover => 5
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 :discover => 2
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.discover(task = klass.new)
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.control.abort_on_exception = true
108
- Roby.control.abort_on_application_exception = false
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
- Roby.on_exception(RuntimeError) do |task, exception|
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.realized_by t2
130
- plan.insert(t1)
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
- Control.once { t2.start! }
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.discover(t0)
149
- t0.realized_by t1
150
- t1.realized_by t2
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([], Propagation.propagate_exceptions([error]))
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], Propagation.propagate_exceptions([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
- Roby.on_exception(CodeError) do |mod, exception|
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, Roby).once
169
- assert_equal([], Propagation.propagate_exceptions([error]))
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 :discover => 3
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.discover(t0)
187
- t0.realized_by t1
188
- t1.realized_by t2
189
- t3.realized_by t2
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([], Propagation.propagate_exceptions([error]))
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 = Propagation.propagate_exceptions([error]))
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 :discover => 3
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.discover(t0)
230
- t0.realized_by t1 ; t1.realized_by t2
231
- t0.realized_by t3 ; t3.realized_by t2
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([], Propagation.propagate_exceptions([error]))
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.discover(ev)
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.discover(ev)
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.discover(ev)
266
+ plan.add(ev)
267
267
  ev2 = EventGenerator.new(true)
268
- ev.on ev2
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.discover(ev)
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.control.abort_on_exception = true
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.realized_by task
307
- plan.insert(parent)
306
+ parent.depends_on task
307
+ plan.add_mission(parent)
308
308
 
309
- Roby::Control.once { task.start! }
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
- Roby::Control.once { mock.other_once_handler }
314
- Roby::Control.event_processing << lambda { mock.other_event_processing }
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
- Roby.on_exception(RuntimeError) do ||
341
+ plan.on_exception(RuntimeError) do ||
342
342
  end
343
343
  end
344
344
  assert_raises(ArgumentError) do |a, b|
345
- Roby.on_exception(RuntimeError) do |_|
345
+ plan.on_exception(RuntimeError) do |_|
346
346
  end
347
347
  end
348
348
  assert_nothing_raised do
349
- Roby.on_exception(RuntimeError) do |_, _|
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.discover(root)
368
- root.realized_by(t11)
369
- root.realized_by(t12)
370
- root.realized_by(t13)
367
+ plan.add(root)
368
+ root.depends_on(t11)
369
+ root.depends_on(t12)
370
+ root.depends_on(t13)
371
371
 
372
- t11.realized_by(t21 = Task.new(:id => '21'))
373
- t12.realized_by(t21)
372
+ t11.depends_on(t21 = Task.new(:id => '21'))
373
+ t12.depends_on(t21)
374
374
 
375
- t13.realized_by(t22 = Task.new(:id => '22'))
376
- t22.realized_by(t31 = Task.new(:id => '31'))
377
- t31.realized_by(t21)
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
- Propagation.propagate_exceptions([ExecutionException.new(LocalizedError.new(t21))])
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.discover(task = model.new)
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.insert(parent)
420
- parent.realized_by child
421
- parent.on :start, child, :start
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 = Roby.control.structure_checking
425
+ exceptions = plan.check_structure
426
426
 
427
- plan.discover(repairing_task = SimpleTask.new)
427
+ plan.add(repairing_task = SimpleTask.new)
428
428
  repairing_task.start!
429
- assert_equal(exceptions.to_a, Propagation.remove_inhibited_exceptions(exceptions))
430
- assert_equal(exceptions.keys, Propagation.propagate_exceptions(exceptions))
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([], Propagation.remove_inhibited_exceptions(exceptions))
433
- assert_equal([], Propagation.propagate_exceptions(exceptions))
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.insert(parent)
448
- parent.realized_by child
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 = Roby.control.structure_checking
456
+ exceptions = plan.check_structure
457
457
 
458
- assert_equal([], Propagation.propagate_exceptions(exceptions))
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.control.abort_on_exception = false
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, Propagation.propagate_exceptions(exceptions))
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 test_handling_missions_exceptions
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 = Roby.control.structure_checking
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([], Propagation.propagate_exceptions(exceptions))
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.control.abort_on_exception = false
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, Propagation.propagate_exceptions(exceptions))
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.discard(mission)
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.realized_by child
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
+