roby 0.7

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 (240) hide show
  1. data/.gitignore +29 -0
  2. data/History.txt +4 -0
  3. data/License-fr.txt +519 -0
  4. data/License.txt +515 -0
  5. data/Manifest.txt +245 -0
  6. data/NOTES +4 -0
  7. data/README.txt +163 -0
  8. data/Rakefile +161 -0
  9. data/TODO.txt +146 -0
  10. data/app/README.txt +24 -0
  11. data/app/Rakefile +8 -0
  12. data/app/config/ROBOT.rb +5 -0
  13. data/app/config/app.yml +91 -0
  14. data/app/config/init.rb +7 -0
  15. data/app/config/roby.yml +3 -0
  16. data/app/controllers/.gitattributes +0 -0
  17. data/app/controllers/ROBOT.rb +2 -0
  18. data/app/data/.gitattributes +0 -0
  19. data/app/planners/ROBOT/main.rb +6 -0
  20. data/app/planners/main.rb +5 -0
  21. data/app/scripts/distributed +3 -0
  22. data/app/scripts/generate/bookmarks +3 -0
  23. data/app/scripts/replay +3 -0
  24. data/app/scripts/results +3 -0
  25. data/app/scripts/run +3 -0
  26. data/app/scripts/server +3 -0
  27. data/app/scripts/shell +3 -0
  28. data/app/scripts/test +3 -0
  29. data/app/tasks/.gitattributes +0 -0
  30. data/app/tasks/ROBOT/.gitattributes +0 -0
  31. data/bin/roby +210 -0
  32. data/bin/roby-log +168 -0
  33. data/bin/roby-shell +25 -0
  34. data/doc/images/event_generalization.png +0 -0
  35. data/doc/images/exception_propagation_1.png +0 -0
  36. data/doc/images/exception_propagation_2.png +0 -0
  37. data/doc/images/exception_propagation_3.png +0 -0
  38. data/doc/images/exception_propagation_4.png +0 -0
  39. data/doc/images/exception_propagation_5.png +0 -0
  40. data/doc/images/replay_handler_error.png +0 -0
  41. data/doc/images/replay_handler_error_0.png +0 -0
  42. data/doc/images/replay_handler_error_1.png +0 -0
  43. data/doc/images/roby_cycle_overview.png +0 -0
  44. data/doc/images/roby_replay_02.png +0 -0
  45. data/doc/images/roby_replay_03.png +0 -0
  46. data/doc/images/roby_replay_04.png +0 -0
  47. data/doc/images/roby_replay_event_representation.png +0 -0
  48. data/doc/images/roby_replay_first_state.png +0 -0
  49. data/doc/images/roby_replay_relations.png +0 -0
  50. data/doc/images/roby_replay_startup.png +0 -0
  51. data/doc/images/task_event_generalization.png +0 -0
  52. data/doc/papers.rdoc +11 -0
  53. data/doc/styles/allison.css +314 -0
  54. data/doc/styles/allison.js +316 -0
  55. data/doc/styles/allison.rb +276 -0
  56. data/doc/styles/jamis.rb +593 -0
  57. data/doc/tutorials/01-GettingStarted.rdoc +86 -0
  58. data/doc/tutorials/02-GoForward.rdoc +220 -0
  59. data/doc/tutorials/03-PlannedPath.rdoc +268 -0
  60. data/doc/tutorials/04-EventPropagation.rdoc +236 -0
  61. data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
  62. data/doc/tutorials/06-Overview.rdoc +40 -0
  63. data/doc/videos.rdoc +69 -0
  64. data/ext/droby/dump.cc +175 -0
  65. data/ext/droby/extconf.rb +3 -0
  66. data/ext/graph/algorithm.cc +746 -0
  67. data/ext/graph/extconf.rb +7 -0
  68. data/ext/graph/graph.cc +529 -0
  69. data/ext/graph/graph.hh +183 -0
  70. data/ext/graph/iterator_sequence.hh +102 -0
  71. data/ext/graph/undirected_dfs.hh +226 -0
  72. data/ext/graph/undirected_graph.hh +421 -0
  73. data/lib/roby.rb +41 -0
  74. data/lib/roby/app.rb +870 -0
  75. data/lib/roby/app/rake.rb +56 -0
  76. data/lib/roby/app/run.rb +14 -0
  77. data/lib/roby/app/scripts/distributed.rb +13 -0
  78. data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
  79. data/lib/roby/app/scripts/replay.rb +31 -0
  80. data/lib/roby/app/scripts/results.rb +15 -0
  81. data/lib/roby/app/scripts/run.rb +26 -0
  82. data/lib/roby/app/scripts/server.rb +18 -0
  83. data/lib/roby/app/scripts/shell.rb +88 -0
  84. data/lib/roby/app/scripts/test.rb +40 -0
  85. data/lib/roby/basic_object.rb +151 -0
  86. data/lib/roby/config.rb +5 -0
  87. data/lib/roby/control.rb +747 -0
  88. data/lib/roby/decision_control.rb +17 -0
  89. data/lib/roby/distributed.rb +32 -0
  90. data/lib/roby/distributed/base.rb +440 -0
  91. data/lib/roby/distributed/communication.rb +871 -0
  92. data/lib/roby/distributed/connection_space.rb +592 -0
  93. data/lib/roby/distributed/distributed_object.rb +206 -0
  94. data/lib/roby/distributed/drb.rb +62 -0
  95. data/lib/roby/distributed/notifications.rb +539 -0
  96. data/lib/roby/distributed/peer.rb +550 -0
  97. data/lib/roby/distributed/protocol.rb +529 -0
  98. data/lib/roby/distributed/proxy.rb +343 -0
  99. data/lib/roby/distributed/subscription.rb +311 -0
  100. data/lib/roby/distributed/transaction.rb +498 -0
  101. data/lib/roby/event.rb +897 -0
  102. data/lib/roby/exceptions.rb +234 -0
  103. data/lib/roby/executives/simple.rb +30 -0
  104. data/lib/roby/graph.rb +166 -0
  105. data/lib/roby/interface.rb +390 -0
  106. data/lib/roby/log.rb +3 -0
  107. data/lib/roby/log/chronicle.rb +303 -0
  108. data/lib/roby/log/console.rb +72 -0
  109. data/lib/roby/log/data_stream.rb +197 -0
  110. data/lib/roby/log/dot.rb +279 -0
  111. data/lib/roby/log/event_stream.rb +151 -0
  112. data/lib/roby/log/file.rb +340 -0
  113. data/lib/roby/log/gui/basic_display.ui +83 -0
  114. data/lib/roby/log/gui/chronicle.rb +26 -0
  115. data/lib/roby/log/gui/chronicle_view.rb +40 -0
  116. data/lib/roby/log/gui/chronicle_view.ui +70 -0
  117. data/lib/roby/log/gui/data_displays.rb +172 -0
  118. data/lib/roby/log/gui/data_displays.ui +155 -0
  119. data/lib/roby/log/gui/notifications.rb +26 -0
  120. data/lib/roby/log/gui/relations.rb +248 -0
  121. data/lib/roby/log/gui/relations.ui +123 -0
  122. data/lib/roby/log/gui/relations_view.rb +185 -0
  123. data/lib/roby/log/gui/relations_view.ui +149 -0
  124. data/lib/roby/log/gui/replay.rb +327 -0
  125. data/lib/roby/log/gui/replay_controls.rb +200 -0
  126. data/lib/roby/log/gui/replay_controls.ui +259 -0
  127. data/lib/roby/log/gui/runtime.rb +130 -0
  128. data/lib/roby/log/hooks.rb +185 -0
  129. data/lib/roby/log/logger.rb +202 -0
  130. data/lib/roby/log/notifications.rb +244 -0
  131. data/lib/roby/log/plan_rebuilder.rb +470 -0
  132. data/lib/roby/log/relations.rb +1056 -0
  133. data/lib/roby/log/server.rb +550 -0
  134. data/lib/roby/log/sqlite.rb +47 -0
  135. data/lib/roby/log/timings.rb +164 -0
  136. data/lib/roby/plan-object.rb +247 -0
  137. data/lib/roby/plan.rb +762 -0
  138. data/lib/roby/planning.rb +13 -0
  139. data/lib/roby/planning/loops.rb +302 -0
  140. data/lib/roby/planning/model.rb +906 -0
  141. data/lib/roby/planning/task.rb +151 -0
  142. data/lib/roby/propagation.rb +562 -0
  143. data/lib/roby/query.rb +619 -0
  144. data/lib/roby/relations.rb +583 -0
  145. data/lib/roby/relations/conflicts.rb +70 -0
  146. data/lib/roby/relations/ensured.rb +20 -0
  147. data/lib/roby/relations/error_handling.rb +23 -0
  148. data/lib/roby/relations/events.rb +9 -0
  149. data/lib/roby/relations/executed_by.rb +193 -0
  150. data/lib/roby/relations/hierarchy.rb +239 -0
  151. data/lib/roby/relations/influence.rb +10 -0
  152. data/lib/roby/relations/planned_by.rb +63 -0
  153. data/lib/roby/robot.rb +7 -0
  154. data/lib/roby/standard_errors.rb +218 -0
  155. data/lib/roby/state.rb +5 -0
  156. data/lib/roby/state/events.rb +221 -0
  157. data/lib/roby/state/information.rb +55 -0
  158. data/lib/roby/state/pos.rb +110 -0
  159. data/lib/roby/state/shapes.rb +32 -0
  160. data/lib/roby/state/state.rb +353 -0
  161. data/lib/roby/support.rb +92 -0
  162. data/lib/roby/task-operations.rb +182 -0
  163. data/lib/roby/task.rb +1618 -0
  164. data/lib/roby/test/common.rb +399 -0
  165. data/lib/roby/test/distributed.rb +214 -0
  166. data/lib/roby/test/tasks/empty_task.rb +9 -0
  167. data/lib/roby/test/tasks/goto.rb +36 -0
  168. data/lib/roby/test/tasks/simple_task.rb +23 -0
  169. data/lib/roby/test/testcase.rb +519 -0
  170. data/lib/roby/test/tools.rb +160 -0
  171. data/lib/roby/thread_task.rb +87 -0
  172. data/lib/roby/transactions.rb +462 -0
  173. data/lib/roby/transactions/proxy.rb +292 -0
  174. data/lib/roby/transactions/updates.rb +139 -0
  175. data/plugins/fault_injection/History.txt +4 -0
  176. data/plugins/fault_injection/README.txt +37 -0
  177. data/plugins/fault_injection/Rakefile +18 -0
  178. data/plugins/fault_injection/TODO.txt +0 -0
  179. data/plugins/fault_injection/app.rb +52 -0
  180. data/plugins/fault_injection/fault_injection.rb +89 -0
  181. data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
  182. data/plugins/subsystems/README.txt +40 -0
  183. data/plugins/subsystems/Rakefile +18 -0
  184. data/plugins/subsystems/app.rb +171 -0
  185. data/plugins/subsystems/test/app/README +24 -0
  186. data/plugins/subsystems/test/app/Rakefile +8 -0
  187. data/plugins/subsystems/test/app/config/app.yml +71 -0
  188. data/plugins/subsystems/test/app/config/init.rb +9 -0
  189. data/plugins/subsystems/test/app/config/roby.yml +3 -0
  190. data/plugins/subsystems/test/app/planners/main.rb +20 -0
  191. data/plugins/subsystems/test/app/scripts/distributed +3 -0
  192. data/plugins/subsystems/test/app/scripts/replay +3 -0
  193. data/plugins/subsystems/test/app/scripts/results +3 -0
  194. data/plugins/subsystems/test/app/scripts/run +3 -0
  195. data/plugins/subsystems/test/app/scripts/server +3 -0
  196. data/plugins/subsystems/test/app/scripts/shell +3 -0
  197. data/plugins/subsystems/test/app/scripts/test +3 -0
  198. data/plugins/subsystems/test/app/tasks/services.rb +15 -0
  199. data/plugins/subsystems/test/test_subsystems.rb +71 -0
  200. data/test/distributed/test_communication.rb +178 -0
  201. data/test/distributed/test_connection.rb +282 -0
  202. data/test/distributed/test_execution.rb +373 -0
  203. data/test/distributed/test_mixed_plan.rb +341 -0
  204. data/test/distributed/test_plan_notifications.rb +238 -0
  205. data/test/distributed/test_protocol.rb +516 -0
  206. data/test/distributed/test_query.rb +102 -0
  207. data/test/distributed/test_remote_plan.rb +491 -0
  208. data/test/distributed/test_transaction.rb +463 -0
  209. data/test/mockups/tasks.rb +27 -0
  210. data/test/planning/test_loops.rb +380 -0
  211. data/test/planning/test_model.rb +427 -0
  212. data/test/planning/test_task.rb +106 -0
  213. data/test/relations/test_conflicts.rb +42 -0
  214. data/test/relations/test_ensured.rb +38 -0
  215. data/test/relations/test_executed_by.rb +149 -0
  216. data/test/relations/test_hierarchy.rb +158 -0
  217. data/test/relations/test_planned_by.rb +54 -0
  218. data/test/suite_core.rb +24 -0
  219. data/test/suite_distributed.rb +9 -0
  220. data/test/suite_planning.rb +3 -0
  221. data/test/suite_relations.rb +8 -0
  222. data/test/test_bgl.rb +508 -0
  223. data/test/test_control.rb +399 -0
  224. data/test/test_event.rb +894 -0
  225. data/test/test_exceptions.rb +592 -0
  226. data/test/test_interface.rb +37 -0
  227. data/test/test_log.rb +114 -0
  228. data/test/test_log_server.rb +132 -0
  229. data/test/test_plan.rb +584 -0
  230. data/test/test_propagation.rb +210 -0
  231. data/test/test_query.rb +266 -0
  232. data/test/test_relations.rb +180 -0
  233. data/test/test_state.rb +414 -0
  234. data/test/test_support.rb +16 -0
  235. data/test/test_task.rb +938 -0
  236. data/test/test_testcase.rb +122 -0
  237. data/test/test_thread_task.rb +73 -0
  238. data/test/test_transactions.rb +569 -0
  239. data/test/test_transactions_proxy.rb +198 -0
  240. metadata +570 -0
@@ -0,0 +1,341 @@
1
+ $LOAD_PATH.unshift File.expand_path('../..', File.dirname(__FILE__))
2
+ require 'roby/test/distributed'
3
+ require 'roby/test/tasks/simple_task'
4
+
5
+ # This testcase tests buildings plans where local tasks are interacting with remote tasks
6
+ #
7
+ # Naming scheme:
8
+ # test_r<type of remote object>_realizes_l<type of local object>(_dynamic)
9
+ #
10
+ # For instance, test_rproxy_realizes_lproxy means that we are building a
11
+ # transaction where a remote transaction proxy is linked to a local transaction
12
+ # proxy. If _dynamic is appended, #propose is called when the transaction is still
13
+ # empty. Otherwise, #propose is called after all modifications have been put into
14
+ # the transaction (propose then build or build then propose)
15
+ #
16
+ # The transaction is always built locally
17
+ class TC_DistributedMixedPlan < Test::Unit::TestCase
18
+ include Roby::Distributed::Test
19
+
20
+ # Creates in +plan+ a task which is a child in a realized_by relation and a parent
21
+ # in a planned_by relation. All tasks have an ID of "#{name}-#{number}", with
22
+ # 2 for the central task, 1 for its parent task and 3 for its planning task.
23
+ #
24
+ # Returns [-1, -2, -3]
25
+ def add_tasks(plan, name)
26
+ t1, t2, t3 = (1..3).map { |i| SimpleTask.new(:id => "#{name}-#{i}") }
27
+ t1.realized_by t2
28
+ t2.planned_by t3
29
+ plan.insert(t1)
30
+ plan.discover(t2)
31
+ plan.discover(t3)
32
+
33
+ [t1, t2, t3]
34
+ end
35
+
36
+ def check_local_center_structure(node, removed_planner)
37
+ if node.respond_to?(:__getobj__)
38
+ assert_equal(["remote-2"], node.parents.map { |obj| obj.arguments[:id] })
39
+ else
40
+ assert_equal(["local-1", "remote-2"].to_set, node.parents.map { |obj| obj.arguments[:id] }.to_set)
41
+ unless removed_planner
42
+ assert_equal(["local-3"], node.enum_for(:each_planning_task).map { |obj| obj.arguments[:id] })
43
+ end
44
+ end
45
+ end
46
+
47
+ def check_remote_center_structure(node, removed_planner)
48
+ assert_equal(["local-2"], node.children.map { |obj| obj.arguments[:id] })
49
+ unless node.respond_to?(:__getobj__)
50
+ assert_equal(["remote-1"], node.parents.map { |obj| obj.arguments[:id] })
51
+ unless removed_planner
52
+ assert_equal(["remote-3"], node.enum_for(:each_planning_task).map { |obj| obj.arguments[:id] })
53
+ end
54
+ end
55
+ end
56
+
57
+ # Checks that +plan+ has all 6 tasks with remote-2 and local-2 linked as
58
+ # expected +plan+ may be either the transaction or the plan
59
+ #
60
+ # Tests are in two parts: first we build the transaction and check the
61
+ # relations of the resulting proxies. Then, we remove all relations of
62
+ # tasks *in the plan*. Since we have added a realized_by between the
63
+ # central tasks, the realized_by relations are kept inside the transaction.
64
+ # However, this is not the case for planning relations. Thus, the planning
65
+ # relation does not exist anymore in the transaction after they have been
66
+ # removed from the plan.
67
+ def check_resulting_plan(plan, removed_planner)
68
+ assert(remote_center_node = plan.known_tasks.find { |t| t.arguments[:id] == "remote-2" }, plan.known_tasks)
69
+ check_remote_center_structure(remote_center_node, removed_planner)
70
+ assert(local_center_node = plan.known_tasks.find { |t| t.arguments[:id] == "local-2" }, plan.known_tasks)
71
+ check_local_center_structure(local_center_node, removed_planner)
72
+ end
73
+
74
+ def assert_cleared_relations(plan)
75
+ if remote_center_node = plan.known_tasks.find { |t| t.arguments[:id] == "remote-2" }
76
+ assert_equal([], remote_center_node.enum_for(:each_planning_task).to_a)
77
+ end
78
+
79
+ if local_center_node = plan.known_tasks.find { |t| t.arguments[:id] == "local-2" }
80
+ assert_equal([], local_center_node.enum_for(:each_planning_task).to_a)
81
+ end
82
+ end
83
+
84
+ # Common setup of the remote peer
85
+ def common_setup(propose_first)
86
+ peer2peer(true) do |remote|
87
+ testcase = self
88
+ remote.singleton_class.class_eval do
89
+ define_method(:add_tasks) do |plan|
90
+ plan = local_peer.proxy(plan)
91
+ plan.edit do
92
+ testcase.add_tasks(plan, "remote")
93
+ end
94
+ end
95
+ define_method(:check_resulting_plan) do |plan, removed_planner|
96
+ plan = local_peer.proxy(plan)
97
+ plan.edit do
98
+ testcase.check_resulting_plan(local_peer.proxy(plan), removed_planner)
99
+ end
100
+ end
101
+ define_method(:assert_cleared_relations) do |plan|
102
+ plan = local_peer.proxy(plan)
103
+ plan.edit do
104
+ testcase.assert_cleared_relations(local_peer.proxy(plan))
105
+ end
106
+ end
107
+ def remove_relations(t2)
108
+ plan.edit do
109
+ t2 = local_peer.local_object(t2)
110
+ raise unless t3 = t2.planning_task
111
+ t2.remove_planning_task(t3)
112
+ end
113
+ end
114
+ def subscribe(remote_task)
115
+ local_peer.subscribe(remote_task)
116
+ end
117
+ end
118
+ end
119
+
120
+ # Create the transaction, and do the necessary modifications
121
+ trsc = Distributed::Transaction.new(plan, :conflict_solver => SolverIgnoreUpdate.new)
122
+
123
+ trsc.add_owner remote_peer
124
+ trsc.self_owned
125
+ trsc.propose(remote_peer) if propose_first
126
+
127
+ yield(trsc)
128
+
129
+ # Check the transaction is still valid, regardless of the
130
+ # changes we made to the plan
131
+ check_resulting_plan(trsc, true)
132
+ trsc.release(false)
133
+ remote.check_resulting_plan(Distributed.format(trsc), true)
134
+ trsc.edit
135
+
136
+ # Commit and check the result
137
+ trsc.commit_transaction
138
+
139
+ check_resulting_plan(plan, true)
140
+ remote.check_resulting_plan(Distributed.format(plan), true)
141
+ end
142
+
143
+ def test_rproxy_realizes_lproxy(propose_first = false)
144
+ common_setup(propose_first) do |trsc|
145
+ # First, add relations between two nodes that are already existing
146
+ remote.add_tasks(Distributed.format(plan))
147
+ r_t2 = subscribe_task(:id => 'remote-2')
148
+ assert(1, r_t2.parents.to_a.size)
149
+ r_t1 = r_t2.parents.find { true }
150
+ t1, t2, t3 = Control.synchronize { add_tasks(plan, "local") }
151
+
152
+ assert(plan.useful_task?(r_t1))
153
+ trsc[r_t2].realized_by trsc[t2]
154
+ trsc[r_t2].on(:start, trsc[t2])
155
+ assert(plan.useful_task?(r_t1))
156
+ check_resulting_plan(trsc, false)
157
+ if propose_first
158
+ trsc.release(false)
159
+ remote.check_resulting_plan(Distributed.format(trsc), false)
160
+ trsc.edit
161
+ end
162
+
163
+ # Remove the relations in the real tasks (not the proxies)
164
+ Control.synchronize do
165
+ t2.remove_planning_task(t3)
166
+ end
167
+ remote.remove_relations(Distributed.format(r_t2))
168
+ remote.subscribe(Distributed.format(t2))
169
+
170
+ process_events
171
+ assert(plan.useful_task?(r_t1))
172
+ assert_cleared_relations(plan)
173
+
174
+ unless propose_first
175
+ trsc.propose(remote_peer)
176
+ end
177
+
178
+ process_events
179
+ remote.assert_cleared_relations(Distributed.format(plan))
180
+ end
181
+ end
182
+ def test_rproxy_realizes_lproxy_dynamic; test_rproxy_realizes_lproxy(true) end
183
+
184
+ def test_rproxy_realizes_ltask(propose_first = false)
185
+ common_setup(propose_first) do |trsc|
186
+ remote.add_tasks(Distributed.format(plan))
187
+ r_t2 = subscribe_task(:id => 'remote-2')
188
+ t1, t2, t3 = Control.synchronize { add_tasks(trsc, "local") }
189
+
190
+ trsc[r_t2].realized_by t2
191
+ trsc[r_t2].on(:start, t2)
192
+ check_resulting_plan(trsc, false)
193
+ process_events
194
+ if propose_first
195
+ remote_peer.push_subscription(t2)
196
+ trsc.release(false)
197
+ remote.check_resulting_plan(Distributed.format(trsc), false)
198
+ trsc.edit
199
+ end
200
+
201
+ # remove the relations in the real tasks (not the proxies)
202
+ remote.remove_relations(Distributed.format(r_t2))
203
+
204
+ unless propose_first
205
+ trsc.propose(remote_peer)
206
+ remote_peer.push_subscription(t2)
207
+ end
208
+ process_events
209
+ remote.assert_cleared_relations(Distributed.format(plan))
210
+ end
211
+ end
212
+ def test_rproxy_realizes_ltask_dynamic; test_rproxy_realizes_ltask(true) end
213
+
214
+ # no non-dynamic version for that since we need the transactio to be
215
+ # present on both sides if we want to have remote tasks in it
216
+ def test_rtask_realizes_lproxy
217
+ common_setup(true) do |trsc|
218
+ trsc.release(false)
219
+ r_t1, r_t2, r_t3 = remote.add_tasks(Distributed.format(trsc)).map { |t| remote_peer.proxy(t) }
220
+ trsc.edit
221
+
222
+ assert(r_t2.subscribed?)
223
+ t1, t2, t3 = Control.synchronize { add_tasks(plan, "local") }
224
+ r_t2.realized_by trsc[t2]
225
+ r_t2.on(:start, trsc[t2])
226
+ remote_peer.subscribe(r_t2)
227
+ remote_peer.push_subscription(t2)
228
+
229
+ check_resulting_plan(trsc, false)
230
+ trsc.release(false)
231
+ remote.check_resulting_plan(Distributed.format(trsc), false)
232
+ trsc.edit
233
+
234
+ # remove the relations in the real tasks (not the proxies)
235
+ t2.remove_planning_task(t3)
236
+ process_events
237
+ remote.assert_cleared_relations(Distributed.format(plan))
238
+ end
239
+ end
240
+
241
+ def test_garbage_collect
242
+ peer2peer(true) do |remote|
243
+ remote.plan.insert(SimpleTask.new(:id => 'remote-1'))
244
+ def remote.insert_children(trsc, root_task)
245
+ trsc = local_peer.local_object(trsc)
246
+ root_task = local_peer.local_object(root_task)
247
+ trsc.edit
248
+
249
+ root_task.realized_by(r2 = SimpleTask.new(:id => 'remote-2'))
250
+ r2.realized_by(r3 = SimpleTask.new(:id => 'remote-3'))
251
+ trsc.release(false)
252
+ end
253
+ end
254
+
255
+ r1 = subscribe_task(:id => 'remote-1')
256
+ assert(!plan.unneeded_tasks.include?(r1))
257
+
258
+ t1 = SimpleTask.new
259
+
260
+ # Add a local child to r1. This local child, and r1, must be kept event
261
+ # we are not subscribed to r1 anymore
262
+ trsc = Distributed::Transaction.new(plan)
263
+ trsc.add_owner(remote_peer)
264
+ trsc[r1].realized_by t1
265
+ Roby::Control.synchronize do
266
+ remote_peer.unsubscribe(r1)
267
+ assert(!plan.unneeded_tasks.include?(r1))
268
+ end
269
+
270
+ trsc.propose(remote_peer)
271
+ trsc.commit_transaction
272
+ assert(!plan.unneeded_tasks.include?(r1))
273
+ assert(!plan.unneeded_tasks.include?(t1), plan.unneeded_tasks)
274
+
275
+ # Ok, we now create a r1 => t1 => t2 => t3 chain
276
+ # * t2 and t3 are kept because they are useful for r1
277
+ t2, t3 = nil
278
+ Roby::Control.synchronize do
279
+ t1.realized_by(t2 = SimpleTask.new)
280
+ assert(!plan.unneeded_tasks.include?(t2))
281
+ t2.realized_by(t3 = SimpleTask.new)
282
+ assert(!plan.unneeded_tasks.include?(t3))
283
+ end
284
+
285
+ # Now, create a t3 => r2 => r3 chain
286
+ # * r2 should be kept since it is related to a task which is kept
287
+ # * r3 should not be kept
288
+ trsc = Distributed::Transaction.new(plan)
289
+ trsc.add_owner(remote_peer)
290
+ trsc.propose(remote_peer)
291
+ trsc.release
292
+ remote.insert_children(Distributed.format(trsc), Distributed.format(trsc[t3]))
293
+ trsc.edit
294
+ trsc.commit_transaction
295
+ process_events
296
+
297
+ r2 = remote_task(:id => 'remote-2')
298
+ Roby::Control.synchronize do
299
+ assert(r2.plan && !plan.unneeded_tasks.include?(r2))
300
+ assert(t3.child_object?(r2, TaskStructure::Hierarchy))
301
+ end
302
+
303
+ r3 = remote_task(:id => 'remote-3')
304
+ Roby::Control.synchronize do
305
+ assert(!r3.plan || plan.unneeded_tasks.include?(r3))
306
+ end
307
+ end
308
+
309
+ # This tests that the race condition between transaction commit and plan GC
310
+ # is handled properly: if a task inside a transaction will be GCed just
311
+ # after the commit, there is a race condition possibility if the other
312
+ # peers do not have committed the transaction yet
313
+ def test_commit_race_condition
314
+ peer2peer(true) do |remote|
315
+ def remote.add_task(trsc)
316
+ trsc = local_peer.local_object(trsc)
317
+ trsc.edit
318
+ trsc.discover(SimpleTask.new(:id => 'remote'))
319
+ trsc.release(false)
320
+ end
321
+ end
322
+
323
+ # Create an empty transaction and send it to our peer
324
+ # The peer will then discover a task, which
325
+ # will be GCed as soon as the transaction is committed
326
+ trsc = Distributed::Transaction.new(plan)
327
+ trsc.add_owner(remote_peer)
328
+ trsc.propose(remote_peer)
329
+ trsc.release
330
+ remote.add_task(Distributed.format(trsc))
331
+ trsc.edit
332
+
333
+ assert_nothing_raised do
334
+ trsc.commit_transaction
335
+ process_events
336
+ end
337
+ assert(remote_peer.connected?)
338
+ end
339
+ end
340
+
341
+
@@ -0,0 +1,238 @@
1
+ $LOAD_PATH.unshift File.expand_path('../..', File.dirname(__FILE__))
2
+ require 'roby/test/distributed'
3
+ require 'roby/test/tasks/simple_task'
4
+ require 'flexmock'
5
+
6
+ class TC_DistributedPlanNotifications < Test::Unit::TestCase
7
+ include Roby::Distributed::Test
8
+
9
+ def test_triggers
10
+ peer2peer(true) do |remote|
11
+ def remote.new_task(kind, args)
12
+ Roby.execute do
13
+ new_task = kind.proxy(local_peer).new(args)
14
+ yield(new_task.remote_id) if block_given?
15
+ plan.insert(new_task)
16
+ end
17
+ nil
18
+ end
19
+ end
20
+
21
+ notification = TaskMatcher.new.
22
+ with_model(SimpleTask).
23
+ with_arguments(:id => 2)
24
+
25
+ FlexMock.use do |mock|
26
+ remote_peer.on(notification) do |task|
27
+ assert(plan.useful_task?(task))
28
+ assert(task.plan)
29
+ mock.notified(task.sibling_on(remote_peer))
30
+ nil
31
+ end
32
+
33
+ simple_task = Distributed.format(SimpleTask)
34
+ roby_task = Distributed.format(Roby::Task)
35
+
36
+ remote.new_task(simple_task, :id => 3)
37
+ remote.new_task(roby_task, :id => 2)
38
+ remote.new_task(simple_task, :id => 2) do |inserted_id|
39
+ mock.should_receive(:notified).with(inserted_id).once.ordered
40
+ nil
41
+ end
42
+
43
+ remote.new_task(simple_task, :id => 3)
44
+ remote.new_task(roby_task, :id => 2)
45
+ remote.new_task(simple_task, :id => 2) do |inserted_id|
46
+ mock.should_receive(:notified).with(inserted_id).once.ordered
47
+ nil
48
+ end
49
+
50
+ remote_peer.synchro_point
51
+ end
52
+ end
53
+
54
+ def test_trigger_subscribe
55
+ peer2peer(true) do |remote|
56
+ def remote.new_task
57
+ plan.insert(SimpleTask.new(:id => 1))
58
+ nil
59
+ end
60
+ end
61
+
62
+ notification = TaskMatcher.new.
63
+ with_model(SimpleTask).
64
+ with_arguments(:id => 1)
65
+
66
+ task = nil
67
+ remote_peer.on(notification) do |t|
68
+ remote_peer.subscribe(t)
69
+ task = t
70
+ end
71
+ remote.new_task
72
+ while !task
73
+ remote_peer.synchro_point
74
+ end
75
+
76
+ assert(task)
77
+ assert_equal([task], plan.find_tasks.with_arguments(:id => 1).to_a)
78
+ end
79
+
80
+ def test_subscribe_plan
81
+ peer2peer(true) do |remote|
82
+ plan.insert(mission = Task.new(:id => 'mission'))
83
+ subtask = Task.new :id => 'subtask'
84
+ plan.insert(next_mission = Task.new(:id => 'next_mission'))
85
+ mission.realized_by subtask
86
+ mission.on(:start, next_mission, :start)
87
+ end
88
+
89
+ # Subscribe to the remote plan
90
+ remote_peer.subscribe_plan
91
+ assert(remote_peer.subscribed_plan?)
92
+
93
+ # Check that the remote plan has been mapped locally
94
+ process_events
95
+ tasks = plan.known_tasks
96
+ assert_equal(4, tasks.size)
97
+ assert(p_mission = tasks.find { |t| t.arguments[:id] == 'mission' })
98
+ assert(p_subtask = tasks.find { |t| t.arguments[:id] == 'subtask' })
99
+ assert(p_next_mission = tasks.find { |t| t.arguments[:id] == 'next_mission' })
100
+
101
+ assert(p_mission.child_object?(p_subtask, TaskStructure::Hierarchy))
102
+ assert(p_mission.event(:start).child_object?(p_next_mission.event(:start), EventStructure::Signal))
103
+ end
104
+
105
+ def test_plan_updates
106
+ peer2peer(true) do |remote|
107
+ class << remote
108
+ attr_reader :mission, :subtask, :next_mission, :free_event
109
+ def create_mission
110
+ @mission = Roby::Task.new :id => 'mission'
111
+ plan.insert(mission)
112
+ end
113
+ def create_subtask
114
+ plan.permanent(@subtask = Roby::Task.new(:id => 'subtask'))
115
+ mission.realized_by subtask
116
+ end
117
+ def create_next_mission
118
+ @next_mission = Roby::Task.new :id => 'next_mission'
119
+ mission.on(:start, next_mission, :start)
120
+ plan.insert(next_mission)
121
+ end
122
+ def create_free_event
123
+ @free_event = Roby::EventGenerator.new(true)
124
+ # Link the event to a task to protect it from GC
125
+ @next_mission.on(:start, @free_event)
126
+ plan.discover(free_event)
127
+ end
128
+ def remove_free_event
129
+ plan.remove_object(free_event)
130
+ end
131
+ def unlink_next_mission; mission.event(:start).remove_signal(next_mission.event(:start)) end
132
+ def remove_next_mission; plan.remove_object(next_mission) end
133
+ def unlink_subtask; mission.remove_child(subtask) end
134
+ def remove_subtask; plan.remove_object(subtask) end
135
+ def discard_mission
136
+ plan.permanent(mission)
137
+ plan.discard(mission)
138
+ end
139
+ def remove_mission; plan.remove_object(mission) end
140
+ end
141
+ end
142
+
143
+ # Subscribe to the remote plan
144
+ remote_peer.subscribe_plan
145
+
146
+ remote.create_mission
147
+ process_events
148
+ p_mission = remote_task(:id => 'mission')
149
+ # NOTE: the count is always remote_tasks + 1 since we have the ConnectionTask for our connection
150
+ assert_equal(2, plan.size, plan.known_tasks.to_a)
151
+ assert(p_mission.mission?)
152
+ process_events
153
+ assert(p_mission.plan)
154
+
155
+ remote.create_subtask
156
+ process_events
157
+ p_subtask = remote_task(:id => 'subtask')
158
+ assert_equal(3, plan.size)
159
+ assert(p_mission.child_object?(p_subtask, TaskStructure::Hierarchy))
160
+
161
+ remote.create_next_mission
162
+ process_events
163
+ p_next_mission = remote_task(:id => 'next_mission')
164
+ assert_equal(4, plan.size)
165
+ assert(p_mission.event(:start).child_object?(p_next_mission.event(:start), EventStructure::Signal))
166
+
167
+ remote.create_free_event
168
+ process_events
169
+ assert_equal(1, plan.free_events.size)
170
+ process_events
171
+ assert_equal(1, plan.free_events.size)
172
+
173
+ remote.remove_free_event
174
+ process_events
175
+ assert_equal(0, plan.free_events.size)
176
+
177
+ remote.unlink_next_mission
178
+ process_events
179
+ assert_equal(4, plan.size)
180
+ assert(!p_mission.event(:start).child_object?(p_next_mission.event(:start), EventStructure::Signal))
181
+
182
+ remote.remove_next_mission
183
+ process_events
184
+ assert_equal(3, plan.size)
185
+ assert(!p_next_mission.plan)
186
+
187
+ remote.unlink_subtask
188
+ assert(p_subtask.subscribed?)
189
+ process_events
190
+ assert_equal(3, plan.size, plan.known_tasks)
191
+ assert(!p_mission.child_object?(p_subtask, TaskStructure::Hierarchy))
192
+
193
+ remote.remove_subtask
194
+ process_events
195
+ assert_equal(2, plan.size)
196
+ assert(!p_subtask.plan)
197
+
198
+ remote.discard_mission
199
+ process_events
200
+ assert(!p_mission.mission?)
201
+
202
+ remote.remove_mission
203
+ process_events
204
+ assert_equal(1, plan.size)
205
+ assert(!p_mission.plan)
206
+ end
207
+
208
+ def test_unsubscribe_plan
209
+ peer2peer(true) do |remote|
210
+ remote.plan.insert(Task.new(:id => 'remote-1'))
211
+ remote.plan.insert(Task.new(:id => 'remote-2'))
212
+
213
+ def remote.new_task
214
+ plan.insert(Task.new(:id => 'remote-3'))
215
+ end
216
+ end
217
+
218
+ remote_peer.subscribe_plan
219
+ assert_equal(3, plan.size)
220
+
221
+ # Subscribe to the remote-1 task and unsubscribe to the plan
222
+ r1 = *plan.find_tasks.with_arguments(:id => 'remote-1').to_a
223
+ remote_peer.subscribe(r1)
224
+
225
+ remote_peer.unsubscribe_plan
226
+ assert(!remote_peer.subscribed_plan?)
227
+
228
+ # Start plan GC, the subscribed task should remain
229
+ process_events
230
+ assert_equal(2, plan.size, plan.known_tasks)
231
+
232
+ # Add a new task in the remote plan, check we do not get the updates
233
+ # anymore
234
+ remote.new_task
235
+ process_events
236
+ assert_equal(2, plan.size)
237
+ end
238
+ end