Module: Roby::Test
- Extended by:
- Logger::Forward, Logger::Hierarchy
- Includes:
- Roby
- Included in:
- Distributed::Test, TestCase
- Defined in:
- lib/roby/test/tools.rb,
lib/roby/test/common.rb,
lib/roby/test/testcase.rb,
lib/roby/test/tasks/goto.rb,
lib/roby/test/tasks/empty_task.rb,
lib/roby/test/tasks/simple_task.rb
Defined Under Namespace
Modules: Assertions Classes: EmptyTask, FailedTimeout, Goto2D, SimpleTask, Stat, TestCase
Constant Summary collapse
- Unit =
::Test::Unit
- BASE_PORT =
1245- DISCOVERY_SERVER =
"druby://localhost:#{BASE_PORT}"- REMOTE_PORT =
BASE_PORT + 1
- LOCAL_PORT =
BASE_PORT + 2
- REMOTE_SERVER =
"druby://localhost:#{BASE_PORT + 3}"- LOCAL_SERVER =
"druby://localhost:#{BASE_PORT + 4}"- ASSERT_ANY_EVENTS_TLS =
:assert_any_events
Constants included from Roby
ROBY_LIB_DIR, ROBY_ROOT_DIR, RX_IN_FRAMEWORK, VERSION
Class Attribute Summary collapse
-
.check_allocation_count ⇒ Object
Returns the value of attribute check_allocation_count.
-
.event_assertions ⇒ Object
readonly
A [thread, cv, positive, negative] list of event assertions.
-
.waiting_threads ⇒ Object
readonly
A set of threads waiting for something to happen.
Instance Attribute Summary collapse
-
#console_logger ⇒ Object
The console logger object.
-
#original_collections ⇒ Object
readonly
a [collection, collection_backup] array of the collections saved by #original_collections.
-
#remote_processes ⇒ Object
readonly
The list of children started using #remote_process.
-
#timings ⇒ Object
readonly
Returns the value of attribute timings.
Class Method Summary collapse
-
.assert_any_event_result(positive, negative) ⇒ Object
Tests for events in
positiveandnegativeand returns the set of failing events if the assertion has finished. -
.check_event_assertions ⇒ Object
This method is inserted in the control thread to implement Assertions#assert_events.
- .finalize_event_assertions ⇒ Object
-
.interrupt_waiting_threads ⇒ Object
This proc is to be called by Control when it quits.
- .sampling(duration, period, *fields) ⇒ Object
-
.stats(samples, spec) ⇒ Object
Computes mean and standard deviation about the samples in
samplesspecdescribes what to compute: * if nothing is specified, we compute the statistics on v(i - 1) - v(i) * if spec is ‘rate’, we compute the statistics on (v(i - 1) - v(i)) / (t(i - 1) / t(i)) * if spec is ‘absolute’, we compute the statistics on v(i) * if spec is ‘absolute_rate’, we compute the statistics on v(i) / (t(i - 1) / t(i)).
Instance Method Summary collapse
-
#assert_doesnt_timeout(seconds, message = "watchdog #{seconds} failed") ⇒ Object
Checks that the given block returns within
secondsseconds. - #assert_marshallable(object) ⇒ Object
- #assert_original_error(klass, localized_error_type = LocalizedError) ⇒ Object
- #display_event_structure(object, relation, indent = " ") ⇒ Object
- #display_timings! ⇒ Object
-
#new_plan ⇒ Object
Clear the plan and return it.
-
#plan ⇒ Object
The plan used by the tests.
-
#prepare_plan(options) ⇒ Object
Creates a set of tasks and returns them.
-
#process_events ⇒ Object
Process pending events.
-
#remote_process ⇒ Object
Start a new process and saves its PID in #remote_processes.
-
#restore_collections ⇒ Object
Restors the collections saved by #save_collection to their previous state.
-
#save_collection(obj) ⇒ Object
Saves the current state of
obj. - #setup ⇒ Object
-
#stop_remote_processes ⇒ Object
Stop all the remote processes that have been started using #remote_process.
- #teardown ⇒ Object
- #teardown_plan ⇒ Object
- #wait_thread_stopped(thread) ⇒ Object
Methods included from Roby
RelationSpace, app, check_failed_missions, condition_variable, control_thread, each_cycle, each_exception_handler, every, execute, filter_backtrace, format_exception, inside_control?, load_all_relations, log_exception, on_exception, once, outside_control?, poll_state_events, pretty_print_backtrace, return_condition_variable, wait_one_cycle, wait_until
Methods included from ExceptionHandlingObject
#handle_exception, #pass_exception
Class Attribute Details
.check_allocation_count ⇒ Object
Returns the value of attribute check_allocation_count.
21 22 23 |
# File 'lib/roby/test/common.rb', line 21 def check_allocation_count @check_allocation_count end |
.event_assertions ⇒ Object (readonly)
A [thread, cv, positive, negative] list of event assertions
24 25 26 |
# File 'lib/roby/test/testcase.rb', line 24 def event_assertions @event_assertions end |
.waiting_threads ⇒ Object (readonly)
A set of threads waiting for something to happen. This is used during #teardown to make sure no threads are block indefinitely
68 69 70 |
# File 'lib/roby/test/testcase.rb', line 68 def waiting_threads @waiting_threads end |
Instance Attribute Details
#console_logger ⇒ Object
The console logger object. See #console_logger=
336 337 338 |
# File 'lib/roby/test/common.rb', line 336 def console_logger @console_logger end |
#original_collections ⇒ Object (readonly)
a [collection, collection_backup] array of the collections saved by #original_collections
35 36 37 |
# File 'lib/roby/test/common.rb', line 35 def original_collections @original_collections end |
#remote_processes ⇒ Object (readonly)
The list of children started using #remote_process
194 195 196 |
# File 'lib/roby/test/common.rb', line 194 def remote_processes @remote_processes end |
#timings ⇒ Object (readonly)
Returns the value of attribute timings.
19 20 21 |
# File 'lib/roby/test/common.rb', line 19 def timings @timings end |
Class Method Details
.assert_any_event_result(positive, negative) ⇒ Object
Tests for events in positive and negative and returns the set of failing events if the assertion has finished. If the set is empty, it means that the assertion finished successfully
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/roby/test/testcase.rb', line 30 def assert_any_event_result(positive, negative) if positive_ev = positive.find { |ev| ev.happened? } return false, "#{positive_ev} happened" end failure = negative.find_all { |ev| ev.happened? } unless failure.empty? return true, "#{failure} happened" end if positive.all? { |ev| ev.unreachable? } return true, "all positive events are unreachable" end nil end |
.check_event_assertions ⇒ Object
This method is inserted in the control thread to implement Assertions#assert_events
48 49 50 51 52 53 54 55 56 57 |
# File 'lib/roby/test/testcase.rb', line 48 def check_event_assertions event_assertions.delete_if do |thread, cv, positive, negative| error, result = assert_any_event_result(positive, negative) if !error.nil? thread[ASSERT_ANY_EVENTS_TLS] = [error, result] cv.broadcast true end end end |
.finalize_event_assertions ⇒ Object
59 60 61 62 63 64 |
# File 'lib/roby/test/testcase.rb', line 59 def finalize_event_assertions check_event_assertions event_assertions.dup.each do |thread, *_| thread.raise ControlQuitError end end |
.interrupt_waiting_threads ⇒ Object
This proc is to be called by Control when it quits. It makes sure that threads which are waiting are interrupted
72 73 74 75 76 77 78 |
# File 'lib/roby/test/testcase.rb', line 72 def interrupt_waiting_threads waiting_threads.dup.each do |task| task.raise ControlQuitError end ensure waiting_threads.clear end |
.sampling(duration, period, *fields) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/roby/test/tools.rb', line 4 def sampling(duration, period, *fields) Test.info "starting sampling #{fields.join(", ")} every #{period}s for #{duration}s" samples = Array.new fields.map! { |n| n.to_sym } if fields.include?(:dt) raise ArgumentError, "dt is reserved by #sampling" end if compute_time = !fields.include?(:t) fields << :t end fields << :dt sample_type = Struct.new(*fields) start = Time.now Roby.condition_variable(true) do |cv, mt| first_sample = nil mt.synchronize do id = Roby::Control.every(period) do result = yield if result if compute_time result << Roby.control.cycle_start end new_sample = sample_type.new(*result) unless samples.empty? new_sample.dt = new_sample.t- samples.last.t end samples << new_sample if samples.last.t - samples.first.t > duration mt.synchronize do cv.broadcast end end end end cv.wait(mt) Roby::Control.remove_periodic_handler(id) end end samples end |
.stats(samples, spec) ⇒ Object
Computes mean and standard deviation about the samples in samples spec describes what to compute:
-
if nothing is specified, we compute the statistics on
v(i - 1) - v(i) -
if spec is ‘rate’, we compute the statistics on (v(i - 1) - v(i)) / (t(i - 1) / t(i))
-
if spec is ‘absolute’, we compute the statistics on v(i)
-
if spec is ‘absolute_rate’, we compute the statistics on v(i) / (t(i - 1) / t(i))
The returned value is a struct with the same fields than the samples. Each element is a Stats object
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/roby/test/tools.rb', line 70 def stats(samples, spec) return if samples.empty? type = samples.first.class spec = spec.inject(Hash.new) do |h, (k, v)| spec[k.to_sym] = v.to_sym spec end spec[:t] = :exclude spec[:dt] = :absolute # Initialize the result value fields = type.members. find_all { |n| spec[n.to_sym] != :exclude }. map { |n| n.to_sym } result = Struct.new(*fields).new fields.each do |name| result[name] = Stat.new(0, 0, 0, 0, nil, nil) end # Compute the deltas if the mode is not absolute last_sample = nil samples = samples.map do |original_sample| sample = original_sample.dup fields.each do |name| next unless value = sample[name] unless spec[name] == :absolute || spec[name] == :absolute_rate if last_sample && last_sample[name] sample[name] -= last_sample[name] else sample[name] = nil next end end end last_sample = original_sample sample end # Compute the rates if needed samples = samples.map do |sample| fields.each do |name| next unless value = sample[name] if spec[name] == :rate || spec[name] == :absolute_rate if sample.dt sample[name] = value / sample.dt else sample[name] = nil next end end end sample end samples.each do |sample| fields.each do |name| next unless value = sample[name] if !result[name].max || value > result[name].max result[name].max = value end if !result[name].min || value < result[name].min result[name].min = value end result[name].total += value result[name].count += 1 end last_sample = sample end result.each do |r| r.mean = Float(r.total) / r.count end samples.each do |sample| fields.each do |name| next unless value = sample[name] result[name].stddev += (value - result[name].mean) ** 2 end end result.each do |r| r.stddev = Math.sqrt(r.stddev / r.count) end result end |
Instance Method Details
#assert_doesnt_timeout(seconds, message = "watchdog #{seconds} failed") ⇒ Object
Checks that the given block returns within seconds seconds
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/roby/test/common.rb', line 308 def assert_doesnt_timeout(seconds, = "watchdog #{seconds} failed") watched_thread = Thread.current watchdog = Thread.new do sleep(seconds) watched_thread.raise FailedTimeout end assert_block() do begin yield true rescue FailedTimeout ensure watchdog.kill watchdog.join end end end |
#assert_marshallable(object) ⇒ Object
327 328 329 330 331 332 333 |
# File 'lib/roby/test/common.rb', line 327 def assert_marshallable(object) begin Marshal.dump(object) true rescue TypeError end end |
#assert_original_error(klass, localized_error_type = LocalizedError) ⇒ Object
292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/roby/test/common.rb', line 292 def assert_original_error(klass, localized_error_type = LocalizedError) old_level = Roby.logger.level Roby.logger.level = Logger::FATAL assert_nothing_raised do begin yield rescue localized_error_type => e assert_respond_to(e, :error) assert_kind_of(klass, e.error) end end ensure Roby.logger.level = old_level end |
#display_event_structure(object, relation, indent = " ") ⇒ Object
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/roby/test/common.rb', line 382 def display_event_structure(object, relation, indent = " ") result = object.to_s object.history.each do |event| result << "#{indent}#{event.time.to_hms} #{event}" end children = object.child_objects(relation) unless children.empty? result << " ->\n" << indent children.each do |child| result << display_event_structure(child, relation, indent + " ") end end result end |
#display_timings! ⇒ Object
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
# File 'lib/roby/test/common.rb', line 340 def display_timings! timings = self.timings.sort_by { |_, t| t } ref = timings[0].last format, header, times = "", [], [] format << "%#{method_name.size}s" header << method_name times << "" timings.each do |name, time| name = name.to_s time = "%.2f" % [time - ref] col_size = [name.size, time.size].max format << " % #{col_size}s" header << name times << time end puts puts format % header puts format % times end |
#new_plan ⇒ Object
Clear the plan and return it
28 29 30 31 |
# File 'lib/roby/test/common.rb', line 28 def new_plan Roby.plan.clear plan end |
#plan ⇒ Object
The plan used by the tests
25 |
# File 'lib/roby/test/common.rb', line 25 def plan; Roby.plan end |
#prepare_plan(options) ⇒ Object
Creates a set of tasks and returns them. Each task is given an unique ‘id’ which allows to recognize it in a failed assertion.
Known options are:
- missions
-
how many mission to create [0]
- discover
-
how many tasks should be discovered [0]
- tasks
-
how many tasks to create outside the plan [0]
- model
-
the task model [Roby::Task]
- plan
-
the plan to apply on [plan]
The return value is [missions, discovered, tasks]
(t1, t2), (t3, t4, t5), (t6, t7) = prepare_plan :missions => 2,
:discover => 3, :tasks => 2
An empty set is omitted
(t1, t2), (t6, t7) = prepare_plan :missions => 2, :tasks => 2
If a set is a singleton, the only object of this singleton is returned
t1, (t6, t7) = prepare_plan :missions => 1, :tasks => 2
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/roby/test/common.rb', line 216 def prepare_plan() = , :missions => 0, :discover => 0, :tasks => 0, :permanent => 0, :model => Roby::Task, :plan => plan missions, permanent, discovered, tasks = [], [], [], [] (1..[:missions]).each do |i| [:plan].insert(t = [:model].new(:id => "mission-#{i}")) missions << t end (1..[:permanent]).each do |i| [:plan].permanent(t = [:model].new(:id => "perm-#{i}")) permanent << t end (1..[:discover]).each do |i| [:plan].discover(t = [:model].new(:id => "discover-#{i}")) discovered << t end (1..[:tasks]).each do |i| tasks << [:model].new(:id => "task-#{i}") end result = [] [missions, permanent, discovered, tasks].each do |set| unless set.empty? set = *set result << set end end if result.size == 1 then result.first else result end end |
#process_events ⇒ Object
Process pending events
187 188 189 190 191 |
# File 'lib/roby/test/common.rb', line 187 def process_events Roby::Control.synchronize do Roby.control.process_events end end |
#remote_process ⇒ Object
Start a new process and saves its PID in #remote_processes. If a block is given, it is called in the new child. #remote_process returns only after this block has returned.
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/roby/test/common.rb', line 254 def remote_process start_r, start_w= IO.pipe quit_r, quit_w = IO.pipe remote_pid = fork do start_r.close yield start_w.write('OK') quit_r.read(2) end start_w.close start_r.read(2) remote_processes << [remote_pid, quit_w] remote_pid ensure start_r.close end |
#restore_collections ⇒ Object
Restors the collections saved by #save_collection to their previous state
45 46 47 48 49 50 51 52 53 54 |
# File 'lib/roby/test/common.rb', line 45 def restore_collections original_collections.each do |col, backup| col.clear if col.kind_of?(Hash) col.merge! backup else backup.each(&col.method(:<<)) end end end |
#save_collection(obj) ⇒ Object
Saves the current state of obj. This state will be restored by #restore_collections. obj must respond to #<< to add new elements (hashes do not work whild arrays or sets do)
40 41 42 |
# File 'lib/roby/test/common.rb', line 40 def save_collection(obj) original_collections << [obj, obj.dup] end |
#setup ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/roby/test/common.rb', line 56 def setup @console_logger ||= false if !defined? Roby::State Roby.app.reset end @original_roby_logger_level = Roby.logger.level @timings = { :start => Time.now } @original_collections = [] Thread.abort_on_exception = true @remote_processes = [] if Test.check_allocation_count GC.start GC.disable end unless DRb.primary_server DRb.start_service 'druby://localhost:0' end if defined? Roby::Planning::Planner Roby::Planning::Planner.last_id = 0 end # Save and restore Control's global arrays save_collection Roby::Control.event_processing save_collection Roby::Control.structure_checks save_collection Roby::Control.at_cycle_end_handlers save_collection Roby::EventGenerator.event_gathering Roby.control.abort_on_exception = true Roby.control.abort_on_application_exception = true Roby.control.abort_on_framework_exception = true save_collection Roby::Propagation.event_ordering save_collection Roby::Propagation.delayed_events save_collection Roby.exception_handlers timings[:setup] = Time.now end |
#stop_remote_processes ⇒ Object
Stop all the remote processes that have been started using #remote_process
274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/roby/test/common.rb', line 274 def stop_remote_processes remote_processes.reverse.each do |pid, quit_w| begin quit_w.write('OK') rescue Errno::EPIPE end begin Process.waitpid(pid) rescue Errno::ECHILD end end remote_processes.clear end |
#teardown ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/roby/test/common.rb', line 116 def teardown timings[:quit] = Time.now teardown_plan timings[:teardown_plan] = Time.now stop_remote_processes DRb.stop_service if DRb.thread restore_collections # Clear all relation graphs in TaskStructure and EventStructure spaces = [] if defined? Roby::TaskStructure spaces << Roby::TaskStructure end if defined? Roby::EventStructure spaces << Roby::EventStructure end spaces.each do |space| space.relations.each do |rel| vertices = rel.enum_for(:each_vertex).to_a unless vertices.empty? Roby.warn " the following vertices are still present in #{rel}: #{vertices.to_a}" vertices.each { |v| v.clear_vertex } end end end Roby::TaskStructure::Hierarchy.interesting_events.clear if defined? Roby::Control Roby.control.abort_on_exception = false Roby.control.abort_on_application_exception = false Roby.control.abort_on_framework_exception = false end if defined? Roby::Log Roby::Log.known_objects.clear end if Test.check_allocation_count require 'utilrb/objectstats' count = ObjectStats.count GC.start remains = ObjectStats.count Roby.warn "#{count} -> #{remains} (#{count - remains})" end timings[:end] = Time.now if display_timings? begin display_timings! rescue Roby.warn $!. end end rescue Exception => e STDERR.puts "failed teardown: #{e.full_message}" ensure while Roby.control.running? Roby.control.quit Roby.control.join rescue nil end Roby.plan.clear Roby.logger.level = @original_roby_logger_level self.console_logger = false end |
#teardown_plan ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/roby/test/common.rb', line 98 def teardown_plan old_gc_roby_logger_level = Roby.logger.level if debug_gc? Roby.logger.level = Logger::DEBUG end if !Roby.control.running? Roby.control.run :detach => true end Roby.control.quit Roby.control.join plan.clear ensure Roby.logger.level = old_gc_roby_logger_level end |
#wait_thread_stopped(thread) ⇒ Object
375 376 377 378 379 380 |
# File 'lib/roby/test/common.rb', line 375 def wait_thread_stopped(thread) while !thread.stop? sleep(0.1) raise "#{thread} died" unless thread.alive? end end |