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/minitest_plugin.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/droby_log_helpers.rb,
lib/roby/test/minitest_reporter.rb,
lib/roby/test/robot_test_helpers.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, DRobyLogHelpers, DSL, ExpectExecution, MinitestHelpers, MinitestPlugin, RobotTestHelpers, RobyAppHelpers, RunPlanners, Self, TeardownPlans Classes: Assertion, EmptyTask, Error, EventReporter, ExecutionExpectations, Goto2D, MinitestReporter, Spec, Stat, TestCase, ValidateStateMachine
Constant Summary collapse
- BASE_PORT =
21_000
- 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 ExpectExecution
ExpectExecution::SETUP_METHODS
Constants included from Roby
BIN_DIR, Conf, FORMAT_EXCEPTION_RECURSION_GUARD_KEY, 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, Void, VoidClass
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
#default_teardown_poll, #registered_plans
Attributes included from ExpectExecution
#expect_execution_default_timeout
Class Method Summary collapse
- .register_spec_type(spec_type) ⇒ Object
- .sampling(engine, duration, period, *fields) ⇒ Object
-
.self_test=(flag) ⇒ Object
Set #self_test?.
-
.self_test? ⇒ Boolean
Whether we are running Roby’s own test suite or not.
-
.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)).
Instance Method Summary collapse
- #create_transaction ⇒ Object
- #deprecated_feature ⇒ 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
- #make_random_plan(plan = Plan.new, tasks: 5, free_events: 5, task_relations: 5, event_relations: 5) ⇒ Object
-
#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, #initialize, #register_plan, #teardown_clear, #teardown_forced_killall, #teardown_killall, #teardown_registered_plans, #teardown_warn
Methods included from ExpectExecution
#add_expectations, #execute, #execute_one_cycle, #expect_execution, #reset_current_expect_execution, #setup_current_expect_execution
Methods included from Roby
RelationSpace, app, color, control, control=, disable_colors, display_exception, disposable, 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, find_in_path, flatten_exception, format_backtrace, format_exception, format_exception_recursion_guard, format_one_exception, format_time, from, from_conf, from_state, inside_control?, log_all_threads_backtraces, log_backtrace, log_callers, log_error, log_exception, log_exception_with_backtrace, log_level_enabled?, log_pp, make_backtrace_relative_to_app_dir, monotonic_time, null_disposable, on_exception, once, outside_control?, plan, poll_state_events, pretty_print_backtrace, sanitize_keywords, sanitize_keywords_to_array, sanitize_keywords_to_hash, scheduler, scheduler=, wait_one_cycle, wait_until, warn_deprecated, which
Instance Attribute Details
#app ⇒ Object (readonly)
Returns the value of attribute app.
136 137 138 |
# File 'lib/roby/test/common.rb', line 136 def app @app end |
#control ⇒ Object (readonly)
The decision control component used by the tests
69 70 71 |
# File 'lib/roby/test/common.rb', line 69 def control @control end |
#original_collections ⇒ Object (readonly)
a [collection, collection_backup] array of the collections saved by #original_collections
112 113 114 |
# File 'lib/roby/test/common.rb', line 112 def original_collections @original_collections end |
#plan ⇒ Object (readonly)
The plan used by the tests
67 68 69 |
# File 'lib/roby/test/common.rb', line 67 def plan @plan end |
#remote_processes ⇒ Object (readonly)
The list of children started using #remote_process
322 323 324 |
# File 'lib/roby/test/common.rb', line 322 def remote_processes @remote_processes end |
Class Method Details
.register_spec_type(spec_type) ⇒ Object
245 246 247 248 249 250 |
# File 'lib/roby/test/spec.rb', line 245 def self.register_spec_type(spec_type) Minitest::Spec.register_spec_type spec_type do |desc, roby_spec: nil| (roby_spec == true || (roby_spec.nil? && !Roby::Test.self_test?)) && yield(desc) end end |
.sampling(engine, duration, period, *fields) ⇒ Object
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 56 57 |
# File 'lib/roby/test/tools.rb', line 6 def sampling(engine, duration, period, *fields) Test.info "starting sampling #{fields.join(', ')} every #{period}s for #{duration}s" samples = [] fields.map!(&: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 until timeout cv.wait(mt) end engine.remove_periodic_handler(id) end end samples end |
.self_test=(flag) ⇒ Object
Set #self_test?
82 83 84 |
# File 'lib/roby/test/common.rb', line 82 def self.self_test=(flag) @self_test = flag end |
.self_test? ⇒ Boolean
Whether we are running Roby’s own test suite or not
This is used for instance in test/spec to avoid using the Spec classes designed for ‘roby test` when running Roby’s own test suite
77 78 79 |
# File 'lib/roby/test/common.rb', line 77 def self.self_test? @self_test 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
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 161 162 163 164 165 166 167 |
# File 'lib/roby/test/tools.rb', line 76 def stats(samples, spec) return if samples.empty? type = samples.first.class spec = spec.inject({}) 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(&: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
96 97 98 99 100 |
# File 'lib/roby/test/common.rb', line 96 def create_transaction t = Roby::Transaction.new(plan) @transactions << t t end |
#deprecated_feature ⇒ Object
102 103 104 105 106 107 108 |
# File 'lib/roby/test/common.rb', line 102 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 |
#execution_engine ⇒ Object
86 87 88 |
# File 'lib/roby/test/common.rb', line 86 def execution_engine plan.execution_engine if plan&.executable? end |
#flexmock_call_original(object, method, *args, &block) ⇒ Object
Use to call the original method on a partial mock
302 303 304 305 |
# File 'lib/roby/test/common.rb', line 302 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
308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/roby/test/common.rb', line 308 def flexmock_invoke_original(object, method, *args, &block) if FlexMock::VERSION >= "3.0.0" object.instance_variable_get(:@flexmock_proxy) .proxy.flexmock_invoke_original(method, args, {}, block) elsif FlexMock::VERSION >= "2.4.0" object.instance_variable_get(:@flexmock_proxy) .proxy.flexmock_invoke_original(method, args, block) else object.instance_variable_get(:@flexmock_proxy) .proxy.flexmock_invoke_original(method, *args, &block) end end |
#inhibit_fatal_messages(&block) ⇒ Object
use Roby::Test::Assertions#capture_log instead
167 168 169 170 |
# File 'lib/roby/test/common.rb', line 167 def (&block) Roby.warn_deprecated "##{__method__} is deprecated, use #capture_log instead" with_log_level(Roby, Logger::FATAL, &block) end |
#make_random_plan(plan = Plan.new, tasks: 5, free_events: 5, task_relations: 5, event_relations: 5) ⇒ Object
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/roby/test/common.rb', line 388 def make_random_plan(plan = Plan.new, tasks: 5, free_events: 5, task_relations: 5, event_relations: 5) tasks = (0...tasks).map do plan.add(t = Roby::Task.new) t end free_events = (0...free_events).map do plan.add(e = Roby::EventGenerator.new) e end events = (free_events + plan.task_events.to_a) task_relations.times do a = rand(tasks.size) b = rand(tasks.size) loop do begin tasks[a].depends_on tasks[b] break rescue Exception b = rand(tasks.size) end end end event_relations.times do a = rand(events.size) b = rand(events.size) loop do begin events[a].forward_to events[b] break rescue Exception b = rand(events.size) end end end plan end |
#new_plan ⇒ Object
Clear the plan and return it
91 92 93 94 |
# File 'lib/roby/test/common.rb', line 91 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
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 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
# File 'lib/roby/test/common.rb', line 344 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 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
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 276 277 278 279 280 |
# File 'lib/roby/test/common.rb', line 240 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 = [] registered_plans.each do |p| engine = p.execution_engine loop do engine.start_new_cycle errors = begin current_scheduler_state = engine.scheduler.enabled? unless 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({}) caller_block = nil break unless join_all_waiting_work && engine.has_waiting_work? end 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
287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/roby/test/common.rb', line 287 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 until 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.
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 |
# File 'lib/roby/test/common.rb', line 430 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
183 184 185 186 187 188 189 190 191 |
# File 'lib/roby/test/common.rb', line 183 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
122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/roby/test/common.rb', line 122 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)
117 118 119 |
# File 'lib/roby/test/common.rb', line 117 def save_collection(obj) original_collections << [obj, obj.dup] end |
#set_log_level(log_object, level) ⇒ Object
use Roby::Test::Assertions#capture_log instead
173 174 175 176 177 178 179 180 |
# File 'lib/roby/test/common.rb', line 173 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
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 |
# File 'lib/roby/test/common.rb', line 138 def setup @app = Roby.app @app.development_mode = false Roby.app.reload_config @log_levels = {} @transactions = [] @plan ||= Roby.app.plan 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 end |
#stop_remote_processes ⇒ Object
Stop all the remote processes that have been started using #remote_process
454 455 456 457 458 459 460 461 462 463 464 465 466 |
# File 'lib/roby/test/common.rb', line 454 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
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 235 236 237 |
# File 'lib/roby/test/common.rb', line 209 def teardown Timecop.return @transactions.each do |trsc| unless trsc.finalized? trsc.discard_transaction end end teardown_registered_plans # 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
194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/roby/test/common.rb', line 194 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 |