Package roshlaunch :: Module core
[frames] | no frames]

Source Code for Module roshlaunch.core

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2008, Willow Garage, Inc. 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32  # 
 33  # Revision $Id: core.py 10265 2010-07-06 18:52:04Z kwc $ 
 34   
 35  """ 
 36  Core roslaunch model and lower-level utility routines. 
 37  """ 
 38   
 39  import os 
 40  import logging 
 41  import socket 
 42  import sys 
 43   
 44  import roslib.names  
 45  import roslib.network 
 46  # import roslib.substitution_args 
 47  import roslib.rosenv 
 48   
 49  #TODO:temporary until xacro is ported after next ROS stable release 
 50  # resolve_args = roslib.substitution_args.resolve_args 
 51   
52 -class RLException(Exception):
53 """Base roslaunch exception type""" 54 pass
55 56 57 ## Phases allow executables to be assigned to a particular run period 58 PHASE_SETUP = 'setup' 59 PHASE_RUN = 'run' 60 PHASE_TEARDOWN = 'teardown' 61 62 # ROSHTODO: get rid of is_child_mode() and replace with 'logerr_output' option 63 _child_mode = False
64 -def is_child_mode():
65 """ 66 @return: True if roslaunch is running in remote child mode 67 @rtype: bool 68 """ 69 return _child_mode
70 -def set_child_mode(child_mode):
71 """ 72 @param child_mode: True if roslaunch is running in remote 73 child mode 74 @type child_mode: bool 75 """ 76 global _child_mode 77 _child_mode = child_mode
78
79 -def is_machine_local(machine):
80 """ 81 Check to see if machine is local. NOTE: a machine is not local if 82 its user credentials do not match the current user. 83 @param machine: Machine 84 @type machine: L{Machine} 85 @return: True if machine is local and doesn't require remote login 86 @rtype: bool 87 """ 88 try: 89 machine_addr = socket.gethostbyname(machine.address) 90 except socket.gaierror: 91 raise RLException("cannot resolve host address for machine [%s]"%machine.address) 92 local_addresses = ['localhost'] + roslib.network.get_local_addresses() 93 # check 127/8 and local addresses 94 is_local = machine_addr.startswith('127.') or machine_addr in local_addresses 95 96 #491: override local to be ssh if machine.user != local user 97 if is_local and machine.user: 98 import getpass 99 is_local = machine.user == getpass.getuser() 100 return is_local
101 102 _printlog_handlers = [] 103 _printerrlog_handlers = []
104 -def printlog(msg):
105 """ 106 Core utility for printing message to stdout as well as printlog handlers 107 @param msg: message to print 108 @type msg: str 109 """ 110 for h in _printlog_handlers: 111 try: # don't let this bomb out the actual code 112 h(msg) 113 except: 114 pass 115 try: # don't let this bomb out the actual code 116 print msg 117 except: 118 pass
119
120 -def printlog_bold(msg):
121 """ 122 Similar to L{printlog()}, but the message printed to screen is bolded for greater clarity 123 @param msg: message to print 124 @type msg: str 125 """ 126 for h in _printlog_handlers: 127 try: # don't let this bomb out the actual code 128 h(msg) 129 except: 130 pass 131 try: # don't let this bomb out the actual code 132 print '\033[1m%s\033[0m'%msg 133 except: 134 pass
135
136 -def printerrlog(msg):
137 """ 138 Core utility for printing message to stderr as well as printerrlog handlers 139 @param msg: message to print 140 @type msg: str 141 """ 142 for h in _printerrlog_handlers: 143 try: # don't let this bomb out the actual code 144 h(msg) 145 except: 146 pass 147 # #1003: this has raised IOError (errno 104) in robot use. Better to 148 # trap than let a debugging routine fault code. 149 try: # don't let this bomb out the actual code 150 print >> sys.stderr, '\033[31m%s\033[0m'%msg 151 except: 152 pass
153
154 -def add_printlog_handler(h):
155 """ 156 Register additional handler for printlog() 157 """ 158 _printlog_handlers.append(h)
159
160 -def add_printerrlog_handler(h):
161 """ 162 Register additional handler for printerrlog() 163 """ 164 _printerrlog_handlers.append(h)
165
166 -def clear_printlog_handlers():
167 """ 168 Delete all printlog handlers. required for testing 169 """ 170 del _printlog_handlers[:]
171
172 -def clear_printerrlog_handlers():
173 """ 174 Delete all printerrlog handlers. required for testing 175 """ 176 del _printerrlog_handlers[:]
177
178 -def setup_env(node, machine, master_uri):
179 """ 180 Create dictionary of environment variables to set for launched 181 process. 182 183 setup_env() will only set ROS_*, PYTHONPATH, and user-specified 184 environment variables. 185 186 @param machine Machine: machine being launched on 187 @type machine: L{Machine} 188 @param node: node that is being launched or None 189 @type node: L{Node} 190 @param master_uri: ROS master URI 191 @type master_uri: str 192 @return: process env dictionary 193 @rtype: dict 194 """ 195 d = {} 196 d[roslib.rosenv.ROS_MASTER_URI] = master_uri 197 d[roslib.rosenv.ROS_ROOT] = machine.ros_root or get_ros_root() 198 if machine.ros_package_path: #optional 199 d[roslib.rosenv.ROS_PACKAGE_PATH] = machine.ros_package_path 200 if machine.ros_ip: #optional 201 d[roslib.rosenv.ROS_IP] = machine.ros_ip 202 203 # roslib now depends on PYTHONPATH being set. This environment 204 # needs to be setup correctly for roslaunch through ssh to work 205 d['PYTHONPATH'] = os.path.join(d[roslib.rosenv.ROS_ROOT],'core','roslib', 'src') 206 207 # load in machine env_args. Node env args have precedence 208 for name, value in machine.env_args: 209 d[name] = value 210 211 # add node-specific env args last as they have highest precedence 212 if node: 213 ns = node.namespace 214 if ns[-1] == '/': 215 ns = ns[:-1] 216 if ns: 217 d[roslib.rosenv.ROS_NAMESPACE] = ns 218 for name, value in node.env_args: 219 d[name] = value 220 221 return d
222
223 -def rle_wrapper(fn):
224 """ 225 Wrap lower-level exceptions in RLException class 226 @return: function wrapper that throws an RLException if the 227 wrapped function throws an Exception 228 @rtype: fn 229 """ 230 def wrapped_fn(*args): 231 try: 232 return fn(*args) 233 except Exception, e: 234 # we specifically catch RLExceptions and print their messages differently 235 raise RLException("ERROR: %s"%e)
236 return wrapped_fn 237 238 get_ros_root = rle_wrapper(roslib.rosenv.get_ros_root) 239 get_master_uri_env = rle_wrapper(roslib.rosenv.get_master_uri)
240 -def get_ros_package_path():
241 """ 242 @return: ROS_PACKAGE_PATH value 243 @rtype: str 244 """ 245 # ROS_PACKAGE_PATH not required to be set 246 return roslib.rosenv.get_ros_package_path(required=False)
247
248 -def remap_localhost_uri(uri, force_localhost=False):
249 """ 250 Resolve localhost addresses to an IP address so that 251 @param uri: XML-RPC URI 252 @type uri: str 253 @param force_localhost: if True, URI is mapped onto the local machine no matter what 254 @type force_localhost: bool 255 """ 256 hostname, port = roslib.network.parse_http_host_and_port(uri) 257 if force_localhost or hostname == 'localhost': 258 return roslib.network.create_local_xmlrpc_uri(port) 259 else: 260 return uri
261 262 ################################################################## 263 # DATA STRUCTURES 264
265 -class Master:
266 """ 267 Data structure for representing and querying state of master 268 """ 269 __slots__ = ['type', 'auto', 'uri'] 270 ROSMASTER = 'rosmaster' 271
272 - def __init__(self, type_=None, uri=None, auto=None):
273 """ 274 Create new Master instance. 275 @param uri: master URI. Defaults to ROS_MASTER_URI environment variable. 276 @type uri: str 277 @param type_: Currently only support 'rosmaster' 278 @type type_: str 279 """ 280 if auto is not None and type(auto) != int: 281 raise RLException("invalid auto value: %s"%auto) 282 self.type = type_ or Master.ROSMASTER 283 self.uri = uri or get_master_uri_env()
284
285 - def get_host(self):
286 # parse from the URI 287 host, _ = roslib.network.parse_http_host_and_port(self.uri) 288 return host
289
290 - def get_port(self):
291 """ 292 Get the port this master is configured for. 293 """ 294 # parse from the URI 295 _, urlport = roslib.network.parse_http_host_and_port(self.uri) 296 return urlport
297
298 - def __eq__(self, m2):
299 if not isinstance(m2, Master): 300 return False 301 else: 302 return m2.type == self.type and m2.uri == self.uri
303
304 - def get(self):
305 """ 306 @return: XMLRPC proxy for communicating with master 307 @rtype: xmlrpclib.ServerProxy 308 """ 309 import xmlrpclib 310 return xmlrpclib.ServerProxy(self.uri)
311
312 - def get_multi(self):
313 """ 314 @return: multicall XMLRPC proxy for communicating with master 315 @rtype: xmlrpclib.MultiCall 316 """ 317 import xmlrpclib 318 return xmlrpclib.MultiCall(self.get())
319
320 - def is_running(self):
321 """ 322 Check if master is running. 323 @return: True if the master is running 324 @rtype: bool 325 """ 326 try: 327 try: 328 to_orig = socket.getdefaulttimeout() 329 # enable timeout 330 socket.setdefaulttimeout(5.0) 331 logging.getLogger('roslaunch').info('master.is_running[%s]'%self.uri) 332 code, status, val = self.get().getPid('/roslaunch') 333 if code != 1: 334 raise RLException("ERROR: master failed status check: %s"%msg) 335 logging.getLogger('roslaunch.core').debug('master.is_running[%s]: True'%self.uri) 336 return True 337 finally: 338 socket.setdefaulttimeout(to_orig) 339 except: 340 logging.getLogger('roslaunch.core').debug('master.is_running[%s]: True'%self.uri) 341 return False
342 343 ## number of seconds that a child machine is allowed to register with 344 ## the parent before being considered failed 345 _DEFAULT_REGISTER_TIMEOUT = 10.0 346
347 -class Machine(object):
348 """ 349 Data structure for storing information about a machine in the ROS 350 system. Corresponds to the 'machine' tag in the launch 351 specification. 352 """ 353 __slots__ = ['name', 'ros_root', 'ros_package_path', 'ros_ip',\ 354 'address', 'ssh_port', 'user', 'password', 'assignable',\ 355 'env_args', 'timeout']
356 - def __init__(self, name, ros_root, ros_package_path, \ 357 address, ros_ip=None, ssh_port=22, user=None, password=None, \ 358 assignable=True, env_args=[], timeout=None):
359 """ 360 @param name: machine name 361 @type name: str 362 @param ros_root: ROS_ROOT on machine 363 @type ros_root: str 364 @param ros_package_path: ROS_PACKAGE_PATH on machine 365 @type ros_package_path: str 366 @param address: network address of machine 367 @type address: str 368 @param ros_ip: ROS_IP on machine 369 @type ros_ip: str 370 @param ssh_port: SSH port number 371 @type ssh_port: int 372 @param user: SSH username 373 @type user: str 374 @param password: SSH password. Not recommended for use. Use SSH keys instead. 375 @type password: str 376 """ 377 self.name = name 378 self.ros_root = ros_root 379 self.ros_package_path = ros_package_path 380 self.ros_ip = ros_ip or None 381 self.user = user or None 382 self.password = password or None 383 self.address = address 384 self.ssh_port = ssh_port 385 self.assignable = assignable 386 self.env_args = env_args 387 self.timeout = timeout or _DEFAULT_REGISTER_TIMEOUT
388
389 - def __str__(self):
390 return "Machine(name[%s] ros_root[%s] ros_package_path[%s] ros_ip[%s] address[%s] ssh_port[%s] user[%s] assignable[%s] env_args[%s] timeout[%s])"%(self.name, self.ros_root, self.ros_package_path, self.ros_ip, self.address, self.ssh_port, self.user, self.assignable, str(self.env_args), self.timeout)
391 - def __eq__(self, m2):
392 if not isinstance(m2, Machine): 393 return False 394 return self.name == m2.name and \ 395 self.assignable == m2.assignable and \ 396 self.config_equals(m2)
397
398 - def config_key(self):
399 """ 400 Get a key that represents the configuration of the 401 machine. machines with identical configurations have identical 402 keys 403 404 @return: configuration key 405 @rtype: str 406 """ 407 return "Machine(address[%s] ros_root[%s] ros_package_path[%s] ros_ip[%s] ssh_port[%s] user[%s] password[%s] env_args[%s] timeout[%s])"%(self.address, self.ros_root, self.ros_package_path, self.ros_ip or '', self.ssh_port, self.user or '', self.password or '', str(self.env_args), self.timeout)
408
409 - def config_equals(self, m2):
410 """ 411 @return: True if machines have identical configurations 412 @rtype: bool 413 """ 414 if not isinstance(m2, Machine): 415 return False 416 return self.ros_root == m2.ros_root and \ 417 self.ros_package_path == m2.ros_package_path and \ 418 self.ros_ip == m2.ros_ip and \ 419 self.address == m2.address and \ 420 self.ssh_port == m2.ssh_port and \ 421 self.user == m2.user and \ 422 self.password == m2.password and \ 423 set(self.env_args) == set(m2.env_args) and \ 424 self.timeout == m2.timeout
425
426 - def __ne__(self, m2):
427 return not self.__eq__(m2)
428
429 -class Param(object):
430 """ 431 Data structure for storing information about a desired parameter in 432 the ROS system Corresponds to the 'param' tag in the launch 433 specification. 434 """
435 - def __init__(self, key, value):
436 self.key = roslib.names.canonicalize_name(key) 437 self.value = value
438 - def __eq__(self, p):
439 if not isinstance(p, Param): 440 return False 441 return p.key == self.key and p.value == self.value
442 - def __ne__(self, p):
443 return not self.__eq__(p)
444 - def __str__(self):
445 return "%s=%s"%(self.key, self.value)
446 - def __repr__(self):
447 return "%s=%s"%(self.key, self.value)
448 449 _local_m = None
450 -def local_machine():
451 """ 452 @return: Machine instance representing the local machine 453 @rtype: L{Machine} 454 """ 455 global _local_m 456 if _local_m is None: 457 _local_m = Machine('', get_ros_root(), \ 458 get_ros_package_path(), 'localhost',\ 459 ros_ip=roslib.network.get_address_override()) 460 return _local_m
461
462 -class Node(object):
463 """ 464 Data structure for storing information about a desired node in 465 the ROS system Corresponds to the 'node' tag in the launch 466 specification. 467 """ 468 __slots__ = ['package', 'type', 'name', 'namespace', \ 469 'machine_name', 'machine', 'args', 'respawn', \ 470 'remap_args', 'env_args',\ 471 'process_name', 'output', 'cwd', 472 'launch_prefix', 'required', 473 'filename'] 474
475 - def __init__(self, package, node_type, name=None, namespace='/', \ 476 machine_name=None, args='', respawn=False, \ 477 remap_args=None,env_args=None, output=None, cwd=None, \ 478 launch_prefix=None, required=False, filename='<unknown>'):
479 """ 480 @param package: node package name 481 @type package: str 482 @param node_type: node type 483 @type node_type: str 484 @param name: node name 485 @type name: str 486 @param namespace: namespace for node 487 @type namespace: str 488 @param machine_name: name of machine to run node on 489 @type machine_name: str 490 @param args: argument string to pass to node executable 491 @type args: str 492 @param respawn: if True, respawn node if it dies 493 @type respawn: bool 494 @param remap_args: list of [(from, to)] remapping arguments 495 @type remap_args: [(str, str)]: 496 @param env_args: list of [(key, value)] of 497 additional environment vars to set for node 498 @type env_args: [(str, str)] 499 @param output: where to log output to, either Node, 'screen' or 'log' 500 @type output: str 501 @param cwd: current working directory of node, either 'node', 'ROS_HOME' or 'ros-root'. Default: ROS_HOME 502 @type cwd: str 503 @param launch_prefix: launch command/arguments to prepend to node executable arguments 504 @type launch_prefix: str 505 @param required: node is required to stay running (launch fails if node dies) 506 @type required: bool 507 @param filename: name of file Node was parsed from 508 @type filename: str 509 510 @raise ValueError: if parameters do not validate 511 """ 512 513 self.package = package 514 self.type = node_type 515 self.name = name or None 516 self.namespace = roslib.names.make_global_ns(namespace or '/') 517 self.machine_name = machine_name or None 518 self.respawn = respawn 519 self.args = args or '' 520 self.remap_args = remap_args or [] 521 self.env_args = env_args or [] 522 self.output = output 523 self.cwd = cwd 524 if self.cwd == 'ros_home': # be lenient on case 525 self.cwd = 'ROS_HOME' 526 elif self.cwd == 'ros-root': 527 printerrlog("WARNING: 'ros-root' value for <node> 'cwd' attribute is deprecated.") 528 529 self.launch_prefix = launch_prefix or None 530 self.required = required 531 self.filename = filename 532 533 if self.respawn and self.required: 534 raise ValueError("respawn and required cannot both be set to true") 535 536 # validation 537 if self.name and roslib.names.SEP in self.name: # #1821, namespaces in nodes need to be banned 538 raise ValueError("node name cannot contain a namespace") 539 if not len(self.package.strip()): 540 raise ValueError("package must be non-empty") 541 if not len(self.type.strip()): 542 raise ValueError("type must be non-empty") 543 if not self.output in ['log', 'screen', None]: 544 raise ValueError("output must be one of 'log', 'screen'") 545 if not self.cwd in ['ROS_HOME', 'ros-root', 'node', None]: 546 raise ValueError("cwd must be one of 'ROS_HOME', 'ros-root', 'node'") 547 548 # Extra slots for assigning later 549 550 # slot to store the process name in so that we can query the 551 # associated process state 552 self.process_name = None 553 554 # machine is the assigned machine instance. should probably 555 # consider storing this elsewhere as it can be inconsistent 556 # with machine_name and is also a runtime, rather than 557 # configuration property 558 self.machine = None
559 560 561
562 - def xmltype(self):
563 return 'node'
564
565 - def xmlattrs(self):
566 name_str = cwd_str = respawn_str = None 567 if self.name: 568 name_str = self.name 569 if self.cwd: 570 cwd_str = self.cwd 571 572 return [ 573 ('pkg', self.package), 574 ('type', self.type), 575 ('machine', self.machine_name), 576 ('ns', self.namespace), 577 ('args', self.args), 578 ('output', self.output), 579 ('cwd', cwd_str), 580 ('respawn', self.respawn), #not valid on <test> 581 ('name', name_str), 582 ('launch-prefix', self.launch_prefix), 583 ('required', self.required), 584 ]
585 586 #TODO: unify with to_remote_xml using a filter_fn
587 - def to_xml(self):
588 """ 589 convert representation into XML representation. Currently cannot represent private parameters. 590 @return: XML representation for remote machine 591 @rtype: str 592 """ 593 t = self.xmltype() 594 attrs = [(a, v) for a, v in self.xmlattrs() if v != None] 595 xmlstr = '<%s %s>\n'%(t, ' '.join(['%s="%s"'%(val[0], _xml_escape(val[1])) for val in attrs])) 596 xmlstr += ''.join([' <remap from="%s" to="%s" />\n'%tuple(r) for r in self.remap_args]) 597 xmlstr += ''.join([' <env name="%s" value="%s" />\n'%tuple(e) for e in self.env_args]) 598 xmlstr += "</%s>"%t 599 return xmlstr
600
601 - def to_remote_xml(self):
602 """ 603 convert representation into remote representation. Remote representation does 604 not include parameter settings or 'machine' attribute 605 @return: XML representation for remote machine 606 @rtype: str 607 """ 608 t = self.xmltype() 609 attrs = [(a, v) for a, v in self.xmlattrs() if v != None and a != 'machine'] 610 xmlstr = '<%s %s>\n'%(t, ' '.join(['%s="%s"'%(val[0], _xml_escape(val[1])) for val in attrs])) 611 xmlstr += ''.join([' <remap from="%s" to="%s" />\n'%tuple(r) for r in self.remap_args]) 612 xmlstr += ''.join([' <env name="%s" value="%s" />\n'%tuple(e) for e in self.env_args]) 613 xmlstr += "</%s>"%t 614 return xmlstr
615
616 -def _xml_escape(s):
617 """ 618 Escape string for XML 619 @param s: string to escape 620 @type s: str 621 @return: string with XML entities (<, >, ", &) escaped. 622 @rtype: str 623 """ 624 # gross, but doesn't need to be fast. always replace amp first 625 s = str(s) 626 s = s.replace('&', '&amp;') 627 s = s.replace('"', '&quot;') 628 s = s.replace('>', '&gt;') 629 s = s.replace('<', '&lt;') 630 return s
631 632 TEST_TIME_LIMIT_DEFAULT = 1 * 60 #seconds 633 634
635 -class Test(Node):
636 """ 637 A Test is a Node with special semantics that it performs a 638 unit/integration test. The data model is the same except the 639 option to set the respawn flag is removed. 640 """ 641 __slots__ = ['test_name', 'time_limit', 'retry'] 642
643 - def __init__(self, test_name, package, node_type, name=None, \ 644 namespace='/', machine_name=None, args='', \ 645 remap_args=None, env_args=None, time_limit=None, cwd=None, 646 launch_prefix=None, retry=None, filename="<unknown>"):
647 """ 648 Construct a new test node. 649 @param test_name: name of test for recording in test results 650 @type test_name: str 651 @param time_limit: number of seconds that a test 652 should run before marked as a failure 653 @type time_limit: int/float/long 654 """ 655 super(Test, self).__init__(package, node_type, name=name, \ 656 namespace=namespace, \ 657 machine_name=machine_name, args=args, \ 658 remap_args=remap_args, 659 env_args=env_args, 660 #output always is log 661 output='log', cwd=cwd, 662 launch_prefix=launch_prefix, filename=filename) 663 self.test_name = test_name 664 665 self.retry = retry or 0 666 time_limit = time_limit or TEST_TIME_LIMIT_DEFAULT 667 if not type(time_limit) in (float, int, long): 668 raise ValueError("'time-limit' must be a number") 669 time_limit = float(time_limit) #force to floating point 670 if time_limit <= 0: 671 raise ValueError("'time-limit' must be a positive number") 672 673 self.time_limit = time_limit
674
675 - def xmltype(self):
676 return 'test'
677
678 - def xmlattrs(self):
679 """ 680 NOTE: xmlattrs does not necessarily produce identical XML as 681 to what it was initialized with, though the properties are the same 682 """ 683 attrs = Node.xmlattrs(self) 684 attrs = [(a, v) for (a, v) in attrs if a != 'respawn'] 685 attrs.append(('test-name', self.test_name)) 686 687 if self.retry: 688 attrs.append(('retry', str(self.retry))) 689 if self.time_limit != TEST_TIME_LIMIT_DEFAULT: 690 attrs.append(('time-limit', self.time_limit)) 691 return attrs
692 693
694 -class Executable(object):
695 """ 696 Executable is a generic container for exectuable commands. 697 """ 698
699 - def __init__(self, cmd, args, phase=PHASE_RUN):
700 """ 701 @param cmd: name of command to run 702 @type cmd: str 703 @param args: arguments to command 704 @type args: (str,) 705 @param phase: PHASE_SETUP|PHASE_RUN|PHASE_TEARDOWN. Indicates whether the 706 command should be run before, during, or after launch. 707 @type phase: str 708 """ 709 self.command = cmd 710 self.args = args 711 self.phase = phase
712 - def __repr__(self):
713 return "%s %s"%(self.command, ' '.join(self.args))
714 - def __str__(self):
715 return "%s %s"%(self.command, ' '.join(self.args))
716
717 -class RosbinExecutable(Executable):
718 """ 719 RosbinExecutables are exectuables stored in ROS_ROOT/bin. 720 """
721 - def __init__(self, cmd, args, phase=PHASE_RUN):
722 super(RosbinExecutable, self).__init__(cmd, args, phase)
723 - def __repr__(self):
724 return "ros/bin/%s %s"%(self.command, ' '.join(self.args))
725 - def __str__(self):
726 return "ros/bin/%s %s"%(self.command, ' '.join(self.args))
727 728
729 -def generate_run_id():
730 """ 731 Utility routine for generating run IDs (UUIDs) 732 @return: guid 733 @rtype: str 734 """ 735 import uuid 736 return str(uuid.uuid1())
737