runner.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # License: BSD
4 # https://raw.github.com/robotics-in-concert/rocon_multimaster/license/LICENSE
5 #
6 
7 ##############################################################################
8 # Imports
9 ##############################################################################
10 
11 from __future__ import print_function
12 
13 import os
14 import sys
15 import unittest
16 from urlparse import urlparse
17 import rospkg
18 import roslib
19 import roslaunch
20 import rosunit
21 
22 # Local imports
23 from loggers import printlog, printlogerr
24 from test_parent import RoconTestLaunchParent
25 
26 ##############################################################################
27 # Globals
28 ##############################################################################
29 
30 _DEFAULT_TEST_PORT = 22422
31 _results = rosunit.junitxml.Result('rocon_test', 0, 0, 0)
32 _text_mode = False
33 _pause_mode = False
34 
35 
37  return _results
38 
39 
40 def _accumulate_results(results):
41  _results.accumulate(results)
42 
43 
44 def set_text_mode(val):
45  global _text_mode
46  _text_mode = val
47 
48 
49 def set_pause_mode(val):
50  global _pause_mode
51  _pause_mode = val
52 
53 ##############################################################################
54 # Parents
55 ##############################################################################
56 
57 # global store of all ROSLaunchRunners so we can do an extra shutdown
58 # in the rare event a tearDown fails to execute
59 _test_parents = []
60 # Is _config actually used anywhere?
61 _config = None
62 
63 
65  global _test_parents, _config
66  _test_parents.append(runner)
67  _config = runner.config
68 
69 
71  return _test_parents
72 
73 
74 ##############################################################################
75 # Construction Methods
76 ##############################################################################
77 
79  '''
80  Provides configuration details for a rocon test launch configuration
81  associated with a single master.
82 
83  :param launcher: a roslaunch test configuration
84  :type launcher: :class:`rocon_launch.RosLaunchConfiguration`
85  '''
86  def __init__(self, launcher):
87  # Launcher configuration (name, port, path to file, etch)
88  self.launcher = launcher
89  # ros launch configuration info
90  self.configuration = roslaunch.parent.load_config_default([launcher.path], launcher.port)
91  self.test_parent = None # ros test launcher parent, handles start, stop, shutdown
92 
93 
94 def setUp(self):
95  '''
96  Function that becomes TestCase.setUp()
97 
98  For each launcher representing a launch on a single master in the rocon
99  launcher, this makes sure there is an entity that is to be the 'parent'
100  who will later be responsible for shutting everything down.
101 
102  This could be prone to problems if someone puts multiple test launchers in
103  rocon launcher with the same master port (untested).
104  '''
105  # new parent for each run. we are a bit inefficient as it would be possible to
106  # reuse the roslaunch base infrastructure for each test, but the roslaunch code
107  # is not abstracted well enough yet
108  uuids = {}
109  for rocon_launch_configuration in self.rocon_launch_configurations:
110  config = rocon_launch_configuration.configuration
111  launcher = rocon_launch_configuration.launcher
112  o = urlparse(config.master.uri)
113  if o.port not in uuids.keys():
114  uuids[o.port] = roslaunch.core.generate_run_id()
115  if not config.tests:
116  rocon_launch_configuration.parent = roslaunch.parent.ROSLaunchParent(
117  uuids[o.port],
118  [launcher.path],
119  is_core=False,
120  port=o.port,
121  verbose=False,
122  force_screen=False,
123  is_rostest=False
124  )
125  rocon_launch_configuration.parent._load_config()
126  rocon_launch_configuration.parent.start()
127  printlog("Launch Parent - type ...............ROSLaunchParent")
128  else:
129  rocon_launch_configuration.parent = RoconTestLaunchParent(uuids[o.port], config, [launcher.path], port=o.port)
130  rocon_launch_configuration.parent.setUp()
131  # the config attribute makes it easy for tests to access the ROSLaunchConfig instance
132  # Should we do this - it doesn't make a whole lot of sense?
133  #rocon_launch_configuration.configuration = rocon_launch_configuration.parent.config
134  _add_rocon_test_parent(rocon_launch_configuration.parent)
135  printlog("Launch Parent - type ...............ROSTestLaunchParent")
136  printlog("Launch Parent - run id..............%s" % rocon_launch_configuration.parent.run_id)
137  printlog("Launch Parent - launcher............%s" % rocon_launch_configuration.launcher.path)
138  printlog("Launch Parent - port................%s" % o.port)
139  if not config.tests:
140  printlog("Launch Parent - tests...............no")
141  else:
142  printlog("Launch Parent - tests...............yes")
143 
144 
145 def tearDown(self):
146  '''
147  Function that becomes TestCase.tearDown()
148  '''
149  for rocon_launch_configuration in self.rocon_launch_configurations:
150  config = rocon_launch_configuration.configuration
151  parent = rocon_launch_configuration.parent
152  launcher = rocon_launch_configuration.launcher
153  if _pause_mode:
154  raw_input("Press Enter to continue...")
155  set_pause_mode(False) # only trigger pause once
156  printlog("Tear Down - launcher..........................%s" % launcher.path)
157  if config.tests:
158  printlog("Tear Down - tests..............................%s" % [test.test_name for test in config.tests])
159  if parent:
160  parent.tearDown()
161  printlog("Tear Down - run id............................%s" % parent.run_id)
162  else:
163  parent.shutdown()
164 
165 
166 ## generate test failure if tests with same name in launch file
167 def fail_duplicate_runner(test_name):
168  def fn(self):
169  print("Duplicate tests named [%s] in rostest suite" % test_name)
170  self.fail("Duplicate tests named [%s] in rostest suite" % test_name)
171  return fn
172 
173 
174 def fail_runner(test_name, message):
175  def fn(self):
176  print(message, file=sys.stderr)
177  self.fail(message)
178  return fn
179 
180 
181 def rocon_test_runner(test, test_launch_configuration, test_pkg):
182  """
183  Test function generator that takes in a roslaunch Test object and
184  returns a class instance method that runs the test. TestCase
185  setUp() is responsible for ensuring that the rest of the roslaunch
186  state is correct and tearDown() is responsible for tearing
187  everything down cleanly.
188 
189  :param test: ros test to run
190  :type test: :class:`.RoconTestLaunchConfiguration`
191  :returns: function object to run testObj
192  :rtype: fn
193  """
194 
195  ## test case pass/fail is a measure of whether or not the test ran
196  def fn(self):
197  printlog("Launching tests")
198  done = False
199  while not done:
200  parent = test_launch_configuration.parent
201  self.assert_(parent is not None, "ROSTestParent initialization failed")
202  test_name = test.test_name
203  printlog(" name..............................%s" % test_name)
204 
205  #launch the other nodes
206  #for parent in self.parents:
207  # DJS - will this mean I miss starting up othe test parents?
208  unused_succeeded, failed = parent.launch()
209  self.assert_(not failed, "Test Fixture Nodes %s failed to launch" % failed)
210 
211  #setup the test
212  # - we pass in the output test_file name so we can scrape it
213  test_file = rosunit.xml_results_file(test_pkg, test_name, False)
214  printlog(" test results file.................%s", test_file)
215  if os.path.exists(test_file):
216  os.remove(test_file)
217  printlog(" clear old test results file.......done")
218 
219  # TODO: have to redeclare this due to a bug -- this file
220  # needs to be renamed as it aliases the module where the
221  # constant is elsewhere defined. The fix is to rename
222  # rostest.py
223  XML_OUTPUT_FLAG = '--gtest_output=xml:' # use gtest-compatible flag
224  test.args = "%s %s%s" % (test.args, XML_OUTPUT_FLAG, test_file)
225  if _text_mode:
226  test.output = 'screen'
227  test.args = test.args + " --text"
228 
229  # run the test, blocks until completion
230  printlog("Running test %s" % test_name)
231  timeout_failure = False
232  try:
233  parent.run_test(test)
234  except roslaunch.launch.RLTestTimeoutException as unused_e:
235  if test.retry:
236  timeout_failure = True
237  else:
238  raise
239 
240  if not timeout_failure:
241  printlog("test finished [%s]" % test_name)
242  else:
243  printlogerr("test timed out [%s]" % test_name)
244 
245  if not _text_mode or timeout_failure:
246  if not timeout_failure:
247  self.assert_(os.path.isfile(test_file), "test [%s] did not generate test results" % test_name)
248  printlog("test [%s] results are in [%s]", test_name, test_file)
249  results = rosunit.junitxml.read(test_file, test_name)
250  test_fail = results.num_errors or results.num_failures
251  else:
252  test_fail = True
253  if test.retry > 0 and test_fail:
254  test.retry -= 1
255  printlog("test [%s] failed, retrying. Retries left: %s" % (test_name, test.retry))
256  self.tearDown()
257  self.setUp()
258  else:
259  done = True
260  _accumulate_results(results)
261  printlog("test [%s] results summary: %s errors, %s failures, %s tests",
262  test_name, results.num_errors, results.num_failures, results.num_tests)
263  #self.assertEquals(0, results.num_errors, "unit test reported errors")
264  #self.assertEquals(0, results.num_failures, "unit test reported failures")
265  else:
266  if test.retry:
267  printlogerr("retry is disabled in --text mode")
268  done = True
269  printlog("test done [%s]", test_name)
270  return fn
271 
272 
273 def create_unit_rocon_test(rocon_launcher, launchers):
274  '''
275  Constructs the python unit test class.
276 
277  @param rocon_launcher : absolute path to the rocon test launcher
278  @param launchers : list of individual launcher configurations (name, path, port etc) in rocon_launcher
279  @return unittest.TestCase : unit test class
280  '''
281  rocon_launch_configurations = []
282  for launcher in launchers:
283  rocon_launch_configurations.append(RoconTestLaunchConfiguration(launcher))
284  # pass in config to class as a property so that parent can be initialized
285  classdict = {'setUp': setUp, 'tearDown': tearDown,
286  'rocon_launch_configurations': rocon_launch_configurations,
287  'test_file': rocon_launcher}
288 
289  # add in the tests
290  test_names = []
291  for rocon_launch_configuration in rocon_launch_configurations:
292  config = rocon_launch_configuration.configuration
293  for test in config.tests:
294  # #1989: find test first to make sure it exists and is executable
295  err_msg = None
296  try:
297  rp = rospkg.RosPack()
298  cmd = roslib.packages.find_node(test.package, test.type, rp)
299  if not cmd:
300  err_msg = "Test node [%s/%s] does not exist or is not executable" % (test.package, test.type)
301  except rospkg.ResourceNotFound:
302  err_msg = "Package [%s] for test node [%s/%s] does not exist" % (test.package, test.package, test.type)
303 
304  test_name = 'test%s' % (test.test_name)
305  if err_msg:
306  classdict[test_name] = fail_runner(test.test_name, err_msg)
307  elif test_name in test_names:
308  classdict[test_name] = fail_duplicate_runner(test.test_name)
309  else:
310  classdict[test_name] = rocon_test_runner(test, rocon_launch_configuration, launcher.package)
311  test_names.append(test_name)
312 
313  # instantiate the TestCase instance with our magically-created tests
314  return type('RoconTest', (unittest.TestCase,), classdict)
def create_unit_rocon_test(rocon_launcher, launchers)
Definition: runner.py:273
def fail_duplicate_runner(test_name)
generate test failure if tests with same name in launch file
Definition: runner.py:167
def tearDown(self)
Definition: runner.py:145
def fail_runner(test_name, message)
Definition: runner.py:174
def printlog(msg, args)
Definition: loggers.py:54
def _accumulate_results(results)
Definition: runner.py:40
def printlogerr(msg, args)
Definition: loggers.py:61
def _add_rocon_test_parent(runner)
Definition: runner.py:64
def setUp(self)
Definition: runner.py:94
def rocon_test_runner(test, test_launch_configuration, test_pkg)
Definition: runner.py:181
def set_text_mode(val)
Definition: runner.py:44
def get_rocon_test_parents()
Definition: runner.py:70
def set_pause_mode(val)
Definition: runner.py:49
def get_results()
Definition: runner.py:36


rocon_test
Author(s): Daniel Stonier
autogenerated on Mon Jun 10 2019 14:40:14