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

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