diff --git a/test/rostest/bin/roslaunch-check.py b/test/rostest/bin/roslaunch-check.py new file mode 100755 index 00000000..7d291fbd --- /dev/null +++ b/test/rostest/bin/roslaunch-check.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# Software License Agreement (BSD License) +# +# Copyright (c) 2009, Willow Garage, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Willow Garage, Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# Revision $Id: catunit 3804 2009-02-11 02:16:00Z rob_wheeler $ + +from __future__ import with_statement +import roslib; roslib.load_manifest('rostest') + +import os +import sys + +import roslib.packages + +def usage(): + print >> sys.stderr, """Usage: +\troslaunch-check file.launch +""" + print sys.argv + sys.exit(os.EX_USAGE) + +def check_roslaunch(f): + pass + +## run check and output test result file +if __name__ == '__main__': + if len(sys.argv) < 2: + usage() + roslaunch_file = sys.argv[1] + _, package = roslib.packages.get_dir_pkg(roslaunch_file) + + import roslaunch.rlutil + import rostest.rostestutil + + pkg_dir, pkg = roslib.packages.get_dir_pkg(roslaunch_file) + outname = os.path.basename(roslaunch_file).replace('.', '_') + test_file = rostest.rostestutil.xmlResultsFile(pkg, outname, is_rostest=False) + + error_msg = roslaunch.rlutil.check_roslaunch(roslaunch_file) + test_name = roslaunch_file + print "...writing test results to", test_file + if error_msg: + print>> sys.stderr, "FAILURE:\n%s"%error_msg + if not os.path.exists(os.path.dirname(test_file)): + os.makedirs(os.path.dirname(test_file)) + with open(test_file, 'w') as f: + message = "roslaunch file %s failed to parse:\n %s"%(roslaunch_file, error_msg) + f.write(rostest.rostestutil.test_failure_junit_xml(test_name, message)) + f.close() + print "wrote test file to [%s]"%test_file + else: + print "passed" + with open(test_file, 'w') as f: + f.write(rostest.rostestutil.test_success_junit_xml(test_name)) + diff --git a/test/rostest/src/rostest/rostestutil.py b/test/rostest/src/rostest/rostestutil.py index 2e38bece..6baced99 100644 --- a/test/rostest/src/rostest/rostestutil.py +++ b/test/rostest/src/rostest/rostestutil.py @@ -177,3 +177,31 @@ def xmlResultsFile(test_pkg, test_name, is_rostest=False): else: return os.path.join(test_dir, 'TEST-%s.xml'%test_name) +def test_failure_junit_xml(test_name, message): + """ + Generate JUnit XML file for a unary test suite where the test failed + + @param test_name: Name of test that failed + @type test_name: str + @param message: failure message + @type message: str + """ + return """ + + + + +"""%(test_name, message) + +def test_success_junit_xml(test_name): + """ + Generate JUnit XML file for a unary test suite where the test succeeded. + + @param test_name: Name of test that passed + @type test_name: str + """ + return """ + + + +"""%(test_name) diff --git a/tools/roslaunch/src/roslaunch/config.py b/tools/roslaunch/src/roslaunch/config.py index bdfca176..2600ca40 100644 --- a/tools/roslaunch/src/roslaunch/config.py +++ b/tools/roslaunch/src/roslaunch/config.py @@ -298,15 +298,22 @@ class ROSLaunchConfig(object): -## Base routine for creating a ROSLaunchConfig from a set of \a -## roslaunch_files and or launch XML strings and initializing it. This -## config will have a core definition and also set the master to run -## on \a port. -## @param roslaunch_files [str] -## @param port int: roscore/master port -## @param roslaunch_strs [str]: roslaunch XML strings to load -## @return ROSLaunchConfig initialized rosconfig instance def load_config_default(roslaunch_files, port, roslaunch_strs=None, loader=None, verbose=True): + """ + Base routine for creating a ROSLaunchConfig from a set of + roslaunch_files and or launch XML strings and initializing + it. This config will have a core definition and also set the + master to run on port. + + @param roslaunch_files: files to load + @type roslaunch_files: [str] + @param port roscore/master port + @type port: int + @param roslaunch_strs: roslaunch XML strings to load + @type roslaunch_strs: [str] + @return: initialized rosconfig instance + @rtype: L{ROSLaunchConfig} + """ logger = logging.getLogger('roslaunch.config') # This is the main roslaunch server process. Load up the diff --git a/tools/roslaunch/src/roslaunch/rlutil.py b/tools/roslaunch/src/roslaunch/rlutil.py index 9e9a3005..913d468c 100644 --- a/tools/roslaunch/src/roslaunch/rlutil.py +++ b/tools/roslaunch/src/roslaunch/rlutil.py @@ -153,3 +153,51 @@ def get_or_generate_uuid(options_runid, options_wait_for_master): val = roslaunch.core.generate_run_id() return val +def check_roslaunch(f): + """ + Check roslaunch file for errors, returning error message if check fails. This routine + is mainly to support rostest's roslaunch_check. + + @param f: roslaunch file name + @type f: str + @return: error message or None + @rtype: str + """ + try: + import roslaunch.config + config = roslaunch.config.load_config_default([f], 11311, verbose=False) + except roslaunch.RLException, e: + return str(e) + + errors = [] + # check for missing deps + import roslaunch.depends + base_pkg, file_deps, missing = roslaunch.depends.roslaunch_deps([f]) + for pkg, miss in missing.iteritems(): + if miss: + errors.append("Missing manifest dependencies: %s/manifest.xml: %s"%(pkg, ', '.join(miss))) + + # load all node defs + nodes = [] + for filename, rldeps in file_deps.iteritems(): + nodes.extend(rldeps.nodes) + + # check for missing packages + import roslib.packages + for pkg, node_type in nodes: + try: + roslib.packages.get_pkg_dir(pkg, required=True) + except: + errors.append("cannot find package [%s] for node [%s]"%(pkg, node_type)) + + # check for missing nodes + for pkg, node_type in nodes: + try: + if not roslib.packages.find_node(pkg, node_type): + errors.append("cannot find node [%s] in package [%s]"%(node_type, pkg)) + except Exception, e: + errors.append("unable to find node [%s/%s]: %s"%(pkg, node_type, str(e))) + + if errors: + return '\n'.join(errors) + diff --git a/tools/roswtf/src/roswtf/roslaunchwtf.py b/tools/roswtf/src/roswtf/roslaunchwtf.py index 4626fae5..97b93e9f 100644 --- a/tools/roswtf/src/roswtf/roslaunchwtf.py +++ b/tools/roswtf/src/roswtf/roslaunchwtf.py @@ -55,6 +55,10 @@ def bin_roslaunch_check(ctx): if not is_executable(roslaunch): return "%s is lacking executable permissions"%roslaunch +# this is very similar to roslib.packages.find_node. However, we +# cannot use that implementation as it returns the first found +# path. For the sake of this test, we have to find all potential +# candidates. def _find_node(pkg, node_type): try: dir = roslib.packages.get_pkg_dir(pkg) @@ -100,20 +104,6 @@ def roslaunch_duplicate_node_check(ctx): warnings.append("node [%s] in package [%s]\n"%(node_type, pkg)) return warnings -def roslaunch_machine_credentials_check(ctx): - """ - Do basic SSH checks for machine, this does not do the full NxN test - """ - nodes = [] - for filename, rldeps in ctx.launch_file_deps.iteritems(): - nodes.extend(rldeps.nodes) - warnings = [] - for pkg, node_type in nodes: - paths = _find_node(pkg, node_type) - if len(paths) > 1: - warnings.append("node [%s] in package [%s]\n"%(node_type, pkg)) - return warnings - def pycrypto_check(ctx): try: import Crypto