Module: Roby::Test
- Extended by:
- Logger::Forward, Logger::Hierarchy
- Includes:
- Roby, TeardownPlans
- Defined in:
- lib/roby/test/common.rb,
lib/roby/test/dsl.rb,
lib/roby/test/self.rb,
lib/roby/test/spec.rb,
lib/roby/test/error.rb,
lib/roby/test/tools.rb,
lib/roby/test/testcase.rb,
lib/roby/test/assertion.rb,
lib/roby/test/assertions.rb,
lib/roby/test/tasks/goto.rb,
lib/roby/test/run_planners.rb,
lib/roby/test/aruba_minitest.rb,
lib/roby/test/event_reporter.rb,
lib/roby/test/teardown_plans.rb,
lib/roby/test/expect_execution.rb,
lib/roby/test/minitest_helpers.rb,
lib/roby/test/roby_app_helpers.rb,
lib/roby/test/tasks/empty_task.rb,
lib/roby/test/execution_expectations.rb,
lib/roby/test/validate_state_machine.rb
Overview
This module is defining common support for tests that need the Roby infrastructure
It assumes that the tests are started using roby’s test command. Tests using this module can NOT be started with only e.g. testrb.
Defined Under Namespace
Modules: ArubaMinitest, Assertions, DSL, ExpectExecution, MinitestHelpers, RobyAppHelpers, RunPlanners, Self, TeardownPlans Classes: Assertion, EmptyTask, Error, EventReporter, ExecutionExpectations, Goto2D, Spec, Stat, TestCase, ValidateStateMachine
Constant Summary collapse
- BASE_PORT =
21000- 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}"
Constants included from Roby
BIN_DIR, Conf, LOG_SYMBOLIC_TO_NUMERIC, NullTask, ROBY_LIB_DIR, ROBY_ROOT_DIR, RX_IN_FRAMEWORK, RX_IN_METARUBY, RX_IN_UTILRB, RX_REQUIRE, SelfTest, State, TaskService, VERSION, VirtualTask
Instance Attribute Summary collapse
-
#app ⇒ Object
readonly
Returns the value of attribute app.
-
#control ⇒ Object
readonly
The decision control component used by the tests.
-
#original_collections ⇒ Object
readonly
a [collection, collection_backup] array of the collections saved by #original_collections.
-
#plan ⇒ Object
readonly
The plan used by the tests.
-
#remote_processes ⇒ Object
readonly
The list of children started using #remote_process.
Attributes included from TeardownPlans
Class Method Summary collapse
- .sampling(engine, 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
- #create_transaction ⇒ Object
- #deprecated_feature ⇒ Object
- #execute(&block) ⇒ Object
- #execution_engine ⇒ Object
-
#flexmock_call_original(object, method, *args, &block) ⇒ Object
Use to call the original method on a partial mock.
-
#flexmock_invoke_original(object, method, *args, &block) ⇒ Object
Use to call the original method on a partial mock.
-
#inhibit_fatal_messages(&block) ⇒ Object
deprecated
Deprecated.
use Assertions#capture_log instead
-
#new_plan ⇒ Object
Clear the plan and return it.
-
#prepare_plan(options) ⇒ Object
Creates a set of tasks and returns them.
-
#process_events(timeout: 2, enable_scheduler: nil, join_all_waiting_work: true, raise_errors: true, garbage_collect_pass: true, &caller_block) ⇒ Object
Process pending events.
-
#process_events_until(timeout: 5, join_all_waiting_work: false, **options) ⇒ Object
Repeatedly process events until a condition is met.
-
#remote_process ⇒ Object
Start a new process and saves its PID in #remote_processes.
-
#reset_log_levels(warn_deprecated: true) ⇒ Object
deprecated
Deprecated.
use Assertions#capture_log instead
-
#restore_collections ⇒ Object
Restors the collections saved by #save_collection to their previous state.
-
#save_collection(obj) ⇒ Object
Saves the current state of
obj. -
#set_log_level(log_object, level) ⇒ Object
deprecated
Deprecated.
use Assertions#capture_log instead
- #setup ⇒ Object
-
#stop_remote_processes ⇒ Object
Stop all the remote processes that have been started using #remote_process.
- #teardown ⇒ Object
-
#with_log_level(log_object, level) ⇒ Object
deprecated
Deprecated.
use Assertions#capture_log instead
Methods included from TeardownPlans
#clear_registered_plans, #register_plan, #teardown_registered_plans
Methods included from Roby
RelationSpace, app, color, control, disable_colors, display_exception, do_display_exception, do_display_exception_formatted, do_display_exception_raw, each_cycle, enable_colors, enable_colors_if_available, engine, error_deprecated, every, execute, execution_engine, filter_backtrace, flatten_exception, format_backtrace, format_exception, format_one_exception, format_time, from, from_conf, from_state, inside_control?, log_backtrace, log_error, log_exception, log_exception_with_backtrace, log_level_enabled?, log_pp, on_exception, once, outside_control?, plan, poll_state_events, pretty_print_backtrace, scheduler, scheduler=, wait_one_cycle, wait_until, warn_deprecated
Instance Attribute Details
#app ⇒ Object (readonly)
Returns the value of attribute app.
122 123 124 |
# File 'lib/roby/test/common.rb', line 122 def app @app end |
#control ⇒ Object (readonly)
The decision control component used by the tests
66 67 68 |
# File 'lib/roby/test/common.rb', line 66 def control @control end |
#original_collections ⇒ Object (readonly)
a [collection, collection_backup] array of the collections saved by #original_collections
98 99 100 |
# File 'lib/roby/test/common.rb', line 98 def original_collections @original_collections end |
#plan ⇒ Object (readonly)
The plan used by the tests
64 65 66 |
# File 'lib/roby/test/common.rb', line 64 def plan @plan end |
#remote_processes ⇒ Object (readonly)
The list of children started using #remote_process
308 309 310 |
# File 'lib/roby/test/common.rb', line 308 def remote_processes @remote_processes end |
Class Method Details
.sampling(engine, 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 52 53 54 55 |
# File 'lib/roby/test/tools.rb', line 4 def sampling(engine, 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 timeout = false id = engine.every(period) do result = yield if result if compute_time result << engine.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 timeout = true cv.broadcast end end end end while !timeout cv.wait(mt) end engine.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
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 157 158 159 160 |
# File 'lib/roby/test/tools.rb', line 74 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
#create_transaction ⇒ Object
82 83 84 85 86 |
# File 'lib/roby/test/common.rb', line 82 def create_transaction t = Roby::Transaction.new(plan) @transactions << t t end |
#deprecated_feature ⇒ Object
88 89 90 91 92 93 94 |
# File 'lib/roby/test/common.rb', line 88 def deprecated_feature Roby.enable_deprecation_warnings = false flexmock(Roby).should_receive(:warn_deprecated).at_least.once yield ensure Roby.enable_deprecation_warnings = true end |
#execute(&block) ⇒ Object
72 73 74 |
# File 'lib/roby/test/common.rb', line 72 def execute(&block) execution_engine.execute(&block) end |
#execution_engine ⇒ Object
68 69 70 |
# File 'lib/roby/test/common.rb', line 68 def execution_engine plan.execution_engine if plan && plan.executable? end |
#flexmock_call_original(object, method, *args, &block) ⇒ Object
Use to call the original method on a partial mock
297 298 299 300 |
# File 'lib/roby/test/common.rb', line 297 def flexmock_call_original(object, method, *args, &block) Test.warn "#flexmock_call_original is deprecated, use #flexmock_invoke_original instead" flexmock_invoke_original(object, method, *args, &block) end |
#flexmock_invoke_original(object, method, *args, &block) ⇒ Object
Use to call the original method on a partial mock
303 304 305 |
# File 'lib/roby/test/common.rb', line 303 def flexmock_invoke_original(object, method, *args, &block) object.instance_variable_get(:@flexmock_proxy).proxy.flexmock_invoke_original(method, args, &block) end |
#inhibit_fatal_messages(&block) ⇒ Object
use Roby::Test::Assertions#capture_log instead
156 157 158 159 |
# File 'lib/roby/test/common.rb', line 156 def (&block) Roby.warn_deprecated "##{__method__} is deprecated, use #capture_log instead" with_log_level(Roby, Logger::FATAL, &block) end |
#new_plan ⇒ Object
Clear the plan and return it
77 78 79 80 |
# File 'lib/roby/test/common.rb', line 77 def new_plan plan.clear 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
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/roby/test/common.rb', line 330 def prepare_plan() = , missions: 0, add: 0, discover: 0, tasks: 0, permanent: 0, model: Roby::Task, plan: plan missions, permanent, added, tasks = [], [], [], [] (1..[:missions]).each do |i| [:plan].add_mission_task(t = [:model].new(id: "mission-#{i}")) missions << t end (1..[:permanent]).each do |i| [:plan].add_permanent_task(t = [:model].new(id: "perm-#{i}")) permanent << t end (1..([:discover] + [:add])).each do |i| [:plan].add(t = [:model].new(id: "discover-#{i}")) added << t end (1..[:tasks]).each do |i| tasks << [:model].new(id: "task-#{i}") end result = [] [missions, permanent, added, tasks].each do |set| unless set.empty? result << set end end result = result.map do |set| if set.size == 1 then set.first else set end end if result.size == 1 return result.first end return *result end |
#process_events(timeout: 2, enable_scheduler: nil, join_all_waiting_work: true, raise_errors: true, garbage_collect_pass: true, &caller_block) ⇒ Object
Process pending events
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/roby/test/common.rb', line 237 def process_events(timeout: 2, enable_scheduler: nil, join_all_waiting_work: true, raise_errors: true, garbage_collect_pass: true, &caller_block) Roby.warn_deprecated "Test#process_events is deprecated, use #expect_execution instead" exceptions = Array.new registered_plans.each do |p| engine = p.execution_engine begin engine.start_new_cycle errors = begin current_scheduler_state = engine.scheduler.enabled? if !enable_scheduler.nil? engine.scheduler.enabled = enable_scheduler end engine.process_events(garbage_collect_pass: garbage_collect_pass, &caller_block) ensure engine.scheduler.enabled = current_scheduler_state end if join_all_waiting_work engine.join_all_waiting_work(timeout: timeout) end exceptions.concat(errors.exceptions) engine.cycle_end(Hash.new) caller_block = nil end while engine.has_waiting_work? && join_all_waiting_work end if raise_errors && !exceptions.empty? if exceptions.size == 1 e = exceptions.first raise e.exception else raise SynchronousEventProcessingMultipleErrors.new(exceptions.map(&:exception)) end end end |
#process_events_until(timeout: 5, join_all_waiting_work: false, **options) ⇒ Object
Repeatedly process events until a condition is met
282 283 284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/roby/test/common.rb', line 282 def process_events_until(timeout: 5, join_all_waiting_work: false, **) Roby.warn_deprecated "Test#process_events_until is deprecated, use #expect_execution.to { achieve { ... } } instead" start = Time.now while !yield now = Time.now remaining = timeout - (now - start) if remaining < 0 flunk("failed to reach condition #{proc} within #{timeout} seconds") end process_events(timeout: remaining, join_all_waiting_work: join_all_waiting_work, **) sleep 0.01 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.
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/roby/test/common.rb', line 375 def remote_process start_r, start_w= IO.pipe quit_r, quit_w = IO.pipe remote_pid = fork do begin start_r.close yield rescue Exception => e puts e. end start_w.write('OK') quit_r.read(2) end start_w.close result = start_r.read(2) remote_processes << [remote_pid, quit_w] remote_pid ensure # start_r.close end |
#reset_log_levels(warn_deprecated: true) ⇒ Object
use Roby::Test::Assertions#capture_log instead
172 173 174 175 176 177 178 179 180 |
# File 'lib/roby/test/common.rb', line 172 def reset_log_levels(warn_deprecated: true) if warn_deprecated Roby.warn_deprecated "##{__method__} is deprecated, use #capture_log instead" end @log_levels.each do |log_object, level| log_object.level = level end @log_levels.clear end |
#restore_collections ⇒ Object
Restors the collections saved by #save_collection to their previous state
108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/roby/test/common.rb', line 108 def restore_collections original_collections.each do |col, backup| col.clear if col.kind_of?(Hash) col.merge! backup else backup.each do |obj| col << obj end end end original_collections.clear 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)
103 104 105 |
# File 'lib/roby/test/common.rb', line 103 def save_collection(obj) original_collections << [obj, obj.dup] end |
#set_log_level(log_object, level) ⇒ Object
use Roby::Test::Assertions#capture_log instead
162 163 164 165 166 167 168 169 |
# File 'lib/roby/test/common.rb', line 162 def set_log_level(log_object, level) Roby.warn_deprecated "#set_log_level is deprecated, use #capture_log instead" if log_object.respond_to?(:logger) log_object = log_object.logger end @log_levels[log_object] ||= log_object.level log_object.level = level end |
#setup ⇒ Object
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 |
# File 'lib/roby/test/common.rb', line 124 def setup @app = Roby.app @app.development_mode = false Roby.app.reload_config @log_levels = Hash.new @transactions = Array.new if !@plan @plan = Roby.app.plan end register_plan(@plan) super @console_logger ||= false @event_logger ||= false @original_roby_logger_level = Roby.logger.level @original_collections = [] Thread.abort_on_exception = false @remote_processes = [] Roby.app.log_server = false plan.execution_engine.gc_warning = false @watched_events = nil @handler_ids = Array.new end |
#stop_remote_processes ⇒ Object
Stop all the remote processes that have been started using #remote_process
400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/roby/test/common.rb', line 400 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
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/roby/test/common.rb', line 199 def teardown Timecop.return @transactions.each do |trsc| if !trsc.finalized? trsc.discard_transaction end end teardown_registered_plans if @handler_ids && execution_engine @handler_ids.each do |handler_id| execution_engine.remove_propagation_handler(handler_id) end end # Plan teardown would have disconnected the peers already stop_remote_processes restore_collections if defined? Roby::Application Roby.app.abort_on_exception = false Roby.app.abort_on_application_exception = true end super ensure reset_log_levels(warn_deprecated: false) clear_registered_plans if @original_roby_logger_level Roby.logger.level = @original_roby_logger_level end end |
#with_log_level(log_object, level) ⇒ Object
use Roby::Test::Assertions#capture_log instead
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/roby/test/common.rb', line 183 def with_log_level(log_object, level) Roby.warn_deprecated "##{__method__} is deprecated, use #capture_log instead" if log_object.respond_to?(:logger) log_object = log_object.logger end current_level = log_object.level log_object.level = level yield ensure if current_level log_object.level = current_level end end |