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)--clearoption has been set without setting--reuse-masteras well- launch arguments could not be resolved