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