Understanding rostest

rostest is the ROS node integration test framework of ROS.

The function rostestmain() is called which provides the command line interface logic. The function creates the command line parser, configures and starts logging,

Understanding rostest's command line interface

rostest functionality can be used via a script which can be controlled via a command line interface. This way to use rostest is most suitable for test development and test debugging. If tests have been implemented, debugged and potentially tested itself rostest is "called" via catkin build integration instead.

The command line interface of rostest looks as follows (rostest -h):

Usage: rostest [options] [package] <filename>

Options:
  -h, --help            show this help message and exit
  -t, --text            Run with stdout output instead of XML output
  --pkgdir=PKG_DIR      package dir
  --package=PACKAGE     package
  --results-filename=RESULTS_FILENAME
                        results_filename
  --results-base-dir=RESULTS_BASE_DIR
                        The base directory of the test results. The test
                        result file is created in a subfolder name PKG_DIR.
  -r, --reuse-master    Connect to an existing ROS master instead of spawning
                        a new ROS master on a custom port
  -c, --clear           Clear all parameters when connecting to an existing
                        ROS master (only works with --reuse-master)

When the command line interface of rostest (scripts/rostest) is invoced the function rostestmain() (src/rostest/init.py) is called which calls rostestmain() (src/rostest/rostest_main.py). rostestmain() handles the command line parsing and resolves the command line options as launch arguments args = roslaunch.rlutil.resolve_launch_arguments(args). The logging is configured including registering the info and error logger to roslaunch's printlog handlers roslaunch.core.add_printlog_handler(logger.info) and roslaunch.core.add_printerrlog_handler(logger.error). If no package directory is given with option --pkgdir (default) and no package is given with option --package (default) the package directory and package is determined from the test file set with command line argument <filename>. Otherwise if --pkgdir and --package are set they are considered.

# compute some common names we'll be using to generate test names and files
test_file = args[0]
if options.pkg_dir and options.package:  
    # rosbuild2: the build system knows what package and directory, so let it tell us,
    # instead of shelling back out to rospack
    pkg_dir, pkg = options.pkg_dir, options.package
else:
    pkg = rospkg.get_package_name(test_file)
    r = rospkg.RosPack()
    pkg_dir = r.get_path(pkg)

If no result filename is given with option --results-filename (default) it is determined from the package directory and test file. Otherwise the given filename is considered.

if options.results_filename:
    outname = options.results_filename
    if '.' in outname:
        outname = outname[:outname.rfind('.')]
else:
    outname = rostest_name_from_path(pkg_dir, test_file)

If no directory for the XML test result file is given with option --results-base-dir (default) it is keeps defined aas none. (The following functions consider the current directory as XML test result file directory if option -t is not set.) Otherwise the XML result file is stored into the defined directory.

env = None
if options.results_base_dir:
    env = {ROS_TEST_RESULTS_DIR: options.results_base_dir}

A rostest unit test is created testCase = rostest.runner.createUnitTest(pkg, test_file, options.reuse_master, options.clear, options.results_base_dir). ´src/rostest/runner.py/createUnitTest()´ is a factory which creates a unittest class based on roslaunch.

createUnitTest() loads the config from the test file config = roslaunch.parent.load_config_default([test_file], None) which is then passed to a dictionary representing test atrributes which will be used later to create a test instance.

classdict = { 'setUp': setUp, 'tearDown': tearDown, 'config': config,
              'test_parent': None, 'test_file': test_file,
              'reuse_master': reuse_master, 'clear': clear }

For every test in the test file it is checked if the command to start the package's test node can be determined.

for test in config.tests:
    ...
    try:
        rp = rospkg.RosPack()
        cmd = roslib.packages.find_node(test.package, test.type, rp)
        if not cmd:
            err_msg = "Test node [%s/%s] does not exist or is not executable"%(test.package, test.type)
    except rospkg.ResourceNotFound as e:
        err_msg = "Package [%s] for test node [%s/%s] does not exist"%(test.package, test.package, test.type)

If the test node could be determined and the test name is not dublicated the test runner rostestRunner() is invoked.

for test in config.tests:
    ...
    if err_msg:
        classdict[testName] = failRunner(test.test_name, err_msg)
    elif testName in testNames:
        classdict[testName] = failDuplicateRunner(test.test_name)
    else:
        classdict[testName] = rostestRunner(test, pkg, results_base_dir=results_base_dir)
        testNames.append(testName)

Finally createUnitTest() returns a unittest based rostest test case to rostestmain().

return type('RosTest',(unittest.TestCase,),classdict)

The rostest unit test is loaded into the created Python unittest test suite suite = unittest.TestLoader().loadTestsFromTestCase(testCase). The tests from the unit test are loaded into a Python unittest test suite suite = unittest.TestLoader().loadTestsFromTestCase(testCase). Dependent on the option -t/--text either a XML test runner is created and used to run the test suite result = xml_runner.run(suite) (default) or the Python unittest.TextTestRunner(verbosity=2).run(suite)is used to generate the results. During development of tests it can be very helpful to see the output of the test runner for debugging. In these cases you will use the -t option.

rostest exits with a parser error under the following conditions:

  • no test file supplied if len(args) == 0:
  • more than one test file supplied if len(args) != 1

rostest's exit codes differ for the following conditions:

  • 1 sys.exit(1)
    • --clear option has been set without setting --reuse-master as well
    • launch arguments could not be resolved

results matching ""

    No results matching ""