"Fossies" - the Fresh Open Source Software Archive

Member "snapcraft-3.8/tests/unit/plugins/test_catkin.py" (9 Sep 2019, 72512 Bytes) of package /linux/misc/snapcraft-3.8.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "test_catkin.py": 3.7.2_vs_3.8.

    1 # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
    2 #
    3 # Copyright (C) 2015-2019 Canonical Ltd
    4 #
    5 # This program is free software: you can redistribute it and/or modify
    6 # it under the terms of the GNU General Public License version 3 as
    7 # published by the Free Software Foundation.
    8 #
    9 # This program is distributed in the hope that it will be useful,
   10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12 # GNU General Public License for more details.
   13 #
   14 # You should have received a copy of the GNU General Public License
   15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
   16 
   17 import ast
   18 import builtins
   19 import os
   20 import os.path
   21 import re
   22 import subprocess
   23 import sys
   24 import tempfile
   25 import textwrap
   26 
   27 from unittest import mock
   28 import testtools
   29 from testtools.matchers import (
   30     Contains,
   31     Equals,
   32     FileExists,
   33     HasLength,
   34     LessThan,
   35     MatchesRegex,
   36     Not,
   37 )
   38 
   39 import snapcraft
   40 from snapcraft import repo
   41 from snapcraft.internal import errors
   42 from snapcraft.plugins import catkin
   43 from snapcraft.plugins import _ros
   44 from tests import unit
   45 
   46 
   47 class _CompareContainers:
   48     def __init__(self, test, expected):
   49         self.test = test
   50         self.expected = expected
   51 
   52     def __eq__(self, container):
   53         self.test.assertThat(
   54             len(container),
   55             Equals(len(self.expected)),
   56             "Expected {} items to be in container, "
   57             "got {}".format(len(self.expected), len(container)),
   58         )
   59 
   60         for expectation in self.expected:
   61             self.test.assertTrue(
   62                 expectation in container,
   63                 'Expected "{}" to be in container'.format(expectation),
   64             )
   65 
   66         return True
   67 
   68 
   69 class CatkinPluginBaseTest(unit.TestCase):
   70     def setUp(self):
   71         super().setUp()
   72 
   73         class props:
   74             catkin_packages = ["my_package"]
   75             source_space = "src"
   76             source_subdir = None
   77             include_roscore = False
   78             catkin_cmake_args = []
   79             underlay = None
   80             rosinstall_files = None
   81             recursive_rosinstall = False
   82             build_attributes = []
   83             catkin_ros_master_uri = "http://localhost:11311"
   84             disable_parallel = False
   85 
   86         self.properties = props()
   87         self.ros_distro = "kinetic"
   88         self.ubuntu_distro = "xenial"
   89 
   90         self.project = snapcraft.project.Project(
   91             snapcraft_yaml_file_path=self.make_snapcraft_yaml(
   92                 textwrap.dedent(
   93                     """\
   94                     name: catkin-snap
   95                     base: core16
   96                     """
   97                 )
   98             )
   99         )
  100 
  101         patcher = mock.patch("snapcraft.repo.Ubuntu")
  102         self.ubuntu_mock = patcher.start()
  103         self.addCleanup(patcher.stop)
  104 
  105         patcher = mock.patch(
  106             "snapcraft.plugins.catkin._find_system_dependencies", return_value={}
  107         )
  108         self.dependencies_mock = patcher.start()
  109         self.addCleanup(patcher.stop)
  110 
  111         patcher = mock.patch("snapcraft.plugins._ros.rosdep.Rosdep")
  112         self.rosdep_mock = patcher.start()
  113         self.addCleanup(patcher.stop)
  114 
  115         patcher = mock.patch("snapcraft.plugins._ros.rospack.Rospack")
  116         self.rospack_mock = patcher.start()
  117         self.addCleanup(patcher.stop)
  118 
  119         patcher = mock.patch("snapcraft.plugins.catkin._Catkin")
  120         self.catkin_mock = patcher.start()
  121         self.addCleanup(patcher.stop)
  122 
  123         patcher = mock.patch("snapcraft.plugins._ros.wstool.Wstool")
  124         self.wstool_mock = patcher.start()
  125         self.addCleanup(patcher.stop)
  126 
  127         patcher = mock.patch("snapcraft.plugins._python.Pip")
  128         self.pip_mock = patcher.start()
  129         self.addCleanup(patcher.stop)
  130         self.pip_mock.return_value.list.return_value = {}
  131 
  132     def assert_rosdep_setup(
  133         self, rosdistro, package_path, rosdep_path, ubuntu_distro, sources, keyrings
  134     ):
  135         self.rosdep_mock.assert_has_calls(
  136             [
  137                 mock.call(
  138                     ros_distro=rosdistro,
  139                     ros_package_path=package_path,
  140                     rosdep_path=rosdep_path,
  141                     ubuntu_distro=ubuntu_distro,
  142                     ubuntu_sources=sources,
  143                     ubuntu_keyrings=keyrings,
  144                     project=self.project,
  145                 ),
  146                 mock.call().setup(),
  147             ]
  148         )
  149 
  150     def assert_rospack_setup(
  151         self, rosdistro, package_path, rospack_path, sources, keyrings
  152     ):
  153         self.rospack_mock.assert_has_calls(
  154             [
  155                 mock.call(
  156                     ros_distro=rosdistro,
  157                     ros_package_path=package_path,
  158                     rospack_path=rospack_path,
  159                     ubuntu_sources=sources,
  160                     ubuntu_keyrings=keyrings,
  161                     project=self.project,
  162                 ),
  163                 mock.call().setup(),
  164             ]
  165         )
  166 
  167     def assert_wstool_setup(self, package_path, wstool_path, sources, keyrings):
  168         self.wstool_mock.assert_has_calls(
  169             [
  170                 mock.call(package_path, wstool_path, sources, keyrings, self.project),
  171                 mock.call().setup(),
  172             ]
  173         )
  174 
  175     def assert_pip_setup(self, python_major_version, part_dir, install_dir, stage_dir):
  176         self.pip_mock.assert_has_calls(
  177             [
  178                 mock.call(
  179                     python_major_version=python_major_version,
  180                     part_dir=part_dir,
  181                     install_dir=install_dir,
  182                     stage_dir=stage_dir,
  183                 ),
  184                 mock.call().setup(),
  185             ]
  186         )
  187 
  188 
  189 class CatkinPluginTestCase(CatkinPluginBaseTest):
  190     def test_schema(self):
  191         schema = catkin.CatkinPlugin.schema()
  192 
  193         properties = schema["properties"]
  194         expected = (
  195             "catkin-packages",
  196             "source-space",
  197             "include-roscore",
  198             "catkin-cmake-args",
  199             "underlay",
  200             "rosinstall-files",
  201             "recursive-rosinstall",
  202             "catkin-ros-master-uri",
  203         )
  204         self.assertThat(properties, HasLength(len(expected)))
  205         for prop in expected:
  206             self.assertThat(properties, Contains(prop))
  207 
  208     def test_schema_catkin_packages(self):
  209         schema = catkin.CatkinPlugin.schema()
  210 
  211         # Check catkin-packages property
  212         catkin_packages = schema["properties"]["catkin-packages"]
  213         expected = ("type", "minitems", "uniqueItems", "items")
  214         self.assertThat(catkin_packages, HasLength(len(expected)))
  215         for prop in expected:
  216             self.assertThat(catkin_packages, Contains(prop))
  217         self.assertThat(catkin_packages["type"], Equals("array"))
  218         self.assertThat(catkin_packages["minitems"], Equals(1))
  219         self.assertTrue(catkin_packages["uniqueItems"])
  220         self.assertThat(catkin_packages["items"], Contains("type"))
  221         self.assertThat(catkin_packages["items"]["type"], Equals("string"))
  222 
  223     def test_schema_source_space(self):
  224         schema = catkin.CatkinPlugin.schema()
  225 
  226         # Check source-space property
  227         source_space = schema["properties"]["source-space"]
  228         expected = ("type", "default")
  229         self.assertThat(source_space, HasLength(len(expected)))
  230         for prop in expected:
  231             self.assertThat(source_space, Contains(prop))
  232         self.assertThat(source_space["type"], Equals("string"))
  233         self.assertThat(source_space["default"], Equals("src"))
  234 
  235     def test_schema_include_roscore(self):
  236         schema = catkin.CatkinPlugin.schema()
  237 
  238         # Check include-roscore property
  239         include_roscore = schema["properties"]["include-roscore"]
  240         expected = ("type", "default")
  241         self.assertThat(include_roscore, HasLength(len(expected)))
  242         for prop in expected:
  243             self.assertThat(include_roscore, Contains(prop))
  244         self.assertThat(include_roscore["type"], Equals("boolean"))
  245         self.assertThat(include_roscore["default"], Equals(True))
  246 
  247     def test_schema_catkin_catkin_cmake_args(self):
  248         schema = catkin.CatkinPlugin.schema()
  249 
  250         # Check catkin-cmake-args property
  251         catkin_cmake_args = schema["properties"]["catkin-cmake-args"]
  252         expected = ("type", "default", "minitems", "items")
  253         self.assertThat(catkin_cmake_args, HasLength(len(expected)))
  254         for prop in expected:
  255             self.assertThat(catkin_cmake_args, Contains(prop))
  256         self.assertThat(catkin_cmake_args["type"], Equals("array"))
  257         self.assertThat(catkin_cmake_args["default"], Equals([]))
  258         self.assertThat(catkin_cmake_args["minitems"], Equals(1))
  259         self.assertThat(catkin_cmake_args["items"], Contains("type"))
  260         self.assertThat(catkin_cmake_args["items"]["type"], Equals("string"))
  261 
  262     def test_schema_underlay(self):
  263         schema = catkin.CatkinPlugin.schema()
  264 
  265         # Check underlay property
  266         underlay = schema["properties"]["underlay"]
  267         expected = ("type", "properties", "required")
  268         self.assertThat(underlay, HasLength(len(expected)))
  269         for prop in expected:
  270             self.assertThat(underlay, Contains(prop))
  271         self.assertThat(underlay["type"], Equals("object"))
  272 
  273         underlay_required = underlay["required"]
  274         expected = ("build-path", "run-path")
  275         self.assertThat(underlay_required, HasLength(len(expected)))
  276         for prop in expected:
  277             self.assertThat(underlay_required, Contains(prop))
  278 
  279         underlay_properties = underlay["properties"]
  280         expected = ("build-path", "run-path")
  281         self.assertThat(underlay_properties, HasLength(len(expected)))
  282         for prop in expected:
  283             self.assertThat(underlay_properties, Contains(prop))
  284         underlay_build_path = underlay_properties["build-path"]
  285         self.assertThat(underlay_build_path, Contains("type"))
  286         self.assertThat(underlay_build_path["type"], Equals("string"))
  287         underlay_run_path = underlay_properties["run-path"]
  288         self.assertThat(underlay_run_path, Contains("type"))
  289         self.assertThat(underlay_run_path["type"], Equals("string"))
  290 
  291     def test_schema_rosinstall_files(self):
  292         schema = catkin.CatkinPlugin.schema()
  293 
  294         # Check rosinstall-files property
  295         rosinstall_files = schema["properties"]["rosinstall-files"]
  296         expected = ("type", "default", "minitems", "uniqueItems", "items")
  297         self.assertThat(rosinstall_files, HasLength(len(expected)))
  298         for prop in expected:
  299             self.assertThat(rosinstall_files, Contains(prop))
  300         self.assertThat(rosinstall_files["type"], Equals("array"))
  301         self.assertThat(rosinstall_files["default"], Equals([]))
  302         self.assertThat(rosinstall_files["minitems"], Equals(1))
  303         self.assertTrue(rosinstall_files["uniqueItems"])
  304         self.assertThat(rosinstall_files["items"], Contains("type"))
  305         self.assertThat(rosinstall_files["items"]["type"], Equals("string"))
  306 
  307     def test_schema_recursive_rosinstall(self):
  308         schema = catkin.CatkinPlugin.schema()
  309 
  310         # Check recursive-rosinstall property
  311         recursive_rosinstall = schema["properties"]["recursive-rosinstall"]
  312         expected = ("type", "default")
  313         self.assertThat(recursive_rosinstall, HasLength(len(expected)))
  314         for prop in expected:
  315             self.assertThat(recursive_rosinstall, Contains(prop))
  316         self.assertThat(recursive_rosinstall["type"], Equals("boolean"))
  317         self.assertThat(recursive_rosinstall["default"], Equals(False))
  318 
  319     def test_schema_catkin_ros_master_uri(self):
  320         schema = catkin.CatkinPlugin.schema()
  321 
  322         # Check ros-master-uri property
  323         catkin_ros_master_uri = schema["properties"]["catkin-ros-master-uri"]
  324         expected = ("type", "default")
  325         self.assertThat(catkin_ros_master_uri, HasLength(len(expected)))
  326         for prop in expected:
  327             self.assertThat(catkin_ros_master_uri, Contains(prop))
  328         self.assertThat(catkin_ros_master_uri["type"], Equals("string"))
  329         self.assertThat(
  330             catkin_ros_master_uri["default"], Equals("http://localhost:11311")
  331         )
  332 
  333     def test_get_pull_properties(self):
  334         expected_pull_properties = [
  335             "catkin-packages",
  336             "source-space",
  337             "include-roscore",
  338             "underlay",
  339             "rosinstall-files",
  340             "recursive-rosinstall",
  341         ]
  342         actual_pull_properties = catkin.CatkinPlugin.get_pull_properties()
  343 
  344         self.assertThat(
  345             actual_pull_properties, HasLength(len(expected_pull_properties))
  346         )
  347 
  348         for property in expected_pull_properties:
  349             self.assertIn(property, actual_pull_properties)
  350 
  351     def test_get_build_properties(self):
  352         expected_build_properties = ["catkin-cmake-args"]
  353         actual_build_properties = catkin.CatkinPlugin.get_build_properties()
  354 
  355         self.assertThat(
  356             actual_build_properties, HasLength(len(expected_build_properties))
  357         )
  358 
  359         for property in expected_build_properties:
  360             self.assertIn(property, actual_build_properties)
  361 
  362     def test_get_stage_sources_core(self):
  363         self.project = snapcraft.project.Project(
  364             snapcraft_yaml_file_path=self.make_snapcraft_yaml(
  365                 textwrap.dedent(
  366                     """\
  367                     name: catkin-snap
  368                     base: core
  369                     """
  370                 )
  371             )
  372         )
  373 
  374         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  375         self.assertTrue("xenial" in plugin.PLUGIN_STAGE_SOURCES)
  376 
  377     def test_get_stage_sources_core16(self):
  378         self.project = snapcraft.project.Project(
  379             snapcraft_yaml_file_path=self.make_snapcraft_yaml(
  380                 textwrap.dedent(
  381                     """\
  382                     name: catkin-snap
  383                     base: core16
  384                     """
  385                 )
  386             )
  387         )
  388 
  389         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  390         self.assertTrue("xenial" in plugin.PLUGIN_STAGE_SOURCES)
  391 
  392     def test_get_stage_sources_core18(self):
  393         self.project = snapcraft.project.Project(
  394             snapcraft_yaml_file_path=self.make_snapcraft_yaml(
  395                 textwrap.dedent(
  396                     """\
  397                     name: catkin-snap
  398                     base: core18
  399                     """
  400                 )
  401             )
  402         )
  403 
  404         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  405         self.assertTrue("bionic" in plugin.PLUGIN_STAGE_SOURCES)
  406 
  407     def test_pull_invalid_dependency(self):
  408         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  409         os.makedirs(os.path.join(plugin.sourcedir, "src"))
  410 
  411         self.dependencies_mock.return_value = {"apt": {"foo"}}
  412 
  413         mock_instance = self.ubuntu_mock.return_value
  414         mock_instance.get.side_effect = repo.errors.PackageNotFoundError("foo")
  415 
  416         raised = self.assertRaises(catkin.CatkinAptDependencyFetchError, plugin.pull)
  417 
  418         self.assertThat(
  419             str(raised),
  420             Equals(
  421                 "Failed to fetch apt dependencies: The package 'foo' was not found."
  422             ),
  423         )
  424 
  425     def test_pull_unable_to_resolve_roscore(self):
  426         self.properties.include_roscore = True
  427         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  428         os.makedirs(os.path.join(plugin.sourcedir, "src"))
  429 
  430         # No system dependencies
  431         self.dependencies_mock.return_value = {}
  432 
  433         self.rosdep_mock.return_value.resolve_dependency.return_value = None
  434 
  435         self.assertRaises(catkin.CatkinCannotResolveRoscoreError, plugin.pull)
  436 
  437     @mock.patch.object(catkin.CatkinPlugin, "_generate_snapcraft_setup_sh")
  438     def test_pull_invalid_underlay(self, generate_setup_mock):
  439         self.properties.underlay = {
  440             "build-path": "test-build-path",
  441             "run-path": "test-run-path",
  442         }
  443         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  444         os.makedirs(os.path.join(plugin.sourcedir, "src"))
  445 
  446         # No system dependencies
  447         self.dependencies_mock.return_value = {}
  448 
  449         raised = self.assertRaises(errors.SnapcraftEnvironmentError, plugin.pull)
  450 
  451         self.assertThat(
  452             str(raised),
  453             MatchesRegex(".*Requested underlay.*does not point to a valid directory"),
  454         )
  455 
  456     def test_clean_pull(self):
  457         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  458         os.makedirs(os.path.join(plugin.sourcedir, "src"))
  459 
  460         self.dependencies_mock.return_value = {"apt": {"foo", "bar", "baz"}}
  461 
  462         plugin.pull()
  463         os.makedirs(plugin._rosdep_path)
  464 
  465         plugin.clean_pull()
  466         self.assertFalse(os.path.exists(plugin._rosdep_path))
  467 
  468     def test_valid_catkin_workspace_src(self):
  469         # sourcedir is expected to be the root of the Catkin workspace. Since
  470         # it contains a 'src' directory, this is a valid Catkin workspace.
  471         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  472         os.makedirs(os.path.join(plugin.sourcedir, "src"))
  473         # An exception will be raised if pull can't handle the valid workspace.
  474         plugin.pull()
  475 
  476     def test_invalid_catkin_workspace_no_src(self):
  477         # sourcedir is expected to be the root of the Catkin workspace. Since
  478         # it does not contain a `src` folder and `source-space` is 'src', this
  479         # should fail.
  480         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  481         raised = self.assertRaises(catkin.CatkinPackagePathNotFoundError, plugin.pull)
  482 
  483         self.assertThat(
  484             str(raised),
  485             Equals(
  486                 "Failed to find package path: {!r}".format(
  487                     os.path.join(plugin.sourcedir, "src")
  488                 )
  489             ),
  490         )
  491 
  492     def test_valid_catkin_workspace_source_space(self):
  493         self.properties.source_space = "foo"
  494 
  495         # sourcedir is expected to be the root of the Catkin workspace.
  496         # Normally this would mean it contained a `src` directory, but it can
  497         # be remapped via the `source-space` key.
  498         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  499         os.makedirs(os.path.join(plugin.sourcedir, self.properties.source_space))
  500         # An exception will be raised if pull can't handle the source space.
  501         plugin.pull()
  502 
  503     def test_invalid_catkin_workspace_invalid_source_space(self):
  504         self.properties.source_space = "foo"
  505 
  506         # sourcedir is expected to be the root of the Catkin workspace. Since
  507         # it does not contain a `src` folder and source_space wasn't
  508         # specified, this should fail.
  509         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  510         raised = self.assertRaises(catkin.CatkinPackagePathNotFoundError, plugin.pull)
  511 
  512         self.assertThat(
  513             str(raised),
  514             Equals(
  515                 "Failed to find package path: {!r}".format(
  516                     os.path.join(plugin.sourcedir, self.properties.source_space)
  517                 )
  518             ),
  519         )
  520 
  521     def test_invalid_catkin_workspace_invalid_source_space_build_all(self):
  522         self.properties.source_space = "foo"
  523         self.properties.catkin_packages = None
  524 
  525         # sourcedir is expected to be the root of the Catkin workspace. Since
  526         # it does not contain a `src` folder and source_space wasn't
  527         # specified, this should fail.
  528         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  529         raised = self.assertRaises(catkin.CatkinPackagePathNotFoundError, plugin.pull)
  530 
  531         self.assertThat(
  532             str(raised),
  533             Equals(
  534                 "Failed to find package path: {!r}".format(
  535                     os.path.join(plugin.sourcedir, self.properties.source_space)
  536                 )
  537             ),
  538         )
  539 
  540     def test_invalid_catkin_workspace_invalid_source_no_packages(self):
  541         """Test that an invalid source space is fine iff no packages."""
  542         self.properties.source_space = "foo"
  543         self.properties.catkin_packages = []
  544 
  545         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  546 
  547         # Normally pulling should fail, but since there are no packages to
  548         # build, even an invalid workspace should be okay.
  549         plugin.pull()
  550 
  551     def test_invalid_catkin_workspace_source_space_same_as_source(self):
  552         self.properties.source_space = "."
  553 
  554         # sourcedir is expected to be the root of the Catkin workspace. Since
  555         # source_space was specified to be the same as the root, this should
  556         # fail.
  557         self.assertRaises(
  558             catkin.CatkinWorkspaceIsRootError,
  559             catkin.CatkinPlugin,
  560             "test-part",
  561             self.properties,
  562             self.project,
  563         )
  564 
  565     @mock.patch.object(catkin.CatkinPlugin, "run")
  566     @mock.patch.object(catkin.CatkinPlugin, "_run_in_bash")
  567     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="foo")
  568     @mock.patch.object(catkin.CatkinPlugin, "_prepare_build")
  569     @mock.patch.object(catkin.CatkinPlugin, "_finish_build")
  570     def test_build_multiple(
  571         self,
  572         finish_build_mock,
  573         prepare_build_mock,
  574         run_output_mock,
  575         bashrun_mock,
  576         run_mock,
  577     ):
  578         self.properties.catkin_packages.append("package_2")
  579 
  580         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  581         os.makedirs(os.path.join(plugin.sourcedir, "src"))
  582 
  583         plugin.build()
  584 
  585         class check_pkg_arguments:
  586             def __init__(self, test):
  587                 self.test = test
  588 
  589             def __eq__(self, args):
  590                 index = args.index("--pkg")
  591                 packages = args[index + 1 : index + 3]
  592                 self.test.assertIn("my_package", packages)
  593                 self.test.assertIn("package_2", packages)
  594                 return True
  595 
  596         bashrun_mock.assert_called_with(check_pkg_arguments(self))
  597 
  598         self.assertFalse(
  599             self.dependencies_mock.called,
  600             "Dependencies should have been discovered in the pull() step",
  601         )
  602 
  603         finish_build_mock.assert_called_once_with()
  604 
  605     @mock.patch.object(catkin.CatkinPlugin, "run")
  606     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="foo")
  607     def test_build_runs_in_bash(self, run_output_mock, run_mock):
  608         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  609         os.makedirs(os.path.join(plugin.sourcedir, "src"))
  610 
  611         plugin.build()
  612 
  613         run_mock.assert_has_calls(
  614             [mock.call(["/bin/bash", mock.ANY], cwd=mock.ANY, env=mock.ANY)]
  615         )
  616 
  617     def test_use_in_snap_python_rewrites_shebangs(self):
  618         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  619         os.makedirs(os.path.join(plugin.rosdir, "bin"))
  620 
  621         # Place a few files with bad shebangs, and some files that shouldn't be
  622         # changed.
  623         files = [
  624             {
  625                 "path": os.path.join(plugin.rosdir, "_setup_util.py"),
  626                 "contents": "#!/foo/bar/baz/python",
  627                 "expected": "#!/usr/bin/env python",
  628             },
  629             {
  630                 "path": os.path.join(plugin.rosdir, "bin/catkin"),
  631                 "contents": "#!/foo/baz/python",
  632                 "expected": "#!/usr/bin/env python",
  633             },
  634             {
  635                 "path": os.path.join(plugin.rosdir, "foo"),
  636                 "contents": "foo",
  637                 "expected": "foo",
  638             },
  639         ]
  640 
  641         for file_info in files:
  642             with open(file_info["path"], "w") as f:
  643                 f.write(file_info["contents"])
  644 
  645         plugin._use_in_snap_python()
  646 
  647         for file_info in files:
  648             with open(os.path.join(plugin.rosdir, file_info["path"]), "r") as f:
  649                 self.assertThat(f.read(), Equals(file_info["expected"]))
  650 
  651     @mock.patch.object(catkin.CatkinPlugin, "run")
  652     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="foo")
  653     def test_use_in_snap_python_skips_binarys(self, run_output_mock, run_mock):
  654         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  655         os.makedirs(plugin.rosdir)
  656 
  657         # Place a file to be discovered by _use_in_snap_python().
  658         open(os.path.join(plugin.rosdir, "foo"), "w").close()
  659 
  660         file_mock = mock.mock_open()
  661         with mock.patch.object(builtins, "open", file_mock):
  662             # Reading a binary file may throw a UnicodeDecodeError. Make sure
  663             # that's handled.
  664             file_mock.return_value.read.side_effect = UnicodeDecodeError(
  665                 "foo", b"bar", 1, 2, "baz"
  666             )
  667             # An exception will be raised if the function can't handle the
  668             # binary file.
  669             plugin._use_in_snap_python()
  670 
  671     def test_use_in_snap_python_rewrites_10_ros_sh(self):
  672         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  673         os.makedirs(os.path.join(plugin.rosdir, "etc", "catkin", "profile.d"))
  674 
  675         ros_profile = os.path.join(
  676             plugin.rosdir, "etc", "catkin", "profile.d", "10.ros.sh"
  677         )
  678 
  679         # Place 10.ros.sh with an absolute path to python
  680         with open(ros_profile, "w") as f:
  681             f.write("/usr/bin/python foo")
  682 
  683         plugin._use_in_snap_python()
  684 
  685         # Verify that the absolute path in 10.ros.sh was rewritten correctly
  686         with open(ros_profile, "r") as f:
  687             self.assertThat(
  688                 f.read(),
  689                 Equals("python foo"),
  690                 "The absolute path to python was not replaced as expected",
  691             )
  692 
  693     def test_use_in_snap_python_rewrites_1_ros_package_path_sh(self):
  694         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  695         os.makedirs(os.path.join(plugin.rosdir, "etc", "catkin", "profile.d"))
  696 
  697         ros_profile = os.path.join(
  698             plugin.rosdir, "etc", "catkin", "profile.d", "1.ros_package_path.sh"
  699         )
  700 
  701         # Place 1.ros_package_path.sh with an absolute path to python
  702         with open(ros_profile, "w") as f:
  703             f.write("/usr/bin/python foo")
  704 
  705         plugin._use_in_snap_python()
  706 
  707         # Verify that the absolute path in 1.ros_package_path.sh was rewritten
  708         # correctly
  709         with open(ros_profile, "r") as f:
  710             self.assertThat(
  711                 f.read(),
  712                 Equals("python foo"),
  713                 "The absolute path to python was not replaced as expected",
  714             )
  715 
  716     def _verify_run_environment(self, plugin):
  717         python_path = os.path.join(
  718             plugin.installdir, "usr", "lib", "python2.7", "dist-packages"
  719         )
  720         os.makedirs(python_path)
  721 
  722         # Joining and re-splitting to get hacked script in there as well
  723         environment = "\n".join(plugin.env(plugin.installdir)).split("\n")
  724 
  725         self.assertThat(
  726             environment,
  727             Contains("PYTHONPATH={}${{PYTHONPATH:+:$PYTHONPATH}}".format(python_path)),
  728         )
  729 
  730         self.assertThat(
  731             environment,
  732             Contains("ROS_MASTER_URI={}".format(self.properties.catkin_ros_master_uri)),
  733         )
  734 
  735         self.assertThat(environment, Contains("ROS_HOME=${SNAP_USER_DATA:-/tmp}/ros"))
  736 
  737         self.assertThat(environment, Contains("LC_ALL=C.UTF-8"))
  738 
  739         # Verify that LD_LIBRARY_PATH was set before setup.sh is sourced
  740         ld_library_path_index = [
  741             i for i, line in enumerate(environment) if "LD_LIBRARY_PATH" in line
  742         ][0]
  743         source_setup_index = [
  744             i for i, line in enumerate(environment) if "setup.sh" in line
  745         ][0]
  746         self.assertThat(ld_library_path_index, LessThan(source_setup_index))
  747 
  748         return environment
  749 
  750     @mock.patch.object(
  751         catkin.CatkinPlugin, "_source_setup_sh", return_value="test-source-setup.sh"
  752     )
  753     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="bar")
  754     def test_run_environment(self, run_mock, source_setup_sh_mock):
  755         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  756 
  757         environment = self._verify_run_environment(plugin)
  758 
  759         source_setup_sh_mock.assert_called_with(plugin.installdir, None)
  760         self.assertThat(environment, Contains("test-source-setup.sh"))
  761 
  762     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="bar")
  763     def test_run_environment_with_underlay(self, run_mock):
  764         self.properties.underlay = {
  765             "build-path": "test-build-path",
  766             "run-path": "test-run-path",
  767         }
  768         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  769 
  770         environment = self._verify_run_environment(plugin)
  771 
  772         setup_path = os.path.join(plugin.rosdir, "snapcraft-setup.sh")
  773         lines_of_interest = [
  774             "if [ -f {} ]; then".format(setup_path),
  775             ". {}".format(setup_path),
  776             "fi",
  777         ]
  778         actual_lines = []
  779 
  780         for line in environment:
  781             line = line.strip()
  782             if line in lines_of_interest:
  783                 actual_lines.append(line)
  784 
  785         self.assertThat(
  786             actual_lines,
  787             Equals(lines_of_interest),
  788             "Expected snapcraft-setup.sh to be sourced after checking for its "
  789             "existence",
  790         )
  791 
  792     @mock.patch.object(
  793         catkin.CatkinPlugin, "_source_setup_sh", return_value="test-source-setup.sh"
  794     )
  795     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="bar")
  796     def test_run_environment_with_catkin_ros_master_uri(
  797         self, run_mock, source_setup_sh_mock
  798     ):
  799 
  800         self.properties.catkin_ros_master_uri = "http://rosmaster:11311"
  801         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  802 
  803         self._verify_run_environment(plugin)
  804 
  805     def _evaluate_environment(self, predefinition=""):
  806         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  807 
  808         python_path = os.path.join(
  809             plugin.installdir, "usr", "lib", "python2.7", "dist-packages"
  810         )
  811         os.makedirs(python_path)
  812 
  813         # Save plugin environment off into a file and read the evaluated
  814         # version back in, thus obtaining the real environment
  815         with tempfile.NamedTemporaryFile(mode="w+") as f:
  816             f.write(predefinition)
  817             f.write("\n".join(["export " + e for e in plugin.env(plugin.installdir)]))
  818             f.write('python3 -c "import os; print(dict(os.environ))"')
  819             f.flush()
  820             return ast.literal_eval(
  821                 subprocess.check_output(["/bin/sh", f.name])
  822                 .decode(sys.getfilesystemencoding())
  823                 .strip()
  824             )
  825 
  826     def _pythonpath_segments(self, environment):
  827         # Verify that the environment contains PYTHONPATH, and return its
  828         # segments as a list.
  829         self.assertThat(environment, Contains("PYTHONPATH"))
  830         return environment["PYTHONPATH"].split(":")
  831 
  832     def _list_contains_empty_items(self, item_list):
  833         empty_items = [i for i in item_list if not i.strip()]
  834         return len(empty_items) > 0
  835 
  836     def test_pythonpath_if_not_defined(self):
  837         environment = self._evaluate_environment()
  838         segments = self._pythonpath_segments(environment)
  839         self.assertFalse(
  840             self._list_contains_empty_items(segments),
  841             "PYTHONPATH unexpectedly contains empty segments: {}".format(
  842                 environment["PYTHONPATH"]
  843             ),
  844         )
  845 
  846     def test_pythonpath_if_null(self):
  847         environment = self._evaluate_environment(
  848             textwrap.dedent(
  849                 """
  850             export PYTHONPATH=
  851         """
  852             )
  853         )
  854 
  855         segments = self._pythonpath_segments(environment)
  856         self.assertFalse(
  857             self._list_contains_empty_items(segments),
  858             "PYTHONPATH unexpectedly contains empty segments: {}".format(
  859                 environment["PYTHONPATH"]
  860             ),
  861         )
  862 
  863     def test_pythonpath_if_not_empty(self):
  864         environment = self._evaluate_environment(
  865             textwrap.dedent(
  866                 """
  867             export PYTHONPATH=foo
  868         """
  869             )
  870         )
  871 
  872         segments = self._pythonpath_segments(environment)
  873         self.assertFalse(
  874             self._list_contains_empty_items(segments),
  875             "PYTHONPATH unexpectedly contains empty segments: {}".format(
  876                 environment["PYTHONPATH"]
  877             ),
  878         )
  879         self.assertThat(segments, Contains("foo"))
  880 
  881     @mock.patch.object(
  882         catkin.CatkinPlugin, "_source_setup_sh", return_value="test-source-setup"
  883     )
  884     def test_generate_snapcraft_sh(self, source_setup_sh_mock):
  885         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  886 
  887         plugin._generate_snapcraft_setup_sh("test-root", None)
  888         source_setup_sh_mock.assert_called_with("test-root", None)
  889         with open(os.path.join(plugin.rosdir, "snapcraft-setup.sh"), "r") as f:
  890             self.assertThat(f.read(), Equals("test-source-setup"))
  891 
  892     def test_source_setup_sh(self):
  893         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  894 
  895         # Make sure $@ is zeroed, then setup.sh sourced, then $@ is restored
  896         rosdir = os.path.join("test-root", "opt", "ros", self.ros_distro)
  897         setup_path = os.path.join(rosdir, "setup.sh")
  898         lines_of_interest = [
  899             "set --",
  900             "if [ -f {} ]; then".format(setup_path),
  901             "_CATKIN_SETUP_DIR={} . {}".format(rosdir, setup_path),
  902             "fi",
  903             'eval "set -- $BACKUP_ARGS"',
  904         ]
  905         actual_lines = []
  906 
  907         for line in plugin._source_setup_sh("test-root", None).split("\n"):
  908             line = line.strip()
  909             if line in lines_of_interest:
  910                 actual_lines.append(line)
  911 
  912         self.assertThat(
  913             actual_lines,
  914             Equals(lines_of_interest),
  915             "Expected ROS's setup.sh to be sourced after args were zeroed, "
  916             "followed by the args being restored.",
  917         )
  918 
  919     def test_generate_snapcraft_sh_with_underlay(self):
  920         self.properties.underlay = {
  921             "build-path": "test-build-underlay",
  922             "run-path": "test-run-underlay",
  923         }
  924         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  925 
  926         # Make sure $@ is zeroed, then setup.sh sourced, then $@ is restored
  927         underlay_setup_path = os.path.join("test-underlay", "setup.sh")
  928         rosdir = os.path.join("test-root", "opt", "ros", self.ros_distro)
  929         setup_path = os.path.join(rosdir, "setup.sh")
  930         lines_of_interest = [
  931             "set --",
  932             "if [ -f {} ]; then".format(underlay_setup_path),
  933             "_CATKIN_SETUP_DIR={} . {}".format("test-underlay", underlay_setup_path),
  934             "if [ -f {} ]; then".format(setup_path),
  935             "set -- --extend",
  936             "_CATKIN_SETUP_DIR={} . {}".format(rosdir, setup_path),
  937             "fi",
  938             "fi",
  939             'eval "set -- $BACKUP_ARGS"',
  940         ]
  941         actual_lines = []
  942 
  943         plugin._generate_snapcraft_setup_sh("test-root", "test-underlay")
  944         with open(os.path.join(plugin.rosdir, "snapcraft-setup.sh"), "r") as f:
  945             for line in f:
  946                 line = line.strip()
  947                 if line in lines_of_interest:
  948                     actual_lines.append(line)
  949 
  950         self.assertThat(
  951             actual_lines,
  952             Equals(lines_of_interest),
  953             "Expected ROS's setup.sh to be sourced after args were zeroed, "
  954             "followed by the args being restored.",
  955         )
  956 
  957     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="bar")
  958     def test_run_environment_no_python(self, run_mock):
  959         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  960 
  961         python_path = os.path.join(
  962             plugin.installdir, "usr", "lib", "python2.7", "dist-packages"
  963         )
  964 
  965         environment = plugin.env(plugin.installdir)
  966 
  967         self.assertFalse(
  968             "PYTHONPATH={}".format(python_path) in environment, environment
  969         )
  970 
  971     @mock.patch.object(catkin.CatkinPlugin, "_use_in_snap_python")
  972     def test_prepare_build(self, use_python_mock):
  973         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
  974         os.makedirs(os.path.join(plugin.rosdir, "test"))
  975 
  976         # Place a few .cmake files with incorrect paths, and some files that
  977         # shouldn't be changed.
  978         files = [
  979             {
  980                 "path": "fooConfig.cmake",
  981                 "contents": '"/usr/lib/foo"',
  982                 "expected": '"{}/usr/lib/foo"'.format(plugin.installdir),
  983             },
  984             {
  985                 "path": "anotherConfig.cmake",
  986                 "contents": '"$ENV{SNAPCRAFT_STAGE}/usr/lib/another"',
  987                 "expected": '"{}/usr/lib/another"'.format(plugin.installdir),
  988             },
  989             {
  990                 "path": "bar.cmake",
  991                 "contents": '"/usr/lib/bar"',
  992                 "expected": '"/usr/lib/bar"',
  993             },
  994             {
  995                 "path": "test/bazConfig.cmake",
  996                 "contents": '"/test/baz;/usr/lib/baz"',
  997                 "expected": '"{0}/test/baz;{0}/usr/lib/baz"'.format(plugin.installdir),
  998             },
  999             {"path": "test/quxConfig.cmake", "contents": "qux", "expected": "qux"},
 1000             {
 1001                 "path": "test/installedConfig.cmake",
 1002                 "contents": '"{}/foo"'.format(plugin.installdir),
 1003                 "expected": '"{}/foo"'.format(plugin.installdir),
 1004             },
 1005         ]
 1006 
 1007         for file_info in files:
 1008             path = os.path.join(plugin.rosdir, file_info["path"])
 1009             with open(path, "w") as f:
 1010                 f.write(file_info["contents"])
 1011 
 1012         plugin._prepare_build()
 1013 
 1014         self.assertTrue(use_python_mock.called)
 1015 
 1016         for file_info in files:
 1017             path = os.path.join(plugin.rosdir, file_info["path"])
 1018             with open(path, "r") as f:
 1019                 self.assertThat(f.read(), Equals(file_info["expected"]))
 1020 
 1021 
 1022 class PullTestCase(CatkinPluginBaseTest):
 1023 
 1024     scenarios = [
 1025         ("no underlay", {"underlay": None, "expected_underlay_path": None}),
 1026         (
 1027             "underlay",
 1028             {
 1029                 "underlay": {
 1030                     "build-path": "test-build-path",
 1031                     "run-path": "test-run-path",
 1032                 },
 1033                 "expected_underlay_path": "test-build-path",
 1034             },
 1035         ),
 1036     ]
 1037 
 1038     def setUp(self):
 1039         super().setUp()
 1040 
 1041         # Make the underlay a valid workspace
 1042         if self.underlay:
 1043             os.makedirs(self.underlay["build-path"])
 1044             open(os.path.join(self.underlay["build-path"], "setup.sh"), "w").close()
 1045             self.properties.underlay = self.underlay
 1046 
 1047     @mock.patch.object(catkin.CatkinPlugin, "_generate_snapcraft_setup_sh")
 1048     def test_pull_debian_dependencies(self, generate_setup_mock):
 1049         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
 1050         os.makedirs(os.path.join(plugin.sourcedir, "src"))
 1051 
 1052         self.dependencies_mock.return_value = {"apt": {"foo", "bar", "baz"}}
 1053 
 1054         plugin.pull()
 1055 
 1056         self.assert_rosdep_setup(
 1057             self.ros_distro,
 1058             os.path.join(plugin.sourcedir, "src"),
 1059             os.path.join(plugin.partdir, "rosdep"),
 1060             self.ubuntu_distro,
 1061             plugin.PLUGIN_STAGE_SOURCES,
 1062             plugin.PLUGIN_STAGE_KEYRINGS,
 1063         )
 1064 
 1065         self.assert_rospack_setup(
 1066             self.ros_distro,
 1067             os.path.join(plugin.sourcedir, "src"),
 1068             os.path.join(plugin.partdir, "rospack"),
 1069             plugin.PLUGIN_STAGE_SOURCES,
 1070             plugin.PLUGIN_STAGE_KEYRINGS,
 1071         )
 1072 
 1073         self.wstool_mock.assert_not_called()
 1074 
 1075         # This shouldn't be called unless there's an underlay
 1076         if self.properties.underlay:
 1077             generate_setup_mock.assert_called_once_with(
 1078                 plugin.installdir, self.expected_underlay_path
 1079             )
 1080         else:
 1081             generate_setup_mock.assert_not_called()
 1082 
 1083         # Verify that dependencies were found as expected. TODO: Would really
 1084         # like to use ANY here instead of verifying explicit arguments, but
 1085         # Python issue #25195 won't let me.
 1086         self.assertThat(self.dependencies_mock.call_count, Equals(1))
 1087         self.assertThat(self.dependencies_mock.call_args[0][0], Equals({"my_package"}))
 1088 
 1089         # Verify that the dependencies were installed
 1090         self.ubuntu_mock.return_value.get.assert_called_with(
 1091             _CompareContainers(self, ["foo", "bar", "baz"])
 1092         )
 1093         self.ubuntu_mock.return_value.unpack.assert_called_with(plugin.installdir)
 1094 
 1095     @mock.patch.object(catkin.CatkinPlugin, "_generate_snapcraft_setup_sh")
 1096     def test_pull_local_dependencies(self, generate_setup_mock):
 1097         self.properties.catkin_packages.append("package_2")
 1098 
 1099         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
 1100         os.makedirs(os.path.join(plugin.sourcedir, "src"))
 1101 
 1102         # No system dependencies (only local)
 1103         self.dependencies_mock.return_value = {}
 1104 
 1105         plugin.pull()
 1106 
 1107         self.assert_rosdep_setup(
 1108             self.ros_distro,
 1109             os.path.join(plugin.sourcedir, "src"),
 1110             os.path.join(plugin.partdir, "rosdep"),
 1111             self.ubuntu_distro,
 1112             plugin.PLUGIN_STAGE_SOURCES,
 1113             plugin.PLUGIN_STAGE_KEYRINGS,
 1114         )
 1115 
 1116         self.assert_rospack_setup(
 1117             self.ros_distro,
 1118             os.path.join(plugin.sourcedir, "src"),
 1119             os.path.join(plugin.partdir, "rospack"),
 1120             plugin.PLUGIN_STAGE_SOURCES,
 1121             plugin.PLUGIN_STAGE_KEYRINGS,
 1122         )
 1123 
 1124         self.wstool_mock.assert_not_called()
 1125 
 1126         # This shouldn't be called unless there's an underlay
 1127         if self.properties.underlay:
 1128             generate_setup_mock.assert_called_once_with(
 1129                 plugin.installdir, self.expected_underlay_path
 1130             )
 1131         else:
 1132             generate_setup_mock.assert_not_called()
 1133 
 1134         # Verify that dependencies were found as expected. TODO: Would really
 1135         # like to use ANY here instead of verifying explicit arguments, but
 1136         # Python issue #25195 won't let me.
 1137         self.assertThat(self.dependencies_mock.call_count, Equals(1))
 1138         self.assertThat(
 1139             self.dependencies_mock.call_args[0][0], Equals({"my_package", "package_2"})
 1140         )
 1141 
 1142         # Verify that no .deb packages were installed
 1143         self.assertTrue(
 1144             mock.call().unpack(plugin.installdir) not in self.ubuntu_mock.mock_calls
 1145         )
 1146 
 1147     @mock.patch.object(catkin.CatkinPlugin, "_generate_snapcraft_setup_sh")
 1148     def test_pull_with_roscore(self, generate_setup_mock):
 1149         self.properties.include_roscore = True
 1150         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
 1151         os.makedirs(os.path.join(plugin.sourcedir, "src"))
 1152 
 1153         # No system dependencies
 1154         self.dependencies_mock.return_value = {}
 1155 
 1156         def resolve(package_name):
 1157             if package_name == "ros_core":
 1158                 return {"apt": {"ros-core-dependency"}}
 1159 
 1160         self.rosdep_mock.return_value.resolve_dependency = resolve
 1161 
 1162         plugin.pull()
 1163 
 1164         self.assert_rosdep_setup(
 1165             self.ros_distro,
 1166             os.path.join(plugin.sourcedir, "src"),
 1167             os.path.join(plugin.partdir, "rosdep"),
 1168             self.ubuntu_distro,
 1169             plugin.PLUGIN_STAGE_SOURCES,
 1170             plugin.PLUGIN_STAGE_KEYRINGS,
 1171         )
 1172 
 1173         self.assert_rospack_setup(
 1174             self.ros_distro,
 1175             os.path.join(plugin.sourcedir, "src"),
 1176             os.path.join(plugin.partdir, "rospack"),
 1177             plugin.PLUGIN_STAGE_SOURCES,
 1178             plugin.PLUGIN_STAGE_KEYRINGS,
 1179         )
 1180 
 1181         self.wstool_mock.assert_not_called()
 1182 
 1183         # This shouldn't be called unless there's an underlay
 1184         if self.properties.underlay:
 1185             generate_setup_mock.assert_called_once_with(
 1186                 plugin.installdir, self.expected_underlay_path
 1187             )
 1188         else:
 1189             generate_setup_mock.assert_not_called()
 1190 
 1191         # Verify that roscore was installed
 1192         self.ubuntu_mock.return_value.get.assert_called_with({"ros-core-dependency"})
 1193         self.ubuntu_mock.return_value.unpack.assert_called_with(plugin.installdir)
 1194 
 1195     @mock.patch.object(catkin.CatkinPlugin, "_generate_snapcraft_setup_sh")
 1196     def test_pull_with_rosinstall_files(self, generate_setup_mock):
 1197         self.properties.rosinstall_files = ["rosinstall-file"]
 1198         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
 1199         os.makedirs(os.path.join(plugin.sourcedir, "src"))
 1200 
 1201         # No system dependencies
 1202         self.dependencies_mock.return_value = {}
 1203 
 1204         plugin.pull()
 1205 
 1206         self.assert_rosdep_setup(
 1207             self.ros_distro,
 1208             os.path.join(plugin.sourcedir, "src"),
 1209             os.path.join(plugin.partdir, "rosdep"),
 1210             self.ubuntu_distro,
 1211             plugin.PLUGIN_STAGE_SOURCES,
 1212             plugin.PLUGIN_STAGE_KEYRINGS,
 1213         )
 1214 
 1215         self.assert_rospack_setup(
 1216             self.ros_distro,
 1217             os.path.join(plugin.sourcedir, "src"),
 1218             os.path.join(plugin.partdir, "rospack"),
 1219             plugin.PLUGIN_STAGE_SOURCES,
 1220             plugin.PLUGIN_STAGE_KEYRINGS,
 1221         )
 1222 
 1223         self.assert_wstool_setup(
 1224             os.path.join(plugin.sourcedir, "src"),
 1225             os.path.join(plugin.partdir, "wstool"),
 1226             plugin.PLUGIN_STAGE_SOURCES,
 1227             plugin.PLUGIN_STAGE_KEYRINGS,
 1228         )
 1229 
 1230         self.wstool_mock.assert_has_calls(
 1231             [
 1232                 mock.call().merge(os.path.join(plugin.sourcedir, "rosinstall-file")),
 1233                 mock.call().update(),
 1234             ]
 1235         )
 1236 
 1237         # This shouldn't be called unless there's an underlay
 1238         if self.properties.underlay:
 1239             generate_setup_mock.assert_called_once_with(
 1240                 plugin.installdir, self.expected_underlay_path
 1241             )
 1242         else:
 1243             generate_setup_mock.assert_not_called()
 1244 
 1245         # Verify that no .deb packages were installed
 1246         self.assertTrue(
 1247             mock.call().unpack(plugin.installdir) not in self.ubuntu_mock.mock_calls
 1248         )
 1249 
 1250     @mock.patch.object(catkin.CatkinPlugin, "_generate_snapcraft_setup_sh")
 1251     def test_pull_pip_dependencies(self, generate_setup_mock):
 1252         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
 1253         os.makedirs(os.path.join(plugin.sourcedir, "src"))
 1254 
 1255         self.dependencies_mock.return_value = {"pip": {"foo", "bar", "baz"}}
 1256 
 1257         plugin.pull()
 1258 
 1259         self.assert_rosdep_setup(
 1260             self.ros_distro,
 1261             os.path.join(plugin.sourcedir, "src"),
 1262             os.path.join(plugin.partdir, "rosdep"),
 1263             self.ubuntu_distro,
 1264             plugin.PLUGIN_STAGE_SOURCES,
 1265             plugin.PLUGIN_STAGE_KEYRINGS,
 1266         )
 1267 
 1268         self.assert_rospack_setup(
 1269             self.ros_distro,
 1270             os.path.join(plugin.sourcedir, "src"),
 1271             os.path.join(plugin.partdir, "rospack"),
 1272             plugin.PLUGIN_STAGE_SOURCES,
 1273             plugin.PLUGIN_STAGE_KEYRINGS,
 1274         )
 1275 
 1276         self.wstool_mock.assert_not_called()
 1277 
 1278         self.assert_pip_setup(
 1279             "2", plugin.partdir, plugin.installdir, plugin.project.stage_dir
 1280         )
 1281 
 1282         # This shouldn't be called unless there's an underlay
 1283         if self.properties.underlay:
 1284             generate_setup_mock.assert_called_once_with(
 1285                 plugin.installdir, self.expected_underlay_path
 1286             )
 1287         else:
 1288             generate_setup_mock.assert_not_called()
 1289 
 1290         # Verify that dependencies were found as expected. TODO: Would really
 1291         # like to use ANY here instead of verifying explicit arguments, but
 1292         # Python issue #25195 won't let me.
 1293         self.assertThat(self.dependencies_mock.call_count, Equals(1))
 1294         self.assertThat(self.dependencies_mock.call_args[0][0], Equals({"my_package"}))
 1295 
 1296         # Verify that the pip dependencies were installed
 1297         self.pip_mock.return_value.download.assert_called_once_with(
 1298             {"foo", "bar", "baz"}
 1299         )
 1300         self.pip_mock.return_value.install.assert_called_once_with(
 1301             {"foo", "bar", "baz"}
 1302         )
 1303 
 1304 
 1305 class BuildTestCase(CatkinPluginBaseTest):
 1306 
 1307     scenarios = [
 1308         (
 1309             "release without catkin-cmake-args",
 1310             {"build_attributes": [], "catkin_cmake_args": []},
 1311         ),
 1312         (
 1313             "release with catkin-cmake-args",
 1314             {"build_attributes": [], "catkin_cmake_args": ["-DFOO"]},
 1315         ),
 1316         (
 1317             "debug without catkin-cmake-args",
 1318             {"build_attributes": ["debug"], "catkin_cmake_args": []},
 1319         ),
 1320         (
 1321             "debug with catkin-cmake-args",
 1322             {"build_attributes": ["debug"], "catkin_cmake_args": ["-DFOO"]},
 1323         ),
 1324         (
 1325             "release without catkin-cmake-args single threaded",
 1326             {"build_attributes": [], "catkin_cmake_args": [], "disable_parallel": True},
 1327         ),
 1328     ]
 1329 
 1330     def setUp(self):
 1331         super().setUp()
 1332         self.properties.build_attributes.extend(self.build_attributes)
 1333         self.properties.catkin_cmake_args = self.catkin_cmake_args
 1334         if hasattr(self, "disable_parallel"):
 1335             self.properties.disable_parallel = self.disable_parallel
 1336 
 1337     @mock.patch.object(catkin.CatkinPlugin, "run")
 1338     @mock.patch.object(catkin.CatkinPlugin, "_run_in_bash")
 1339     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="foo")
 1340     @mock.patch.object(catkin.CatkinPlugin, "_prepare_build")
 1341     @mock.patch.object(catkin.CatkinPlugin, "_finish_build")
 1342     def test_build(
 1343         self,
 1344         finish_build_mock,
 1345         prepare_build_mock,
 1346         run_output_mock,
 1347         bashrun_mock,
 1348         run_mock,
 1349     ):
 1350         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
 1351         os.makedirs(os.path.join(plugin.sourcedir, "src"))
 1352 
 1353         plugin.build()
 1354 
 1355         prepare_build_mock.assert_called_once_with()
 1356 
 1357         # Matching like this for order independence (otherwise it would be
 1358         # quite fragile)
 1359         build_attributes = self.build_attributes
 1360         catkin_cmake_args = self.catkin_cmake_args
 1361         disable_parallel = self.properties.disable_parallel
 1362 
 1363         class check_build_command:
 1364             def __eq__(self, args):
 1365                 command = " ".join(args)
 1366                 if "debug" in build_attributes:
 1367                     build_type_valid = re.match(
 1368                         ".*--cmake-args.*-DCMAKE_BUILD_TYPE=Debug", command
 1369                     )
 1370                 else:
 1371                     build_type_valid = re.match(
 1372                         ".*--cmake-args.*-DCMAKE_BUILD_TYPE=Release", command
 1373                     )
 1374                 args_valid = True
 1375                 if catkin_cmake_args:
 1376                     expected_args = " ".join(catkin_cmake_args)
 1377                     args_valid = re.match(
 1378                         ".*--cmake-args.*{}".format(re.escape(expected_args)), command
 1379                     )
 1380                 if disable_parallel:
 1381                     args_parallel_build_count = re.match(".*-j1", command)
 1382                 else:
 1383                     args_parallel_build_count = re.match(
 1384                         ".*-j{}".format(plugin.parallel_build_count), command
 1385                     )
 1386                 return (
 1387                     args_valid
 1388                     and build_type_valid
 1389                     and args[0] == "catkin_make_isolated"
 1390                     and "--install" in command
 1391                     and "--pkg my_package" in command
 1392                     and "--directory {}".format(plugin.builddir) in command
 1393                     and "--install-space {}".format(plugin.rosdir) in command
 1394                     and "--source-space {}".format(
 1395                         os.path.join(plugin.builddir, plugin.options.source_space)
 1396                     )
 1397                     in command
 1398                     and args_parallel_build_count
 1399                 )
 1400 
 1401         bashrun_mock.assert_called_with(check_build_command())
 1402 
 1403         self.assertFalse(
 1404             self.dependencies_mock.called,
 1405             "Dependencies should have been discovered in the pull() step",
 1406         )
 1407 
 1408         finish_build_mock.assert_called_once_with()
 1409 
 1410     @mock.patch.object(catkin.CatkinPlugin, "run")
 1411     @mock.patch.object(catkin.CatkinPlugin, "_run_in_bash")
 1412     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="foo")
 1413     @mock.patch.object(catkin.CatkinPlugin, "_prepare_build")
 1414     @mock.patch.object(catkin.CatkinPlugin, "_finish_build")
 1415     def test_build_all_packages(
 1416         self,
 1417         finish_build_mock,
 1418         prepare_build_mock,
 1419         run_output_mock,
 1420         bashrun_mock,
 1421         run_mock,
 1422     ):
 1423         self.properties.catkin_packages = None
 1424         plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
 1425         os.makedirs(os.path.join(plugin.sourcedir, "src"))
 1426 
 1427         plugin.build()
 1428 
 1429         prepare_build_mock.assert_called_once_with()
 1430 
 1431         # Matching like this for order independence (otherwise it would be
 1432         # quite fragile)
 1433         build_attributes = self.build_attributes
 1434         catkin_cmake_args = self.catkin_cmake_args
 1435 
 1436         class check_build_command:
 1437             def __eq__(self, args):
 1438                 command = " ".join(args)
 1439                 if "debug" in build_attributes:
 1440                     build_type_valid = re.match(
 1441                         ".*--cmake-args.*-DCMAKE_BUILD_TYPE=Debug", command
 1442                     )
 1443                 else:
 1444                     build_type_valid = re.match(
 1445                         ".*--cmake-args.*-DCMAKE_BUILD_TYPE=Release", command
 1446                     )
 1447                 args_valid = True
 1448                 if catkin_cmake_args:
 1449                     expected_args = " ".join(catkin_cmake_args)
 1450                     args_valid = re.match(
 1451                         ".*--cmake-args.*{}".format(re.escape(expected_args)), command
 1452                     )
 1453                 return (
 1454                     args_valid
 1455                     and build_type_valid
 1456                     and args[0] == "catkin_make_isolated"
 1457                     and "--install" in command
 1458                     and "--directory {}".format(plugin.builddir) in command
 1459                     and "--install-space {}".format(plugin.rosdir) in command
 1460                     and "--source-space {}".format(
 1461                         os.path.join(plugin.builddir, plugin.options.source_space)
 1462                     )
 1463                     in command
 1464                 )
 1465 
 1466         bashrun_mock.assert_called_with(check_build_command())
 1467 
 1468         self.assertFalse(
 1469             self.dependencies_mock.called,
 1470             "Dependencies should have been discovered in the pull() step",
 1471         )
 1472 
 1473         finish_build_mock.assert_called_once_with()
 1474 
 1475 
 1476 class FinishBuildTestCase(CatkinPluginBaseTest):
 1477 
 1478     scenarios = [
 1479         ("no underlay", {"underlay": None, "expected_underlay_path": None}),
 1480         (
 1481             "underlay",
 1482             {
 1483                 "underlay": {
 1484                     "build-path": "test-build-path",
 1485                     "run-path": "test-run-path",
 1486                 },
 1487                 "expected_underlay_path": "test-run-path",
 1488             },
 1489         ),
 1490     ]
 1491 
 1492     def setUp(self):
 1493         super().setUp()
 1494 
 1495         self.properties.underlay = self.underlay
 1496         self.plugin = catkin.CatkinPlugin("test-part", self.properties, self.project)
 1497 
 1498     @mock.patch.object(catkin.CatkinPlugin, "_generate_snapcraft_setup_sh")
 1499     @mock.patch.object(catkin.CatkinPlugin, "_use_in_snap_python")
 1500     def test_finish_build_cmake_paths(self, use_python_mock, generate_setup_mock):
 1501         os.makedirs(os.path.join(self.plugin.rosdir, "test"))
 1502 
 1503         # Place a few .cmake files with incorrect paths, and some files that
 1504         # shouldn't be changed.
 1505         files = [
 1506             {
 1507                 "path": "fooConfig.cmake",
 1508                 "contents": '"{}/usr/lib/foo"'.format(self.plugin.installdir),
 1509                 "expected": '"$ENV{SNAPCRAFT_STAGE}/usr/lib/foo"',
 1510             },
 1511             {
 1512                 "path": "bar.cmake",
 1513                 "contents": '"/usr/lib/bar"',
 1514                 "expected": '"/usr/lib/bar"',
 1515             },
 1516             {
 1517                 "path": "test/bazConfig.cmake",
 1518                 "contents": '"{0}/test/baz;{0}/usr/lib/baz"'.format(
 1519                     self.plugin.installdir
 1520                 ),
 1521                 "expected": '"$ENV{SNAPCRAFT_STAGE}/test/baz;'
 1522                 '$ENV{SNAPCRAFT_STAGE}/usr/lib/baz"',
 1523             },
 1524             {"path": "test/quxConfig.cmake", "contents": "qux", "expected": "qux"},
 1525             {
 1526                 "path": "test/installedConfig.cmake",
 1527                 "contents": '"$ENV{SNAPCRAFT_STAGE}/foo"',
 1528                 "expected": '"$ENV{SNAPCRAFT_STAGE}/foo"',
 1529             },
 1530         ]
 1531 
 1532         for file_info in files:
 1533             path = os.path.join(self.plugin.rosdir, file_info["path"])
 1534             with open(path, "w") as f:
 1535                 f.write(file_info["contents"])
 1536 
 1537         self.plugin._finish_build()
 1538 
 1539         self.assertTrue(use_python_mock.called)
 1540 
 1541         # This shouldn't be called unless there's an underlay
 1542         if self.properties.underlay:
 1543             generate_setup_mock.assert_called_once_with(
 1544                 "$SNAP", self.expected_underlay_path
 1545             )
 1546         else:
 1547             generate_setup_mock.assert_not_called()
 1548 
 1549         for file_info in files:
 1550             path = os.path.join(self.plugin.rosdir, file_info["path"])
 1551             with open(path, "r") as f:
 1552                 self.assertThat(f.read(), Equals(file_info["expected"]))
 1553 
 1554         # Verify that no sitecustomize.py was generated
 1555         self.assertThat(
 1556             os.path.join(
 1557                 self.plugin.installdir, "usr", "lib", "python2.7", "sitecustomize.py"
 1558             ),
 1559             Not(FileExists()),
 1560         )
 1561 
 1562     @mock.patch.object(catkin.CatkinPlugin, "_generate_snapcraft_setup_sh")
 1563     @mock.patch.object(catkin.CatkinPlugin, "run")
 1564     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="foo")
 1565     @mock.patch.object(catkin.CatkinPlugin, "_use_in_snap_python")
 1566     def test_finish_build_cmake_prefix_path(
 1567         self, use_python_mock, run_output_mock, run_mock, generate_setup_mock
 1568     ):
 1569         setup_file = os.path.join(self.plugin.rosdir, "_setup_util.py")
 1570         os.makedirs(os.path.dirname(setup_file))
 1571 
 1572         with open(setup_file, "w") as f:
 1573             f.write(
 1574                 "CMAKE_PREFIX_PATH = '{0}/{1};{0}\n".format(
 1575                     self.plugin.rosdir, self.ros_distro
 1576                 )
 1577             )
 1578 
 1579         self.plugin._finish_build()
 1580 
 1581         self.assertTrue(use_python_mock.called)
 1582 
 1583         # This shouldn't be called unless there's an underlay
 1584         if self.properties.underlay:
 1585             generate_setup_mock.assert_called_once_with(
 1586                 "$SNAP", self.expected_underlay_path
 1587             )
 1588         else:
 1589             generate_setup_mock.assert_not_called()
 1590 
 1591         expected = "CMAKE_PREFIX_PATH = []\n"
 1592 
 1593         with open(setup_file, "r") as f:
 1594             self.assertThat(
 1595                 f.read(),
 1596                 Equals(expected),
 1597                 "The absolute path to python or the CMAKE_PREFIX_PATH "
 1598                 "was not replaced as expected",
 1599             )
 1600 
 1601         # Verify that no sitecustomize.py was generated
 1602         self.assertThat(
 1603             os.path.join(
 1604                 self.plugin.installdir, "usr", "lib", "python2.7", "sitecustomize.py"
 1605             ),
 1606             Not(FileExists()),
 1607         )
 1608 
 1609     @mock.patch.object(catkin.CatkinPlugin, "_generate_snapcraft_setup_sh")
 1610     @mock.patch.object(catkin.CatkinPlugin, "run")
 1611     @mock.patch.object(catkin.CatkinPlugin, "run_output", return_value="foo")
 1612     @mock.patch.object(catkin.CatkinPlugin, "_use_in_snap_python")
 1613     def test_finish_build_python_sitecustomize(
 1614         self, use_python_mock, run_output_mock, run_mock, generate_setup_mock
 1615     ):
 1616         self.pip_mock.return_value.list.return_value = {"test-package"}
 1617 
 1618         # Create site.py, indicating that python2 was a stage-package
 1619         site_py_path = os.path.join(
 1620             self.plugin.installdir, "usr", "lib", "python2.7", "site.py"
 1621         )
 1622         os.makedirs(os.path.dirname(site_py_path), exist_ok=True)
 1623         open(site_py_path, "w").close()
 1624 
 1625         # Also create python2 site-packages, indicating that pip packages were
 1626         # installed.
 1627         os.makedirs(
 1628             os.path.join(self.plugin.installdir, "lib", "python2.7", "site-packages"),
 1629             exist_ok=True,
 1630         )
 1631 
 1632         self.plugin._finish_build()
 1633 
 1634         # Verify that sitecustomize.py was generated
 1635         self.assertThat(
 1636             os.path.join(
 1637                 self.plugin.installdir, "usr", "lib", "python2.7", "sitecustomize.py"
 1638             ),
 1639             FileExists(),
 1640         )
 1641 
 1642 
 1643 class FindSystemDependenciesTestCase(unit.TestCase):
 1644     def setUp(self):
 1645         super().setUp()
 1646 
 1647         self.rosdep_mock = mock.MagicMock()
 1648         self.rosdep_mock.get_dependencies.return_value = {"bar"}
 1649 
 1650         self.rospack_mock = mock.MagicMock()
 1651         self.rospack_mock.list_names.return_value = set()
 1652 
 1653         self.catkin_mock = mock.MagicMock()
 1654         exception = catkin.CatkinPackageNotFoundError("foo")
 1655         self.catkin_mock.find.side_effect = exception
 1656 
 1657     def test_find_system_dependencies_system_only(self):
 1658         self.rosdep_mock.resolve_dependency.return_value = {"apt": {"baz"}}
 1659 
 1660         self.assertThat(
 1661             catkin._find_system_dependencies(
 1662                 {"foo"}, self.rosdep_mock, self.rospack_mock, self.catkin_mock
 1663             ),
 1664             Equals({"apt": {"baz"}}),
 1665         )
 1666 
 1667         self.rosdep_mock.get_dependencies.assert_called_once_with("foo")
 1668         self.rosdep_mock.resolve_dependency.assert_called_once_with("bar")
 1669         self.catkin_mock.find.assert_called_once_with("bar")
 1670 
 1671     def test_find_system_dependencies_system_only_no_packages(self):
 1672         self.rosdep_mock.resolve_dependency.return_value = {"apt": {"baz"}}
 1673 
 1674         self.assertThat(
 1675             catkin._find_system_dependencies(
 1676                 None, self.rosdep_mock, self.rospack_mock, self.catkin_mock
 1677             ),
 1678             Equals({"apt": {"baz"}}),
 1679         )
 1680 
 1681         self.rosdep_mock.get_dependencies.assert_called_once_with()
 1682         self.rosdep_mock.resolve_dependency.assert_called_once_with("bar")
 1683         self.catkin_mock.find.assert_called_once_with("bar")
 1684 
 1685     def test_find_system_dependencies_already_satisfied_in_source(self):
 1686         self.rospack_mock.list_names.return_value = {"bar"}
 1687 
 1688         self.assertThat(
 1689             catkin._find_system_dependencies(
 1690                 None, self.rosdep_mock, self.rospack_mock, self.catkin_mock
 1691             ),
 1692             Equals(dict()),
 1693         )
 1694 
 1695         self.rosdep_mock.get_dependencies.assert_called_once_with()
 1696         self.rospack_mock.list_names.assert_called_once_with()
 1697         self.rosdep_mock.resolve_dependency.assert_not_called()
 1698         self.catkin_mock.find.assert_not_called()
 1699 
 1700     def test_find_system_dependencies_local_only(self):
 1701         self.assertThat(
 1702             catkin._find_system_dependencies(
 1703                 {"foo", "bar"}, self.rosdep_mock, self.rospack_mock, self.catkin_mock
 1704             ),
 1705             HasLength(0),
 1706         )
 1707 
 1708         self.rosdep_mock.get_dependencies.assert_has_calls(
 1709             [mock.call("foo"), mock.call("bar")], any_order=True
 1710         )
 1711         self.rosdep_mock.resolve_dependency.assert_not_called()
 1712         self.catkin_mock.find.assert_not_called()
 1713 
 1714     def test_find_system_dependencies_satisfied_in_stage(self):
 1715         self.catkin_mock.find.side_effect = None
 1716         self.catkin_mock.find.return_value = "baz"
 1717 
 1718         self.assertThat(
 1719             catkin._find_system_dependencies(
 1720                 {"foo"}, self.rosdep_mock, self.rospack_mock, self.catkin_mock
 1721             ),
 1722             HasLength(0),
 1723         )
 1724 
 1725         self.rosdep_mock.get_dependencies.assert_called_once_with("foo")
 1726         self.catkin_mock.find.assert_called_once_with("bar")
 1727         self.rosdep_mock.resolve_dependency.assert_not_called()
 1728 
 1729     def test_find_system_dependencies_mixed(self):
 1730         self.rosdep_mock.get_dependencies.return_value = {"bar", "baz", "qux"}
 1731         self.rosdep_mock.resolve_dependency.return_value = {"apt": {"quux"}}
 1732 
 1733         def _fake_find(package_name):
 1734             if package_name == "qux":
 1735                 return package_name
 1736             raise catkin.CatkinPackageNotFoundError(package_name)
 1737 
 1738         self.catkin_mock.find.side_effect = _fake_find
 1739         self.assertThat(
 1740             catkin._find_system_dependencies(
 1741                 {"foo", "bar"}, self.rosdep_mock, self.rospack_mock, self.catkin_mock
 1742             ),
 1743             Equals({"apt": {"quux"}}),
 1744         )
 1745 
 1746         self.rosdep_mock.get_dependencies.assert_has_calls(
 1747             [mock.call("foo"), mock.call("bar")], any_order=True
 1748         )
 1749         self.rosdep_mock.resolve_dependency.assert_called_once_with("baz")
 1750         self.catkin_mock.find.assert_has_calls(
 1751             [mock.call("baz"), mock.call("qux")], any_order=True
 1752         )
 1753 
 1754     def test_find_system_dependencies_missing_local_dependency(self):
 1755         # Setup a dependency on a non-existing package, and it doesn't resolve
 1756         # to a system dependency.'
 1757         exception = _ros.rosdep.RosdepDependencyNotResolvedError("foo")
 1758         self.rosdep_mock.resolve_dependency.side_effect = exception
 1759 
 1760         raised = self.assertRaises(
 1761             catkin.CatkinInvalidSystemDependencyError,
 1762             catkin._find_system_dependencies,
 1763             {"foo"},
 1764             self.rosdep_mock,
 1765             self.rospack_mock,
 1766             self.catkin_mock,
 1767         )
 1768 
 1769         self.assertThat(
 1770             str(raised),
 1771             Equals(
 1772                 "Package 'bar' isn't a valid system dependency. Did "
 1773                 "you forget to add it to catkin-packages? If not, "
 1774                 "add the Ubuntu package containing it to "
 1775                 "stage-packages until you can get it into the rosdep "
 1776                 "database."
 1777             ),
 1778         )
 1779 
 1780     def test_find_system_dependencies_raises_if_unsupported_type(self):
 1781         self.rosdep_mock.resolve_dependency.return_value = {"unsupported-type": {"baz"}}
 1782 
 1783         raised = self.assertRaises(
 1784             catkin.CatkinUnsupportedDependencyTypeError,
 1785             catkin._find_system_dependencies,
 1786             {"foo"},
 1787             self.rosdep_mock,
 1788             self.rospack_mock,
 1789             self.catkin_mock,
 1790         )
 1791 
 1792         self.assertThat(
 1793             str(raised),
 1794             Equals(
 1795                 "Package 'bar' resolved to an unsupported type of dependency: "
 1796                 "'unsupported-type'."
 1797             ),
 1798         )
 1799 
 1800 
 1801 class HandleRosinstallFilesTestCase(unit.TestCase):
 1802     def setUp(self):
 1803         super().setUp()
 1804 
 1805         self.wstool_mock = mock.MagicMock()
 1806 
 1807     def test_single_rosinstall_file(self):
 1808         rosinstall_file = os.path.join("source_path", "rosinstall_file")
 1809         catkin._handle_rosinstall_files(self.wstool_mock, [rosinstall_file])
 1810         self.wstool_mock.merge.assert_called_once_with(
 1811             os.path.join("source_path", "rosinstall_file")
 1812         )
 1813 
 1814     def test_multiple_rosinstall_files(self):
 1815         rosinstall_files = [
 1816             os.path.join("source_path", "file1"),
 1817             os.path.join("source_path", "file2"),
 1818         ]
 1819 
 1820         catkin._handle_rosinstall_files(self.wstool_mock, rosinstall_files)
 1821 
 1822         # The order matters here. It should be the same as how they were passed
 1823         self.wstool_mock.merge.assert_has_calls(
 1824             [
 1825                 mock.call(os.path.join("source_path", "file1")),
 1826                 mock.call(os.path.join("source_path", "file2")),
 1827             ]
 1828         )
 1829 
 1830 
 1831 class RecursivelyHandleRosinstallFilesTestCase(unit.TestCase):
 1832     def setUp(self):
 1833         super().setUp()
 1834 
 1835         self.wstool_mock = mock.MagicMock()
 1836 
 1837     def test_recursive_rosinstall(self):
 1838         counter = 0
 1839 
 1840         # A fake update that plops a new rosinstall file down every time
 1841         # it's called (up to two times).
 1842         def _fake_wstool_update():
 1843             nonlocal counter
 1844             if counter < 2:
 1845                 counter += 1
 1846                 open(
 1847                     os.path.join("source_path", "{}.rosinstall".format(counter)), "w"
 1848                 ).close()
 1849 
 1850         self.wstool_mock.update.side_effect = _fake_wstool_update
 1851 
 1852         os.mkdir("source_path")
 1853         open(os.path.join("source_path", "0.rosinstall"), "w").close()
 1854 
 1855         catkin._recursively_handle_rosinstall_files(self.wstool_mock, "source_path")
 1856 
 1857         self.wstool_mock.merge.assert_has_calls(
 1858             [
 1859                 mock.call(os.path.join("source_path", "0.rosinstall")),
 1860                 mock.call(os.path.join("source_path", "1.rosinstall")),
 1861                 mock.call(os.path.join("source_path", "2.rosinstall")),
 1862             ]
 1863         )
 1864 
 1865 
 1866 class CatkinFindTestCase(unit.TestCase):
 1867     def setUp(self):
 1868         super().setUp()
 1869 
 1870         self.project = snapcraft.project.Project()
 1871         self.catkin = catkin._Catkin(
 1872             "kinetic",
 1873             "workspace_path",
 1874             "catkin_path",
 1875             "sources",
 1876             ["keyring"],
 1877             self.project,
 1878         )
 1879 
 1880         patcher = mock.patch("snapcraft.repo.Ubuntu")
 1881         self.ubuntu_mock = patcher.start()
 1882         self.addCleanup(patcher.stop)
 1883 
 1884         patcher = mock.patch("subprocess.check_output")
 1885         self.check_output_mock = patcher.start()
 1886         self.addCleanup(patcher.stop)
 1887 
 1888     def test_setup(self):
 1889         # Return something other than a Mock to ease later assertions
 1890         self.check_output_mock.return_value = b""
 1891 
 1892         self.catkin.setup()
 1893 
 1894         # Verify that only rospack was installed (no other .debs)
 1895         self.assertThat(self.ubuntu_mock.call_count, Equals(1))
 1896         self.assertThat(self.ubuntu_mock.return_value.get.call_count, Equals(1))
 1897         self.assertThat(self.ubuntu_mock.return_value.unpack.call_count, Equals(1))
 1898         self.ubuntu_mock.assert_has_calls(
 1899             [
 1900                 mock.call(
 1901                     self.catkin._catkin_path,
 1902                     sources="sources",
 1903                     keyrings=["keyring"],
 1904                     project_options=self.project,
 1905                 ),
 1906                 mock.call().get(["ros-kinetic-catkin"]),
 1907                 mock.call().unpack(self.catkin._catkin_install_path),
 1908             ]
 1909         )
 1910 
 1911     def test_setup_can_run_multiple_times(self):
 1912         self.catkin.setup()
 1913 
 1914         # Make sure running setup() again doesn't have problems with the old
 1915         # environment. An exception will be raised if setup() can't be called
 1916         # twice.
 1917         self.catkin.setup()
 1918 
 1919     def test_find(self):
 1920         self.check_output_mock.return_value = b"bar"
 1921 
 1922         self.assertThat(self.catkin.find("foo"), Equals("bar"))
 1923 
 1924         self.assertTrue(self.check_output_mock.called)
 1925         positional_args = self.check_output_mock.call_args[0][0]
 1926         self.assertThat(
 1927             " ".join(positional_args), Contains("catkin_find --first-only foo")
 1928         )
 1929 
 1930     def test_find_only_in_catkin_workspace(self):
 1931         self.check_output_mock.return_value = os.path.join(
 1932             self.catkin._catkin_install_path, "bar"
 1933         ).encode(sys.getfilesystemencoding())
 1934 
 1935         with testtools.ExpectedException(
 1936             catkin.CatkinPackageNotFoundError, "Unable to find Catkin package 'foo'"
 1937         ):
 1938             self.catkin.find("foo")
 1939 
 1940     def test_find_non_existing_package(self):
 1941         self.check_output_mock.side_effect = subprocess.CalledProcessError(1, "foo")
 1942 
 1943         with testtools.ExpectedException(
 1944             catkin.CatkinPackageNotFoundError, "Unable to find Catkin package 'foo'"
 1945         ):
 1946             self.catkin.find("foo")
 1947 
 1948         self.assertTrue(self.check_output_mock.called)
 1949         positional_args = self.check_output_mock.call_args[0][0]
 1950         self.assertThat(
 1951             " ".join(positional_args), Contains("catkin_find --first-only foo")
 1952         )