Class: Roby::Test::TestCase

Inherits:
TestCase
  • Object
show all
Includes:
Roby::Test, Assertions
Defined in:
lib/roby/test/testcase.rb

Overview

This is the base class for running tests which uses a Roby control loop (i.e. plan execution).

Because configuration and planning can be robot-specific, parts of the tests can also be splitted into generic parts and specific parts. The TestCase.robot statement allows to specify that a given test case is specific to a given robot, in which case it is ran only if the call to scripts/test specified a robot which matches (i.e. same name and type).

Finally, two other mode of operation control the way tests are ran

simulation

if the --sim flag is given to scripts/test, the tests are ran under simulation. Otherwise, they are run in live mode (see Roby::Application for a description of simulation and live modes). It is possible to constrain that a given test method is run only in simulation or live mode with the TestCase.sim and TestCase.nosim statements:

sim :sim_only
def test_sim_only
end

nosim :live_only
def test_live_only
end
interactive

Sometime, it is hard to actually assess the quality of processing results automatically. In these cases, it is possible to show the user the result of data processing, and then ask if the result is valid by using the #user_validation method. Nonetheless, the tests can be ran in automatic mode, in which the assertions which require user validation are simply skipped. The --interactive or -i flags of scripts/test specify that user interaction is possible.

Constant Summary collapse

@@first_time =
true

Constants included from Roby::Test

ASSERT_ANY_EVENTS_TLS, BASE_PORT, DISCOVERY_SERVER, LOCAL_PORT, LOCAL_SERVER, REMOTE_PORT, REMOTE_SERVER, Unit

Constants included from Roby

ROBY_LIB_DIR, ROBY_ROOT_DIR, RX_IN_FRAMEWORK, VERSION

Class Attribute Summary collapse

Attributes included from Roby::Test

#console_logger, #original_collections, #remote_processes, #timings

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Assertions

#assert_any_event, #assert_relative_error, #assert_same_position, #assert_succeeds, #control_priority

Methods included from Roby::Test

assert_any_event_result, #assert_doesnt_timeout, #assert_marshallable, #assert_original_error, check_event_assertions, #display_event_structure, #display_timings!, finalize_event_assertions, interrupt_waiting_threads, #new_plan, #plan, #prepare_plan, #process_events, #remote_process, #restore_collections, sampling, #save_collection, stats, #stop_remote_processes, #teardown_plan, #wait_thread_stopped

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

.app_setupObject (readonly)

Returns the value of attribute app_setup.



248
249
250
# File 'lib/roby/test/testcase.rb', line 248

def app_setup
  @app_setup
end

Class Method Details

.apply_robot_setupObject

Loads the configuration as specified by TestCase.robot



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/roby/test/testcase.rb', line 263

def self.apply_robot_setup
    app = Roby.app
    if @@first_time
 # Make sure the log directory is empty
 if File.exists?(app.log_dir)
      if !Dir.new(app.log_dir).empty?
  if !STDIN.ask("#{app.log_dir} still exists and must be cleaned before starting. Proceed ? [N,y]", false)
        raise "user abort"
  end
      end
      FileUtils.rm_rf app.log_dir
 end
 @@first_time = false
    end

    name, kind, block = app_setup
    # Silently ignore the test suites which use a different robot
    if app.robot_name && 
 (app.robot_name != name || app.robot_type != kind)
 return
    end
    app.robot name, kind
    app.reset
    app.single
    app.setup
    if block
 block.call
    end

    app.control.delete('executive')

    yield if block_given?
end

.nosim(*names) ⇒ Object

Do not run test_name inside a simulation environment test_name is the name of the method without test_. For instance:

nosim :init
def test_init
end

See also TestCase.sim



368
369
370
371
372
373
# File 'lib/roby/test/testcase.rb', line 368

def self.nosim(*names)
    names.each do |test_name|
 config = (methods_config[test_name.to_s] ||= Hash.new)
 config[:mode] = :nosim
    end
end

.robot(name, kind = name, &block) ⇒ Object

Sets the robot configuration for this test case. If a block is given, it is called between the time the robot configuration is loaded and the time the test methods are started. It can therefore be used to change the robot configuration for the need of this particular test case



256
257
258
259
# File 'lib/roby/test/testcase.rb', line 256

def self.robot(name, kind = name, &block)
    @app_setup = [name, kind, block]
    apply_robot_setup
end

.sim(*names) ⇒ Object

Run test_name only inside a simulation environment test_name is the name of the method without test_. For instance:

sim :init
def test_init
end

See also TestCase.nosim



383
384
385
386
387
388
# File 'lib/roby/test/testcase.rb', line 383

def self.sim(*names)
    names.each do |test_name|
 config = (methods_config[test_name.to_s] ||= Hash.new)
 config[:mode] = :sim
    end
end

.suiteObject

:nodoc:



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/roby/test/testcase.rb', line 390

def self.suite # :nodoc:
    method_names = public_instance_methods(true)
    tests = method_names.delete_if {|method_name| method_name !~ /^(dataset|test)./}
    suite = Test::Unit::TestSuite.new(name)
    tests.sort.each do |test|
 catch(:invalid_test) do
      suite << new(test)
 end
    end
    if (suite.empty?)
 catch(:invalid_test) do
      suite << new("default_test")
 end
    end
    return suite
end

Instance Method Details

#add_error(*args, &block) ⇒ Object

:nodoc:



456
457
458
459
# File 'lib/roby/test/testcase.rb', line 456

def add_error(*args, &block) # :nodoc:
    @failed_test = true
    super
end

#add_failure(*args, &block) ⇒ Object

:nodoc:



460
461
462
463
# File 'lib/roby/test/testcase.rb', line 460

def add_failure(*args, &block) # :nodoc:
    @failed_test = true
    super
end

#automatic_testing?Boolean

Returns true if user interaction is to be disabled during this test

Returns:

  • (Boolean)


317
318
319
# File 'lib/roby/test/testcase.rb', line 317

def automatic_testing?
    Roby.app.automatic_testing?
end

#dataset_file_path(dataset_name, file) ⇒ Object

Returns the full path of the file name into which the log file file should be saved to be referred to as the dataset_name dataset



476
477
478
479
480
481
482
483
484
485
# File 'lib/roby/test/testcase.rb', line 476

def dataset_file_path(dataset_name, file)
    path = File.join(datasets_dir, dataset_name, file)
    if !File.file?(path)
 raise "#{path} does not exist"
    end

    path
rescue
    flunk("dataset #{dataset_name} has not been generated: #{$!.message}")
end

#dataset_prefixObject

The directory into which the datasets generated by the current testcase are to be saved.



471
472
473
# File 'lib/roby/test/testcase.rb', line 471

def dataset_prefix
    "#{Roby.app.robot_name}-#{self.class.name.gsub('TC_', '').underscore}/, '')}"
end

#datasets_dirObject

The directory in which datasets are to be saved



466
467
468
# File 'lib/roby/test/testcase.rb', line 466

def datasets_dir
    "#{APP_DIR}/test/datasets" 
end

#method_configObject

:nodoc:



312
313
314
# File 'lib/roby/test/testcase.rb', line 312

def method_config # :nodoc:
    self.class.case_config.merge(self.class.methods_config[method_name] || Hash.new)
end

#plannerObject

Returns a fresh MainPlanner object for the current plan



298
299
300
# File 'lib/roby/test/testcase.rb', line 298

def planner
    MainPlanner.new(plan)
end

#progress(value, max = nil) ⇒ Object

Progress report for the curren test. If max is given, then value is assumed to be between 0 and max. Otherwise, value is a float value between 0 and 1 and is displayed as a percentage.



324
325
326
327
328
329
330
331
# File 'lib/roby/test/testcase.rb', line 324

def progress(value, max = nil)
    if max
 print "\r#{@method_name} progress: #{value}/#{max}"
    else
 print "\r#{@method_name} progress: #{"%.2f %%" % [value * 100]}"
    end
    STDOUT.flush
end

#run(result) ⇒ Object

:nodoc:



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/roby/test/testcase.rb', line 407

def run(result) # :nodoc:
    Roby::Test.waiting_threads.clear

    self.class.apply_robot_setup do
 yield if block_given?

 case method_config[:mode]
 when :nosim
      return if Roby.app.simulation?
 when :sim
      return unless Roby.app.simulation?
 end

 @failed_test = false
 begin
      Roby.app.run do
  super
      end
 rescue Exception => e
      if @_result
  add_error(e)
      else
  raise
      end
 end

 keep_logdir = @failed_test || Roby.app.testing_keep_logs?
 save_logdir = (@failed_test && automatic_testing?) ||  Roby.app.testing_keep_logs?
 if save_logdir
      subdir = @failed_test ? 'failures' : 'results'
      basedir = File.join(APP_DIR, 'test', subdir)
      dirname = Roby::Application.unique_dirname(basedir, dataset_prefix)

      if Roby.app.testing_overwrites_logs?
  dirname.gsub! /\.\d+$/, ''
  FileUtils.rm_rf dirname
      end

      FileUtils.mv Roby.app.log_dir, dirname
 end
 if !keep_logdir
      FileUtils.rm_rf Roby.app.log_dir
 end
    end

rescue Exception
    puts "testcase #{method_name} teardown failed with\n#{$!.full_message}"
end

#sampling(*args, &block) ⇒ Object



513
# File 'lib/roby/test/testcase.rb', line 513

def sampling(*args, &block); Test.sampling(*args, &block) end

#save_dataset(files = nil, suffix = '') ⇒ Object

Saves file, which is taken in the log directory, in the test/datasets directory. The data set is saved as ‘robot-testname-testmethod-suffix’



490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/roby/test/testcase.rb', line 490

def save_dataset(files = nil, suffix = '')
    destname = dataset_prefix
    destname << "-#{suffix}" unless suffix.empty?

    dir = File.join(datasets_dir, destname)
    if File.exists?(dir)
 relative_dir = dir.gsub(/^#{Regexp.quote(APP_DIR)}/, '')
 unless STDIN.ask("\r#{relative_dir} already exists. Delete ? [N,y]", false)
      raise "user abort"
 end
 FileUtils.rm_rf dir
    end
    FileUtils.mkdir_p(dir)

    files ||= Dir.entries(Roby.app.log_dir).find_all do |path|
 File.file? File.join(Roby.app.log_dir, path)
    end

    [*files].each do |path|
 FileUtils.mv "#{Roby.app.log_dir}/#{path}", dir
    end
end

#setupObject

:nodoc:



302
303
304
305
# File 'lib/roby/test/testcase.rb', line 302

def setup # :nodoc:
    super
    Roby::Test.waiting_threads << Thread.current
end

#stats(*args, &block) ⇒ Object



514
# File 'lib/roby/test/testcase.rb', line 514

def stats(*args, &block); Test.stats(*args, &block) end

#teardownObject

:nodoc:



307
308
309
310
# File 'lib/roby/test/testcase.rb', line 307

def teardown # :nodoc:
    Roby::Test.waiting_threads.delete(Thread.current)
    super
end

#user_interactionObject



333
334
335
336
337
338
339
340
341
342
343
# File 'lib/roby/test/testcase.rb', line 333

def user_interaction
    return unless automatic_testing?

    test_result = catch(:validation_result) do
 yield 
 return
    end
    if test_result
 flunk(*test_result)
    end
end

#user_validation(msg) ⇒ Object

Ask for user validation. The method first yields, and then asks the user if the showed dataset is nominal. If the tests are ran in automated mode (#automatic_testing? returns true), it does nothing.



349
350
351
352
353
354
355
356
357
358
# File 'lib/roby/test/testcase.rb', line 349

def user_validation(msg)
    return if automatic_testing?

    assert_block(msg) do
 STDOUT.puts "Now validating #{msg}"
 yield

 STDIN.ask("\rIs the result OK ? [N,y]", false)
    end
end