Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changes between 0.6.18 and 0.7.6 for backporting #858

Merged
merged 8 commits into from
Mar 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/catkin_make_isolated
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def parse_args(args=None):
help='Disables colored output (only for catkin_make and CMake)')
pkg = parser.add_mutually_exclusive_group(required=False)
pkg.add_argument('--pkg', nargs='+', metavar='PKGNAME', dest='packages',
help="Invoke 'make' on specific packages "
help="Process only specific packages "
"(only after catkin_make_isolated has been invoked before with the same install flag)")
pkg.add_argument('--from-pkg', metavar='PKGNAME', dest='from_package',
help='Restart catkin_make_isolated at the given package continuing from there '
Expand Down
7 changes: 4 additions & 3 deletions bin/catkin_test_results
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import sys
# find the import relatively if available to work before installing catkin or overlaying installed version
if os.path.exists(os.path.join(os.path.dirname(__file__), '..', 'python', 'catkin', '__init__.py')):
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'python'))
from catkin.test_results import aggregate_results, print_summary, test_results
from catkin.test_results import aggregate_results, print_summary2, test_results2


def main():
Expand All @@ -24,10 +24,11 @@ def main():
sys.exit('Test results directory "%s" does not exist' % test_results_dir)

try:
results = test_results(
results = test_results2(
test_results_dir, show_verbose=args.verbose, show_all=args.all)
_, sum_errors, sum_failures = aggregate_results(results)
print_summary(results, show_stable=args.all)
print_summary2(results, show_stable=args.all)
# Skipped tests alone should not count as a failure
if sum_errors or sum_failures:
sys.exit(1)
except Exception as e:
Expand Down
46 changes: 23 additions & 23 deletions cmake/templates/_setup_util.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -72,42 +72,42 @@ def rollback_env_variables(environ, env_var_subfolders):
subfolders = env_var_subfolders[key]
if not isinstance(subfolders, list):
subfolders = [subfolders]
for subfolder in subfolders:
value = _rollback_env_variable(unmodified_environ, key, subfolder)
if value is not None:
environ[key] = value
lines.append(assignment(key, value))
value = _rollback_env_variable(unmodified_environ, key, subfolders)
if value is not None:
environ[key] = value
lines.append(assignment(key, value))
if lines:
lines.insert(0, comment('reset environment variables by unrolling modifications based on all workspaces in CMAKE_PREFIX_PATH'))
return lines


def _rollback_env_variable(environ, name, subfolder):
def _rollback_env_variable(environ, name, subfolders):
'''
For each catkin workspace in CMAKE_PREFIX_PATH remove the first entry from env[NAME] matching workspace + subfolder.

:param subfolder: str '' or subfoldername that may start with '/'
:param subfolders: list of str '' or subfoldername that may start with '/'
:returns: the updated value of the environment variable.
'''
value = environ[name] if name in environ else ''
env_paths = [path for path in value.split(os.pathsep) if path]
value_modified = False
if subfolder:
if subfolder.startswith(os.path.sep) or (os.path.altsep and subfolder.startswith(os.path.altsep)):
subfolder = subfolder[1:]
if subfolder.endswith(os.path.sep) or (os.path.altsep and subfolder.endswith(os.path.altsep)):
subfolder = subfolder[:-1]
for ws_path in _get_workspaces(environ, include_fuerte=True, include_non_existing=True):
path_to_find = os.path.join(ws_path, subfolder) if subfolder else ws_path
path_to_remove = None
for env_path in env_paths:
env_path_clean = env_path[:-1] if env_path and env_path[-1] in [os.path.sep, os.path.altsep] else env_path
if env_path_clean == path_to_find:
path_to_remove = env_path
break
if path_to_remove:
env_paths.remove(path_to_remove)
value_modified = True
for subfolder in subfolders:
if subfolder:
if subfolder.startswith(os.path.sep) or (os.path.altsep and subfolder.startswith(os.path.altsep)):
subfolder = subfolder[1:]
if subfolder.endswith(os.path.sep) or (os.path.altsep and subfolder.endswith(os.path.altsep)):
subfolder = subfolder[:-1]
for ws_path in _get_workspaces(environ, include_fuerte=True, include_non_existing=True):
path_to_find = os.path.join(ws_path, subfolder) if subfolder else ws_path
path_to_remove = None
for env_path in env_paths:
env_path_clean = env_path[:-1] if env_path and env_path[-1] in [os.path.sep, os.path.altsep] else env_path
if env_path_clean == path_to_find:
path_to_remove = env_path
break
if path_to_remove:
env_paths.remove(path_to_remove)
value_modified = True
new_value = os.pathsep.join(env_paths)
return new_value if value_modified else None

Expand Down
4 changes: 2 additions & 2 deletions cmake/templates/setup.bat.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ REM It tries it's best to undo changes from a previously sourced setup file befo
REM Supported command line options:
REM --extend: skips the undoing of changes from a previously sourced setup file

set _SETUP_UTIL="@SETUP_DIR@/_setup_util.py"
set _SETUP_UTIL=@SETUP_DIR@/_setup_util.py

if NOT EXIST "%_SETUP_UTIL%" (
echo "Missing Python script: %_SETUP_UTIL%"
Expand All @@ -33,7 +33,7 @@ if NOT EXIST %_SETUP_TMP% (
)

REM invoke Python script to generate necessary exports of environment variables
%_PYTHON% %_SETUP_UTIL% %* > %_SETUP_TMP%
%_PYTHON% "%_SETUP_UTIL%" %* > %_SETUP_TMP%
if NOT EXIST %_SETUP_TMP% (
echo "Could not create temporary file: %_SETUP_TMP%"
return 1
Expand Down
8 changes: 5 additions & 3 deletions doc/dev_guide/generated_cmake_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ Public CMake functions / macros
.. note:: The test can be executed by calling ``nosetests``
directly or using:
`` make run_tests_${PROJECT_NAME}_nosetests_${dir}``
(where slashes in the ``dir`` are replaced with underscores)
(where slashes in the ``dir`` are replaced with periods)

:param path: a relative or absolute directory to search for
nosetests in or a relative or absolute file containing tests
Expand Down Expand Up @@ -374,8 +374,10 @@ Public CMake functions / macros
:param CFG_EXTRAS: a CMake file containing extra stuff that should
be accessible to users of this package after
``find_package``\ -ing it. This file must live in the
subdirectory ``cmake`` or be an absolute path. Various additional
file extension are possible:
subdirectory ``cmake`` or be an absolute path.
All passed extra files must have unique basenames since they are
being installed into a single folder.
Various additional file extension are possible:
for a plain cmake file just ``.cmake``, for files expanded using
CMake's ``configure_file()`` use ``.cmake.in`` or for files expanded
by empy use ``.cmake.em``. The templates can distinguish between
Expand Down
3 changes: 3 additions & 0 deletions doc/howto/format1/installing_cmake.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ want to export::

catkin_package(CFG_EXTRAS your_macros.cmake your_modules.cmake)

When another package uses ``find_package()`` on this package, the listed
CMake files are automatically included.

Since these data are platform-independent, they should be installed in
your package's **share/** subtree. This example assumes your CMake
sources are in the customary **cmake/** subdirectory::
Expand Down
14 changes: 14 additions & 0 deletions doc/howto/format1/python_nose_configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ You can also let nosetest find all tests recursively::
catkin_add_nosetests(tests)
endif()

If you used a message of the current package in the nosetest, make sure to
specify the nosetest like this::

catkin_add_nosetests(tests/test_your_node.py
DEPENDENCIES ${${PROJECT_NAME}_EXPORTED_TARGETS})

If you used messages from other packages, use::

catkin_add_nosetests(tests/test_your_node.py
DEPENDENCIES ${catkin_EXPORTED_TARGETS})

The test will then make sure that the messages used in the test are built
before they are used.

For more info, please have a look at the :ref:`API <catkin_add_nosetests_ref>`.

.. _Nosetest: http://www.ros.org/wiki/nosetest
Expand Down
3 changes: 3 additions & 0 deletions doc/howto/format2/installing_cmake.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ want to export::

catkin_package(CFG_EXTRAS your_macros.cmake your_modules.cmake)

When another package uses ``find_package()`` on this package, the listed
CMake files are automatically included.

Since these data are platform-independent, they should be installed in
your package's **share/** subtree. This example assumes your CMake
sources are in the customary **cmake/** subdirectory::
Expand Down
14 changes: 14 additions & 0 deletions doc/howto/format2/python_nose_configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ You can also let nosetest find all tests recursively::
catkin_add_nosetests(tests)
endif()

If you used a message of the current package in the nosetest, make sure to
specify the nosetest like this::

catkin_add_nosetests(tests/test_your_node.py
DEPENDENCIES ${${PROJECT_NAME}_EXPORTED_TARGETS})

If you used messages from other packages, use::

catkin_add_nosetests(tests/test_your_node.py
DEPENDENCIES ${catkin_EXPORTED_TARGETS})

The test will then make sure that the messages used in the test are built
before they are used.

For more info, please have a look at the :ref:`API <catkin_add_nosetests_ref>`.

.. _Nosetest: http://www.ros.org/wiki/nosetest
Expand Down
30 changes: 30 additions & 0 deletions doc/user_guide/setup_dot_py.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,30 @@ sourcespace using the python exec() function.
The version will be compared to that declared in package.xml, and
raise an error on mismatch.

.. note::

If you have written non-ROS Python packages before, you have
probably used the ``requires`` field in the distuils setup function.
This field, however, *has no meaning* in ROS.

All your Python
dependencies should be specified in ``package.xml`` as e.g.
``<run_depend>python-numpy</run_depend>`` (for older version 1
of package.xml) or ``<exec_depend>python-numpy</exec_depend>``
(if you use format 2 of package.xml).

Not all Python or pip packages are mapped to ROS dependencies.
For a quick check, try running ``rosdep resolve python-mypackage``
or ``rosdep resolve python-mypackage-pip`` if you want to
add a dependency on ``mypackage`` Python package. If these calls
return error, you may want to search through the
`python.yaml file in rosdep`_ for similar names. If you don't
find the requested package, you may consider
`creating a Pull request to add it`_.

.. _python.yaml file in rosdep: https://github.com/ros/rosdistro/blob/master/rosdep/python.yaml
.. _creating a Pull request to add it: http://docs.ros.org/independent/api/rosdep/html/contributing_rules.html

Using package.xml in setup.py
=============================

Expand Down Expand Up @@ -100,6 +124,12 @@ one distributes to pypi.
catkin_install_python(PROGRAMS scripts/myscript
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})

.. note::

See the note in previous section about the otherwise useful
field ``requires`` that's usually a part of distutils setup.
*Do not use it* in ROS.


Develspace limitations
======================
Expand Down
83 changes: 67 additions & 16 deletions python/catkin/test_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,18 @@ def _get_missing_junit_result_filename(filename):


def read_junit(filename):
"""Same as `read_junit2` except it doesn't return num_skipped."""
num_tests, num_errors, num_failures, _ = read_junit2(filename)
return (num_tests, num_errors, num_failures)


def read_junit2(filename):
"""
parses xml file expected to follow junit/gtest conventions
see http://code.google.com/p/googletest/wiki/AdvancedGuide#Generating_an_XML_Report

:param filename: str junit xml file name
:returns: num_tests, num_errors, num_failures
:returns: num_tests, num_errors, num_failures, num_skipped
:raises ParseError: if xml is not well-formed
:raises IOError: if filename does not exist
"""
Expand All @@ -110,17 +116,29 @@ def read_junit(filename):
num_tests = int(root.attrib['tests'])
num_errors = int(root.attrib['errors'])
num_failures = int(root.attrib['failures'])
return (num_tests, num_errors, num_failures)
num_skipped = int(root.get('skip', '0')) + int(root.get('disabled', '0'))
return (num_tests, num_errors, num_failures, num_skipped)


def test_results(test_results_dir, show_verbose=False, show_all=False):
"""Same as `test_results2` except the returned values don't include num_skipped."""
results = {}
results2 = test_results2(
test_results_dir, show_verbose=show_verbose, show_all=show_all)
for name, values in results2.items():
num_tests, num_errors, num_failures, _ = values
results[name] = (num_tests, num_errors, num_failures)
return results


def test_results2(test_results_dir, show_verbose=False, show_all=False):
'''
Collects test results by parsing all xml files in given path,
attempting to interpret them as junit results.

:param test_results_dir: str foldername
:param show_verbose: bool show output for tests which had errors or failed
:returns: dict {rel_path, (num_tests, num_errors, num_failures)}
:returns: dict {rel_path, (num_tests, num_errors, num_failures, num_skipped)}
'''
results = {}
for dirpath, dirnames, filenames in os.walk(test_results_dir):
Expand All @@ -130,12 +148,12 @@ def test_results(test_results_dir, show_verbose=False, show_all=False):
filename_abs = os.path.join(dirpath, filename)
name = filename_abs[len(test_results_dir) + 1:]
try:
num_tests, num_errors, num_failures = read_junit(filename_abs)
num_tests, num_errors, num_failures, num_skipped = read_junit2(filename_abs)
except Exception as e:
if show_all:
print('Skipping "%s": %s' % (name, str(e)))
continue
results[name] = (num_tests, num_errors, num_failures)
results[name] = (num_tests, num_errors, num_failures, num_skipped)
if show_verbose and (num_errors + num_failures > 0):
print("Full test results for '%s'" % (name))
print('-------------------------------------------------')
Expand All @@ -146,35 +164,68 @@ def test_results(test_results_dir, show_verbose=False, show_all=False):


def aggregate_results(results, callback_per_result=None):
"""Same as `aggregate_results2` except it doesn't return num_skipped."""
callback = None
if callback_per_result is not None:
def callback(name, num_tests, num_errors, num_failures, num_skipped):
callback_per_result(name, num_tests, num_errors, num_failures)
sum_tests, sum_errors, sum_failures, _ = aggregate_results2(
results, callback_per_result=callback)
return (sum_tests, sum_errors, sum_failures)


def aggregate_results2(results, callback_per_result=None):
"""
Aggregate results

:param results: dict as from test_results()
:returns: tuple (num_tests, num_errors, num_failures)
:returns: tuple (num_tests, num_errors, num_failures, num_skipped)
"""
sum_tests = sum_errors = sum_failures = 0
sum_tests = sum_errors = sum_failures = sum_skipped = 0
for name in sorted(results.keys()):
(num_tests, num_errors, num_failures) = results[name]
(num_tests, num_errors, num_failures, num_skipped) = results[name]
sum_tests += num_tests
sum_errors += num_errors
sum_failures += num_failures
sum_skipped += num_skipped
if callback_per_result:
callback_per_result(name, num_tests, num_errors, num_failures)
return sum_tests, sum_errors, sum_failures
callback_per_result(
name, num_tests, num_errors, num_failures, num_skipped)
return sum_tests, sum_errors, sum_failures, sum_skipped


def print_summary(results, show_stable=False, show_unstable=True):
"""Same as `print_summary2` except it doesn't print skipped tests."""
print_summary2(
results, show_stable=show_stable, show_unstable=show_unstable,
print_skipped=False)


def print_summary2(results, show_stable=False, show_unstable=True, print_skipped=True):
"""
print summary to stdout

:param results: dict as from test_results()
:param show_stable: print tests without failures extra
:param show_stable: print tests with failures extra
:param print_skipped: include skipped tests in output
"""
def callback(name, num_tests, num_errors, num_failures):
if show_stable and not num_errors and not num_failures:
def callback(name, num_tests, num_errors, num_failures, num_skipped):
if show_stable and not num_errors and not num_failures and not num_skipped:
print('%s: %d tests' % (name, num_tests))
if show_unstable and (num_errors or num_failures):
print('%s: %d tests, %d errors, %d failures' % (name, num_tests, num_errors, num_failures))
sum_tests, sum_errors, sum_failures = aggregate_results(results, callback)
print('Summary: %d tests, %d errors, %d failures' % (sum_tests, sum_errors, sum_failures))
if show_unstable and (num_errors or num_failures or num_skipped):
msg = '{}: {} tests, {} errors, {} failures'
msg_args = [name, num_tests, num_errors, num_failures]
if print_skipped:
msg += ', {} skipped'
msg_args.append(num_skipped)
print(msg.format(*msg_args))
sum_tests, sum_errors, sum_failures, sum_skipped = aggregate_results2(results, callback)

msg = 'Summary: {} tests, {} errors, {} failures'
msg_args = [sum_tests, sum_errors, sum_failures]
if print_skipped:
msg += ', {} skipped'
msg_args.append(sum_skipped)

print(msg.format(*msg_args))
Loading