roby 0.7

Sign up to get free protection for your applications and to get access to all the features.
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,9 @@
1
+ module Roby
2
+ module Test
3
+ class EmptyTask < Roby::Task
4
+ terminates
5
+ forward :start => :success
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,36 @@
1
+
2
+ module Roby
3
+ module Test
4
+ class Goto2D < Roby::Task
5
+ terminates
6
+ argument :x, :y
7
+
8
+ def speed; State.goto_speed end
9
+ def x; arguments[:x] end
10
+ def y; arguments[:y] end
11
+
12
+ poll do
13
+ dx = x - State.pos.x
14
+ dy = y - State.pos.y
15
+ d = Math.sqrt(dx * dx + dy * dy)
16
+ if d > speed
17
+ State.pos.x += speed * dx / d
18
+ State.pos.y += speed * dy / d
19
+ else
20
+ State.pos.x = x
21
+ State.pos.y = y
22
+ emit :success
23
+ end
24
+ STDERR.puts "#{x} #{y} #{speed} #{State.pos}"
25
+ end
26
+
27
+ module Planning
28
+ planning_library
29
+ method(:go_to) do
30
+ Goto2D.new(arguments)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,23 @@
1
+ require 'roby/task'
2
+
3
+ module Roby
4
+ module Test
5
+ # This task model is a simple task where +start+, +success+ and
6
+ # +failed+ are pass-through controlable events. They have an +id+
7
+ # argument which is automatically set to the object's #object_id if not
8
+ # explicitely given at initialization.
9
+ class SimpleTask < Roby::Task
10
+ argument :id
11
+
12
+ def initialize(arguments = {}) # :nodoc:
13
+ arguments = { :id => object_id.to_s }.merge(arguments)
14
+ super(arguments)
15
+ end
16
+
17
+ event :start, :command => true
18
+ event :success, :command => true, :terminal => true
19
+ terminates
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,519 @@
1
+ require 'roby'
2
+ require 'active_support/core_ext/string/inflections'
3
+ class String # :nodoc: all
4
+ include ActiveSupport::CoreExtensions::String::Inflections
5
+ end
6
+
7
+ require 'test/unit'
8
+ require 'roby/test/common'
9
+ require 'roby/test/tools'
10
+ require 'fileutils'
11
+
12
+ module Roby
13
+ module Test
14
+ extend Logger::Hierarchy
15
+ extend Logger::Forward
16
+
17
+ @event_assertions = []
18
+ @waiting_threads = []
19
+
20
+ ASSERT_ANY_EVENTS_TLS = :assert_any_events
21
+
22
+ class << self
23
+ # A [thread, cv, positive, negative] list of event assertions
24
+ attr_reader :event_assertions
25
+
26
+ # Tests for events in +positive+ and +negative+ and returns
27
+ # the set of failing events if the assertion has finished.
28
+ # If the set is empty, it means that the assertion finished
29
+ # successfully
30
+ def assert_any_event_result(positive, negative)
31
+ if positive_ev = positive.find { |ev| ev.happened? }
32
+ return false, "#{positive_ev} happened"
33
+ end
34
+ failure = negative.find_all { |ev| ev.happened? }
35
+ unless failure.empty?
36
+ return true, "#{failure} happened"
37
+ end
38
+
39
+ if positive.all? { |ev| ev.unreachable? }
40
+ return true, "all positive events are unreachable"
41
+ end
42
+
43
+ nil
44
+ end
45
+
46
+ # This method is inserted in the control thread to implement
47
+ # Assertions#assert_events
48
+ def check_event_assertions
49
+ event_assertions.delete_if do |thread, cv, positive, negative|
50
+ error, result = assert_any_event_result(positive, negative)
51
+ if !error.nil?
52
+ thread[ASSERT_ANY_EVENTS_TLS] = [error, result]
53
+ cv.broadcast
54
+ true
55
+ end
56
+ end
57
+ end
58
+
59
+ def finalize_event_assertions
60
+ check_event_assertions
61
+ event_assertions.dup.each do |thread, *_|
62
+ thread.raise ControlQuitError
63
+ end
64
+ end
65
+
66
+ # A set of threads waiting for something to happen. This is used
67
+ # during #teardown to make sure no threads are block indefinitely
68
+ attr_reader :waiting_threads
69
+
70
+ # This proc is to be called by Control when it quits. It makes sure
71
+ # that threads which are waiting are interrupted
72
+ def interrupt_waiting_threads
73
+ waiting_threads.dup.each do |task|
74
+ task.raise ControlQuitError
75
+ end
76
+ ensure
77
+ waiting_threads.clear
78
+ end
79
+
80
+ end
81
+ Roby::Control.at_cycle_end(&method(:check_event_assertions))
82
+ Roby::Control.finalizers << method(:finalize_event_assertions)
83
+ Roby::Control.finalizers << method(:interrupt_waiting_threads)
84
+
85
+ module Assertions
86
+ # Wait for any event in +positive+ to happen. If +negative+ is
87
+ # non-empty, any event happening in this set will make the
88
+ # assertion fail. If events in +positive+ are task events, the
89
+ # :stop events of the corresponding tasks are added to negative
90
+ # automatically.
91
+ #
92
+ # If a block is given, it is called from within the control thread
93
+ # after the checks are in place
94
+ #
95
+ # So, to check that a task fails, do
96
+ #
97
+ # assert_events(task.event(:fail)) do
98
+ # task.start!
99
+ # end
100
+ #
101
+ def assert_any_event(positive, negative = [], msg = nil, &block)
102
+ control_priority do
103
+ Roby.condition_variable(false) do |cv|
104
+ positive = Array[*positive].to_value_set
105
+ negative = Array[*negative].to_value_set
106
+
107
+ unreachability_reason = ValueSet.new
108
+ Roby::Control.synchronize do
109
+ positive.each do |ev|
110
+ ev.if_unreachable(true) do |reason|
111
+ unreachability_reason << reason if reason
112
+ end
113
+ end
114
+
115
+ error, result = Test.assert_any_event_result(positive, negative)
116
+ if error.nil?
117
+ this_thread = Thread.current
118
+
119
+ Test.event_assertions << [this_thread, cv, positive, negative]
120
+ Roby.once(&block) if block_given?
121
+ begin
122
+ cv.wait(Roby::Control.mutex)
123
+ ensure
124
+ Test.event_assertions.delete_if { |thread, _| thread == this_thread }
125
+ end
126
+
127
+ error, result = this_thread[ASSERT_ANY_EVENTS_TLS]
128
+ end
129
+
130
+ if error
131
+ if !unreachability_reason.empty?
132
+ msg = unreachability_reason.map do |reason|
133
+ if reason.respond_to?(:context)
134
+ context = reason.context.map do |obj|
135
+ if obj.kind_of?(Exception)
136
+ obj.full_message
137
+ else
138
+ obj.to_s
139
+ end
140
+ end
141
+ reason.to_s + context.join("\n ")
142
+ end
143
+ end
144
+ msg.join("\n ")
145
+
146
+ flunk("#{msg} all positive events are unreachable for the following reason:\n #{msg}")
147
+ elsif msg
148
+ flunk("#{msg} failed: #{result}")
149
+ else
150
+ flunk(result)
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ # Starts +task+ and checks it succeeds
159
+ def assert_succeeds(task, *args)
160
+ control_priority do
161
+ if !task.kind_of?(Roby::Task)
162
+ Roby.execute do
163
+ plan.insert(task = planner.send(task, *args))
164
+ end
165
+ end
166
+
167
+ assert_any_event([task.event(:success)], [], nil) do
168
+ plan.permanent(task)
169
+ task.start! if task.pending?
170
+ yield if block_given?
171
+ end
172
+ end
173
+ end
174
+
175
+ def control_priority
176
+ old_priority = Thread.current.priority
177
+ Thread.current.priority = Roby.control.thread.priority + 1
178
+
179
+ yield
180
+ ensure
181
+ Thread.current.priority = old_priority
182
+ end
183
+
184
+ # This assertion fails if the relative error between +found+ and
185
+ # +expected+is more than +error+
186
+ def assert_relative_error(expected, found, error, msg = "")
187
+ if expected == 0
188
+ assert_in_delta(0, found, error, "comparing #{found} to #{expected} in #{msg}")
189
+ else
190
+ assert_in_delta(0, (found - expected) / expected, error, "comparing #{found} to #{expected} in #{msg}")
191
+ end
192
+ end
193
+
194
+ # This assertion fails if +found+ and +expected+ are more than +dl+
195
+ # meters apart in the x, y and z coordinates, or +dt+ radians apart
196
+ # in angles
197
+ def assert_same_position(expected, found, dl = 0.01, dt = 0.01, msg = "")
198
+ assert_relative_error(expected.x, found.x, dl, msg)
199
+ assert_relative_error(expected.y, found.y, dl, msg)
200
+ assert_relative_error(expected.z, found.z, dl, msg)
201
+ assert_relative_error(expected.yaw, found.yaw, dt, msg)
202
+ assert_relative_error(expected.pitch, found.pitch, dt, msg)
203
+ assert_relative_error(expected.roll, found.roll, dt, msg)
204
+ end
205
+ end
206
+
207
+ # This is the base class for running tests which uses a Roby control
208
+ # loop (i.e. plan execution).
209
+ #
210
+ # Because configuration and planning can be robot-specific, parts of
211
+ # the tests can also be splitted into generic parts and specific parts.
212
+ # The TestCase.robot statement allows to specify that a given test case
213
+ # is specific to a given robot, in which case it is ran only if the
214
+ # call to <tt>scripts/test</tt> specified a robot which matches (i.e.
215
+ # same name and type).
216
+ #
217
+ # Finally, two other mode of operation control the way tests are ran
218
+ # [simulation]
219
+ # if the <tt>--sim</tt> flag is given to <tt>scripts/test</tt>, the
220
+ # tests are ran under simulation. Otherwise, they are run in live
221
+ # mode (see Roby::Application for a description of simulation and
222
+ # live modes). It is possible to constrain that a given test method
223
+ # is run only in simulation or live mode with the TestCase.sim and
224
+ # TestCase.nosim statements:
225
+ #
226
+ # sim :sim_only
227
+ # def test_sim_only
228
+ # end
229
+ #
230
+ # nosim :live_only
231
+ # def test_live_only
232
+ # end
233
+ # [interactive]
234
+ # Sometime, it is hard to actually assess the quality of processing
235
+ # results automatically. In these cases, it is possible to show the
236
+ # user the result of data processing, and then ask if the result is
237
+ # valid by using the #user_validation method. Nonetheless, the tests
238
+ # can be ran in automatic mode, in which the assertions which require
239
+ # user validation are simply skipped. The <tt>--interactive</tt> or
240
+ # <tt>-i</tt> flags of <tt>scripts/test</tt> specify that user
241
+ # interaction is possible.
242
+ class TestCase < Test::Unit::TestCase
243
+ include Roby::Test
244
+ include Assertions
245
+ class << self
246
+ attribute(:case_config) { Hash.new }
247
+ attribute(:methods_config) { Hash.new }
248
+ attr_reader :app_setup
249
+ end
250
+
251
+ # Sets the robot configuration for this test case. If a block is
252
+ # given, it is called between the time the robot configuration is
253
+ # loaded and the time the test methods are started. It can
254
+ # therefore be used to change the robot configuration for the need
255
+ # of this particular test case
256
+ def self.robot(name, kind = name, &block)
257
+ @app_setup = [name, kind, block]
258
+ apply_robot_setup
259
+ end
260
+
261
+ @@first_time = true
262
+ # Loads the configuration as specified by TestCase.robot
263
+ def self.apply_robot_setup
264
+ app = Roby.app
265
+ if @@first_time
266
+ # Make sure the log directory is empty
267
+ if File.exists?(app.log_dir)
268
+ if !Dir.new(app.log_dir).empty?
269
+ if !STDIN.ask("#{app.log_dir} still exists and must be cleaned before starting. Proceed ? [N,y]", false)
270
+ raise "user abort"
271
+ end
272
+ end
273
+ FileUtils.rm_rf app.log_dir
274
+ end
275
+ @@first_time = false
276
+ end
277
+
278
+ name, kind, block = app_setup
279
+ # Silently ignore the test suites which use a different robot
280
+ if app.robot_name &&
281
+ (app.robot_name != name || app.robot_type != kind)
282
+ return
283
+ end
284
+ app.robot name, kind
285
+ app.reset
286
+ app.single
287
+ app.setup
288
+ if block
289
+ block.call
290
+ end
291
+
292
+ app.control.delete('executive')
293
+
294
+ yield if block_given?
295
+ end
296
+
297
+ # Returns a fresh MainPlanner object for the current plan
298
+ def planner
299
+ MainPlanner.new(plan)
300
+ end
301
+
302
+ def setup # :nodoc:
303
+ super
304
+ Roby::Test.waiting_threads << Thread.current
305
+ end
306
+
307
+ def teardown # :nodoc:
308
+ Roby::Test.waiting_threads.delete(Thread.current)
309
+ super
310
+ end
311
+
312
+ def method_config # :nodoc:
313
+ self.class.case_config.merge(self.class.methods_config[method_name] || Hash.new)
314
+ end
315
+
316
+ # Returns true if user interaction is to be disabled during this test
317
+ def automatic_testing?
318
+ Roby.app.automatic_testing?
319
+ end
320
+
321
+ # Progress report for the curren test. If +max+ is given, then
322
+ # +value+ is assumed to be between 0 and +max+. Otherwise, +value+
323
+ # is a float value between 0 and 1 and is displayed as a percentage.
324
+ def progress(value, max = nil)
325
+ if max
326
+ print "\r#{@method_name} progress: #{value}/#{max}"
327
+ else
328
+ print "\r#{@method_name} progress: #{"%.2f %%" % [value * 100]}"
329
+ end
330
+ STDOUT.flush
331
+ end
332
+
333
+ def user_interaction
334
+ return unless automatic_testing?
335
+
336
+ test_result = catch(:validation_result) do
337
+ yield
338
+ return
339
+ end
340
+ if test_result
341
+ flunk(*test_result)
342
+ end
343
+ end
344
+
345
+ # Ask for user validation. The method first yields, and then asks
346
+ # the user if the showed dataset is nominal. If the tests are ran
347
+ # in automated mode (#automatic_testing? returns true), it does
348
+ # nothing.
349
+ def user_validation(msg)
350
+ return if automatic_testing?
351
+
352
+ assert_block(msg) do
353
+ STDOUT.puts "Now validating #{msg}"
354
+ yield
355
+
356
+ STDIN.ask("\rIs the result OK ? [N,y]", false)
357
+ end
358
+ end
359
+
360
+ # Do not run +test_name+ inside a simulation environment
361
+ # +test_name+ is the name of the method without +test_+. For
362
+ # instance:
363
+ # nosim :init
364
+ # def test_init
365
+ # end
366
+ #
367
+ # See also TestCase.sim
368
+ def self.nosim(*names)
369
+ names.each do |test_name|
370
+ config = (methods_config[test_name.to_s] ||= Hash.new)
371
+ config[:mode] = :nosim
372
+ end
373
+ end
374
+
375
+ # Run +test_name+ only inside a simulation environment
376
+ # +test_name+ is the name of the method without +test_+. For
377
+ # instance:
378
+ # sim :init
379
+ # def test_init
380
+ # end
381
+ #
382
+ # See also TestCase.nosim
383
+ def self.sim(*names)
384
+ names.each do |test_name|
385
+ config = (methods_config[test_name.to_s] ||= Hash.new)
386
+ config[:mode] = :sim
387
+ end
388
+ end
389
+
390
+ def self.suite # :nodoc:
391
+ method_names = public_instance_methods(true)
392
+ tests = method_names.delete_if {|method_name| method_name !~ /^(dataset|test)./}
393
+ suite = Test::Unit::TestSuite.new(name)
394
+ tests.sort.each do |test|
395
+ catch(:invalid_test) do
396
+ suite << new(test)
397
+ end
398
+ end
399
+ if (suite.empty?)
400
+ catch(:invalid_test) do
401
+ suite << new("default_test")
402
+ end
403
+ end
404
+ return suite
405
+ end
406
+
407
+ def run(result) # :nodoc:
408
+ Roby::Test.waiting_threads.clear
409
+
410
+ self.class.apply_robot_setup do
411
+ yield if block_given?
412
+
413
+ case method_config[:mode]
414
+ when :nosim
415
+ return if Roby.app.simulation?
416
+ when :sim
417
+ return unless Roby.app.simulation?
418
+ end
419
+
420
+ @failed_test = false
421
+ begin
422
+ Roby.app.run do
423
+ super
424
+ end
425
+ rescue Exception => e
426
+ if @_result
427
+ add_error(e)
428
+ else
429
+ raise
430
+ end
431
+ end
432
+
433
+ keep_logdir = @failed_test || Roby.app.testing_keep_logs?
434
+ save_logdir = (@failed_test && automatic_testing?) || Roby.app.testing_keep_logs?
435
+ if save_logdir
436
+ subdir = @failed_test ? 'failures' : 'results'
437
+ basedir = File.join(APP_DIR, 'test', subdir)
438
+ dirname = Roby::Application.unique_dirname(basedir, dataset_prefix)
439
+
440
+ if Roby.app.testing_overwrites_logs?
441
+ dirname.gsub! /\.\d+$/, ''
442
+ FileUtils.rm_rf dirname
443
+ end
444
+
445
+ FileUtils.mv Roby.app.log_dir, dirname
446
+ end
447
+ if !keep_logdir
448
+ FileUtils.rm_rf Roby.app.log_dir
449
+ end
450
+ end
451
+
452
+ rescue Exception
453
+ puts "testcase #{method_name} teardown failed with\n#{$!.full_message}"
454
+ end
455
+
456
+ def add_error(*args, &block) # :nodoc:
457
+ @failed_test = true
458
+ super
459
+ end
460
+ def add_failure(*args, &block) # :nodoc:
461
+ @failed_test = true
462
+ super
463
+ end
464
+
465
+ # The directory in which datasets are to be saved
466
+ def datasets_dir
467
+ "#{APP_DIR}/test/datasets"
468
+ end
469
+ # The directory into which the datasets generated by the current
470
+ # testcase are to be saved.
471
+ def dataset_prefix
472
+ "#{Roby.app.robot_name}-#{self.class.name.gsub('TC_', '').underscore}/, '')}"
473
+ end
474
+ # Returns the full path of the file name into which the log file +file+
475
+ # should be saved to be referred to as the +dataset_name+ dataset
476
+ def dataset_file_path(dataset_name, file)
477
+ path = File.join(datasets_dir, dataset_name, file)
478
+ if !File.file?(path)
479
+ raise "#{path} does not exist"
480
+ end
481
+
482
+ path
483
+ rescue
484
+ flunk("dataset #{dataset_name} has not been generated: #{$!.message}")
485
+ end
486
+
487
+ # Saves +file+, which is taken in the log directory, in the
488
+ # test/datasets directory. The data set is saved as
489
+ # 'robot-testname-testmethod-suffix'
490
+ def save_dataset(files = nil, suffix = '')
491
+ destname = dataset_prefix
492
+ destname << "-#{suffix}" unless suffix.empty?
493
+
494
+ dir = File.join(datasets_dir, destname)
495
+ if File.exists?(dir)
496
+ relative_dir = dir.gsub(/^#{Regexp.quote(APP_DIR)}/, '')
497
+ unless STDIN.ask("\r#{relative_dir} already exists. Delete ? [N,y]", false)
498
+ raise "user abort"
499
+ end
500
+ FileUtils.rm_rf dir
501
+ end
502
+ FileUtils.mkdir_p(dir)
503
+
504
+ files ||= Dir.entries(Roby.app.log_dir).find_all do |path|
505
+ File.file? File.join(Roby.app.log_dir, path)
506
+ end
507
+
508
+ [*files].each do |path|
509
+ FileUtils.mv "#{Roby.app.log_dir}/#{path}", dir
510
+ end
511
+ end
512
+
513
+ def sampling(*args, &block); Test.sampling(*args, &block) end
514
+ def stats(*args, &block); Test.stats(*args, &block) end
515
+ end
516
+ end
517
+ end
518
+
519
+