v01
This commit is contained in:
229
thirdparty/ros/ros_comm/tools/roslaunch/CHANGELOG.rst
vendored
Normal file
229
thirdparty/ros/ros_comm/tools/roslaunch/CHANGELOG.rst
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Changelog for package roslaunch
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1.12.14 (2018-08-23)
|
||||
--------------------
|
||||
* fix "pass_all_args" for roslaunch-check, add nosetest (`#1368 <https://github.com/ros/ros_comm/issues/1368>`_)
|
||||
* add substitution when loading yaml files (`#1354 <https://github.com/ros/ros_comm/issues/1354>`_)
|
||||
|
||||
1.12.13 (2018-02-21)
|
||||
--------------------
|
||||
* add process listeners to XML RPC server (`#1319 <https://github.com/ros/ros_comm/issues/1319>`_)
|
||||
* make master process explicitly 'required' for parent launch (`#1228 <https://github.com/ros/ros_comm/issues/1228>`_)
|
||||
|
||||
1.12.12 (2017-11-16)
|
||||
--------------------
|
||||
|
||||
1.12.11 (2017-11-07)
|
||||
--------------------
|
||||
|
||||
1.12.10 (2017-11-06)
|
||||
--------------------
|
||||
|
||||
1.12.9 (2017-11-06)
|
||||
-------------------
|
||||
|
||||
1.12.8 (2017-11-06)
|
||||
-------------------
|
||||
* fix parameter leaking into sibling scopes (`#1158 <https://github.com/ros/ros_comm/issues/1158>`_)
|
||||
* remove mention of rosmake from error message (`#1140 <https://github.com/ros/ros_comm/issues/1140>`_)
|
||||
* only launch core nodes if master was launched by roslaunch (`#1098 <https://github.com/ros/ros_comm/pull/1098>`_)
|
||||
* ensure pid file is removed on exit (`#1057 <https://github.com/ros/ros_comm/pull/1057>`_, `#1084 <https://github.com/ros/ros_comm/pull/1084>`_)
|
||||
* ensure cwd exists (`#1031 <https://github.com/ros/ros_comm/pull/1031>`_)
|
||||
* respect if/unless for roslaunch-check (`#998 <https://github.com/ros/ros_comm/pull/998>`_)
|
||||
|
||||
1.12.7 (2017-02-17)
|
||||
-------------------
|
||||
* improve error message for invalid tags (`#989 <https://github.com/ros/ros_comm/pull/989>`_)
|
||||
* fix caching logic to improve performance (`#931 <https://github.com/ros/ros_comm/pull/931>`_)
|
||||
|
||||
1.12.6 (2016-10-26)
|
||||
-------------------
|
||||
* add USE_TEST_DEPENDENCIES option to roslaunch_add_file_check() (`#910 <https://github.com/ros/ros_comm/pull/910>`_)
|
||||
|
||||
1.12.5 (2016-09-30)
|
||||
-------------------
|
||||
|
||||
1.12.4 (2016-09-19)
|
||||
-------------------
|
||||
|
||||
1.12.3 (2016-09-17)
|
||||
-------------------
|
||||
* better naming for roslaunch check test results (`#897 <https://github.com/ros/ros_comm/pull/897>`_)
|
||||
* support use_test_depends option for roslaunch-check (`#887 <https://github.com/ros/ros_comm/pull/887>`_)
|
||||
* allow empty include (`#882 <https://github.com/ros/ros_comm/pull/882>`_)
|
||||
* fix param command for Python 3 (`#840 <https://github.com/ros/ros_comm/pull/840>`_)
|
||||
|
||||
1.12.2 (2016-06-03)
|
||||
-------------------
|
||||
* support registering the same test multiple times with different arguments (`#814 <https://github.com/ros/ros_comm/pull/814>`_)
|
||||
* fix passing multiple args to roslaunch_add_file_check (`#814 <https://github.com/ros/ros_comm/pull/814>`_)
|
||||
|
||||
1.12.1 (2016-04-18)
|
||||
-------------------
|
||||
* add support for Python expressions (`#784 <https://github.com/ros/ros_comm/pull/784>`_, `#793 <https://github.com/ros/ros_comm/pull/793>`_)
|
||||
|
||||
1.12.0 (2016-03-18)
|
||||
-------------------
|
||||
|
||||
1.11.18 (2016-03-17)
|
||||
--------------------
|
||||
|
||||
1.11.17 (2016-03-11)
|
||||
--------------------
|
||||
* improve roslaunch-check to not fail if recursive dependencies lack dependencies (`#730 <https://github.com/ros/ros_comm/pull/730>`_)
|
||||
* add "pass_all_args" attribute to roslaunch "include" tag (`#710 <https://github.com/ros/ros_comm/pull/710>`_)
|
||||
* fix a typo in unknown host error message (`#735 <https://github.com/ros/ros_comm/pull/735>`_)
|
||||
* wait for param server to be available before trying to get param (`#711 <https://github.com/ros/ros_comm/pull/711>`_)
|
||||
|
||||
1.11.16 (2015-11-09)
|
||||
--------------------
|
||||
* add `-w` and `-t` options (`#687 <https://github.com/ros/ros_comm/pull/687>`_)
|
||||
* fix missing minimum version for rospkg dependency (`#693 <https://github.com/ros/ros_comm/issues/693>`_)
|
||||
|
||||
1.11.15 (2015-10-13)
|
||||
--------------------
|
||||
* improve performance by reusing the rospack instance across nodes with the same default environment (`#682 <https://github.com/ros/ros_comm/pull/682>`_)
|
||||
|
||||
1.11.14 (2015-09-19)
|
||||
--------------------
|
||||
* add more information when test times out
|
||||
|
||||
1.11.13 (2015-04-28)
|
||||
--------------------
|
||||
|
||||
1.11.12 (2015-04-27)
|
||||
--------------------
|
||||
|
||||
1.11.11 (2015-04-16)
|
||||
--------------------
|
||||
|
||||
1.11.10 (2014-12-22)
|
||||
--------------------
|
||||
* fix exception at roscore startup if python has IPv6 disabled (`#515 <https://github.com/ros/ros_comm/issues/515>`_)
|
||||
* fix error handling (`#516 <https://github.com/ros/ros_comm/pull/516>`_)
|
||||
* fix compatibility with paramiko 1.10.0 (`#498 <https://github.com/ros/ros_comm/pull/498>`_)
|
||||
|
||||
1.11.9 (2014-08-18)
|
||||
-------------------
|
||||
* fix usage of logger before it is initialized (`#490 <https://github.com/ros/ros_comm/issues/490>`_) (regression from 1.11.6)
|
||||
|
||||
1.11.8 (2014-08-04)
|
||||
-------------------
|
||||
* remove implicit rostest dependency and use rosunit instead (`#475 <https://github.com/ros/ros_comm/issues/475>`_)
|
||||
* accept stdin input alongside files (`#472 <https://github.com/ros/ros_comm/issues/472>`_)
|
||||
|
||||
1.11.7 (2014-07-18)
|
||||
-------------------
|
||||
* fix the ROS_MASTER_URI environment variable logic on Windows (`#2 <https://github.com/windows/ros_comm/issues/2>`_)
|
||||
|
||||
1.11.6 (2014-07-10)
|
||||
-------------------
|
||||
* fix printing of non-ascii roslaunch parameters (`#454 <https://github.com/ros/ros_comm/issues/454>`_)
|
||||
* add respawn_delay attribute to node tag in roslaunch (`#446 <https://github.com/ros/ros_comm/issues/446>`_)
|
||||
* write traceback for exceptions in roslaunch to log file
|
||||
|
||||
1.11.5 (2014-06-24)
|
||||
-------------------
|
||||
|
||||
1.11.4 (2014-06-16)
|
||||
-------------------
|
||||
* fix handling of if/unless attributes on args (`#437 <https://github.com/ros/ros_comm/issues/437>`_)
|
||||
* improve parameter printing in roslaunch (`#89 <https://github.com/ros/ros_comm/issues/89>`_)
|
||||
* Python 3 compatibility (`#426 <https://github.com/ros/ros_comm/issues/426>`_, `#427 <https://github.com/ros/ros_comm/issues/427>`_, `#429 <https://github.com/ros/ros_comm/issues/429>`_)
|
||||
|
||||
1.11.3 (2014-05-21)
|
||||
-------------------
|
||||
|
||||
1.11.2 (2014-05-08)
|
||||
-------------------
|
||||
|
||||
1.11.1 (2014-05-07)
|
||||
-------------------
|
||||
* fix roslaunch anonymous function to generate the same output for the same input (`#297 <https://github.com/ros/ros_comm/issues/297>`_)
|
||||
* add doc attribute to roslaunch arg tags (`#379 <https://github.com/ros/ros_comm/issues/379>`_)
|
||||
* print parameter values in roslaunch (`#89 <https://github.com/ros/ros_comm/issues/89>`_)
|
||||
* add architecture_independent flag in package.xml (`#391 <https://github.com/ros/ros_comm/issues/391>`_)
|
||||
|
||||
1.11.0 (2014-03-04)
|
||||
-------------------
|
||||
* use catkin_install_python() to install Python scripts (`#361 <https://github.com/ros/ros_comm/issues/361>`_)
|
||||
|
||||
1.10.0 (2014-02-11)
|
||||
-------------------
|
||||
* add optional DEPENDENCIES argument to roslaunch_add_file_check()
|
||||
* add explicit run dependency (`#347 <https://github.com/ros/ros_comm/issues/347>`_)
|
||||
|
||||
1.9.54 (2014-01-27)
|
||||
-------------------
|
||||
* add missing run/test dependencies on rosbuild to get ROS_ROOT environment variable
|
||||
|
||||
1.9.53 (2014-01-14)
|
||||
-------------------
|
||||
|
||||
1.9.52 (2014-01-08)
|
||||
-------------------
|
||||
|
||||
1.9.51 (2014-01-07)
|
||||
-------------------
|
||||
* fix roslaunch-check for unreleased wet dependencies (`#332 <https://github.com/ros/ros_comm/issues/332>`_)
|
||||
|
||||
1.9.50 (2013-10-04)
|
||||
-------------------
|
||||
* add option to disable terminal title setting
|
||||
* fix roslaunch-check to handle more complex launch files
|
||||
|
||||
1.9.49 (2013-09-16)
|
||||
-------------------
|
||||
|
||||
1.9.48 (2013-08-21)
|
||||
-------------------
|
||||
* update roslaunch to support ROS_NAMESPACE (`#58 <https://github.com/ros/ros_comm/issues/58>`_)
|
||||
* make roslaunch relocatable (`ros/catkin#490 <https://github.com/ros/catkin/issues/490>`_)
|
||||
* change roslaunch resolve order (`#256 <https://github.com/ros/ros_comm/issues/256>`_)
|
||||
* fix roslaunch check script in install space (`#257 <https://github.com/ros/ros_comm/issues/257>`_)
|
||||
|
||||
1.9.47 (2013-07-03)
|
||||
-------------------
|
||||
* improve roslaunch completion to include launch file arguments (`#230 <https://github.com/ros/ros_comm/issues/230>`_)
|
||||
* check for CATKIN_ENABLE_TESTING to enable configure without tests
|
||||
|
||||
1.9.46 (2013-06-18)
|
||||
-------------------
|
||||
* add CMake function roslaunch_add_file_check() (`#241 <https://github.com/ros/ros_comm/issues/241>`_)
|
||||
|
||||
1.9.45 (2013-06-06)
|
||||
-------------------
|
||||
* modified roslaunch $(find PKG) to consider path behind it for resolve strategy (`#233 <https://github.com/ros/ros_comm/pull/233>`_)
|
||||
* add boolean attribute 'subst_value' to rosparam tag in launch files (`#218 <https://github.com/ros/ros_comm/issues/218>`_)
|
||||
* add command line parameter to print out launch args
|
||||
* fix missing import in arg_dump.py
|
||||
|
||||
1.9.44 (2013-03-21)
|
||||
-------------------
|
||||
* fix 'roslaunch --files' with non-unique anononymous ids (`#186 <https://github.com/ros/ros_comm/issues/186>`_)
|
||||
* fix ROS_MASTER_URI for Windows
|
||||
|
||||
1.9.43 (2013-03-13)
|
||||
-------------------
|
||||
* implement process killer for Windows
|
||||
|
||||
1.9.42 (2013-03-08)
|
||||
-------------------
|
||||
* add option --skip-log-check (`#133 <https://github.com/ros/ros_comm/issues/133>`_)
|
||||
* update API doc to list raised exceptions in config.py
|
||||
* fix invocation of Python scripts under Windows (`#54 <https://github.com/ros/ros_comm/issues/54>`_)
|
||||
|
||||
1.9.41 (2013-01-24)
|
||||
-------------------
|
||||
* improve performance of $(find ...)
|
||||
|
||||
1.9.40 (2013-01-13)
|
||||
-------------------
|
||||
* fix 'roslaunch --pid=' when pointing to ROS_HOME but folder does not exist (`#43 <https://github.com/ros/ros_comm/issues/43>`_)
|
||||
* fix 'roslaunch --pid=' to use shell expansion for the pid value (`#44 <https://github.com/ros/ros_comm/issues/44>`_)
|
||||
|
||||
1.9.39 (2012-12-29)
|
||||
-------------------
|
||||
* first public release for Groovy
|
||||
27
thirdparty/ros/ros_comm/tools/roslaunch/CMakeLists.txt
vendored
Normal file
27
thirdparty/ros/ros_comm/tools/roslaunch/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
cmake_minimum_required(VERSION 2.8.3)
|
||||
project(roslaunch)
|
||||
find_package(catkin REQUIRED)
|
||||
catkin_package(CFG_EXTRAS roslaunch-extras.cmake)
|
||||
|
||||
if(CMAKE_HOST_UNIX)
|
||||
catkin_add_env_hooks(10.roslaunch SHELLS sh DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/env-hooks)
|
||||
else()
|
||||
catkin_add_env_hooks(10.roslaunch SHELLS bat DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/env-hooks)
|
||||
endif()
|
||||
|
||||
catkin_python_setup()
|
||||
|
||||
install(FILES resources/roscore.xml
|
||||
DESTINATION ${CATKIN_GLOBAL_ETC_DESTINATION}/ros)
|
||||
|
||||
# install example launch files
|
||||
install(DIRECTORY resources
|
||||
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
|
||||
|
||||
# required for old rosbuild macro to be under PKGNAME/scripts
|
||||
catkin_install_python(PROGRAMS scripts/roslaunch-check
|
||||
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/scripts)
|
||||
|
||||
if(CATKIN_ENABLE_TESTING)
|
||||
catkin_add_nosetests(test/unit)
|
||||
endif()
|
||||
64
thirdparty/ros/ros_comm/tools/roslaunch/cmake/roslaunch-extras.cmake.em
vendored
Normal file
64
thirdparty/ros/ros_comm/tools/roslaunch/cmake/roslaunch-extras.cmake.em
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
# generated from ros_comm/tools/roslaunch/cmake/roslaunch-extras.cmake.em
|
||||
|
||||
@[if DEVELSPACE]@
|
||||
# set path to roslaunch-check script in develspace
|
||||
set(roslaunch_check_script @(CMAKE_CURRENT_SOURCE_DIR)/scripts/roslaunch-check)
|
||||
@[else]@
|
||||
# set path to roslaunch-check script installspace
|
||||
set(roslaunch_check_script ${roslaunch_DIR}/../scripts/roslaunch-check)
|
||||
@[end if]@
|
||||
|
||||
#
|
||||
# Check ROS launch files for validity as part of the unit tests.
|
||||
#
|
||||
# :param path: the path to a launch file or a directory containing launch files
|
||||
# either relative to th current CMakeLists file or absolute
|
||||
# :type url: string
|
||||
# :param DEPENDENCIES: the targets which must be built before executing
|
||||
# the test
|
||||
# :type DEPENDENCIES: list of strings
|
||||
# :param USE_TEST_DEPENDENCIES: beside run dependencies also consider test
|
||||
# dependencies when checking if all dependencies of a launch file are present
|
||||
# :type USE_TEST_DEPENDENCIES: option
|
||||
# :param ARGV: arbitrary arguments in the form 'key=value'
|
||||
# which will be set as environment variables
|
||||
# :type ARGV: string
|
||||
#
|
||||
function(roslaunch_add_file_check path)
|
||||
cmake_parse_arguments(_roslaunch "USE_TEST_DEPENDENCIES" "" "DEPENDENCIES" ${ARGN})
|
||||
if(IS_ABSOLUTE ${path})
|
||||
set(abspath ${path})
|
||||
else()
|
||||
set(abspath "${CMAKE_CURRENT_SOURCE_DIR}/${path}")
|
||||
endif()
|
||||
if(NOT EXISTS ${abspath})
|
||||
message(FATAL_ERROR "roslaunch_add_file_check() path '${abspath}' was not found")
|
||||
endif()
|
||||
|
||||
set(testname ${path})
|
||||
|
||||
# to support registering the same test with different ARGS
|
||||
# append the args to the test name
|
||||
if(_roslaunch_UNPARSED_ARGUMENTS)
|
||||
get_filename_component(_ext ${testname} EXT)
|
||||
get_filename_component(testname ${testname} NAME_WE)
|
||||
foreach(arg ${_roslaunch_UNPARSED_ARGUMENTS})
|
||||
string(REPLACE ":=" "_" arg_string "${arg}")
|
||||
string(REPLACE "=" "_" arg_string "${arg_string}")
|
||||
set(testname "${testname}__${arg_string}")
|
||||
endforeach()
|
||||
set(testname "${testname}${_ext}")
|
||||
endif()
|
||||
|
||||
string(REPLACE "/" "_" testname ${testname})
|
||||
set(output_path ${CATKIN_TEST_RESULTS_DIR}/${PROJECT_NAME})
|
||||
set(cmd "${CMAKE_COMMAND} -E make_directory ${output_path}")
|
||||
set(output_file_name "roslaunch-check_${testname}.xml")
|
||||
string(REPLACE ";" " " _roslaunch_UNPARSED_ARGUMENTS "${_roslaunch_UNPARSED_ARGUMENTS}")
|
||||
set(use_test_dependencies "")
|
||||
if(${_roslaunch_USE_TEST_DEPENDENCIES})
|
||||
set(use_test_dependencies " -t")
|
||||
endif()
|
||||
set(cmd ${cmd} "${roslaunch_check_script} -o '${output_path}/${output_file_name}'${use_test_dependencies} '${abspath}' ${_roslaunch_UNPARSED_ARGUMENTS}")
|
||||
catkin_run_tests_target("roslaunch-check" ${testname} "${output_file_name}" COMMAND ${cmd} DEPENDENCIES ${_roslaunch_DEPENDENCIES})
|
||||
endfunction()
|
||||
5
thirdparty/ros/ros_comm/tools/roslaunch/env-hooks/10.roslaunch.bat
vendored
Normal file
5
thirdparty/ros/ros_comm/tools/roslaunch/env-hooks/10.roslaunch.bat
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
REM roslaunch/env-hooks/10.roslaunch.bat
|
||||
|
||||
if "%ROS_MASTER_URI%" == "" (
|
||||
set ROS_MASTER_URI=http://localhost:11311
|
||||
)
|
||||
5
thirdparty/ros/ros_comm/tools/roslaunch/env-hooks/10.roslaunch.sh
vendored
Normal file
5
thirdparty/ros/ros_comm/tools/roslaunch/env-hooks/10.roslaunch.sh
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# roslaunch/env-hooks/10.roslaunch.sh
|
||||
|
||||
if [ ! "$ROS_MASTER_URI" ]; then
|
||||
export ROS_MASTER_URI=http://localhost:11311
|
||||
fi
|
||||
40
thirdparty/ros/ros_comm/tools/roslaunch/package.xml
vendored
Normal file
40
thirdparty/ros/ros_comm/tools/roslaunch/package.xml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<package>
|
||||
<name>roslaunch</name>
|
||||
<version>1.12.14</version>
|
||||
<description>
|
||||
roslaunch is a tool for easily launching multiple ROS <a
|
||||
href="http://ros.org/wiki/Nodes">nodes</a> locally and remotely
|
||||
via SSH, as well as setting parameters on the <a
|
||||
href="http://ros.org/wiki/Parameter Server">Parameter
|
||||
Server</a>. It includes options to automatically respawn processes
|
||||
that have already died. roslaunch takes in one or more XML
|
||||
configuration files (with the <tt>.launch</tt> extension) that
|
||||
specify the parameters to set and nodes to launch, as well as the
|
||||
machines that they should be run on.
|
||||
</description>
|
||||
<maintainer email="dthomas@osrfoundation.org">Dirk Thomas</maintainer>
|
||||
<license>BSD</license>
|
||||
|
||||
<url>http://ros.org/wiki/roslaunch</url>
|
||||
<author>Ken Conley</author>
|
||||
|
||||
<buildtool_depend version_gte="0.5.78">catkin</buildtool_depend>
|
||||
|
||||
<run_depend>python-paramiko</run_depend>
|
||||
<run_depend version_gte="1.0.37">python-rospkg</run_depend>
|
||||
<run_depend>python-yaml</run_depend>
|
||||
<run_depend>rosclean</run_depend>
|
||||
<run_depend>rosgraph_msgs</run_depend>
|
||||
<run_depend>roslib</run_depend>
|
||||
<run_depend version_gte="1.11.16">rosmaster</run_depend>
|
||||
<run_depend>rosout</run_depend>
|
||||
<run_depend>rosparam</run_depend>
|
||||
<run_depend version_gte="1.13.3">rosunit</run_depend>
|
||||
|
||||
<test_depend>rosbuild</test_depend>
|
||||
|
||||
<export>
|
||||
<rosdoc config="rosdoc.yaml"/>
|
||||
<architecture_independent/>
|
||||
</export>
|
||||
</package>
|
||||
3
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-args.launch
vendored
Normal file
3
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-args.launch
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<launch>
|
||||
<arg name="test_foo" />
|
||||
</launch>
|
||||
3
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-env-substutition.xml
vendored
Normal file
3
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-env-substutition.xml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<launch>
|
||||
<param name="example_env_param" value="$(env VALUE)" />
|
||||
</launch>
|
||||
4
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-gdb.launch
vendored
Normal file
4
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-gdb.launch
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<!-- NOTE: this requires that you 'rosmake roscpp_tutorials' -->
|
||||
<launch>
|
||||
<node name="talker" pkg="roscpp_tutorials" type="talker" launch-prefix="xterm -e gdb --args"/>
|
||||
</launch>
|
||||
17
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-include.launch
vendored
Normal file
17
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-include.launch
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<launch>
|
||||
<node name="talker" pkg="rospy" type="talker.py" />
|
||||
<node name="listener" pkg="rospy" type="listener.py" />
|
||||
|
||||
<param name="param1" value="bar" />
|
||||
<param name="param2" value="2"/>
|
||||
|
||||
<!-- you can set parameters in child namespaces -->
|
||||
<param name="wg2/param3" value="a child namespace parameter" />
|
||||
|
||||
<group ns="wg2">
|
||||
<param name="param4" value="4" />
|
||||
<node name="talker" pkg="rospy" type="talker.py" />
|
||||
<node name="listener" pkg="rospy" type="listener.py" />
|
||||
</group>
|
||||
|
||||
</launch>
|
||||
4
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-min.launch
vendored
Normal file
4
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-min.launch
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<launch>
|
||||
<!-- $(anon talker) creates an anonymous name for this node -->
|
||||
<node name="$(anon talker)" pkg="rospy" type="talker.py" />
|
||||
</launch>
|
||||
46
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-params.launch
vendored
Normal file
46
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-params.launch
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<launch>
|
||||
|
||||
<!--
|
||||
|
||||
Parameter Server parameters. You can omit the 'type' attribute if
|
||||
value is unambiguous. Supported types are str, int, double, bool.
|
||||
You can also specify the contents of a file instead using the
|
||||
'textfile' or 'binfile' attributes.
|
||||
|
||||
-->
|
||||
<param name="somestring1" value="bar2" />
|
||||
<!-- force to string instead of integer -->
|
||||
<param name="somestring2" value="10" type="str" />
|
||||
|
||||
<param name="someinteger1" value="1" type="int" />
|
||||
<param name="someinteger2" value="2" />
|
||||
|
||||
<param name="somefloat1" value="3.14159" type="double" />
|
||||
<param name="somefloat2" value="3.0" />
|
||||
|
||||
<!-- you can set parameters in child namespaces -->
|
||||
<param name="wg/wgchildparam" value="a child namespace parameter" />
|
||||
<group ns="wg2">
|
||||
<param name="wg2childparam1" value="a child namespace parameter" />
|
||||
<param name="wg2childparam2" value="a child namespace parameter" />
|
||||
</group>
|
||||
|
||||
<!-- use rosparam for more complex types -->
|
||||
<rosparam param="list">[1, 2, 3, 4]</rosparam>
|
||||
<rosparam>
|
||||
rp_key1: a
|
||||
rp_key2: b
|
||||
</rosparam>
|
||||
|
||||
<!-- use rosparam files -->
|
||||
<group ns="rosparam">
|
||||
<rosparam command="load" file="$(find rosparam)/example.yaml" />
|
||||
</group>
|
||||
|
||||
<!-- upload the contents of a file as a param -->
|
||||
<param name="configfile" textfile="$(find roslaunch)/resources/example.launch" />
|
||||
<!-- upload the contents of a file as base64 binary as a param -->
|
||||
<param name="binaryfile" binfile="$(find roslaunch)/resources/example.launch" />
|
||||
<!-- upload the output of a command as a param. -->
|
||||
|
||||
</launch>
|
||||
4
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-pass_all_args.launch
vendored
Normal file
4
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-pass_all_args.launch
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<launch>
|
||||
<arg name="test_foo" default="value_foo" />
|
||||
<include file="$(find roslaunch)/resources/example-args.launch" pass_all_args="true" />
|
||||
</launch>
|
||||
17
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-ssh.launch
vendored
Normal file
17
thirdparty/ros/ros_comm/tools/roslaunch/resources/example-ssh.launch
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<!-- an example launch configuration that launches two demo nodes on a remote machine -->
|
||||
<launch>
|
||||
|
||||
<group ns="local">
|
||||
<node name="talker" pkg="rospy" type="talker.py" />
|
||||
<node name="listener" pkg="rospy" type="listener.py" />
|
||||
</group>
|
||||
|
||||
<!-- default only affects nodes defined later -->
|
||||
<machine name="machine-1" default="true" address="foo.bar.com" env-loader="/opt/ros/fuerte/env.sh" user="whoami" ssh-port="22" />
|
||||
|
||||
<group ns="remote">
|
||||
<node name="talker" pkg="rospy" type="talker.py" />
|
||||
<node name="listener" pkg="rospy" type="listener.py" />
|
||||
</group>
|
||||
|
||||
</launch>
|
||||
73
thirdparty/ros/ros_comm/tools/roslaunch/resources/example.launch
vendored
Normal file
73
thirdparty/ros/ros_comm/tools/roslaunch/resources/example.launch
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
<launch>
|
||||
|
||||
<!-- localhost definition can be omitted. -->
|
||||
<machine name="localhost" address="localhost" default="true" />
|
||||
<machine name="localhost-altroot" address="localhost" env-loader="/opt/ros/fuerte/env.sh" />
|
||||
|
||||
<!--
|
||||
|
||||
Parameter Server parameters. You can omit the 'type' attribute if
|
||||
value is unambiguous. Supported types are str, int, double, bool.
|
||||
You can also specify the contents of a file instead using the
|
||||
'textfile' or 'binfile' attributes.
|
||||
|
||||
-->
|
||||
<param name="somestring1" value="bar" />
|
||||
<!-- force to string instead of integer -->
|
||||
<param name="somestring2" value="10" type="str" />
|
||||
|
||||
<param name="someinteger1" value="1" type="int" />
|
||||
<param name="someinteger2" value="2" />
|
||||
|
||||
<param name="somefloat1" value="3.14159" type="double" />
|
||||
<param name="somefloat2" value="3.0" />
|
||||
|
||||
<!-- you can set parameters in child namespaces -->
|
||||
<param name="wg/childparam" value="a child namespace parameter" />
|
||||
|
||||
<!-- upload the contents of a file as a param -->
|
||||
<param name="configfile" textfile="$(find roslaunch)/resources/example.launch" />
|
||||
<!-- upload the contents of a file as base64 binary as a param -->
|
||||
<param name="binaryfile" binfile="$(find roslaunch)/resources/example.launch" />
|
||||
<!-- upload the output of a command as a param. -->
|
||||
<param name="commandoutput" command="cat "$(find roslaunch)/resources/example.launch"" />
|
||||
|
||||
<!-- Group a collection of tags. ns attribute is optional -->
|
||||
<group ns="wg">
|
||||
<!-- remap applies to all future nodes in this group -->
|
||||
<remap from="chatter" to="hello"/>
|
||||
|
||||
<node name="listener" pkg="rospy" type="listener.py" respawn="true" />
|
||||
|
||||
<node name="talker1" pkg="rospy" type="talker.py">
|
||||
<env name="ENV_EXAMPLE" value="example value" />
|
||||
<remap from="foo" to="bar"/>
|
||||
<!-- params within node tags are equivalent to ~params.
|
||||
You must set the 'name' attribute of a node to use this feature. -->
|
||||
<param name="talker_param" value="a value" />
|
||||
</node>
|
||||
|
||||
<node name="talker2" pkg="rospy" type="talker.py" />
|
||||
|
||||
</group>
|
||||
|
||||
|
||||
<!-- import another roslaunch config file -->
|
||||
<group ns="included">
|
||||
<include file="$(find roslaunch)/resources/example-include.launch" />
|
||||
</group>
|
||||
<!-- more compact import syntax -->
|
||||
<include ns="included2" file="$(find roslaunch)/resources/example-include.launch" />
|
||||
|
||||
<!-- Pass over an include and node with if-attributes that evaluate to false. -->
|
||||
<arg name="false_arg" value="false" />
|
||||
<include if="$(arg false_arg)" file="does/not/exist.launch" />
|
||||
<node if="$(arg false_arg)" pkg="doesnt_exist" type="nope" name="nope" />
|
||||
|
||||
<!-- Pass over a group with an unless-attribute that evaluate to true. -->
|
||||
<arg name="true_arg" value="true" />
|
||||
<group unless="$(arg true_arg)">
|
||||
<include file="does/not/exist.launch" />
|
||||
<node pkg="doesnt_exist" type="nope" name="nope" />
|
||||
</group>
|
||||
</launch>
|
||||
13
thirdparty/ros/ros_comm/tools/roslaunch/resources/roscore.xml
vendored
Normal file
13
thirdparty/ros/ros_comm/tools/roslaunch/resources/roscore.xml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
<!--
|
||||
ROS Core Stack definition
|
||||
|
||||
Before making any modifications to this file, please read:
|
||||
http://ros.org/wiki/roscore
|
||||
-->
|
||||
<launch>
|
||||
<group ns="/">
|
||||
<param name="rosversion" command="rosversion roslaunch" />
|
||||
<param name="rosdistro" command="rosversion -d" />
|
||||
<node pkg="rosout" type="rosout" name="rosout" respawn="true"/>
|
||||
</group>
|
||||
</launch>
|
||||
1
thirdparty/ros/ros_comm/tools/roslaunch/rosdoc.yaml
vendored
Normal file
1
thirdparty/ros/ros_comm/tools/roslaunch/rosdoc.yaml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
- builder: epydoc
|
||||
70
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roscore
vendored
Executable file
70
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roscore
vendored
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
from rosmaster.master_api import NUM_WORKERS
|
||||
|
||||
NAME = 'roscore'
|
||||
|
||||
def _get_optparse():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
prog=NAME,
|
||||
description="roscore will start up a ROS Master, a ROS Parameter Server and a rosout logging node",
|
||||
epilog="See http://www.ros.org/wiki/roscore"
|
||||
)
|
||||
parser.add_option("-p", "--port",
|
||||
dest="port", default=None,
|
||||
help="master port. Only valid if master is launched", metavar="PORT")
|
||||
parser.add_option("-v", action="store_true",
|
||||
dest="verbose", default=False,
|
||||
help="verbose printing")
|
||||
parser.add_option("-w", "--numworkers",
|
||||
dest="num_workers", default=NUM_WORKERS, type=int,
|
||||
help="override number of worker threads", metavar="NUM_WORKERS")
|
||||
parser.add_option("-t", "--timeout",
|
||||
dest="timeout",
|
||||
help="override the socket connection timeout (in seconds).", metavar="TIMEOUT")
|
||||
return parser
|
||||
|
||||
|
||||
parser = _get_optparse()
|
||||
(options, args) = parser.parse_args(sys.argv[1:])
|
||||
|
||||
if len(args) > 0:
|
||||
parser.error("roscore does not take arguments")
|
||||
|
||||
|
||||
import roslaunch
|
||||
roslaunch.main(['roscore', '--core'] + sys.argv[1:])
|
||||
35
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roslaunch
vendored
Executable file
35
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roslaunch
vendored
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
import roslaunch
|
||||
roslaunch.main()
|
||||
125
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roslaunch-check
vendored
Executable file
125
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roslaunch-check
vendored
Executable file
@@ -0,0 +1,125 @@
|
||||
#!/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.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import rospkg
|
||||
|
||||
import roslaunch.rlutil
|
||||
import rosunit
|
||||
|
||||
def check_roslaunch_file(roslaunch_file, use_test_depends=False):
|
||||
print("checking", roslaunch_file)
|
||||
error_msg = roslaunch.rlutil.check_roslaunch(roslaunch_file, use_test_depends=use_test_depends)
|
||||
# error message has to be XML attr safe
|
||||
if error_msg:
|
||||
return "[%s]:\n\t%s"%(roslaunch_file,error_msg)
|
||||
|
||||
def check_roslaunch_dir(roslaunch_dir, use_test_depends=False):
|
||||
error_msgs = []
|
||||
for f in os.listdir(roslaunch_dir):
|
||||
if f.endswith('.launch'):
|
||||
roslaunch_file = os.path.join(roslaunch_dir, f)
|
||||
if os.path.isfile(roslaunch_file):
|
||||
error_msgs.append(check_roslaunch_file(roslaunch_file, use_test_depends=use_test_depends))
|
||||
# error message has to be XML attr safe
|
||||
return '\n'.join([e for e in error_msgs if e])
|
||||
|
||||
## run check and output test result file
|
||||
if __name__ == '__main__':
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage="usage: \troslaunch-check [options] <file|directory> [env=value...]")
|
||||
# #3674
|
||||
parser.add_option("-o", "--output-file",
|
||||
dest="output_file", default=None,
|
||||
help="file to store test results in", metavar="FILE")
|
||||
parser.add_option("-t", "--use-test-depends",
|
||||
action="store_true", dest="test_depends", default=False,
|
||||
help="Assuming packages listed in test_depends are also installed")
|
||||
|
||||
options, args = parser.parse_args()
|
||||
if not args:
|
||||
parser.error('please specify a file or directory for roslaunch files to test')
|
||||
roslaunch_path = args[0]
|
||||
|
||||
# #2590: implementing this quick and dirty as this script should only be used by higher level tools
|
||||
env_vars = args[1:]
|
||||
for e in env_vars:
|
||||
var, val = e.split('=')
|
||||
os.environ[var] = val
|
||||
|
||||
pkg = rospkg.get_package_name(roslaunch_path)
|
||||
r = rospkg.RosPack()
|
||||
pkg_dir = r.get_path(pkg)
|
||||
|
||||
if os.path.isfile(roslaunch_path):
|
||||
error_msg = check_roslaunch_file(roslaunch_path, use_test_depends=options.test_depends)
|
||||
else:
|
||||
print("checking *.launch in directory", roslaunch_path)
|
||||
error_msg = check_roslaunch_dir(roslaunch_path, use_test_depends=options.test_depends)
|
||||
relpath = os.path.abspath(roslaunch_path)[len(os.path.abspath(pkg_dir))+1:]
|
||||
outname = relpath.replace('.', '_').replace(os.sep, '_')
|
||||
if outname == '_':
|
||||
outname = '_pkg'
|
||||
|
||||
if options.output_file:
|
||||
test_file = options.output_file
|
||||
# If we were given a test file name to write, then construct the
|
||||
# test name from it, to ensure uniqueness
|
||||
test_name = os.path.basename(test_file)
|
||||
else:
|
||||
test_file = rosunit.xml_results_file(pkg, "roslaunch_check_"+outname, is_rostest=False)
|
||||
test_name = roslaunch_path
|
||||
|
||||
print("...writing test results to", test_file)
|
||||
|
||||
dir_path = os.path.abspath(os.path.dirname(test_file))
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
|
||||
if error_msg:
|
||||
print("FAILURE:\n%s"%error_msg, file=sys.stderr)
|
||||
with open(test_file, 'w') as f:
|
||||
message = "roslaunch check [%s] failed"%(roslaunch_path)
|
||||
f.write(rosunit.junitxml.test_failure_junit_xml(test_name, message, stdout=error_msg,
|
||||
class_name="roslaunch.RoslaunchCheck", testcase_name="%s_%s" % (pkg, outname)))
|
||||
print("wrote test file to [%s]"%test_file)
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("passed")
|
||||
with open(test_file, 'w') as f:
|
||||
f.write(rosunit.junitxml.test_success_junit_xml(test_name,
|
||||
class_name="roslaunch.RoslaunchCheck", testcase_name="%s_%s" % (pkg, outname)))
|
||||
55
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roslaunch-complete
vendored
Executable file
55
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roslaunch-complete
vendored
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2013, 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.
|
||||
|
||||
import roslaunch.core
|
||||
import roslaunch.arg_dump as roslaunch_arg_dump
|
||||
from roslaunch import rlutil
|
||||
import sys
|
||||
|
||||
try:
|
||||
args = rlutil.resolve_launch_arguments(sys.argv[1:])
|
||||
# Strip all other args: As we are completing they might be incomplete
|
||||
# and will lead to an error. Anyways we only want all possible args.
|
||||
args = args[0:1]
|
||||
roslaunch_args = roslaunch_arg_dump.get_args(args)
|
||||
except roslaunch.core.RLException:
|
||||
# stay silent for completion
|
||||
sys.exit(1)
|
||||
|
||||
#complete_args = [a + ":=" for a in sorted(roslaunch_args.keys())]
|
||||
# Currently don't put := after that as the completion will escape := to \:\=
|
||||
# Unfortunately we also want the escaping for roslaunch, when completing filenames
|
||||
complete_args = [a for a in sorted(roslaunch_args.keys())]
|
||||
print " ".join(complete_args)
|
||||
sys.exit(0)
|
||||
|
||||
35
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roslaunch-deps
vendored
Executable file
35
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roslaunch-deps
vendored
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/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.
|
||||
|
||||
import roslaunch.depends
|
||||
roslaunch.depends.roslaunch_deps_main()
|
||||
35
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roslaunch-logs
vendored
Executable file
35
thirdparty/ros/ros_comm/tools/roslaunch/scripts/roslaunch-logs
vendored
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/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.
|
||||
|
||||
import roslaunch.roslaunch_logs
|
||||
roslaunch.roslaunch_logs.logs_main()
|
||||
17
thirdparty/ros/ros_comm/tools/roslaunch/setup.py
vendored
Normal file
17
thirdparty/ros/ros_comm/tools/roslaunch/setup.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
from catkin_pkg.python_setup import generate_distutils_setup
|
||||
|
||||
d = generate_distutils_setup(
|
||||
packages=['roslaunch'],
|
||||
package_dir={'': 'src'},
|
||||
scripts=['scripts/roscore',
|
||||
'scripts/roslaunch',
|
||||
'scripts/roslaunch-complete',
|
||||
'scripts/roslaunch-deps',
|
||||
'scripts/roslaunch-logs'],
|
||||
requires=['genmsg', 'genpy', 'roslib', 'rospkg']
|
||||
)
|
||||
|
||||
setup(**d)
|
||||
333
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/__init__.py
vendored
Normal file
333
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/__init__.py
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
# monkey-patch to suppress threading error message in Python 2.7.3
|
||||
# see http://stackoverflow.com/questions/13193278/understand-python-threading-bug
|
||||
if sys.version_info[:3] == (2, 7, 3):
|
||||
import threading
|
||||
threading._DummyThread._Thread__stop = lambda _dummy: None
|
||||
|
||||
import rospkg
|
||||
|
||||
from . import core as roslaunch_core
|
||||
from . import param_dump as roslaunch_param_dump
|
||||
|
||||
# symbol exports
|
||||
from .core import Node, Test, Master, RLException
|
||||
from .config import ROSLaunchConfig
|
||||
from .launch import ROSLaunchRunner
|
||||
from .xmlloader import XmlLoader, XmlParseException
|
||||
|
||||
|
||||
# script api
|
||||
from .scriptapi import ROSLaunch
|
||||
from .pmon import Process
|
||||
|
||||
try:
|
||||
from rosmaster import DEFAULT_MASTER_PORT
|
||||
except:
|
||||
DEFAULT_MASTER_PORT = 11311
|
||||
|
||||
from rosmaster.master_api import NUM_WORKERS
|
||||
|
||||
NAME = 'roslaunch'
|
||||
|
||||
def configure_logging(uuid):
|
||||
"""
|
||||
scripts using roslaunch MUST call configure_logging
|
||||
"""
|
||||
try:
|
||||
import socket
|
||||
import rosgraph.roslogging
|
||||
logfile_basename = os.path.join(uuid, '%s-%s-%s.log'%(NAME, socket.gethostname(), os.getpid()))
|
||||
# additional: names of python packages we depend on that may also be logging
|
||||
logfile_name = rosgraph.roslogging.configure_logging(NAME, filename=logfile_basename)
|
||||
if logfile_name:
|
||||
print("... logging to %s"%logfile_name)
|
||||
|
||||
# add logger to internal roslaunch logging infrastructure
|
||||
logger = logging.getLogger('roslaunch')
|
||||
roslaunch_core.add_printlog_handler(logger.info)
|
||||
roslaunch_core.add_printerrlog_handler(logger.error)
|
||||
except:
|
||||
print("WARNING: unable to configure logging. No log files will be generated", file=sys.stderr)
|
||||
|
||||
def write_pid_file(options_pid_fn, options_core, port):
|
||||
if options_pid_fn or options_core:
|
||||
# #2987
|
||||
ros_home = rospkg.get_ros_home()
|
||||
if options_pid_fn:
|
||||
pid_fn = os.path.expanduser(options_pid_fn)
|
||||
if os.path.dirname(pid_fn) == ros_home and not os.path.exists(ros_home):
|
||||
os.makedirs(ros_home)
|
||||
else:
|
||||
# NOTE: this assumption is not 100% valid until work on #3097 is complete
|
||||
if port is None:
|
||||
port = DEFAULT_MASTER_PORT
|
||||
pid_fn = os.path.join(ros_home, 'roscore-%s.pid'%(port))
|
||||
# #3828
|
||||
if not os.path.exists(ros_home):
|
||||
os.makedirs(ros_home)
|
||||
|
||||
with open(pid_fn, "w") as f:
|
||||
f.write(str(os.getpid()))
|
||||
|
||||
def _get_optparse():
|
||||
from optparse import OptionParser
|
||||
|
||||
usage = "usage: %prog [options] [package] <filename> [arg_name:=value...]\n"
|
||||
usage += " %prog [options] <filename> [<filename>...] [arg_name:=value...]\n\n"
|
||||
usage += "If <filename> is a single dash ('-'), launch XML is read from standard input."
|
||||
parser = OptionParser(usage=usage, prog=NAME)
|
||||
parser.add_option("--files",
|
||||
dest="file_list", default=False, action="store_true",
|
||||
help="Print list files loaded by launch file, including launch file itself")
|
||||
parser.add_option("--args",
|
||||
dest="node_args", default=None,
|
||||
help="Print command-line arguments for node", metavar="NODE_NAME")
|
||||
parser.add_option("--nodes",
|
||||
dest="node_list", default=False, action="store_true",
|
||||
help="Print list of node names in launch file")
|
||||
parser.add_option("--find-node",
|
||||
dest="find_node", default=None,
|
||||
help="Find launch file that node is defined in", metavar="NODE_NAME")
|
||||
parser.add_option("-c", "--child",
|
||||
dest="child_name", default=None,
|
||||
help="Run as child service 'NAME'. Required with -u", metavar="NAME")
|
||||
parser.add_option("--local",
|
||||
dest="local_only", default=False, action="store_true",
|
||||
help="Do not launch remote nodes")
|
||||
# #2370
|
||||
parser.add_option("--screen",
|
||||
dest="force_screen", default=False, action="store_true",
|
||||
help="Force output of all local nodes to screen")
|
||||
parser.add_option("-u", "--server_uri",
|
||||
dest="server_uri", default=None,
|
||||
help="URI of server. Required with -c", metavar="URI")
|
||||
parser.add_option("--run_id",
|
||||
dest="run_id", default=None,
|
||||
help="run_id of session. Required with -c", metavar="RUN_ID")
|
||||
# #1254: wait until master comes online before starting
|
||||
parser.add_option("--wait", action="store_true",
|
||||
dest="wait_for_master", default=False,
|
||||
help="wait for master to start before launching")
|
||||
parser.add_option("-p", "--port",
|
||||
dest="port", default=None,
|
||||
help="master port. Only valid if master is launched", metavar="PORT")
|
||||
parser.add_option("--core", action="store_true",
|
||||
dest="core", default=False,
|
||||
help="Launch core services only")
|
||||
parser.add_option("--pid",
|
||||
dest="pid_fn", default="",
|
||||
help="write the roslaunch pid to filename")
|
||||
parser.add_option("-v", action="store_true",
|
||||
dest="verbose", default=False,
|
||||
help="verbose printing")
|
||||
# 2685 - Dump parameters of launch files
|
||||
parser.add_option("--dump-params", default=False, action="store_true",
|
||||
dest="dump_params",
|
||||
help="Dump parameters of all roslaunch files to stdout")
|
||||
parser.add_option("--skip-log-check", default=False, action="store_true",
|
||||
dest="skip_log_check",
|
||||
help="skip check size of log folder")
|
||||
parser.add_option("--ros-args", default=False, action="store_true",
|
||||
dest="ros_args",
|
||||
help="Display command-line arguments for this launch file")
|
||||
parser.add_option("--disable-title", default=False, action="store_true",
|
||||
dest="disable_title",
|
||||
help="Disable setting of terminal title")
|
||||
parser.add_option("-w", "--numworkers",
|
||||
dest="num_workers", default=NUM_WORKERS, type=int,
|
||||
help="override number of worker threads. Only valid for core services.", metavar="NUM_WORKERS")
|
||||
parser.add_option("-t", "--timeout",
|
||||
dest="timeout",
|
||||
help="override the socket connection timeout (in seconds). Only valid for core services.", metavar="TIMEOUT")
|
||||
|
||||
return parser
|
||||
|
||||
def _validate_args(parser, options, args):
|
||||
# validate args first so we don't spin up any resources
|
||||
if options.child_name:
|
||||
if not options.server_uri:
|
||||
parser.error("--child option requires --server_uri to be set as well")
|
||||
if not options.run_id:
|
||||
parser.error("--child option requires --run_id to be set as well")
|
||||
if options.port:
|
||||
parser.error("port option cannot be used with roslaunch child mode")
|
||||
if args:
|
||||
parser.error("Input files are not allowed when run in child mode")
|
||||
elif options.core:
|
||||
if args:
|
||||
parser.error("Input files are not allowed when launching core")
|
||||
if options.run_id:
|
||||
parser.error("--run_id should only be set for child roslaunches (-c)")
|
||||
|
||||
# we don't actually do anything special for core as the roscore.xml file
|
||||
# is an implicit include for any roslaunch
|
||||
|
||||
elif len(args) == 0:
|
||||
parser.error("you must specify at least one input file")
|
||||
elif [f for f in args if not (f == '-' or os.path.exists(f))]:
|
||||
parser.error("The following input files do not exist: %s"%f)
|
||||
|
||||
if args.count('-') > 1:
|
||||
parser.error("Only a single instance of the dash ('-') may be specified.")
|
||||
|
||||
if len([x for x in [options.node_list, options.find_node, options.node_args, options.ros_args] if x]) > 1:
|
||||
parser.error("only one of [--nodes, --find-node, --args --ros-args] may be specified")
|
||||
|
||||
def main(argv=sys.argv):
|
||||
options = None
|
||||
logger = None
|
||||
try:
|
||||
from . import rlutil
|
||||
parser = _get_optparse()
|
||||
|
||||
(options, args) = parser.parse_args(argv[1:])
|
||||
args = rlutil.resolve_launch_arguments(args)
|
||||
_validate_args(parser, options, args)
|
||||
|
||||
# node args doesn't require any roslaunch infrastructure, so process it first
|
||||
if any([options.node_args, options.node_list, options.find_node, options.dump_params, options.file_list, options.ros_args]):
|
||||
if options.node_args and not args:
|
||||
parser.error("please specify a launch file")
|
||||
|
||||
from . import node_args
|
||||
if options.node_args:
|
||||
node_args.print_node_args(options.node_args, args)
|
||||
elif options.find_node:
|
||||
node_args.print_node_filename(options.find_node, args)
|
||||
# Dump parameters, #2685
|
||||
elif options.dump_params:
|
||||
roslaunch_param_dump.dump_params(args)
|
||||
elif options.file_list:
|
||||
rlutil.print_file_list(args)
|
||||
elif options.ros_args:
|
||||
import arg_dump as roslaunch_arg_dump
|
||||
roslaunch_arg_dump.dump_args(args)
|
||||
else:
|
||||
node_args.print_node_list(args)
|
||||
return
|
||||
|
||||
# we have to wait for the master here because we don't have the run_id yet
|
||||
if options.wait_for_master:
|
||||
if options.core:
|
||||
parser.error("--wait cannot be used with roscore")
|
||||
rlutil._wait_for_master()
|
||||
|
||||
# write the pid to a file
|
||||
write_pid_file(options.pid_fn, options.core, options.port)
|
||||
|
||||
# spin up the logging infrastructure. have to wait until we can read options.run_id
|
||||
uuid = rlutil.get_or_generate_uuid(options.run_id, options.wait_for_master)
|
||||
configure_logging(uuid)
|
||||
|
||||
# #3088: don't check disk usage on remote machines
|
||||
if not options.child_name and not options.skip_log_check:
|
||||
# #2761
|
||||
rlutil.check_log_disk_usage()
|
||||
|
||||
logger = logging.getLogger('roslaunch')
|
||||
logger.info("roslaunch starting with args %s"%str(argv))
|
||||
logger.info("roslaunch env is %s"%os.environ)
|
||||
|
||||
if options.child_name:
|
||||
logger.info('starting in child mode')
|
||||
|
||||
# This is a roslaunch child, spin up client server.
|
||||
# client spins up an XML-RPC server that waits for
|
||||
# commands and configuration from the server.
|
||||
from . import child as roslaunch_child
|
||||
c = roslaunch_child.ROSLaunchChild(uuid, options.child_name, options.server_uri)
|
||||
c.run()
|
||||
else:
|
||||
logger.info('starting in server mode')
|
||||
|
||||
# #1491 change terminal name
|
||||
if not options.disable_title:
|
||||
rlutil.change_terminal_name(args, options.core)
|
||||
|
||||
# Read roslaunch string from stdin when - is passed as launch filename.
|
||||
roslaunch_strs = []
|
||||
if '-' in args:
|
||||
roslaunch_core.printlog("Passed '-' as file argument, attempting to read roslaunch XML from stdin.")
|
||||
roslaunch_strs.append(sys.stdin.read())
|
||||
roslaunch_core.printlog("... %d bytes read successfully.\n" % len(roslaunch_strs[-1]))
|
||||
args.remove('-')
|
||||
|
||||
# This is a roslaunch parent, spin up parent server and launch processes.
|
||||
# args are the roslaunch files to load
|
||||
from . import parent as roslaunch_parent
|
||||
# force a port binding spec if we are running a core
|
||||
if options.core:
|
||||
options.port = options.port or DEFAULT_MASTER_PORT
|
||||
p = roslaunch_parent.ROSLaunchParent(uuid, args, roslaunch_strs=roslaunch_strs,
|
||||
is_core=options.core, port=options.port, local_only=options.local_only,
|
||||
verbose=options.verbose, force_screen=options.force_screen,
|
||||
num_workers=options.num_workers, timeout=options.timeout)
|
||||
p.start()
|
||||
p.spin()
|
||||
|
||||
except RLException as e:
|
||||
roslaunch_core.printerrlog(str(e))
|
||||
roslaunch_core.printerrlog('The traceback for the exception was written to the log file')
|
||||
if logger:
|
||||
logger.error(traceback.format_exc())
|
||||
sys.exit(1)
|
||||
except ValueError as e:
|
||||
# TODO: need to trap better than this high-level trap
|
||||
roslaunch_core.printerrlog(str(e))
|
||||
roslaunch_core.printerrlog('The traceback for the exception was written to the log file')
|
||||
if logger:
|
||||
logger.error(traceback.format_exc())
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
finally:
|
||||
# remove the pid file
|
||||
if options is not None and options.pid_fn:
|
||||
try: os.unlink(options.pid_fn)
|
||||
except os.error: pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
82
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/arg_dump.py
vendored
Normal file
82
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/arg_dump.py
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2013, 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$
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
"""
|
||||
Utility module of roslaunch that computes the command-line arguments
|
||||
for a launch file.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import roslaunch.xmlloader
|
||||
from roslaunch.core import RLException
|
||||
from roslaunch.config import load_config_default
|
||||
|
||||
def get_args(roslaunch_files):
|
||||
loader = roslaunch.xmlloader.XmlLoader(resolve_anon=False)
|
||||
config = load_config_default(roslaunch_files, None, loader=loader, verbose=False, assign_machines=False)
|
||||
return loader.root_context.resolve_dict.get('arg_doc', {})
|
||||
|
||||
def dump_args(roslaunch_files):
|
||||
"""
|
||||
Print list of args to screen. Will cause system exit if exception
|
||||
occurs. This is a subroutine for the roslaunch main handler.
|
||||
|
||||
@param roslaunch_files: list of launch files to load
|
||||
@type roslaunch_files: str
|
||||
"""
|
||||
|
||||
try:
|
||||
args = get_args(roslaunch_files)
|
||||
|
||||
if len(args) == 0:
|
||||
print("No arguments.")
|
||||
else:
|
||||
required_args = [(name, (doc or 'undocumented', default)) for (name, (doc, default)) in args.items() if not default]
|
||||
optional_args = [(name, (doc or 'undocumented', default)) for (name, (doc, default)) in args.items() if default]
|
||||
|
||||
if len(required_args) > 0:
|
||||
print("Required Arguments:")
|
||||
for (name, (doc, _)) in sorted(required_args):
|
||||
print(" %s: %s" % (name, doc))
|
||||
|
||||
if len(optional_args) > 0:
|
||||
print("Optional Arguments:")
|
||||
for (name, (doc, default)) in sorted(optional_args):
|
||||
print(" %s (default \"%s\"): %s" % (name, default, doc))
|
||||
|
||||
except RLException as e:
|
||||
print(str(e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
125
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/child.py
vendored
Normal file
125
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/child.py
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
"""
|
||||
ROSLaunch child server.
|
||||
|
||||
ROSLaunch has a client/server architecture for running remote
|
||||
processes. When a user runs roslaunch, this creates a "parent"
|
||||
roslaunch process. This parent process will then start "child"
|
||||
processes on remote machines. The parent can then invoke methods on
|
||||
this child process to launch remote processes, and the child can
|
||||
invoke methods on the parent to provide feedback.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
import roslaunch.core
|
||||
import roslaunch.pmon
|
||||
import roslaunch.server
|
||||
|
||||
class ROSLaunchChild(object):
|
||||
"""
|
||||
ROSLaunchChild infrastructure.
|
||||
|
||||
This must be called from the Python Main thread due to signal registration.
|
||||
"""
|
||||
|
||||
def __init__(self, run_id, name, server_uri):
|
||||
"""
|
||||
Startup roslaunch remote client XML-RPC services. Blocks until shutdown
|
||||
@param run_id: UUID of roslaunch session
|
||||
@type run_id: str
|
||||
@param name: name of remote client
|
||||
@type name: str
|
||||
@param server_uri: XML-RPC URI of roslaunch server
|
||||
@type server_uri: str
|
||||
@return: XML-RPC URI
|
||||
@rtype: str
|
||||
"""
|
||||
roslaunch.core.set_child_mode(True)
|
||||
|
||||
self.logger = logging.getLogger("roslaunch.child")
|
||||
self.run_id = run_id
|
||||
self.name = name
|
||||
self.server_uri = server_uri
|
||||
self.child_server = None
|
||||
self.pm = None
|
||||
|
||||
roslaunch.pmon._init_signal_handlers()
|
||||
|
||||
def _start_pm(self):
|
||||
"""
|
||||
Start process monitor for child roslaunch
|
||||
"""
|
||||
# start process monitor
|
||||
# - this test is mainly here so that testing logic can
|
||||
# override process monitor with a mock
|
||||
if self.pm is None:
|
||||
self.pm = roslaunch.pmon.start_process_monitor()
|
||||
if self.pm is None:
|
||||
# this should only happen if a shutdown signal is received during startup
|
||||
raise roslaunch.core.RLException("cannot startup remote child: unable to start process monitor.")
|
||||
self.logger.debug("started process monitor")
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Runs child. Blocks until child processes exit.
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
self.logger.info("starting roslaunch child process [%s], server URI is [%s]", self.name, self.server_uri)
|
||||
self._start_pm()
|
||||
self.child_server = roslaunch.server.ROSLaunchChildNode(self.run_id, self.name, self.server_uri, self.pm)
|
||||
self.logger.info("... creating XMLRPC server for child")
|
||||
self.child_server.start()
|
||||
self.logger.info("... started XMLRPC server for child")
|
||||
# block until process monitor is shutdown
|
||||
self.pm.mainthread_spin()
|
||||
self.logger.info("... process monitor is done spinning")
|
||||
except:
|
||||
self.logger.error(traceback.format_exc())
|
||||
raise
|
||||
finally:
|
||||
if self.pm:
|
||||
self.pm.shutdown()
|
||||
self.pm.join()
|
||||
if self.child_server:
|
||||
self.child_server.shutdown('roslaunch child complete')
|
||||
|
||||
def shutdown(self):
|
||||
if self.pm:
|
||||
self.pm.shutdown()
|
||||
self.pm.join()
|
||||
473
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/config.py
vendored
Normal file
473
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/config.py
vendored
Normal file
@@ -0,0 +1,473 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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: launch.py 2165 2008-09-17 22:38:49Z sfkwc $
|
||||
|
||||
"""
|
||||
Defines the L{ROSLaunchConfig} object, which holds and the state of
|
||||
the roslaunch file.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import types
|
||||
|
||||
import rospkg
|
||||
import rospkg.distro
|
||||
import rosgraph.names
|
||||
import rosgraph.network
|
||||
|
||||
from .core import Master, local_machine, is_machine_local, RLException
|
||||
import roslaunch.loader
|
||||
import roslaunch.xmlloader
|
||||
|
||||
try:
|
||||
from rosmaster import DEFAULT_MASTER_PORT
|
||||
except:
|
||||
DEFAULT_MASTER_PORT = 11311
|
||||
|
||||
def namespaces_of(name):
|
||||
"""
|
||||
utility to determine namespaces of a name
|
||||
@raises ValueError
|
||||
@raises TypeError
|
||||
"""
|
||||
if name is None:
|
||||
raise ValueError('name')
|
||||
try:
|
||||
if not isinstance(name, basestring):
|
||||
raise TypeError('name')
|
||||
except NameError:
|
||||
if not isinstance(name, str):
|
||||
raise TypeError('name')
|
||||
if not name:
|
||||
return ['/']
|
||||
|
||||
splits = [x for x in name.split('/') if x]
|
||||
return ['/'] + ['/'+'/'.join(splits[:i]) for i in range(1, len(splits))]
|
||||
|
||||
def get_roscore_filename():
|
||||
# precedence: look for version in /etc/ros. If it's not there, fall back to roslaunch package
|
||||
filename = os.path.join(rospkg.get_etc_ros_dir(), 'roscore.xml')
|
||||
if os.path.isfile(filename):
|
||||
return filename
|
||||
r = rospkg.RosPack()
|
||||
return os.path.join(r.get_path('roslaunch'), 'resources', 'roscore.xml')
|
||||
|
||||
def load_roscore(loader, config, verbose=True):
|
||||
"""
|
||||
Load roscore configuration into the ROSLaunchConfig using the specified XmlLoader
|
||||
@param config ROSLaunchConfig
|
||||
@param loader XmlLoader
|
||||
"""
|
||||
f_roscore = get_roscore_filename()
|
||||
logging.getLogger('roslaunch').info('loading roscore config file %s'%f_roscore)
|
||||
loader.load(f_roscore, config, core=True, verbose=verbose)
|
||||
|
||||
def calculate_env_loader(env=None):
|
||||
"""
|
||||
@raise RLException
|
||||
"""
|
||||
if env is None:
|
||||
env = os.environ
|
||||
# guess the env loader based on distro name
|
||||
distro_name = rospkg.distro.current_distro_codename()
|
||||
# sanity check
|
||||
if distro_name in ['electric', 'diamondback', 'cturtle']:
|
||||
raise RLException("This version of roslaunch is not compatible with pre-Fuerte ROS distributions")
|
||||
return '/opt/ros/%s/env.sh'%(distro_name)
|
||||
|
||||
def _summary_name(node):
|
||||
"""
|
||||
Generate summary label for node based on its package, type, and name
|
||||
"""
|
||||
if node.name:
|
||||
return "%s (%s/%s)"%(node.name, node.package, node.type)
|
||||
else:
|
||||
return "%s/%s"%(node.package, node.type)
|
||||
|
||||
class ROSLaunchConfig(object):
|
||||
"""
|
||||
ROSLaunchConfig is the container for the loaded roslaunch file state. It also
|
||||
is responsible for validating then executing the desired state.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize an empty config object. Master defaults to the environment's master.
|
||||
"""
|
||||
self.master = Master()
|
||||
self.nodes_core = []
|
||||
self.nodes = [] #nodes are unnamed
|
||||
|
||||
self.roslaunch_files = [] # metadata about files used to create config
|
||||
|
||||
# list of resolved node names. This is so that we can check for naming collisions
|
||||
self.resolved_node_names = []
|
||||
|
||||
self.tests = []
|
||||
self.machines = {} #key is name
|
||||
self.params = {} #key is name
|
||||
self.clear_params = []
|
||||
self.executables = []
|
||||
|
||||
# for tools like roswtf
|
||||
self.config_errors = []
|
||||
|
||||
m = local_machine() #for local exec
|
||||
self.machines[m.name] = m
|
||||
self._assign_machines_complete = False
|
||||
self._remote_nodes_present = None
|
||||
|
||||
self.logger = logging.getLogger('roslaunch')
|
||||
|
||||
def add_roslaunch_file(self, f):
|
||||
"""
|
||||
Add metadata about file used to create config
|
||||
"""
|
||||
self.roslaunch_files.append(f)
|
||||
|
||||
def add_config_error(self, msg):
|
||||
"""
|
||||
Report human-readable error message related to configuration error
|
||||
@param msg: error message
|
||||
@type msg: str
|
||||
"""
|
||||
self.config_errors.append(msg)
|
||||
|
||||
def set_master(self, m):
|
||||
"""
|
||||
Set the master configuration
|
||||
@param m: Master
|
||||
@type m: L{Master}
|
||||
"""
|
||||
self.master = m
|
||||
|
||||
def has_remote_nodes(self):
|
||||
"""
|
||||
@return: True if roslaunch will launch nodes on a remote machine
|
||||
@rtype: bool
|
||||
@raises: RLException
|
||||
"""
|
||||
if not self._assign_machines_complete:
|
||||
raise RLException("ERROR: has_remote_nodes() cannot be called until prelaunch check is complete")
|
||||
return self._remote_nodes_present
|
||||
|
||||
def assign_machines(self):
|
||||
"""
|
||||
Assign nodes to machines and determine whether or not there are any remote machines
|
||||
"""
|
||||
# don't repeat machine assignment
|
||||
if self._assign_machines_complete:
|
||||
return
|
||||
|
||||
machine_unify_dict = {}
|
||||
|
||||
self._assign_machines_complete = True
|
||||
# #653: current have to set all core nodes to local launch
|
||||
local_machine = self.machines['']
|
||||
for n in self.nodes_core:
|
||||
n.machine = local_machine
|
||||
|
||||
#for n in self.nodes_core + self.nodes + self.tests:
|
||||
for n in self.nodes + self.tests:
|
||||
m = self._select_machine(n)
|
||||
|
||||
# if machines have the same config keys it means that they are identical except
|
||||
# for their name. we unify the machine assignments so that we don't use
|
||||
# extra resources.
|
||||
config_key = m.config_key()
|
||||
if config_key in machine_unify_dict:
|
||||
new_m = machine_unify_dict[config_key]
|
||||
if m != new_m:
|
||||
self.logger.info("... changing machine assignment from [%s] to [%s] as they are equivalent", m.name, new_m.name)
|
||||
m = new_m
|
||||
else:
|
||||
machine_unify_dict[config_key] = m
|
||||
n.machine = m
|
||||
self.logger.info("... selected machine [%s] for node of type [%s/%s]", m.name, n.package, n.type)
|
||||
|
||||
# determine whether or not there are any machines we will need
|
||||
# to setup remote roslaunch clients for
|
||||
self._remote_nodes_present = False
|
||||
if [m for m in machine_unify_dict.values() if not is_machine_local(m)]:
|
||||
self._remote_nodes_present = True
|
||||
|
||||
def summary(self, local=False):
|
||||
"""
|
||||
Get a human-readable string summary of the launch
|
||||
@param local bool: if True, only print local nodes
|
||||
@return: summary
|
||||
@rtype: str
|
||||
"""
|
||||
summary = '\nSUMMARY\n========'
|
||||
if self.clear_params:
|
||||
summary += '\n\nCLEAR PARAMETERS\n' + '\n'.join(sorted([' * %s'%p for p in self.clear_params]))
|
||||
if self.params:
|
||||
def strip_string(value):
|
||||
# not dealing with non-ascii characters here
|
||||
try:
|
||||
value = str(value)
|
||||
except UnicodeEncodeError:
|
||||
return '<...>'
|
||||
max_length = 20
|
||||
if len(value) > max_length:
|
||||
value = value[:max_length - 3] + '...'
|
||||
# if non printable characters are present return replacement
|
||||
for i, char in enumerate(value):
|
||||
o = ord(char)
|
||||
if o < 32 or o > 126:
|
||||
# skip when the special characters are only trailing whitespaces
|
||||
value = value.rstrip()
|
||||
if i >= len(value):
|
||||
break
|
||||
return '<...>'
|
||||
return value
|
||||
summary += '\n\nPARAMETERS\n' + '\n'.join(sorted([' * %s: %s' % (k, strip_string(v.value)) for k, v in self.params.items()]))
|
||||
if not local:
|
||||
summary += '\n\nMACHINES\n' + '\n'.join(sorted([' * %s'%k for k in self.machines if k]))
|
||||
summary += '\n\nNODES\n'
|
||||
namespaces = {}
|
||||
if local:
|
||||
nodes = [n for n in self.nodes if is_machine_local(n.machine)]
|
||||
else:
|
||||
nodes = self.nodes
|
||||
for n in nodes:
|
||||
ns = n.namespace
|
||||
if ns not in namespaces:
|
||||
namespaces[ns] = [n]
|
||||
else:
|
||||
namespaces[ns].append(n)
|
||||
for k,v in namespaces.items():
|
||||
summary += ' %s\n'%k + '\n'.join(sorted([' %s'%_summary_name(n) for n in v]))
|
||||
summary += '\n'
|
||||
return summary
|
||||
|
||||
def add_executable(self, exe):
|
||||
"""
|
||||
Declare an exectuable to be run during the launch
|
||||
@param exe: Executable
|
||||
@type exe: L{Executable}
|
||||
@raises ValueError
|
||||
"""
|
||||
if not exe:
|
||||
raise ValueError("exe is None")
|
||||
self.executables.append(exe)
|
||||
|
||||
def add_clear_param(self, param):
|
||||
"""
|
||||
Declare a parameter to be cleared before new parameters are set
|
||||
@param param: parameter to clear
|
||||
@type param: str
|
||||
"""
|
||||
self.clear_params.append(param)
|
||||
|
||||
def add_param(self, p, filename=None, verbose=True):
|
||||
"""
|
||||
Declare a parameter to be set on the param server before launching nodes
|
||||
@param p: parameter instance
|
||||
@type p: L{Param}
|
||||
"""
|
||||
key = p.key
|
||||
|
||||
# check for direct overrides
|
||||
if key in self.params and self.params[key] != p:
|
||||
if filename:
|
||||
self.logger.debug("[%s] overriding parameter [%s]"%(filename, p.key))
|
||||
else:
|
||||
self.logger.debug("overriding parameter [%s]"%p.key)
|
||||
# check for parent conflicts
|
||||
for parent_key in [pk for pk in namespaces_of(key) if pk in self.params]:
|
||||
self.add_config_error("parameter [%s] conflicts with parent parameter [%s]"%(key, parent_key))
|
||||
|
||||
self.params[key] = p
|
||||
if verbose:
|
||||
print("Added parameter [%s]" % key)
|
||||
t = type(p.value)
|
||||
if t in [bool, int, float]:
|
||||
self.logger.debug("add_param[%s]: type [%s] value [%s]"%(p.key, t, p.value))
|
||||
else:
|
||||
self.logger.debug("add_param[%s]: type [%s]"%(p.key, t))
|
||||
|
||||
def add_machine(self, m, verbose=True):
|
||||
"""
|
||||
Declare a machine and associated parameters so that it can be used for
|
||||
running nodes.
|
||||
@param m: machine instance
|
||||
@type m: L{Machine}
|
||||
@return: True if new machine added, False if machine already specified.
|
||||
@rtype: bool
|
||||
@raises RLException: if cannot add machine as specified
|
||||
"""
|
||||
name = m.name
|
||||
# Fuerte: all machines must have an env loader. We can guess
|
||||
# it from the distro name for easier migration.
|
||||
if not m.env_loader:
|
||||
m.env_loader = calculate_env_loader()
|
||||
if m.address == 'localhost': #simplify address comparison
|
||||
address = rosgraph.network.get_local_address()
|
||||
self.logger.info("addMachine[%s]: remapping localhost address to %s"%(name, address))
|
||||
if name in self.machines:
|
||||
if m != self.machines[name]:
|
||||
raise RLException("Machine [%s] already added and does not match duplicate entry"%name)
|
||||
return False
|
||||
else:
|
||||
self.machines[name] = m
|
||||
if verbose:
|
||||
print("Added machine [%s]" % name)
|
||||
return True
|
||||
|
||||
def add_test(self, test, verbose=True):
|
||||
"""
|
||||
Add test declaration. Used by rostest
|
||||
@param test: test node instance to add to launch
|
||||
@type test: L{Test}
|
||||
"""
|
||||
self.tests.append(test)
|
||||
|
||||
def add_node(self, node, core=False, verbose=True):
|
||||
"""
|
||||
Add node declaration
|
||||
@param node: node instance to add to launch
|
||||
@type node: L{Node}
|
||||
@param core: if True, node is a ROS core node
|
||||
@type core: bool
|
||||
@raises RLException: if ROS core node is missing required name
|
||||
"""
|
||||
if node.name:
|
||||
# check for duplicates
|
||||
resolved_name = rosgraph.names.ns_join(node.namespace, node.name)
|
||||
matches = [n for n in self.resolved_node_names if n == resolved_name]
|
||||
if matches:
|
||||
raise RLException("roslaunch file contains multiple nodes named [%s].\nPlease check all <node> 'name' attributes to make sure they are unique.\nAlso check that $(anon id) use different ids."%resolved_name)
|
||||
else:
|
||||
self.resolved_node_names.append(resolved_name)
|
||||
|
||||
if not core:
|
||||
self.nodes.append(node)
|
||||
if verbose:
|
||||
print("Added node of type [%s/%s] in namespace [%s]" % (node.package, node.type, node.namespace))
|
||||
self.logger.info("Added node of type [%s/%s] in namespace [%s]", node.package, node.type, node.namespace)
|
||||
else:
|
||||
if not node.name:
|
||||
raise RLException("ROS core nodes must have a name. [%s/%s]"%(node.package, node.type))
|
||||
self.nodes_core.append(node)
|
||||
if verbose:
|
||||
print("Added core node of type [%s/%s] in namespace [%s]" % (node.package, node.type, node.namespace))
|
||||
self.logger.info("Added core node of type [%s/%s] in namespace [%s]", node.package, node.type, node.namespace)
|
||||
|
||||
def _select_machine(self, node):
|
||||
"""
|
||||
Select a machine for a node to run on. For nodes that are
|
||||
already assigned to a machine, this will map the string name to
|
||||
a L{Machine} instance. If the node isn't already tagged with a
|
||||
particular machine, one will be selected for it.
|
||||
@param node: node to assign machine for
|
||||
@type node: L{Node}
|
||||
@return: machine to run on
|
||||
@rtype: L{Machine}
|
||||
@raises RLException: If machine state is improperly configured
|
||||
"""
|
||||
machine = node.machine_name
|
||||
#Lookup machine
|
||||
if machine:
|
||||
if not machine in self.machines:
|
||||
raise RLException("ERROR: unknown machine [%s]"%machine)
|
||||
return self.machines[machine]
|
||||
else:
|
||||
# assign to local machine
|
||||
return self.machines['']
|
||||
|
||||
def load_config_default(roslaunch_files, port, roslaunch_strs=None, loader=None, verbose=False, assign_machines=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: list of launch files to load
|
||||
@type roslaunch_files: [str]
|
||||
@param port: roscore/master port override. Set to 0 or None to use default.
|
||||
@type port: int
|
||||
@param roslaunch_strs: (optional) roslaunch XML strings to load
|
||||
@type roslaunch_strs: [str]
|
||||
@param verbose: (optional) print info to screen about model as it is loaded.
|
||||
@type verbose: bool
|
||||
@param assign_machines: (optional) assign nodes to machines (default: True)
|
||||
@type assign_machines: bool
|
||||
@return: initialized rosconfig instance
|
||||
@rytpe: L{ROSLaunchConfig} initialized rosconfig instance
|
||||
@raises: RLException
|
||||
"""
|
||||
logger = logging.getLogger('roslaunch.config')
|
||||
|
||||
# This is the main roslaunch server process. Load up the
|
||||
# files specified on the command line and launch the
|
||||
# requested resourcs.
|
||||
|
||||
config = ROSLaunchConfig()
|
||||
if port:
|
||||
config.master.uri = rosgraph.network.create_local_xmlrpc_uri(port)
|
||||
|
||||
loader = loader or roslaunch.xmlloader.XmlLoader()
|
||||
|
||||
# load the roscore file first. we currently have
|
||||
# last-declaration wins rules. roscore is just a
|
||||
# roslaunch file with special load semantics
|
||||
load_roscore(loader, config, verbose=verbose)
|
||||
|
||||
# load the roslaunch_files into the config
|
||||
for f in roslaunch_files:
|
||||
try:
|
||||
logger.info('loading config file %s'%f)
|
||||
loader.load(f, config, verbose=verbose)
|
||||
except roslaunch.xmlloader.XmlParseException as e:
|
||||
raise RLException(e)
|
||||
except roslaunch.loader.LoadException as e:
|
||||
raise RLException(e)
|
||||
|
||||
# we need this for the hardware test systems, which builds up
|
||||
# roslaunch launch files in memory
|
||||
if roslaunch_strs:
|
||||
for launch_str in roslaunch_strs:
|
||||
try:
|
||||
logger.info('loading config file from string')
|
||||
loader.load_string(launch_str, config)
|
||||
except roslaunch.xmlloader.XmlParseException as e:
|
||||
raise RLException('Launch string: %s\nException: %s'%(launch_str, e))
|
||||
except roslaunch.loader.LoadException as e:
|
||||
raise RLException('Launch string: %s\nException: %s'%(launch_str, e))
|
||||
|
||||
# choose machines for the nodes
|
||||
if assign_machines:
|
||||
config.assign_machines()
|
||||
return config
|
||||
|
||||
675
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/core.py
vendored
Normal file
675
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/core.py
vendored
Normal file
@@ -0,0 +1,675 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
"""
|
||||
Core roslaunch model and lower-level utility routines.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
import socket
|
||||
import sys
|
||||
try:
|
||||
from xmlrpc.client import MultiCall, ServerProxy
|
||||
except ImportError:
|
||||
from xmlrpclib import MultiCall, ServerProxy
|
||||
|
||||
import rospkg
|
||||
|
||||
import rosgraph
|
||||
import rosgraph.names
|
||||
import rosgraph.network
|
||||
|
||||
from xml.sax.saxutils import escape
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
# Python 3: for _xml_escape
|
||||
basestring = unicode = str
|
||||
|
||||
class RLException(Exception):
|
||||
"""Base roslaunch exception type"""
|
||||
pass
|
||||
|
||||
## Phases allow executables to be assigned to a particular run period
|
||||
PHASE_SETUP = 'setup'
|
||||
PHASE_RUN = 'run'
|
||||
PHASE_TEARDOWN = 'teardown'
|
||||
|
||||
_child_mode = False
|
||||
def is_child_mode():
|
||||
"""
|
||||
:returns: ``True`` if roslaunch is running in remote child mode, ``bool``
|
||||
"""
|
||||
return _child_mode
|
||||
def set_child_mode(child_mode):
|
||||
"""
|
||||
:param child_mode: True if roslaunch is running in remote
|
||||
child mode, ``bool``
|
||||
"""
|
||||
global _child_mode
|
||||
_child_mode = child_mode
|
||||
|
||||
def is_machine_local(machine):
|
||||
"""
|
||||
Check to see if machine is local. NOTE: a machine is not local if
|
||||
its user credentials do not match the current user.
|
||||
:param machine: Machine, ``Machine``
|
||||
:returns: True if machine is local and doesn't require remote login, ``bool``
|
||||
"""
|
||||
try:
|
||||
# If Python has ipv6 disabled but machine.address can be resolved somehow to an ipv6 address, then host[4][0] will be int
|
||||
machine_ips = [host[4][0] for host in socket.getaddrinfo(machine.address, 0, 0, 0, socket.SOL_TCP) if isinstance(host[4][0], str)]
|
||||
except socket.gaierror:
|
||||
raise RLException("cannot resolve host address for machine [%s]"%machine.address)
|
||||
local_addresses = ['localhost'] + rosgraph.network.get_local_addresses()
|
||||
# check 127/8 and local addresses
|
||||
is_local = ([ip for ip in machine_ips if (ip.startswith('127.') or ip == '::1')] != []) or (set(machine_ips) & set(local_addresses) != set())
|
||||
|
||||
#491: override local to be ssh if machine.user != local user
|
||||
if is_local and machine.user:
|
||||
import getpass
|
||||
is_local = machine.user == getpass.getuser()
|
||||
return is_local
|
||||
|
||||
_printlog_handlers = []
|
||||
_printerrlog_handlers = []
|
||||
def printlog(msg):
|
||||
"""
|
||||
Core utility for printing message to stdout as well as printlog handlers
|
||||
:param msg: message to print, ``str``
|
||||
"""
|
||||
for h in _printlog_handlers:
|
||||
try: # don't let this bomb out the actual code
|
||||
h(msg)
|
||||
except:
|
||||
pass
|
||||
try: # don't let this bomb out the actual code
|
||||
print(msg)
|
||||
except:
|
||||
pass
|
||||
|
||||
def printlog_bold(msg):
|
||||
"""
|
||||
Similar to L{printlog()}, but the message printed to screen is bolded for greater clarity
|
||||
:param msg: message to print, ``str``
|
||||
"""
|
||||
for h in _printlog_handlers:
|
||||
try: # don't let this bomb out the actual code
|
||||
h(msg)
|
||||
except:
|
||||
pass
|
||||
try: # don't let this bomb out the actual code
|
||||
if sys.platform in ['win32']:
|
||||
print('%s' % msg) # windows console is terrifically boring
|
||||
else:
|
||||
print('\033[1m%s\033[0m' % msg)
|
||||
except:
|
||||
pass
|
||||
|
||||
def printerrlog(msg):
|
||||
"""
|
||||
Core utility for printing message to stderr as well as printerrlog handlers
|
||||
:param msg: message to print, ``str``
|
||||
"""
|
||||
for h in _printerrlog_handlers:
|
||||
try: # don't let this bomb out the actual code
|
||||
h(msg)
|
||||
except:
|
||||
pass
|
||||
# #1003: this has raised IOError (errno 104) in robot use. Better to
|
||||
# trap than let a debugging routine fault code.
|
||||
try: # don't let this bomb out the actual code
|
||||
print('\033[31m%s\033[0m' % msg, file=sys.stderr)
|
||||
except:
|
||||
pass
|
||||
|
||||
def add_printlog_handler(h):
|
||||
"""
|
||||
Register additional handler for printlog()
|
||||
"""
|
||||
_printlog_handlers.append(h)
|
||||
|
||||
def add_printerrlog_handler(h):
|
||||
"""
|
||||
Register additional handler for printerrlog()
|
||||
"""
|
||||
_printerrlog_handlers.append(h)
|
||||
|
||||
def clear_printlog_handlers():
|
||||
"""
|
||||
Delete all printlog handlers. required for testing
|
||||
"""
|
||||
del _printlog_handlers[:]
|
||||
|
||||
def clear_printerrlog_handlers():
|
||||
"""
|
||||
Delete all printerrlog handlers. required for testing
|
||||
"""
|
||||
del _printerrlog_handlers[:]
|
||||
|
||||
def setup_env(node, machine, master_uri, env=None):
|
||||
"""
|
||||
Create dictionary of environment variables to set for launched
|
||||
process.
|
||||
|
||||
setup_env() will only set ROS_*, PYTHONPATH, and user-specified
|
||||
environment variables.
|
||||
|
||||
:param machine: machine being launched on, ``Machine``
|
||||
:param node: node that is being launched or None, ``Node``
|
||||
:param master_uri: ROS master URI, ``str``
|
||||
:param env: base environment configuration, defaults to ``os.environ``
|
||||
:returns: process env dictionary, ``dict``
|
||||
"""
|
||||
if env is None:
|
||||
env = os.environ
|
||||
|
||||
d = env.copy()
|
||||
d[rosgraph.ROS_MASTER_URI] = master_uri
|
||||
|
||||
# add node-specific env args last as they have highest precedence
|
||||
if node:
|
||||
if rosgraph.ROS_NAMESPACE in d:
|
||||
del d[rosgraph.ROS_NAMESPACE]
|
||||
ns = node.namespace
|
||||
if ns[-1] == '/':
|
||||
ns = ns[:-1]
|
||||
if ns:
|
||||
d[rosgraph.ROS_NAMESPACE] = ns
|
||||
for name, value in node.env_args:
|
||||
d[name] = value
|
||||
|
||||
return d
|
||||
|
||||
def rle_wrapper(fn):
|
||||
"""
|
||||
Wrap lower-level exceptions in RLException class
|
||||
:returns:: function wrapper that throws an RLException if the
|
||||
wrapped function throws an Exception, ``fn``
|
||||
"""
|
||||
def wrapped_fn(*args):
|
||||
try:
|
||||
return fn(*args)
|
||||
except Exception as e:
|
||||
# we specifically catch RLExceptions and print their messages differently
|
||||
raise RLException("ERROR: %s"%e)
|
||||
return wrapped_fn
|
||||
|
||||
get_ros_root = rospkg.get_ros_root
|
||||
get_master_uri_env = rle_wrapper(rosgraph.get_master_uri)
|
||||
get_ros_package_path = rospkg.get_ros_package_path
|
||||
|
||||
def remap_localhost_uri(uri, force_localhost=False):
|
||||
"""
|
||||
Resolve localhost addresses to an IP address so that
|
||||
:param uri: XML-RPC URI, ``str``
|
||||
:param force_localhost: if True, URI is mapped onto the local machine no matter what, ``bool``
|
||||
"""
|
||||
hostname, port = rosgraph.network.parse_http_host_and_port(uri)
|
||||
if force_localhost or hostname == 'localhost':
|
||||
return rosgraph.network.create_local_xmlrpc_uri(port)
|
||||
else:
|
||||
return uri
|
||||
|
||||
##################################################################
|
||||
# DATA STRUCTURES
|
||||
|
||||
class Master(object):
|
||||
"""
|
||||
Data structure for representing and querying state of master
|
||||
"""
|
||||
__slots__ = ['type', 'auto', 'uri']
|
||||
ROSMASTER = 'rosmaster'
|
||||
|
||||
# deprecated
|
||||
ZENMASTER = 'zenmaster'
|
||||
|
||||
def __init__(self, type_=None, uri=None, auto=None):
|
||||
"""
|
||||
Create new Master instance.
|
||||
:param uri: master URI. Defaults to ROS_MASTER_URI environment variable, ``str``
|
||||
:param type_: Currently only support 'rosmaster', ``str``
|
||||
"""
|
||||
self.auto = None # no longer used
|
||||
self.type = type_ or Master.ROSMASTER
|
||||
self.uri = uri or get_master_uri_env()
|
||||
|
||||
def get_host(self):
|
||||
# parse from the URI
|
||||
host, _ = rosgraph.network.parse_http_host_and_port(self.uri)
|
||||
return host
|
||||
|
||||
def get_port(self):
|
||||
"""
|
||||
Get the port this master is configured for.
|
||||
"""
|
||||
# parse from the URI
|
||||
_, urlport = rosgraph.network.parse_http_host_and_port(self.uri)
|
||||
return urlport
|
||||
|
||||
def __eq__(self, m2):
|
||||
if not isinstance(m2, Master):
|
||||
return False
|
||||
else:
|
||||
return m2.type == self.type and m2.uri == self.uri
|
||||
|
||||
def get(self):
|
||||
"""
|
||||
:returns:: XMLRPC proxy for communicating with master, ``xmlrpc.client.ServerProxy``
|
||||
"""
|
||||
return ServerProxy(self.uri)
|
||||
|
||||
def get_multi(self):
|
||||
"""
|
||||
:returns:: multicall XMLRPC proxy for communicating with master, ``xmlrpc.client.MultiCall``
|
||||
"""
|
||||
return MultiCall(self.get())
|
||||
|
||||
def is_running(self):
|
||||
"""
|
||||
Check if master is running.
|
||||
:returns:: True if the master is running, ``bool``
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
to_orig = socket.getdefaulttimeout()
|
||||
# enable timeout
|
||||
socket.setdefaulttimeout(5.0)
|
||||
logging.getLogger('roslaunch').info('master.is_running[%s]'%self.uri)
|
||||
code, status, val = self.get().getPid('/roslaunch')
|
||||
if code != 1:
|
||||
raise RLException("ERROR: master failed status check: %s"%msg)
|
||||
logging.getLogger('roslaunch.core').debug('master.is_running[%s]: True'%self.uri)
|
||||
return True
|
||||
finally:
|
||||
socket.setdefaulttimeout(to_orig)
|
||||
except:
|
||||
logging.getLogger('roslaunch.core').debug('master.is_running[%s]: False'%self.uri)
|
||||
return False
|
||||
|
||||
## number of seconds that a child machine is allowed to register with
|
||||
## the parent before being considered failed
|
||||
_DEFAULT_REGISTER_TIMEOUT = 10.0
|
||||
|
||||
class Machine(object):
|
||||
"""
|
||||
Data structure for storing information about a machine in the ROS
|
||||
system. Corresponds to the 'machine' tag in the launch
|
||||
specification.
|
||||
"""
|
||||
__slots__ = ['name', 'address', 'ssh_port', 'user', 'password', 'assignable',
|
||||
'env_loader', 'timeout']
|
||||
def __init__(self, name, address,
|
||||
env_loader=None, ssh_port=22, user=None, password=None,
|
||||
assignable=True, env_args=[], timeout=None):
|
||||
"""
|
||||
:param name: machine name, ``str``
|
||||
:param address: network address of machine, ``str``
|
||||
:param env_loader: Path to environment loader, ``str``
|
||||
:param ssh_port: SSH port number, ``int``
|
||||
:param user: SSH username, ``str``
|
||||
:param password: SSH password. Not recommended for use. Use SSH keys instead., ``str``
|
||||
"""
|
||||
self.name = name
|
||||
self.env_loader = env_loader
|
||||
self.user = user or None
|
||||
self.password = password or None
|
||||
self.address = address
|
||||
self.ssh_port = ssh_port
|
||||
self.assignable = assignable
|
||||
self.timeout = timeout or _DEFAULT_REGISTER_TIMEOUT
|
||||
|
||||
def __str__(self):
|
||||
return "Machine(name[%s] env_loader[%s] address[%s] ssh_port[%s] user[%s] assignable[%s] timeout[%s])"%(self.name, self.env_loader, self.address, self.ssh_port, self.user, self.assignable, self.timeout)
|
||||
def __eq__(self, m2):
|
||||
if not isinstance(m2, Machine):
|
||||
return False
|
||||
return self.name == m2.name and \
|
||||
self.assignable == m2.assignable and \
|
||||
self.config_equals(m2)
|
||||
|
||||
def config_key(self):
|
||||
"""
|
||||
Get a key that represents the configuration of the
|
||||
machine. machines with identical configurations have identical
|
||||
keys
|
||||
|
||||
:returns:: configuration key, ``str``
|
||||
"""
|
||||
return "Machine(address[%s] env_loader[%s] ssh_port[%s] user[%s] password[%s] timeout[%s])"%(self.address, self.env_loader, self.ssh_port, self.user or '', self.password or '', self.timeout)
|
||||
|
||||
def config_equals(self, m2):
|
||||
"""
|
||||
:returns:: True if machines have identical configurations, ``bool``
|
||||
"""
|
||||
if not isinstance(m2, Machine):
|
||||
return False
|
||||
return self.config_key() == m2.config_key()
|
||||
|
||||
def __ne__(self, m2):
|
||||
return not self.__eq__(m2)
|
||||
|
||||
class Param(object):
|
||||
"""
|
||||
Data structure for storing information about a desired parameter in
|
||||
the ROS system Corresponds to the 'param' tag in the launch
|
||||
specification.
|
||||
"""
|
||||
def __init__(self, key, value):
|
||||
self.key = rosgraph.names.canonicalize_name(key)
|
||||
self.value = value
|
||||
def __eq__(self, p):
|
||||
if not isinstance(p, Param):
|
||||
return False
|
||||
return p.key == self.key and p.value == self.value
|
||||
def __ne__(self, p):
|
||||
return not self.__eq__(p)
|
||||
def __str__(self):
|
||||
return "%s=%s"%(self.key, self.value)
|
||||
def __repr__(self):
|
||||
return "%s=%s"%(self.key, self.value)
|
||||
|
||||
_local_m = None
|
||||
def local_machine():
|
||||
"""
|
||||
:returns:: Machine instance representing the local machine, ``Machine``
|
||||
"""
|
||||
global _local_m
|
||||
if _local_m is None:
|
||||
_local_m = Machine('', 'localhost')
|
||||
return _local_m
|
||||
|
||||
class Node(object):
|
||||
"""
|
||||
Data structure for storing information about a desired node in
|
||||
the ROS system Corresponds to the 'node' tag in the launch
|
||||
specification.
|
||||
"""
|
||||
__slots__ = ['package', 'type', 'name', 'namespace', \
|
||||
'machine_name', 'machine', 'args', 'respawn', \
|
||||
'respawn_delay', \
|
||||
'remap_args', 'env_args',\
|
||||
'process_name', 'output', 'cwd',
|
||||
'launch_prefix', 'required',
|
||||
'filename']
|
||||
|
||||
def __init__(self, package, node_type, name=None, namespace='/', \
|
||||
machine_name=None, args='', \
|
||||
respawn=False, respawn_delay=0.0, \
|
||||
remap_args=None,env_args=None, output=None, cwd=None, \
|
||||
launch_prefix=None, required=False, filename='<unknown>'):
|
||||
"""
|
||||
:param package: node package name, ``str``
|
||||
:param node_type: node type, ``str``
|
||||
:param name: node name, ``str``
|
||||
:param namespace: namespace for node, ``str``
|
||||
:param machine_name: name of machine to run node on, ``str``
|
||||
:param args: argument string to pass to node executable, ``str``
|
||||
:param respawn: if True, respawn node if it dies, ``bool``
|
||||
:param respawn: if respawn is True, respawn node after delay, ``float``
|
||||
:param remap_args: list of [(from, to)] remapping arguments, ``[(str, str)]``
|
||||
:param env_args: list of [(key, value)] of
|
||||
additional environment vars to set for node, ``[(str, str)]``
|
||||
:param output: where to log output to, either Node, 'screen' or 'log', ``str``
|
||||
:param cwd: current working directory of node, either 'node', 'ROS_HOME'. Default: ROS_HOME, ``str``
|
||||
:param launch_prefix: launch command/arguments to prepend to node executable arguments, ``str``
|
||||
:param required: node is required to stay running (launch fails if node dies), ``bool``
|
||||
:param filename: name of file Node was parsed from, ``str``
|
||||
|
||||
:raises: :exc:`ValueError` If parameters do not validate
|
||||
"""
|
||||
|
||||
self.package = package
|
||||
self.type = node_type
|
||||
self.name = name or None
|
||||
self.namespace = rosgraph.names.make_global_ns(namespace or '/')
|
||||
self.machine_name = machine_name or None
|
||||
self.respawn = respawn
|
||||
self.respawn_delay = respawn_delay
|
||||
self.args = args or ''
|
||||
self.remap_args = remap_args or []
|
||||
self.env_args = env_args or []
|
||||
self.output = output
|
||||
self.cwd = cwd
|
||||
if self.cwd == 'ros_home': # be lenient on case
|
||||
self.cwd = 'ROS_HOME'
|
||||
|
||||
self.launch_prefix = launch_prefix or None
|
||||
self.required = required
|
||||
self.filename = filename
|
||||
|
||||
if self.respawn and self.required:
|
||||
raise ValueError("respawn and required cannot both be set to true")
|
||||
|
||||
# validation
|
||||
if self.name and rosgraph.names.SEP in self.name: # #1821, namespaces in nodes need to be banned
|
||||
raise ValueError("node name cannot contain a namespace")
|
||||
if not len(self.package.strip()):
|
||||
raise ValueError("package must be non-empty")
|
||||
if not len(self.type.strip()):
|
||||
raise ValueError("type must be non-empty")
|
||||
if not self.output in ['log', 'screen', None]:
|
||||
raise ValueError("output must be one of 'log', 'screen'")
|
||||
if not self.cwd in ['ROS_HOME', 'node', None]:
|
||||
raise ValueError("cwd must be one of 'ROS_HOME', 'node'")
|
||||
|
||||
# Extra slots for assigning later
|
||||
|
||||
# slot to store the process name in so that we can query the
|
||||
# associated process state
|
||||
self.process_name = None
|
||||
|
||||
# machine is the assigned machine instance. should probably
|
||||
# consider storing this elsewhere as it can be inconsistent
|
||||
# with machine_name and is also a runtime, rather than
|
||||
# configuration property
|
||||
self.machine = None
|
||||
|
||||
|
||||
|
||||
def xmltype(self):
|
||||
return 'node'
|
||||
|
||||
def xmlattrs(self):
|
||||
name_str = cwd_str = respawn_str = None
|
||||
if self.name:
|
||||
name_str = self.name
|
||||
if self.cwd:
|
||||
cwd_str = self.cwd
|
||||
|
||||
return [
|
||||
('pkg', self.package),
|
||||
('type', self.type),
|
||||
('machine', self.machine_name),
|
||||
('ns', self.namespace),
|
||||
('args', self.args),
|
||||
('output', self.output),
|
||||
('cwd', cwd_str),
|
||||
('respawn', self.respawn), #not valid on <test>
|
||||
('respawn_delay', self.respawn_delay), # not valid on <test>
|
||||
('name', name_str),
|
||||
('launch-prefix', self.launch_prefix),
|
||||
('required', self.required),
|
||||
]
|
||||
|
||||
#TODO: unify with to_remote_xml using a filter_fn
|
||||
def to_xml(self):
|
||||
"""
|
||||
convert representation into XML representation. Currently cannot represent private parameters.
|
||||
:returns:: XML representation for remote machine, ``str``
|
||||
"""
|
||||
t = self.xmltype()
|
||||
attrs = [(a, v) for a, v in self.xmlattrs() if v != None]
|
||||
xmlstr = '<%s %s>\n'%(t, ' '.join(['%s="%s"'%(val[0], _xml_escape(val[1])) for val in attrs]))
|
||||
xmlstr += ''.join([' <remap from="%s" to="%s" />\n'%tuple(r) for r in self.remap_args])
|
||||
xmlstr += ''.join([' <env name="%s" value="%s" />\n'%tuple(e) for e in self.env_args])
|
||||
xmlstr += "</%s>"%t
|
||||
return xmlstr
|
||||
|
||||
def to_remote_xml(self):
|
||||
"""
|
||||
convert representation into remote representation. Remote representation does
|
||||
not include parameter settings or 'machine' attribute
|
||||
:returns:: XML representation for remote machine, ``str``
|
||||
"""
|
||||
t = self.xmltype()
|
||||
attrs = [(a, v) for a, v in self.xmlattrs() if v != None and a != 'machine']
|
||||
xmlstr = '<%s %s>\n'%(t, ' '.join(['%s="%s"'%(val[0], _xml_escape(val[1])) for val in attrs]))
|
||||
xmlstr += ''.join([' <remap from="%s" to="%s" />\n'%tuple(r) for r in self.remap_args])
|
||||
xmlstr += ''.join([' <env name="%s" value="%s" />\n'%tuple(e) for e in self.env_args])
|
||||
xmlstr += "</%s>"%t
|
||||
return xmlstr
|
||||
|
||||
def _xml_escape(s):
|
||||
"""
|
||||
Escape string for XML
|
||||
:param s: string to escape, ``str``
|
||||
:returns:: string with XML entities (<, >, \", &) escaped, ``str``
|
||||
"""
|
||||
# use official escaping to preserve unicode.
|
||||
# see import at the top for py3k-compat
|
||||
if isinstance(s, basestring):
|
||||
return escape(s, entities={'"': '"'})
|
||||
else:
|
||||
# don't escape non-string attributes
|
||||
return s
|
||||
|
||||
TEST_TIME_LIMIT_DEFAULT = 1 * 60 #seconds
|
||||
|
||||
|
||||
class Test(Node):
|
||||
"""
|
||||
A Test is a Node with special semantics that it performs a
|
||||
unit/integration test. The data model is the same except the
|
||||
option to set the respawn flag is removed.
|
||||
"""
|
||||
__slots__ = ['test_name', 'time_limit', 'retry']
|
||||
|
||||
def __init__(self, test_name, package, node_type, name=None, \
|
||||
namespace='/', machine_name=None, args='', \
|
||||
remap_args=None, env_args=None, time_limit=None, cwd=None,
|
||||
launch_prefix=None, retry=None, filename="<unknown>"):
|
||||
"""
|
||||
Construct a new test node.
|
||||
:param test_name: name of test for recording in test results, ``str``
|
||||
:param time_limit: number of seconds that a test
|
||||
should run before marked as a failure, ``int/float/long``
|
||||
"""
|
||||
super(Test, self).__init__(package, node_type, name=name, \
|
||||
namespace=namespace, \
|
||||
machine_name=machine_name, args=args, \
|
||||
remap_args=remap_args,
|
||||
env_args=env_args,
|
||||
#output always is log
|
||||
output='log', cwd=cwd,
|
||||
launch_prefix=launch_prefix, filename=filename)
|
||||
self.test_name = test_name
|
||||
|
||||
self.retry = retry or 0
|
||||
time_limit = time_limit or TEST_TIME_LIMIT_DEFAULT
|
||||
number_types = [float, int]
|
||||
try:
|
||||
number_types.append(long)
|
||||
except NameError:
|
||||
pass
|
||||
if not type(time_limit) in number_types:
|
||||
raise ValueError("'time-limit' must be a number")
|
||||
time_limit = float(time_limit) #force to floating point
|
||||
if time_limit <= 0:
|
||||
raise ValueError("'time-limit' must be a positive number")
|
||||
|
||||
self.time_limit = time_limit
|
||||
|
||||
def xmltype(self):
|
||||
return 'test'
|
||||
|
||||
def xmlattrs(self):
|
||||
"""
|
||||
NOTE: xmlattrs does not necessarily produce identical XML as
|
||||
to what it was initialized with, though the properties are the same
|
||||
"""
|
||||
attrs = Node.xmlattrs(self)
|
||||
attrs = [(a, v) for (a, v) in attrs if a not in ['respawn', \
|
||||
'respawn_delay']]
|
||||
attrs.append(('test-name', self.test_name))
|
||||
|
||||
if self.retry:
|
||||
attrs.append(('retry', str(self.retry)))
|
||||
if self.time_limit != TEST_TIME_LIMIT_DEFAULT:
|
||||
attrs.append(('time-limit', self.time_limit))
|
||||
return attrs
|
||||
|
||||
|
||||
class Executable(object):
|
||||
"""
|
||||
Executable is a generic container for exectuable commands.
|
||||
"""
|
||||
|
||||
def __init__(self, cmd, args, phase=PHASE_RUN):
|
||||
"""
|
||||
:param cmd: name of command to run, ``str``
|
||||
:param args: arguments to command, ``(str,)``
|
||||
:param phase: PHASE_SETUP|PHASE_RUN|PHASE_TEARDOWN. Indicates whether the
|
||||
command should be run before, during, or after launch, ``str``
|
||||
"""
|
||||
self.command = cmd
|
||||
self.args = args
|
||||
self.phase = phase
|
||||
def __repr__(self):
|
||||
return "%s %s"%(self.command, ' '.join(self.args))
|
||||
def __str__(self):
|
||||
return "%s %s"%(self.command, ' '.join(self.args))
|
||||
|
||||
class RosbinExecutable(Executable):
|
||||
"""
|
||||
RosbinExecutables are exectuables stored in ROS_ROOT/bin.
|
||||
"""
|
||||
def __init__(self, cmd, args, phase=PHASE_RUN):
|
||||
super(RosbinExecutable, self).__init__(cmd, args, phase)
|
||||
def __repr__(self):
|
||||
return "ros/bin/%s %s"%(self.command, ' '.join(self.args))
|
||||
def __str__(self):
|
||||
return "ros/bin/%s %s"%(self.command, ' '.join(self.args))
|
||||
|
||||
|
||||
def generate_run_id():
|
||||
"""
|
||||
Utility routine for generating run IDs (UUIDs)
|
||||
:returns: guid, ``str``
|
||||
"""
|
||||
import uuid
|
||||
return str(uuid.uuid1())
|
||||
|
||||
371
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/depends.py
vendored
Normal file
371
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/depends.py
vendored
Normal file
@@ -0,0 +1,371 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
"""
|
||||
Utility module of roslaunch that extracts dependency information from
|
||||
roslaunch files, including calculating missing package dependencies.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from xml.dom.minidom import parse
|
||||
from xml.dom import Node as DomNode
|
||||
|
||||
import rospkg
|
||||
|
||||
from .loader import convert_value
|
||||
from .substitution_args import resolve_args
|
||||
|
||||
NAME="roslaunch-deps"
|
||||
|
||||
class RoslaunchDepsException(Exception):
|
||||
"""
|
||||
Base exception of roslaunch.depends errors.
|
||||
"""
|
||||
pass
|
||||
|
||||
class RoslaunchDeps(object):
|
||||
"""
|
||||
Represents dependencies of a roslaunch file.
|
||||
"""
|
||||
def __init__(self, nodes=None, includes=None, pkgs=None):
|
||||
if nodes == None:
|
||||
nodes = []
|
||||
if includes == None:
|
||||
includes = []
|
||||
if pkgs == None:
|
||||
pkgs = []
|
||||
self.nodes = nodes
|
||||
self.includes = includes
|
||||
self.pkgs = pkgs
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, RoslaunchDeps):
|
||||
return False
|
||||
return set(self.nodes) == set(other.nodes) and \
|
||||
set(self.includes) == set(other.includes) and \
|
||||
set(self.pkgs) == set(other.pkgs)
|
||||
|
||||
def __repr__(self):
|
||||
return "nodes: %s\nincludes: %s\npkgs: %s"%(str(self.nodes), str(self.includes), str(self.pkgs))
|
||||
|
||||
def __str__(self):
|
||||
return "nodes: %s\nincludes: %s\npkgs: %s"%(str(self.nodes), str(self.includes), str(self.pkgs))
|
||||
|
||||
def _get_arg_value(tag, context):
|
||||
name = tag.attributes['name'].value
|
||||
if tag.attributes.has_key('value'):
|
||||
return resolve_args(tag.attributes['value'].value, context)
|
||||
elif name in context['arg']:
|
||||
return context['arg'][name]
|
||||
elif tag.attributes.has_key('default'):
|
||||
return resolve_args(tag.attributes['default'].value, context)
|
||||
else:
|
||||
raise RoslaunchDepsException("No value for arg [%s]"%(name))
|
||||
|
||||
def _check_ifunless(tag, context):
|
||||
if tag.attributes.has_key('if'):
|
||||
val = resolve_args(tag.attributes['if'].value, context)
|
||||
if not convert_value(val, 'bool'):
|
||||
return False
|
||||
elif tag.attributes.has_key('unless'):
|
||||
val = resolve_args(tag.attributes['unless'].value, context)
|
||||
if convert_value(val, 'bool'):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _parse_subcontext(tags, context):
|
||||
subcontext = {'arg': {}}
|
||||
|
||||
if tags == None:
|
||||
return subcontext
|
||||
|
||||
for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]:
|
||||
if tag.tagName == 'arg' and _check_ifunless(tag, context):
|
||||
subcontext['arg'][tag.attributes['name'].value] = _get_arg_value(tag, context)
|
||||
return subcontext
|
||||
|
||||
def _parse_launch(tags, launch_file, file_deps, verbose, context):
|
||||
dir_path = os.path.dirname(os.path.abspath(launch_file))
|
||||
launch_file_pkg = rospkg.get_package_name(dir_path)
|
||||
|
||||
# process group, include, node, and test tags from launch file
|
||||
for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]:
|
||||
if not _check_ifunless(tag, context):
|
||||
continue
|
||||
|
||||
if tag.tagName == 'group':
|
||||
|
||||
#descend group tags as they can contain node tags
|
||||
_parse_launch(tag.childNodes, launch_file, file_deps, verbose, context)
|
||||
|
||||
elif tag.tagName == 'arg':
|
||||
context['arg'][tag.attributes['name'].value] = _get_arg_value(tag, context)
|
||||
|
||||
elif tag.tagName == 'include':
|
||||
try:
|
||||
sub_launch_file = resolve_args(tag.attributes['file'].value, context)
|
||||
except KeyError as e:
|
||||
raise RoslaunchDepsException("Cannot load roslaunch <%s> tag: missing required attribute %s.\nXML is %s"%(tag.tagName, str(e), tag.toxml()))
|
||||
|
||||
# Check if an empty file is included, and skip if so.
|
||||
# This will allow a default-empty <include> inside a conditional to pass
|
||||
if sub_launch_file == '':
|
||||
if verbose:
|
||||
print("Empty <include> in %s. Skipping <include> of %s" %
|
||||
(launch_file, tag.attributes['file'].value))
|
||||
continue
|
||||
|
||||
if verbose:
|
||||
print("processing included launch %s"%sub_launch_file)
|
||||
|
||||
# determine package dependency for included file
|
||||
sub_pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(sub_launch_file)))
|
||||
if sub_pkg is None:
|
||||
print("ERROR: cannot determine package for [%s]"%sub_launch_file, file=sys.stderr)
|
||||
|
||||
if sub_launch_file not in file_deps[launch_file].includes:
|
||||
file_deps[launch_file].includes.append(sub_launch_file)
|
||||
if launch_file_pkg != sub_pkg:
|
||||
file_deps[launch_file].pkgs.append(sub_pkg)
|
||||
|
||||
# recurse
|
||||
file_deps[sub_launch_file] = RoslaunchDeps()
|
||||
try:
|
||||
dom = parse(sub_launch_file).getElementsByTagName('launch')
|
||||
if not len(dom):
|
||||
print("ERROR: %s is not a valid roslaunch file"%sub_launch_file, file=sys.stderr)
|
||||
else:
|
||||
launch_tag = dom[0]
|
||||
sub_context = _parse_subcontext(tag.childNodes, context)
|
||||
try:
|
||||
if tag.attributes['pass_all_args']:
|
||||
sub_context["arg"] = context["arg"]
|
||||
sub_context["arg"].update(_parse_subcontext(tag.childNodes, context)["arg"])
|
||||
except KeyError as e:
|
||||
pass
|
||||
_parse_launch(launch_tag.childNodes, sub_launch_file, file_deps, verbose, sub_context)
|
||||
except IOError as e:
|
||||
raise RoslaunchDepsException("Cannot load roslaunch include '%s' in '%s'"%(sub_launch_file, launch_file))
|
||||
|
||||
elif tag.tagName in ['node', 'test']:
|
||||
try:
|
||||
pkg, type = [resolve_args(tag.attributes[a].value, context) for a in ['pkg', 'type']]
|
||||
except KeyError as e:
|
||||
raise RoslaunchDepsException("Cannot load roslaunch <%s> tag: missing required attribute %s.\nXML is %s"%(tag.tagName, str(e), tag.toxml()))
|
||||
if (pkg, type) not in file_deps[launch_file].nodes:
|
||||
file_deps[launch_file].nodes.append((pkg, type))
|
||||
# we actually want to include the package itself if that's referenced
|
||||
#if launch_file_pkg != pkg:
|
||||
if pkg not in file_deps[launch_file].pkgs:
|
||||
file_deps[launch_file].pkgs.append(pkg)
|
||||
|
||||
def parse_launch(launch_file, file_deps, verbose):
|
||||
if verbose:
|
||||
print("processing launch %s"%launch_file)
|
||||
|
||||
try:
|
||||
dom = parse(launch_file).getElementsByTagName('launch')
|
||||
except:
|
||||
raise RoslaunchDepsException("invalid XML in %s"%(launch_file))
|
||||
if not len(dom):
|
||||
raise RoslaunchDepsException("invalid XML in %s"%(launch_file))
|
||||
|
||||
file_deps[launch_file] = RoslaunchDeps()
|
||||
launch_tag = dom[0]
|
||||
context = { 'arg': {}}
|
||||
_parse_launch(launch_tag.childNodes, launch_file, file_deps, verbose, context)
|
||||
|
||||
def rl_file_deps(file_deps, launch_file, verbose=False):
|
||||
"""
|
||||
Generate file_deps file dependency info for the specified
|
||||
roslaunch file and its dependencies.
|
||||
@param file_deps: dictionary mapping roslaunch filenames to
|
||||
roslaunch dependency information. This dictionary will be
|
||||
updated with dependency information.
|
||||
@type file_deps: { str : RoslaunchDeps }
|
||||
@param verbose: if True, print verbose output
|
||||
@type verbose: bool
|
||||
@param launch_file: name of roslaunch file
|
||||
@type launch_file: str
|
||||
"""
|
||||
parse_launch(launch_file, file_deps, verbose)
|
||||
|
||||
def fullusage():
|
||||
name = NAME
|
||||
print("""Usage:
|
||||
\t%(name)s [options] <file-or-package>
|
||||
"""%locals())
|
||||
|
||||
def print_deps(base_pkg, file_deps, verbose):
|
||||
pkgs = []
|
||||
|
||||
# for verbose output we print extra source information
|
||||
if verbose:
|
||||
for f, deps in file_deps.items():
|
||||
for p, t in deps.nodes:
|
||||
print("%s [%s/%s]"%(p, p, t))
|
||||
|
||||
pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(f)))
|
||||
if pkg is None: #cannot determine package
|
||||
print("ERROR: cannot determine package for [%s]"%pkg, file=sys.stderr)
|
||||
else:
|
||||
print("%s [%s]"%(pkg, f))
|
||||
print('-'*80)
|
||||
|
||||
# print out list of package dependencies
|
||||
pkgs = []
|
||||
for deps in file_deps.values():
|
||||
pkgs.extend(deps.pkgs)
|
||||
# print space-separated to be friendly to rosmake
|
||||
print(' '.join([p for p in set(pkgs)]))
|
||||
|
||||
def calculate_missing(base_pkg, missing, file_deps, use_test_depends=False):
|
||||
"""
|
||||
Calculate missing package dependencies in the manifest. This is
|
||||
mainly used as a subroutine of roslaunch_deps().
|
||||
|
||||
@param base_pkg: name of package where initial walk begins (unused).
|
||||
@type base_pkg: str
|
||||
@param missing: dictionary mapping package names to set of missing package dependencies.
|
||||
@type missing: { str: set(str) }
|
||||
@param file_deps: dictionary mapping launch file names to RoslaunchDeps of each file
|
||||
@type file_deps: { str: RoslaunchDeps}
|
||||
@param use_test_depends [bool]: use test_depends as installed package
|
||||
@type use_test_depends: [bool]
|
||||
@return: missing (see parameter)
|
||||
@rtype: { str: set(str) }
|
||||
"""
|
||||
rospack = rospkg.RosPack()
|
||||
for launch_file in file_deps.keys():
|
||||
pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(launch_file)))
|
||||
|
||||
if pkg is None: #cannot determine package
|
||||
print("ERROR: cannot determine package for [%s]"%pkg, file=sys.stderr)
|
||||
continue
|
||||
m = rospack.get_manifest(pkg)
|
||||
d_pkgs = set([d.name for d in m.depends])
|
||||
if m.is_catkin:
|
||||
# for catkin packages consider the run dependencies instead
|
||||
# else not released packages will not appear in the dependency list
|
||||
# since rospkg does uses rosdep to decide which dependencies to return
|
||||
from catkin_pkg.package import parse_package
|
||||
p = parse_package(os.path.dirname(m.filename))
|
||||
d_pkgs = set([d.name for d in p.run_depends])
|
||||
if use_test_depends:
|
||||
for d in p.test_depends:
|
||||
d_pkgs.add(d.name)
|
||||
# make sure we don't count ourselves as a dep
|
||||
d_pkgs.add(pkg)
|
||||
|
||||
diff = list(set(file_deps[launch_file].pkgs) - d_pkgs)
|
||||
if not pkg in missing:
|
||||
missing[pkg] = set()
|
||||
missing[pkg].update(diff)
|
||||
return missing
|
||||
|
||||
|
||||
def roslaunch_deps(files, verbose=False, use_test_depends=False):
|
||||
"""
|
||||
@param packages: list of packages to check
|
||||
@type packages: [str]
|
||||
@param files [str]: list of roslaunch files to check. Must be in
|
||||
same package.
|
||||
@type files: [str]
|
||||
@param use_test_depends [bool]: use test_depends as installed package
|
||||
@type use_test_depends: [bool]
|
||||
@return: base_pkg, file_deps, missing.
|
||||
base_pkg is the package of all files
|
||||
file_deps is a { filename : RoslaunchDeps } dictionary of
|
||||
roslaunch dependency information to update, indexed by roslaunch
|
||||
file name.
|
||||
missing is a { package : [packages] } dictionary of missing
|
||||
manifest dependencies, indexed by package.
|
||||
@rtype: str, dict, dict
|
||||
"""
|
||||
file_deps = {}
|
||||
missing = {}
|
||||
base_pkg = None
|
||||
|
||||
for launch_file in files:
|
||||
if not os.path.exists(launch_file):
|
||||
raise RoslaunchDepsException("roslaunch file [%s] does not exist"%launch_file)
|
||||
|
||||
pkg = rospkg.get_package_name(os.path.dirname(os.path.abspath(launch_file)))
|
||||
if base_pkg and pkg != base_pkg:
|
||||
raise RoslaunchDepsException("roslaunch files must be in the same package: %s vs. %s"%(base_pkg, pkg))
|
||||
base_pkg = pkg
|
||||
rl_file_deps(file_deps, launch_file, verbose)
|
||||
|
||||
calculate_missing(base_pkg, missing, file_deps, use_test_depends=use_test_depends)
|
||||
return base_pkg, file_deps, missing
|
||||
|
||||
def roslaunch_deps_main(argv=sys.argv):
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage="usage: %prog [options] <file(s)...>", prog=NAME)
|
||||
parser.add_option("--verbose", "-v", action="store_true",
|
||||
dest="verbose", default=False,
|
||||
help="Verbose output")
|
||||
parser.add_option("--warn", "-w", action="store_true",
|
||||
dest="warn", default=False,
|
||||
help="Warn about missing manifest dependencies")
|
||||
|
||||
(options, args) = parser.parse_args(argv[1:])
|
||||
if not args:
|
||||
parser.error('please specify a launch file')
|
||||
|
||||
files = [arg for arg in args if os.path.exists(arg)]
|
||||
packages = [arg for arg in args if not os.path.exists(arg)]
|
||||
if packages:
|
||||
parser.error("cannot locate %s"%','.join(packages))
|
||||
try:
|
||||
base_pkg, file_deps, missing = roslaunch_deps(files, verbose=options.verbose)
|
||||
except RoslaunchDepsException as e:
|
||||
print(sys.stderr, str(e))
|
||||
sys.exit(1)
|
||||
|
||||
if options.warn:
|
||||
print("Dependencies:")
|
||||
|
||||
print_deps(base_pkg, file_deps, options.verbose)
|
||||
|
||||
if options.warn:
|
||||
print('\nMissing declarations:')
|
||||
for pkg, miss in missing.items():
|
||||
if miss:
|
||||
print("%s/manifest.xml:"%pkg)
|
||||
for m in miss:
|
||||
print(' <depend package="%s" />'%m)
|
||||
|
||||
714
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/launch.py
vendored
Normal file
714
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/launch.py
vendored
Normal file
@@ -0,0 +1,714 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
"""
|
||||
Top-level implementation of launching processes. Coordinates
|
||||
lower-level libraries.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import rosgraph
|
||||
import rosgraph.names
|
||||
import rosgraph.network
|
||||
|
||||
from roslaunch.core import *
|
||||
#from roslaunch.core import setup_env
|
||||
from roslaunch.nodeprocess import create_master_process, create_node_process
|
||||
from roslaunch.pmon import start_process_monitor, ProcessListener
|
||||
|
||||
from roslaunch.rlutil import update_terminal_name
|
||||
|
||||
from rosmaster.master_api import NUM_WORKERS
|
||||
|
||||
_TIMEOUT_MASTER_START = 10.0 #seconds
|
||||
_TIMEOUT_MASTER_STOP = 10.0 #seconds
|
||||
|
||||
_ID = '/roslaunch'
|
||||
|
||||
class RLTestTimeoutException(RLException): pass
|
||||
|
||||
def validate_master_launch(m, is_core, is_rostest=False):
|
||||
"""
|
||||
Validate the configuration of a master we are about to launch. Ths
|
||||
validation already assumes that no existing master is running at
|
||||
this configuration and merely checks configuration for a new
|
||||
launch.
|
||||
"""
|
||||
# Before starting a master, we do some sanity check on the
|
||||
# master configuration. There are two ways the user starts:
|
||||
# roscore or roslaunch. If the user types roscore, we always
|
||||
# will start a core if possible, but we warn about potential
|
||||
# misconfigurations. If the user types roslaunch, we will
|
||||
# auto-start a new master if it is achievable, i.e. if the
|
||||
# master is local.
|
||||
if not rosgraph.network.is_local_address(m.get_host()):
|
||||
# The network configuration says that the intended
|
||||
# hostname is not local, so...
|
||||
if is_core:
|
||||
# ...user is expecting to start a new master. Thus, we
|
||||
# only warn if network configuration appears
|
||||
# non-local.
|
||||
try:
|
||||
reverse_ips = [host[4][0] for host in socket.getaddrinfo(m.get_host(), 0, 0, 0, socket.SOL_TCP)]
|
||||
local_addrs = rosgraph.network.get_local_addresses()
|
||||
printerrlog("""WARNING: IP addresses %s for local hostname '%s' do not appear to match
|
||||
any local IP address (%s). Your ROS nodes may fail to communicate.
|
||||
|
||||
Please use ROS_IP to set the correct IP address to use."""%(','.join(reverse_ips), m.get_host(), ','.join(local_addrs)))
|
||||
except:
|
||||
printerrlog("""WARNING: local hostname '%s' does not map to an IP address.
|
||||
Your ROS nodes may fail to communicate.
|
||||
|
||||
Please use ROS_IP to set the correct IP address to use."""%(m.get_host()))
|
||||
|
||||
else:
|
||||
# ... the remote master cannot be contacted, so we fail. #3097
|
||||
raise RLException("ERROR: unable to contact ROS master at [%s]"%(m.uri))
|
||||
|
||||
if is_core and not is_rostest:
|
||||
# User wants to start a master, and our configuration does
|
||||
# point to the local host, and we're not running as rostest.
|
||||
env_uri = rosgraph.get_master_uri()
|
||||
env_host, env_port = rosgraph.network.parse_http_host_and_port(env_uri)
|
||||
|
||||
if not rosgraph.network.is_local_address(env_host):
|
||||
# The ROS_MASTER_URI points to a different machine, warn user
|
||||
printerrlog("WARNING: ROS_MASTER_URI [%s] host is not set to this machine"%(env_uri))
|
||||
elif env_port != m.get_port():
|
||||
# The ROS_MASTER_URI points to a master on a different port, warn user
|
||||
printerrlog("WARNING: ROS_MASTER_URI port [%s] does not match this roscore [%s]"%(env_port, m.get_port()))
|
||||
|
||||
def _all_namespace_parents(p):
|
||||
splits = [s for s in p.split(rosgraph.names.SEP) if s]
|
||||
curr =rosgraph.names.SEP
|
||||
parents = [curr]
|
||||
for s in splits[:-1]:
|
||||
next_ = curr + s + rosgraph.names.SEP
|
||||
parents.append(next_)
|
||||
curr = next_
|
||||
return parents
|
||||
|
||||
def _unify_clear_params(params):
|
||||
"""
|
||||
Reduce clear_params such that only the highest-level namespaces
|
||||
are represented for overlapping namespaces. e.g. if both /foo/ and
|
||||
/foo/bar are present, return just /foo/.
|
||||
|
||||
@param params: paremter namees
|
||||
@type params: [str]
|
||||
@return: unified parameters
|
||||
@rtype: [str]
|
||||
"""
|
||||
# note: this is a quick-and-dirty implementation
|
||||
|
||||
# canonicalize parameters
|
||||
canon_params = []
|
||||
for p in params:
|
||||
if not p[-1] == rosgraph.names.SEP:
|
||||
p += rosgraph.names.SEP
|
||||
if not p in canon_params:
|
||||
canon_params.append(p)
|
||||
# reduce operation
|
||||
clear_params = canon_params[:]
|
||||
for p in canon_params:
|
||||
for parent in _all_namespace_parents(p):
|
||||
if parent in canon_params and p in clear_params and p != '/':
|
||||
clear_params.remove(p)
|
||||
return clear_params
|
||||
|
||||
def _hostname_to_rosname(hostname):
|
||||
"""
|
||||
Utility function to strip illegal characters from hostname. This
|
||||
is implemented to be correct, not efficient."""
|
||||
|
||||
# prepend 'host_', which guarantees leading alpha character
|
||||
fixed = 'host_'
|
||||
# simplify comparison by making name lowercase
|
||||
hostname = hostname.lower()
|
||||
for c in hostname:
|
||||
# only allow alphanumeric and numeral
|
||||
if (c >= 'a' and c <= 'z') or \
|
||||
(c >= '0' and c <= '9'):
|
||||
fixed+=c
|
||||
else:
|
||||
fixed+='_'
|
||||
return fixed
|
||||
|
||||
class _ROSLaunchListeners(ProcessListener):
|
||||
"""
|
||||
Helper class to manage distributing process events. It functions as
|
||||
a higher level aggregator of events. In particular, events about
|
||||
remote and local processes require different mechanisms for being
|
||||
caught and reported.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.process_listeners = []
|
||||
|
||||
def add_process_listener(self, l):
|
||||
"""
|
||||
Add listener to list of listeners. Not threadsafe.
|
||||
@param l: listener
|
||||
@type l: L{ProcessListener}
|
||||
"""
|
||||
self.process_listeners.append(l)
|
||||
|
||||
def process_died(self, process_name, exit_code):
|
||||
"""
|
||||
ProcessListener callback
|
||||
"""
|
||||
for l in self.process_listeners:
|
||||
try:
|
||||
l.process_died(process_name, exit_code)
|
||||
except Exception as e:
|
||||
logging.getLogger('roslaunch').error(traceback.format_exc())
|
||||
|
||||
class ROSLaunchListener(object):
|
||||
"""
|
||||
Listener interface for events related to ROSLaunch.
|
||||
ROSLaunchListener is currently identical to the lower-level
|
||||
L{roslaunch.pmon.ProcessListener} interface, but is separate for
|
||||
architectural reasons. The lower-level
|
||||
L{roslaunch.pmon.ProcessMonitor} does not provide events about
|
||||
remotely running processes.
|
||||
"""
|
||||
|
||||
def process_died(self, process_name, exit_code):
|
||||
"""
|
||||
Notifies listener that process has died. This callback only
|
||||
occurs for processes that die during normal process monitor
|
||||
execution -- processes that are forcibly killed during
|
||||
L{roslaunch.pmon.ProcessMonitor} shutdown are not reported.
|
||||
@param process_name: name of process
|
||||
@type process_name: str
|
||||
@param exit_code int: exit code of process. If None, it means
|
||||
that L{roslaunch.pmon.ProcessMonitor} was unable to determine an exit code.
|
||||
@type exit_code: int
|
||||
"""
|
||||
pass
|
||||
|
||||
class ROSLaunchRunner(object):
|
||||
"""
|
||||
Runs a roslaunch. The normal sequence of API calls is L{launch()}
|
||||
followed by L{spin()}. An external thread can call L{stop()}; otherwise
|
||||
the runner will block until an exit signal. Another usage is to
|
||||
call L{launch()} followed by repeated calls to L{spin_once()}. This usage
|
||||
allows the main thread to continue to do work while processes are
|
||||
monitored.
|
||||
"""
|
||||
|
||||
def __init__(self, run_id, config, server_uri=None, pmon=None, is_core=False, remote_runner=None, is_child=False, is_rostest=False, num_workers=NUM_WORKERS, timeout=None):
|
||||
"""
|
||||
@param run_id: /run_id for this launch. If the core is not
|
||||
running, this value will be used to initialize /run_id. If
|
||||
the core is already running, this value will be checked
|
||||
against the value stored on the core. L{ROSLaunchRunner} will
|
||||
fail during L{launch()} if they do not match.
|
||||
@type run_id: str
|
||||
@param config: roslauch instance to run
|
||||
@type config: L{ROSLaunchConfig}
|
||||
@param server_uri: XML-RPC URI of roslaunch server.
|
||||
@type server_uri: str
|
||||
@param pmon: optionally override the process
|
||||
monitor the runner uses for starting and tracking processes
|
||||
@type pmon: L{ProcessMonitor}
|
||||
|
||||
@param is_core: if True, this runner is a roscore
|
||||
instance. This affects the error behavior if a master is
|
||||
already running -- aborts if is_core is True and a core is
|
||||
detected.
|
||||
@type is_core: bool
|
||||
@param remote_runner: remote roslaunch process runner
|
||||
@param is_rostest: if True, this runner is a rostest
|
||||
instance. This affects certain validation checks.
|
||||
@type is_rostest: bool
|
||||
@param num_workers: If this is the core, the number of worker-threads to use.
|
||||
@type num_workers: int
|
||||
@param timeout: If this is the core, the socket-timeout to use.
|
||||
@type timeout: Float or None
|
||||
"""
|
||||
if run_id is None:
|
||||
raise RLException("run_id is None")
|
||||
self.run_id = run_id
|
||||
|
||||
# In the future we should can separate the notion of a core
|
||||
# config vs. the config being launched. In that way, we can
|
||||
# start to migrate to a model where a config is a parameter to
|
||||
# a launch() method, rather than a central thing to the
|
||||
# runner.
|
||||
self.config = config
|
||||
self.server_uri = server_uri
|
||||
self.is_child = is_child
|
||||
self.is_core = is_core
|
||||
self.is_rostest = is_rostest
|
||||
self.num_workers = num_workers
|
||||
self.timeout = timeout
|
||||
self.logger = logging.getLogger('roslaunch')
|
||||
self.pm = pmon or start_process_monitor()
|
||||
|
||||
# wire in ProcessMonitor events to our listeners
|
||||
# aggregator. We similarly wire in the remote events when we
|
||||
# create the remote runner.
|
||||
self.listeners = _ROSLaunchListeners()
|
||||
if self.pm is None:
|
||||
raise RLException("unable to initialize roslaunch process monitor")
|
||||
if self.pm.is_shutdown:
|
||||
raise RLException("bad roslaunch process monitor initialization: process monitor is already dead")
|
||||
|
||||
self.pm.add_process_listener(self.listeners)
|
||||
|
||||
self.remote_runner = remote_runner
|
||||
|
||||
def add_process_listener(self, l):
|
||||
"""
|
||||
Add listener to list of listeners. Not threadsafe. Must be
|
||||
called before processes started.
|
||||
@param l: listener
|
||||
@type l: L{ProcessListener}
|
||||
"""
|
||||
self.listeners.add_process_listener(l)
|
||||
|
||||
def _load_parameters(self):
|
||||
"""
|
||||
Load parameters onto the parameter server
|
||||
"""
|
||||
self.logger.info("load_parameters starting ...")
|
||||
config = self.config
|
||||
param_server = config.master.get()
|
||||
p = None
|
||||
try:
|
||||
# multi-call style xmlrpc
|
||||
param_server_multi = config.master.get_multi()
|
||||
|
||||
# clear specified parameter namespaces
|
||||
# #2468 unify clear params to prevent error
|
||||
for p in _unify_clear_params(config.clear_params):
|
||||
if param_server.hasParam(_ID, p)[2]:
|
||||
#printlog("deleting parameter [%s]"%p)
|
||||
param_server_multi.deleteParam(_ID, p)
|
||||
r = param_server_multi()
|
||||
for code, msg, _ in r:
|
||||
if code != 1:
|
||||
raise RLException("Failed to clear parameter: %s"%(msg))
|
||||
|
||||
# multi-call objects are not reusable
|
||||
param_server_multi = config.master.get_multi()
|
||||
for p in config.params.values():
|
||||
# suppressing this as it causes too much spam
|
||||
#printlog("setting parameter [%s]"%p.key)
|
||||
param_server_multi.setParam(_ID, p.key, p.value)
|
||||
r = param_server_multi()
|
||||
for code, msg, _ in r:
|
||||
if code != 1:
|
||||
raise RLException("Failed to set parameter: %s"%(msg))
|
||||
except RLException:
|
||||
raise
|
||||
except Exception as e:
|
||||
printerrlog("load_parameters: unable to set parameters (last param was [%s]): %s"%(p,e))
|
||||
raise #re-raise as this is fatal
|
||||
self.logger.info("... load_parameters complete")
|
||||
|
||||
def _launch_nodes(self):
|
||||
"""
|
||||
Launch all the declared nodes/master
|
||||
@return: two lists of node names where the first
|
||||
is the nodes that successfully launched and the second is the
|
||||
nodes that failed to launch.
|
||||
@rtype: [[str], [str]]
|
||||
"""
|
||||
config = self.config
|
||||
succeeded = []
|
||||
failed = []
|
||||
self.logger.info("launch_nodes: launching local nodes ...")
|
||||
local_nodes = config.nodes
|
||||
|
||||
# don't launch remote nodes
|
||||
local_nodes = [n for n in config.nodes if is_machine_local(n.machine)]
|
||||
|
||||
for node in local_nodes:
|
||||
proc, success = self.launch_node(node)
|
||||
if success:
|
||||
succeeded.append(str(proc))
|
||||
else:
|
||||
failed.append(str(proc))
|
||||
|
||||
if self.remote_runner:
|
||||
self.logger.info("launch_nodes: launching remote nodes ...")
|
||||
r_succ, r_fail = self.remote_runner.launch_remote_nodes()
|
||||
succeeded.extend(r_succ)
|
||||
failed.extend(r_fail)
|
||||
|
||||
self.logger.info("... launch_nodes complete")
|
||||
return succeeded, failed
|
||||
|
||||
def _launch_master(self):
|
||||
"""
|
||||
Launches master if requested.
|
||||
@return: True if a master was launched, False if a master was
|
||||
already running.
|
||||
@rtype: bool
|
||||
@raise RLException: if master launch fails
|
||||
"""
|
||||
m = self.config.master
|
||||
is_running = m.is_running()
|
||||
|
||||
if self.is_core and is_running:
|
||||
raise RLException("roscore cannot run as another roscore/master is already running. \nPlease kill other roscore/master processes before relaunching.\nThe ROS_MASTER_URI is %s"%(m.uri))
|
||||
|
||||
if not is_running:
|
||||
validate_master_launch(m, self.is_core, self.is_rostest)
|
||||
|
||||
printlog("auto-starting new master")
|
||||
p = create_master_process(self.run_id, m.type, get_ros_root(), m.get_port(), self.num_workers, self.timeout)
|
||||
self.pm.register_core_proc(p)
|
||||
success = p.start()
|
||||
if not success:
|
||||
raise RLException("ERROR: unable to auto-start master process")
|
||||
timeout_t = time.time() + _TIMEOUT_MASTER_START
|
||||
while not m.is_running() and time.time() < timeout_t:
|
||||
time.sleep(0.1)
|
||||
|
||||
if not m.is_running():
|
||||
raise RLException("ERROR: could not contact master [%s]"%m.uri)
|
||||
|
||||
printlog_bold("ROS_MASTER_URI=%s"%m.uri)
|
||||
# TODO: although this dependency doesn't cause anything bad,
|
||||
# it's still odd for launch to know about console stuff. This
|
||||
# really should be an event.
|
||||
update_terminal_name(m.uri)
|
||||
|
||||
# Param Server config params
|
||||
param_server = m.get()
|
||||
|
||||
# #773: unique run ID
|
||||
self._check_and_set_run_id(param_server, self.run_id)
|
||||
|
||||
if self.server_uri:
|
||||
# store parent XML-RPC URI on param server
|
||||
# - param name is the /roslaunch/hostname:port so that multiple roslaunches can store at once
|
||||
hostname, port = rosgraph.network.parse_http_host_and_port(self.server_uri)
|
||||
hostname = _hostname_to_rosname(hostname)
|
||||
self.logger.info("setting /roslaunch/uris/%s__%s' to %s"%(hostname, port, self.server_uri))
|
||||
param_server.setParam(_ID, '/roslaunch/uris/%s__%s'%(hostname, port),self.server_uri)
|
||||
|
||||
return not is_running
|
||||
|
||||
def _check_and_set_run_id(self, param_server, run_id):
|
||||
"""
|
||||
Initialize self.run_id to existing value or setup parameter
|
||||
server with /run_id set to default_run_id
|
||||
@param default_run_id: run_id to use if value is not set
|
||||
@type default_run_id: str
|
||||
@param param_server: parameter server proxy
|
||||
@type param_server: xmlrpclib.ServerProxy
|
||||
"""
|
||||
code, _, val = param_server.hasParam(_ID, '/run_id')
|
||||
if code == 1 and not val:
|
||||
printlog_bold("setting /run_id to %s"%run_id)
|
||||
param_server.setParam('/roslaunch', '/run_id', run_id)
|
||||
else:
|
||||
# verify that the run_id we have been set to matches what's on the parameter server
|
||||
code, _, val = param_server.getParam('/roslaunch', '/run_id')
|
||||
if code != 1:
|
||||
#could only happen in a bad race condition with
|
||||
#someone else restarting core
|
||||
raise RLException("ERROR: unable to retrieve /run_id from parameter server")
|
||||
if run_id != val:
|
||||
raise RLException("run_id on parameter server does not match declared run_id: %s vs %s"%(val, run_id))
|
||||
#self.run_id = val
|
||||
#printlog_bold("/run_id is %s"%self.run_id)
|
||||
|
||||
def _launch_executable(self, e):
|
||||
"""
|
||||
Launch a single L{Executable} object. Blocks until executable finishes.
|
||||
@param e: Executable
|
||||
@type e: L{Executable}
|
||||
@raise RLException: if exectuable fails. Failure includes non-zero exit code.
|
||||
"""
|
||||
try:
|
||||
#kwc: I'm still debating whether shell=True is proper
|
||||
cmd = e.command
|
||||
cmd = "%s %s"%(cmd, ' '.join(e.args))
|
||||
print("running %s" % cmd)
|
||||
local_machine = self.config.machines['']
|
||||
env = setup_env(None, local_machine, self.config.master.uri)
|
||||
retcode = subprocess.call(cmd, shell=True, env=env)
|
||||
if retcode < 0:
|
||||
raise RLException("command [%s] failed with exit code %s"%(cmd, retcode))
|
||||
except OSError as e:
|
||||
raise RLException("command [%s] failed: %s"%(cmd, e))
|
||||
|
||||
#TODO: _launch_run_executables, _launch_teardown_executables
|
||||
#TODO: define and implement behavior for remote launch
|
||||
def _launch_setup_executables(self):
|
||||
"""
|
||||
@raise RLException: if exectuable fails. Failure includes non-zero exit code.
|
||||
"""
|
||||
exes = [e for e in self.config.executables if e.phase == PHASE_SETUP]
|
||||
for e in exes:
|
||||
self._launch_executable(e)
|
||||
|
||||
def _launch_core_nodes(self):
|
||||
"""
|
||||
launch any core services that are not already running. master must
|
||||
be already running
|
||||
@raise RLException: if core launches fail
|
||||
"""
|
||||
config = self.config
|
||||
master = config.master.get()
|
||||
tolaunch = []
|
||||
for node in config.nodes_core:
|
||||
node_name = rosgraph.names.ns_join(node.namespace, node.name)
|
||||
code, msg, _ = master.lookupNode(_ID, node_name)
|
||||
if code == -1:
|
||||
tolaunch.append(node)
|
||||
elif code == 1:
|
||||
print("core service [%s] found" % node_name)
|
||||
else:
|
||||
print("WARN: master is not behaving well (unexpected return value when looking up node)", file=sys.stderr)
|
||||
self.logger.error("ERROR: master return [%s][%s] on lookupNode call"%(code,msg))
|
||||
|
||||
for node in tolaunch:
|
||||
node_name = rosgraph.names.ns_join(node.namespace, node.name)
|
||||
name, success = self.launch_node(node, core=True)
|
||||
if success:
|
||||
print("started core service [%s]" % node_name)
|
||||
else:
|
||||
raise RLException("failed to start core service [%s]"%node_name)
|
||||
|
||||
def launch_node(self, node, core=False):
|
||||
"""
|
||||
Launch a single node locally. Remote launching is handled separately by the remote module.
|
||||
If node name is not assigned, one will be created for it.
|
||||
|
||||
@param node Node: node to launch
|
||||
@param core bool: if True, core node
|
||||
@return obj, bool: Process handle, successful launch. If success, return actual Process instance. Otherwise return name.
|
||||
"""
|
||||
self.logger.info("... preparing to launch node of type [%s/%s]", node.package, node.type)
|
||||
|
||||
# TODO: should this always override, per spec?. I added this
|
||||
# so that this api can be called w/o having to go through an
|
||||
# extra assign machines step.
|
||||
if node.machine is None:
|
||||
node.machine = self.config.machines['']
|
||||
if node.name is None:
|
||||
node.name = rosgraph.names.anonymous_name(node.type)
|
||||
|
||||
master = self.config.master
|
||||
import roslaunch.node_args
|
||||
try:
|
||||
process = create_node_process(self.run_id, node, master.uri)
|
||||
except roslaunch.node_args.NodeParamsException as e:
|
||||
self.logger.error(e)
|
||||
printerrlog("ERROR: cannot launch node of type [%s/%s]: %s"%(node.package, node.type, str(e)))
|
||||
if node.name:
|
||||
return node.name, False
|
||||
else:
|
||||
return "%s/%s"%(node.package,node.type), False
|
||||
|
||||
self.logger.info("... created process [%s]", process.name)
|
||||
if core:
|
||||
self.pm.register_core_proc(process)
|
||||
else:
|
||||
self.pm.register(process)
|
||||
node.process_name = process.name #store this in the node object for easy reference
|
||||
self.logger.info("... registered process [%s]", process.name)
|
||||
|
||||
# note: this may raise FatalProcessLaunch, which aborts the entire launch
|
||||
success = process.start()
|
||||
if not success:
|
||||
if node.machine.name:
|
||||
printerrlog("launch of %s/%s on %s failed"%(node.package, node.type, node.machine.name))
|
||||
else:
|
||||
printerrlog("local launch of %s/%s failed"%(node.package, node.type))
|
||||
else:
|
||||
self.logger.info("... successfully launched [%s]", process.name)
|
||||
return process, success
|
||||
|
||||
def is_node_running(self, node):
|
||||
"""
|
||||
Check for running node process.
|
||||
@param node Node: node object to check
|
||||
@return bool: True if process associated with node is running (launched && !dead)
|
||||
"""
|
||||
#process_name is not set until node is launched.
|
||||
return node.process_name and self.pm.has_process(node.process_name)
|
||||
|
||||
def spin_once(self):
|
||||
"""
|
||||
Same as spin() but only does one cycle. must be run from the main thread.
|
||||
"""
|
||||
if not self.pm:
|
||||
return False
|
||||
return self.pm.mainthread_spin_once()
|
||||
|
||||
def spin(self):
|
||||
"""
|
||||
spin() must be run from the main thread. spin() is very
|
||||
important for roslaunch as it picks up jobs that the process
|
||||
monitor need to be run in the main thread.
|
||||
"""
|
||||
self.logger.info("spin")
|
||||
|
||||
# #556: if we're just setting parameters and aren't launching
|
||||
# any processes, exit.
|
||||
if not self.pm or not self.pm.get_active_names():
|
||||
printlog_bold("No processes to monitor")
|
||||
self.stop()
|
||||
return # no processes
|
||||
self.pm.mainthread_spin()
|
||||
#self.pm.join()
|
||||
self.logger.info("process monitor is done spinning, initiating full shutdown")
|
||||
self.stop()
|
||||
printlog_bold("done")
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop the launch and all associated processes. not thread-safe.
|
||||
"""
|
||||
self.logger.info("runner.stop()")
|
||||
if self.pm is not None:
|
||||
printlog("shutting down processing monitor...")
|
||||
self.logger.info("shutting down processing monitor %s"%self.pm)
|
||||
self.pm.shutdown()
|
||||
self.pm.join()
|
||||
self.pm = None
|
||||
printlog("... shutting down processing monitor complete")
|
||||
else:
|
||||
self.logger.info("... roslaunch runner has already been stopped")
|
||||
|
||||
def _setup(self):
|
||||
"""
|
||||
Setup the state of the ROS network, including the parameter
|
||||
server state and core services
|
||||
"""
|
||||
# this may have already been done, but do just in case
|
||||
self.config.assign_machines()
|
||||
|
||||
if self.remote_runner:
|
||||
# hook in our listener aggregator
|
||||
self.remote_runner.add_process_listener(self.listeners)
|
||||
|
||||
# start up the core: master + core nodes defined in core.xml
|
||||
launched = self._launch_master()
|
||||
if launched:
|
||||
self._launch_core_nodes()
|
||||
|
||||
# run exectuables marked as setup period. this will block
|
||||
# until these executables exit. setup executable have to run
|
||||
# *before* parameters are uploaded so that commands like
|
||||
# rosparam delete can execute.
|
||||
self._launch_setup_executables()
|
||||
|
||||
# no parameters for a child process
|
||||
if not self.is_child:
|
||||
self._load_parameters()
|
||||
|
||||
def launch(self):
|
||||
"""
|
||||
Run the launch. Depending on usage, caller should call
|
||||
spin_once or spin as appropriate after launch().
|
||||
@return ([str], [str]): tuple containing list of nodes that
|
||||
successfully launches and list of nodes that failed to launch
|
||||
@rtype: ([str], [str])
|
||||
@raise RLException: if launch fails (e.g. run_id parameter does
|
||||
not match ID on parameter server)
|
||||
"""
|
||||
try:
|
||||
self._setup()
|
||||
succeeded, failed = self._launch_nodes()
|
||||
return succeeded, failed
|
||||
except KeyboardInterrupt:
|
||||
self.stop()
|
||||
raise
|
||||
|
||||
def run_test(self, test):
|
||||
"""
|
||||
Run the test node. Blocks until completion or timeout.
|
||||
@param test: test node to run
|
||||
@type test: Test
|
||||
@raise RLTestTimeoutException: if test fails to launch or test times out
|
||||
"""
|
||||
self.logger.info("... preparing to run test [%s] of type [%s/%s]", test.test_name, test.package, test.type)
|
||||
proc_h, success = self.launch_node(test)
|
||||
if not success:
|
||||
raise RLException("test [%s] failed to launch"%test.test_name)
|
||||
|
||||
#poll until test terminates or alloted time exceed
|
||||
timeout_t = time.time() + test.time_limit
|
||||
pm = self.pm
|
||||
while pm.mainthread_spin_once() and self.is_node_running(test):
|
||||
#test fails on timeout
|
||||
if time.time() > timeout_t:
|
||||
raise RLTestTimeoutException(
|
||||
"max time [%ss] allotted for test [%s] of type [%s/%s]" %
|
||||
(test.time_limit, test.test_name, test.package, test.type))
|
||||
time.sleep(0.1)
|
||||
|
||||
# NOTE: the mainly exists to prevent implicit circular dependency as
|
||||
# the runner needs to invoke methods on the remote API, which depends
|
||||
# on launch.
|
||||
|
||||
class ROSRemoteRunnerIF(object):
|
||||
"""
|
||||
API for remote running
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
def setup(self):
|
||||
pass
|
||||
def add_process_listener(self, l):
|
||||
"""
|
||||
Listen to events about remote processes dying. Not
|
||||
threadsafe. Must be called before processes started.
|
||||
@param l: listener
|
||||
@type l: L{ProcessListener}
|
||||
"""
|
||||
pass
|
||||
|
||||
def launch_remote_nodes(self):
|
||||
"""
|
||||
Contact each child to launch remote nodes
|
||||
@return: succeeded, failed
|
||||
@rtype: [str], [str]
|
||||
"""
|
||||
pass
|
||||
506
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/loader.py
vendored
Normal file
506
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/loader.py
vendored
Normal file
@@ -0,0 +1,506 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
"""
|
||||
General routines and representations for loading roslaunch model.
|
||||
"""
|
||||
|
||||
import os
|
||||
from copy import deepcopy
|
||||
|
||||
from roslaunch.core import Param, RosbinExecutable, RLException, PHASE_SETUP
|
||||
|
||||
from rosgraph.names import make_global_ns, ns_join, PRIV_NAME, load_mappings, is_legal_name, canonicalize_name
|
||||
|
||||
#lazy-import global for yaml and rosparam
|
||||
yaml = None
|
||||
rosparam = None
|
||||
|
||||
class LoadException(RLException):
|
||||
"""Error loading data as specified (e.g. cannot find included files, etc...)"""
|
||||
pass
|
||||
|
||||
#TODO: lists, maps(?)
|
||||
def convert_value(value, type_):
|
||||
"""
|
||||
Convert a value from a string representation into the specified
|
||||
type
|
||||
|
||||
@param value: string representation of value
|
||||
@type value: str
|
||||
@param type_: int, double, string, bool, or auto
|
||||
@type type_: str
|
||||
@raise ValueError: if parameters are invalid
|
||||
"""
|
||||
type_ = type_.lower()
|
||||
# currently don't support XML-RPC date, dateTime, maps, or list
|
||||
# types
|
||||
if type_ == 'auto':
|
||||
#attempt numeric conversion
|
||||
try:
|
||||
if '.' in value:
|
||||
return float(value)
|
||||
else:
|
||||
return int(value)
|
||||
except ValueError as e:
|
||||
pass
|
||||
#bool
|
||||
lval = value.lower()
|
||||
if lval == 'true' or lval == 'false':
|
||||
return convert_value(value, 'bool')
|
||||
#string
|
||||
return value
|
||||
elif type_ == 'str' or type_ == 'string':
|
||||
return value
|
||||
elif type_ == 'int':
|
||||
return int(value)
|
||||
elif type_ == 'double':
|
||||
return float(value)
|
||||
elif type_ == 'bool' or type_ == 'boolean':
|
||||
value = value.lower()
|
||||
if value == 'true' or value == '1':
|
||||
return True
|
||||
elif value == 'false' or value == '0':
|
||||
return False
|
||||
raise ValueError("%s is not a '%s' type"%(value, type_))
|
||||
else:
|
||||
raise ValueError("Unknown type '%s'"%type_)
|
||||
|
||||
def process_include_args(context):
|
||||
"""
|
||||
Processes arg declarations in context and makes sure that they are
|
||||
properly declared for passing into an included file. Also will
|
||||
correctly setup the context for passing to the included file.
|
||||
"""
|
||||
|
||||
# make sure all arguments have values. arg_names and resolve_dict
|
||||
# are cleared when the context was initially created.
|
||||
arg_dict = context.include_resolve_dict.get('arg', {})
|
||||
for arg in context.arg_names:
|
||||
if not arg in arg_dict:
|
||||
raise LoadException("include args must have declared values")
|
||||
|
||||
# save args that were passed so we can check for unused args in post-processing
|
||||
context.args_passed = list(arg_dict.keys())
|
||||
# clear arg declarations so included file can re-declare
|
||||
context.arg_names = []
|
||||
|
||||
# swap in new resolve dict for passing
|
||||
context.resolve_dict = context.include_resolve_dict
|
||||
context.include_resolve_dict = None
|
||||
|
||||
def post_process_include_args(context):
|
||||
bad = [a for a in context.args_passed if a not in context.arg_names]
|
||||
if bad:
|
||||
raise LoadException("unused args [%s] for include of [%s]"%(', '.join(bad), context.filename))
|
||||
|
||||
def load_sysargs_into_context(context, argv):
|
||||
"""
|
||||
Load in ROS remapping arguments as arg assignments for context.
|
||||
|
||||
@param context: context to load into. context's resolve_dict for 'arg' will be reinitialized with values.
|
||||
@type context: L{LoaderContext{
|
||||
@param argv: command-line arguments
|
||||
@type argv: [str]
|
||||
"""
|
||||
# we use same command-line spec as ROS nodes
|
||||
mappings = load_mappings(argv)
|
||||
context.resolve_dict['arg'] = mappings
|
||||
|
||||
class LoaderContext(object):
|
||||
"""
|
||||
Container for storing current loader context (e.g. namespace,
|
||||
local parameter state, remapping state).
|
||||
"""
|
||||
|
||||
def __init__(self, ns, filename, parent=None, params=None, env_args=None, \
|
||||
resolve_dict=None, include_resolve_dict=None, arg_names=None):
|
||||
"""
|
||||
@param ns: namespace
|
||||
@type ns: str
|
||||
@param filename: name of file this is being loaded from
|
||||
@type filename: str
|
||||
@param resolve_dict: (optional) resolution dictionary for substitution args
|
||||
@type resolve_dict: dict
|
||||
@param include_resolve_dict: special resolution dictionary for
|
||||
<include> tags. Must be None if this is not an <include>
|
||||
context.
|
||||
@type include_resolve_dict: dict
|
||||
@param arg_names: name of args that have been declared in this context
|
||||
@type arg_names: [str]
|
||||
"""
|
||||
self.parent = parent
|
||||
self.ns = make_global_ns(ns or '/')
|
||||
self._remap_args = []
|
||||
self.params = params or []
|
||||
self.env_args = env_args or []
|
||||
self.filename = filename
|
||||
# for substitution args
|
||||
self.resolve_dict = resolve_dict or {}
|
||||
# arg names. Args that have been declared in this context
|
||||
self.arg_names = arg_names or []
|
||||
# special scoped resolve dict for processing in <include> tag
|
||||
self.include_resolve_dict = include_resolve_dict or None
|
||||
# when this context was created via include, was pass_all_args set?
|
||||
self.pass_all_args = False
|
||||
|
||||
def add_param(self, p):
|
||||
"""
|
||||
Add a ~param to the context. ~params are evaluated by any node
|
||||
declarations that occur later in the same context.
|
||||
|
||||
@param p: parameter
|
||||
@type p: L{Param}
|
||||
"""
|
||||
|
||||
# override any params already set
|
||||
matches = [m for m in self.params if m.key == p.key]
|
||||
for m in matches:
|
||||
self.params.remove(m)
|
||||
self.params.append(p)
|
||||
|
||||
def add_remap(self, remap):
|
||||
"""
|
||||
Add a new remap setting to the context. if a remap already
|
||||
exists with the same from key, it will be removed
|
||||
|
||||
@param remap: remap setting
|
||||
@type remap: (str, str)
|
||||
"""
|
||||
remap = [canonicalize_name(x) for x in remap]
|
||||
if not remap[0] or not remap[1]:
|
||||
raise RLException("remap from/to attributes cannot be empty")
|
||||
if not is_legal_name(remap[0]):
|
||||
raise RLException("remap from [%s] is not a valid ROS name"%remap[0])
|
||||
if not is_legal_name(remap[1]):
|
||||
raise RLException("remap to [%s] is not a valid ROS name"%remap[1])
|
||||
|
||||
matches = [r for r in self._remap_args if r[0] == remap[0]]
|
||||
for m in matches:
|
||||
self._remap_args.remove(m)
|
||||
self._remap_args.append(remap)
|
||||
|
||||
def add_arg(self, name, default=None, value=None, doc=None):
|
||||
"""
|
||||
Add 'arg' to existing context. Args are only valid for their immediate context.
|
||||
"""
|
||||
if name in self.arg_names:
|
||||
# Ignore the duplication if pass_all_args was set
|
||||
if not self.pass_all_args:
|
||||
raise LoadException("arg '%s' has already been declared"%name)
|
||||
else:
|
||||
self.arg_names.append(name)
|
||||
|
||||
resolve_dict = self.resolve_dict if self.include_resolve_dict is None else self.include_resolve_dict
|
||||
|
||||
if not 'arg' in resolve_dict:
|
||||
resolve_dict['arg'] = {}
|
||||
arg_dict = resolve_dict['arg']
|
||||
|
||||
# args can only be declared once. they must also have one and
|
||||
# only value at the time that they are declared.
|
||||
if value is not None:
|
||||
# value is set, error if declared in our arg dict as args
|
||||
# with set values are constant/grounded.
|
||||
# But don't error if pass_all_args was used to include this
|
||||
# context; rather just override the passed-in value.
|
||||
if name in arg_dict and not self.pass_all_args:
|
||||
raise LoadException("cannot override arg '%s', which has already been set"%name)
|
||||
arg_dict[name] = value
|
||||
elif default is not None:
|
||||
# assign value if not in context
|
||||
if name not in arg_dict:
|
||||
arg_dict[name] = default
|
||||
else:
|
||||
# no value or default: appending to arg_names is all we
|
||||
# need to do as it declares the existence of the arg.
|
||||
pass
|
||||
|
||||
# add arg documentation string dict if it doesn't exist yet and if it can be used
|
||||
if not 'arg_doc' in resolve_dict:
|
||||
resolve_dict['arg_doc'] = {}
|
||||
arg_doc_dict = resolve_dict['arg_doc']
|
||||
|
||||
if not value:
|
||||
# store the documentation for this argument
|
||||
arg_doc_dict[name] = (doc, default)
|
||||
|
||||
|
||||
def remap_args(self):
|
||||
"""
|
||||
@return: copy of the current remap arguments
|
||||
@rtype: [(str, str)]
|
||||
"""
|
||||
if self.parent:
|
||||
args = []
|
||||
# filter out any parent remap args that have the same from key
|
||||
for pr in self.parent.remap_args():
|
||||
if not [r for r in self._remap_args if r[0] == pr[0]]:
|
||||
args.append(pr)
|
||||
args.extend(self._remap_args)
|
||||
return args
|
||||
return self._remap_args[:]
|
||||
|
||||
def include_child(self, ns, filename):
|
||||
"""
|
||||
Create child namespace based on include inheritance rules
|
||||
@param ns: sub-namespace of child context, or None if the
|
||||
child context shares the same namespace
|
||||
@type ns: str
|
||||
@param filename: name of include file
|
||||
@type filename: str
|
||||
@return: A child xml context that inherits from this context
|
||||
@rtype: L{LoaderContext}jj
|
||||
"""
|
||||
ctx = self.child(ns)
|
||||
# arg declarations are reset across include boundaries
|
||||
ctx.arg_names = []
|
||||
ctx.filename = filename
|
||||
# keep the resolve_dict for now, we will do all new assignments into include_resolve_dict
|
||||
ctx.include_resolve_dict = {}
|
||||
#del ctx.resolve_dict['arg']
|
||||
return ctx
|
||||
|
||||
def child(self, ns):
|
||||
"""
|
||||
@param ns: sub-namespace of child context, or None if the
|
||||
child context shares the same namespace
|
||||
@type ns: str
|
||||
@return: A child xml context that inherits from this context
|
||||
@rtype: L{LoaderContext}
|
||||
"""
|
||||
if ns:
|
||||
if ns[0] == '/': # global (discouraged)
|
||||
child_ns = ns
|
||||
elif ns == PRIV_NAME: # ~name
|
||||
# private names can only be scoped privately or globally
|
||||
child_ns = PRIV_NAME
|
||||
else:
|
||||
child_ns = ns_join(self.ns, ns)
|
||||
else:
|
||||
child_ns = self.ns
|
||||
return LoaderContext(child_ns, self.filename, parent=self,
|
||||
params=self.params, env_args=self.env_args[:],
|
||||
resolve_dict=deepcopy(self.resolve_dict),
|
||||
arg_names=self.arg_names[:], include_resolve_dict=self.include_resolve_dict)
|
||||
|
||||
#TODO: in-progress refactorization. I'm slowly separating out
|
||||
#non-XML-specific logic from xmlloader and moving into Loader. Soon
|
||||
#this will mean that it will be easier to write coverage tests for
|
||||
#lower-level logic.
|
||||
|
||||
class Loader(object):
|
||||
"""
|
||||
Lower-level library for loading ROS launch model. It provides an
|
||||
abstraction between the representation (e.g. XML) and the
|
||||
validation of the property values.
|
||||
"""
|
||||
|
||||
def add_param(self, ros_config, param_name, param_value, verbose=True):
|
||||
"""
|
||||
Add L{Param} instances to launch config. Dictionary values are
|
||||
unrolled into individual parameters.
|
||||
|
||||
@param ros_config: launch configuration
|
||||
@type ros_config: L{ROSLaunchConfig}
|
||||
@param param_name: name of parameter namespace to load values
|
||||
into. If param_name is '/', param_value must be a dictionary
|
||||
@type param_name: str
|
||||
@param param_value: value to assign to param_name. If
|
||||
param_value is a dictionary, it's values will be unrolled
|
||||
into individual parameters.
|
||||
@type param_value: str
|
||||
@raise ValueError: if parameters cannot be processed into valid Params
|
||||
"""
|
||||
|
||||
# shouldn't ever happen
|
||||
if not param_name:
|
||||
raise ValueError("no parameter name specified")
|
||||
|
||||
if param_name == '/' and type(param_value) != dict:
|
||||
raise ValueError("Cannot load non-dictionary types into global namespace '/'")
|
||||
|
||||
if type(param_value) == dict:
|
||||
# unroll params
|
||||
for k, v in param_value.items():
|
||||
self.add_param(ros_config, ns_join(param_name, k), v, verbose=verbose)
|
||||
else:
|
||||
ros_config.add_param(Param(param_name, param_value), verbose=verbose)
|
||||
|
||||
def load_rosparam(self, context, ros_config, cmd, param, file_, text, verbose=True, subst_function=None):
|
||||
"""
|
||||
Load rosparam setting
|
||||
|
||||
@param context: Loader context
|
||||
@type context: L{LoaderContext}
|
||||
@param ros_config: launch configuration
|
||||
@type ros_config: L{ROSLaunchConfig}
|
||||
@param cmd: 'load', 'dump', or 'delete'
|
||||
@type cmd: str
|
||||
@param file_: filename for rosparam to use or None
|
||||
@type file_: str
|
||||
@param text: text for rosparam to load. Ignored if file_ is set.
|
||||
@type text: str
|
||||
@raise ValueError: if parameters cannot be processed into valid rosparam setting
|
||||
"""
|
||||
if not cmd in ('load', 'dump', 'delete'):
|
||||
raise ValueError("command must be 'load', 'dump', or 'delete'")
|
||||
if file_ is not None:
|
||||
if cmd == 'load' and not os.path.isfile(file_):
|
||||
raise ValueError("file does not exist [%s]"%file_)
|
||||
if cmd == 'delete':
|
||||
raise ValueError("'file' attribute is invalid with 'delete' command.")
|
||||
|
||||
full_param = ns_join(context.ns, param) if param else context.ns
|
||||
|
||||
if cmd == 'dump':
|
||||
ros_config.add_executable(RosbinExecutable('rosparam', (cmd, file_, full_param), PHASE_SETUP))
|
||||
elif cmd == 'delete':
|
||||
ros_config.add_executable(RosbinExecutable('rosparam', (cmd, full_param), PHASE_SETUP))
|
||||
elif cmd == 'load':
|
||||
# load YAML text
|
||||
if file_:
|
||||
with open(file_, 'r') as f:
|
||||
text = f.read()
|
||||
|
||||
if subst_function is not None:
|
||||
text = subst_function(text)
|
||||
# parse YAML text
|
||||
# - lazy import
|
||||
global yaml
|
||||
if yaml is None:
|
||||
import yaml
|
||||
# - lazy import: we have to import rosparam in oder to to configure the YAML constructors
|
||||
global rosparam
|
||||
if rosparam is None:
|
||||
import rosparam
|
||||
try:
|
||||
data = yaml.load(text)
|
||||
# #3162: if there is no YAML, load() will return an
|
||||
# empty string. We want an empty dictionary instead
|
||||
# for our representation of empty.
|
||||
if data is None:
|
||||
data = {}
|
||||
except yaml.MarkedYAMLError as e:
|
||||
if not file_:
|
||||
raise ValueError("Error within YAML block:\n\t%s\n\nYAML is:\n%s"%(str(e), text))
|
||||
else:
|
||||
raise ValueError("file %s contains invalid YAML:\n%s"%(file_, str(e)))
|
||||
except Exception as e:
|
||||
if not file_:
|
||||
raise ValueError("invalid YAML: %s\n\nYAML is:\n%s"%(str(e), text))
|
||||
else:
|
||||
raise ValueError("file %s contains invalid YAML:\n%s"%(file_, str(e)))
|
||||
|
||||
# 'param' attribute is required for non-dictionary types
|
||||
if not param and type(data) != dict:
|
||||
raise ValueError("'param' attribute must be set for non-dictionary values")
|
||||
|
||||
self.add_param(ros_config, full_param, data, verbose=verbose)
|
||||
|
||||
else:
|
||||
raise ValueError("unknown command %s"%cmd)
|
||||
|
||||
|
||||
def load_env(self, context, ros_config, name, value):
|
||||
"""
|
||||
Load environment variable setting
|
||||
|
||||
@param context: Loader context
|
||||
@type context: L{LoaderContext}
|
||||
@param ros_config: launch configuration
|
||||
@type ros_config: L{ROSLaunchConfig}
|
||||
@param name: environment variable name
|
||||
@type name: str
|
||||
@param value: environment variable value
|
||||
@type value: str
|
||||
"""
|
||||
if not name:
|
||||
raise ValueError("'name' attribute must be non-empty")
|
||||
context.env_args.append((name, value))
|
||||
|
||||
|
||||
def param_value(self, verbose, name, ptype, value, textfile, binfile, command):
|
||||
"""
|
||||
Parse text representation of param spec into Python value
|
||||
@param name: param name, for error message use only
|
||||
@type name: str
|
||||
@param verbose: print verbose output
|
||||
@type verbose: bool
|
||||
@param textfile: name of text file to load from, or None
|
||||
@type textfile: str
|
||||
@param binfile: name of binary file to load from, or None
|
||||
@type binfile: str
|
||||
@param command: command to execute for parameter value, or None
|
||||
@type command: str
|
||||
@raise ValueError: if parameters are invalid
|
||||
"""
|
||||
if value is not None:
|
||||
return convert_value(value.strip(), ptype)
|
||||
elif textfile is not None:
|
||||
with open(textfile, 'r') as f:
|
||||
return f.read()
|
||||
elif binfile is not None:
|
||||
try:
|
||||
from xmlrpc.client import Binary
|
||||
except ImportError:
|
||||
from xmlrpclib import Binary
|
||||
with open(binfile, 'rb') as f:
|
||||
return Binary(f.read())
|
||||
elif command is not None:
|
||||
try:
|
||||
if type(command) == unicode:
|
||||
command = command.encode('utf-8') #attempt to force to string for shlex/subprocess
|
||||
except NameError:
|
||||
pass
|
||||
if verbose:
|
||||
print("... executing command param [%s]" % command)
|
||||
import subprocess, shlex #shlex rocks
|
||||
try:
|
||||
p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
|
||||
c_value = p.communicate()[0]
|
||||
if not isinstance(c_value, str):
|
||||
c_value = c_value.decode('utf-8')
|
||||
if p.returncode != 0:
|
||||
raise ValueError("Cannot load command parameter [%s]: command [%s] returned with code [%s]"%(name, command, p.returncode))
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
raise ValueError("Cannot load command parameter [%s]: no such command [%s]"%(name, command))
|
||||
raise
|
||||
if c_value is None:
|
||||
raise ValueError("parameter: unable to get output of command [%s]"%command)
|
||||
return c_value
|
||||
else: #_param_tag prevalidates, so this should not be reachable
|
||||
raise ValueError("unable to determine parameter value")
|
||||
|
||||
98
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/netapi.py
vendored
Normal file
98
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/netapi.py
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
"""
|
||||
Convience methods for manipulating XML-RPC APIs
|
||||
"""
|
||||
|
||||
try:
|
||||
from xmlrpc.client import ServerProxy
|
||||
except ImportError:
|
||||
from xmlrpclib import ServerProxy
|
||||
|
||||
import rosgraph
|
||||
import rosgraph.network
|
||||
|
||||
_ID = '/roslaunch_netapi'
|
||||
def get_roslaunch_uris():
|
||||
"""
|
||||
@return: list of roslaunch XML-RPC URIs for roscore that's in
|
||||
the current environment, or None if roscore cannot be contacted.
|
||||
@rtype: [str]
|
||||
"""
|
||||
try:
|
||||
m = rosgraph.Master(_ID)
|
||||
vals = m.getParam('/roslaunch/uris')
|
||||
return vals.values()
|
||||
except rosgraph.MasterException:
|
||||
return None
|
||||
|
||||
class NetProcess(object):
|
||||
def __init__(self, name, respawn_count, is_alive, roslaunch_uri):
|
||||
self.is_alive = is_alive
|
||||
self.respawn_count = respawn_count
|
||||
self.name = name
|
||||
|
||||
self.roslaunch_uri = roslaunch_uri
|
||||
self.machine, _ = rosgraph.network.parse_http_host_and_port(roslaunch_uri)
|
||||
|
||||
def list_processes(roslaunch_uris=None):
|
||||
"""
|
||||
@param roslaunch_uris: (optional) list of XML-RPCS. If none
|
||||
are provided, will look up URIs dynamically
|
||||
@type roslaunch_uris: [str]
|
||||
@return: list of roslaunch processes
|
||||
@rtype: [L{NetProcess}]
|
||||
"""
|
||||
if not roslaunch_uris:
|
||||
roslaunch_uris = get_roslaunch_uris()
|
||||
if not roslaunch_uris:
|
||||
return []
|
||||
|
||||
procs = []
|
||||
for uri in roslaunch_uris:
|
||||
try:
|
||||
r = ServerProxy(uri)
|
||||
code, msg, val = r.list_processes()
|
||||
if code == 1:
|
||||
active, dead = val
|
||||
procs.extend([NetProcess(a[0], a[1], True, uri) for a in active])
|
||||
procs.extend([NetProcess(d[0], d[1], False, uri) for d in dead])
|
||||
except:
|
||||
#import traceback
|
||||
#traceback.print_exc()
|
||||
# don't have a mechanism for reporting these errors upwards yet
|
||||
pass
|
||||
return procs
|
||||
|
||||
266
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/node_args.py
vendored
Normal file
266
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/node_args.py
vendored
Normal file
@@ -0,0 +1,266 @@
|
||||
# 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$
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
"""
|
||||
Utility module of roslaunch that computes the command-line arguments
|
||||
for a node.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
import time
|
||||
|
||||
import rospkg
|
||||
import rosgraph
|
||||
import rosgraph.names
|
||||
from rosgraph.names import script_resolve_name
|
||||
|
||||
import roslib.packages
|
||||
|
||||
from . import substitution_args
|
||||
|
||||
from roslaunch.core import setup_env, local_machine, RLException
|
||||
from roslaunch.config import load_config_default
|
||||
import roslaunch.xmlloader
|
||||
|
||||
class NodeParamsException(Exception):
|
||||
"""
|
||||
Exception to indicate that node parameters were invalid
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_node_list(config):
|
||||
"""
|
||||
@param config: roslaunch config
|
||||
@type config: ROSLaunchConfig
|
||||
@return: list of node names in config
|
||||
@rtype: [str]
|
||||
"""
|
||||
l = [_resolved_name(node) for node in config.nodes] + [_resolved_name(test) for test in config.tests]
|
||||
# filter out unnamed nodes
|
||||
return [x for x in l if x]
|
||||
|
||||
def print_node_list(roslaunch_files):
|
||||
"""
|
||||
Print list of nodes to screen. Will cause system exit if exception
|
||||
occurs. This is a subroutine for the roslaunch main handler.
|
||||
|
||||
@param roslaunch_files: list of launch files to load
|
||||
@type roslaunch_files: str
|
||||
"""
|
||||
try:
|
||||
loader = roslaunch.xmlloader.XmlLoader(resolve_anon=False)
|
||||
config = load_config_default(roslaunch_files, None, loader=loader, verbose=False, assign_machines=False)
|
||||
node_list = get_node_list(config)
|
||||
print('\n'.join(node_list))
|
||||
except RLException as e:
|
||||
print(str(e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def print_node_args(node_name, roslaunch_files):
|
||||
"""
|
||||
Print arguments of node to screen. Will cause system exit if
|
||||
exception occurs. This is a subroutine for the roslaunch main
|
||||
handler.
|
||||
|
||||
@param node_name: node name
|
||||
@type node_name: str
|
||||
@param roslaunch_files: list of launch files to load
|
||||
@type roslaunch_files: str
|
||||
"""
|
||||
try:
|
||||
node_name = script_resolve_name('roslaunch', node_name)
|
||||
args = get_node_args(node_name, roslaunch_files)
|
||||
print(' '.join(args))
|
||||
except RLException as e:
|
||||
print(str(e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def _resolved_name(node):
|
||||
if node.name:
|
||||
# $(anon id) passthrough
|
||||
if node.name.startswith('$'):
|
||||
return node.name
|
||||
else:
|
||||
return rosgraph.names.ns_join(node.namespace, node.name)
|
||||
else:
|
||||
return None
|
||||
|
||||
def print_node_filename(node_name, roslaunch_files):
|
||||
try:
|
||||
# #2309
|
||||
node_name = script_resolve_name('roslaunch', node_name)
|
||||
|
||||
loader = roslaunch.xmlloader.XmlLoader(resolve_anon=False)
|
||||
config = load_config_default(roslaunch_files, None, loader=loader, verbose=False, assign_machines=False)
|
||||
nodes = [n for n in config.nodes if _resolved_name(n) == node_name] + \
|
||||
[t for t in config.tests if _resolved_name(t) == node_name]
|
||||
|
||||
if len(nodes) > 1:
|
||||
raise RLException("ERROR: multiple nodes named [%s] in [%s].\nPlease fix the launch files as duplicate names are not allowed."%(node_name, ', '.join(roslaunch_files)))
|
||||
if not nodes:
|
||||
print('ERROR: cannot find node named [%s]. Run \n\troslaunch --nodes <files>\nto see list of node names.' % (node_name), file=sys.stderr)
|
||||
else:
|
||||
print(nodes[0].filename)
|
||||
|
||||
except RLException as e:
|
||||
print(str(e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def get_node_args(node_name, roslaunch_files):
|
||||
"""
|
||||
Get the node arguments for a node in roslaunch_files.
|
||||
|
||||
@param node_name: name of node in roslaunch_files.
|
||||
@type node_name: str
|
||||
@param roslaunch_files: roslaunch file names
|
||||
@type roslaunch_files: [str]
|
||||
@return: list of command-line arguments used to launch node_name
|
||||
@rtype: [str]
|
||||
@raise RLException: if node args cannot be retrieved
|
||||
"""
|
||||
|
||||
# we have to create our own XmlLoader so that we can use the same
|
||||
# resolution context for substitution args
|
||||
|
||||
loader = roslaunch.xmlloader.XmlLoader(resolve_anon=False)
|
||||
config = load_config_default(roslaunch_files, None, loader=loader, verbose=False, assign_machines=False)
|
||||
(node_name) = substitution_args.resolve_args((node_name), resolve_anon=False)
|
||||
node_name = script_resolve_name('roslaunch', node_name) if not node_name.startswith('$') else node_name
|
||||
|
||||
node = [n for n in config.nodes if _resolved_name(n) == node_name] + \
|
||||
[n for n in config.tests if _resolved_name(n) == node_name]
|
||||
if not node:
|
||||
node_list = get_node_list(config)
|
||||
node_list_str = '\n'.join([" * %s"%x for x in node_list])
|
||||
raise RLException("ERROR: Cannot find node named [%s] in [%s].\nNode names are:\n%s"%(node_name, ', '.join(roslaunch_files), node_list_str))
|
||||
elif len(node) > 1:
|
||||
raise RLException("ERROR: multiple nodes named [%s] in [%s].\nPlease fix the launch files as duplicate names are not allowed."%(node_name, ', '.join(roslaunch_files)))
|
||||
node = node[0]
|
||||
|
||||
master_uri = rosgraph.get_master_uri()
|
||||
machine = local_machine()
|
||||
env = setup_env(node, machine, master_uri)
|
||||
|
||||
# remove setting identical to current environment for easier debugging
|
||||
to_remove = []
|
||||
for k in env.keys():
|
||||
if env[k] == os.environ.get(k, None):
|
||||
to_remove.append(k)
|
||||
for k in to_remove:
|
||||
del env[k]
|
||||
|
||||
# resolve node name for generating args
|
||||
args = create_local_process_args(node, machine)
|
||||
# join environment vars are bash prefix args
|
||||
return ["%s=%s"%(k, v) for k, v in env.items()] + args
|
||||
|
||||
def _launch_prefix_args(node):
|
||||
if node.launch_prefix:
|
||||
prefix = node.launch_prefix
|
||||
try:
|
||||
if type(prefix) == unicode:
|
||||
prefix = prefix.encode('UTF-8')
|
||||
except NameError:
|
||||
pass
|
||||
return shlex.split(prefix)
|
||||
else:
|
||||
return []
|
||||
|
||||
_rospack = None
|
||||
|
||||
|
||||
def create_local_process_args(node, machine, env=None):
|
||||
"""
|
||||
Subroutine for creating node arguments.
|
||||
|
||||
:param env: override os.environ. Warning, this does not override
|
||||
substitution args in node configuration (for now), ``dict``
|
||||
:returns: arguments for node process, ``[str]``
|
||||
:raises: :exc:`NodeParamsException` If args cannot be constructed for Node
|
||||
as specified (e.g. the node type does not exist)
|
||||
"""
|
||||
global _rospack
|
||||
if not node.name:
|
||||
raise ValueError("node name must be defined")
|
||||
# create rospack instance if no cached value is available or for custom environments
|
||||
if not _rospack or env is not None:
|
||||
rospack = rospkg.RosPack(rospkg.get_ros_paths(env=env))
|
||||
# cache rospack instance for default environment
|
||||
if env is None:
|
||||
_rospack = rospack
|
||||
else:
|
||||
rospack = _rospack
|
||||
|
||||
# - Construct rosrun command
|
||||
remap_args = ["%s:=%s"%(src,dst) for src, dst in node.remap_args]
|
||||
resolve_dict = {}
|
||||
|
||||
#resolve args evaluates substitution commands
|
||||
#shlex parses a command string into a list of args
|
||||
# - for the local process args, we *do* resolve the anon tag so that the user can execute
|
||||
# - the node name and args must be resolved together in case the args refer to the anon node name
|
||||
(node_name) = substitution_args.resolve_args((node.name), context=resolve_dict, resolve_anon=True)
|
||||
node.name = node_name
|
||||
remap_args.append('__name:=%s'%node_name)
|
||||
|
||||
resolved = substitution_args.resolve_args(node.args, context=resolve_dict, resolve_anon=True)
|
||||
try:
|
||||
if type(resolved) == unicode:
|
||||
resolved = resolved.encode('UTF-8') #attempt to force to string for shlex/subprocess
|
||||
except NameError:
|
||||
pass
|
||||
args = shlex.split(resolved) + remap_args
|
||||
try:
|
||||
#TODO:fuerte: pass through rospack and catkin cache
|
||||
matches = roslib.packages.find_node(node.package, node.type, rospack=rospack)
|
||||
except rospkg.ResourceNotFound as e:
|
||||
# multiple nodes, invalid package
|
||||
raise NodeParamsException(str(e))
|
||||
if not matches:
|
||||
raise NodeParamsException("can't locate node [%s] in package [%s]"%(node.type, node.package))
|
||||
else:
|
||||
# old behavior was to take first, do we want to change this in Fuerte-style?
|
||||
cmd = matches[0]
|
||||
if not cmd:
|
||||
raise NodeParamsException("Cannot locate node of type [%s] in package [%s]"%(node.type, node.package))
|
||||
cmd = [cmd]
|
||||
if sys.platform in ['win32']:
|
||||
if os.path.splitext(cmd[0])[1] == '.py':
|
||||
cmd = ['python'] + cmd
|
||||
return _launch_prefix_args(node) + cmd + args
|
||||
543
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/nodeprocess.py
vendored
Normal file
543
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/nodeprocess.py
vendored
Normal file
@@ -0,0 +1,543 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
"""
|
||||
Local process implementation for running and monitoring nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import rospkg
|
||||
|
||||
from roslaunch.core import *
|
||||
#from roslaunch.core import setup_env
|
||||
from roslaunch.node_args import create_local_process_args
|
||||
from roslaunch.pmon import Process, FatalProcessLaunch
|
||||
|
||||
from rosmaster.master_api import NUM_WORKERS
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger("roslaunch")
|
||||
|
||||
_TIMEOUT_SIGINT = 15.0 #seconds
|
||||
_TIMEOUT_SIGTERM = 2.0 #seconds
|
||||
|
||||
_counter = 0
|
||||
def _next_counter():
|
||||
global _counter
|
||||
_counter += 1
|
||||
return _counter
|
||||
|
||||
def create_master_process(run_id, type_, ros_root, port, num_workers=NUM_WORKERS, timeout=None):
|
||||
"""
|
||||
Launch a master
|
||||
@param type_: name of master executable (currently just Master.ZENMASTER)
|
||||
@type type_: str
|
||||
@param ros_root: ROS_ROOT environment setting
|
||||
@type ros_root: str
|
||||
@param port: port to launch master on
|
||||
@type port: int
|
||||
@param num_workers: number of worker threads.
|
||||
@type num_workers: int
|
||||
@param timeout: socket timeout for connections.
|
||||
@type timeout: float
|
||||
@raise RLException: if type_ or port is invalid
|
||||
"""
|
||||
if port < 1 or port > 65535:
|
||||
raise RLException("invalid port assignment: %s"%port)
|
||||
|
||||
_logger.info("create_master_process: %s, %s, %s, %s, %s", type_, ros_root, port, num_workers, timeout)
|
||||
# catkin/fuerte: no longer use ROS_ROOT-relative executables, search path instead
|
||||
master = type_
|
||||
# zenmaster is deprecated and aliased to rosmaster
|
||||
if type_ in [Master.ROSMASTER, Master.ZENMASTER]:
|
||||
package = 'rosmaster'
|
||||
args = [master, '--core', '-p', str(port), '-w', str(num_workers)]
|
||||
if timeout is not None:
|
||||
args += ['-t', str(timeout)]
|
||||
else:
|
||||
raise RLException("unknown master typ_: %s"%type_)
|
||||
|
||||
_logger.info("process[master]: launching with args [%s]"%args)
|
||||
log_output = False
|
||||
return LocalProcess(run_id, package, 'master', args, os.environ, log_output, None, required=True)
|
||||
|
||||
def create_node_process(run_id, node, master_uri):
|
||||
"""
|
||||
Factory for generating processes for launching local ROS
|
||||
nodes. Also registers the process with the L{ProcessMonitor} so that
|
||||
events can be generated when the process dies.
|
||||
|
||||
@param run_id: run_id of launch
|
||||
@type run_id: str
|
||||
@param node: node to launch. Node name must be assigned.
|
||||
@type node: L{Node}
|
||||
@param master_uri: API URI for master node
|
||||
@type master_uri: str
|
||||
@return: local process instance
|
||||
@rtype: L{LocalProcess}
|
||||
@raise NodeParamsException: If the node's parameters are improperly specific
|
||||
"""
|
||||
_logger.info("create_node_process: package[%s] type[%s] machine[%s] master_uri[%s]", node.package, node.type, node.machine, master_uri)
|
||||
# check input args
|
||||
machine = node.machine
|
||||
if machine is None:
|
||||
raise RLException("Internal error: no machine selected for node of type [%s/%s]"%(node.package, node.type))
|
||||
if not node.name:
|
||||
raise ValueError("node name must be assigned")
|
||||
|
||||
# - setup env for process (vars must be strings for os.environ)
|
||||
env = setup_env(node, machine, master_uri)
|
||||
|
||||
if not node.name:
|
||||
raise ValueError("node name must be assigned")
|
||||
|
||||
# we have to include the counter to prevent potential name
|
||||
# collisions between the two branches
|
||||
|
||||
name = "%s-%s"%(rosgraph.names.ns_join(node.namespace, node.name), _next_counter())
|
||||
if name[0] == '/':
|
||||
name = name[1:]
|
||||
|
||||
_logger.info('process[%s]: env[%s]', name, env)
|
||||
|
||||
args = create_local_process_args(node, machine)
|
||||
|
||||
_logger.info('process[%s]: args[%s]', name, args)
|
||||
|
||||
# default for node.output not set is 'log'
|
||||
log_output = node.output != 'screen'
|
||||
_logger.debug('process[%s]: returning LocalProcess wrapper')
|
||||
return LocalProcess(run_id, node.package, name, args, env, log_output, \
|
||||
respawn=node.respawn, respawn_delay=node.respawn_delay, \
|
||||
required=node.required, cwd=node.cwd)
|
||||
|
||||
|
||||
class LocalProcess(Process):
|
||||
"""
|
||||
Process launched on local machine
|
||||
"""
|
||||
|
||||
def __init__(self, run_id, package, name, args, env, log_output,
|
||||
respawn=False, respawn_delay=0.0, required=False, cwd=None,
|
||||
is_node=True):
|
||||
"""
|
||||
@param run_id: unique run ID for this roslaunch. Used to
|
||||
generate log directory location. run_id may be None if this
|
||||
feature is not being used.
|
||||
@type run_id: str
|
||||
@param package: name of package process is part of
|
||||
@type package: str
|
||||
@param name: name of process
|
||||
@type name: str
|
||||
@param args: list of arguments to process
|
||||
@type args: [str]
|
||||
@param env: environment dictionary for process
|
||||
@type env: {str : str}
|
||||
@param log_output: if True, log output streams of process
|
||||
@type log_output: bool
|
||||
@param respawn: respawn process if it dies (default is False)
|
||||
@type respawn: bool
|
||||
@param respawn_delay: respawn process after a delay
|
||||
@type respawn_delay: float
|
||||
@param cwd: working directory of process, or None
|
||||
@type cwd: str
|
||||
@param is_node: (optional) if True, process is ROS node and accepts ROS node command-line arguments. Default: True
|
||||
@type is_node: False
|
||||
"""
|
||||
super(LocalProcess, self).__init__(package, name, args, env,
|
||||
respawn, respawn_delay, required)
|
||||
self.run_id = run_id
|
||||
self.popen = None
|
||||
self.log_output = log_output
|
||||
self.started = False
|
||||
self.stopped = False
|
||||
self.cwd = cwd
|
||||
self.log_dir = None
|
||||
self.pid = -1
|
||||
self.is_node = is_node
|
||||
|
||||
# NOTE: in the future, info() is going to have to be sufficient for relaunching a process
|
||||
def get_info(self):
|
||||
"""
|
||||
Get all data about this process in dictionary form
|
||||
"""
|
||||
info = super(LocalProcess, self).get_info()
|
||||
info['pid'] = self.pid
|
||||
if self.run_id:
|
||||
info['run_id'] = self.run_id
|
||||
info['log_output'] = self.log_output
|
||||
if self.cwd is not None:
|
||||
info['cwd'] = self.cwd
|
||||
return info
|
||||
|
||||
def _configure_logging(self):
|
||||
"""
|
||||
Configure logging of node's log file and stdout/stderr
|
||||
@return: stdout log file name, stderr log file
|
||||
name. Values are None if stdout/stderr are not logged.
|
||||
@rtype: str, str
|
||||
"""
|
||||
log_dir = rospkg.get_log_dir(env=os.environ)
|
||||
if self.run_id:
|
||||
log_dir = os.path.join(log_dir, self.run_id)
|
||||
if not os.path.exists(log_dir):
|
||||
try:
|
||||
os.makedirs(log_dir)
|
||||
except OSError as e:
|
||||
if e.errno == 13:
|
||||
raise RLException("unable to create directory for log file [%s].\nPlease check permissions."%log_dir)
|
||||
else:
|
||||
raise RLException("unable to create directory for log file [%s]: %s"%(log_dir, e.strerror))
|
||||
# #973: save log dir for error messages
|
||||
self.log_dir = log_dir
|
||||
|
||||
# send stdout/stderr to file. in the case of respawning, we have to
|
||||
# open in append mode
|
||||
# note: logfileerr: disabling in favor of stderr appearing in the console.
|
||||
# will likely reinstate once roserr/rosout is more properly used.
|
||||
logfileout = logfileerr = None
|
||||
logfname = self._log_name()
|
||||
|
||||
if self.log_output:
|
||||
outf, errf = [os.path.join(log_dir, '%s-%s.log'%(logfname, n)) for n in ['stdout', 'stderr']]
|
||||
if self.respawn:
|
||||
mode = 'a'
|
||||
else:
|
||||
mode = 'w'
|
||||
logfileout = open(outf, mode)
|
||||
if is_child_mode():
|
||||
logfileerr = open(errf, mode)
|
||||
|
||||
# #986: pass in logfile name to node
|
||||
node_log_file = log_dir
|
||||
if self.is_node:
|
||||
# #1595: on respawn, these keep appending
|
||||
self.args = _cleanup_remappings(self.args, '__log:=')
|
||||
self.args.append("__log:=%s"%os.path.join(log_dir, "%s.log"%(logfname)))
|
||||
|
||||
return logfileout, logfileerr
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start the process.
|
||||
|
||||
@raise FatalProcessLaunch: if process cannot be started and it
|
||||
is not likely to ever succeed
|
||||
"""
|
||||
super(LocalProcess, self).start()
|
||||
try:
|
||||
self.lock.acquire()
|
||||
if self.started:
|
||||
_logger.info("process[%s]: restarting os process", self.name)
|
||||
else:
|
||||
_logger.info("process[%s]: starting os process", self.name)
|
||||
self.started = self.stopped = False
|
||||
|
||||
full_env = self.env
|
||||
|
||||
# _configure_logging() can mutate self.args
|
||||
try:
|
||||
logfileout, logfileerr = self._configure_logging()
|
||||
except Exception as e:
|
||||
_logger.error(traceback.format_exc())
|
||||
printerrlog("[%s] ERROR: unable to configure logging [%s]"%(self.name, str(e)))
|
||||
# it's not safe to inherit from this process as
|
||||
# rostest changes stdout to a StringIO, which is not a
|
||||
# proper file.
|
||||
logfileout, logfileerr = subprocess.PIPE, subprocess.PIPE
|
||||
|
||||
if self.cwd == 'node':
|
||||
cwd = os.path.dirname(self.args[0])
|
||||
elif self.cwd == 'cwd':
|
||||
cwd = os.getcwd()
|
||||
elif self.cwd == 'ros-root':
|
||||
cwd = get_ros_root()
|
||||
else:
|
||||
cwd = rospkg.get_ros_home()
|
||||
if not os.path.exists(cwd):
|
||||
try:
|
||||
os.makedirs(cwd)
|
||||
except OSError:
|
||||
# exist_ok=True
|
||||
pass
|
||||
|
||||
_logger.info("process[%s]: start w/ args [%s]", self.name, self.args)
|
||||
_logger.info("process[%s]: cwd will be [%s]", self.name, cwd)
|
||||
|
||||
try:
|
||||
self.popen = subprocess.Popen(self.args, cwd=cwd, stdout=logfileout, stderr=logfileerr, env=full_env, close_fds=True, preexec_fn=os.setsid)
|
||||
except OSError as e:
|
||||
self.started = True # must set so is_alive state is correct
|
||||
_logger.error("OSError(%d, %s)", e.errno, e.strerror)
|
||||
if e.errno == 8: #Exec format error
|
||||
raise FatalProcessLaunch("Unable to launch [%s]. \nIf it is a script, you may be missing a '#!' declaration at the top."%self.name)
|
||||
elif e.errno == 2: #no such file or directory
|
||||
raise FatalProcessLaunch("""Roslaunch got a '%s' error while attempting to run:
|
||||
|
||||
%s
|
||||
|
||||
Please make sure that all the executables in this command exist and have
|
||||
executable permission. This is often caused by a bad launch-prefix."""%(e.strerror, ' '.join(self.args)))
|
||||
else:
|
||||
raise FatalProcessLaunch("unable to launch [%s]: %s"%(' '.join(self.args), e.strerror))
|
||||
|
||||
self.started = True
|
||||
# Check that the process is either still running (poll returns
|
||||
# None) or that it completed successfully since when we
|
||||
# launched it above (poll returns the return code, 0).
|
||||
poll_result = self.popen.poll()
|
||||
if poll_result is None or poll_result == 0:
|
||||
self.pid = self.popen.pid
|
||||
printlog_bold("process[%s]: started with pid [%s]"%(self.name, self.pid))
|
||||
return True
|
||||
else:
|
||||
printerrlog("failed to start local process: %s"%(' '.join(self.args)))
|
||||
return False
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def _log_name(self):
|
||||
return self.name.replace('/', '-')
|
||||
|
||||
def is_alive(self):
|
||||
"""
|
||||
@return: True if process is still running
|
||||
@rtype: bool
|
||||
"""
|
||||
if not self.started: #not started yet
|
||||
return True
|
||||
if self.stopped or self.popen is None:
|
||||
if self.time_of_death is None:
|
||||
self.time_of_death = time.time()
|
||||
return False
|
||||
self.exit_code = self.popen.poll()
|
||||
if self.exit_code is not None:
|
||||
if self.time_of_death is None:
|
||||
self.time_of_death = time.time()
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_exit_description(self):
|
||||
"""
|
||||
@return: human-readable description of exit state
|
||||
@rtype: str
|
||||
"""
|
||||
if self.exit_code is None:
|
||||
output = 'process has died without exit code [pid %s, cmd %s].'%(self.pid, ' '.join(self.args))
|
||||
elif self.exit_code != 0:
|
||||
output = 'process has died [pid %s, exit code %s, cmd %s].'%(self.pid, self.exit_code, ' '.join(self.args))
|
||||
else:
|
||||
output = 'process has finished cleanly'
|
||||
|
||||
if self.log_dir:
|
||||
# #973: include location of output location in message
|
||||
output += '\nlog file: %s*.log'%(os.path.join(self.log_dir, self._log_name()))
|
||||
return output
|
||||
|
||||
def _stop_unix(self, errors):
|
||||
"""
|
||||
UNIX implementation of process killing
|
||||
|
||||
@param errors: error messages. stop() will record messages into this list.
|
||||
@type errors: [str]
|
||||
"""
|
||||
self.exit_code = self.popen.poll()
|
||||
if self.exit_code is not None:
|
||||
_logger.debug("process[%s].stop(): process has already returned %s", self.name, self.exit_code)
|
||||
#print "process[%s].stop(): process has already returned %s"%(self.name, self.exit_code)
|
||||
self.popen = None
|
||||
self.stopped = True
|
||||
return
|
||||
|
||||
pid = self.popen.pid
|
||||
pgid = os.getpgid(pid)
|
||||
_logger.info("process[%s]: killing os process with pid[%s] pgid[%s]", self.name, pid, pgid)
|
||||
|
||||
try:
|
||||
# Start with SIGINT and escalate from there.
|
||||
_logger.info("[%s] sending SIGINT to pgid [%s]", self.name, pgid)
|
||||
os.killpg(pgid, signal.SIGINT)
|
||||
_logger.info("[%s] sent SIGINT to pgid [%s]", self.name, pgid)
|
||||
timeout_t = time.time() + _TIMEOUT_SIGINT
|
||||
retcode = self.popen.poll()
|
||||
while time.time() < timeout_t and retcode is None:
|
||||
time.sleep(0.1)
|
||||
retcode = self.popen.poll()
|
||||
# Escalate non-responsive process
|
||||
if retcode is None:
|
||||
printerrlog("[%s] escalating to SIGTERM"%self.name)
|
||||
timeout_t = time.time() + _TIMEOUT_SIGTERM
|
||||
os.killpg(pgid, signal.SIGTERM)
|
||||
_logger.info("[%s] sent SIGTERM to pgid [%s]"%(self.name, pgid))
|
||||
retcode = self.popen.poll()
|
||||
while time.time() < timeout_t and retcode is None:
|
||||
time.sleep(0.2)
|
||||
_logger.debug('poll for retcode')
|
||||
retcode = self.popen.poll()
|
||||
if retcode is None:
|
||||
printerrlog("[%s] escalating to SIGKILL"%self.name)
|
||||
errors.append("process[%s, pid %s]: required SIGKILL. May still be running."%(self.name, pid))
|
||||
try:
|
||||
os.killpg(pgid, signal.SIGKILL)
|
||||
_logger.info("[%s] sent SIGKILL to pgid [%s]"%(self.name, pgid))
|
||||
# #2096: don't block on SIGKILL, because this results in more orphaned processes overall
|
||||
#self.popen.wait()
|
||||
#os.wait()
|
||||
_logger.info("process[%s]: sent SIGKILL", self.name)
|
||||
except OSError as e:
|
||||
if e.args[0] == 3:
|
||||
printerrlog("no [%s] process with pid [%s]"%(self.name, pid))
|
||||
else:
|
||||
printerrlog("errors shutting down [%s], see log for details"%self.name)
|
||||
_logger.error(traceback.format_exc())
|
||||
else:
|
||||
_logger.info("process[%s]: SIGTERM killed with return value %s", self.name, retcode)
|
||||
else:
|
||||
_logger.info("process[%s]: SIGINT killed with return value %s", self.name, retcode)
|
||||
|
||||
finally:
|
||||
self.popen = None
|
||||
|
||||
def _stop_win32(self, errors):
|
||||
"""
|
||||
Win32 implementation of process killing. In part, refer to
|
||||
|
||||
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/347462
|
||||
|
||||
Note that it doesn't work as completely as _stop_unix as it can't utilise
|
||||
group id's. This means that any program which forks children underneath it
|
||||
won't get caught by this kill mechanism.
|
||||
|
||||
@param errors: error messages. stop() will record messages into this list.
|
||||
@type errors: [str]
|
||||
"""
|
||||
self.exit_code = self.popen.poll()
|
||||
if self.exit_code is not None:
|
||||
_logger.debug("process[%s].stop(): process has already returned %s", self.name, self.exit_code)
|
||||
self.popen = None
|
||||
self.stopped = True
|
||||
return
|
||||
|
||||
pid = self.popen.pid
|
||||
_logger.info("process[%s]: killing os process/subprocesses with pid[%s]", self.name, pid)
|
||||
# windows has no group id's :(
|
||||
try:
|
||||
# Start with SIGINT and escalate from there.
|
||||
_logger.info("[%s] sending SIGINT to pgid [%s]", self.name, pid)
|
||||
os.kill(pid, signal.SIGINT)
|
||||
_logger.info("[%s] sent SIGINT to pgid [%s]", self.name, pid)
|
||||
timeout_t = time.time() + _TIMEOUT_SIGINT
|
||||
retcode = self.popen.poll()
|
||||
while time.time() < timeout_t and retcode is None:
|
||||
time.sleep(0.1)
|
||||
retcode = self.popen.poll()
|
||||
# Escalate non-responsive process
|
||||
if retcode is None:
|
||||
printerrlog("[%s] escalating to SIGTERM"%self.name)
|
||||
timeout_t = time.time() + _TIMEOUT_SIGTERM
|
||||
os.killpg(pid, signal.SIGTERM)
|
||||
_logger.info("[%s] sent SIGTERM to pid [%s]"%(self.name, pid))
|
||||
retcode = self.popen.poll()
|
||||
while time.time() < timeout_t and retcode is None:
|
||||
time.sleep(0.2)
|
||||
_logger.debug('poll for retcode')
|
||||
retcode = self.popen.poll()
|
||||
if retcode is None:
|
||||
printerrlog("[%s] escalating to SIGKILL"%self.name)
|
||||
errors.append("process[%s, pid %s]: required SIGKILL. May still be running."%(self.name, pid))
|
||||
try:
|
||||
os.killpg(pid, signal.SIGKILL)
|
||||
_logger.info("[%s] sent SIGKILL to pid [%s]"%(self.name, pid))
|
||||
# #2096: don't block on SIGKILL, because this results in more orphaned processes overall
|
||||
#self.popen.wait()
|
||||
#os.wait()
|
||||
_logger.info("process[%s]: sent SIGKILL", self.name)
|
||||
except OSError as e:
|
||||
if e.args[0] == 3:
|
||||
printerrlog("no [%s] process with pid [%s]"%(self.name, pid))
|
||||
else:
|
||||
printerrlog("errors shutting down [%s], see log for details"%self.name)
|
||||
_logger.error(traceback.format_exc())
|
||||
else:
|
||||
_logger.info("process[%s]: SIGTERM killed with return value %s", self.name, retcode)
|
||||
else:
|
||||
_logger.info("process[%s]: SIGINT killed with return value %s", self.name, retcode)
|
||||
finally:
|
||||
self.popen = None
|
||||
|
||||
def stop(self, errors=None):
|
||||
"""
|
||||
Stop the process. Record any significant error messages in the errors parameter
|
||||
|
||||
@param errors: error messages. stop() will record messages into this list.
|
||||
@type errors: [str]
|
||||
"""
|
||||
if errors is None:
|
||||
errors = []
|
||||
super(LocalProcess, self).stop(errors)
|
||||
self.lock.acquire()
|
||||
try:
|
||||
try:
|
||||
_logger.debug("process[%s].stop() starting", self.name)
|
||||
if self.popen is None:
|
||||
_logger.debug("process[%s].stop(): popen is None, nothing to kill")
|
||||
return
|
||||
if sys.platform in ['win32']: # cygwin seems to be ok
|
||||
self._stop_win32(errors)
|
||||
else:
|
||||
self._stop_unix(errors)
|
||||
except:
|
||||
#traceback.print_exc()
|
||||
_logger.error("[%s] EXCEPTION %s", self.name, traceback.format_exc())
|
||||
finally:
|
||||
self.stopped = True
|
||||
self.lock.release()
|
||||
|
||||
|
||||
# #1595
|
||||
def _cleanup_remappings(args, prefix):
|
||||
"""
|
||||
Remove all instances of args that start with prefix. This is used
|
||||
to remove args that were previously added (and are now being
|
||||
regenerated due to respawning)
|
||||
"""
|
||||
existing_args = [a for a in args if a.startswith(prefix)]
|
||||
for a in existing_args:
|
||||
args.remove(a)
|
||||
return args
|
||||
69
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/param_dump.py
vendored
Normal file
69
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/param_dump.py
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2010, 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.
|
||||
#
|
||||
|
||||
"""
|
||||
Dumps parameters from ROSLaunch files to stdout as YAML.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import roslaunch.config
|
||||
import roslaunch.xmlloader
|
||||
|
||||
import yaml
|
||||
|
||||
def dump_params(files):
|
||||
"""
|
||||
Dumps ROS parameters of a list of files to STDOUT in YAML
|
||||
|
||||
@param files: List of ROSLaunch files to load
|
||||
@type files: [ str ]
|
||||
@return: True if loaded parameters successfully
|
||||
@rtype: bool
|
||||
"""
|
||||
config = roslaunch.config.ROSLaunchConfig()
|
||||
loader = roslaunch.xmlloader.XmlLoader()
|
||||
|
||||
for f in files:
|
||||
try:
|
||||
loader.load(f, config, verbose = False)
|
||||
except Exception as e:
|
||||
sys.stderr.write("Unable to load file %s: %s" % (f, e))
|
||||
return False
|
||||
|
||||
# Now print params in YAML format.
|
||||
params_dict = {}
|
||||
for k, v in config.params.items():
|
||||
params_dict[str(k)] = v.value
|
||||
sys.stdout.write(yaml.safe_dump(params_dict)+'\n')
|
||||
return True
|
||||
316
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/parent.py
vendored
Normal file
316
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/parent.py
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
"""
|
||||
roslaunch.parent providees the L{ROSLaunchParent} implementation,
|
||||
which represents the main 'parent' roslaunch process.
|
||||
|
||||
ROSLaunch has a client/server architecture for running remote
|
||||
processes. When a user runs roslaunch, this creates a "parent"
|
||||
roslaunch process, which is responsible for managing local
|
||||
processes. This parent process will also start "child" processes on
|
||||
remote machines. The parent can then invoke methods on this child
|
||||
process to launch remote processes, and the child can invoke methods
|
||||
on the parent to provide feedback.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import roslaunch.config
|
||||
from roslaunch.core import printlog_bold, printerrlog, RLException
|
||||
import roslaunch.launch
|
||||
import roslaunch.pmon
|
||||
import roslaunch.server
|
||||
import roslaunch.xmlloader
|
||||
|
||||
from rosmaster.master_api import NUM_WORKERS
|
||||
|
||||
#TODO: probably move process listener infrastructure into here
|
||||
|
||||
# TODO: remove after wg_hardware_roslaunch has been updated
|
||||
# qualification accesses this API, which has been relocated
|
||||
load_config_default = roslaunch.config.load_config_default
|
||||
|
||||
class ROSLaunchParent(object):
|
||||
"""
|
||||
ROSLaunchParent represents the main 'parent' roslaunch process. It
|
||||
is responsible for loading the launch files, assigning machines,
|
||||
and then starting up any remote processes. The __main__ method
|
||||
delegates most of runtime to ROSLaunchParent.
|
||||
|
||||
This must be called from the Python Main thread due to signal registration.
|
||||
"""
|
||||
|
||||
def __init__(self, run_id, roslaunch_files, is_core=False, port=None, local_only=False, process_listeners=None,
|
||||
verbose=False, force_screen=False, is_rostest=False, roslaunch_strs=None, num_workers=NUM_WORKERS, timeout=None):
|
||||
"""
|
||||
@param run_id: UUID of roslaunch session
|
||||
@type run_id: str
|
||||
@param roslaunch_files: list of launch configuration
|
||||
files to load
|
||||
@type roslaunch_files: [str]
|
||||
@param is_core bool: if True, this launch is a roscore
|
||||
instance. This affects the error behavior if a master is
|
||||
already running (i.e. it fails).
|
||||
@type is_core: bool
|
||||
@param process_listeners: (optional) list of process listeners
|
||||
to register with process monitor once launch is running
|
||||
@type process_listeners: [L{roslaunch.pmon.ProcessListener}]
|
||||
@param port: (optional) override master port number from what is specified in the master URI.
|
||||
@type port: int
|
||||
@param verbose: (optional) print verbose output
|
||||
@type verbose: boolean
|
||||
@param force_screen: (optional) force output of all nodes to screen
|
||||
@type force_screen: boolean
|
||||
@param is_rostest bool: if True, this launch is a rostest
|
||||
instance. This affects validation checks.
|
||||
@type is_rostest: bool
|
||||
@param num_workers: If this is the core, the number of worker-threads to use.
|
||||
@type num_workers: int
|
||||
@param timeout: If this is the core, the socket-timeout to use.
|
||||
@type timeout: Float or None
|
||||
@throws RLException
|
||||
"""
|
||||
|
||||
self.logger = logging.getLogger('roslaunch.parent')
|
||||
self.run_id = run_id
|
||||
self.process_listeners = process_listeners
|
||||
|
||||
self.roslaunch_files = roslaunch_files
|
||||
self.roslaunch_strs = roslaunch_strs
|
||||
self.is_core = is_core
|
||||
self.is_rostest = is_rostest
|
||||
self.port = port
|
||||
self.local_only = local_only
|
||||
self.verbose = verbose
|
||||
self.num_workers = num_workers
|
||||
self.timeout = timeout
|
||||
|
||||
# I don't think we should have to pass in so many options from
|
||||
# the outside into the roslaunch parent. One possibility is to
|
||||
# allow alternate config loaders to be passed in.
|
||||
self.force_screen = force_screen
|
||||
|
||||
# flag to prevent multiple shutdown attempts
|
||||
self._shutting_down = False
|
||||
|
||||
self.config = self.runner = self.server = self.pm = self.remote_runner = None
|
||||
|
||||
def _load_config(self):
|
||||
self.config = roslaunch.config.load_config_default(self.roslaunch_files, self.port,
|
||||
roslaunch_strs=self.roslaunch_strs, verbose=self.verbose)
|
||||
|
||||
# #2370 (I really want to move this logic outside of parent)
|
||||
if self.force_screen:
|
||||
for n in self.config.nodes:
|
||||
n.output = 'screen'
|
||||
|
||||
def _start_pm(self):
|
||||
"""
|
||||
Start the process monitor
|
||||
"""
|
||||
self.pm = roslaunch.pmon.start_process_monitor()
|
||||
|
||||
def _init_runner(self):
|
||||
"""
|
||||
Initialize the roslaunch runner
|
||||
"""
|
||||
if self.config is None:
|
||||
raise RLException("config is not initialized")
|
||||
if self.pm is None:
|
||||
raise RLException("pm is not initialized")
|
||||
if self.server is None:
|
||||
raise RLException("server is not initialized")
|
||||
self.runner = roslaunch.launch.ROSLaunchRunner(self.run_id, self.config, server_uri=self.server.uri, pmon=self.pm, is_core=self.is_core, remote_runner=self.remote_runner, is_rostest=self.is_rostest, num_workers=self.num_workers, timeout=self.timeout)
|
||||
|
||||
# print runner info to user, put errors last to make the more visible
|
||||
if self.is_core:
|
||||
print("ros_comm version %s" % (self.config.params['/rosversion'].value))
|
||||
|
||||
print(self.config.summary(local=self.remote_runner is None))
|
||||
if self.config:
|
||||
for err in self.config.config_errors:
|
||||
printerrlog("WARNING: %s"%err)
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Initialize the roslaunch parent XML-RPC server
|
||||
"""
|
||||
if self.config is None:
|
||||
raise RLException("config is not initialized")
|
||||
if self.pm is None:
|
||||
raise RLException("pm is not initialized")
|
||||
|
||||
self.logger.info("starting parent XML-RPC server")
|
||||
self.server = roslaunch.server.ROSLaunchParentNode(self.config, self.pm)
|
||||
self.server.start()
|
||||
if not self.server.uri:
|
||||
raise RLException("server URI did not initialize")
|
||||
self.logger.info("... parent XML-RPC server started")
|
||||
|
||||
def _init_remote(self):
|
||||
"""
|
||||
Initialize the remote process runner, if required. Subroutine
|
||||
of _start_remote, separated out for easier testing
|
||||
"""
|
||||
if self.config is None:
|
||||
raise RLException("config is not initialized")
|
||||
if self.pm is None:
|
||||
raise RLException("pm is not initialized")
|
||||
if self.server is None:
|
||||
raise RLException("server is not initialized")
|
||||
|
||||
if not self.local_only and self.config.has_remote_nodes():
|
||||
# keep the remote package lazy-imported
|
||||
import roslaunch.remote
|
||||
self.remote_runner = roslaunch.remote.ROSRemoteRunner(self.run_id, self.config, self.pm, self.server)
|
||||
elif self.local_only:
|
||||
printlog_bold("LOCAL\nlocal only launch specified, will not launch remote nodes\nLOCAL\n")
|
||||
|
||||
def _start_remote(self):
|
||||
"""
|
||||
Initializes and runs the remote process runner, if required
|
||||
"""
|
||||
if self.remote_runner is None:
|
||||
self._init_remote()
|
||||
|
||||
if self.remote_runner is not None:
|
||||
# start_servers() runs the roslaunch children
|
||||
self.remote_runner.start_children()
|
||||
|
||||
def _start_infrastructure(self):
|
||||
"""
|
||||
load config, start XMLRPC servers and process monitor
|
||||
"""
|
||||
if self.config is None:
|
||||
self._load_config()
|
||||
|
||||
# Start the process monitor
|
||||
if self.pm is None:
|
||||
self._start_pm()
|
||||
|
||||
# Startup the roslaunch runner and XMLRPC server.
|
||||
# Requires pm
|
||||
if self.server is None:
|
||||
self._start_server()
|
||||
|
||||
# Startup the remote infrastructure.
|
||||
# Requires config, pm, and server
|
||||
self._start_remote()
|
||||
|
||||
def _stop_infrastructure(self):
|
||||
"""
|
||||
Tear down server and process monitor. Not multithread safe.
|
||||
"""
|
||||
#TODO more explicit teardown of remote infrastructure
|
||||
|
||||
# test and set flag so we don't shutdown multiple times
|
||||
if self._shutting_down:
|
||||
return
|
||||
self._shutting_down = True
|
||||
|
||||
if self.server:
|
||||
try:
|
||||
self.server.shutdown("roslaunch parent complete")
|
||||
except:
|
||||
# don't let exceptions halt the rest of the shutdown
|
||||
pass
|
||||
if self.pm:
|
||||
self.pm.shutdown()
|
||||
self.pm.join()
|
||||
|
||||
def start(self, auto_terminate=True):
|
||||
"""
|
||||
Run the parent roslaunch.
|
||||
|
||||
@param auto_terminate: stop process monitor once there are no
|
||||
more processes to monitor (default True). This defaults to
|
||||
True, which is the command-line behavior of roslaunch. Scripts
|
||||
may wish to set this to False if they wish to keep the
|
||||
roslauch infrastructure up regardless of processes being
|
||||
monitored.
|
||||
"""
|
||||
self.logger.info("starting roslaunch parent run")
|
||||
|
||||
# load config, start XMLRPC servers and process monitor
|
||||
try:
|
||||
self._start_infrastructure()
|
||||
except:
|
||||
# infrastructure did not initialize, do teardown on whatever did come up
|
||||
self._stop_infrastructure()
|
||||
raise
|
||||
|
||||
# Initialize the actual runner.
|
||||
# Requires config, pm, server and remote_runner
|
||||
self._init_runner()
|
||||
|
||||
# Start the launch
|
||||
self.runner.launch()
|
||||
|
||||
# inform process monitor that we are done with process registration
|
||||
if auto_terminate:
|
||||
self.pm.registrations_complete()
|
||||
|
||||
self.logger.info("... roslaunch parent running, waiting for process exit")
|
||||
if self.process_listeners:
|
||||
for l in self.process_listeners:
|
||||
self.runner.pm.add_process_listener(l)
|
||||
# Add listeners to server as well, otherwise they won't be
|
||||
# called when a node on a remote machine dies.
|
||||
self.server.add_process_listener(l)
|
||||
|
||||
def spin_once(self):
|
||||
"""
|
||||
Run the parent roslaunch event loop once
|
||||
"""
|
||||
if self.runner:
|
||||
self.runner.spin_once()
|
||||
|
||||
def spin(self):
|
||||
"""
|
||||
Run the parent roslaunch until exit
|
||||
"""
|
||||
if not self.runner:
|
||||
raise RLException("parent not started yet")
|
||||
try:
|
||||
# Blocks until all processes dead/shutdown
|
||||
self.runner.spin()
|
||||
finally:
|
||||
self._stop_infrastructure()
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Stop the parent roslaunch.
|
||||
"""
|
||||
self._stop_infrastructure()
|
||||
719
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/pmon.py
vendored
Normal file
719
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/pmon.py
vendored
Normal file
@@ -0,0 +1,719 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import with_statement
|
||||
|
||||
"""
|
||||
Process monitoring implementation for roslaunch.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import logging
|
||||
try:
|
||||
from queue import Empty, Queue
|
||||
except ImportError:
|
||||
from Queue import Empty, Queue
|
||||
import signal
|
||||
import atexit
|
||||
from threading import Thread, RLock, Lock
|
||||
|
||||
import roslib
|
||||
from roslaunch.core import printlog, printlog_bold, printerrlog, RLException
|
||||
|
||||
logger = logging.getLogger("roslaunch.pmon")
|
||||
|
||||
class FatalProcessLaunch(RLException):
|
||||
"""
|
||||
Exception to indicate that a process launch has failed in a fatal
|
||||
manner (i.e. relaunch is unlikely to succeed)
|
||||
"""
|
||||
pass
|
||||
|
||||
# start/shutdown ################################################
|
||||
|
||||
_pmons = []
|
||||
_pmon_counter = 0
|
||||
def start_process_monitor():
|
||||
global _pmon_counter
|
||||
if _shutting_down:
|
||||
#logger.error("start_process_monitor: cannot start new ProcessMonitor (shutdown initiated)")
|
||||
return None
|
||||
_pmon_counter += 1
|
||||
name = "ProcessMonitor-%s"%_pmon_counter
|
||||
logger.info("start_process_monitor: creating ProcessMonitor")
|
||||
process_monitor = ProcessMonitor(name)
|
||||
try:
|
||||
# prevent race condition with pmon_shutdown() being triggered
|
||||
# as we are starting a ProcessMonitor (i.e. user hits ctrl-C
|
||||
# during startup)
|
||||
_shutdown_lock.acquire()
|
||||
_pmons.append(process_monitor)
|
||||
process_monitor.start()
|
||||
logger.info("start_process_monitor: ProcessMonitor started")
|
||||
finally:
|
||||
_shutdown_lock.release()
|
||||
|
||||
return process_monitor
|
||||
|
||||
def shutdown_process_monitor(process_monitor):
|
||||
"""
|
||||
@param process_monitor: process monitor to kill
|
||||
@type process_monitor: L{ProcessMonitor}
|
||||
@return: True if process_monitor was successfully
|
||||
shutdown. False if it could not be shutdown cleanly or if there is
|
||||
a problem with process_monitor
|
||||
parameter. shutdown_process_monitor() does not throw any exceptions
|
||||
as this is shutdown-critical code.
|
||||
@rtype: bool
|
||||
"""
|
||||
try:
|
||||
if process_monitor is None or process_monitor.is_shutdown:
|
||||
return False
|
||||
|
||||
# I've decided to comment out use of logger until after
|
||||
# critical section is done, just in case logger is already
|
||||
# being torn down
|
||||
|
||||
#logger.debug("shutdown_process_monitor: shutting down ProcessMonitor")
|
||||
process_monitor.shutdown()
|
||||
#logger.debug("shutdown_process_monitor: joining ProcessMonitor")
|
||||
process_monitor.join(20.0)
|
||||
if process_monitor.isAlive():
|
||||
logger.error("shutdown_process_monitor: ProcessMonitor shutdown failed!")
|
||||
return False
|
||||
else:
|
||||
logger.debug("shutdown_process_monitor: ProcessMonitor shutdown succeeded")
|
||||
return True
|
||||
except Exception as e:
|
||||
print("exception in shutdown_process_monitor: %s" % e, file=sys.stderr)
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
_shutdown_lock = Lock()
|
||||
def pmon_shutdown():
|
||||
global _pmons
|
||||
try:
|
||||
_shutdown_lock.acquire()
|
||||
try:
|
||||
if not _pmons:
|
||||
return
|
||||
for p in _pmons:
|
||||
shutdown_process_monitor(p)
|
||||
del _pmons[:]
|
||||
except:
|
||||
print("exception in pmon_shutdown")
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
_shutdown_lock.release()
|
||||
|
||||
_signal_chain = {}
|
||||
_shutting_down = False
|
||||
def rl_signal(sig, stackframe):
|
||||
global _shutting_down
|
||||
if _shutting_down:
|
||||
return #prevent infinite callbacks
|
||||
_shutting_down = True
|
||||
pmon_shutdown()
|
||||
prev_handler = _signal_chain.get(sig, None)
|
||||
if prev_handler and prev_handler not in [signal.SIG_IGN, signal.SIG_DFL, rl_signal]:
|
||||
try:
|
||||
prev_handler(sig, stackframe)
|
||||
except KeyboardInterrupt:
|
||||
pass #filter out generic keyboard interrupt handler
|
||||
|
||||
if sys.platform in ['win32']: # cygwin seems to be ok
|
||||
_signal_list = [signal.SIGTERM, signal.SIGINT]
|
||||
else:
|
||||
_signal_list = [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]
|
||||
|
||||
_sig_initialized = False
|
||||
def _init_signal_handlers():
|
||||
global _sig_initialized
|
||||
if _sig_initialized:
|
||||
return
|
||||
if not roslib.is_interactive():
|
||||
for s in _signal_list:
|
||||
_signal_chain[s] = signal.signal(s, rl_signal)
|
||||
atexit.register(pmon_shutdown)
|
||||
_sig_initialized = True
|
||||
|
||||
# ##############################################################
|
||||
|
||||
class Process(object):
|
||||
"""
|
||||
Basic process representation for L{ProcessMonitor}. Must be subclassed
|
||||
to provide actual start()/stop() implementations.
|
||||
|
||||
Constructor *must* be called from the Python Main thread in order
|
||||
for signal handlers to register properly.
|
||||
"""
|
||||
|
||||
def __init__(self, package, name, args, env,
|
||||
respawn=False, respawn_delay=0.0, required=False):
|
||||
self.package = package
|
||||
self.name = name
|
||||
self.args = args
|
||||
self.env = env
|
||||
self.respawn = respawn
|
||||
self.respawn_delay = respawn_delay
|
||||
self.required = required
|
||||
self.lock = Lock()
|
||||
self.exit_code = None
|
||||
# for keeping track of respawning
|
||||
self.spawn_count = 0
|
||||
self.time_of_death = None
|
||||
|
||||
_init_signal_handlers()
|
||||
|
||||
def __str__(self):
|
||||
return "Process<%s>"%(self.name)
|
||||
|
||||
# NOTE: get_info() is going to have to be sufficient for
|
||||
# generating respawn requests, so we must be complete about it
|
||||
|
||||
def get_info(self):
|
||||
"""
|
||||
Get all data about this process in dictionary form
|
||||
@return: dictionary of all relevant process properties
|
||||
@rtype: dict { str: val }
|
||||
"""
|
||||
info = {
|
||||
'spawn_count': self.spawn_count,
|
||||
'args': self.args,
|
||||
'env': self.env,
|
||||
'package': self.package,
|
||||
'name': self.name,
|
||||
'alive': self.is_alive(),
|
||||
'respawn': self.respawn,
|
||||
'respawn_delay': self.respawn_delay,
|
||||
'required': self.required,
|
||||
}
|
||||
if self.exit_code is not None:
|
||||
info['exit_code'] = self.exit_code
|
||||
return info
|
||||
|
||||
def start(self):
|
||||
self.time_of_death = None
|
||||
self.spawn_count += 1
|
||||
|
||||
def is_alive(self):
|
||||
if self.time_of_death is None:
|
||||
self.time_of_death = time.time()
|
||||
return False
|
||||
|
||||
def should_respawn(self):
|
||||
"""
|
||||
@return: False if process should not respawn
|
||||
floating point seconds until respawn otherwise
|
||||
"""
|
||||
if not self.respawn:
|
||||
return False
|
||||
if self.time_of_death is None:
|
||||
if self.is_alive():
|
||||
return False
|
||||
return (self.time_of_death + self.respawn_delay) - time.time()
|
||||
|
||||
def stop(self, errors=None):
|
||||
"""
|
||||
Stop the process. Record any significant error messages in the errors parameter
|
||||
|
||||
@param errors: error messages. stop() will record messages into this list.
|
||||
@type errors: [str]
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_exit_description(self):
|
||||
if self.exit_code is not None:
|
||||
if self.exit_code:
|
||||
return 'process has died [exit code %s]'%self.exit_code
|
||||
else:
|
||||
# try not to scare users about process exit
|
||||
return 'process has finished cleanly'
|
||||
else:
|
||||
return 'process has died'
|
||||
|
||||
class DeadProcess(Process):
|
||||
"""
|
||||
Container class to maintain information about a process that has died. This
|
||||
container allows us to delete the actual Process but still maintain the metadata
|
||||
"""
|
||||
def __init__(self, p):
|
||||
super(DeadProcess, self).__init__(p.package, p.name, p.args, p.env,
|
||||
p.respawn, p.respawn_delay)
|
||||
self.exit_code = p.exit_code
|
||||
self.lock = None
|
||||
self.spawn_count = p.spawn_count
|
||||
self.info = p.get_info()
|
||||
def get_info(self):
|
||||
return self.info
|
||||
def start(self):
|
||||
raise Exception("cannot call start on a dead process!")
|
||||
def is_alive(self):
|
||||
return False
|
||||
|
||||
class ProcessListener(object):
|
||||
"""
|
||||
Listener class for L{ProcessMonitor}
|
||||
"""
|
||||
|
||||
def process_died(self, process_name, exit_code):
|
||||
"""
|
||||
Notifies listener that process has died. This callback only
|
||||
occurs for processes that die during normal process monitor
|
||||
execution -- processes that are forcibly killed during
|
||||
ProcessMonitor shutdown are not reported.
|
||||
@param process_name: name of process
|
||||
@type process_name: str
|
||||
@param exit_code: exit code of process. If None, it means
|
||||
that ProcessMonitor was unable to determine an exit code.
|
||||
@type exit_code: int
|
||||
"""
|
||||
pass
|
||||
|
||||
class ProcessMonitor(Thread):
|
||||
|
||||
def __init__(self, name="ProcessMonitor"):
|
||||
Thread.__init__(self, name=name)
|
||||
self.procs = []
|
||||
self.plock = RLock()
|
||||
self.is_shutdown = False
|
||||
self.done = False
|
||||
self.setDaemon(True)
|
||||
self.reacquire_signals = set()
|
||||
self.listeners = []
|
||||
self.dead_list = []
|
||||
# #885: ensure core procs
|
||||
self.core_procs = []
|
||||
# #642: flag to prevent process monitor exiting prematurely
|
||||
self._registrations_complete = False
|
||||
|
||||
logger.info("created process monitor %s"%self)
|
||||
|
||||
def add_process_listener(self, l):
|
||||
"""
|
||||
Listener for process events. MUST be called before
|
||||
ProcessMonitor is running.See ProcessListener class.
|
||||
@param l: listener instance
|
||||
@type l: L{ProcessListener}
|
||||
"""
|
||||
self.listeners.append(l)
|
||||
|
||||
def register(self, p):
|
||||
"""
|
||||
Register process with L{ProcessMonitor}
|
||||
@param p: Process
|
||||
@type p: L{Process}
|
||||
@raise RLException: if process with same name is already registered
|
||||
"""
|
||||
logger.info("ProcessMonitor.register[%s]"%p.name)
|
||||
e = None
|
||||
with self.plock:
|
||||
if self.has_process(p.name):
|
||||
e = RLException("cannot add process with duplicate name '%s'"%p.name)
|
||||
elif self.is_shutdown:
|
||||
e = RLException("cannot add process [%s] after process monitor has been shut down"%p.name)
|
||||
else:
|
||||
self.procs.append(p)
|
||||
if e:
|
||||
logger.error("ProcessMonitor.register[%s] failed %s"%(p.name, e))
|
||||
raise e
|
||||
else:
|
||||
logger.info("ProcessMonitor.register[%s] complete"%p.name)
|
||||
|
||||
def register_core_proc(self, p):
|
||||
"""
|
||||
Register core process with ProcessMonitor. Coreprocesses
|
||||
have special shutdown semantics. They are killed after all
|
||||
other processes, in reverse order in which they are added.
|
||||
@param p Process
|
||||
@type p: L{Process}
|
||||
@raise RLException: if process with same name is already registered
|
||||
"""
|
||||
self.register(p)
|
||||
self.core_procs.append(p)
|
||||
|
||||
def registrations_complete(self):
|
||||
"""
|
||||
Inform the process monitor that registrations are complete.
|
||||
After the registrations_complete flag is set, process monitor
|
||||
will exit if there are no processes left to monitor.
|
||||
"""
|
||||
self._registrations_complete = True
|
||||
logger.info("registrations completed %s"%self)
|
||||
|
||||
def unregister(self, p):
|
||||
logger.info("ProcessMonitor.unregister[%s] starting"%p.name)
|
||||
with self.plock:
|
||||
self.procs.remove(p)
|
||||
logger.info("ProcessMonitor.unregister[%s] complete"%p.name)
|
||||
|
||||
def has_process(self, name):
|
||||
"""
|
||||
@return: True if process is still be monitored. If False, process
|
||||
has died or was never registered with process
|
||||
@rtype: bool
|
||||
"""
|
||||
return len([p for p in self.procs if p.name == name]) > 0
|
||||
|
||||
def get_process(self, name):
|
||||
"""
|
||||
@return: process registered under \a name, or None
|
||||
@rtype: L{Process}
|
||||
"""
|
||||
with self.plock:
|
||||
v = [p for p in self.procs if p.name == name]
|
||||
if v:
|
||||
return v[0]
|
||||
|
||||
def has_main_thread_jobs(self):
|
||||
"""
|
||||
@return: True if ProcessMonitor has tasks that need to be run in the main thread
|
||||
@rtype: bool
|
||||
"""
|
||||
return len(self.reacquire_signals)
|
||||
|
||||
def do_main_thread_jobs(self):
|
||||
"""
|
||||
Execute tasks that need to be run in the main thread. Must be
|
||||
called from main thread.
|
||||
"""
|
||||
#not entirely threadsafe
|
||||
sigs = [s for s in self.reacquire_signals]
|
||||
for s in sigs:
|
||||
_signal_chain[s] = signal.signal(s, rl_signal)
|
||||
self.reacquire_signals.remove(s)
|
||||
|
||||
def kill_process(self, name):
|
||||
"""
|
||||
Kill process that matches name. NOTE: a killed process will
|
||||
continue to show up as active until the process monitor thread
|
||||
has caught that it has died.
|
||||
@param name: Process name
|
||||
@type name: str
|
||||
@return: True if a process named name was removed from
|
||||
process monitor. A process is considered killed if its stop()
|
||||
method was called.
|
||||
@rtype: bool
|
||||
"""
|
||||
def isstring(s):
|
||||
"""Small helper version to check an object is a string in
|
||||
a way that works for both Python 2 and 3
|
||||
"""
|
||||
try:
|
||||
return isinstance(s, basestring)
|
||||
except NameError:
|
||||
return isinstance(s, str)
|
||||
|
||||
if not isstring(name):
|
||||
raise RLException("kill_process takes in a process name but was given: %s"%name)
|
||||
logger.debug("ProcessMonitor.kill_process[%s]"%name)
|
||||
printlog("[%s] kill requested"%name)
|
||||
with self.plock:
|
||||
p = self.get_process(name)
|
||||
if p:
|
||||
try:
|
||||
# no need to accumulate errors, so pass in []
|
||||
p.stop([])
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Shutdown the process monitor thread
|
||||
"""
|
||||
logger.info("ProcessMonitor.shutdown %s"%self)
|
||||
self.is_shutdown = True
|
||||
|
||||
def get_active_names(self):
|
||||
"""
|
||||
@return [str]: list of active process names
|
||||
"""
|
||||
with self.plock:
|
||||
retval = [p.name for p in self.procs]
|
||||
return retval
|
||||
|
||||
def get_process_names_with_spawn_count(self):
|
||||
"""
|
||||
@return: Two lists, where first
|
||||
list of active process names along with the number of times
|
||||
that process has been spawned. Second list contains dead process names
|
||||
and their spawn count.
|
||||
@rtype: [[(str, int),], [(str,int),]]
|
||||
"""
|
||||
with self.plock:
|
||||
actives = [(p.name, p.spawn_count) for p in self.procs]
|
||||
deads = [(p.name, p.spawn_count) for p in self.dead_list]
|
||||
retval = [actives, deads]
|
||||
return retval
|
||||
|
||||
def mainthread_spin_once(self):
|
||||
"""
|
||||
run() occurs in a separate thread and cannot do certain signal-related
|
||||
work. The main thread of the application must call mainthread_spin()
|
||||
or mainthread_spin_once() in order to perform these jobs.
|
||||
"""
|
||||
if not self.done:
|
||||
if self.has_main_thread_jobs():
|
||||
self.do_main_thread_jobs()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def mainthread_spin(self):
|
||||
"""
|
||||
run() occurs in a separate thread and cannot do certain signal-related
|
||||
work. The main thread of the application must call mainthread_spin()
|
||||
or mainthread_spin_once() in order to perform these jobs. mainthread_spin()
|
||||
blocks until the process monitor is complete.
|
||||
"""
|
||||
while not self.done:
|
||||
if sys.platform in ['win32']: # cygwin seems to be ok
|
||||
# windows sleep throws an exception when a signal has arrived, even when
|
||||
# it has a handler. We can either use win32api.Sleep OR....catch
|
||||
# the exception
|
||||
try:
|
||||
time.sleep(0.1)
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
|
||||
if self.has_main_thread_jobs():
|
||||
self.do_main_thread_jobs()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
thread routine of the process monitor. NOTE: you must still
|
||||
call mainthread_spin or mainthread_spin_once() from the main
|
||||
thread in order to pick up main thread work from the process
|
||||
monitor.
|
||||
"""
|
||||
try:
|
||||
#don't let exceptions bomb thread, interferes with exit
|
||||
try:
|
||||
self._run()
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
self._post_run()
|
||||
|
||||
def _run(self):
|
||||
"""
|
||||
Internal run loop of ProcessMonitor
|
||||
"""
|
||||
plock = self.plock
|
||||
dead = []
|
||||
respawn = []
|
||||
while not self.is_shutdown:
|
||||
with plock: #copy self.procs
|
||||
procs = self.procs[:]
|
||||
if self.is_shutdown:
|
||||
break
|
||||
|
||||
# check current signal handlers to see if children have stolen them away
|
||||
# TODO: this code may not be necessary anymore (have to test)
|
||||
for s in _signal_list:
|
||||
if signal.getsignal(s) != rl_signal:
|
||||
self.reacquire_signals.add(s)
|
||||
|
||||
for p in procs:
|
||||
try:
|
||||
if not p.is_alive():
|
||||
logger.debug("Process[%s] has died, respawn=%s, required=%s, exit_code=%s",
|
||||
p.name,
|
||||
"True(%f)" % p.respawn_delay if p.respawn else p.respawn,
|
||||
p.required, p.exit_code)
|
||||
exit_code_str = p.get_exit_description()
|
||||
if p.required:
|
||||
printerrlog('='*80+"REQUIRED process [%s] has died!\n%s\nInitiating shutdown!\n"%(p.name, exit_code_str)+'='*80)
|
||||
self.is_shutdown = True
|
||||
elif not p in respawn:
|
||||
if p.exit_code:
|
||||
printerrlog("[%s] %s"%(p.name, exit_code_str))
|
||||
else:
|
||||
printlog_bold("[%s] %s"%(p.name, exit_code_str))
|
||||
dead.append(p)
|
||||
|
||||
## no need for lock as we require listeners be
|
||||
## added before process monitor is launched
|
||||
for l in self.listeners:
|
||||
l.process_died(p.name, p.exit_code)
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
#don't respawn as this is an internal error
|
||||
dead.append(p)
|
||||
if self.is_shutdown:
|
||||
break #stop polling
|
||||
for d in dead:
|
||||
try:
|
||||
if d.should_respawn():
|
||||
respawn.append(d)
|
||||
else:
|
||||
self.unregister(d)
|
||||
# stop process, don't accumulate errors
|
||||
d.stop([])
|
||||
# save process data to dead list
|
||||
with plock:
|
||||
self.dead_list.append(DeadProcess(d))
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
# dead check is to make sure that ProcessMonitor at least
|
||||
# waits until its had at least one process before exiting
|
||||
if self._registrations_complete and dead and not self.procs and not respawn:
|
||||
printlog("all processes on machine have died, roslaunch will exit")
|
||||
self.is_shutdown = True
|
||||
del dead[:]
|
||||
_respawn=[]
|
||||
for r in respawn:
|
||||
try:
|
||||
if self.is_shutdown:
|
||||
break
|
||||
if r.should_respawn() <= 0.0:
|
||||
printlog("[%s] restarting process" % r.name)
|
||||
# stop process, don't accumulate errors
|
||||
r.stop([])
|
||||
r.start()
|
||||
else:
|
||||
# not ready yet, keep it around
|
||||
_respawn.append(r)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
logger.error("Restart failed %s",traceback.format_exc())
|
||||
respawn = _respawn
|
||||
time.sleep(0.1) #yield thread
|
||||
#moved this to finally block of _post_run
|
||||
#self._post_run() #kill all processes
|
||||
|
||||
def _post_run(self):
|
||||
logger.info("ProcessMonitor._post_run %s"%self)
|
||||
# this is already true entering, but go ahead and make sure
|
||||
self.is_shutdown = True
|
||||
# killall processes on run exit
|
||||
|
||||
q = Queue()
|
||||
q.join()
|
||||
|
||||
with self.plock:
|
||||
# make copy of core_procs for threadsafe usage
|
||||
core_procs = self.core_procs[:]
|
||||
logger.info("ProcessMonitor._post_run %s: remaining procs are %s"%(self, self.procs))
|
||||
|
||||
# enqueue all non-core procs in reverse order for parallel kill
|
||||
# #526/885: ignore core procs
|
||||
[q.put(p) for p in reversed(self.procs) if not p in core_procs]
|
||||
|
||||
# use 10 workers
|
||||
killers = []
|
||||
for i in range(10):
|
||||
t = _ProcessKiller(q, i)
|
||||
killers.append(t)
|
||||
t.start()
|
||||
|
||||
# wait for workers to finish
|
||||
q.join()
|
||||
shutdown_errors = []
|
||||
|
||||
# accumulate all the shutdown errors
|
||||
for t in killers:
|
||||
shutdown_errors.extend(t.errors)
|
||||
del killers[:]
|
||||
|
||||
# #526/885: kill core procs last
|
||||
# we don't want to parallelize this as the master has to be last
|
||||
for p in reversed(core_procs):
|
||||
_kill_process(p, shutdown_errors)
|
||||
|
||||
# delete everything except dead_list
|
||||
logger.info("ProcessMonitor exit: cleaning up data structures and signals")
|
||||
with self.plock:
|
||||
del core_procs[:]
|
||||
del self.procs[:]
|
||||
del self.core_procs[:]
|
||||
|
||||
reacquire_signals = self.reacquire_signals
|
||||
if reacquire_signals:
|
||||
reacquire_signals.clear()
|
||||
logger.info("ProcessMonitor exit: pmon has shutdown")
|
||||
self.done = True
|
||||
|
||||
if shutdown_errors:
|
||||
printerrlog("Shutdown errors:\n"+'\n'.join([" * %s"%e for e in shutdown_errors]))
|
||||
|
||||
def _kill_process(p, errors):
|
||||
"""
|
||||
Routine for kill Process p with appropriate logging to screen and logfile
|
||||
|
||||
@param p: process to kill
|
||||
@type p: Process
|
||||
@param errors: list of error messages from killed process
|
||||
@type errors: [str]
|
||||
"""
|
||||
try:
|
||||
logger.info("ProcessMonitor exit: killing %s", p.name)
|
||||
printlog("[%s] killing on exit"%p.name)
|
||||
# we accumulate errors from each process so that we can print these at the end
|
||||
p.stop(errors)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
class _ProcessKiller(Thread):
|
||||
|
||||
def __init__(self, q, i):
|
||||
Thread.__init__(self, name="ProcessKiller-%s"%i)
|
||||
self.q = q
|
||||
self.errors = []
|
||||
|
||||
def run(self):
|
||||
q = self.q
|
||||
while not q.empty():
|
||||
try:
|
||||
p = q.get(False)
|
||||
_kill_process(p, self.errors)
|
||||
q.task_done()
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
225
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/remote.py
vendored
Normal file
225
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/remote.py
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
"""
|
||||
Integrates roslaunch remote process launching capabilities.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import socket
|
||||
import time
|
||||
|
||||
import rosgraph.network as network
|
||||
|
||||
import roslaunch.config
|
||||
import roslaunch.remoteprocess
|
||||
from roslaunch.remoteprocess import SSHChildROSLaunchProcess
|
||||
import roslaunch.launch
|
||||
import roslaunch.server #ROSLaunchParentNode hidden dep
|
||||
from roslaunch.core import RLException, is_machine_local, printerrlog, printlog
|
||||
|
||||
_CHILD_REGISTER_TIMEOUT = 10.0 #seconds
|
||||
|
||||
class ROSRemoteRunner(roslaunch.launch.ROSRemoteRunnerIF):
|
||||
"""
|
||||
Manages the running of remote roslaunch children
|
||||
"""
|
||||
|
||||
def __init__(self, run_id, rosconfig, pm, server):
|
||||
"""
|
||||
:param run_id: roslaunch run_id of this runner, ``str``
|
||||
:param config: launch configuration, ``ROSConfig``
|
||||
:param pm process monitor, ``ProcessMonitor``
|
||||
:param server: roslaunch parent server, ``ROSLaunchParentNode``
|
||||
"""
|
||||
self.run_id = run_id
|
||||
self.rosconfig = rosconfig
|
||||
self.server = server
|
||||
self.pm = pm
|
||||
self.logger = logging.getLogger('roslaunch.remote')
|
||||
self.listeners = []
|
||||
|
||||
self.machine_list = []
|
||||
self.remote_processes = []
|
||||
|
||||
def add_process_listener(self, l):
|
||||
"""
|
||||
Listen to events about remote processes dying. Not
|
||||
threadsafe. Must be called before processes started.
|
||||
|
||||
:param l: ProcessListener
|
||||
"""
|
||||
self.listeners.append(l)
|
||||
|
||||
def _start_child(self, server_node_uri, machine, counter):
|
||||
# generate a name for the machine. don't use config key as
|
||||
# it's too long to easily display
|
||||
name = "%s-%s"%(machine.address, counter)
|
||||
|
||||
self.logger.info("remote[%s] starting roslaunch", name)
|
||||
printlog("remote[%s] starting roslaunch"%name)
|
||||
|
||||
p = SSHChildROSLaunchProcess(self.run_id, name, server_node_uri, machine, self.rosconfig.master.uri)
|
||||
success = p.start()
|
||||
self.pm.register(p)
|
||||
if not success: #treat as fatal
|
||||
raise RLException("unable to start remote roslaunch child: %s"%name)
|
||||
self.server.add_child(name, p)
|
||||
return p
|
||||
|
||||
def start_children(self):
|
||||
"""
|
||||
Start the child roslaunch processes
|
||||
"""
|
||||
server_node_uri = self.server.uri
|
||||
if not server_node_uri:
|
||||
raise RLException("server URI is not initialized")
|
||||
|
||||
# TODOXXX: break out table building code into a separate
|
||||
# routine so we can unit test it _start_child() should not be
|
||||
# determining the process name
|
||||
|
||||
# Build table of unique machines that we are going to launch on
|
||||
machines = {}
|
||||
for n in self.rosconfig.nodes:
|
||||
if not is_machine_local(n.machine):
|
||||
machines[n.machine.config_key()] = n.machine
|
||||
|
||||
# Launch child roslaunch processes on remote machines
|
||||
counter = 0
|
||||
# - keep a list of procs so we can check for those that failed to launch
|
||||
procs = []
|
||||
for m in machines:
|
||||
p = self._start_child(server_node_uri, machines[m], counter)
|
||||
procs.append(p)
|
||||
counter += 1
|
||||
|
||||
# Wait for all children to call register() callback. The machines can have
|
||||
# non-uniform registration timeouts. We consider the failure to occur once
|
||||
# one of the machines has failed to meet it's timeout.
|
||||
start_t = time.time()
|
||||
while True:
|
||||
pending = []
|
||||
for p in procs:
|
||||
if not p.is_alive():
|
||||
raise RLException("remote roslaunch failed to launch: %s"%p.machine.name)
|
||||
elif not p.uri:
|
||||
pending.append(p.machine)
|
||||
if not pending:
|
||||
break
|
||||
# timeout is the minimum of the remaining timeouts of the machines
|
||||
timeout_t = start_t + min([m.timeout for m in pending])
|
||||
if time.time() > timeout_t:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
if pending:
|
||||
raise RLException(
|
||||
"""The following roslaunch remote processes failed to register:
|
||||
%s
|
||||
|
||||
If this is a network latency issue, you may wish to consider setting
|
||||
<machine timeout="NUMBER OF SECONDS" ... />
|
||||
in your launch"""%'\n'.join([" * %s (timeout %ss)"%(m.name, m.timeout) for m in pending]))
|
||||
|
||||
# convert machine dictionary to a list
|
||||
self.machine_list = machines.values()
|
||||
# save a list of the remote processes
|
||||
self.remote_processes = procs
|
||||
|
||||
|
||||
def _assume_failed(self, nodes, failed):
|
||||
"""
|
||||
Utility routine for logging/recording nodes that failed
|
||||
|
||||
:param nodes: list of nodes that are assumed to have failed, ``Node``
|
||||
:param failed: list of names of nodes that have failed to extend, ``[str]``
|
||||
"""
|
||||
str_nodes = ["%s/%s"%(n.package, n.type) for n in nodes]
|
||||
failed.extend(str_nodes)
|
||||
printerrlog("Launch of the following nodes most likely failed: %s"%', '.join(str_nodes))
|
||||
|
||||
def launch_remote_nodes(self):
|
||||
"""
|
||||
Contact each child to launch remote nodes
|
||||
"""
|
||||
succeeded = []
|
||||
failed = []
|
||||
|
||||
# initialize remote_nodes. we use the machine config key as
|
||||
# the key for the dictionary so that we can bin the nodes.
|
||||
self.remote_nodes = {}
|
||||
for m in self.machine_list:
|
||||
self.remote_nodes[m.config_key()] = []
|
||||
|
||||
# build list of nodes that will be launched by machine
|
||||
nodes = [x for x in self.rosconfig.nodes if not is_machine_local(x.machine)]
|
||||
for n in nodes:
|
||||
self.remote_nodes[n.machine.config_key()].append(n)
|
||||
|
||||
for child in self.remote_processes:
|
||||
nodes = self.remote_nodes[child.machine.config_key()]
|
||||
body = '\n'.join([n.to_remote_xml() for n in nodes])
|
||||
# #3799: force utf-8 encoding
|
||||
xml = '<?xml version="1.0" encoding="utf-8"?>\n<launch>\n%s</launch>'%body
|
||||
|
||||
api = child.getapi()
|
||||
# TODO: timeouts
|
||||
try:
|
||||
self.logger.debug("sending [%s] XML [\n%s\n]"%(child.uri, xml))
|
||||
code, msg, val = api.launch(xml)
|
||||
if code == 1:
|
||||
c_succ, c_fail = val
|
||||
succeeded.extend(c_succ)
|
||||
failed.extend(c_fail)
|
||||
else:
|
||||
printerrlog('error launching on [%s, uri %s]: %s'%(child.name, child.uri, msg))
|
||||
self._assume_failed(nodes, failed)
|
||||
except socket.error as e:
|
||||
errno, msg = e
|
||||
printerrlog('error launching on [%s, uri %s]: %s'%(child.name, child.uri, str(msg)))
|
||||
self._assume_failed(nodes, failed)
|
||||
|
||||
except socket.gaierror as e:
|
||||
errno, msg = e
|
||||
# usually errno == -2. See #815.
|
||||
child_host, _ = network.parse_http_host_and_port(child.uri)
|
||||
printerrlog("Unable to contact remote roslaunch at [%s]. This is most likely due to a network misconfiguration with host lookups. Please make sure that you can contact '%s' from this machine"%(child.uri, child_host))
|
||||
self._assume_failed(nodes, failed)
|
||||
|
||||
except Exception as e:
|
||||
printerrlog('error launching on [%s, uri %s]: %s'%(child.name, child.uri, str(e)))
|
||||
self._assume_failed(nodes, failed)
|
||||
|
||||
return succeeded, failed
|
||||
|
||||
334
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/remoteprocess.py
vendored
Normal file
334
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/remoteprocess.py
vendored
Normal file
@@ -0,0 +1,334 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
"""
|
||||
Process handler for launching ssh-based roslaunch child processes.
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import traceback
|
||||
try:
|
||||
from xmlrpc.client import ServerProxy
|
||||
except ImportError:
|
||||
from xmlrpclib import ServerProxy
|
||||
|
||||
import rosgraph
|
||||
from roslaunch.core import printlog, printerrlog
|
||||
import roslaunch.pmon
|
||||
import roslaunch.server
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger("roslaunch.remoteprocess")
|
||||
|
||||
# #1975 timeout for creating ssh connections
|
||||
TIMEOUT_SSH_CONNECT = 30.
|
||||
|
||||
def ssh_check_known_hosts(ssh, address, port, username=None, logger=None):
|
||||
"""
|
||||
Validation routine for loading the host keys and making sure that
|
||||
they are configured properly for the desired SSH. The behavior of
|
||||
this routine can be modified by the ROSLAUNCH_SSH_UNKNOWN
|
||||
environment variable, which enables the paramiko.AutoAddPolicy.
|
||||
|
||||
:param ssh: paramiko SSH client, :class:`paramiko.SSHClient`
|
||||
:param address: SSH IP address, ``str``
|
||||
:param port: SSH port, ``int``
|
||||
:param username: optional username to include in error message if check fails, ``str``
|
||||
:param logger: (optional) logger to record tracebacks to, :class:`logging.Logger`
|
||||
:returns: error message if improperly configured, or ``None``. ``str``
|
||||
"""
|
||||
import paramiko
|
||||
try:
|
||||
try:
|
||||
if os.path.isfile('/etc/ssh/ssh_known_hosts'): #default ubuntu location
|
||||
ssh.load_system_host_keys('/etc/ssh/ssh_known_hosts')
|
||||
except IOError:
|
||||
pass
|
||||
ssh.load_system_host_keys() #default user location
|
||||
except:
|
||||
if logger:
|
||||
logger.error(traceback.format_exc())
|
||||
# as seen in #767, base64 raises generic Error.
|
||||
#
|
||||
# A corrupt pycrypto build can also cause this, though
|
||||
# the cause of the corrupt builds has been fixed.
|
||||
return "cannot load SSH host keys -- your known_hosts file may be corrupt"
|
||||
|
||||
# #3158: resolve the actual host using the user's ~/.ssh/config
|
||||
ssh_config = paramiko.SSHConfig()
|
||||
try:
|
||||
with open(os.path.join(os.path.expanduser('~'), '.ssh', 'config')) as f:
|
||||
ssh_config.parse(f)
|
||||
config_lookup = ssh_config.lookup(address)
|
||||
resolved_address = config_lookup['hostname'] if 'hostname' in config_lookup else address
|
||||
except:
|
||||
resolved_address = address
|
||||
|
||||
# #1849: paramiko will raise an SSHException with an 'Unknown
|
||||
# server' message if the address is not in the known_hosts
|
||||
# file. This causes a lot of confusion to users, so we try
|
||||
# and diagnose this in advance and offer better guidance
|
||||
|
||||
# - ssh.get_host_keys() does not return the system host keys
|
||||
hk = ssh._system_host_keys
|
||||
override = os.environ.get('ROSLAUNCH_SSH_UNKNOWN', 0)
|
||||
if override == '1':
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
elif hk.lookup(resolved_address) is None:
|
||||
port_str = user_str = ''
|
||||
if port != 22:
|
||||
port_str = "-p %s "%port
|
||||
if username:
|
||||
user_str = username+'@'
|
||||
return """%s is not in your SSH known_hosts file.
|
||||
|
||||
Please manually:
|
||||
ssh %s%s%s
|
||||
|
||||
then try roslaunching again.
|
||||
|
||||
If you wish to configure roslaunch to automatically recognize unknown
|
||||
hosts, please set the environment variable ROSLAUNCH_SSH_UNKNOWN=1"""%(resolved_address, port_str, user_str, resolved_address)
|
||||
|
||||
class SSHChildROSLaunchProcess(roslaunch.server.ChildROSLaunchProcess):
|
||||
"""
|
||||
Process wrapper for launching and monitoring a child roslaunch process over SSH
|
||||
"""
|
||||
def __init__(self, run_id, name, server_uri, machine, master_uri=None):
|
||||
"""
|
||||
:param machine: Machine instance. Must be fully configured.
|
||||
machine.env_loader is required to be set.
|
||||
"""
|
||||
if not machine.env_loader:
|
||||
raise ValueError("machine.env_loader must have been assigned before creating ssh child instance")
|
||||
args = [machine.env_loader, 'roslaunch', '-c', name, '-u', server_uri, '--run_id', run_id]
|
||||
# env is always empty dict because we only use env_loader
|
||||
super(SSHChildROSLaunchProcess, self).__init__(name, args, {})
|
||||
self.machine = machine
|
||||
self.master_uri = master_uri
|
||||
self.ssh = self.sshin = self.sshout = self.ssherr = None
|
||||
self.started = False
|
||||
self.uri = None
|
||||
# self.is_dead is a flag set by is_alive that affects whether or not we
|
||||
# log errors during a stop().
|
||||
self.is_dead = False
|
||||
|
||||
def _ssh_exec(self, command, address, port, username=None, password=None):
|
||||
"""
|
||||
:returns: (ssh pipes, message). If error occurs, returns (None, error message).
|
||||
"""
|
||||
if self.master_uri:
|
||||
env_command = 'env %s=%s' % (rosgraph.ROS_MASTER_URI, self.master_uri)
|
||||
command = '%s %s' % (env_command, command)
|
||||
try:
|
||||
import Crypto
|
||||
except ImportError as e:
|
||||
_logger.error("cannot use SSH: pycrypto is not installed")
|
||||
return None, "pycrypto is not installed"
|
||||
try:
|
||||
import paramiko
|
||||
except ImportError as e:
|
||||
_logger.error("cannot use SSH: paramiko is not installed")
|
||||
return None, "paramiko is not installed"
|
||||
#load user's ssh configuration
|
||||
config_block = {'hostname': None, 'user': None, 'identityfile': None}
|
||||
ssh_config = paramiko.SSHConfig()
|
||||
try:
|
||||
with open(os.path.join(os.path.expanduser('~'), '.ssh','config')) as f:
|
||||
ssh_config.parse(f)
|
||||
config_block.update(ssh_config.lookup(address))
|
||||
except:
|
||||
pass
|
||||
address = config_block['hostname'] or address
|
||||
username = username or config_block['user']
|
||||
identity_file = None
|
||||
if config_block.get('identityfile', None):
|
||||
if isinstance(config_block['identityfile'], list):
|
||||
identity_file = [os.path.expanduser(f) for f in config_block['identityfile']]
|
||||
else:
|
||||
identity_file = os.path.expanduser(config_block['identityfile'])
|
||||
#load ssh client and connect
|
||||
ssh = paramiko.SSHClient()
|
||||
err_msg = ssh_check_known_hosts(ssh, address, port, username=username, logger=_logger)
|
||||
|
||||
if not err_msg:
|
||||
username_str = '%s@'%username if username else ''
|
||||
try:
|
||||
if not password: #use SSH agent
|
||||
ssh.connect(address, port, username, timeout=TIMEOUT_SSH_CONNECT, key_filename=identity_file)
|
||||
else: #use SSH with login/pass
|
||||
ssh.connect(address, port, username, password, timeout=TIMEOUT_SSH_CONNECT)
|
||||
except paramiko.BadHostKeyException:
|
||||
_logger.error(traceback.format_exc())
|
||||
err_msg = "Unable to verify host key for remote computer[%s:%s]"%(address, port)
|
||||
except paramiko.AuthenticationException:
|
||||
_logger.error(traceback.format_exc())
|
||||
err_msg = "Authentication to remote computer[%s%s:%s] failed.\nA common cause of this error is a missing key in your authorized_keys file."%(username_str, address, port)
|
||||
except paramiko.SSHException as e:
|
||||
_logger.error(traceback.format_exc())
|
||||
if str(e).startswith("Unknown server"):
|
||||
pass
|
||||
err_msg = "Unable to establish ssh connection to [%s%s:%s]: %s"%(username_str, address, port, e)
|
||||
except socket.error as e:
|
||||
# #1824
|
||||
if e[0] == 111:
|
||||
err_msg = "network connection refused by [%s:%s]"%(address, port)
|
||||
else:
|
||||
err_msg = "network error connecting to [%s:%s]: %s"%(address, port, str(e))
|
||||
if err_msg:
|
||||
return None, err_msg
|
||||
else:
|
||||
printlog("launching remote roslaunch child with command: [%s]"%(str(command)))
|
||||
sshin, sshout, ssherr = ssh.exec_command(command)
|
||||
return (ssh, sshin, sshout, ssherr), "executed remotely"
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start the remote process. This will create an SSH connection
|
||||
to the remote host.
|
||||
"""
|
||||
self.started = False #won't set to True until we are finished
|
||||
self.ssh = self.sshin = self.sshout = self.ssherr = None
|
||||
with self.lock:
|
||||
name = self.name
|
||||
m = self.machine
|
||||
if m.user is not None:
|
||||
printlog("remote[%s]: creating ssh connection to %s:%s, user[%s]"%(name, m.address, m.ssh_port, m.user))
|
||||
else:
|
||||
printlog("remote[%s]: creating ssh connection to %s:%s"%(name, m.address, m.ssh_port))
|
||||
_logger.info("remote[%s]: invoking with ssh exec args [%s]"%(name, ' '.join(self.args)))
|
||||
sshvals, msg = self._ssh_exec(' '.join(self.args), m.address, m.ssh_port, m.user, m.password)
|
||||
if sshvals is None:
|
||||
printerrlog("remote[%s]: failed to launch on %s:\n\n%s\n\n"%(name, m.name, msg))
|
||||
return False
|
||||
self.ssh, self.sshin, self.sshout, self.ssherr = sshvals
|
||||
printlog("remote[%s]: ssh connection created"%name)
|
||||
self.started = True
|
||||
return True
|
||||
|
||||
def getapi(self):
|
||||
"""
|
||||
:returns: ServerProxy to remote client XMLRPC server, `ServerProxy`
|
||||
"""
|
||||
if self.uri:
|
||||
return ServerProxy(self.uri)
|
||||
else:
|
||||
return None
|
||||
|
||||
def is_alive(self):
|
||||
"""
|
||||
:returns: ``True`` if the process is alive. is_alive needs to be
|
||||
called periodically as it drains the SSH buffer, ``bool``
|
||||
"""
|
||||
if self.started and not self.ssh:
|
||||
return False
|
||||
elif not self.started:
|
||||
return True #not started is equivalent to alive in our logic
|
||||
s = self.ssherr
|
||||
s.channel.settimeout(0)
|
||||
try:
|
||||
#drain the pipes
|
||||
data = s.read(2048)
|
||||
if not len(data):
|
||||
self.is_dead = True
|
||||
return False
|
||||
# #2012 il8n: ssh *should* be UTF-8, but often isn't
|
||||
# (e.g. Japan)
|
||||
data = data.decode('utf-8')
|
||||
printerrlog("remote[%s]: %s"%(self.name, data))
|
||||
except socket.timeout:
|
||||
pass
|
||||
except IOError:
|
||||
return False
|
||||
except UnicodeDecodeError:
|
||||
# #2012: soft fail, printing is not essential. This occurs
|
||||
# with older machines that don't send utf-8 over ssh
|
||||
pass
|
||||
|
||||
s = self.sshout
|
||||
s.channel.settimeout(0)
|
||||
try:
|
||||
#drain the pipes
|
||||
#TODO: write to log file
|
||||
data = s.read(2048)
|
||||
if not len(data):
|
||||
self.is_dead = True
|
||||
return False
|
||||
except socket.timeout:
|
||||
pass
|
||||
except IOError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def stop(self, errors=None):
|
||||
"""
|
||||
Terminate this process, including the SSH connection.
|
||||
"""
|
||||
if errors is None:
|
||||
errors = []
|
||||
with self.lock:
|
||||
if not self.ssh:
|
||||
return
|
||||
|
||||
# call the shutdown API first as closing the SSH connection
|
||||
# won't actually kill the process unless it triggers SIGPIPE
|
||||
try:
|
||||
api = self.getapi()
|
||||
if api is not None:
|
||||
#TODO: probably need a timeout on this
|
||||
api.shutdown()
|
||||
except socket.error:
|
||||
# normal if process is already dead
|
||||
address, port = self.machine.address, self.machine.ssh_port
|
||||
if not self.is_dead:
|
||||
printerrlog("remote[%s]: unable to contact [%s] to shutdown remote processes!"%(self.name, address))
|
||||
else:
|
||||
printlog("remote[%s]: unable to contact [%s] to shutdown cleanly. The remote roslaunch may have exited already."%(self.name, address))
|
||||
except:
|
||||
# temporary: don't really want to log here as this
|
||||
# may occur during shutdown
|
||||
traceback.print_exc()
|
||||
|
||||
_logger.info("remote[%s]: closing ssh connection", self.name)
|
||||
self.sshin.close()
|
||||
self.sshout.close()
|
||||
self.ssherr.close()
|
||||
self.ssh.close()
|
||||
|
||||
self.sshin = None
|
||||
self.sshout = None
|
||||
self.ssherr = None
|
||||
self.ssh = None
|
||||
_logger.info("remote[%s]: ssh connection closed", self.name)
|
||||
270
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/rlutil.py
vendored
Normal file
270
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/rlutil.py
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Uncategorized utility routines for roslaunch.
|
||||
|
||||
This API should not be considered stable.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
import roslib.packages
|
||||
|
||||
import rosclean
|
||||
import rospkg
|
||||
import rosgraph
|
||||
|
||||
import roslaunch.core
|
||||
import roslaunch.config
|
||||
import roslaunch.depends
|
||||
from rosmaster import DEFAULT_MASTER_PORT
|
||||
|
||||
def check_log_disk_usage():
|
||||
"""
|
||||
Check size of log directory. If high, print warning to user
|
||||
"""
|
||||
try:
|
||||
d = rospkg.get_log_dir()
|
||||
roslaunch.core.printlog("Checking log directory for disk usage. This may take awhile.\nPress Ctrl-C to interrupt")
|
||||
disk_usage = rosclean.get_disk_usage(d)
|
||||
# warn if over a gig
|
||||
if disk_usage > 1073741824:
|
||||
roslaunch.core.printerrlog("WARNING: disk usage in log directory [%s] is over 1GB.\nIt's recommended that you use the 'rosclean' command."%d)
|
||||
else:
|
||||
roslaunch.core.printlog("Done checking log file disk usage. Usage is <1GB.")
|
||||
except:
|
||||
pass
|
||||
|
||||
def resolve_launch_arguments(args):
|
||||
"""
|
||||
Resolve command-line args to roslaunch filenames.
|
||||
|
||||
:returns: resolved filenames, ``[str]``
|
||||
"""
|
||||
|
||||
# strip remapping args for processing
|
||||
args = rosgraph.myargv(args)
|
||||
|
||||
# user can either specify:
|
||||
# - filename + launch args
|
||||
# - package + relative-filename + launch args
|
||||
if not args:
|
||||
return args
|
||||
resolved_args = None
|
||||
|
||||
# try to resolve launch file in package first
|
||||
if len(args) >= 2:
|
||||
try:
|
||||
resolved = roslib.packages.find_resource(args[0], args[1])
|
||||
if len(resolved) > 1:
|
||||
raise roslaunch.core.RLException("multiple files named [%s] in package [%s]:%s\nPlease specify full path instead" % (args[1], args[0], ''.join(['\n- %s' % r for r in resolved])))
|
||||
if len(resolved) == 1:
|
||||
resolved_args = [resolved[0]] + args[2:]
|
||||
except rospkg.ResourceNotFound:
|
||||
pass
|
||||
# try to resolve launch file
|
||||
if resolved_args is None and (args[0] == '-' or os.path.isfile(args[0])):
|
||||
resolved_args = [args[0]] + args[1:]
|
||||
# raise if unable to resolve
|
||||
if resolved_args is None:
|
||||
if len(args) >= 2:
|
||||
raise roslaunch.core.RLException("[%s] is neither a launch file in package [%s] nor is [%s] a launch file name" % (args[1], args[0], args[0]))
|
||||
else:
|
||||
raise roslaunch.core.RLException("[%s] is not a launch file name" % args[0])
|
||||
return resolved_args
|
||||
|
||||
def _wait_for_master():
|
||||
"""
|
||||
Block until ROS Master is online
|
||||
|
||||
:raise: :exc:`RuntimeError` If unexpected error occurs
|
||||
"""
|
||||
m = roslaunch.core.Master() # get a handle to the default master
|
||||
is_running = m.is_running()
|
||||
if not is_running:
|
||||
roslaunch.core.printlog("roscore/master is not yet running, will wait for it to start")
|
||||
while not is_running:
|
||||
time.sleep(0.1)
|
||||
is_running = m.is_running()
|
||||
if is_running:
|
||||
roslaunch.core.printlog("master has started, initiating launch")
|
||||
else:
|
||||
raise RuntimeError("unknown error waiting for master to start")
|
||||
|
||||
_terminal_name = None
|
||||
|
||||
def _set_terminal(s):
|
||||
import platform
|
||||
if platform.system() in ['FreeBSD', 'Linux', 'Darwin', 'Unix']:
|
||||
try:
|
||||
print('\033]2;%s\007'%(s))
|
||||
except:
|
||||
pass
|
||||
|
||||
def update_terminal_name(ros_master_uri):
|
||||
"""
|
||||
append master URI to the terminal name
|
||||
"""
|
||||
if _terminal_name:
|
||||
_set_terminal(_terminal_name + ' ' + ros_master_uri)
|
||||
|
||||
def change_terminal_name(args, is_core):
|
||||
"""
|
||||
use echo (where available) to change the name of the terminal window
|
||||
"""
|
||||
global _terminal_name
|
||||
_terminal_name = 'roscore' if is_core else ','.join(args)
|
||||
_set_terminal(_terminal_name)
|
||||
|
||||
def get_or_generate_uuid(options_runid, options_wait_for_master):
|
||||
"""
|
||||
:param options_runid: run_id value from command-line or ``None``, ``str``
|
||||
:param options_wait_for_master: the wait_for_master command
|
||||
option. If this is True, it means that we must retrieve the
|
||||
value from the parameter server and need to avoid any race
|
||||
conditions with the roscore being initialized. ``bool``
|
||||
"""
|
||||
|
||||
# Three possible sources of the run_id:
|
||||
#
|
||||
# - if we're a child process, we get it from options_runid
|
||||
# - if there's already a roscore running, read from the param server
|
||||
# - generate one if we're running the roscore
|
||||
if options_runid:
|
||||
return options_runid
|
||||
|
||||
# #773: Generate a run_id to use if we launch a master
|
||||
# process. If a master is already running, we'll get the
|
||||
# run_id from it instead
|
||||
param_server = rosgraph.Master('/roslaunch')
|
||||
val = None
|
||||
while val is None:
|
||||
try:
|
||||
val = param_server.getParam('/run_id')
|
||||
except:
|
||||
if not options_wait_for_master:
|
||||
val = roslaunch.core.generate_run_id()
|
||||
return val
|
||||
|
||||
def check_roslaunch(f, use_test_depends=False):
|
||||
"""
|
||||
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, ``str``
|
||||
:param use_test_depends: Consider test_depends, ``Bool``
|
||||
:returns: error message or ``None``
|
||||
"""
|
||||
try:
|
||||
rl_config = roslaunch.config.load_config_default([f], DEFAULT_MASTER_PORT, verbose=False)
|
||||
except roslaunch.core.RLException as e:
|
||||
return str(e)
|
||||
|
||||
rospack = rospkg.RosPack()
|
||||
errors = []
|
||||
# check for missing deps
|
||||
try:
|
||||
base_pkg, file_deps, missing = roslaunch.depends.roslaunch_deps([f], use_test_depends=use_test_depends)
|
||||
except rospkg.common.ResourceNotFound as r:
|
||||
errors.append("Could not find package [%s] included from [%s]"%(str(r), f))
|
||||
missing = {}
|
||||
file_deps = {}
|
||||
except roslaunch.substitution_args.ArgException as e:
|
||||
errors.append("Could not resolve arg [%s] in [%s]"%(str(e), f))
|
||||
missing = {}
|
||||
file_deps = {}
|
||||
for pkg, miss in missing.items():
|
||||
# even if the pkgs is not found in packges.xml, if other package.xml provdes that pkgs, then it will be ok
|
||||
all_pkgs = []
|
||||
try:
|
||||
for file_dep in file_deps.keys():
|
||||
file_pkg = rospkg.get_package_name(file_dep)
|
||||
all_pkgs.extend(rospack.get_depends(file_pkg,implicit=False))
|
||||
miss_all = list(set(miss) - set(all_pkgs))
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
miss_all = True
|
||||
if miss_all:
|
||||
print("Missing package dependencies: %s/package.xml: %s"%(pkg, ', '.join(miss)), file=sys.stderr)
|
||||
errors.append("Missing package dependencies: %s/package.xml: %s"%(pkg, ', '.join(miss)))
|
||||
elif miss:
|
||||
print("Missing package dependencies: %s/package.xml: %s (notify upstream maintainer)"%(pkg, ', '.join(miss)), file=sys.stderr)
|
||||
|
||||
# load all node defs
|
||||
nodes = []
|
||||
for filename, rldeps in file_deps.items():
|
||||
nodes.extend(rldeps.nodes)
|
||||
|
||||
# check for missing packages
|
||||
for pkg, node_type in nodes:
|
||||
try:
|
||||
rospack.get_path(pkg)
|
||||
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, rospack=rospack):
|
||||
errors.append("cannot find node [%s] in package [%s]"%(node_type, pkg))
|
||||
except Exception as e:
|
||||
errors.append("unable to find node [%s/%s]: %s"%(pkg, node_type, str(e)))
|
||||
|
||||
# Check for configuration errors, #2889
|
||||
for err in rl_config.config_errors:
|
||||
errors.append('ROSLaunch config error: %s' % err)
|
||||
|
||||
if errors:
|
||||
return '\n'.join(errors)
|
||||
|
||||
def print_file_list(roslaunch_files):
|
||||
"""
|
||||
:param roslaunch_files: list of launch files to load, ``str``
|
||||
|
||||
:returns: list of files involved in processing roslaunch_files, including the files themselves.
|
||||
"""
|
||||
from roslaunch.config import load_config_default, get_roscore_filename
|
||||
import roslaunch.xmlloader
|
||||
try:
|
||||
loader = roslaunch.xmlloader.XmlLoader(resolve_anon=True)
|
||||
config = load_config_default(roslaunch_files, None, loader=loader, verbose=False, assign_machines=False)
|
||||
files = [os.path.abspath(x) for x in set(config.roslaunch_files) - set([get_roscore_filename()])]
|
||||
print('\n'.join(files))
|
||||
except roslaunch.core.RLException as e:
|
||||
print(str(e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
77
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/roslaunch_logs.py
vendored
Normal file
77
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/roslaunch_logs.py
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
"""
|
||||
Implementation for roslaunch-logs command-line utility.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import rospkg
|
||||
import rosgraph
|
||||
|
||||
NAME = 'roslaunch-logs'
|
||||
|
||||
def get_run_id():
|
||||
try:
|
||||
param_server = rosgraph.Master('/roslaunch')
|
||||
return param_server.getParam('/run_id')
|
||||
except: # cannot contact parameter server
|
||||
pass
|
||||
|
||||
def logs_main():
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage="usage: %prog", prog=NAME)
|
||||
options, args = parser.parse_args()
|
||||
if args:
|
||||
parser.error("%s takes no arguments"%NAME)
|
||||
|
||||
log_dir = rospkg.get_log_dir()
|
||||
if not log_dir:
|
||||
print("Cannot determine ROS log directory", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
run_id = get_run_id()
|
||||
if not run_id:
|
||||
# go ahead and print the log directory
|
||||
print("No active roscore", file=sys.stderr)
|
||||
print(log_dir)
|
||||
sys.exit(2)
|
||||
|
||||
print(os.path.join(log_dir, run_id))
|
||||
118
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/scriptapi.py
vendored
Normal file
118
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/scriptapi.py
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
"""
|
||||
Scripting interface for roslaunch
|
||||
"""
|
||||
|
||||
from roslaunch.core import Node, Master, RLException
|
||||
|
||||
import roslaunch.config
|
||||
import roslaunch.parent
|
||||
import roslaunch.xmlloader
|
||||
|
||||
class ROSLaunch(object):
|
||||
"""
|
||||
ROSLaunchParent represents the main 'parent' roslaunch process. It
|
||||
is responsible for loading the launch files, assigning machines,
|
||||
and then starting up any remote processes. The __main__ method
|
||||
delegates most of runtime to ROSLaunchParent.
|
||||
|
||||
This must be called from the Python Main thread due to signal registration.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
@raise RLException: if fails to initialize
|
||||
"""
|
||||
import rosgraph.masterapi
|
||||
master = rosgraph.masterapi.Master('/roslaunch_script')
|
||||
uuid = roslaunch.rlutil.get_or_generate_uuid(None, True)
|
||||
self.parent = roslaunch.parent.ROSLaunchParent(uuid, [], is_core=False)
|
||||
self.started = False
|
||||
|
||||
def load(self, f):
|
||||
"""
|
||||
Load roslaunch file
|
||||
|
||||
@param f: filename
|
||||
@type f: str
|
||||
"""
|
||||
raise NotImplemented
|
||||
|
||||
def load_str(self, s):
|
||||
"""
|
||||
Load roslaunch string
|
||||
|
||||
@param s: string representation of roslaunch config
|
||||
@type s: str
|
||||
"""
|
||||
raise NotImplemented
|
||||
|
||||
def launch(self, node):
|
||||
"""
|
||||
Launch a roslaunch node instance
|
||||
|
||||
@param node: roslaunch Node instance
|
||||
@type node: roslaunch.Node
|
||||
@return: node process
|
||||
@rtype: roslaunch.Process
|
||||
@raise RLException: if launch fails
|
||||
"""
|
||||
if not self.started:
|
||||
raise RLException("please start ROSLaunch first")
|
||||
elif not isinstance(node, Node):
|
||||
raise ValueError("arg must be of type Node")
|
||||
|
||||
proc, success = self.parent.runner.launch_node(node)
|
||||
if not success:
|
||||
raise RLException("failed to launch %s/%s"%(node.package, node.type))
|
||||
return proc
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start roslaunch. This will launch any pre-configured launches and spin up the process monitor thread.
|
||||
"""
|
||||
self.parent.start(auto_terminate=False)
|
||||
self.started = True
|
||||
|
||||
def spin(self):
|
||||
self.parent.spin()
|
||||
|
||||
def spin_once(self):
|
||||
self.parent.spin_once()
|
||||
|
||||
def stop(self):
|
||||
self.parent.shutdown()
|
||||
|
||||
533
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/server.py
vendored
Normal file
533
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/server.py
vendored
Normal file
@@ -0,0 +1,533 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
"""
|
||||
XML-RPC servers for parent and children
|
||||
|
||||
Following typical XmlRpcNode code, code is divided into:
|
||||
|
||||
a) Handlers: these actually define and execute the XML-RPC API
|
||||
b) Nodes: these run the XML-RPC server
|
||||
|
||||
In this code you'll find 'Parent' and 'Child' code. The parent node
|
||||
is the original roslaunch process. The child nodes are the child
|
||||
processes it launches in order to handle remote launching (or
|
||||
execution as a different user).
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from urlparse import urlparse
|
||||
try:
|
||||
from xmlrpc.client import ServerProxy
|
||||
except ImportError:
|
||||
from xmlrpclib import ServerProxy
|
||||
|
||||
import rosgraph.network as network
|
||||
import rosgraph.xmlrpc as xmlrpc
|
||||
|
||||
import roslaunch.config
|
||||
from roslaunch.pmon import ProcessListener, Process
|
||||
import roslaunch.xmlloader
|
||||
|
||||
from roslaunch.launch import ROSLaunchRunner
|
||||
from roslaunch.core import RLException, \
|
||||
add_printlog_handler, add_printerrlog_handler, printlog, printerrlog, printlog_bold
|
||||
|
||||
#For using Log message level constants
|
||||
from rosgraph_msgs.msg import Log
|
||||
|
||||
# interface class so that we don't have circular deps
|
||||
class ChildROSLaunchProcess(Process):
|
||||
"""
|
||||
API for remote roslaunch processes
|
||||
"""
|
||||
def __init__(self, name, args, env):
|
||||
super(ChildROSLaunchProcess, self).__init__('roslaunch', name, args, env, False)
|
||||
self.uri = None
|
||||
|
||||
def set_uri(self, uri):
|
||||
self.uri = uri
|
||||
|
||||
class ROSLaunchBaseHandler(xmlrpc.XmlRpcHandler):
|
||||
"""
|
||||
Common XML-RPC API for the roslaunch server and child node
|
||||
"""
|
||||
def __init__(self, pm):
|
||||
self.pm = pm
|
||||
self.logger = logging.getLogger('roslaunch.server')
|
||||
if self.pm is None:
|
||||
raise RLException("cannot create xmlrpc handler: pm is not initialized")
|
||||
|
||||
#TODO: kill process, restart (with optional prefix). list active, list dead. CPU usage
|
||||
|
||||
def list_processes(self):
|
||||
"""
|
||||
@return: code, msg, process list.
|
||||
Process list is two lists, where first list of
|
||||
active process names along with the number of times that
|
||||
process has been spawned. Second list contains dead process
|
||||
names and their spawn count.
|
||||
@rtype: int, str, [[(str, int),], [(str,int),]]
|
||||
"""
|
||||
return 1, "processes on parent machine", self.pm.get_process_names_with_spawn_count()
|
||||
|
||||
def process_info(self, process_name):
|
||||
"""
|
||||
@return: dictionary of metadata about process. Keys vary by implementation
|
||||
@rtype: int, str, dict
|
||||
"""
|
||||
p = self.pm.get_process(process_name)
|
||||
if p is None:
|
||||
return -1, "no process by that name", {}
|
||||
else:
|
||||
return 1, "process info", p.get_info()
|
||||
|
||||
def get_pid(self):
|
||||
"""
|
||||
@return: code, msg, pid
|
||||
@rtype: int, str, int
|
||||
"""
|
||||
|
||||
pid = os.getpid()
|
||||
return 1, str(pid), pid
|
||||
|
||||
def get_node_names(self):
|
||||
"""
|
||||
@return: code, msg, list of node names
|
||||
@rtype: int, str, [str]
|
||||
"""
|
||||
if self.pm is None:
|
||||
return 0, "uninitialized", []
|
||||
return 1, "node names", self.pm.get_active_names()
|
||||
|
||||
def _shutdown(self, reason):
|
||||
"""
|
||||
xmlrpc.XmlRpcHandler API: inform handler of shutdown
|
||||
@param reason: human-readable shutdown reason
|
||||
@type reason: str
|
||||
"""
|
||||
return 1, '', 1
|
||||
|
||||
|
||||
# Uses camel-case network-API naming conventions
|
||||
class ROSLaunchParentHandler(ROSLaunchBaseHandler):
|
||||
"""
|
||||
XML-RPC API for the roslaunch server node
|
||||
"""
|
||||
|
||||
def __init__(self, pm, child_processes, listeners):
|
||||
"""
|
||||
@param child_processes: Map of remote processes so that server can update processes
|
||||
with information as children register. Handler will not modify
|
||||
keys.
|
||||
@type child_processes: {name : ChildROSLaunchProcess}.
|
||||
@param listeners [ProcessListener]: list of
|
||||
listeners to notify when process_died events occur.
|
||||
"""
|
||||
super(ROSLaunchParentHandler, self).__init__(pm)
|
||||
self.child_processes = child_processes
|
||||
self.listeners = listeners
|
||||
|
||||
def register(self, client, uri):
|
||||
"""
|
||||
Registration callback from newly launched roslaunch clients
|
||||
@param client: name of client
|
||||
@type client: str
|
||||
@param uri: XML-RPC URI of client
|
||||
@type uri: str
|
||||
@return: code, msg, ignore
|
||||
@rtype: int, str, int
|
||||
"""
|
||||
if client not in self.child_processes:
|
||||
self.logger.error("Unknown child [%s] registered with server", client)
|
||||
return -1, "unknown child [%s]"%client, 0
|
||||
else:
|
||||
self.logger.info("child [%s] registered with server, uri[%s]", client, uri)
|
||||
self.child_processes[client].set_uri(uri)
|
||||
return 1, "registered", 1
|
||||
|
||||
def list_children(self):
|
||||
"""
|
||||
List the roslaunch child processes.
|
||||
@return int, str, [str]: code, msg, list of the roslaunch children URIS
|
||||
"""
|
||||
return 1, 'roslaunch children', [v.uri for v in self.child_processes.values() if v.uri is not None]
|
||||
|
||||
def process_died(self, process_name, exit_code):
|
||||
"""
|
||||
Inform roslaunch server that a remote process has died
|
||||
@param process_name: name of process that died
|
||||
@type process_name: str
|
||||
@param exit_code: exit code of remote process
|
||||
@type exit_code: int
|
||||
@return: code, msg, ignore
|
||||
@rtype: int, str, int
|
||||
"""
|
||||
for l in self.listeners:
|
||||
try:
|
||||
l.process_died(process_name, exit_code)
|
||||
except:
|
||||
self.logger.error(traceback.format_exc())
|
||||
return 1, '', 0
|
||||
|
||||
def log(self, client, level, message):
|
||||
"""
|
||||
Report a log message to the server
|
||||
@param client: name of client
|
||||
@type client: str
|
||||
@param level: log level (uses rosgraph_msgs.msg.Log levels)
|
||||
@type level: int
|
||||
@param message: message to log
|
||||
@type message: str
|
||||
"""
|
||||
try:
|
||||
if level >= Log.ERROR:
|
||||
printerrlog("[%s]: %s"%(client, message))
|
||||
else:
|
||||
#hack due to the fact that we only have one INFO level
|
||||
if 'started with pid' in message:
|
||||
printlog_bold("[%s]: %s"%(client, message))
|
||||
else:
|
||||
printlog("[%s]: %s"%(client, message))
|
||||
except:
|
||||
# can't trust the logging system at this point, so just dump to screen
|
||||
traceback.print_exc()
|
||||
return 1, '', 1
|
||||
|
||||
class ROSLaunchChildHandler(ROSLaunchBaseHandler):
|
||||
"""
|
||||
XML-RPC API implementation for child roslaunches
|
||||
NOTE: the client handler runs a process monitor so that
|
||||
it can track processes across requests
|
||||
"""
|
||||
|
||||
def __init__(self, run_id, name, server_uri, pm):
|
||||
"""
|
||||
@param server_uri: XML-RPC URI of server
|
||||
@type server_uri: str
|
||||
@param pm: process monitor to use
|
||||
@type pm: L{ProcessMonitor}
|
||||
@raise RLException: If parameters are invalid
|
||||
"""
|
||||
super(ROSLaunchChildHandler, self).__init__(pm)
|
||||
if server_uri is None:
|
||||
raise RLException("server_uri is not initialized")
|
||||
self.run_id = run_id
|
||||
|
||||
# parse the URI to make sure it's valid
|
||||
_, urlport = network.parse_http_host_and_port(server_uri)
|
||||
if urlport <= 0:
|
||||
raise RLException("ERROR: roslaunch server URI is not a valid XML-RPC URI. Value is [%s]"%m.uri)
|
||||
|
||||
self.name = name
|
||||
self.pm = pm
|
||||
self.server_uri = server_uri
|
||||
self.server = ServerProxy(server_uri)
|
||||
|
||||
def _shutdown(self, reason):
|
||||
"""
|
||||
xmlrpc.XmlRpcHandler API: inform handler of shutdown
|
||||
@param reason: human-readable shutdown reason
|
||||
@type reason: str
|
||||
"""
|
||||
if self.pm is not None:
|
||||
self.pm.shutdown()
|
||||
self.pm.join()
|
||||
self.pm = None
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
@return: code, msg, ignore
|
||||
@rtype: int, str, int
|
||||
"""
|
||||
self._shutdown("external call")
|
||||
return 1, "success", 1
|
||||
|
||||
def _log(self, level, message):
|
||||
"""
|
||||
log message to log file and roslaunch server
|
||||
@param level: log level
|
||||
@type level: int
|
||||
@param message: message to log
|
||||
@type message: str
|
||||
"""
|
||||
try:
|
||||
if self.logger is not None:
|
||||
self.logger.debug(message)
|
||||
if self.server is not None:
|
||||
self.server.log(str(self.name), level, str(message))
|
||||
except:
|
||||
self.logger.error(traceback.format_exc())
|
||||
|
||||
def launch(self, launch_xml):
|
||||
"""
|
||||
Launch the roslaunch XML file. Because this is a child
|
||||
roslaunch, it will not set parameters nor manipulate the
|
||||
master. Call blocks until launch is complete
|
||||
@param xml: roslaunch XML file to launch
|
||||
@type xml: str
|
||||
@return: code, msg, [ [ successful launches], [failed launches] ]
|
||||
@rtype: int, str, [ [str], [str] ]
|
||||
"""
|
||||
if self.pm is None:
|
||||
return 0, "uninitialized", -1
|
||||
|
||||
rosconfig = roslaunch.config.ROSLaunchConfig()
|
||||
try:
|
||||
roslaunch.xmlloader.XmlLoader().load_string(launch_xml, rosconfig)
|
||||
except roslaunch.xmlloader.XmlParseException as e:
|
||||
return -1, "ERROR: %s"%e, [[], []]
|
||||
|
||||
# won't actually do anything other than local, but still required
|
||||
rosconfig.assign_machines()
|
||||
|
||||
try:
|
||||
# roslaunch clients try to behave like normal roslaunches as much as possible. It's
|
||||
# mainly the responsibility of the roslaunch server to not give us any XML that might
|
||||
# cause conflict (e.g. master tags, param tags, etc...).
|
||||
self._log(Log.INFO, "launching nodes...")
|
||||
runner = ROSLaunchRunner(self.run_id, rosconfig, server_uri=self.server_uri, pmon=self.pm)
|
||||
succeeded, failed = runner.launch()
|
||||
self._log(Log.INFO, "... done launching nodes")
|
||||
# enable the process monitor to exit of all processes die
|
||||
self.pm.registrations_complete()
|
||||
return 1, "launched", [ succeeded, failed ]
|
||||
except Exception as e:
|
||||
return 0, "ERROR: %s"%traceback.format_exc(), [[], []]
|
||||
|
||||
_STARTUP_TIMEOUT = 5.0 #seconds
|
||||
|
||||
class ROSLaunchNode(xmlrpc.XmlRpcNode):
|
||||
"""
|
||||
Base XML-RPC server for roslaunch parent/child processes
|
||||
"""
|
||||
|
||||
def __init__(self, handler):
|
||||
"""
|
||||
@param handler: xmlrpc api handler
|
||||
@type handler: L{ROSLaunchBaseHandler}
|
||||
"""
|
||||
super(ROSLaunchNode, self).__init__(0, handler)
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Startup roslaunch server XML-RPC services
|
||||
@raise RLException: if server fails to start
|
||||
"""
|
||||
logger = logging.getLogger('roslaunch.server')
|
||||
logger.info("starting roslaunch XML-RPC server")
|
||||
super(ROSLaunchNode, self).start()
|
||||
|
||||
# wait for node thread to initialize
|
||||
timeout_t = time.time() + _STARTUP_TIMEOUT
|
||||
logger.info("waiting for roslaunch XML-RPC server to initialize")
|
||||
while not self.uri and time.time() < timeout_t:
|
||||
time.sleep(0.01)
|
||||
if not self.uri:
|
||||
raise RLException("XML-RPC initialization failed")
|
||||
|
||||
# Make sure our xmlrpc server is actually up. We've seen very
|
||||
# odd cases where remote nodes are unable to contact the
|
||||
# server but have been unable to prove this is the cause.
|
||||
server_up = False
|
||||
while not server_up and time.time() < timeout_t:
|
||||
try:
|
||||
code, msg, val = ServerProxy(self.uri).get_pid()
|
||||
if val != os.getpid():
|
||||
raise RLException("Server at [%s] did not respond with correct PID. There appears to be something wrong with the networking configuration"%self.uri)
|
||||
server_up = True
|
||||
except IOError:
|
||||
# presumably this can occur if we call in a small time
|
||||
# interval between the server socket port being
|
||||
# assigned and the XMLRPC server initializing, but it
|
||||
# is highly unlikely and unconfirmed
|
||||
time.sleep(0.1)
|
||||
except socket.error as e:
|
||||
if e.errno == 113:
|
||||
p = urlparse(self.uri)
|
||||
raise RLException("Unable to contact the address [%s], which should be local.\nThis is generally caused by:\n * bad local network configuration\n * bad ROS_IP environment variable\n * bad ROS_HOSTNAME environment variable\nCan you ping %s?"%(self.uri, p.hostname))
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
if not server_up:
|
||||
p = urlparse(self.uri)
|
||||
raise RLException("""Unable to contact my own server at [%s].
|
||||
This usually means that the network is not configured properly.
|
||||
|
||||
A common cause is that the machine cannot ping itself. Please check
|
||||
for errors by running:
|
||||
|
||||
\tping %s
|
||||
|
||||
For more tips, please see
|
||||
|
||||
\thttp://www.ros.org/wiki/ROS/NetworkSetup
|
||||
"""%(self.uri, p.hostname))
|
||||
printlog_bold("started roslaunch server %s"%(self.uri))
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
run() should not be called by higher-level code. ROSLaunchNode
|
||||
overrides underlying xmlrpc.XmlRpcNode implementation in order
|
||||
to log errors.
|
||||
"""
|
||||
try:
|
||||
super(ROSLaunchNode, self).run()
|
||||
except:
|
||||
logging.getLogger("roslaunch.remote").error(traceback.format_exc())
|
||||
print("ERROR: failed to launch XML-RPC server for roslaunch", file=sys.stderr)
|
||||
|
||||
class ROSLaunchParentNode(ROSLaunchNode):
|
||||
"""
|
||||
XML-RPC server for parent roslaunch.
|
||||
"""
|
||||
|
||||
def __init__(self, rosconfig, pm):
|
||||
"""
|
||||
@param config: ROSConfig launch configuration
|
||||
@type config: L{ROSConfig}
|
||||
@param pm: process monitor
|
||||
@type pm: L{ProcessMonitor}
|
||||
"""
|
||||
self.rosconfig = rosconfig
|
||||
self.listeners = []
|
||||
self.child_processes = {} #{ child-name : ChildROSLaunchProcess}.
|
||||
|
||||
if pm is None:
|
||||
raise RLException("cannot create parent node: pm is not initialized")
|
||||
handler = ROSLaunchParentHandler(pm, self.child_processes, self.listeners)
|
||||
super(ROSLaunchParentNode, self).__init__(handler)
|
||||
|
||||
def add_child(self, name, p):
|
||||
"""
|
||||
@param name: child roslaunch's name. NOTE: \a name is not
|
||||
the same as the machine config key.
|
||||
@type name: str
|
||||
@param p: process handle of child
|
||||
@type p: L{Process}
|
||||
"""
|
||||
self.child_processes[name] = p
|
||||
|
||||
def add_process_listener(self, l):
|
||||
"""
|
||||
Listen to events about remote processes dying. Not
|
||||
threadsafe. Must be called before processes started.
|
||||
@param l: Process listener
|
||||
@type l: L{ProcessListener}
|
||||
"""
|
||||
self.listeners.append(l)
|
||||
|
||||
class _ProcessListenerForwarder(ProcessListener):
|
||||
"""
|
||||
Simple listener that forwards ProcessListener events to a roslaunch server
|
||||
"""
|
||||
def __init__(self, server):
|
||||
self.server = server
|
||||
def process_died(self, process_name, exit_code):
|
||||
try:
|
||||
self.server.process_died(process_name, exit_code)
|
||||
except Exception as e:
|
||||
logging.getLogger("roslaunch.remote").error(traceback.format_exc())
|
||||
|
||||
class ROSLaunchChildNode(ROSLaunchNode):
|
||||
"""
|
||||
XML-RPC server for roslaunch child processes
|
||||
"""
|
||||
|
||||
def __init__(self, run_id, name, server_uri, pm):
|
||||
"""
|
||||
## Startup roslaunch remote client XML-RPC services. Blocks until shutdown
|
||||
## @param name: name of remote client
|
||||
## @type name: str
|
||||
## @param server_uri: XML-RPC URI of roslaunch server
|
||||
## @type server_uri: str
|
||||
## @return: XML-RPC URI
|
||||
## @rtype: str
|
||||
"""
|
||||
self.logger = logging.getLogger("roslaunch.server")
|
||||
self.run_id = run_id
|
||||
self.name = name
|
||||
self.server_uri = server_uri
|
||||
self.pm = pm
|
||||
|
||||
if self.pm is None:
|
||||
raise RLException("cannot create child node: pm is not initialized")
|
||||
handler = ROSLaunchChildHandler(self.run_id, self.name, self.server_uri, self.pm)
|
||||
super(ROSLaunchChildNode, self).__init__(handler)
|
||||
|
||||
def _register_with_server(self):
|
||||
"""
|
||||
Register child node with server
|
||||
"""
|
||||
name = self.name
|
||||
self.logger.info("attempting to register with roslaunch parent [%s]"%self.server_uri)
|
||||
try:
|
||||
server = ServerProxy(self.server_uri)
|
||||
code, msg, _ = server.register(name, self.uri)
|
||||
if code != 1:
|
||||
raise RLException("unable to register with roslaunch server: %s"%msg)
|
||||
except Exception as e:
|
||||
self.logger.error("Exception while registering with roslaunch parent [%s]: %s"%(self.server_uri, traceback.format_exc()))
|
||||
# fail
|
||||
raise RLException("Exception while registering with roslaunch parent [%s]: %s"%(self.server_uri, traceback.format_exc()))
|
||||
|
||||
self.logger.debug("child registered with server")
|
||||
|
||||
# register printlog handler so messages are funneled to remote
|
||||
def serverlog(msg):
|
||||
server.log(name, Log.INFO, msg)
|
||||
def servererrlog(msg):
|
||||
server.log(name, Log.ERROR, msg)
|
||||
add_printlog_handler(serverlog)
|
||||
add_printerrlog_handler(servererrlog)
|
||||
|
||||
# register process listener to forward process death events to main server
|
||||
self.pm.add_process_listener(_ProcessListenerForwarder(server))
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Initialize child. Must be called before run
|
||||
"""
|
||||
self.logger.info("starting roslaunch child process [%s], server URI is [%s]", self.name, self.server_uri)
|
||||
super(ROSLaunchChildNode, self).start()
|
||||
self._register_with_server()
|
||||
437
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/substitution_args.py
vendored
Normal file
437
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/substitution_args.py
vendored
Normal file
@@ -0,0 +1,437 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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: substitution_args.py 15178 2011-10-10 21:22:53Z kwc $
|
||||
|
||||
"""
|
||||
Library for processing XML substitution args. This is currently used
|
||||
by roslaunch and xacro, but it is not yet a top-level ROS feature.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO # Python 2.x
|
||||
except ImportError:
|
||||
from io import StringIO # Python 3.x
|
||||
|
||||
import rosgraph.names
|
||||
import rospkg
|
||||
from roslaunch.loader import convert_value
|
||||
import math
|
||||
|
||||
_rospack = None
|
||||
|
||||
class SubstitutionException(Exception):
|
||||
"""
|
||||
Base class for exceptions in substitution_args routines
|
||||
"""
|
||||
pass
|
||||
class ArgException(SubstitutionException):
|
||||
"""
|
||||
Exception for missing $(arg) values
|
||||
"""
|
||||
pass
|
||||
|
||||
def _eval_env(name):
|
||||
try:
|
||||
return os.environ[name]
|
||||
except KeyError as e:
|
||||
raise SubstitutionException("environment variable %s is not set" % str(e))
|
||||
|
||||
def _env(resolved, a, args, context):
|
||||
"""
|
||||
process $(env) arg
|
||||
@return: updated resolved argument
|
||||
@rtype: str
|
||||
@raise SubstitutionException: if arg invalidly specified
|
||||
"""
|
||||
if len(args) != 1:
|
||||
raise SubstitutionException("$(env var) command only accepts one argument [%s]"%a)
|
||||
return resolved.replace("$(%s)" % a, _eval_env(args[0]))
|
||||
|
||||
def _eval_optenv(name, default=''):
|
||||
if name in os.environ:
|
||||
return os.environ[name]
|
||||
return default
|
||||
|
||||
def _optenv(resolved, a, args, context):
|
||||
"""
|
||||
process $(optenv) arg
|
||||
@return: updated resolved argument
|
||||
@rtype: str
|
||||
@raise SubstitutionException: if arg invalidly specified
|
||||
"""
|
||||
if len(args) == 0:
|
||||
raise SubstitutionException("$(optenv var) must specify an environment variable [%s]"%a)
|
||||
return resolved.replace("$(%s)" % a, _eval_optenv(args[0], default=' '.join(args[1:])))
|
||||
|
||||
def _eval_anon(id, anons):
|
||||
if id in anons:
|
||||
return anons[id]
|
||||
resolve_to = rosgraph.names.anonymous_name(id)
|
||||
anons[id] = resolve_to
|
||||
return resolve_to
|
||||
|
||||
def _anon(resolved, a, args, context):
|
||||
"""
|
||||
process $(anon) arg
|
||||
@return: updated resolved argument
|
||||
@rtype: str
|
||||
@raise SubstitutionException: if arg invalidly specified
|
||||
"""
|
||||
# #1559 #1660
|
||||
if len(args) == 0:
|
||||
raise SubstitutionException("$(anon var) must specify a name [%s]"%a)
|
||||
elif len(args) > 1:
|
||||
raise SubstitutionException("$(anon var) may only specify one name [%s]"%a)
|
||||
if 'anon' not in context:
|
||||
context['anon'] = {}
|
||||
anon_context = context['anon']
|
||||
return resolved.replace("$(%s)" % a, _eval_anon(id=args[0], anons=anon_context))
|
||||
|
||||
def _eval_find(pkg):
|
||||
rp = _get_rospack()
|
||||
return rp.get_path(pkg)
|
||||
|
||||
def _find(resolved, a, args, context):
|
||||
"""
|
||||
process $(find PKG)
|
||||
Resolves the path while considering the path following the command to provide backward compatible results.
|
||||
If it is followed by a path it first tries to resolve it as an executable and than as a normal file under share.
|
||||
Else it resolves to the source share folder of the PKG.
|
||||
:returns: updated resolved argument, ``str``
|
||||
:raises: :exc:SubstitutionException: if PKG invalidly specified
|
||||
:raises: :exc:`rospkg.ResourceNotFound` If PKG requires resource (e.g. package) that does not exist
|
||||
"""
|
||||
if len(args) != 1:
|
||||
raise SubstitutionException("$(find pkg) command only accepts one argument [%s]" % a)
|
||||
before, after = _split_command(resolved, a)
|
||||
path, after = _separate_first_path(after)
|
||||
resolve_without_path = before + ('$(%s)' % a) + after
|
||||
path = _sanitize_path(path)
|
||||
if path.startswith('/') or path.startswith('\\'):
|
||||
path = path[1:]
|
||||
rp = _get_rospack()
|
||||
if path:
|
||||
source_path_to_packages = rp.get_custom_cache('source_path_to_packages', {})
|
||||
res = None
|
||||
try:
|
||||
res = _find_executable(
|
||||
resolve_without_path, a, [args[0], path], context,
|
||||
source_path_to_packages=source_path_to_packages)
|
||||
except SubstitutionException:
|
||||
pass
|
||||
if res is None:
|
||||
try:
|
||||
res = _find_resource(
|
||||
resolve_without_path, a, [args[0], path], context,
|
||||
source_path_to_packages=source_path_to_packages)
|
||||
except SubstitutionException:
|
||||
pass
|
||||
# persist mapping of packages in rospack instance
|
||||
if source_path_to_packages:
|
||||
rp.set_custom_cache('source_path_to_packages', source_path_to_packages)
|
||||
if res is not None:
|
||||
return res
|
||||
pkg_path = rp.get_path(args[0])
|
||||
if path:
|
||||
pkg_path = os.path.join(pkg_path, path)
|
||||
return before + pkg_path + after
|
||||
|
||||
|
||||
def _find_executable(resolved, a, args, _context, source_path_to_packages=None):
|
||||
"""
|
||||
process $(find-executable PKG PATH)
|
||||
It finds the executable with the basename(PATH) in the libexec folder
|
||||
or under the PATH relative to the package.xml file.
|
||||
:returns: updated resolved argument, ``str``
|
||||
:raises: :exc:SubstitutionException: if PKG/PATH invalidly specified or executable is not found for PKG
|
||||
"""
|
||||
if len(args) != 2:
|
||||
raise SubstitutionException("$(find-executable pkg path) command only accepts two argument [%s]" % a)
|
||||
before, after = _split_command(resolved, a)
|
||||
path = _sanitize_path(args[1])
|
||||
# we try to find the specific executable in libexec via catkin
|
||||
# which will search in install/devel space
|
||||
full_path = None
|
||||
from catkin.find_in_workspaces import find_in_workspaces
|
||||
paths = find_in_workspaces(
|
||||
['libexec'], project=args[0], first_matching_workspace_only=True,
|
||||
# implicitly first_match_only=True
|
||||
source_path_to_packages=source_path_to_packages)
|
||||
if paths:
|
||||
full_path = _get_executable_path(paths[0], os.path.basename(path))
|
||||
if not full_path:
|
||||
# else we will look for the executable in the source folder of the package
|
||||
rp = _get_rospack()
|
||||
full_path = _get_executable_path(rp.get_path(args[0]), path)
|
||||
if not full_path:
|
||||
raise SubstitutionException("$(find-executable pkg path) could not find executable [%s]" % a)
|
||||
return before + full_path + after
|
||||
|
||||
|
||||
def _find_resource(resolved, a, args, _context, source_path_to_packages=None):
|
||||
"""
|
||||
process $(find-resource PKG PATH)
|
||||
Resolves the relative PATH from the share folder of the PKG either from install space, devel space or from the source folder.
|
||||
:returns: updated resolved argument, ``str``
|
||||
:raises: :exc:SubstitutionException: if PKG and PATH invalidly specified or relative PATH is not found for PKG
|
||||
"""
|
||||
if len(args) != 2:
|
||||
raise SubstitutionException("$(find-resource pkg path) command only accepts two argument [%s]" % a)
|
||||
before, after = _split_command(resolved, a)
|
||||
path = _sanitize_path(args[1])
|
||||
# we try to find the specific path in share via catkin
|
||||
# which will search in install/devel space and the source folder of the package
|
||||
from catkin.find_in_workspaces import find_in_workspaces
|
||||
paths = find_in_workspaces(
|
||||
['share'], project=args[0], path=path, first_matching_workspace_only=True,
|
||||
first_match_only=True, source_path_to_packages=source_path_to_packages)
|
||||
if not paths:
|
||||
raise SubstitutionException("$(find-resource pkg path) could not find path [%s]" % a)
|
||||
return before + paths[0] + after
|
||||
|
||||
|
||||
def _split_command(resolved, command_with_args):
|
||||
cmd = '$(%s)' % command_with_args
|
||||
idx1 = resolved.find(cmd)
|
||||
idx2 = idx1 + len(cmd)
|
||||
return resolved[0:idx1], resolved[idx2:]
|
||||
|
||||
|
||||
def _separate_first_path(value):
|
||||
idx = value.find(' ')
|
||||
if idx < 0:
|
||||
path, rest = value, ''
|
||||
else:
|
||||
path, rest = value[0:idx], value[idx:]
|
||||
return path, rest
|
||||
|
||||
|
||||
def _sanitize_path(path):
|
||||
path = path.replace('/', os.sep)
|
||||
path = path.replace('\\', os.sep)
|
||||
return path
|
||||
|
||||
|
||||
def _get_executable_path(base_path, path):
|
||||
full_path = os.path.join(base_path, path)
|
||||
if os.path.isfile(full_path) and os.access(full_path, os.X_OK):
|
||||
return full_path
|
||||
return None
|
||||
|
||||
|
||||
def _get_rospack():
|
||||
global _rospack
|
||||
if _rospack is None:
|
||||
_rospack = rospkg.RosPack()
|
||||
return _rospack
|
||||
|
||||
|
||||
def _eval_arg(name, args):
|
||||
try:
|
||||
return args[name]
|
||||
except KeyError:
|
||||
raise ArgException(name)
|
||||
|
||||
def _arg(resolved, a, args, context):
|
||||
"""
|
||||
process $(arg) arg
|
||||
|
||||
:returns: updated resolved argument, ``str``
|
||||
:raises: :exc:`ArgException` If arg invalidly specified
|
||||
"""
|
||||
if len(args) == 0:
|
||||
raise SubstitutionException("$(arg var) must specify a variable name [%s]"%(a))
|
||||
elif len(args) > 1:
|
||||
raise SubstitutionException("$(arg var) may only specify one arg [%s]"%(a))
|
||||
|
||||
if 'arg' not in context:
|
||||
context['arg'] = {}
|
||||
return resolved.replace("$(%s)" % a, _eval_arg(name=args[0], args=context['arg']))
|
||||
|
||||
# Create a dictionary of global symbols that will be available in the eval
|
||||
# context. We disable all the builtins, then add back True and False, and also
|
||||
# add true and false for convenience (because we accept those lower-case strings
|
||||
# as boolean values in XML).
|
||||
_eval_dict={
|
||||
'true': True, 'false': False,
|
||||
'True': True, 'False': False,
|
||||
'__builtins__': {k: __builtins__[k] for k in ['list', 'dict', 'map', 'str', 'float', 'int']},
|
||||
'env': _eval_env,
|
||||
'optenv': _eval_optenv,
|
||||
'find': _eval_find
|
||||
}
|
||||
# also define all math symbols and functions
|
||||
_eval_dict.update(math.__dict__)
|
||||
|
||||
class _DictWrapper(object):
|
||||
def __init__(self, args, functions):
|
||||
self._args = args
|
||||
self._functions = functions
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return self._functions[key]
|
||||
except KeyError:
|
||||
return convert_value(self._args[key], 'auto')
|
||||
|
||||
def _eval(s, context):
|
||||
if 'anon' not in context:
|
||||
context['anon'] = {}
|
||||
if 'arg' not in context:
|
||||
context['arg'] = {}
|
||||
|
||||
# inject correct anon context
|
||||
def _eval_anon_context(id): return _eval_anon(id, anons=context['anon'])
|
||||
# inject arg context
|
||||
def _eval_arg_context(name): return convert_value(_eval_arg(name, args=context['arg']), 'auto')
|
||||
functions = dict(anon=_eval_anon_context, arg=_eval_arg_context)
|
||||
functions.update(_eval_dict)
|
||||
|
||||
# ignore values containing double underscores (for safety)
|
||||
# http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
|
||||
if s.find('__') >= 0:
|
||||
raise SubstitutionException("$(eval ...) may not contain double underscore expressions")
|
||||
return str(eval(s, {}, _DictWrapper(context['arg'], functions)))
|
||||
|
||||
def resolve_args(arg_str, context=None, resolve_anon=True):
|
||||
"""
|
||||
Resolves substitution args (see wiki spec U{http://ros.org/wiki/roslaunch}).
|
||||
|
||||
@param arg_str: string to resolve zero or more substitution args
|
||||
in. arg_str may be None, in which case resolve_args will
|
||||
return None
|
||||
@type arg_str: str
|
||||
@param context dict: (optional) dictionary for storing results of
|
||||
the 'anon' and 'arg' substitution args. multiple calls to
|
||||
resolve_args should use the same context so that 'anon'
|
||||
substitions resolve consistently. If no context is provided, a
|
||||
new one will be created for each call. Values for the 'arg'
|
||||
context should be stored as a dictionary in the 'arg' key.
|
||||
@type context: dict
|
||||
@param resolve_anon bool: If True (default), will resolve $(anon
|
||||
foo). If false, will leave these args as-is.
|
||||
@type resolve_anon: bool
|
||||
|
||||
@return str: arg_str with substitution args resolved
|
||||
@rtype: str
|
||||
@raise SubstitutionException: if there is an error resolving substitution args
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if not arg_str:
|
||||
return arg_str
|
||||
# special handling of $(eval ...)
|
||||
if arg_str.startswith('$(eval ') and arg_str.endswith(')'):
|
||||
return _eval(arg_str[7:-1], context)
|
||||
# first resolve variables like 'env' and 'arg'
|
||||
commands = {
|
||||
'env': _env,
|
||||
'optenv': _optenv,
|
||||
'anon': _anon,
|
||||
'arg': _arg,
|
||||
}
|
||||
resolved = _resolve_args(arg_str, context, resolve_anon, commands)
|
||||
# then resolve 'find' as it requires the subsequent path to be expanded already
|
||||
commands = {
|
||||
'find': _find,
|
||||
}
|
||||
resolved = _resolve_args(resolved, context, resolve_anon, commands)
|
||||
return resolved
|
||||
|
||||
def _resolve_args(arg_str, context, resolve_anon, commands):
|
||||
valid = ['find', 'env', 'optenv', 'anon', 'arg']
|
||||
resolved = arg_str
|
||||
for a in _collect_args(arg_str):
|
||||
splits = [s for s in a.split(' ') if s]
|
||||
if not splits[0] in valid:
|
||||
raise SubstitutionException("Unknown substitution command [%s]. Valid commands are %s"%(a, valid))
|
||||
command = splits[0]
|
||||
args = splits[1:]
|
||||
if command in commands:
|
||||
resolved = commands[command](resolved, a, args, context)
|
||||
return resolved
|
||||
|
||||
_OUT = 0
|
||||
_DOLLAR = 1
|
||||
_LP = 2
|
||||
_IN = 3
|
||||
def _collect_args(arg_str):
|
||||
"""
|
||||
State-machine parser for resolve_args. Substitution args are of the form:
|
||||
$(find package_name)/scripts/foo.py $(export some/attribute blar) non-relevant stuff
|
||||
|
||||
@param arg_str: argument string to parse args from
|
||||
@type arg_str: str
|
||||
@raise SubstitutionException: if args are invalidly specified
|
||||
@return: list of arguments
|
||||
@rtype: [str]
|
||||
"""
|
||||
buff = StringIO()
|
||||
args = []
|
||||
state = _OUT
|
||||
for c in arg_str:
|
||||
# No escapes supported
|
||||
if c == '$':
|
||||
if state == _OUT:
|
||||
state = _DOLLAR
|
||||
elif state == _DOLLAR:
|
||||
pass
|
||||
else:
|
||||
raise SubstitutionException("Dollar signs '$' cannot be inside of substitution args [%s]"%arg_str)
|
||||
elif c == '(':
|
||||
if state == _DOLLAR:
|
||||
state = _LP
|
||||
elif state != _OUT:
|
||||
raise SubstitutionException("Invalid left parenthesis '(' in substitution args [%s]"%arg_str)
|
||||
elif c == ')':
|
||||
if state == _IN:
|
||||
#save contents of collected buffer
|
||||
args.append(buff.getvalue())
|
||||
buff.truncate(0)
|
||||
buff.seek(0)
|
||||
state = _OUT
|
||||
else:
|
||||
state = _OUT
|
||||
elif state == _DOLLAR:
|
||||
# left paren must immediately follow dollar sign to enter _IN state
|
||||
state = _OUT
|
||||
elif state == _LP:
|
||||
state = _IN
|
||||
|
||||
if state == _IN:
|
||||
buff.write(c)
|
||||
return args
|
||||
|
||||
|
||||
783
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/xmlloader.py
vendored
Normal file
783
thirdparty/ros/ros_comm/tools/roslaunch/src/roslaunch/xmlloader.py
vendored
Normal file
@@ -0,0 +1,783 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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$
|
||||
|
||||
"""
|
||||
Roslaunch XML file parser.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import itertools
|
||||
import sys
|
||||
import traceback
|
||||
import logging
|
||||
|
||||
from xml.dom.minidom import parse, parseString
|
||||
from xml.dom import Node as DomNode #avoid aliasing
|
||||
|
||||
from rosgraph.names import make_global_ns, ns_join, is_private, is_legal_name, get_ros_namespace
|
||||
|
||||
from .core import Param, Node, Test, Machine, RLException
|
||||
from . import loader
|
||||
from . import substitution_args
|
||||
|
||||
# use in our namespace
|
||||
SubstitutionException = substitution_args.SubstitutionException
|
||||
ArgException = substitution_args.ArgException
|
||||
|
||||
NS='ns'
|
||||
CLEAR_PARAMS='clear_params'
|
||||
|
||||
def _get_text(tag):
|
||||
buff = ''
|
||||
for t in tag.childNodes:
|
||||
if t.nodeType in [t.TEXT_NODE, t.CDATA_SECTION_NODE]:
|
||||
buff += t.data
|
||||
return buff
|
||||
|
||||
def ifunless_test(obj, tag, context):
|
||||
"""
|
||||
@return True: if tag should be processed according to its if/unless attributes
|
||||
"""
|
||||
if_val, unless_val = obj.opt_attrs(tag, context, ['if', 'unless'])
|
||||
if if_val is not None and unless_val is not None:
|
||||
raise XmlParseException("cannot set both 'if' and 'unless' on the same tag")
|
||||
if if_val is not None:
|
||||
if_val = loader.convert_value(if_val, 'bool')
|
||||
if if_val:
|
||||
return True
|
||||
elif unless_val is not None:
|
||||
unless_val = loader.convert_value(unless_val, 'bool')
|
||||
if not unless_val:
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
def ifunless(f):
|
||||
"""
|
||||
Decorator for evaluating whether or not tag function should run based on if/unless attributes
|
||||
"""
|
||||
def call(*args, **kwds):
|
||||
#TODO: logging, as well as check for verbose in kwds
|
||||
if ifunless_test(args[0], args[1], args[2]):
|
||||
return f(*args, **kwds)
|
||||
return call
|
||||
|
||||
# This code has gotten a bit crufty as roslaunch has grown far beyond
|
||||
# its original spec. It needs to be far more generic than it is in
|
||||
# order to not replicate bugs in multiple places.
|
||||
|
||||
class XmlParseException(RLException):
|
||||
"""Error with the XML syntax (e.g. invalid attribute/value combinations)"""
|
||||
pass
|
||||
|
||||
def _bool_attr(v, default, label):
|
||||
"""
|
||||
Validate boolean xml attribute.
|
||||
@param v: parameter value or None if no value provided
|
||||
@type v: any
|
||||
@param default: default value
|
||||
@type default: bool
|
||||
@param label: parameter name/label
|
||||
@type label: str
|
||||
@return: boolean value for attribute
|
||||
@rtype: bool
|
||||
@raise XmlParseException: if v is not in correct range or is empty.
|
||||
"""
|
||||
if v is None:
|
||||
return default
|
||||
if v.lower() == 'true':
|
||||
return True
|
||||
elif v.lower() == 'false':
|
||||
return False
|
||||
elif not v:
|
||||
raise XmlParseException("bool value for %s must be non-empty"%(label))
|
||||
else:
|
||||
raise XmlParseException("invalid bool value for %s: %s"%(label, v))
|
||||
|
||||
def _float_attr(v, default, label):
|
||||
"""
|
||||
Validate float xml attribute.
|
||||
@param v: parameter value or None if no value provided
|
||||
@type v: any
|
||||
@param default: default value
|
||||
@type default: float
|
||||
@param label: parameter name/label
|
||||
@type label: str
|
||||
@return: float value for attribute
|
||||
@rtype: float
|
||||
@raise XmlParseException: if v is not in correct range or is empty.
|
||||
"""
|
||||
if v is None:
|
||||
return default
|
||||
if not v:
|
||||
raise XmlParseException("bool value for %s must be non-empty"%(label))
|
||||
try:
|
||||
x = float(v)
|
||||
except ValueError:
|
||||
raise XmlParseException("invalid float value for %s: %s"%(label, v))
|
||||
return x
|
||||
|
||||
|
||||
# maps machine 'default' attribute to Machine default property
|
||||
_is_default = {'true': True, 'false': False, 'never': False }
|
||||
# maps machine 'default' attribute to Machine assignable property
|
||||
_assignable = {'true': True, 'false': True, 'never': False }
|
||||
|
||||
# NOTE: code is currently in a semi-refactored state. I'm slowly
|
||||
# migrating common routines into the Loader class in the hopes it will
|
||||
# make it easier to write alternate loaders and also test.
|
||||
class XmlLoader(loader.Loader):
|
||||
"""
|
||||
Parser for roslaunch XML format. Loads parsed representation into ROSConfig model.
|
||||
"""
|
||||
|
||||
def __init__(self, resolve_anon=True):
|
||||
"""
|
||||
@param resolve_anon: If True (default), will resolve $(anon foo). If
|
||||
false, will leave these args as-is.
|
||||
@type resolve_anon: bool
|
||||
"""
|
||||
# store the root XmlContext so that outside code can access it
|
||||
self.root_context = None
|
||||
self.resolve_anon = resolve_anon
|
||||
|
||||
def resolve_args(self, args, context):
|
||||
"""
|
||||
Wrapper around substitution_args.resolve_args to set common parameters
|
||||
"""
|
||||
# resolve_args gets called a lot, so we optimize by testing for dollar sign before resolving
|
||||
if args and '$' in args:
|
||||
return substitution_args.resolve_args(args, context=context.resolve_dict, resolve_anon=self.resolve_anon)
|
||||
else:
|
||||
return args
|
||||
|
||||
def opt_attrs(self, tag, context, attrs):
|
||||
"""
|
||||
Helper routine for fetching and resolving optional tag attributes
|
||||
@param tag DOM tag
|
||||
@param context LoaderContext
|
||||
@param attrs (str): list of attributes to resolve
|
||||
"""
|
||||
def tag_value(tag, a):
|
||||
if tag.hasAttribute(a):
|
||||
# getAttribute returns empty string for non-existent
|
||||
# attributes, which makes it impossible to distinguish
|
||||
# with actual empty values
|
||||
return tag.getAttribute(a)
|
||||
else:
|
||||
return None
|
||||
return [self.resolve_args(tag_value(tag,a), context) for a in attrs]
|
||||
|
||||
def reqd_attrs(self, tag, context, attrs):
|
||||
"""
|
||||
Helper routine for fetching and resolving required tag attributes
|
||||
@param tag: DOM tag
|
||||
@param attrs: list of attributes to resolve
|
||||
@type attrs: (str)
|
||||
@raise KeyError: if required attribute is missing
|
||||
"""
|
||||
return [self.resolve_args(tag.attributes[a].value, context) for a in attrs]
|
||||
|
||||
def _check_attrs(self, tag, context, ros_config, attrs):
|
||||
tag_attrs = tag.attributes.keys()
|
||||
for t_a in tag_attrs:
|
||||
if not t_a in attrs and not t_a in ['if', 'unless']:
|
||||
ros_config.add_config_error("[%s] unknown <%s> attribute '%s'"%(context.filename, tag.tagName, t_a))
|
||||
|
||||
# 'ns' attribute is now deprecated and is an alias for
|
||||
# 'param'. 'param' is required if the value is a non-dictionary
|
||||
# type
|
||||
ROSPARAM_OPT_ATTRS = ('command', 'ns', 'file', 'param', 'subst_value')
|
||||
@ifunless
|
||||
def _rosparam_tag(self, tag, context, ros_config, verbose=True):
|
||||
try:
|
||||
self._check_attrs(tag, context, ros_config, XmlLoader.ROSPARAM_OPT_ATTRS)
|
||||
cmd, ns, file, param, subst_value = self.opt_attrs(tag, context, (XmlLoader.ROSPARAM_OPT_ATTRS))
|
||||
subst_value = _bool_attr(subst_value, False, 'subst_value')
|
||||
# ns atribute is a bit out-moded and is only left in for backwards compatibility
|
||||
param = ns_join(ns or '', param or '')
|
||||
|
||||
# load is the default command
|
||||
cmd = cmd or 'load'
|
||||
value = _get_text(tag)
|
||||
subst_function = None
|
||||
if subst_value:
|
||||
subst_function = lambda x: self.resolve_args(x, context)
|
||||
self.load_rosparam(context, ros_config, cmd, param, file, value, verbose=verbose, subst_function=subst_function)
|
||||
|
||||
except ValueError as e:
|
||||
raise loader.LoadException("error loading <rosparam> tag: \n\t"+str(e)+"\nXML is %s"%tag.toxml())
|
||||
|
||||
PARAM_ATTRS = ('name', 'value', 'type', 'value', 'textfile', 'binfile', 'command')
|
||||
@ifunless
|
||||
def _param_tag(self, tag, context, ros_config, force_local=False, verbose=True):
|
||||
"""
|
||||
@param force_local: if True, param must be added to context instead of ros_config
|
||||
@type force_local: bool
|
||||
"""
|
||||
try:
|
||||
self._check_attrs(tag, context, ros_config, XmlLoader.PARAM_ATTRS)
|
||||
|
||||
# compute name and value
|
||||
ptype = (tag.getAttribute('type') or 'auto').lower().strip()
|
||||
|
||||
vals = self.opt_attrs(tag, context, ('value', 'textfile', 'binfile', 'command'))
|
||||
if len([v for v in vals if v is not None]) != 1:
|
||||
raise XmlParseException(
|
||||
"<param> tag must have one and only one of value/textfile/binfile.")
|
||||
|
||||
# compute name. if name is a tilde name, it is placed in
|
||||
# the context. otherwise it is placed in the ros config.
|
||||
name = self.resolve_args(tag.attributes['name'].value.strip(), context)
|
||||
value = self.param_value(verbose, name, ptype, *vals)
|
||||
|
||||
if is_private(name) or force_local:
|
||||
p = Param(name, value)
|
||||
context.add_param(p)
|
||||
else:
|
||||
p = Param(ns_join(context.ns, name), value)
|
||||
ros_config.add_param(Param(ns_join(context.ns, name), value), filename=context.filename, verbose=verbose)
|
||||
return p
|
||||
|
||||
except KeyError as e:
|
||||
raise XmlParseException(
|
||||
"<param> tag is missing required attribute: %s. \n\nParam xml is %s"%(e, tag.toxml()))
|
||||
except ValueError as e:
|
||||
raise XmlParseException(
|
||||
"Invalid <param> tag: %s. \n\nParam xml is %s"%(e, tag.toxml()))
|
||||
|
||||
ARG_ATTRS = ('name', 'value', 'default', 'doc')
|
||||
@ifunless
|
||||
def _arg_tag(self, tag, context, ros_config, verbose=True):
|
||||
"""
|
||||
Process an <arg> tag.
|
||||
"""
|
||||
try:
|
||||
self._check_attrs(tag, context, ros_config, XmlLoader.ARG_ATTRS)
|
||||
(name,) = self.reqd_attrs(tag, context, ('name',))
|
||||
value, default, doc = self.opt_attrs(tag, context, ('value', 'default', 'doc'))
|
||||
|
||||
if value is not None and default is not None:
|
||||
raise XmlParseException(
|
||||
"<arg> tag must have one and only one of value/default.")
|
||||
|
||||
context.add_arg(name, value=value, default=default, doc=doc)
|
||||
|
||||
except substitution_args.ArgException as e:
|
||||
raise XmlParseException(
|
||||
"arg '%s' is not defined. \n\nArg xml is %s"%(e, tag.toxml()))
|
||||
except Exception as e:
|
||||
raise XmlParseException(
|
||||
"Invalid <arg> tag: %s. \n\nArg xml is %s"%(e, tag.toxml()))
|
||||
|
||||
def _test_attrs(self, tag, context):
|
||||
"""
|
||||
Process attributes of <test> tag not present in <node>
|
||||
@return: test_name, time_limit
|
||||
@rtype: str, int
|
||||
"""
|
||||
for attr in ['respawn', 'respawn_delay', 'output']:
|
||||
if tag.hasAttribute(attr):
|
||||
raise XmlParseException("<test> tags cannot have '%s' attribute"%attr)
|
||||
|
||||
test_name = self.resolve_args(tag.attributes['test-name'].value, context)
|
||||
time_limit = self.resolve_args(tag.getAttribute('time-limit'), context)
|
||||
retry = self.resolve_args(tag.getAttribute('retry'), context)
|
||||
if time_limit:
|
||||
try:
|
||||
time_limit = float(time_limit)
|
||||
except ValueError:
|
||||
raise XmlParseException("'time-limit' must be a number: [%s]"%time_limit)
|
||||
if time_limit <= 0.0:
|
||||
raise XmlParseException("'time-limit' must be a positive number")
|
||||
if retry:
|
||||
try:
|
||||
retry = int(retry)
|
||||
except ValueError:
|
||||
raise XmlParseException("'retry' must be a number: [%s]"%retry)
|
||||
|
||||
return test_name, time_limit, retry
|
||||
|
||||
NODE_ATTRS = ['pkg', 'type', 'machine', 'name', 'args', 'output', \
|
||||
'respawn', 'respawn_delay', 'cwd', NS, CLEAR_PARAMS, \
|
||||
'launch-prefix', 'required']
|
||||
TEST_ATTRS = NODE_ATTRS + ['test-name','time-limit', 'retry']
|
||||
|
||||
@ifunless
|
||||
def _node_tag(self, tag, context, ros_config, default_machine, is_test=False, verbose=True):
|
||||
"""
|
||||
Process XML <node> or <test> tag
|
||||
@param tag: DOM node
|
||||
@type tag: Node
|
||||
@param context: namespace context
|
||||
@type context: L{LoaderContext}
|
||||
@param params: ROS parameter list
|
||||
@type params: [L{Param}]
|
||||
@param clear_params: list of ROS parameter names to clear before setting parameters
|
||||
@type clear_params: [str]
|
||||
@param default_machine: default machine to assign to node
|
||||
@type default_machine: str
|
||||
@param is_test: if set, will load as L{Test} object instead of L{Node} object
|
||||
@type is_test: bool
|
||||
"""
|
||||
try:
|
||||
if is_test:
|
||||
self._check_attrs(tag, context, ros_config, XmlLoader.TEST_ATTRS)
|
||||
(name,) = self.opt_attrs(tag, context, ('name',))
|
||||
test_name, time_limit, retry = self._test_attrs(tag, context)
|
||||
if not name:
|
||||
name = test_name
|
||||
else:
|
||||
self._check_attrs(tag, context, ros_config, XmlLoader.NODE_ATTRS)
|
||||
(name,) = self.reqd_attrs(tag, context, ('name',))
|
||||
|
||||
if not is_legal_name(name):
|
||||
ros_config.add_config_error("WARN: illegal <node> name '%s'.\nhttp://ros.org/wiki/Names\nThis will likely cause problems with other ROS tools.\nNode xml is %s"%(name, tag.toxml()))
|
||||
|
||||
child_ns = self._ns_clear_params_attr('node', tag, context, ros_config, node_name=name)
|
||||
param_ns = child_ns.child(name)
|
||||
param_ns.params = [] # This is necessary because child() does not make a copy of the param list.
|
||||
|
||||
# required attributes
|
||||
pkg, node_type = self.reqd_attrs(tag, context, ('pkg', 'type'))
|
||||
|
||||
# optional attributes
|
||||
machine, args, output, respawn, respawn_delay, cwd, launch_prefix, \
|
||||
required = self.opt_attrs(tag, context, ('machine', 'args',
|
||||
'output', 'respawn', 'respawn_delay', 'cwd',
|
||||
'launch-prefix', 'required'))
|
||||
if tag.hasAttribute('machine') and not len(machine.strip()):
|
||||
raise XmlParseException("<node> 'machine' must be non-empty: [%s]"%machine)
|
||||
if not machine and default_machine:
|
||||
machine = default_machine.name
|
||||
# validate respawn, required
|
||||
required, respawn = [_bool_attr(*rr) for rr in ((required, False, 'required'),\
|
||||
(respawn, False, 'respawn'))]
|
||||
respawn_delay = _float_attr(respawn_delay, 0.0, 'respawn_delay')
|
||||
|
||||
# each node gets its own copy of <remap> arguments, which
|
||||
# it inherits from its parent
|
||||
remap_context = context.child('')
|
||||
|
||||
# each node gets its own copy of <env> arguments, which
|
||||
# it inherits from its parent
|
||||
env_context = context.child('')
|
||||
|
||||
# nodes can have individual env args set in addition to
|
||||
# the ROS-specific ones.
|
||||
for t in [c for c in tag.childNodes if c.nodeType == DomNode.ELEMENT_NODE]:
|
||||
tag_name = t.tagName.lower()
|
||||
if tag_name == 'remap':
|
||||
r = self._remap_tag(t, context, ros_config)
|
||||
if r is not None:
|
||||
remap_context.add_remap(r)
|
||||
elif tag_name == 'param':
|
||||
self._param_tag(t, param_ns, ros_config, force_local=True, verbose=verbose)
|
||||
elif tag_name == 'rosparam':
|
||||
self._rosparam_tag(t, param_ns, ros_config, verbose=verbose)
|
||||
elif tag_name == 'env':
|
||||
self._env_tag(t, env_context, ros_config)
|
||||
else:
|
||||
ros_config.add_config_error("WARN: unrecognized '%s' child tag in the parent tag element: %s"%(t.tagName, tag.toxml()))
|
||||
|
||||
# #1036 evaluate all ~params in context
|
||||
# TODO: can we get rid of force_local (above), remove this for loop, and just rely on param_tag logic instead?
|
||||
for p in itertools.chain(context.params, param_ns.params):
|
||||
pkey = p.key
|
||||
if is_private(pkey):
|
||||
# strip leading ~, which is optional/inferred
|
||||
pkey = pkey[1:]
|
||||
pkey = param_ns.ns + pkey
|
||||
ros_config.add_param(Param(pkey, p.value), verbose=verbose)
|
||||
|
||||
if not is_test:
|
||||
return Node(pkg, node_type, name=name, namespace=child_ns.ns, machine_name=machine,
|
||||
args=args, respawn=respawn,
|
||||
respawn_delay=respawn_delay,
|
||||
remap_args=remap_context.remap_args(), env_args=env_context.env_args,
|
||||
output=output, cwd=cwd, launch_prefix=launch_prefix,
|
||||
required=required, filename=context.filename)
|
||||
else:
|
||||
return Test(test_name, pkg, node_type, name=name, namespace=child_ns.ns,
|
||||
machine_name=machine, args=args,
|
||||
remap_args=remap_context.remap_args(), env_args=env_context.env_args,
|
||||
time_limit=time_limit, cwd=cwd, launch_prefix=launch_prefix,
|
||||
retry=retry, filename=context.filename)
|
||||
except KeyError as e:
|
||||
raise XmlParseException(
|
||||
"<%s> tag is missing required attribute: %s. Node xml is %s"%(tag.tagName, e, tag.toxml()))
|
||||
except XmlParseException as e:
|
||||
raise XmlParseException(
|
||||
"Invalid <node> tag: %s. \n\nNode xml is %s"%(e, tag.toxml()))
|
||||
except ValueError as e:
|
||||
raise XmlParseException(
|
||||
"Invalid <node> tag: %s. \n\nNode xml is %s"%(e, tag.toxml()))
|
||||
|
||||
MACHINE_ATTRS = ('name', 'address', 'env-loader',
|
||||
'ssh-port', 'user', 'password', 'default', 'timeout')
|
||||
@ifunless
|
||||
def _machine_tag(self, tag, context, ros_config, verbose=True):
|
||||
try:
|
||||
# clone context as <machine> tag sets up its own env args
|
||||
context = context.child(None)
|
||||
|
||||
# pre-fuerte warning attributes
|
||||
attrs = self.opt_attrs(tag, context,
|
||||
('ros-root', 'ros-package-path', 'ros-ip', 'ros-hostname'))
|
||||
if any(attrs):
|
||||
raise XmlParseException("<machine>: ros-* attributes are not supported since ROS Fuerte.\nPlease use env-loader instead")
|
||||
|
||||
self._check_attrs(tag, context, ros_config, XmlLoader.MACHINE_ATTRS)
|
||||
# required attributes
|
||||
name, address = self.reqd_attrs(tag, context, ('name', 'address'))
|
||||
|
||||
# optional attributes
|
||||
attrs = self.opt_attrs(tag, context,
|
||||
('env-loader',
|
||||
'ssh-port', 'user', 'password', 'default', 'timeout'))
|
||||
env_loader, ssh_port, user, password, default, timeout = attrs
|
||||
|
||||
ssh_port = int(ssh_port or '22')
|
||||
|
||||
# check for default switch
|
||||
default = (default or 'false').lower()
|
||||
try:
|
||||
assignable = _assignable[default]
|
||||
is_default = _is_default[default]
|
||||
except KeyError as e:
|
||||
raise XmlParseException("Invalid value for 'attribute': %s"%default)
|
||||
|
||||
# load env args
|
||||
for t in [c for c in tag.childNodes if c.nodeType == DomNode.ELEMENT_NODE]:
|
||||
if t.tagName == 'env':
|
||||
raise XmlParseException("<machine>: <env> tag is not supported since ROS Fuerte.\nPlease use env-loader instead")
|
||||
else:
|
||||
ros_config.add_config_error("unrecognized '%s' tag in <%s> tag"%(t.tagName, tag.tagName))
|
||||
# cast timeout to float. make sure timeout wasn't an empty string or negative
|
||||
if timeout:
|
||||
try:
|
||||
timeout = float(timeout)
|
||||
except ValueError:
|
||||
raise XmlParseException("'timeout' be a number: [%s]"%timeout)
|
||||
elif timeout == '':
|
||||
raise XmlParseException("'timeout' cannot be empty")
|
||||
if timeout is not None and timeout <= 0.:
|
||||
raise XmlParseException("'timeout' be a positive number: [%s]"%timeout)
|
||||
|
||||
m = Machine(name, address, env_loader=env_loader,
|
||||
ssh_port=ssh_port, user=user, password=password,
|
||||
assignable=assignable, env_args=context.env_args, timeout=timeout)
|
||||
return (m, is_default)
|
||||
except KeyError as e:
|
||||
raise XmlParseException("<machine> tag is missing required attribute: %s"%e)
|
||||
except SubstitutionException as e:
|
||||
raise XmlParseException(
|
||||
"%s. \n\nMachine xml is %s"%(e, tag.toxml()))
|
||||
except RLException as e:
|
||||
raise XmlParseException(
|
||||
"%s. \n\nMachine xml is %s"%(e, tag.toxml()))
|
||||
|
||||
REMAP_ATTRS = ('from', 'to')
|
||||
@ifunless
|
||||
def _remap_tag(self, tag, context, ros_config):
|
||||
try:
|
||||
self._check_attrs(tag, context, ros_config, XmlLoader.REMAP_ATTRS)
|
||||
return self.reqd_attrs(tag, context, XmlLoader.REMAP_ATTRS)
|
||||
except KeyError as e:
|
||||
raise XmlParseException("<remap> tag is missing required from/to attributes: %s"%tag.toxml())
|
||||
|
||||
ENV_ATTRS = ('name', 'value')
|
||||
@ifunless
|
||||
def _env_tag(self, tag, context, ros_config):
|
||||
try:
|
||||
self._check_attrs(tag, context, ros_config, XmlLoader.ENV_ATTRS)
|
||||
self.load_env(context, ros_config, *self.reqd_attrs(tag, context, XmlLoader.ENV_ATTRS))
|
||||
except ValueError as e:
|
||||
raise XmlParseException("Invalid <env> tag: %s. \nXML is %s"%(str(e), tag.toxml()))
|
||||
except KeyError as e:
|
||||
raise XmlParseException("<env> tag is missing required name/value attributes: %s"%tag.toxml())
|
||||
|
||||
def _ns_clear_params_attr(self, tag_name, tag, context, ros_config, node_name=None, include_filename=None):
|
||||
"""
|
||||
Common processing routine for xml tags with NS and CLEAR_PARAMS attributes
|
||||
|
||||
@param tag: DOM Node
|
||||
@type tag: Node
|
||||
@param context: current namespace context
|
||||
@type context: LoaderContext
|
||||
@param clear_params: list of params to clear
|
||||
@type clear_params: [str]
|
||||
@param node_name: name of node (for use when tag_name == 'node')
|
||||
@type node_name: str
|
||||
@param include_filename: <include> filename if this is an <include> tag. If specified, context will use include rules.
|
||||
@type include_filename: str
|
||||
@return: loader context
|
||||
@rtype: L{LoaderContext}
|
||||
"""
|
||||
if tag.hasAttribute(NS):
|
||||
ns = self.resolve_args(tag.getAttribute(NS), context)
|
||||
if not ns:
|
||||
raise XmlParseException("<%s> tag has an empty '%s' attribute"%(tag_name, NS))
|
||||
else:
|
||||
ns = None
|
||||
if include_filename is not None:
|
||||
child_ns = context.include_child(ns, include_filename)
|
||||
else:
|
||||
child_ns = context.child(ns)
|
||||
clear_p = self.resolve_args(tag.getAttribute(CLEAR_PARAMS), context)
|
||||
if clear_p:
|
||||
clear_p = _bool_attr(clear_p, False, 'clear_params')
|
||||
if clear_p:
|
||||
if tag_name == 'node':
|
||||
if not node_name:
|
||||
raise XmlParseException("<%s> tag must have a 'name' attribute to use '%s' attribute"%(tag_name, CLEAR_PARAMS))
|
||||
# use make_global_ns to give trailing slash in order to be consistent with XmlContext.ns
|
||||
ros_config.add_clear_param(make_global_ns(ns_join(child_ns.ns, node_name)))
|
||||
else:
|
||||
if not ns:
|
||||
raise XmlParseException("'ns' attribute must be set in order to use 'clear_params'")
|
||||
ros_config.add_clear_param(child_ns.ns)
|
||||
return child_ns
|
||||
|
||||
@ifunless
|
||||
def _launch_tag(self, tag, ros_config, filename=None):
|
||||
# #2499
|
||||
deprecated = tag.getAttribute('deprecated')
|
||||
if deprecated:
|
||||
if filename:
|
||||
ros_config.add_config_error("[%s] DEPRECATED: %s"%(filename, deprecated))
|
||||
else:
|
||||
ros_config.add_config_error("Deprecation Warning: "+deprecated)
|
||||
|
||||
INCLUDE_ATTRS = ('file', NS, CLEAR_PARAMS, 'pass_all_args')
|
||||
@ifunless
|
||||
def _include_tag(self, tag, context, ros_config, default_machine, is_core, verbose):
|
||||
self._check_attrs(tag, context, ros_config, XmlLoader.INCLUDE_ATTRS)
|
||||
inc_filename = self.resolve_args(tag.attributes['file'].value, context)
|
||||
|
||||
if tag.hasAttribute('pass_all_args'):
|
||||
pass_all_args = self.resolve_args(tag.attributes['pass_all_args'].value, context)
|
||||
pass_all_args = _bool_attr(pass_all_args, False, 'pass_all_args')
|
||||
else:
|
||||
pass_all_args = False
|
||||
|
||||
child_ns = self._ns_clear_params_attr(tag.tagName, tag, context, ros_config, include_filename=inc_filename)
|
||||
|
||||
# If we're asked to pass all args, then we need to add them into the
|
||||
# child context.
|
||||
if pass_all_args:
|
||||
if 'arg' in context.resolve_dict:
|
||||
for name, value in context.resolve_dict['arg'].items():
|
||||
child_ns.add_arg(name, value=value)
|
||||
# Also set the flag that tells the child context to ignore (rather than
|
||||
# error on) attempts to set the same arg twice.
|
||||
child_ns.pass_all_args = True
|
||||
|
||||
for t in [c for c in tag.childNodes if c.nodeType == DomNode.ELEMENT_NODE]:
|
||||
tag_name = t.tagName.lower()
|
||||
if tag_name == 'env':
|
||||
self._env_tag(t, child_ns, ros_config)
|
||||
elif tag_name == 'arg':
|
||||
self._arg_tag(t, child_ns, ros_config, verbose=verbose)
|
||||
else:
|
||||
print("WARN: unrecognized '%s' tag in <%s> tag"%(t.tagName, tag.tagName), file=sys.stderr)
|
||||
|
||||
# setup arg passing
|
||||
loader.process_include_args(child_ns)
|
||||
|
||||
try:
|
||||
launch = self._parse_launch(inc_filename, verbose=verbose)
|
||||
ros_config.add_roslaunch_file(inc_filename)
|
||||
self._launch_tag(launch, ros_config, filename=inc_filename)
|
||||
default_machine = \
|
||||
self._recurse_load(ros_config, launch.childNodes, child_ns, \
|
||||
default_machine, is_core, verbose)
|
||||
|
||||
# check for unused args
|
||||
loader.post_process_include_args(child_ns)
|
||||
|
||||
except ArgException as e:
|
||||
raise XmlParseException("included file [%s] requires the '%s' arg to be set"%(inc_filename, str(e)))
|
||||
except XmlParseException as e:
|
||||
raise XmlParseException("while processing %s:\n%s"%(inc_filename, str(e)))
|
||||
if verbose:
|
||||
print("... done importing include file [%s]"%inc_filename)
|
||||
return default_machine
|
||||
|
||||
GROUP_ATTRS = (NS, CLEAR_PARAMS)
|
||||
def _recurse_load(self, ros_config, tags, context, default_machine, is_core, verbose):
|
||||
"""
|
||||
@return: new default machine for current context
|
||||
@rtype: L{Machine}
|
||||
"""
|
||||
for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]:
|
||||
name = tag.tagName
|
||||
if name == 'group':
|
||||
if ifunless_test(self, tag, context):
|
||||
self._check_attrs(tag, context, ros_config, XmlLoader.GROUP_ATTRS)
|
||||
child_ns = self._ns_clear_params_attr(name, tag, context, ros_config)
|
||||
child_ns.params = list(child_ns.params) # copy is needed here to enclose new params
|
||||
default_machine = \
|
||||
self._recurse_load(ros_config, tag.childNodes, child_ns, \
|
||||
default_machine, is_core, verbose)
|
||||
elif name == 'node':
|
||||
n = self._node_tag(tag, context, ros_config, default_machine, verbose=verbose)
|
||||
if n is not None:
|
||||
ros_config.add_node(n, core=is_core, verbose=verbose)
|
||||
elif name == 'test':
|
||||
t = self._node_tag(tag, context, ros_config, default_machine, is_test=True, verbose=verbose)
|
||||
if t is not None:
|
||||
ros_config.add_test(t, verbose=verbose)
|
||||
elif name == 'param':
|
||||
self._param_tag(tag, context, ros_config, verbose=verbose)
|
||||
elif name == 'remap':
|
||||
try:
|
||||
r = self._remap_tag(tag, context, ros_config)
|
||||
if r is not None:
|
||||
context.add_remap(r)
|
||||
except RLException as e:
|
||||
raise XmlParseException("Invalid <remap> tag: %s.\nXML is %s"%(str(e), tag.toxml()))
|
||||
elif name == 'machine':
|
||||
val = self._machine_tag(tag, context, ros_config, verbose=verbose)
|
||||
if val is not None:
|
||||
(m, is_default) = val
|
||||
if is_default:
|
||||
default_machine = m
|
||||
ros_config.add_machine(m, verbose=verbose)
|
||||
elif name == 'rosparam':
|
||||
self._rosparam_tag(tag, context, ros_config, verbose=verbose)
|
||||
elif name == 'master':
|
||||
pass #handled non-recursively
|
||||
elif name == 'include':
|
||||
val = self._include_tag(tag, context, ros_config, default_machine, is_core, verbose)
|
||||
if val is not None:
|
||||
default_machine = val
|
||||
elif name == 'env':
|
||||
self._env_tag(tag, context, ros_config)
|
||||
elif name == 'arg':
|
||||
self._arg_tag(tag, context, ros_config, verbose=verbose)
|
||||
else:
|
||||
ros_config.add_config_error("unrecognized tag "+tag.tagName)
|
||||
return default_machine
|
||||
|
||||
def _load_launch(self, launch, ros_config, is_core=False, filename=None, argv=None, verbose=True):
|
||||
"""
|
||||
subroutine of launch for loading XML DOM into config. Load_launch assumes that it is
|
||||
creating the root XmlContext, and is thus affected by command-line arguments.
|
||||
@param launch: DOM node of the root <launch> tag in the file
|
||||
@type launch: L{Node}
|
||||
@param ros_config: launch configuration to load XML file into
|
||||
@type ros_config: L{ROSLaunchConfig}
|
||||
@param is_core: (optional) if True, load file using ROS core rules. Default False.
|
||||
@type is_core: bool
|
||||
@param filename: (optional) name of file being loaded
|
||||
@type filename: str
|
||||
@param verbose: (optional) print verbose output. Default False.
|
||||
@type verbose: bool
|
||||
@param argv: (optional) command-line args. Default sys.argv.
|
||||
"""
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
self._launch_tag(launch, ros_config, filename)
|
||||
self.root_context = loader.LoaderContext(get_ros_namespace(), filename)
|
||||
loader.load_sysargs_into_context(self.root_context, argv)
|
||||
|
||||
if len(launch.getElementsByTagName('master')) > 0:
|
||||
print("WARNING: ignoring defunct <master /> tag", file=sys.stderr)
|
||||
self._recurse_load(ros_config, launch.childNodes, self.root_context, None, is_core, verbose)
|
||||
|
||||
def _parse_launch(self, filename, verbose):
|
||||
try:
|
||||
if verbose:
|
||||
print("... loading XML file [%s]"%filename)
|
||||
root = parse(filename).getElementsByTagName('launch')
|
||||
except Exception as e:
|
||||
raise XmlParseException("Invalid roslaunch XML syntax: %s"%e)
|
||||
if len(root) != 1:
|
||||
raise XmlParseException("Invalid roslaunch XML syntax: no root <launch> tag")
|
||||
return root[0]
|
||||
|
||||
def load(self, filename, ros_config, core=False, argv=None, verbose=True):
|
||||
"""
|
||||
load XML file into launch configuration
|
||||
@param filename: XML config file to load
|
||||
@type filename: str
|
||||
@param ros_config: launch configuration to load XML file into
|
||||
@type ros_config: L{ROSLaunchConfig}
|
||||
@param core: if True, load file using ROS core rules
|
||||
@type core: bool
|
||||
@param argv: override command-line arguments (mainly for arg testing)
|
||||
@type argv: [str]
|
||||
"""
|
||||
try:
|
||||
launch = self._parse_launch(filename, verbose)
|
||||
ros_config.add_roslaunch_file(filename)
|
||||
self._load_launch(launch, ros_config, is_core=core, filename=filename, argv=argv, verbose=verbose)
|
||||
except ArgException as e:
|
||||
raise XmlParseException("[%s] requires the '%s' arg to be set"%(filename, str(e)))
|
||||
except SubstitutionException as e:
|
||||
raise XmlParseException(str(e))
|
||||
|
||||
def load_string(self, xml_text, ros_config, core=False, verbose=True):
|
||||
"""
|
||||
Load XML text into launch configuration
|
||||
@param xml_text: XML configuration
|
||||
@type xml_text: str
|
||||
@param ros_config: launch configuration to load XML file into
|
||||
@type ros_config: L{ROSLaunchConfig}
|
||||
@param core: if True, load file using ROS core rules
|
||||
@type core: bool
|
||||
"""
|
||||
try:
|
||||
if verbose:
|
||||
print("... loading XML")
|
||||
try:
|
||||
if hasattr(xml_text,'encode') and isinstance(xml_text, unicode):
|
||||
# #3799: xml_text comes in a unicode object, which
|
||||
# #fails since XML text is expected to be encoded.
|
||||
# that's why force encoding to utf-8 here (make sure XML header is utf-8)
|
||||
xml_text = xml_text.encode('utf-8')
|
||||
except NameError:
|
||||
pass
|
||||
root = parseString(xml_text).getElementsByTagName('launch')
|
||||
except Exception as e:
|
||||
logging.getLogger('roslaunch').error("Invalid roslaunch XML syntax:\nstring[%s]\ntraceback[%s]"%(xml_text, traceback.format_exc()))
|
||||
raise XmlParseException("Invalid roslaunch XML syntax: %s"%e)
|
||||
|
||||
if len(root) != 1:
|
||||
raise XmlParseException("Invalid roslaunch XML syntax: no root <launch> tag")
|
||||
self._load_launch(root[0], ros_config, core, filename='string', verbose=verbose)
|
||||
2
thirdparty/ros/ros_comm/tools/roslaunch/test/dump-params.yaml
vendored
Normal file
2
thirdparty/ros/ros_comm/tools/roslaunch/test/dump-params.yaml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
string1: bar
|
||||
dict1: { head: 1, shoulders: 2, knees: 3, toes: 4}
|
||||
22
thirdparty/ros/ros_comm/tools/roslaunch/test/params.yaml
vendored
Normal file
22
thirdparty/ros/ros_comm/tools/roslaunch/test/params.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
string1: bar
|
||||
string2: !!str 10
|
||||
preformattedtext: |
|
||||
This is the first line
|
||||
This is the second line
|
||||
Line breaks are preserved
|
||||
Indentation is stripped
|
||||
list1:
|
||||
- head
|
||||
- shoulders
|
||||
- knees
|
||||
- toes
|
||||
list2: [1, 1, 2, 3, 5, 8]
|
||||
dict1: { head: 1, shoulders: 2, knees: 3, toes: 4}
|
||||
integer1: 1
|
||||
integer2: 2
|
||||
float1: 3.14159
|
||||
float2: 1.2345e+3
|
||||
robots:
|
||||
childparam: a child namespace parameter
|
||||
child:
|
||||
grandchildparam: a grandchild namespace param
|
||||
0
thirdparty/ros/ros_comm/tools/roslaunch/test/params_empty1.yaml
vendored
Normal file
0
thirdparty/ros/ros_comm/tools/roslaunch/test/params_empty1.yaml
vendored
Normal file
3
thirdparty/ros/ros_comm/tools/roslaunch/test/params_empty2.yaml
vendored
Normal file
3
thirdparty/ros/ros_comm/tools/roslaunch/test/params_empty2.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
1
thirdparty/ros/ros_comm/tools/roslaunch/test/params_subst.yaml
vendored
Normal file
1
thirdparty/ros/ros_comm/tools/roslaunch/test/params_subst.yaml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
string1: $(anon foo)
|
||||
0
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/__init__.py
vendored
Normal file
0
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/__init__.py
vendored
Normal file
201
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_core.py
vendored
Normal file
201
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_core.py
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env python
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
NAME = 'test_core'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import rospkg
|
||||
|
||||
_lastmsg = None
|
||||
def printlog_cb(msg):
|
||||
global _lastmsg
|
||||
_lastmsg = msg
|
||||
def printlog_cb_exception(msg):
|
||||
raise Exception(msg)
|
||||
|
||||
## Test roslaunch.core
|
||||
class TestCore(unittest.TestCase):
|
||||
def test_xml_escape(self):
|
||||
from roslaunch.core import _xml_escape
|
||||
self.assertEquals('', _xml_escape(''))
|
||||
self.assertEquals(' ', _xml_escape(' '))
|
||||
self.assertEquals('"', _xml_escape('"'))
|
||||
self.assertEquals('"hello world"', _xml_escape('"hello world"'))
|
||||
self.assertEquals('>', _xml_escape('>'))
|
||||
self.assertEquals('<', _xml_escape('<'))
|
||||
self.assertEquals('&', _xml_escape('&'))
|
||||
self.assertEquals('&amp;', _xml_escape('&'))
|
||||
self.assertEquals('&"><"', _xml_escape('&"><"'))
|
||||
|
||||
|
||||
def test_child_mode(self):
|
||||
from roslaunch.core import is_child_mode, set_child_mode
|
||||
set_child_mode(False)
|
||||
self.failIf(is_child_mode())
|
||||
set_child_mode(True)
|
||||
self.assert_(is_child_mode())
|
||||
set_child_mode(False)
|
||||
self.failIf(is_child_mode())
|
||||
|
||||
def test_printlog(self):
|
||||
from roslaunch.core import add_printlog_handler, add_printerrlog_handler, printlog, printlog_bold, printerrlog
|
||||
add_printlog_handler(printlog_cb)
|
||||
add_printlog_handler(printlog_cb_exception)
|
||||
add_printerrlog_handler(printlog_cb)
|
||||
add_printerrlog_handler(printlog_cb_exception)
|
||||
|
||||
#can't really test functionality, just make sure it doesn't crash
|
||||
global _lastmsg
|
||||
_lastmsg = None
|
||||
printlog('foo')
|
||||
self.assertEquals('foo', _lastmsg)
|
||||
printlog_bold('bar')
|
||||
self.assertEquals('bar', _lastmsg)
|
||||
printerrlog('baz')
|
||||
self.assertEquals('baz', _lastmsg)
|
||||
|
||||
def test_setup_env(self):
|
||||
from roslaunch.core import setup_env, Node, Machine
|
||||
master_uri = 'http://masteruri:1234'
|
||||
|
||||
n = Node('nodepkg','nodetype')
|
||||
m = Machine('name1', '1.2.3.4')
|
||||
d = setup_env(n, m, master_uri)
|
||||
self.assertEquals(d['ROS_MASTER_URI'], master_uri)
|
||||
|
||||
m = Machine('name1', '1.2.3.4')
|
||||
d = setup_env(n, m, master_uri)
|
||||
val = os.environ['ROS_ROOT']
|
||||
self.assertEquals(d['ROS_ROOT'], val)
|
||||
|
||||
# test ROS_NAMESPACE
|
||||
# test stripping
|
||||
n = Node('nodepkg','nodetype', namespace="/ns2/")
|
||||
d = setup_env(n, m, master_uri)
|
||||
self.assertEquals(d['ROS_NAMESPACE'], "/ns2")
|
||||
|
||||
# test node.env_args
|
||||
n = Node('nodepkg','nodetype', env_args=[('NENV1', 'val1'), ('NENV2', 'val2'), ('ROS_ROOT', '/new/root')])
|
||||
d = setup_env(n, m, master_uri)
|
||||
self.assertEquals(d['ROS_ROOT'], "/new/root")
|
||||
self.assertEquals(d['NENV1'], "val1")
|
||||
self.assertEquals(d['NENV2'], "val2")
|
||||
|
||||
|
||||
def test_local_machine(self):
|
||||
try:
|
||||
env_copy = os.environ.copy()
|
||||
|
||||
from roslaunch.core import local_machine
|
||||
lm = local_machine()
|
||||
self.failIf(lm is None)
|
||||
#singleton
|
||||
self.assert_(lm == local_machine())
|
||||
|
||||
self.assertEquals(lm.name, '')
|
||||
self.assertEquals(lm.assignable, True)
|
||||
self.assertEquals(lm.env_loader, None)
|
||||
self.assertEquals(lm.user, None)
|
||||
self.assertEquals(lm.password, None)
|
||||
finally:
|
||||
os.environ = env_copy
|
||||
|
||||
|
||||
def test_Master(self):
|
||||
from roslaunch.core import Master
|
||||
old_env = os.environ.get('ROS_MASTER_URI', None)
|
||||
try:
|
||||
os.environ['ROS_MASTER_URI'] = 'http://foo:789'
|
||||
m = Master()
|
||||
self.assertEquals(m.type, Master.ROSMASTER)
|
||||
self.assertEquals(m.uri, 'http://foo:789')
|
||||
self.assertEquals(m, m)
|
||||
self.assertEquals(m, Master())
|
||||
|
||||
m = Master(Master.ROSMASTER, 'http://foo:1234')
|
||||
self.assertEquals(m.type, Master.ROSMASTER)
|
||||
self.assertEquals(m.uri, 'http://foo:1234')
|
||||
self.assertEquals(m, m)
|
||||
self.assertEquals(m, Master(Master.ROSMASTER, 'http://foo:1234'))
|
||||
|
||||
try:
|
||||
from xmlrpc.client import ServerProxy
|
||||
except ImportError:
|
||||
from xmlrpclib import ServerProxy
|
||||
self.assert_(isinstance(m.get(), ServerProxy))
|
||||
m.uri = 'http://foo:567'
|
||||
self.assertEquals(567, m.get_port())
|
||||
self.failIf(m.is_running())
|
||||
|
||||
finally:
|
||||
if old_env is None:
|
||||
del os.environ['ROS_MASTER_URI']
|
||||
else:
|
||||
os.environ['ROS_MASTER_URI'] = old_env
|
||||
|
||||
def test_Executable(self):
|
||||
from roslaunch.core import Executable, PHASE_SETUP, PHASE_RUN, PHASE_TEARDOWN
|
||||
|
||||
e = Executable('ls', ['-alF'])
|
||||
self.assertEquals('ls', e.command)
|
||||
self.assertEquals(['-alF'], e.args)
|
||||
self.assertEquals(PHASE_RUN, e.phase)
|
||||
self.assertEquals('ls -alF', str(e))
|
||||
self.assertEquals('ls -alF', repr(e))
|
||||
|
||||
e = Executable('ls', ['-alF', 'b*'], PHASE_TEARDOWN)
|
||||
self.assertEquals('ls', e.command)
|
||||
self.assertEquals(['-alF', 'b*'], e.args)
|
||||
self.assertEquals(PHASE_TEARDOWN, e.phase)
|
||||
self.assertEquals('ls -alF b*', str(e))
|
||||
self.assertEquals('ls -alF b*', repr(e))
|
||||
|
||||
def test_RosbinExecutable(self):
|
||||
from roslaunch.core import RosbinExecutable, PHASE_SETUP, PHASE_RUN, PHASE_TEARDOWN
|
||||
|
||||
e = RosbinExecutable('ls', ['-alF'])
|
||||
self.assertEquals('ls', e.command)
|
||||
self.assertEquals(['-alF'], e.args)
|
||||
self.assertEquals(PHASE_RUN, e.phase)
|
||||
self.assertEquals('ros/bin/ls -alF', str(e))
|
||||
self.assertEquals('ros/bin/ls -alF', repr(e))
|
||||
|
||||
e = RosbinExecutable('ls', ['-alF', 'b*'], PHASE_TEARDOWN)
|
||||
self.assertEquals('ls', e.command)
|
||||
self.assertEquals(['-alF', 'b*'], e.args)
|
||||
self.assertEquals(PHASE_TEARDOWN, e.phase)
|
||||
self.assertEquals('ros/bin/ls -alF b*', str(e))
|
||||
self.assertEquals('ros/bin/ls -alF b*', repr(e))
|
||||
280
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_nodeprocess.py
vendored
Normal file
280
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_nodeprocess.py
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
#!/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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import rospkg
|
||||
import roslib.packages
|
||||
import logging
|
||||
logging.getLogger('roslaunch').setLevel(logging.CRITICAL)
|
||||
|
||||
## Test roslaunch.nodeprocess
|
||||
class TestNodeprocess(unittest.TestCase):
|
||||
|
||||
def test_create_master_process(self):
|
||||
from roslaunch.core import Node, Machine, Master, RLException
|
||||
from roslaunch.nodeprocess import create_master_process, LocalProcess
|
||||
|
||||
ros_root = '/ros/root'
|
||||
port = 1234
|
||||
type = Master.ROSMASTER
|
||||
run_id = 'foo'
|
||||
|
||||
# test invalid params
|
||||
try:
|
||||
create_master_process(run_id, type, ros_root, -1)
|
||||
self.fail("shoud have thrown RLException")
|
||||
except RLException: pass
|
||||
try:
|
||||
create_master_process(run_id, type, ros_root, 10000000)
|
||||
self.fail("shoud have thrown RLException")
|
||||
except RLException: pass
|
||||
try:
|
||||
create_master_process(run_id, 'foo', ros_root, port)
|
||||
self.fail("shoud have thrown RLException")
|
||||
except RLException: pass
|
||||
|
||||
# test valid params
|
||||
p = create_master_process(run_id, type, ros_root, port)
|
||||
self.assert_(isinstance(p, LocalProcess))
|
||||
self.assertEquals(p.args[0], 'rosmaster')
|
||||
idx = p.args.index('-p')
|
||||
self.failIf(idx < 1)
|
||||
self.assertEquals(p.args[idx+1], str(port))
|
||||
self.assert_('--core' in p.args)
|
||||
|
||||
self.assertEquals(p.package, 'rosmaster')
|
||||
p = create_master_process(run_id, type, ros_root, port)
|
||||
|
||||
# TODO: have to think more as to the correct environment for the master process
|
||||
|
||||
|
||||
def test_create_node_process(self):
|
||||
from roslaunch.core import Node, Machine, RLException
|
||||
from roslaunch.node_args import NodeParamsException
|
||||
from roslaunch.nodeprocess import create_node_process, LocalProcess
|
||||
# have to use real ROS configuration for these tests
|
||||
ros_root = os.environ['ROS_ROOT']
|
||||
rpp = os.environ.get('ROS_PACKAGE_PATH', None)
|
||||
master_uri = 'http://masteruri:1234'
|
||||
m = Machine('name1', ros_root, rpp, '1.2.3.4')
|
||||
|
||||
run_id = 'id'
|
||||
|
||||
# test invalid params
|
||||
n = Node('not_a_real_package','not_a_node')
|
||||
n.machine = m
|
||||
n.name = 'foo'
|
||||
try: # should fail b/c node cannot be found
|
||||
create_node_process(run_id, n, master_uri)
|
||||
self.fail("should have failed")
|
||||
except NodeParamsException:
|
||||
pass
|
||||
|
||||
# have to specify a real node
|
||||
n = Node('rospy','talker.py')
|
||||
n.machine = m
|
||||
try: # should fail b/c n.name is not set
|
||||
create_node_process(run_id, n, master_uri)
|
||||
self.fail("should have failed")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# have to specify a real node
|
||||
n = Node('rospy','talker.py')
|
||||
|
||||
n.machine = None
|
||||
n.name = 'talker'
|
||||
try: # should fail b/c n.machine is not set
|
||||
create_node_process(run_id, n, master_uri)
|
||||
self.fail("should have failed")
|
||||
except RLException:
|
||||
pass
|
||||
|
||||
# basic integration test
|
||||
n.machine = m
|
||||
p = create_node_process(run_id, n, master_uri)
|
||||
self.assert_(isinstance(p, LocalProcess))
|
||||
|
||||
# repeat some setup_local_process_env tests
|
||||
d = p.env
|
||||
self.assertEquals(d['ROS_MASTER_URI'], master_uri)
|
||||
self.assertEquals(d['ROS_ROOT'], ros_root)
|
||||
self.assertEquals(d['PYTHONPATH'], os.environ['PYTHONPATH'])
|
||||
if rpp:
|
||||
self.assertEquals(d['ROS_PACKAGE_PATH'], rpp)
|
||||
for k in ['ROS_IP', 'ROS_NAMESPACE']:
|
||||
if k in d:
|
||||
self.fail('%s should not be set: %s'%(k,d[k]))
|
||||
|
||||
# test package and name
|
||||
self.assertEquals(p.package, 'rospy')
|
||||
# - no 'correct' full answer here
|
||||
self.assert_(p.name.startswith('talker'), p.name)
|
||||
|
||||
# test log_output
|
||||
n.output = 'log'
|
||||
self.assert_(create_node_process(run_id, n, master_uri).log_output)
|
||||
n.output = 'screen'
|
||||
self.failIf(create_node_process(run_id, n, master_uri).log_output)
|
||||
|
||||
# test respawn
|
||||
n.respawn = True
|
||||
self.assert_(create_node_process(run_id, n, master_uri).respawn)
|
||||
n.respawn = False
|
||||
self.failIf(create_node_process(run_id, n, master_uri).respawn)
|
||||
|
||||
# test cwd
|
||||
n.cwd = None
|
||||
self.assertEquals(create_node_process(run_id, n, master_uri).cwd, None)
|
||||
n.cwd = 'ros-root'
|
||||
self.assertEquals(create_node_process(run_id, n, master_uri).cwd, 'ros-root')
|
||||
n.cwd = 'node'
|
||||
self.assertEquals(create_node_process(run_id, n, master_uri).cwd, 'node')
|
||||
|
||||
# test args
|
||||
|
||||
# - simplest test (no args)
|
||||
n.args = ''
|
||||
p = create_node_process(run_id, n, master_uri)
|
||||
# - the first arg should be the path to the node executable
|
||||
rospack = rospkg.RosPack()
|
||||
cmd = roslib.packages.find_node('rospy', 'talker.py', rospack)[0]
|
||||
self.assertEquals(p.args[0], cmd)
|
||||
|
||||
# - test basic args
|
||||
n.args = "arg1 arg2 arg3"
|
||||
p = create_node_process(run_id, n, master_uri)
|
||||
self.assertEquals(p.args[0], cmd)
|
||||
for a in "arg1 arg2 arg3".split():
|
||||
self.assert_(a in p.args)
|
||||
|
||||
# - test remap args
|
||||
n.remap_args = [('KEY1', 'VAL1'), ('KEY2', 'VAL2')]
|
||||
p = create_node_process(run_id, n, master_uri)
|
||||
self.assert_('KEY1:=VAL1' in p.args)
|
||||
self.assert_('KEY2:=VAL2' in p.args)
|
||||
|
||||
# - test __name
|
||||
n = Node('rospy','talker.py')
|
||||
n.name = 'fooname'
|
||||
n.machine = m
|
||||
self.assert_('__name:=fooname' in create_node_process(run_id, n, master_uri).args)
|
||||
|
||||
# - test substitution args
|
||||
os.environ['SUB_TEST'] = 'subtest'
|
||||
os.environ['SUB_TEST2'] = 'subtest2'
|
||||
n.args = 'foo $(env SUB_TEST) $(env SUB_TEST2)'
|
||||
p = create_node_process(run_id, n, master_uri)
|
||||
self.failIf('SUB_TEST' in p.args)
|
||||
self.assert_('foo' in p.args)
|
||||
self.assert_('subtest' in p.args)
|
||||
self.assert_('subtest2' in p.args)
|
||||
|
||||
def test__cleanup_args(self):
|
||||
# #1595
|
||||
from roslaunch.nodeprocess import _cleanup_remappings
|
||||
args = [
|
||||
'ROS_PACKAGE_PATH=foo',
|
||||
'/home/foo/monitor',
|
||||
'__log:=/home/pr2/21851/ros-0.7.2/log/8d769688-897d-11de-8e93-00238bdfe0ab/monitor-3.log',
|
||||
'__name:=bar',
|
||||
'__log:=/home/pr2/21851/ros-0.7.2/log/8d769688-897d-11de-8e93-00238bdfe0ab/monitor-3.log',
|
||||
'topic:=topic2',
|
||||
'__log:=/home/pr2/21851/ros-0.7.2/log/8d769688-897d-11de-8e93-00238bdfe0ab/monitor-3.log',
|
||||
'__log:=/home/pr2/21851/ros-0.7.2/log/8d769688-897d-11de-8e93-00238bdfe0ab/monitor-3.log',
|
||||
'__log:=/home/pr2/21851/ros-0.7.2/log/8d769688-897d-11de-8e93-00238bdfe0ab/monitor-3.log',
|
||||
'__log:=/home/pr2/21851/ros-0.7.2/log/8d769688-897d-11de-8e93-00238bdfe0ab/monitor-3.log',
|
||||
'__log:=/home/pr2/21851/ros-0.7.2/log/8d769688-897d-11de-8e93-00238bdfe0ab/monitor-3.log']
|
||||
clean_args = [
|
||||
'ROS_PACKAGE_PATH=foo',
|
||||
'/home/foo/monitor',
|
||||
'__name:=bar',
|
||||
'topic:=topic2']
|
||||
|
||||
self.assertEquals([], _cleanup_remappings([], '__log:='))
|
||||
self.assertEquals(clean_args, _cleanup_remappings(args, '__log:='))
|
||||
self.assertEquals(clean_args, _cleanup_remappings(clean_args, '__log:='))
|
||||
self.assertEquals(args, _cleanup_remappings(args, '_foo'))
|
||||
|
||||
def test__next_counter(self):
|
||||
from roslaunch.nodeprocess import _next_counter
|
||||
x = _next_counter()
|
||||
y = _next_counter()
|
||||
self.assert_(x +1 == y)
|
||||
self.assert_(x > 0)
|
||||
|
||||
def test_create_master_process2(self):
|
||||
# accidentally wrote two versions of this, need to merge
|
||||
from roslaunch.core import Master, RLException
|
||||
import rospkg
|
||||
from roslaunch.nodeprocess import create_master_process
|
||||
|
||||
ros_root = rospkg.get_ros_root()
|
||||
|
||||
# test failures
|
||||
failed = False
|
||||
try:
|
||||
create_master_process('runid-unittest', Master.ROSMASTER, rospkg.get_ros_root(), 0)
|
||||
failed = True
|
||||
except RLException: pass
|
||||
self.failIf(failed, "invalid port should have triggered error")
|
||||
|
||||
# test success with ROSMASTER
|
||||
m1 = create_master_process('runid-unittest', Master.ROSMASTER, ros_root, 1234)
|
||||
self.assertEquals('runid-unittest', m1.run_id)
|
||||
self.failIf(m1.started)
|
||||
self.failIf(m1.stopped)
|
||||
self.assertEquals(None, m1.cwd)
|
||||
self.assertEquals('master', m1.name)
|
||||
master_p = 'rosmaster'
|
||||
self.assert_(master_p in m1.args)
|
||||
# - it should have the default environment
|
||||
self.assertEquals(os.environ, m1.env)
|
||||
# - check args
|
||||
self.assert_('--core' in m1.args)
|
||||
# - make sure port arguent is correct
|
||||
idx = m1.args.index('-p')
|
||||
self.assertEquals('1234', m1.args[idx+1])
|
||||
|
||||
# test port argument
|
||||
m2 = create_master_process('runid-unittest', Master.ROSMASTER, ros_root, 1234)
|
||||
self.assertEquals('runid-unittest', m2.run_id)
|
||||
|
||||
# test ros_root argument
|
||||
m3 = create_master_process('runid-unittest', Master.ROSMASTER, ros_root, 1234)
|
||||
self.assertEquals('runid-unittest', m3.run_id)
|
||||
master_p = 'rosmaster'
|
||||
self.assert_(master_p in m3.args)
|
||||
190
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_child.py
vendored
Normal file
190
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_child.py
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
import os, sys, unittest
|
||||
import time
|
||||
|
||||
import rosgraph
|
||||
|
||||
import roslaunch.child
|
||||
import roslaunch.server
|
||||
|
||||
## Fake RemoteProcess object
|
||||
class ChildProcessMock(roslaunch.server.ChildROSLaunchProcess):
|
||||
def __init__(self, name, args=[], env={}):
|
||||
super(ChildProcessMock, self).__init__(name, args, env)
|
||||
self.stopped = False
|
||||
def stop(self):
|
||||
self.stopped = True
|
||||
|
||||
## Fake ProcessMonitor object
|
||||
class ProcessMonitorMock(object):
|
||||
def __init__(self):
|
||||
self.core_procs = []
|
||||
self.procs = []
|
||||
self.listeners = []
|
||||
|
||||
def join(self, timeout=0):
|
||||
pass
|
||||
|
||||
def add_process_listener(self, l):
|
||||
self.listeners.append(l)
|
||||
|
||||
def register(self, p):
|
||||
self.procs.append(p)
|
||||
|
||||
def register_core_proc(self, p):
|
||||
self.core_procs.append(p)
|
||||
|
||||
def registrations_complete(self):
|
||||
pass
|
||||
|
||||
def unregister(self, p):
|
||||
self.procs.remove(p)
|
||||
|
||||
def has_process(self, name):
|
||||
return len([p for p in self.procs if p.name == name]) > 0
|
||||
|
||||
def get_process(self, name):
|
||||
val = [p for p in self.procs if p.name == name]
|
||||
if val:
|
||||
return val[0]
|
||||
return None
|
||||
|
||||
def has_main_thread_jobs(self):
|
||||
return False
|
||||
|
||||
def do_main_thread_jobs(self):
|
||||
pass
|
||||
|
||||
def kill_process(self, name):
|
||||
pass
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def get_active_names(self):
|
||||
return [p.name for p in self.procs]
|
||||
|
||||
def get_process_names_with_spawn_count(self):
|
||||
actives = [(p.name, p.spawn_count) for p in self.procs]
|
||||
deads = []
|
||||
return [actives, deads]
|
||||
|
||||
def mainthread_spin_once(self):
|
||||
pass
|
||||
|
||||
def mainthread_spin(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
## Test roslaunch.server
|
||||
class TestRoslaunchChild(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.pmon = ProcessMonitorMock()
|
||||
try:
|
||||
# if there is a core up, we have to use its run id
|
||||
m = rosgraph.Master('/roslaunch')
|
||||
self.run_id = m.getParam('/run_id')
|
||||
except:
|
||||
self.run_id = 'foo-%s'%time.time()
|
||||
|
||||
def test_roslaunchChild(self):
|
||||
# this is mainly a code coverage test to try and make sure that we don't
|
||||
# have any uninitialized references, etc...
|
||||
|
||||
from roslaunch.child import ROSLaunchChild
|
||||
|
||||
name = 'child-%s'%time.time()
|
||||
server_uri = 'http://unroutable:1234'
|
||||
c = ROSLaunchChild(self.run_id, name, server_uri)
|
||||
self.assertEquals(self.run_id, c.run_id)
|
||||
self.assertEquals(name, c.name)
|
||||
self.assertEquals(server_uri, c.server_uri)
|
||||
# - this check tests our assumption about c's process monitor field
|
||||
self.assertEquals(None, c.pm)
|
||||
self.assertEquals(None, c.child_server)
|
||||
|
||||
# should be a noop
|
||||
c.shutdown()
|
||||
|
||||
# create a new child to test _start_pm() and shutdown()
|
||||
c = ROSLaunchChild(self.run_id, name, server_uri)
|
||||
|
||||
# - test _start_pm and shutdown logic
|
||||
c._start_pm()
|
||||
self.assert_(c.pm is not None)
|
||||
c.shutdown()
|
||||
|
||||
# create a new child to test run() with a fake process
|
||||
# monitor. this requires an actual parent server to be running
|
||||
|
||||
import roslaunch.config
|
||||
server = roslaunch.server.ROSLaunchParentNode(roslaunch.config.ROSLaunchConfig(), self.pmon)
|
||||
# - register a fake child with the server so that it accepts registration from ROSLaunchChild
|
||||
server.add_child(name, ChildProcessMock('foo'))
|
||||
try:
|
||||
server.start()
|
||||
self.assert_(server.uri, "server URI did not initialize")
|
||||
|
||||
c = ROSLaunchChild(self.run_id, name, server.uri)
|
||||
c.pm = self.pmon
|
||||
# - run should speed through
|
||||
c.run()
|
||||
finally:
|
||||
server.shutdown('test done')
|
||||
|
||||
# one final test for code completness: raise an exception during run()
|
||||
c = ROSLaunchChild(self.run_id, name, server_uri)
|
||||
def bad():
|
||||
raise Exception('haha')
|
||||
# - violate some encapsulation here just to make sure the exception happens
|
||||
c._start_pm = bad
|
||||
try:
|
||||
# this isn't really a correctness test, this just manually
|
||||
# tests that the exception is logged
|
||||
c.run()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def kill_parent(p, delay=1.0):
|
||||
# delay execution so that whatever pmon method we're calling has time to enter
|
||||
import time
|
||||
time.sleep(delay)
|
||||
print("stopping parent")
|
||||
p.shutdown()
|
||||
|
||||
311
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_core.py
vendored
Normal file
311
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_core.py
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import rospkg
|
||||
try:
|
||||
from xmlrpc.client import MultiCall, ServerProxy
|
||||
except ImportError:
|
||||
from xmlrpclib import MultiCall, ServerProxy
|
||||
|
||||
|
||||
import roslaunch.core
|
||||
|
||||
def test_Executable():
|
||||
from roslaunch.core import Executable, PHASE_SETUP
|
||||
e = Executable('cmd', ('arg1', 'arg2'), PHASE_SETUP)
|
||||
assert e.command == 'cmd'
|
||||
assert e.args == ('arg1', 'arg2')
|
||||
assert e.phase == PHASE_SETUP
|
||||
assert 'cmd' in str(e)
|
||||
assert 'arg2' in str(e)
|
||||
assert 'cmd' in repr(e)
|
||||
assert 'arg2' in repr(e)
|
||||
|
||||
def test__xml_escape():
|
||||
# this is a really bad xml escaper
|
||||
from roslaunch.core import _xml_escape
|
||||
assert _xml_escape("&<foo>") == "&<foo>"
|
||||
|
||||
def test_RosbinExecutable():
|
||||
from roslaunch.core import RosbinExecutable, PHASE_SETUP, PHASE_RUN
|
||||
e = RosbinExecutable('cmd', ('arg1', 'arg2'), PHASE_SETUP)
|
||||
assert e.command == 'cmd'
|
||||
assert e.args == ('arg1', 'arg2')
|
||||
assert e.phase == PHASE_SETUP
|
||||
assert 'cmd' in str(e)
|
||||
assert 'arg2' in str(e)
|
||||
assert 'cmd' in repr(e)
|
||||
assert 'arg2' in repr(e)
|
||||
assert 'ros/bin' in str(e)
|
||||
assert 'ros/bin' in repr(e)
|
||||
|
||||
e = RosbinExecutable('cmd', ('arg1', 'arg2'))
|
||||
assert e.phase == PHASE_RUN
|
||||
|
||||
def test_generate_run_id():
|
||||
from roslaunch.core import generate_run_id
|
||||
assert generate_run_id()
|
||||
|
||||
def test_Node():
|
||||
from roslaunch.core import Node
|
||||
n = Node('package', 'node_type')
|
||||
assert n.package == 'package'
|
||||
assert n.type == 'node_type'
|
||||
assert n.xmltype() == 'node'
|
||||
assert n.xmlattrs() == [('pkg', 'package'), ('type', 'node_type'),
|
||||
('machine', None), ('ns', '/'), ('args', ''), ('output', None),
|
||||
('cwd', None), ('respawn', False), ('respawn_delay', 0.0),
|
||||
('name', None), ('launch-prefix', None), ('required', False)], \
|
||||
n.xmlattrs()
|
||||
assert n.output == None
|
||||
|
||||
#tripwire for now
|
||||
n.to_xml()
|
||||
val = n.to_remote_xml()
|
||||
assert 'machine' not in val
|
||||
|
||||
# test bad constructors
|
||||
try:
|
||||
n = Node('package', 'node_type', cwd='foo')
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
n = Node('package', 'node_type', output='foo')
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
n = Node('package', '')
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
n = Node('', 'node_type')
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
n = Node('package', 'node_type', name='ns/node')
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
n = Node('package', 'node_type', respawn=True, required=True)
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def test_Param():
|
||||
from roslaunch.core import Param
|
||||
p = Param('key/', 'value')
|
||||
|
||||
assert p.key == 'key'
|
||||
assert p.value == 'value'
|
||||
assert p == Param('key', 'value')
|
||||
assert p != Param('key2', 'value')
|
||||
assert p != 1
|
||||
|
||||
assert 'key' in str(p)
|
||||
assert 'key' in repr(p)
|
||||
assert 'value' in str(p)
|
||||
assert 'value' in repr(p)
|
||||
|
||||
def test_local_machine():
|
||||
from roslaunch.core import local_machine
|
||||
m = local_machine()
|
||||
assert m is local_machine()
|
||||
assert m == local_machine()
|
||||
assert m.env_loader == None
|
||||
assert m.name == ''
|
||||
assert m.address == 'localhost'
|
||||
assert m.ssh_port == 22
|
||||
|
||||
def test_Machine():
|
||||
from roslaunch.core import Machine
|
||||
m = Machine('foo', 'localhost')
|
||||
assert 'foo' in str(m)
|
||||
assert m.name == 'foo'
|
||||
assert m.env_loader == None
|
||||
assert m.assignable == True
|
||||
assert m == m
|
||||
assert not m.__eq__(1)
|
||||
assert not m.config_equals(1)
|
||||
assert m == Machine('foo', 'localhost')
|
||||
assert m.config_equals(Machine('foo', 'localhost'))
|
||||
assert m.config_key() == Machine('foo', 'localhost').config_key()
|
||||
assert m.config_equals(Machine('foo', 'localhost'))
|
||||
assert m.config_key() == Machine('foo', 'localhost', ssh_port=22).config_key()
|
||||
assert m.config_equals(Machine('foo', 'localhost', ssh_port=22))
|
||||
assert m.config_key() == Machine('foo', 'localhost', assignable=False).config_key()
|
||||
assert m.config_equals(Machine('foo', 'localhost', assignable=False))
|
||||
|
||||
# original test suite
|
||||
m = Machine('name1', '1.2.3.4')
|
||||
str(m), m.config_key() #test for error
|
||||
assert m == m
|
||||
assert m == Machine('name1', '1.2.3.4')
|
||||
# verify that config_equals is not tied to name or assignable, but is tied to other properties
|
||||
assert m.config_equals(Machine('name1b', '1.2.3.4'))
|
||||
assert m.config_equals(Machine('name1c', '1.2.3.4', assignable=False))
|
||||
assert not m.config_equals(Machine('name1f', '2.2.3.4'))
|
||||
|
||||
assert m.name == 'name1'
|
||||
assert m.address == '1.2.3.4'
|
||||
assert m.assignable == True
|
||||
assert m.ssh_port == 22
|
||||
assert m.env_loader == None
|
||||
for p in ['user', 'password']:
|
||||
assert getattr(m, p) is None
|
||||
|
||||
m = Machine('name2', '2.2.3.4', assignable=False)
|
||||
assert not m.assignable
|
||||
str(m), m.config_key() #test for error
|
||||
assert m == m
|
||||
assert m == Machine('name2', '2.2.3.4', assignable=False)
|
||||
assert m.config_equals(Machine('name2b', '2.2.3.4', assignable=False))
|
||||
assert m.config_equals(Machine('name2c', '2.2.3.4', assignable=True))
|
||||
|
||||
m = Machine('name3', '3.3.3.4')
|
||||
str(m) #test for error
|
||||
assert m == m
|
||||
assert m == Machine('name3', '3.3.3.4')
|
||||
assert m.config_equals(Machine('name3b', '3.3.3.4'))
|
||||
|
||||
m = Machine('name4', '4.4.3.4', user='user4')
|
||||
assert m.user == 'user4'
|
||||
str(m), m.config_key() #test for error
|
||||
assert m == m
|
||||
assert m == Machine('name4', '4.4.3.4', user='user4')
|
||||
assert m.config_equals(Machine('name4b', '4.4.3.4', user='user4'))
|
||||
assert not m.config_equals(Machine('name4b', '4.4.3.4', user='user4b'))
|
||||
|
||||
m = Machine('name5', '5.5.3.4', password='password5')
|
||||
assert m.password == 'password5'
|
||||
str(m), m.config_key() #test for error
|
||||
assert m == m
|
||||
assert m == Machine('name5', '5.5.3.4', password='password5')
|
||||
assert m.config_equals(Machine('name5b', '5.5.3.4', password='password5'))
|
||||
assert not m.config_equals(Machine('name5c', '5.5.3.4', password='password5c'))
|
||||
|
||||
m = Machine('name6', '6.6.3.4', ssh_port=23)
|
||||
assert m.ssh_port == 23
|
||||
str(m) #test for error
|
||||
m.config_key()
|
||||
assert m == m
|
||||
assert m == Machine('name6', '6.6.3.4', ssh_port=23)
|
||||
assert m.config_equals(Machine('name6b', '6.6.3.4', ssh_port=23))
|
||||
assert not m.config_equals(Machine('name6c', '6.6.3.4', ssh_port=24))
|
||||
|
||||
m = Machine('name6', '6.6.3.4', env_loader='/opt/ros/fuerte/setup.sh')
|
||||
assert m.env_loader == '/opt/ros/fuerte/setup.sh'
|
||||
str(m) #test for error
|
||||
m.config_key()
|
||||
assert m == m
|
||||
assert m == Machine('name6', '6.6.3.4', env_loader='/opt/ros/fuerte/setup.sh')
|
||||
assert m.config_equals(Machine('name6b', '6.6.3.4', env_loader='/opt/ros/fuerte/setup.sh'))
|
||||
assert not m.config_equals(Machine('name6c', '6.6.3.4', env_loader='/opt/ros/groovy/setup.sh'))
|
||||
|
||||
def test_Master():
|
||||
from roslaunch.core import Master
|
||||
# can't verify value of is_running for actual master
|
||||
m = Master(uri='http://localhost:11311')
|
||||
assert m .type == Master.ROSMASTER
|
||||
m.get_host() == 'localhost'
|
||||
m.get_port() == 11311
|
||||
assert m.is_running() in [True, False]
|
||||
assert isinstance(m.get(), ServerProxy)
|
||||
assert isinstance(m.get_multi(), MultiCall)
|
||||
|
||||
m = Master(uri='http://badhostname:11312')
|
||||
m.get_host() == 'badhostname'
|
||||
m.get_port() == 11312
|
||||
assert m.is_running() == False
|
||||
|
||||
|
||||
def test_Test():
|
||||
from roslaunch.core import Test, TEST_TIME_LIMIT_DEFAULT
|
||||
t = Test('test_name', 'package', 'node_type')
|
||||
assert t.xmltype() == 'test'
|
||||
assert t.xmlattrs() == [('pkg', 'package'), ('type', 'node_type'), ('machine', None), ('ns', '/'), ('args', ''), ('output', 'log'), ('cwd', None), ('name', None), ('launch-prefix', None), ('required', False), ('test-name', 'test_name')]
|
||||
|
||||
assert t.output == 'log'
|
||||
assert t.test_name == 'test_name'
|
||||
assert t.package == 'package'
|
||||
assert t.type == 'node_type'
|
||||
assert t.name == None
|
||||
assert t.namespace == '/'
|
||||
assert t.machine_name == None
|
||||
assert t.args == ''
|
||||
assert t.remap_args == [], t.remap_args
|
||||
assert t.env_args == []
|
||||
assert t.time_limit == TEST_TIME_LIMIT_DEFAULT
|
||||
assert t.cwd is None
|
||||
assert t.launch_prefix is None
|
||||
assert t.retry == 0
|
||||
assert t.filename == '<unknown>'
|
||||
|
||||
|
||||
t = Test('test_name', 'package', 'node_type', 'name',
|
||||
'/namespace', 'machine_name', 'arg1 arg2',
|
||||
remap_args=[('from', 'to')], env_args=[('ENV', 'val')], time_limit=1.0, cwd='ros_home',
|
||||
launch_prefix='foo', retry=1, filename="filename.txt")
|
||||
|
||||
xmlattrs = t.xmlattrs()
|
||||
assert ('time-limit', 1.0) in xmlattrs
|
||||
assert ('retry', '1') in xmlattrs
|
||||
|
||||
assert t.launch_prefix == 'foo'
|
||||
assert t.remap_args == [('from', 'to')]
|
||||
assert t.env_args == [('ENV', 'val')]
|
||||
assert t.name == 'name'
|
||||
assert t.namespace == '/namespace/', t.namespace
|
||||
assert t.machine_name == 'machine_name'
|
||||
assert t.args == 'arg1 arg2'
|
||||
assert t.filename == 'filename.txt'
|
||||
assert t.time_limit == 1.0
|
||||
assert t.cwd == 'ROS_HOME'
|
||||
assert t.retry == 1
|
||||
|
||||
try:
|
||||
t = Test('test_name', 'package', 'node_type', time_limit=-1.0)
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
t = Test('test_name', 'package', 'node_type', time_limit=True)
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
161
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_depends.py
vendored
Normal file
161
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_depends.py
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import rospkg
|
||||
|
||||
class Foo: pass
|
||||
|
||||
def test_RoslaunchDeps():
|
||||
from roslaunch.depends import RoslaunchDeps
|
||||
min_deps = RoslaunchDeps(nodes=[('rospy', 'talker.py')], pkgs=['rospy'])
|
||||
assert min_deps == min_deps
|
||||
assert min_deps == RoslaunchDeps(nodes=[('rospy', 'talker.py')], pkgs=['rospy'])
|
||||
assert not min_deps.__eq__(Foo())
|
||||
assert Foo() != min_deps
|
||||
assert min_deps != RoslaunchDeps(nodes=[('rospy', 'talker.py')])
|
||||
assert min_deps != RoslaunchDeps(pkgs=['rospy'])
|
||||
|
||||
assert 'talker.py' in repr(min_deps)
|
||||
assert 'talker.py' in str(min_deps)
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
from contextlib import contextmanager
|
||||
@contextmanager
|
||||
def fakestdout():
|
||||
realstdout = sys.stdout
|
||||
fakestdout = StringIO()
|
||||
sys.stdout = fakestdout
|
||||
yield fakestdout
|
||||
sys.stdout = realstdout
|
||||
|
||||
def test_roslaunch_deps_main():
|
||||
from roslaunch.depends import roslaunch_deps_main
|
||||
roslaunch_d = rospkg.RosPack().get_path('roslaunch')
|
||||
rosmaster_d = rospkg.RosPack().get_path('rosmaster')
|
||||
f = os.path.join(roslaunch_d, 'resources', 'example.launch')
|
||||
rosmaster_f = os.path.join(rosmaster_d, 'test', 'rosmaster.test')
|
||||
invalid_f = os.path.join(roslaunch_d, 'test', 'xml', 'invalid-xml.xml')
|
||||
not_f = os.path.join(roslaunch_d, 'test', 'xml', 'not-launch.xml')
|
||||
|
||||
with fakestdout() as b:
|
||||
roslaunch_deps_main(['roslaunch-deps', f])
|
||||
s = b.getvalue()
|
||||
assert s.strip() == 'rospy', "buffer value [%s]"%(s)
|
||||
with fakestdout() as b:
|
||||
roslaunch_deps_main(['roslaunch-deps', f, '--warn'])
|
||||
s = b.getvalue()
|
||||
assert s.strip() == """Dependencies:
|
||||
rospy
|
||||
|
||||
Missing declarations:
|
||||
roslaunch/manifest.xml:
|
||||
<depend package="rospy" />""", s
|
||||
|
||||
# tripwire, don't really care about exact verbose output
|
||||
with fakestdout() as b:
|
||||
roslaunch_deps_main(['roslaunch-deps', f, '--verbose'])
|
||||
|
||||
# try with no file
|
||||
with fakestdout() as b:
|
||||
try:
|
||||
roslaunch_deps_main(['roslaunch-deps'])
|
||||
assert False, "should have failed"
|
||||
except SystemExit:
|
||||
pass
|
||||
|
||||
# try with non-existent file
|
||||
with fakestdout() as b:
|
||||
try:
|
||||
roslaunch_deps_main(['roslaunch-deps', 'fakefile', '--verbose'])
|
||||
assert False, "should have failed"
|
||||
except SystemExit:
|
||||
pass
|
||||
|
||||
# try with bad file
|
||||
with fakestdout() as b:
|
||||
try:
|
||||
roslaunch_deps_main(['roslaunch-deps', invalid_f, '--verbose'])
|
||||
assert False, "should have failed: %s"%b.getvalue()
|
||||
except SystemExit:
|
||||
pass
|
||||
with fakestdout() as b:
|
||||
try:
|
||||
roslaunch_deps_main(['roslaunch-deps', not_f, '--verbose'])
|
||||
assert False, "should have failed: %s"%b.getvalue()
|
||||
except SystemExit:
|
||||
pass
|
||||
|
||||
# try with files from different pacakges
|
||||
with fakestdout() as b:
|
||||
try:
|
||||
roslaunch_deps_main(['roslaunch-deps', f, rosmaster_f, '--verbose'])
|
||||
assert False, "should have failed"
|
||||
except SystemExit:
|
||||
pass
|
||||
|
||||
def test_roslaunch_deps():
|
||||
from roslaunch.depends import roslaunch_deps, RoslaunchDeps
|
||||
example_d = os.path.join(rospkg.RosPack().get_path('roslaunch'), 'resources')
|
||||
|
||||
min_deps = RoslaunchDeps(nodes=[('rospy', 'talker.py')], pkgs=['rospy'])
|
||||
include_deps = RoslaunchDeps(nodes=[('rospy', 'talker.py'), ('rospy', 'listener.py')], pkgs=['rospy'])
|
||||
example_deps = RoslaunchDeps(nodes=[('rospy', 'talker.py'), ('rospy', 'listener.py')], pkgs=['rospy'],
|
||||
includes=[os.path.join(example_d, 'example-include.launch')])
|
||||
|
||||
example_file_deps = {
|
||||
os.path.join(example_d, 'example.launch') : example_deps,
|
||||
|
||||
os.path.join(example_d, 'example-include.launch') : include_deps,
|
||||
}
|
||||
example_min_file_deps = {
|
||||
os.path.join(example_d, 'example-min.launch') : min_deps,
|
||||
}
|
||||
r_missing = {'roslaunch': set(['rospy'])}
|
||||
tests = [
|
||||
([os.path.join(example_d, 'example-min.launch')], ('roslaunch', example_min_file_deps, r_missing)),
|
||||
|
||||
([os.path.join(example_d, 'example.launch')], ('roslaunch', example_file_deps, r_missing)),
|
||||
|
||||
]
|
||||
for files, results in tests:
|
||||
for v in [True, False]:
|
||||
base_pkg, file_deps, missing = roslaunch_deps(files, verbose=v)
|
||||
assert base_pkg == results[0]
|
||||
assert file_deps == results[1], "\n%s vs \n%s"%(file_deps, results[1])
|
||||
assert missing == results[2], "\n%s vs \n%s"%(missing, results[2])
|
||||
|
||||
108
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_dump_params.py
vendored
Normal file
108
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_dump_params.py
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2010, 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
import yaml
|
||||
|
||||
from subprocess import Popen, PIPE, check_call, call
|
||||
|
||||
class TestDumpParams(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_roslaunch(self):
|
||||
# network is initialized
|
||||
cmd = 'roslaunch'
|
||||
|
||||
# Smoke test for testing parameters
|
||||
p = Popen([cmd, '--dump-params', 'roslaunch', 'noop.launch'], stdout = PIPE)
|
||||
o, e = p.communicate()
|
||||
self.assert_(p.returncode == 0, "Return code nonzero for param dump! Code: %d" % (p.returncode))
|
||||
|
||||
self.assertEquals({'/noop': 'noop'}, yaml.load(o))
|
||||
|
||||
p = Popen([cmd, '--dump-params', 'roslaunch', 'test-dump-rosparam.launch'], stdout = PIPE)
|
||||
o, e = p.communicate()
|
||||
self.assert_(p.returncode == 0, "Return code nonzero for param dump! Code: %d" % (p.returncode))
|
||||
|
||||
val = {
|
||||
'/string1': 'bar',
|
||||
'/dict1/head': 1,
|
||||
'/dict1/shoulders': 2,
|
||||
'/dict1/knees': 3,
|
||||
'/dict1/toes': 4,
|
||||
|
||||
'/rosparam/string1': 'bar',
|
||||
'/rosparam/dict1/head': 1,
|
||||
'/rosparam/dict1/shoulders': 2,
|
||||
'/rosparam/dict1/knees': 3,
|
||||
'/rosparam/dict1/toes': 4,
|
||||
|
||||
'/node_rosparam/string1': 'bar',
|
||||
'/node_rosparam/dict1/head': 1,
|
||||
'/node_rosparam/dict1/shoulders': 2,
|
||||
'/node_rosparam/dict1/knees': 3,
|
||||
'/node_rosparam/dict1/toes': 4,
|
||||
'/node_rosparam/tilde1': 'foo',
|
||||
'/node_rosparam/local_param': 'baz',
|
||||
|
||||
'/node_rosparam2/tilde1': 'foo',
|
||||
|
||||
'/inline_str': 'value1',
|
||||
'/inline_list': [1, 2, 3, 4],
|
||||
'/inline_dict/key1': 'value1',
|
||||
'/inline_dict/key2': 'value2',
|
||||
|
||||
'/inline_dict2/key3': 'value3',
|
||||
'/inline_dict2/key4': 'value4',
|
||||
|
||||
'/override/key1': 'override1',
|
||||
'/override/key2': 'value2',
|
||||
'/noparam1': 'value1',
|
||||
'/noparam2': 'value2',
|
||||
}
|
||||
output_val = yaml.load(o)
|
||||
if not val == output_val:
|
||||
for k, v in val.items():
|
||||
if k not in output_val:
|
||||
self.fail("key [%s] not in output: %s"%(k, output_val))
|
||||
elif v != output_val[k]:
|
||||
self.fail("key [%s] value [%s] does not match output: %s"%(k, v, output_val[k]))
|
||||
self.assertEquals(val, output_val)
|
||||
for k in ('/node_rosparam/tilde2', '/node_rosparam2/tilde2', '/node_rosparam2/local_param'):
|
||||
if k in output_val:
|
||||
self.fail("key [%s] should not be in output: %s"%(k, output_val))
|
||||
179
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_launch.py
vendored
Normal file
179
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_launch.py
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
## Test roslaunch.launch
|
||||
class TestRoslaunchLaunch(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.printerrlog_msg = None
|
||||
|
||||
def my_printerrlog(self, msg):
|
||||
self.printerrlog_msg = msg
|
||||
|
||||
def test_validate_master_launch(self):
|
||||
import roslaunch.launch
|
||||
from roslaunch.core import Master
|
||||
from roslaunch.launch import validate_master_launch
|
||||
roslaunch.launch.printerrlog = self.my_printerrlog
|
||||
|
||||
# Good configurations
|
||||
os.environ['ROS_MASTER_URI'] = 'http://localhost:11311'
|
||||
m = Master(uri='http://localhost:11311')
|
||||
validate_master_launch(m, True)
|
||||
self.assertEquals(None, self.printerrlog_msg)
|
||||
validate_master_launch(m, False)
|
||||
self.assertEquals(None, self.printerrlog_msg)
|
||||
|
||||
# roscore with mismatched port in environment
|
||||
os.environ['ROS_MASTER_URI'] = 'http://localhost:11312'
|
||||
validate_master_launch(m, True)
|
||||
self.assert_('port' in self.printerrlog_msg)
|
||||
self.printerrlog_msg = None
|
||||
|
||||
# roscore with mismatched hostname in environment
|
||||
os.environ['ROS_MASTER_URI'] = 'http://fake:11311'
|
||||
validate_master_launch(m, True)
|
||||
self.assert_('host' in self.printerrlog_msg)
|
||||
self.printerrlog_msg = None
|
||||
|
||||
# roslaunch with remote master that cannot be contacted
|
||||
os.environ['ROS_MASTER_URI'] = 'http://fake:11311'
|
||||
self.assertEquals(None, self.printerrlog_msg)
|
||||
|
||||
# environment doesn't matter for remaining tests
|
||||
os.environ['ROS_MASTER_URI'] = 'http://localhost:11311'
|
||||
m = Master(uri="http://fake:11311")
|
||||
|
||||
# roscore with hostname that points elsewhere, warn user. This
|
||||
# generally could only happen if the user has a bad local host
|
||||
# config.
|
||||
validate_master_launch(m, True)
|
||||
self.assert_("WARNING" in self.printerrlog_msg)
|
||||
self.printerrlog_msg = None
|
||||
|
||||
# roscore with host that is not ours
|
||||
m = Master(uri="http://willowgarage.com:11311")
|
||||
validate_master_launch(m, True)
|
||||
self.assert_("WARNING" in self.printerrlog_msg)
|
||||
self.printerrlog_msg = None
|
||||
|
||||
# roslaunch with remote master that is out of contact, fail
|
||||
try:
|
||||
validate_master_launch(m, False)
|
||||
self.fail("should not pass if remote master cannot be contacted")
|
||||
except roslaunch.RLException:
|
||||
pass
|
||||
|
||||
def test__unify_clear_params(self):
|
||||
from roslaunch.launch import _unify_clear_params
|
||||
self.assertEquals([], _unify_clear_params([]))
|
||||
for t in [['/foo'], ['/foo/'], ['/foo/', '/foo'],
|
||||
['/foo/', '/foo/'], ['/foo/', '/foo/bar', '/foo/'],
|
||||
['/foo/', '/foo/bar', '/foo/bar/baz']]:
|
||||
self.assertEquals(['/foo/'], _unify_clear_params(t))
|
||||
for t in [['/'], ['/', '/foo/'], ['/foo/', '/', '/baz', '/car/dog']]:
|
||||
self.assertEquals(['/'], _unify_clear_params(t))
|
||||
|
||||
self.assertEquals(['/foo/', '/bar/', '/baz/'], _unify_clear_params(['/foo', '/bar', '/baz']))
|
||||
self.assertEquals(['/foo/', '/bar/', '/baz/'], _unify_clear_params(['/foo', '/bar', '/baz', '/bar/delta', '/baz/foo']))
|
||||
self.assertEquals(['/foo/bar/'], _unify_clear_params(['/foo/bar', '/foo/bar/baz']))
|
||||
|
||||
|
||||
def test__hostname_to_rosname(self):
|
||||
from roslaunch.launch import _hostname_to_rosname
|
||||
self.assertEquals("host_ann", _hostname_to_rosname('ann'))
|
||||
self.assertEquals("host_ann", _hostname_to_rosname('ANN'))
|
||||
self.assertEquals("host_", _hostname_to_rosname(''))
|
||||
self.assertEquals("host_1", _hostname_to_rosname('1'))
|
||||
self.assertEquals("host__", _hostname_to_rosname('_'))
|
||||
self.assertEquals("host__", _hostname_to_rosname('-'))
|
||||
self.assertEquals("host_foo_laptop", _hostname_to_rosname('foo-laptop'))
|
||||
|
||||
def test_roslaunchListeners(self):
|
||||
import roslaunch.launch
|
||||
class L(roslaunch.launch.ROSLaunchListener):
|
||||
def process_died(self, process_name, exit_code):
|
||||
self.process_name = process_name
|
||||
self.exit_code = exit_code
|
||||
class LBad(roslaunch.launch.ROSLaunchListener):
|
||||
def process_died(self, process_name, exit_code):
|
||||
raise Exception("foo")
|
||||
|
||||
listeners = roslaunch.launch._ROSLaunchListeners()
|
||||
l1 = L()
|
||||
l2 = L()
|
||||
lbad = L()
|
||||
l3 = L()
|
||||
# test with no listeners
|
||||
listeners.process_died('p0', 0)
|
||||
# test with 1 listener
|
||||
listeners.add_process_listener(l1)
|
||||
listeners.process_died('p1', 1)
|
||||
self.assertEquals(l1.process_name, 'p1')
|
||||
self.assertEquals(l1.exit_code, 1)
|
||||
|
||||
# test with 2 listeners
|
||||
listeners.add_process_listener(l2)
|
||||
listeners.process_died('p2', 2)
|
||||
for l in [l1, l2]:
|
||||
self.assertEquals(l.process_name, 'p2')
|
||||
self.assertEquals(l.exit_code, 2)
|
||||
|
||||
listeners.add_process_listener(lbad)
|
||||
# make sure that this catches errors
|
||||
listeners.process_died('p3', 3)
|
||||
for l in [l1, l2]:
|
||||
self.assertEquals(l.process_name, 'p3')
|
||||
self.assertEquals(l.exit_code, 3)
|
||||
# also add a third listener to make sure that listeners continues after lbad throws
|
||||
listeners.add_process_listener(l3)
|
||||
listeners.process_died('p4', 4)
|
||||
for l in [l1, l2, l3]:
|
||||
self.assertEquals(l.process_name, 'p4')
|
||||
self.assertEquals(l.exit_code, 4)
|
||||
|
||||
# this is just to get coverage, it's an empty class
|
||||
def test_ROSRemoteRunnerIF():
|
||||
from roslaunch.launch import ROSRemoteRunnerIF
|
||||
r = ROSRemoteRunnerIF()
|
||||
r.setup()
|
||||
r.add_process_listener(1)
|
||||
r.launch_remote_nodes()
|
||||
|
||||
def test_ROSLaunchListener():
|
||||
from roslaunch.launch import ROSLaunchListener
|
||||
r = ROSLaunchListener()
|
||||
r.process_died(1, 2)
|
||||
76
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_list_files.py
vendored
Normal file
76
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_list_files.py
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2011, 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.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
import roslib.packages
|
||||
|
||||
from subprocess import Popen, PIPE, check_call, call
|
||||
|
||||
def get_test_path():
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'xml'))
|
||||
|
||||
class TestListFiles(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_list_files(self):
|
||||
cmd = 'roslaunch'
|
||||
|
||||
# check error behavior
|
||||
p = Popen([cmd, '--files'], stdout = PIPE)
|
||||
p.communicate()
|
||||
self.assert_(p.returncode != 0, "Should have failed w/o file argument. Code: %d" % (p.returncode))
|
||||
|
||||
d = get_test_path()
|
||||
|
||||
p = Popen([cmd, '--files', 'roslaunch', 'test-valid.xml'], stdout = PIPE)
|
||||
o = p.communicate()[0]
|
||||
o = o.decode()
|
||||
self.assert_(p.returncode == 0, "Return code nonzero for list files! Code: %d" % (p.returncode))
|
||||
self.assertEquals(os.path.realpath(os.path.join(d, 'test-valid.xml')), os.path.realpath(o.strip()))
|
||||
|
||||
print("check 1", o)
|
||||
|
||||
p = Popen([cmd, '--files', 'roslaunch', 'test-env.xml'], stdout = PIPE)
|
||||
o = p.communicate()[0]
|
||||
o = o.decode()
|
||||
self.assert_(p.returncode == 0, "Return code nonzero for list files! Code: %d" % (p.returncode))
|
||||
self.assertEquals(set([os.path.realpath(os.path.join(d, 'test-env.xml')), os.path.realpath(os.path.join(d, 'test-env-include.xml'))]),
|
||||
set([os.path.realpath(x.strip()) for x in o.split() if x.strip()]))
|
||||
|
||||
print("check 2", o)
|
||||
122
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_param_dump.py
vendored
Normal file
122
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_param_dump.py
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2012, 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
from contextlib import contextmanager
|
||||
@contextmanager
|
||||
def fakestdout():
|
||||
realstdout = sys.stdout
|
||||
fakestdout = StringIO()
|
||||
sys.stdout = fakestdout
|
||||
yield fakestdout
|
||||
sys.stdout = realstdout
|
||||
|
||||
import rospkg
|
||||
import logging
|
||||
|
||||
SAMPLE1 = """/rosparam_load/dict1/head: 1
|
||||
/rosparam_load/dict1/knees: 3
|
||||
/rosparam_load/dict1/shoulders: 2
|
||||
/rosparam_load/dict1/toes: 4
|
||||
/rosparam_load/integer1: 1
|
||||
/rosparam_load/integer2: 2
|
||||
/rosparam_load/list1: [head, shoulders, knees, toes]
|
||||
/rosparam_load/list2: [1, 1, 2, 3, 5, 8]
|
||||
/rosparam_load/preformattedtext: 'This is the first line
|
||||
|
||||
This is the second line
|
||||
|
||||
Line breaks are preserved
|
||||
|
||||
Indentation is stripped
|
||||
|
||||
'
|
||||
/rosparam_load/robots/child/grandchildparam: a grandchild namespace param
|
||||
/rosparam_load/robots/childparam: a child namespace parameter
|
||||
/rosparam_load/string1: bar
|
||||
/rosparam_load/string2: '10'"""
|
||||
|
||||
SAMPLE2 = """/load_ns/subns/dict1/head: 1
|
||||
/load_ns/subns/dict1/knees: 3
|
||||
/load_ns/subns/dict1/shoulders: 2
|
||||
/load_ns/subns/dict1/toes: 4
|
||||
/load_ns/subns/integer1: 1
|
||||
/load_ns/subns/integer2: 2
|
||||
/load_ns/subns/list1: [head, shoulders, knees, toes]
|
||||
/load_ns/subns/list2: [1, 1, 2, 3, 5, 8]
|
||||
/load_ns/subns/preformattedtext: 'This is the first line
|
||||
|
||||
This is the second line
|
||||
|
||||
Line breaks are preserved
|
||||
|
||||
Indentation is stripped
|
||||
|
||||
'
|
||||
/load_ns/subns/robots/child/grandchildparam: a grandchild namespace param
|
||||
/load_ns/subns/robots/childparam: a child namespace parameter
|
||||
/load_ns/subns/string1: bar
|
||||
/load_ns/subns/string2: '10'"""
|
||||
|
||||
|
||||
def test_dump_params():
|
||||
# normal entrypoint has logging configured
|
||||
logger = logging.getLogger('roslaunch').setLevel(logging.CRITICAL)
|
||||
from roslaunch.param_dump import dump_params
|
||||
roslaunch_d = rospkg.RosPack().get_path('roslaunch')
|
||||
test_d = os.path.join(roslaunch_d, 'test', 'xml')
|
||||
node_rosparam_f = os.path.join(test_d, 'test-node-rosparam-load.xml')
|
||||
with fakestdout() as b:
|
||||
assert dump_params([node_rosparam_f])
|
||||
s = b.getvalue().strip()
|
||||
# remove float vals as serialization is not stable
|
||||
s = '\n'.join([x for x in s.split('\n') if not 'float' in x])
|
||||
assert str(s) == str(SAMPLE1), "[%s]\nvs\n[%s]"%(s, SAMPLE1)
|
||||
node_rosparam_f = os.path.join(test_d, 'test-node-rosparam-load-ns.xml')
|
||||
with fakestdout() as b:
|
||||
assert dump_params([node_rosparam_f])
|
||||
s = b.getvalue().strip()
|
||||
# remove float vals as serialization is not stable
|
||||
s = '\n'.join([x for x in s.split('\n') if not 'float' in x])
|
||||
assert str(s) == str(SAMPLE2), "[%s]\nvs\n[%s]"%(s, SAMPLE2)
|
||||
|
||||
invalid_f = os.path.join(test_d, 'invalid-xml.xml')
|
||||
with fakestdout() as b:
|
||||
assert not dump_params([invalid_f])
|
||||
|
||||
244
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_parent.py
vendored
Normal file
244
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_parent.py
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import time
|
||||
|
||||
import rospkg
|
||||
import rosgraph.network
|
||||
import roslaunch.parent
|
||||
|
||||
import rosgraph
|
||||
master = rosgraph.Master('test_roslaunch_parent')
|
||||
def get_param(*args):
|
||||
return master.getParam(*args)
|
||||
|
||||
## Fake Process object
|
||||
class ProcessMock(roslaunch.pmon.Process):
|
||||
def __init__(self, package, name, args, env, respawn=False):
|
||||
super(ProcessMock, self).__init__(package, name, args, env, respawn)
|
||||
self.stopped = False
|
||||
def stop(self):
|
||||
self.stopped = True
|
||||
|
||||
## Fake ProcessMonitor object
|
||||
class ProcessMonitorMock(object):
|
||||
def __init__(self):
|
||||
self.core_procs = []
|
||||
self.procs = []
|
||||
self.listeners = []
|
||||
self.is_shutdown = False
|
||||
|
||||
def join(self, timeout=0):
|
||||
pass
|
||||
|
||||
def add_process_listener(self, l):
|
||||
self.listeners.append(l)
|
||||
|
||||
def register(self, p):
|
||||
self.procs.append(p)
|
||||
|
||||
def register_core_proc(self, p):
|
||||
self.core_procs.append(p)
|
||||
|
||||
def registrations_complete(self):
|
||||
pass
|
||||
|
||||
def unregister(self, p):
|
||||
self.procs.remove(p)
|
||||
|
||||
def has_process(self, name):
|
||||
return len([p for p in self.procs if p.name == name]) > 0
|
||||
|
||||
def get_process(self, name):
|
||||
val = [p for p in self.procs if p.name == name]
|
||||
if val:
|
||||
return val[0]
|
||||
return None
|
||||
|
||||
def has_main_thread_jobs(self):
|
||||
return False
|
||||
|
||||
def do_main_thread_jobs(self):
|
||||
pass
|
||||
|
||||
def kill_process(self, name):
|
||||
pass
|
||||
|
||||
def shutdown(self):
|
||||
self.is_shutdown = True
|
||||
|
||||
def get_active_names(self):
|
||||
return [p.name for p in self.procs]
|
||||
|
||||
def get_process_names_with_spawn_count(self):
|
||||
actives = [(p.name, p.spawn_count) for p in self.procs]
|
||||
deads = []
|
||||
return [actives, deads]
|
||||
|
||||
def mainthread_spin_once(self):
|
||||
pass
|
||||
|
||||
def mainthread_spin(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
## Test roslaunch.server
|
||||
class TestRoslaunchParent(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.pmon = ProcessMonitorMock()
|
||||
|
||||
def test_roslaunchParent(self):
|
||||
try:
|
||||
self._subroslaunchParent()
|
||||
finally:
|
||||
self.pmon.shutdown()
|
||||
|
||||
def _subroslaunchParent(self):
|
||||
from roslaunch.parent import ROSLaunchParent
|
||||
pmon = self.pmon
|
||||
try:
|
||||
# if there is a core up, we have to use its run id
|
||||
run_id = get_param('/run_id')
|
||||
except:
|
||||
run_id = 'test-rl-parent-%s'%time.time()
|
||||
name = 'foo-bob'
|
||||
server_uri = 'http://localhost:12345'
|
||||
|
||||
p = ROSLaunchParent(run_id, [], is_core = True, port=None, local_only=False)
|
||||
self.assertEquals(run_id, p.run_id)
|
||||
self.assertEquals(True, p.is_core)
|
||||
self.assertEquals(False, p.local_only)
|
||||
|
||||
rl_dir = rospkg.RosPack().get_path('roslaunch')
|
||||
rl_file = os.path.join(rl_dir, 'resources', 'example.launch')
|
||||
self.assert_(os.path.isfile(rl_file))
|
||||
|
||||
# validate load_config logic
|
||||
p = ROSLaunchParent(run_id, [rl_file], is_core = False, port=None, local_only=True)
|
||||
self.assertEquals(run_id, p.run_id)
|
||||
self.assertEquals(False, p.is_core)
|
||||
self.assertEquals(True, p.local_only)
|
||||
|
||||
self.assert_(p.config is None)
|
||||
p._load_config()
|
||||
self.assert_(p.config is not None)
|
||||
self.assert_(p.config.nodes)
|
||||
|
||||
# try again with port override
|
||||
p = ROSLaunchParent(run_id, [rl_file], is_core = False, port=11312, local_only=True)
|
||||
self.assertEquals(11312, p.port)
|
||||
self.assert_(p.config is None)
|
||||
p._load_config()
|
||||
# - make sure port got passed into master
|
||||
_, port = rosgraph.network.parse_http_host_and_port(p.config.master.uri)
|
||||
self.assertEquals(11312, port)
|
||||
|
||||
# try again with bad file
|
||||
p = ROSLaunchParent(run_id, ['non-existent-fake.launch'])
|
||||
self.assert_(p.config is None)
|
||||
try:
|
||||
p._load_config()
|
||||
self.fail("load config should have failed due to bad rl file")
|
||||
except roslaunch.core.RLException: pass
|
||||
|
||||
# try again with bad xml
|
||||
rl_dir = rospkg.RosPack().get_path('roslaunch')
|
||||
rl_file = os.path.join(rl_dir, 'test', 'xml', 'test-params-invalid-1.xml')
|
||||
self.assert_(os.path.isfile(rl_file))
|
||||
p = ROSLaunchParent(run_id, [rl_file])
|
||||
self.assert_(p.config is None)
|
||||
try:
|
||||
p._load_config()
|
||||
self.fail("load config should have failed due to bad rl file")
|
||||
except roslaunch.core.RLException: pass
|
||||
|
||||
# Mess around with internal repr to get code coverage on _init_runner/_init_remote
|
||||
p = ROSLaunchParent(run_id, [], is_core = False, port=None, local_only=True)
|
||||
# no config, _init_runner/_init_remote/_start_server should fail
|
||||
for m in ['_init_runner', '_init_remote', '_start_server']:
|
||||
try:
|
||||
getattr(p, m)()
|
||||
self.fail('should have raised')
|
||||
except roslaunch.core.RLException: pass
|
||||
|
||||
# - initialize p.config
|
||||
p.config = roslaunch.config.ROSLaunchConfig()
|
||||
|
||||
# no pm, _init_runner/_init_remote/_start_server should fail
|
||||
for m in ['_init_runner', '_init_remote', '_start_server']:
|
||||
try:
|
||||
getattr(p, m)()
|
||||
self.fail('should have raised')
|
||||
except roslaunch.core.RLException: pass
|
||||
|
||||
# - initialize p.pm
|
||||
p.pm = pmon
|
||||
|
||||
for m in ['_init_runner', '_init_remote']:
|
||||
try:
|
||||
getattr(p, m)()
|
||||
self.fail('should have raised')
|
||||
except roslaunch.core.RLException: pass
|
||||
|
||||
from roslaunch.server import ROSLaunchParentNode
|
||||
p.server = ROSLaunchParentNode(p.config, pmon)
|
||||
p._init_runner()
|
||||
# roslaunch runner should be initialized
|
||||
self.assert_(p.runner is not None)
|
||||
|
||||
# test _init_remote
|
||||
p.local_only = True
|
||||
p._init_remote()
|
||||
p.local_only = False
|
||||
# - this violates many abstractions to do this
|
||||
def ftrue():
|
||||
return True
|
||||
p.config.has_remote_nodes = ftrue
|
||||
p._init_remote()
|
||||
self.assert_(p.remote_runner is not None)
|
||||
|
||||
self.failIf(pmon.is_shutdown)
|
||||
p.shutdown()
|
||||
self.assert_(pmon.is_shutdown)
|
||||
|
||||
def kill_parent(p, delay=1.0):
|
||||
# delay execution so that whatever pmon method we're calling has time to enter
|
||||
time.sleep(delay)
|
||||
print("stopping parent")
|
||||
p.shutdown()
|
||||
|
||||
580
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_pmon.py
vendored
Normal file
580
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_pmon.py
vendored
Normal file
@@ -0,0 +1,580 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import time
|
||||
import threading
|
||||
|
||||
import roslaunch.server
|
||||
|
||||
## handy class for kill_pmon to check whether it was called
|
||||
class Marker(object):
|
||||
def __init__(self):
|
||||
self.marked = False
|
||||
def mark(self):
|
||||
self.marked = True
|
||||
|
||||
## Fake ProcessMonitor object
|
||||
class ProcessMonitorMock(object):
|
||||
def __init__(self):
|
||||
self.core_procs = []
|
||||
self.procs = []
|
||||
self.listeners = []
|
||||
self.alive = False
|
||||
self.is_shutdown = False
|
||||
|
||||
def isAlive(self):
|
||||
return self.alive
|
||||
|
||||
def join(self, *args):
|
||||
return
|
||||
|
||||
def add_process_listener(self, l):
|
||||
self.listeners.append(l)
|
||||
|
||||
def register(self, p):
|
||||
self.procs.append(p)
|
||||
|
||||
def register_core_proc(self, p):
|
||||
self.core_procs.append(p)
|
||||
|
||||
def registrations_complete(self):
|
||||
pass
|
||||
|
||||
def unregister(self, p):
|
||||
self.procs.remove(p)
|
||||
|
||||
def has_process(self, name):
|
||||
return len([p for p in self.procs if p.name == name]) > 0
|
||||
|
||||
def get_process(self, name):
|
||||
return self.procs.get(p, None)
|
||||
|
||||
def has_main_thread_jobs(self):
|
||||
return False
|
||||
|
||||
def do_main_thread_jobs(self):
|
||||
pass
|
||||
|
||||
def kill_process(self, name):
|
||||
pass
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def get_active_names(self):
|
||||
return [p.name for p in self.procs]
|
||||
|
||||
def get_process_names_with_spawn_count(self):
|
||||
actives = [(p.name, p.spawn_count) for p in self.procs]
|
||||
deads = []
|
||||
retval = [actives, deads]
|
||||
|
||||
def mainthread_spin_once(self):
|
||||
pass
|
||||
|
||||
def mainthread_spin(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
class ProcessMock(roslaunch.pmon.Process):
|
||||
def __init__(self, package, name, args, env, respawn=False):
|
||||
super(ProcessMock, self).__init__(package, name, args, env, respawn)
|
||||
self.stopped = False
|
||||
def stop(self, errors):
|
||||
self.stopped = True
|
||||
|
||||
class RespawnOnceProcessMock(ProcessMock):
|
||||
def __init__(self, package, name, args, env, respawn=True):
|
||||
super(ProcessMock, self).__init__(package, name, args, env, respawn)
|
||||
self.spawn_count = 0
|
||||
|
||||
def is_alive(self):
|
||||
self.time_of_death = time.time()
|
||||
return False
|
||||
|
||||
def start(self):
|
||||
self.spawn_count += 1
|
||||
if self.spawn_count > 1:
|
||||
self.respawn = False
|
||||
self.time_of_death = None
|
||||
|
||||
class RespawnOnceWithDelayProcessMock(ProcessMock):
|
||||
def __init__(self, package, name, args, env, respawn=True,
|
||||
respawn_delay=1.0):
|
||||
super(ProcessMock, self).__init__(package, name, args, env, respawn,
|
||||
respawn_delay=respawn_delay)
|
||||
self.spawn_count = 0
|
||||
self.respawn_interval = None
|
||||
|
||||
def is_alive(self):
|
||||
if self.time_of_death is None:
|
||||
self.time_of_death = time.time()
|
||||
return False
|
||||
|
||||
def start(self):
|
||||
self.spawn_count += 1
|
||||
if self.spawn_count > 1:
|
||||
self.respawn = False
|
||||
self.respawn_interval = time.time() - self.time_of_death
|
||||
self.time_of_death = None
|
||||
|
||||
## Test roslaunch.server
|
||||
class TestRoslaunchPmon(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.pmon = roslaunch.pmon.ProcessMonitor()
|
||||
|
||||
## test all apis of Process instance. part coverage/sanity test
|
||||
def _test_Process(self, p, package, name, args, env, respawn, respawn_delay):
|
||||
self.assertEquals(package, p.package)
|
||||
self.assertEquals(name, p.name)
|
||||
self.assertEquals(args, p.args)
|
||||
self.assertEquals(env, p.env)
|
||||
self.assertEquals(respawn, p.respawn)
|
||||
self.assertEquals(respawn_delay, p.respawn_delay)
|
||||
self.assertEquals(0, p.spawn_count)
|
||||
self.assertEquals(None, p.exit_code)
|
||||
self.assert_(p.get_exit_description())
|
||||
self.failIf(p.is_alive())
|
||||
|
||||
info = p.get_info()
|
||||
self.assertEquals(package, info['package'])
|
||||
self.assertEquals(name, info['name'])
|
||||
self.assertEquals(args, info['args'])
|
||||
self.assertEquals(env, info['env'])
|
||||
self.assertEquals(respawn, info['respawn'])
|
||||
self.assertEquals(respawn_delay, info['respawn_delay'])
|
||||
self.assertEquals(0, info['spawn_count'])
|
||||
self.failIf('exit_code' in info)
|
||||
|
||||
p.start()
|
||||
self.assertEquals(1, p.spawn_count)
|
||||
self.assertEquals(1, p.get_info()['spawn_count'])
|
||||
p.start()
|
||||
self.assertEquals(2, p.spawn_count)
|
||||
self.assertEquals(2, p.get_info()['spawn_count'])
|
||||
|
||||
# noop
|
||||
p.stop()
|
||||
|
||||
p.exit_code = 0
|
||||
self.assertEquals(0, p.get_info()['exit_code'])
|
||||
self.assert_('cleanly' in p.get_exit_description())
|
||||
p.exit_code = 1
|
||||
self.assertEquals(1, p.get_info()['exit_code'])
|
||||
self.assert_('[exit code 1]' in p.get_exit_description())
|
||||
|
||||
## tests to make sure that our Process base class has 100% coverage
|
||||
def test_Process(self):
|
||||
from roslaunch.pmon import Process
|
||||
# test constructor params
|
||||
respawn = False
|
||||
package = 'foo-%s'%time.time()
|
||||
name = 'name-%s'%time.time()
|
||||
args = [time.time(), time.time(), time.time()]
|
||||
env = { 'key': time.time(), 'key2': time.time() }
|
||||
|
||||
p = Process(package, name, args, env, 0.0)
|
||||
self._test_Process(p, package, name, args, env, False, 0.0)
|
||||
p = Process(package, name, args, env, True, 0.0)
|
||||
self._test_Process(p, package, name, args, env, True, 0.0)
|
||||
p = Process(package, name, args, env, False, 0.0)
|
||||
self._test_Process(p, package, name, args, env, False, 0.0)
|
||||
p = Process(package, name, args, env, True, 1.0)
|
||||
self._test_Process(p, package, name, args, env, True, 1.0)
|
||||
|
||||
def _test_DeadProcess(self, p0, package, name, args, env, respawn,
|
||||
respawn_delay):
|
||||
from roslaunch.pmon import DeadProcess
|
||||
p0.exit_code = -1
|
||||
dp = DeadProcess(p0)
|
||||
self.assertEquals(package, dp.package)
|
||||
self.assertEquals(name, dp.name)
|
||||
self.assertEquals(args, dp.args)
|
||||
self.assertEquals(env, dp.env)
|
||||
self.assertEquals(respawn, dp.respawn)
|
||||
self.assertEquals(respawn_delay, dp.respawn_delay)
|
||||
self.assertEquals(0, dp.spawn_count)
|
||||
self.assertEquals(-1, dp.exit_code)
|
||||
self.failIf(dp.is_alive())
|
||||
|
||||
info = dp.get_info()
|
||||
info0 = p0.get_info()
|
||||
self.assertEquals(info0['package'], info['package'])
|
||||
self.assertEquals(info0['name'], info['name'])
|
||||
self.assertEquals(info0['args'], info['args'])
|
||||
self.assertEquals(info0['env'], info['env'])
|
||||
self.assertEquals(info0['respawn'], info['respawn'])
|
||||
self.assertEquals(info0['respawn_delay'], info['respawn_delay'])
|
||||
self.assertEquals(0, info['spawn_count'])
|
||||
|
||||
try:
|
||||
dp.start()
|
||||
self.fail("should not be able to start a dead process")
|
||||
except: pass
|
||||
|
||||
# info should be frozen
|
||||
p0.package = 'dead package'
|
||||
p0.name = 'dead name'
|
||||
p0.spawn_count = 1
|
||||
self.assertEquals(package, dp.package)
|
||||
self.assertEquals(name, dp.name)
|
||||
self.assertEquals(0, dp.spawn_count)
|
||||
self.assertEquals(package, dp.get_info()['package'])
|
||||
self.assertEquals(name, dp.get_info()['name'])
|
||||
self.assertEquals(0, dp.get_info()['spawn_count'])
|
||||
p0.start()
|
||||
self.assertEquals(0, dp.spawn_count)
|
||||
self.assertEquals(0, dp.get_info()['spawn_count'])
|
||||
|
||||
# noop
|
||||
p0.stop()
|
||||
|
||||
|
||||
def test_DeadProcess(self):
|
||||
from roslaunch.pmon import Process, DeadProcess
|
||||
# test constructor params
|
||||
respawn = False
|
||||
package = 'foo-%s'%time.time()
|
||||
name = 'name-%s'%time.time()
|
||||
args = [time.time(), time.time(), time.time()]
|
||||
env = { 'key': time.time(), 'key2': time.time() }
|
||||
|
||||
p = Process(package, name, args, env, 0.0)
|
||||
self._test_DeadProcess(p, package, name, args, env, False, 0.0)
|
||||
p = Process(package, name, args, env, True, 0.0)
|
||||
self._test_DeadProcess(p, package, name, args, env, True, 0.0)
|
||||
p = Process(package, name, args, env, False, 0.0)
|
||||
self._test_DeadProcess(p, package, name, args, env, False, 0.0)
|
||||
p = Process(package, name, args, env, True, 1.0)
|
||||
self._test_DeadProcess(p, package, name, args, env, True, 1.0)
|
||||
|
||||
def test_start_shutdown_process_monitor(self):
|
||||
def failer():
|
||||
raise Exception("oops")
|
||||
# noop
|
||||
self.failIf(roslaunch.pmon.shutdown_process_monitor(None))
|
||||
|
||||
# test with fake pmon so we can get branch-complete
|
||||
pmon = ProcessMonitorMock()
|
||||
# - by setting alive to true, shutdown fails, though it can't really do anything about it
|
||||
pmon.alive = True
|
||||
self.failIf(roslaunch.pmon.shutdown_process_monitor(pmon))
|
||||
|
||||
# make sure that exceptions get trapped
|
||||
pmon.shutdown = failer
|
||||
# should cause an exception during execution, but should get caught
|
||||
self.failIf(roslaunch.pmon.shutdown_process_monitor(pmon))
|
||||
|
||||
# Test with a real process monitor
|
||||
pmon = roslaunch.pmon.start_process_monitor()
|
||||
self.assert_(pmon.isAlive())
|
||||
self.assert_(roslaunch.pmon.shutdown_process_monitor(pmon))
|
||||
self.failIf(pmon.isAlive())
|
||||
|
||||
# fiddle around with some state that would shouldn't be
|
||||
roslaunch.pmon._shutting_down = True
|
||||
pmon = roslaunch.pmon.start_process_monitor()
|
||||
if pmon != None:
|
||||
self.failIf(roslaunch.pmon.shutdown_process_monitor(pmon))
|
||||
self.fail("start_process_monitor should fail if during shutdown sequence")
|
||||
|
||||
def test_pmon_shutdown(self):
|
||||
# should be noop
|
||||
roslaunch.pmon.pmon_shutdown()
|
||||
|
||||
# start two real process monitors and kill them
|
||||
# pmon_shutdown
|
||||
pmon1 = roslaunch.pmon.start_process_monitor()
|
||||
pmon2 = roslaunch.pmon.start_process_monitor()
|
||||
self.assert_(pmon1.isAlive())
|
||||
self.assert_(pmon2.isAlive())
|
||||
|
||||
roslaunch.pmon.pmon_shutdown()
|
||||
|
||||
self.failIf(pmon1.isAlive())
|
||||
self.failIf(pmon2.isAlive())
|
||||
|
||||
def test_add_process_listener(self):
|
||||
# coverage test, not a functionality test as that would be much more difficult to simulate
|
||||
from roslaunch.pmon import ProcessListener
|
||||
l = ProcessListener()
|
||||
self.pmon.add_process_listener(l)
|
||||
|
||||
def test_kill_process(self):
|
||||
from roslaunch.core import RLException
|
||||
pmon = self.pmon
|
||||
|
||||
# should return False
|
||||
self.failIf(pmon.kill_process('foo'))
|
||||
|
||||
p1 = ProcessMock('foo', 'name1', [], {})
|
||||
p2 = ProcessMock('bar', 'name2', [], {})
|
||||
pmon.register(p1)
|
||||
pmon.register(p2)
|
||||
self.failIf(p1.stopped)
|
||||
self.failIf(p2.stopped)
|
||||
self.assert_(p1.name in pmon.get_active_names())
|
||||
self.assert_(p2.name in pmon.get_active_names())
|
||||
|
||||
# should fail as pmon API is string-based
|
||||
try:
|
||||
self.assert_(pmon.kill_process(p1))
|
||||
self.fail("kill_process should have thrown RLException")
|
||||
except RLException: pass
|
||||
|
||||
self.assert_(pmon.kill_process(p1.name))
|
||||
self.assert_(p1.stopped)
|
||||
|
||||
# - pmon should not have removed yet as the pmon thread cannot catch the death
|
||||
self.assert_(pmon.has_process(p1.name))
|
||||
self.assert_(p1.name in pmon.get_active_names())
|
||||
|
||||
self.failIf(p2.stopped)
|
||||
self.assert_(p2.name in pmon.get_active_names())
|
||||
pmon.kill_process(p2.name)
|
||||
self.assert_(p2.stopped)
|
||||
|
||||
# - pmon should not have removed yet as the pmon thread cannot catch the death
|
||||
self.assert_(pmon.has_process(p2.name))
|
||||
self.assert_(p2.name in pmon.get_active_names())
|
||||
|
||||
p3 = ProcessMock('bar', 'name3', [], {})
|
||||
def bad(x):
|
||||
raise Exception("ha ha ha")
|
||||
p3.stop = bad
|
||||
pmon.register(p3)
|
||||
# make sure kill_process is safe
|
||||
pmon.kill_process(p3.name)
|
||||
def f():
|
||||
return False
|
||||
|
||||
p1.is_alive = f
|
||||
p2.is_alive = f
|
||||
p3.is_alive = f
|
||||
|
||||
# Now that we've 'killed' all the processes, we should be able
|
||||
# to run through the ProcessMonitor run loop and it should
|
||||
# exit. But first, important that we check that pmon has no
|
||||
# other extra state in it
|
||||
self.assertEquals(3, len(pmon.get_active_names()))
|
||||
|
||||
# put pmon into exitable state
|
||||
pmon.registrations_complete()
|
||||
|
||||
# and run it -- but setup a safety timer to kill it if it doesn't exit
|
||||
marker = Marker()
|
||||
t = threading.Thread(target=kill_pmon, args=(pmon, marker, 10.))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
pmon.run()
|
||||
|
||||
self.failIf(marker.marked, "pmon had to be externally killed")
|
||||
|
||||
|
||||
self.assert_(pmon.done)
|
||||
self.failIf(pmon.has_process(p1.name))
|
||||
self.failIf(pmon.has_process(p2.name))
|
||||
alive, dead = pmon.get_process_names_with_spawn_count()
|
||||
self.failIf(alive)
|
||||
self.assert_((p1.name, p1.spawn_count) in dead)
|
||||
self.assert_((p2.name, p2.spawn_count) in dead)
|
||||
|
||||
|
||||
def test_run(self):
|
||||
# run is really hard to test...
|
||||
pmon = self.pmon
|
||||
|
||||
# put pmon into exitable state
|
||||
pmon.registrations_complete()
|
||||
|
||||
# give the pmon a process that raises an exception when it's
|
||||
# is_alive is checked. this should be marked as a dead process
|
||||
p1 = ProcessMock('bar', 'name1', [], {})
|
||||
def bad():
|
||||
raise Exception('ha ha')
|
||||
p1.is_alive = bad
|
||||
pmon.register(p1)
|
||||
|
||||
# give pmon a well-behaved but dead process
|
||||
p2 = ProcessMock('bar', 'name2', [], {})
|
||||
def f():
|
||||
return False
|
||||
p2.is_alive = f
|
||||
pmon.register(p2)
|
||||
|
||||
# give pmon a process that wants to respawn once
|
||||
p3 = RespawnOnceProcessMock('bar', 'name3', [], {})
|
||||
pmon.register(p3)
|
||||
|
||||
# give pmon a process that wants to respawn once after a delay
|
||||
p4 = RespawnOnceWithDelayProcessMock('bar', 'name4', [], {})
|
||||
pmon.register(p4)
|
||||
|
||||
# test assumptions about pmon's internal data structures
|
||||
# before we begin test
|
||||
self.assert_(p1 in pmon.procs)
|
||||
self.assert_(pmon._registrations_complete)
|
||||
self.failIf(pmon.is_shutdown)
|
||||
|
||||
# and run it -- but setup a safety timer to kill it if it doesn't exit
|
||||
marker = Marker()
|
||||
t = threading.Thread(target=kill_pmon, args=(self.pmon, marker, 10.))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
pmon.run()
|
||||
|
||||
self.failIf(marker.marked, "pmon had to be externally killed")
|
||||
|
||||
self.failIf(p3.spawn_count < 2, "process did not respawn")
|
||||
|
||||
self.failIf(p4.respawn_interval < p4.respawn_delay,
|
||||
"Respawn delay not respected: %s %s" % (p4.respawn_interval,
|
||||
p4.respawn_delay))
|
||||
|
||||
# retest assumptions
|
||||
self.failIf(pmon.procs)
|
||||
self.assert_(pmon.is_shutdown)
|
||||
|
||||
pmon.is_shutdown = False
|
||||
|
||||
def test_get_process_names_with_spawn_count(self):
|
||||
p1 = ProcessMock('foo', 'name1', [], {})
|
||||
p2 = ProcessMock('bar', 'name2', [], {})
|
||||
|
||||
pmon = self.pmon
|
||||
self.assertEquals([[], []], pmon.get_process_names_with_spawn_count())
|
||||
pmon.register(p1)
|
||||
self.assertEquals([[('name1', 0),], []], pmon.get_process_names_with_spawn_count())
|
||||
pmon.register(p2)
|
||||
alive, dead = pmon.get_process_names_with_spawn_count()
|
||||
self.assertEquals([], dead)
|
||||
self.assert_(('name1', 0) in alive)
|
||||
self.assert_(('name2', 0) in alive)
|
||||
|
||||
import random
|
||||
p1.spawn_count = random.randint(1, 10000)
|
||||
p2.spawn_count = random.randint(1, 10000)
|
||||
|
||||
alive, dead = pmon.get_process_names_with_spawn_count()
|
||||
self.assertEquals([], dead)
|
||||
self.assert_((p1.name, p1.spawn_count) in alive)
|
||||
self.assert_((p2.name, p2.spawn_count) in alive)
|
||||
|
||||
#TODO figure out how to test dead_list
|
||||
|
||||
|
||||
## Tests ProcessMonitor.register(), unregister(), has_process(), and get_process()
|
||||
def test_registration(self):
|
||||
from roslaunch.core import RLException
|
||||
from roslaunch.pmon import Process
|
||||
pmon = self.pmon
|
||||
|
||||
p1 = Process('foo', 'name1', [], {})
|
||||
p2 = Process('bar', 'name2', [], {})
|
||||
corep1 = Process('core', 'core1', [], {})
|
||||
corep2 = Process('core', 'core2', [], {})
|
||||
|
||||
pmon.register(p1)
|
||||
self.assert_(pmon.has_process('name1'))
|
||||
self.assertEquals(p1, pmon.get_process('name1'))
|
||||
self.failIf(pmon.has_process('name2'))
|
||||
self.assertEquals(['name1'], pmon.get_active_names())
|
||||
try:
|
||||
pmon.register(Process('foo', p1.name, [], {}))
|
||||
self.fail("should not allow duplicate process name")
|
||||
except RLException: pass
|
||||
|
||||
pmon.register(p2)
|
||||
self.assert_(pmon.has_process('name2'))
|
||||
self.assertEquals(p2, pmon.get_process('name2'))
|
||||
self.assertEquals(set(['name1', 'name2']), set(pmon.get_active_names()))
|
||||
|
||||
pmon.register_core_proc(corep1)
|
||||
self.assert_(pmon.has_process('core1'))
|
||||
self.assertEquals(corep1, pmon.get_process('core1'))
|
||||
self.assertEquals(set(['name1', 'name2', 'core1']), set(pmon.get_active_names()))
|
||||
|
||||
pmon.register_core_proc(corep2)
|
||||
self.assert_(pmon.has_process('core2'))
|
||||
self.assertEquals(corep2, pmon.get_process('core2'))
|
||||
self.assertEquals(set(['name1', 'name2', 'core1', 'core2']), set(pmon.get_active_names()))
|
||||
|
||||
|
||||
pmon.unregister(p2)
|
||||
self.failIf(pmon.has_process('name2'))
|
||||
pmon.unregister(p1)
|
||||
self.failIf(pmon.has_process('name1'))
|
||||
pmon.unregister(corep1)
|
||||
self.failIf(pmon.has_process('core1'))
|
||||
pmon.unregister(corep2)
|
||||
self.failIf(pmon.has_process('core2'))
|
||||
|
||||
pmon.shutdown()
|
||||
try:
|
||||
pmon.register(Process('shutdown_fail', 'shutdown_fail', [], {}))
|
||||
self.fail("registration should fail post-shutdown")
|
||||
except RLException: pass
|
||||
|
||||
|
||||
|
||||
def test_mainthread_spin_once(self):
|
||||
# shouldn't do anything
|
||||
self.pmon.done = False
|
||||
self.pmon.mainthread_spin_once()
|
||||
self.pmon.done = True
|
||||
self.pmon.mainthread_spin_once()
|
||||
|
||||
def test_mainthread_spin(self):
|
||||
# can't test actual spin as that would go forever
|
||||
self.pmon.done = False
|
||||
t = threading.Thread(target=kill_pmon, args=(self.pmon, Marker()))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
self.pmon.mainthread_spin()
|
||||
|
||||
def kill_pmon(pmon, marker, delay=1.0):
|
||||
# delay execution so that whatever pmon method we're calling has time to enter
|
||||
time.sleep(delay)
|
||||
if not pmon.is_shutdown:
|
||||
marker.mark()
|
||||
print("stopping pmon")
|
||||
# pmon has two states that need to be set, as many of the tests do not start the actual process monitor
|
||||
pmon.shutdown()
|
||||
pmon.done = True
|
||||
146
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_remote.py
vendored
Normal file
146
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_remote.py
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import roslaunch.core
|
||||
import roslaunch.remote
|
||||
|
||||
## Unit tests for roslaunch.remote
|
||||
class TestRoslaunchRemote(unittest.TestCase):
|
||||
|
||||
def test_remote_node_xml(self):
|
||||
# these are fairly brittle tests, but need to make sure there aren't regressions here
|
||||
Node = roslaunch.core.Node
|
||||
n = Node('pkg1', 'type1')
|
||||
self.assertEquals('<node pkg="pkg1" type="type1" ns="/" args="" respawn="False" respawn_delay="0.0" required="False">\n</node>', n.to_remote_xml())
|
||||
n = Node('pkg2', 'type2', namespace="/ns2/")
|
||||
self.assertEquals('<node pkg="pkg2" type="type2" ns="/ns2/" args="" respawn="False" respawn_delay="0.0" required="False">\n</node>', n.to_remote_xml())
|
||||
# machine_name should be a noop for remote xml
|
||||
n = Node('pkg3', 'type3', namespace="/ns3/", machine_name="machine3")
|
||||
self.assertEquals('<node pkg="pkg3" type="type3" ns="/ns3/" args="" respawn="False" respawn_delay="0.0" required="False">\n</node>', n.to_remote_xml())
|
||||
|
||||
# test args
|
||||
n = Node('pkg4', 'type4', args="arg4a arg4b")
|
||||
self.assertEquals('<node pkg="pkg4" type="type4" ns="/" args="arg4a arg4b" respawn="False" respawn_delay="0.0" required="False">\n</node>', n.to_remote_xml())
|
||||
# test respawn
|
||||
n = Node('pkg5', 'type5', respawn=True)
|
||||
self.assertEquals('<node pkg="pkg5" type="type5" ns="/" args="" respawn="True" respawn_delay="0.0" required="False">\n</node>', n.to_remote_xml())
|
||||
n = Node('pkg5', 'type5', respawn=True, respawn_delay=1.0)
|
||||
self.assertEquals('<node pkg="pkg5" type="type5" ns="/" args="" respawn="True" respawn_delay="1.0" required="False">\n</node>', n.to_remote_xml())
|
||||
n = Node('pkg6', 'type6', respawn=False)
|
||||
self.assertEquals('<node pkg="pkg6" type="type6" ns="/" args="" respawn="False" respawn_delay="0.0" required="False">\n</node>', n.to_remote_xml())
|
||||
# test remap_args
|
||||
n = Node('pkg6', 'type6', remap_args=[('from6a', 'to6a'), ('from6b', 'to6b')])
|
||||
self.assertEquals("""<node pkg="pkg6" type="type6" ns="/" args="" respawn="False" respawn_delay="0.0" required="False">
|
||||
<remap from="from6a" to="to6a" />
|
||||
<remap from="from6b" to="to6b" />
|
||||
</node>""", n.to_remote_xml())
|
||||
# test env args
|
||||
n = Node('pkg7', 'type7', env_args=[('key7a', 'val7a'), ('key7b', 'val7b')])
|
||||
self.assertEquals("""<node pkg="pkg7" type="type7" ns="/" args="" respawn="False" respawn_delay="0.0" required="False">
|
||||
<env name="key7a" value="val7a" />
|
||||
<env name="key7b" value="val7b" />
|
||||
</node>""", n.to_remote_xml())
|
||||
# test cwd
|
||||
n = Node('pkg8', 'type8', cwd='ROS_HOME')
|
||||
self.assertEquals('<node pkg="pkg8" type="type8" ns="/" args="" cwd="ROS_HOME" respawn="False" respawn_delay="0.0" required="False">\n</node>', n.to_remote_xml())
|
||||
n = Node('pkg9', 'type9', cwd='node')
|
||||
self.assertEquals('<node pkg="pkg9" type="type9" ns="/" args="" cwd="node" respawn="False" respawn_delay="0.0" required="False">\n</node>', n.to_remote_xml())
|
||||
# test output
|
||||
n = Node('pkg10', 'type10', output='screen')
|
||||
self.assertEquals('<node pkg="pkg10" type="type10" ns="/" args="" output="screen" respawn="False" respawn_delay="0.0" required="False">\n</node>', n.to_remote_xml())
|
||||
n = Node('pkg11', 'type11', output='log')
|
||||
self.assertEquals('<node pkg="pkg11" type="type11" ns="/" args="" output="log" respawn="False" respawn_delay="0.0" required="False">\n</node>', n.to_remote_xml())
|
||||
|
||||
# test launch-prefix
|
||||
n = Node('pkg12', 'type12', launch_prefix='xterm -e')
|
||||
self.assertEquals('<node pkg="pkg12" type="type12" ns="/" args="" respawn="False" respawn_delay="0.0" launch-prefix="xterm -e" required="False">\n</node>', n.to_remote_xml())
|
||||
|
||||
# test required
|
||||
n = Node('pkg13', 'type13', required=True)
|
||||
self.assertEquals('<node pkg="pkg13" type="type13" ns="/" args="" respawn="False" respawn_delay="0.0" required="True">\n</node>', n.to_remote_xml())
|
||||
|
||||
#test everything
|
||||
n = Node('pkg20', 'type20', namespace="/ns20/", machine_name="foo", remap_args=[('from20a', 'to20a'), ('from20b', 'to20b')], env_args=[('key20a', 'val20a'), ('key20b', 'val20b')], output="screen", cwd="ROS_HOME", respawn=True, args="arg20a arg20b", launch_prefix="nice", required=False)
|
||||
self.assertEquals("""<node pkg="pkg20" type="type20" ns="/ns20/" args="arg20a arg20b" output="screen" cwd="ROS_HOME" respawn="True" respawn_delay="0.0" launch-prefix="nice" required="False">
|
||||
<remap from="from20a" to="to20a" />
|
||||
<remap from="from20b" to="to20b" />
|
||||
<env name="key20a" value="val20a" />
|
||||
<env name="key20b" value="val20b" />
|
||||
</node>""", n.to_remote_xml())
|
||||
|
||||
def test_remote_test_node_xml(self):
|
||||
Test = roslaunch.core.Test
|
||||
#TODO: will certainly fail right now
|
||||
# these are fairly brittle tests, but need to make sure there aren't regressions here
|
||||
Test = roslaunch.core.Test
|
||||
n = Test('test1', 'pkg1', 'type1')
|
||||
self.assertEquals('<test pkg="pkg1" type="type1" ns="/" args="" output="log" required="False" test-name="test1">\n</test>', n.to_remote_xml())
|
||||
n = Test('test2', 'pkg2', 'type2', namespace="/ns2/")
|
||||
self.assertEquals('<test pkg="pkg2" type="type2" ns="/ns2/" args="" output="log" required="False" test-name="test2">\n</test>', n.to_remote_xml())
|
||||
# machine_name should be a noop for remote xml
|
||||
n = Test('test3', 'pkg3', 'type3', namespace="/ns3/", machine_name="machine3")
|
||||
self.assertEquals('<test pkg="pkg3" type="type3" ns="/ns3/" args="" output="log" required="False" test-name="test3">\n</test>', n.to_remote_xml())
|
||||
|
||||
# test args
|
||||
n = Test('test4', 'pkg4', 'type4', args="arg4a arg4b")
|
||||
self.assertEquals('<test pkg="pkg4" type="type4" ns="/" args="arg4a arg4b" output="log" required="False" test-name="test4">\n</test>', n.to_remote_xml())
|
||||
# test remap_args
|
||||
n = Test('test6', 'pkg6', 'type6', remap_args=[('from6a', 'to6a'), ('from6b', 'to6b')])
|
||||
self.assertEquals("""<test pkg="pkg6" type="type6" ns="/" args="" output="log" required="False" test-name="test6">
|
||||
<remap from="from6a" to="to6a" />
|
||||
<remap from="from6b" to="to6b" />
|
||||
</test>""", n.to_remote_xml())
|
||||
# test env args
|
||||
n = Test('test7', 'pkg7', 'type7', env_args=[('key7a', 'val7a'), ('key7b', 'val7b')])
|
||||
self.assertEquals("""<test pkg="pkg7" type="type7" ns="/" args="" output="log" required="False" test-name="test7">
|
||||
<env name="key7a" value="val7a" />
|
||||
<env name="key7b" value="val7b" />
|
||||
</test>""", n.to_remote_xml())
|
||||
# test cwd
|
||||
n = Test('test8', 'pkg8', 'type8', cwd='ROS_HOME')
|
||||
self.assertEquals('<test pkg="pkg8" type="type8" ns="/" args="" output="log" cwd="ROS_HOME" required="False" test-name="test8">\n</test>', n.to_remote_xml())
|
||||
n = Test('test9', 'pkg9', 'type9', cwd='node')
|
||||
self.assertEquals('<test pkg="pkg9" type="type9" ns="/" args="" output="log" cwd="node" required="False" test-name="test9">\n</test>', n.to_remote_xml())
|
||||
|
||||
#test everything
|
||||
n = Test('test20', 'pkg20', 'type20', namespace="/ns20/", machine_name="foo", remap_args=[('from20a', 'to20a'), ('from20b', 'to20b')], env_args=[('key20a', 'val20a'), ('key20b', 'val20b')], cwd="ROS_HOME", args="arg20a arg20b")
|
||||
self.assertEquals("""<test pkg="pkg20" type="type20" ns="/ns20/" args="arg20a arg20b" output="log" cwd="ROS_HOME" required="False" test-name="test20">
|
||||
<remap from="from20a" to="to20a" />
|
||||
<remap from="from20b" to="to20b" />
|
||||
<env name="key20a" value="val20a" />
|
||||
<env name="key20b" value="val20b" />
|
||||
</test>""", n.to_remote_xml())
|
||||
|
||||
91
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_rlutil.py
vendored
Normal file
91
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_rlutil.py
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
#!/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: roslaunch_node_args.py 5229 2009-07-16 22:31:17Z sfkwc $
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import roslaunch
|
||||
import roslaunch.rlutil
|
||||
|
||||
def get_test_path():
|
||||
# two directories up from here
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
# path to example.launch directory
|
||||
def get_example_path():
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
|
||||
|
||||
## Test roslaunch.node_args
|
||||
class TestRoslaunchRlutil(unittest.TestCase):
|
||||
|
||||
def test_resolve_launch_arguments(self):
|
||||
from roslaunch.rlutil import resolve_launch_arguments
|
||||
|
||||
roslaunch_dir = get_test_path()
|
||||
example_xml_p = os.path.join(get_example_path(), 'example.launch')
|
||||
tests = [
|
||||
([], []),
|
||||
(['roslaunch', 'example.launch'], [example_xml_p]),
|
||||
([example_xml_p], [example_xml_p]),
|
||||
|
||||
(['roslaunch', 'example.launch', 'foo', 'bar'], [example_xml_p, 'foo', 'bar']),
|
||||
([example_xml_p, 'foo', 'bar'], [example_xml_p,'foo', 'bar']),
|
||||
|
||||
]
|
||||
bad = [
|
||||
['does_not_exist'],
|
||||
['does_not_exist', 'foo.launch'],
|
||||
['roslaunch', 'does_not_exist.launch'],
|
||||
]
|
||||
|
||||
for test, result in tests:
|
||||
for v1, v2 in zip(result, resolve_launch_arguments(test)):
|
||||
# workaround for nfs
|
||||
if os.path.exists(v1):
|
||||
self.assert_(os.path.samefile(v1, v2))
|
||||
else:
|
||||
self.assertEquals(v1, v2)
|
||||
for test in bad:
|
||||
try:
|
||||
resolve_launch_arguments(test)
|
||||
self.fail("should have failed")
|
||||
except roslaunch.RLException:
|
||||
pass
|
||||
|
||||
def test_roslaunch_check_pass_all_args(self):
|
||||
filename = os.path.join(get_example_path(), 'example-pass_all_args.launch')
|
||||
error_msg = roslaunch.rlutil.check_roslaunch(filename)
|
||||
assert error_msg is None
|
||||
482
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_server.py
vendored
Normal file
482
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_roslaunch_server.py
vendored
Normal file
@@ -0,0 +1,482 @@
|
||||
# Software License Agreement (BSD License)
|
||||
#
|
||||
# Copyright (c) 2008, 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import time
|
||||
|
||||
import roslaunch.pmon
|
||||
import roslaunch.server
|
||||
from roslaunch.server import ProcessListener
|
||||
|
||||
try:
|
||||
from xmlrpc.client import ServerProxy
|
||||
except ImportError:
|
||||
from xmlrpclib import ServerProxy
|
||||
|
||||
import rosgraph
|
||||
master = rosgraph.Master('test_roslaunch_server')
|
||||
def get_param(*args):
|
||||
return master.getParam(*args)
|
||||
|
||||
class MockProcessListener(ProcessListener):
|
||||
def process_died(self, name, code):
|
||||
self.process_name = name
|
||||
self.exit_code = code
|
||||
|
||||
## Fake Process object
|
||||
class ProcessMock(roslaunch.pmon.Process):
|
||||
def __init__(self, package, name, args, env, respawn=False):
|
||||
super(ProcessMock, self).__init__(package, name, args, env, respawn)
|
||||
self.stopped = False
|
||||
def stop(self):
|
||||
self.stopped = True
|
||||
|
||||
## Fake ProcessMonitor object
|
||||
class ProcessMonitorMock(object):
|
||||
def __init__(self):
|
||||
self.core_procs = []
|
||||
self.procs = []
|
||||
self.listeners = []
|
||||
self.is_shutdown = False
|
||||
|
||||
def join(self, timeout=0):
|
||||
pass
|
||||
|
||||
def add_process_listener(self, l):
|
||||
self.listeners.append(l)
|
||||
|
||||
def register(self, p):
|
||||
self.procs.append(p)
|
||||
|
||||
def register_core_proc(self, p):
|
||||
self.core_procs.append(p)
|
||||
|
||||
def registrations_complete(self):
|
||||
pass
|
||||
|
||||
def unregister(self, p):
|
||||
self.procs.remove(p)
|
||||
|
||||
def has_process(self, name):
|
||||
return len([p for p in self.procs if p.name == name]) > 0
|
||||
|
||||
def get_process(self, name):
|
||||
val = [p for p in self.procs if p.name == name]
|
||||
if val:
|
||||
return val[0]
|
||||
return None
|
||||
|
||||
def has_main_thread_jobs(self):
|
||||
return False
|
||||
|
||||
def do_main_thread_jobs(self):
|
||||
pass
|
||||
|
||||
def kill_process(self, name):
|
||||
pass
|
||||
|
||||
def shutdown(self):
|
||||
self.is_shutdown = True
|
||||
|
||||
def get_active_names(self):
|
||||
return [p.name for p in self.procs]
|
||||
|
||||
def get_process_names_with_spawn_count(self):
|
||||
actives = [(p.name, p.spawn_count) for p in self.procs]
|
||||
deads = []
|
||||
return [actives, deads]
|
||||
|
||||
def mainthread_spin_once(self):
|
||||
pass
|
||||
|
||||
def mainthread_spin(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
class TestHandler(roslaunch.server.ROSLaunchBaseHandler):
|
||||
def __init__(self, pm):
|
||||
super(TestHandler, self).__init__(pm)
|
||||
self.pinged = None
|
||||
def ping(self, val):
|
||||
self.pinged = val
|
||||
return True
|
||||
|
||||
## Test roslaunch.server
|
||||
class TestRoslaunchServer(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.pmon = ProcessMonitorMock()
|
||||
roslaunch.core.clear_printlog_handlers()
|
||||
roslaunch.core.clear_printerrlog_handlers()
|
||||
|
||||
def test_ChildRoslaunchProcess(self):
|
||||
# test that constructor is wired up correctly
|
||||
from roslaunch.server import ChildROSLaunchProcess
|
||||
name = 'foo-%s'%time.time()
|
||||
args = [time.time(), time.time()]
|
||||
env = { 'key-%s'%time.time() : str(time.time()) }
|
||||
cp = ChildROSLaunchProcess(name, args, env)
|
||||
self.assertEquals(name, cp.name)
|
||||
self.assertEquals(args, cp.args)
|
||||
self.assertEquals(env, cp.env)
|
||||
self.assertEquals(None, cp.uri)
|
||||
|
||||
uri = 'http://foo:1234'
|
||||
cp.set_uri(uri)
|
||||
self.assertEquals(uri, cp.uri)
|
||||
|
||||
def _succeed(self, retval):
|
||||
code, msg, val = retval
|
||||
self.assertEquals(1, code)
|
||||
self.assert_(type(msg) == str)
|
||||
return val
|
||||
|
||||
def test_ROSLaunchBaseHandler(self):
|
||||
from roslaunch.server import ROSLaunchBaseHandler
|
||||
|
||||
try:
|
||||
ROSLaunchBaseHandler(None)
|
||||
self.fail("should not allow pm as None")
|
||||
except: pass
|
||||
|
||||
pmon = self.pmon
|
||||
h = ROSLaunchBaseHandler(pmon)
|
||||
self._test_ROSLaunchBaseHandler(h)
|
||||
|
||||
# reusable parent class test
|
||||
def _test_ROSLaunchBaseHandler(self, h):
|
||||
pmon = self.pmon
|
||||
# - test get pid
|
||||
self.assertEquals(os.getpid(), self._succeed(h.get_pid()))
|
||||
|
||||
# - test list processes
|
||||
process_list = self._succeed(h.list_processes())
|
||||
self.assertEquals([[], []], process_list)
|
||||
|
||||
p = ProcessMock('pack', 'name', [], {})
|
||||
p.spawn_count = 1
|
||||
pmon.register(p)
|
||||
|
||||
process_list = self._succeed(h.list_processes())
|
||||
self.assertEquals([('name', 1),], process_list[0])
|
||||
self.assertEquals([], process_list[1])
|
||||
|
||||
p.spawn_count = 2
|
||||
process_list = self._succeed(h.list_processes())
|
||||
self.assertEquals([('name', 2),], process_list[0])
|
||||
self.assertEquals([], process_list[1])
|
||||
# - cleanup our work
|
||||
pmon.unregister(p)
|
||||
|
||||
# - test process_info
|
||||
code, msg, val = h.process_info('fubar')
|
||||
self.assertEquals(-1, code)
|
||||
self.assert_(type(msg) == str)
|
||||
self.assertEquals({}, val)
|
||||
|
||||
pmon.register(p)
|
||||
self.assertEquals(p.get_info(), self._succeed(h.process_info(p.name)))
|
||||
pmon.unregister(p)
|
||||
|
||||
# - test get_node_names
|
||||
# - reach into instance to get branch-complete
|
||||
h.pm = None
|
||||
code, msg, val = h.get_node_names()
|
||||
self.assertEquals(0, code)
|
||||
self.assert_(type(msg) == str)
|
||||
self.assertEquals([], val)
|
||||
h.pm = pmon
|
||||
|
||||
self.assertEquals(pmon.get_active_names(), self._succeed(h.get_node_names()))
|
||||
pmon.register(p)
|
||||
self.assertEquals(pmon.get_active_names(), self._succeed(h.get_node_names()))
|
||||
pmon.unregister(p)
|
||||
|
||||
def test_ROSLaunchParentHandler(self):
|
||||
from roslaunch.server import ROSLaunchParentHandler
|
||||
from roslaunch.server import ChildROSLaunchProcess
|
||||
try:
|
||||
ROSLaunchParentHandler(None, {}, [])
|
||||
self.fail("should not allow pm as None")
|
||||
except: pass
|
||||
|
||||
pmon = self.pmon
|
||||
child_processes = {}
|
||||
listeners = []
|
||||
h = ROSLaunchParentHandler(pmon, child_processes, listeners)
|
||||
self.assertEquals(child_processes, h.child_processes)
|
||||
self.assertEquals(listeners, h.listeners)
|
||||
self._test_ROSLaunchBaseHandler(h)
|
||||
|
||||
from rosgraph_msgs.msg import Log
|
||||
h.log('client-1', Log.FATAL, "message")
|
||||
h.log('client-1', Log.ERROR, "message")
|
||||
h.log('client-1', Log.DEBUG, "message")
|
||||
h.log('client-1', Log.INFO, "started with pid 1234")
|
||||
|
||||
# test process_died
|
||||
# - no listeners
|
||||
self._succeed(h.process_died('dead1', -1))
|
||||
# - well-behaved listener
|
||||
l = roslaunch.pmon.ProcessListener()
|
||||
listeners.append(l)
|
||||
self._succeed(h.process_died('dead2', -1))
|
||||
# - bad listener with exception
|
||||
def bad():
|
||||
raise Exception("haha")
|
||||
l.process_died = bad
|
||||
self._succeed(h.process_died('dead3', -1))
|
||||
|
||||
# test register
|
||||
|
||||
# - verify clean slate with list_children
|
||||
self.assertEquals([], self._succeed(h.list_children()))
|
||||
|
||||
# - first register with unknown
|
||||
code, msg, val = h.register('client-unknown', 'http://unroutable:1234')
|
||||
self.assertEquals(-1, code)
|
||||
self.assertEquals([], self._succeed(h.list_children()))
|
||||
|
||||
# - now register with known
|
||||
uri = 'http://unroutable:1324'
|
||||
child_processes['client-1'] = ChildROSLaunchProcess('foo', [], {})
|
||||
val = self._succeed(h.register('client-1', uri))
|
||||
self.assert_(type(val) == int)
|
||||
self.assertEquals([uri], self._succeed(h.list_children()))
|
||||
|
||||
def test_ROSLaunchChildHandler(self):
|
||||
from roslaunch.server import ROSLaunchChildHandler
|
||||
pmon = self.pmon
|
||||
try:
|
||||
# if there is a core up, we have to use its run id
|
||||
run_id = get_param('/run_id')
|
||||
except:
|
||||
run_id = 'foo-%s'%time.time()
|
||||
name = 'foo-bob'
|
||||
server_uri = 'http://localhost:12345'
|
||||
try:
|
||||
h = ROSLaunchChildHandler(run_id, name, server_uri, None)
|
||||
self.fail("should not allow pm as None")
|
||||
except: pass
|
||||
try:
|
||||
h = ROSLaunchChildHandler(run_id, name, 'http://bad_uri:0', pmon)
|
||||
self.fail("should not allow bad uri")
|
||||
except: pass
|
||||
try:
|
||||
h = ROSLaunchChildHandler(run_id, name, None, pmon)
|
||||
self.fail("should not allow None server_uri")
|
||||
except: pass
|
||||
|
||||
h = ROSLaunchChildHandler(run_id, name, server_uri, pmon)
|
||||
self.assertEquals(run_id, h.run_id)
|
||||
self.assertEquals(name, h.name)
|
||||
self.assertEquals(server_uri, h.server_uri)
|
||||
self._test_ROSLaunchBaseHandler(h)
|
||||
|
||||
# test _log()
|
||||
from rosgraph_msgs.msg import Log
|
||||
h._log(Log.INFO, 'hello')
|
||||
|
||||
# test shutdown()
|
||||
# should uninitialize pm
|
||||
h.shutdown()
|
||||
self.assert_(pmon.is_shutdown)
|
||||
# - this check is mostly to make sure that the launch() call below will exit
|
||||
self.assert_(h.pm is None)
|
||||
code, msg, val = h.launch('<launch></launch>')
|
||||
self.assertEquals(0, code)
|
||||
|
||||
# TODO: actual launch. more difficult as we need a core
|
||||
|
||||
def test__ProcessListenerForwarder(self):
|
||||
from roslaunch.server import _ProcessListenerForwarder, ProcessListener
|
||||
|
||||
# test with bad logic first
|
||||
f = _ProcessListenerForwarder(None)
|
||||
# - should trap exceptions
|
||||
f.process_died("foo", -1)
|
||||
# test with actual listener
|
||||
l = MockProcessListener()
|
||||
f = _ProcessListenerForwarder(l)
|
||||
# - should run normally
|
||||
f.process_died("foo", -1)
|
||||
|
||||
self.assertEquals(l.process_name, 'foo')
|
||||
self.assertEquals(l.exit_code, -1)
|
||||
|
||||
def test_ROSLaunchNode(self):
|
||||
#exercise the basic ROSLaunchNode API
|
||||
from roslaunch.server import ROSLaunchNode
|
||||
|
||||
# - create a node with a handler
|
||||
handler = TestHandler(self.pmon)
|
||||
node = ROSLaunchNode(handler)
|
||||
|
||||
# - start the node
|
||||
node.start()
|
||||
self.assert_(node.uri)
|
||||
|
||||
# - call the ping API that we added
|
||||
s = ServerProxy(node.uri)
|
||||
test_val = 'test-%s'%time.time()
|
||||
s.ping(test_val)
|
||||
self.assertEquals(handler.pinged, test_val)
|
||||
|
||||
# - call the pid API
|
||||
code, msg, pid = s.get_pid()
|
||||
self.assertEquals(1, code)
|
||||
self.assert_(type(msg) == str)
|
||||
self.assertEquals(os.getpid(), pid)
|
||||
|
||||
# - shut it down
|
||||
node.shutdown('test done')
|
||||
|
||||
def test_ROSLaunchParentNode(self):
|
||||
from roslaunch.server import ROSLaunchParentNode
|
||||
from roslaunch.server import ChildROSLaunchProcess
|
||||
from roslaunch.config import ROSLaunchConfig
|
||||
from roslaunch.pmon import ProcessListener
|
||||
rosconfig = ROSLaunchConfig()
|
||||
try:
|
||||
ROSLaunchParentNode(rosconfig, None)
|
||||
self.fail("should not allow pm as None")
|
||||
except: pass
|
||||
pmon = self.pmon
|
||||
n = ROSLaunchParentNode(rosconfig, pmon)
|
||||
self.assertEquals(rosconfig, n.rosconfig)
|
||||
self.assertEquals([], n.listeners)
|
||||
self.assertEquals({}, n.child_processes)
|
||||
self.assertEquals(n.handler.listeners, n.listeners)
|
||||
self.assertEquals(n.handler.child_processes, n.child_processes)
|
||||
|
||||
# test add listener
|
||||
self.assertEquals(n.handler.listeners, n.listeners)
|
||||
l = ProcessListener()
|
||||
n.add_process_listener(l)
|
||||
self.assertEquals([l], n.listeners)
|
||||
self.assertEquals(n.handler.listeners, n.listeners)
|
||||
|
||||
# now, lets make some xmlrpc calls against it
|
||||
import roslaunch.config
|
||||
server = roslaunch.server.ROSLaunchParentNode(roslaunch.config.ROSLaunchConfig(), self.pmon)
|
||||
|
||||
# it's really dangerous for logging when both a parent and a
|
||||
# child are in the same process, so out of paranoia, clear the
|
||||
# logging handlers
|
||||
roslaunch.core.clear_printlog_handlers()
|
||||
roslaunch.core.clear_printerrlog_handlers()
|
||||
|
||||
# - register a fake child with the server so that it accepts registration from ROSLaunchChild
|
||||
child_name = 'child-%s'%time.time()
|
||||
child_proc = ChildROSLaunchProcess('foo', [], {})
|
||||
server.add_child(child_name, child_proc)
|
||||
|
||||
try:
|
||||
server.start()
|
||||
self.assert_(server.uri, "server URI did not initialize")
|
||||
s = ServerProxy(server.uri)
|
||||
child_uri = 'http://fake-unroutable:1324'
|
||||
# - list children should be empty
|
||||
val = self._succeed(s.list_children())
|
||||
self.assertEquals([], val)
|
||||
# - register
|
||||
val = self._succeed(s.register(child_name, child_uri))
|
||||
self.assertEquals(1, val)
|
||||
# - list children
|
||||
val = self._succeed(s.list_children())
|
||||
self.assertEquals([child_uri], val)
|
||||
finally:
|
||||
server.shutdown('test done')
|
||||
|
||||
|
||||
|
||||
def test_ROSLaunchChildNode(self):
|
||||
from roslaunch.server import ROSLaunchChildNode
|
||||
from roslaunch.server import ChildROSLaunchProcess
|
||||
pmon = self.pmon
|
||||
try:
|
||||
# if there is a core up, we have to use its run id
|
||||
run_id = get_param('/run_id')
|
||||
except:
|
||||
run_id = 'foo-%s'%time.time()
|
||||
name = 'foo-bob'
|
||||
server_uri = 'http://localhost:12345'
|
||||
try:
|
||||
ROSLaunchChildNode(run_id, name, server_uri, None)
|
||||
self.fail("should not allow pm as None")
|
||||
except: pass
|
||||
try:
|
||||
ROSLaunchChildNode(run_id, name, 'http://bad_uri:0', pmon)
|
||||
self.fail("should not allow bad uri")
|
||||
except: pass
|
||||
|
||||
n = ROSLaunchChildNode(run_id, name, server_uri, pmon)
|
||||
self.assertEquals(run_id, n.run_id)
|
||||
self.assertEquals(name, n.name)
|
||||
self.assertEquals(server_uri, n.server_uri)
|
||||
|
||||
# tests for actual registration with server
|
||||
import roslaunch.config
|
||||
server = roslaunch.server.ROSLaunchParentNode(roslaunch.config.ROSLaunchConfig(), self.pmon)
|
||||
# - register a fake child with the server so that it accepts registration from ROSLaunchChild
|
||||
child_proc = ChildROSLaunchProcess('foo', [], {})
|
||||
server.add_child(name, child_proc)
|
||||
|
||||
try:
|
||||
server.start()
|
||||
self.assert_(server.uri, "server URI did not initialize")
|
||||
s = ServerProxy(server.uri)
|
||||
|
||||
print("SERVER STARTED")
|
||||
|
||||
# start the child
|
||||
n = ROSLaunchChildNode(run_id, name, server.uri, pmon)
|
||||
n.start()
|
||||
print("CHILD STARTED")
|
||||
self.assert_(n.uri, "child URI did not initialize")
|
||||
|
||||
# verify registration
|
||||
print("VERIFYING REGISTRATION")
|
||||
self.assertEquals(child_proc.uri, n.uri)
|
||||
child_uri = 'http://fake-unroutable:1324'
|
||||
# - list children
|
||||
val = self._succeed(s.list_children())
|
||||
self.assertEquals([n.uri], val)
|
||||
finally:
|
||||
print("SHUTTING DOWN")
|
||||
n.shutdown('test done')
|
||||
server.shutdown('test done')
|
||||
|
||||
199
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_substitution_args.py
vendored
Normal file
199
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_substitution_args.py
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import rospkg
|
||||
|
||||
def test__arg():
|
||||
import random
|
||||
from roslaunch.substitution_args import _arg, ArgException, SubstitutionException
|
||||
|
||||
ctx = { 'arg': {
|
||||
'foo': '12345',
|
||||
'bar': 'hello world',
|
||||
'baz': 'sim',
|
||||
'empty': '',
|
||||
}
|
||||
}
|
||||
|
||||
# test invalid
|
||||
try:
|
||||
_arg('$(arg)', 'arg', [], ctx)
|
||||
assert False, "should have thrown"
|
||||
except SubstitutionException:
|
||||
pass
|
||||
|
||||
# test normal
|
||||
tests = [
|
||||
('12345', ('$(arg foo)', 'arg foo', ['foo'], ctx)),
|
||||
('', ('$(arg empty)', 'arg empty', ['empty'], ctx)),
|
||||
('sim', ('$(arg baz)', 'arg baz', ['baz'], ctx)),
|
||||
|
||||
# test with other args present, should only resolve match
|
||||
('1234512345', ('$(arg foo)$(arg foo)', 'arg foo', ['foo'], ctx)),
|
||||
('12345$(arg baz)', ('$(arg foo)$(arg baz)', 'arg foo', ['foo'], ctx)),
|
||||
('$(arg foo)sim', ('$(arg foo)$(arg baz)', 'arg baz', ['baz'], ctx)),
|
||||
|
||||
# test double-resolve safe
|
||||
('12345', ('12345', 'arg foo', ['foo'], ctx)),
|
||||
]
|
||||
|
||||
for result, test in tests:
|
||||
resolved, a, args, context = test
|
||||
assert result == _arg(resolved, a, args, context)
|
||||
|
||||
# - test that all fail if ctx is not set
|
||||
for result, test in tests:
|
||||
resolved, a, args, context = test
|
||||
try:
|
||||
_arg(resolved, a, args, {})
|
||||
assert False, "should have thrown"
|
||||
except ArgException as e:
|
||||
assert args[0] == str(e)
|
||||
try:
|
||||
_arg(resolved, a, args, {'arg': {}})
|
||||
assert False, "should have thrown"
|
||||
except ArgException as e:
|
||||
assert args[0] == str(e)
|
||||
|
||||
def test_resolve_args():
|
||||
from roslaunch.substitution_args import resolve_args, SubstitutionException
|
||||
|
||||
r = rospkg.RosPack()
|
||||
roslaunch_dir = r.get_path('roslaunch')
|
||||
assert roslaunch_dir
|
||||
|
||||
anon_context = {'foo': 'bar'}
|
||||
arg_context = {'fuga': 'hoge', 'car': 'cdr', 'arg': 'foo', 'True': 'False'}
|
||||
context = {'anon': anon_context, 'arg': arg_context }
|
||||
|
||||
tests = [
|
||||
('$(find roslaunch)', roslaunch_dir),
|
||||
('hello$(find roslaunch)', 'hello'+roslaunch_dir),
|
||||
('$(find roslaunch )', roslaunch_dir),
|
||||
('$$(find roslaunch )', '$'+roslaunch_dir),
|
||||
('$( find roslaunch )', roslaunch_dir),
|
||||
('$(find roslaunch )', roslaunch_dir),
|
||||
('$(find roslaunch)$(find roslaunch)', roslaunch_dir+os.sep+roslaunch_dir),
|
||||
('$(find roslaunch)/foo/bar.xml', roslaunch_dir+os.sep+'foo'+os.sep+'bar.xml'),
|
||||
(r'$(find roslaunch)\foo\bar.xml $(find roslaunch)\bar.xml', roslaunch_dir+os.sep+'foo'+os.sep+'bar.xml '+roslaunch_dir+os.sep+'bar.xml'),
|
||||
('$(find roslaunch)\\foo\\bar.xml more/stuff\\here', roslaunch_dir+os.sep+'foo'+os.sep+'bar.xml more/stuff\\here'),
|
||||
('$(env ROS_ROOT)', os.environ['ROS_ROOT']),
|
||||
('$(env ROS_ROOT)', os.environ['ROS_ROOT']),
|
||||
('$(env ROS_ROOT )', os.environ['ROS_ROOT']),
|
||||
('$(optenv ROS_ROOT)', os.environ['ROS_ROOT']),
|
||||
('$(optenv ROS_ROOT)$(optenv ROS_ROOT)', os.environ['ROS_ROOT']+os.environ['ROS_ROOT']),
|
||||
('$(optenv ROS_ROOT alternate text)', os.environ['ROS_ROOT']),
|
||||
('$(optenv NOT_ROS_ROOT)', ''),
|
||||
('$(optenv NOT_ROS_ROOT)more stuff', 'more stuff'),
|
||||
('$(optenv NOT_ROS_ROOT alternate)', 'alternate'),
|
||||
('$(optenv NOT_ROS_ROOT alternate text)', 'alternate text'),
|
||||
|
||||
# #1776
|
||||
('$(anon foo)', 'bar'),
|
||||
('$(anon foo)/baz', 'bar/baz'),
|
||||
('$(anon foo)/baz/$(anon foo)', 'bar/baz/bar'),
|
||||
|
||||
# arg
|
||||
('$(arg fuga)', 'hoge'),
|
||||
('$(arg fuga)$(arg fuga)', 'hogehoge'),
|
||||
('$(arg car)$(arg fuga)', 'cdrhoge'),
|
||||
('$(arg fuga)hoge', 'hogehoge'),
|
||||
|
||||
# $(eval ...) versions of those tests
|
||||
("$(eval find('roslaunch'))", roslaunch_dir),
|
||||
("$(eval env('ROS_ROOT'))", os.environ['ROS_ROOT']),
|
||||
("$(eval optenv('ROS_ROOT', 'alternate text'))", os.environ['ROS_ROOT']),
|
||||
("$(eval optenv('NOT_ROS_ROOT', 'alternate text'))", "alternate text"),
|
||||
("$(eval optenv('NOT_ROS_ROOT'))", ""),
|
||||
("$(eval anon('foo'))", 'bar'),
|
||||
("$(eval arg('fuga'))", 'hoge'),
|
||||
('$(eval arg("fuga"))', 'hoge'),
|
||||
('$(eval arg("arg"))', 'foo'),
|
||||
('$(eval arg("True"))', 'False'),
|
||||
('$(eval 1==1)', 'True'),
|
||||
('$(eval [0,1,2][1])', '1'),
|
||||
# test implicit arg access
|
||||
('$(eval fuga)', 'hoge'),
|
||||
('$(eval True)', 'True'),
|
||||
# math expressions
|
||||
('$(eval round(sin(pi),1))', '0.0'),
|
||||
('$(eval cos(0))', '1.0'),
|
||||
# str, map
|
||||
("$(eval ''.join(map(str, [4,2])))", '42'),
|
||||
]
|
||||
for arg, val in tests:
|
||||
assert val == resolve_args(arg, context=context), arg
|
||||
|
||||
# more #1776
|
||||
r = resolve_args('$(anon foo)/bar')
|
||||
assert '/bar' in r
|
||||
assert not '$(anon foo)' in r
|
||||
|
||||
|
||||
# test against strings that should not match
|
||||
noop_tests = [
|
||||
'$(find roslaunch', '$find roslaunch', '', ' ', 'noop', 'find roslaunch', 'env ROS_ROOT', '$$', ')', '(', '()',
|
||||
None,
|
||||
]
|
||||
for t in noop_tests:
|
||||
assert t == resolve_args(t)
|
||||
failures = [
|
||||
'$((find roslaunch))', '$(find $roslaunch)',
|
||||
'$(find)', '$(find roslaunch roslaunch)', '$(export roslaunch)',
|
||||
'$(env)', '$(env ROS_ROOT alternate)',
|
||||
'$(env NOT_SET)',
|
||||
'$(optenv)',
|
||||
'$(anon)',
|
||||
'$(anon foo bar)',
|
||||
]
|
||||
for f in failures:
|
||||
try:
|
||||
resolve_args(f)
|
||||
assert False, "resolve_args(%s) should have failed"%f
|
||||
except SubstitutionException: pass
|
||||
|
||||
|
||||
def test_resolve_duplicate_anon():
|
||||
from roslaunch.config import ROSLaunchConfig
|
||||
from roslaunch.core import RLException
|
||||
from roslaunch.xmlloader import XmlLoader
|
||||
loader = XmlLoader()
|
||||
config = ROSLaunchConfig()
|
||||
test_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'xml'))
|
||||
try:
|
||||
loader.load(os.path.join(test_path, 'test-substitution-duplicate-anon-names.xml'), config)
|
||||
assert False, 'loading a launch file with duplicate anon node names should have raised an exception'
|
||||
except RLException:
|
||||
pass
|
||||
1074
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_xmlloader.py
vendored
Normal file
1074
thirdparty/ros/ros_comm/tools/roslaunch/test/unit/test_xmlloader.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/invalid-xml.xml
vendored
Normal file
1
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/invalid-xml.xml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<blah>
|
||||
4
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/noop.launch
vendored
Normal file
4
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/noop.launch
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<launch>
|
||||
<!-- roslaunch file that loads no nodes, and thus should exit immediately -->
|
||||
<param name="noop" value="noop" />
|
||||
</launch>
|
||||
1
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/not-launch.xml
vendored
Normal file
1
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/not-launch.xml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<foo />
|
||||
48
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-all.xml
vendored
Normal file
48
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-all.xml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<launch>
|
||||
|
||||
<arg name="required" />
|
||||
<arg name="optional" default="not_set" />
|
||||
<arg name="grounded" value="parent" />
|
||||
<arg name="include_arg" value="dontcare" />
|
||||
<arg name="if_test" value="dontcare" />
|
||||
|
||||
<arg name="param1_name" value="p1" />
|
||||
<arg name="param2_name" value="p2" />
|
||||
<arg name="param3_name" value="p3" />
|
||||
|
||||
<arg name="param1_value" value="$(arg required)" />
|
||||
<arg name="param2_value" value="$(arg optional)" />
|
||||
<arg name="param3_value" value="$(arg grounded)" />
|
||||
|
||||
<param name="$(arg param1_name)_test" value="$(arg param1_value)" />
|
||||
<param name="$(arg param2_name)_test" value="$(arg param2_value)" />
|
||||
<param name="$(arg param3_name)_test" value="$(arg param3_value)" />
|
||||
|
||||
<!-- Normal include: explicitly set args -->
|
||||
<include ns="notall" file="$(find roslaunch)/test/xml/test-arg-include.xml">
|
||||
<arg name="required" value="$(arg required)" />
|
||||
<arg name="include_arg" value="$(arg include_arg)" />
|
||||
<arg name="if_test" value="$(arg if_test)" />
|
||||
</include>
|
||||
|
||||
<!-- Normal include: explicitly set args, including an optional one -->
|
||||
<include ns="notall_optional" file="$(find roslaunch)/test/xml/test-arg-include.xml">
|
||||
<arg name="required" value="$(arg required)" />
|
||||
<arg name="optional" value="$(arg optional)" />
|
||||
<arg name="include_arg" value="$(arg include_arg)" />
|
||||
<arg name="if_test" value="$(arg if_test)" />
|
||||
</include>
|
||||
|
||||
<!-- Include with passing in all args in my namespace -->
|
||||
<include ns="all" file="$(find roslaunch)/test/xml/test-arg-include.xml"
|
||||
pass_all_args="true">
|
||||
</include>
|
||||
|
||||
<!-- Include with passing in all args in my namespace, and then override one
|
||||
of them explicitly -->
|
||||
<include ns="all_override" file="$(find roslaunch)/test/xml/test-arg-include.xml"
|
||||
pass_all_args="true">
|
||||
<arg name="required" value="override" />
|
||||
</include>
|
||||
|
||||
</launch>
|
||||
26
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-include.xml
vendored
Normal file
26
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-include.xml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<launch>
|
||||
|
||||
<arg name="required" />
|
||||
<arg name="if_test" />
|
||||
<arg name="include_arg" />
|
||||
|
||||
<arg name="optional" default="not_set" />
|
||||
<arg name="grounded" value="set" />
|
||||
|
||||
<arg name="param1_name" value="p1" />
|
||||
<arg name="param2_name" value="p2" />
|
||||
<arg name="param3_name" value="p3" />
|
||||
<arg name="param4_name" value="p4" />
|
||||
|
||||
<arg name="param1_value" value="$(arg required)" />
|
||||
<arg name="param2_value" value="$(arg optional)" />
|
||||
<arg name="param3_value" value="$(arg grounded)" />
|
||||
|
||||
<group ns="include_test">
|
||||
<param name="$(arg param1_name)_test" value="$(arg param1_value)" />
|
||||
<param name="$(arg param2_name)_test" value="$(arg param2_value)" />
|
||||
<param name="$(arg param3_name)_test" value="$(arg param3_value)" />
|
||||
<param name="$(arg param4_name)_test" value="$(arg include_arg)" />
|
||||
</group>
|
||||
|
||||
</launch>
|
||||
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-invalid-include.xml
vendored
Normal file
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-invalid-include.xml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<launch>
|
||||
<include file="$(find roslaunch)/test/xml/test-arg-invalid-included.xml">
|
||||
<arg name="grounded" value="not_set"/>
|
||||
</include>
|
||||
</launch>
|
||||
6
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-invalid-include2.xml
vendored
Normal file
6
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-invalid-include2.xml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<launch>
|
||||
<include file="$(find roslaunch)/test/xml/test-arg-invalid-included.xml" pass_all_args="true">
|
||||
<arg name="grounded" value="not_set"/>
|
||||
</include>
|
||||
</launch>
|
||||
|
||||
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-invalid-included.xml
vendored
Normal file
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-invalid-included.xml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<launch>
|
||||
<arg name="grounded" value="set"/>
|
||||
</launch>
|
||||
6
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-invalid-redecl.xml
vendored
Normal file
6
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-invalid-redecl.xml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<launch>
|
||||
|
||||
<arg name="grounded" value="1"/>
|
||||
<arg name="grounded" value="regrounded"/>
|
||||
|
||||
</launch>
|
||||
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-invalid-sub.xml
vendored
Normal file
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-invalid-sub.xml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<launch>
|
||||
|
||||
<arg name="invalid" value="$(arg missing)"/>
|
||||
|
||||
</launch>
|
||||
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-valid-include.xml
vendored
Normal file
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg-valid-include.xml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<launch>
|
||||
<arg name="grounded" value="not_set"/>
|
||||
<include file="$(find roslaunch)/test/xml/test-arg-invalid-included.xml" pass_all_args="true"/>
|
||||
</launch>
|
||||
|
||||
98
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg.xml
vendored
Normal file
98
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-arg.xml
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
<launch>
|
||||
|
||||
<arg name="required" />
|
||||
<arg name="if_test" />
|
||||
|
||||
<arg name="optional" default="not_set" />
|
||||
<arg name="grounded" value="set" />
|
||||
|
||||
<arg name="include_arg" value="initial" />
|
||||
|
||||
<arg name="param1_name" value="p1" />
|
||||
<arg name="param2_name" value="p2" />
|
||||
<arg name="param3_name" value="p3" />
|
||||
|
||||
<arg name="param1_value" value="$(arg required)" />
|
||||
<arg name="param2_value" value="$(arg optional)" />
|
||||
<arg name="param3_value" value="$(arg grounded)" />
|
||||
|
||||
<arg name="int_value" value="1234" />
|
||||
<arg name="float_value" value="3.0" />
|
||||
|
||||
<arg if="$(arg if_test)" name="if_param_value" value="true" />
|
||||
<arg unless="$(arg if_test)" name="if_param_value" value="false" />
|
||||
|
||||
<arg if="$(arg if_test)" name="py_if_param_value" value="$(eval 1 == 1)"/>
|
||||
<arg unless="$(arg if_test)" name="py_if_param_value" value="$(eval 1 == 0)"/>
|
||||
|
||||
<param name="$(arg param1_name)_test" value="$(arg param1_value)" />
|
||||
<param name="$(arg param2_name)_test" value="$(arg param2_value)" />
|
||||
<param name="$(arg param3_name)_test" value="$(arg param3_value)" />
|
||||
|
||||
<param name="if_param" value="$(arg if_param_value)" />
|
||||
<param name="py_if_param" value="$(arg py_if_param_value)" />
|
||||
<param name="int_param" value="$(arg int_value)" />
|
||||
<param name="float_param" value="$(arg float_value)" />
|
||||
|
||||
<group>
|
||||
<arg name="context" value="group1" />
|
||||
<param name="context1" value="$(arg context)" />
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<arg name="context" value="group2" />
|
||||
<param name="context2" value="$(arg context)" />
|
||||
</group>
|
||||
|
||||
<arg name="fail" value="0" />
|
||||
<group if="$(arg fail)">
|
||||
<param name="fail" value="fail" />
|
||||
</group>
|
||||
|
||||
<group unless="$(arg fail)">
|
||||
<param name="succeed" value="yes" />
|
||||
</group>
|
||||
|
||||
<group if="$(arg if_test)">
|
||||
<param name="if_test" value="ran" />
|
||||
</group>
|
||||
|
||||
<group unless="$(arg if_test)">
|
||||
<param name="if_test" value="not_ran" />
|
||||
</group>
|
||||
|
||||
<arg name="py_fail" value="$(eval 0 > 1)"/>
|
||||
<group if="$(eval py_fail == True)">
|
||||
<param name="py_fail" value="fail" />
|
||||
</group>
|
||||
|
||||
<group unless="$(eval arg('py_fail') == True)">
|
||||
<param name="py_succeed" value="yes" />
|
||||
</group>
|
||||
|
||||
<group if="$(eval if_test == True)">
|
||||
<param name="py_if_test" value="ran" />
|
||||
</group>
|
||||
|
||||
<group unless="$(eval arg('if_test') == True)">
|
||||
<param name="py_if_test" value="not_ran" />
|
||||
</group>
|
||||
|
||||
<include file="$(find roslaunch)/test/xml/test-arg-include.xml">
|
||||
<arg name="required" value="required1" />
|
||||
<arg name="include_arg" value="$(arg include_arg)" />
|
||||
</include>
|
||||
|
||||
<include ns="include2" file="$(find roslaunch)/test/xml/test-arg-include.xml">
|
||||
<arg name="required" value="required2" />
|
||||
<arg name="optional" value="optional2" />
|
||||
<arg name="include_arg" value="new2" />
|
||||
</include>
|
||||
|
||||
<include if="$(arg if_test)" ns="include3" file="$(find roslaunch)/test/xml/test-arg-include.xml">
|
||||
<arg name="required" value="required3" />
|
||||
<arg name="optional" value="optional3" />
|
||||
<arg name="include_arg" value="new3" />
|
||||
</include>
|
||||
|
||||
</launch>
|
||||
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params-include.xml
vendored
Normal file
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params-include.xml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<launch>
|
||||
<!-- intentionally empty -->
|
||||
</launch>
|
||||
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params-invalid-1.xml
vendored
Normal file
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params-invalid-1.xml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<launch>
|
||||
<node pkg="package" type="test_clear_params_no_name" clear_params="true" />
|
||||
</launch>
|
||||
4
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params-invalid-2.xml
vendored
Normal file
4
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params-invalid-2.xml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<launch>
|
||||
<!-- group with no 'ns' attribute -->
|
||||
<group clear_params="true" />
|
||||
</launch>
|
||||
4
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params-invalid-3.xml
vendored
Normal file
4
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params-invalid-3.xml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<launch>
|
||||
<!-- include with no 'ns' attribute -->
|
||||
<include clear_params="true" file="$(find roslaunch)/test/xml/test-clear-params-include.xml" />
|
||||
</launch>
|
||||
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params-invalid-4.xml
vendored
Normal file
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params-invalid-4.xml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<launch>
|
||||
<node pkg="package" name="node_name" type="test_clear_params_invalid_clear_params_attr" clear_params="foo" />
|
||||
</launch>
|
||||
28
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params.xml
vendored
Normal file
28
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-clear-params.xml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
<launch>
|
||||
|
||||
<node pkg="package" type="type" name="test_clear_params1" clear_params="true" />
|
||||
<node pkg="package" type="type" name="test_clear_params2" clear_params="True" />
|
||||
<node pkg="package" type="type" name="test_clear_params_ns" ns="clear_params_ns" clear_params="true"/>
|
||||
|
||||
<node pkg="package" type="type" name="test_clear_params_default" ns="clear_params_default" />
|
||||
<node pkg="package" type="type" name="test_clear_params_false" ns="clear_params_false" clear_params="False"/>
|
||||
|
||||
<group ns="group_test" clear_params="true">
|
||||
</group>
|
||||
|
||||
<group ns="group_test_default">
|
||||
</group>
|
||||
|
||||
<group ns="group_test_false" clear_params="false">
|
||||
</group>
|
||||
|
||||
<group ns="embed_group_test">
|
||||
<group ns="embedded_group" clear_params="true" />
|
||||
</group>
|
||||
|
||||
<include ns="include_test" clear_params="true" file="$(find roslaunch)/test/xml/test-clear-params-include.xml" />
|
||||
|
||||
<include ns="include_test_false" clear_params="false" file="$(find roslaunch)/test/xml/test-clear-params-include.xml" />
|
||||
<include ns="include_test_default" file="$(find roslaunch)/test/xml/test-clear-params-include.xml" />
|
||||
|
||||
</launch>
|
||||
41
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-dump-rosparam.launch
vendored
Normal file
41
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-dump-rosparam.launch
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<launch>
|
||||
|
||||
<param name="~tilde1" value="foo" />
|
||||
<rosparam file="$(find roslaunch)/test/dump-params.yaml" command="load" />
|
||||
|
||||
<group ns="rosparam">
|
||||
<param name="~tilde2" value="bar" />
|
||||
<rosparam file="$(find roslaunch)/test/dump-params.yaml" command="load" />
|
||||
</group>
|
||||
|
||||
<node pkg="package" type="test_base" name="node_rosparam">
|
||||
<param name="local_param" value="baz" />
|
||||
<rosparam file="$(find roslaunch)/test/dump-params.yaml" command="load" />
|
||||
</node>
|
||||
|
||||
<node pkg="package" type="test_base" name="node_rosparam2">
|
||||
</node>
|
||||
|
||||
<rosparam param="inline_str">value1</rosparam>
|
||||
<rosparam param="inline_list">[1, 2, 3, 4]</rosparam>
|
||||
<rosparam param="inline_dict">{key1: value1, key2: value2}</rosparam>
|
||||
<rosparam param="inline_dict2">
|
||||
key3: value3
|
||||
key4: value4
|
||||
</rosparam>
|
||||
|
||||
<rosparam param="override">{key1: value1, key2: value2}</rosparam>
|
||||
<rosparam param="override">{key1: override1}</rosparam>
|
||||
|
||||
<!-- no key tests -->
|
||||
<rosparam>
|
||||
noparam1: value1
|
||||
noparam2: value2
|
||||
</rosparam>
|
||||
|
||||
<!-- allow empty files. we test absense of loading in test-rosparam-empty.
|
||||
Here we include to make sure this doesn't blank out other parameters -->
|
||||
<rosparam file="$(find roslaunch)/test/params_empty1.yaml" command="load" />
|
||||
|
||||
</launch>
|
||||
|
||||
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-env-include.xml
vendored
Normal file
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-env-include.xml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<launch>
|
||||
<node name="n12i" pkg="package" type="test_one_two_include" />
|
||||
</launch>
|
||||
18
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-env.xml
vendored
Normal file
18
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-env.xml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
<launch>
|
||||
<node name="n0" pkg="package" type="test_none" />
|
||||
<env name="ONE" value="one" />
|
||||
<node name="n1" pkg="package" type="test_one">
|
||||
</node>
|
||||
|
||||
<env name="TWO" value="two" />
|
||||
<node name="n2" pkg="package" type="test_one_two_priv">
|
||||
<env name="PRIVATE_TWO" value="private_two" />
|
||||
</node>
|
||||
|
||||
<node name="n3" pkg="package" type="test_one_two" />
|
||||
|
||||
<include file="$(find roslaunch)/test/xml/test-env-include.xml">
|
||||
<env name="INCLUDE" value="include" />
|
||||
</include>
|
||||
|
||||
</launch>
|
||||
6
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-if-unless-invalid-both.xml
vendored
Normal file
6
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-if-unless-invalid-both.xml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<launch>
|
||||
|
||||
<group if="1" unless="1">
|
||||
</group>
|
||||
|
||||
</launch>
|
||||
63
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-if-unless.xml
vendored
Normal file
63
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-if-unless.xml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
<launch>
|
||||
|
||||
<group if="1">
|
||||
<param name="group_if_pass" value="1" />
|
||||
</group>
|
||||
<group if="0">
|
||||
<param name="group_if_fail" value="1" />
|
||||
</group>
|
||||
<group unless="0">
|
||||
<param name="group_unless_pass" value="1" />
|
||||
</group>
|
||||
<group unless="1">
|
||||
<param name="group_unless_fail" value="1" />
|
||||
</group>
|
||||
|
||||
<param name="param_if_pass" value="1" if="1" />
|
||||
<param name="param_if_fail" value="1" if="0" />
|
||||
|
||||
<param name="param_unless_pass" value="1" unless="0" />
|
||||
<param name="param_unless_fail" value="1" unless="1" />
|
||||
|
||||
<remap from="from_if_pass" to="to_if_pass" if="1" />
|
||||
<remap from="from_if_fail" to="to_if_fail" if="0" />
|
||||
|
||||
<remap from="from_unless_pass" to="to_unless_pass" unless="0" />
|
||||
<remap from="from_unless_fail" to="to_unless_fail" unless="1" />
|
||||
|
||||
<!-- Test Python parsing -->
|
||||
<group if="$(eval 1 == 1)">
|
||||
<param name="py_group_if_pass" value="1" />
|
||||
</group>
|
||||
<group if="$(eval 0 == 1)">
|
||||
<param name="py_group_if_fail" value="1" />
|
||||
</group>
|
||||
<group unless="$(eval 0 == 1)">
|
||||
<param name="py_group_unless_pass" value="1" />
|
||||
</group>
|
||||
<group unless="$(eval 1 == 1)">
|
||||
<param name="py_group_unless_fail" value="1" />
|
||||
</group>
|
||||
|
||||
<arg name="true_lower" value="true"/>
|
||||
<arg name="true_upper" value="True"/>
|
||||
<arg name="false_lower" value="false"/>
|
||||
<arg name="false_upper" value="False"/>
|
||||
|
||||
<param name="py_param_if_pass" value="1" if="$(eval arg('true_lower') and (40+2) == 42)"/>
|
||||
<param name="py_param_if_fail" value="1" if="$(eval false_lower or (40-2) != 38)"/>
|
||||
|
||||
<param name="py_param_unless_pass" value="1" unless="$(eval arg('false_upper') or 1 < 1)"/>
|
||||
<param name="py_param_unless_fail" value="1" unless="$(eval true_upper and 1 >= 1)"/>
|
||||
|
||||
<remap from="py_from_if_pass" to="py_to_if_pass" if="$(eval arg('true_upper') == True)"/>
|
||||
<remap from="py_from_if_fail" to="py_to_if_fail" if="$(eval false_upper != False)"/>
|
||||
|
||||
<remap from="py_from_unless_pass" to="py_to_unless_pass"
|
||||
unless="$(eval arg('false_lower') or false_upper or arg('false_upper') != False)"/>
|
||||
<remap from="py_from_unless_fail" to="py_to_unless_fail"
|
||||
unless="$(eval arg('true_lower') and true_upper and arg('true_upper') == True)"/>
|
||||
|
||||
<node name="remap" pkg="rospy" type="talker.py" />
|
||||
|
||||
</launch>
|
||||
20
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-local-param-group.xml
vendored
Normal file
20
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-local-param-group.xml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<launch>
|
||||
|
||||
<!-- no local params -->
|
||||
<node pkg="pkg" type="type0" name="node0"/>
|
||||
|
||||
<group ns="group1">
|
||||
<!-- basic test, named nodes -->
|
||||
<node pkg="pkg" type="gtype0" name="g1node0" />
|
||||
<param name="~gparam1" value="val1" />
|
||||
<node pkg="pkg" type="g1type1" name="g1node1" />
|
||||
<param name="~gparam2" value="val1" />
|
||||
<node pkg="pkg" type="g1type2" name="g1node2" />
|
||||
</group>
|
||||
|
||||
<!-- make sure scope is cleared by end of group -->
|
||||
<param name="~param1" value="val1" />
|
||||
<node pkg="pkg" type="type1" name="node1" />
|
||||
|
||||
|
||||
</launch>
|
||||
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-10.xml
vendored
Normal file
3
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-10.xml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<launch>
|
||||
<machine name="machine3" address="address3" ros-ip="hostname" />
|
||||
</launch>
|
||||
9
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-11.xml
vendored
Normal file
9
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-11.xml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<launch>
|
||||
|
||||
<machine name="machine4" address="address4">
|
||||
<env name="ENV1" value="value1" />
|
||||
<env name="ENV2" value="value2" />
|
||||
<env name="ENV3" value="value3" />
|
||||
</machine>
|
||||
|
||||
</launch>
|
||||
6
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-12.xml
vendored
Normal file
6
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-12.xml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<launch>
|
||||
|
||||
<!-- if ros-root is explicitly set and ros-package-path is not, ros-package-path should not get a default value -->
|
||||
<machine name="machine5" address="address5" ros-root="/ros/root" />
|
||||
|
||||
</launch>
|
||||
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-4.xml
vendored
Normal file
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-4.xml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<launch>
|
||||
<!-- address is required -->
|
||||
<machine name="machine1" />
|
||||
</launch>
|
||||
|
||||
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-5.xml
vendored
Normal file
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-5.xml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<launch>
|
||||
<!-- name is required -->
|
||||
<machine address="address1" />
|
||||
</launch>
|
||||
|
||||
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-6.xml
vendored
Normal file
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-6.xml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<launch>
|
||||
<!-- timeout must be non-empty -->
|
||||
<machine timeout="" name="one" address="address1" />
|
||||
</launch>
|
||||
|
||||
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-7.xml
vendored
Normal file
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-7.xml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<launch>
|
||||
<!-- timeout must be a number -->
|
||||
<machine timeout="ten" name="one" address="address1" />
|
||||
</launch>
|
||||
|
||||
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-8.xml
vendored
Normal file
5
thirdparty/ros/ros_comm/tools/roslaunch/test/xml/test-machine-invalid-8.xml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<launch>
|
||||
<!-- timeout must be a number -->
|
||||
<machine timeout="-1" name="one" address="address1" />
|
||||
</launch>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user