roby 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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
+