"Fossies" - the Fresh Open Source Software Archive

Member "salt-3002.2/tests/unit/utils/test_thin.py" (18 Nov 2020, 48665 Bytes) of package /linux/misc/salt-3002.2.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_thin.py": 3002.1_vs_3002.2.

    1 """
    2     :codeauthor: :email:`Bo Maryniuk <bo@suse.de>`
    3 """
    4 
    5 import copy
    6 import os
    7 import pathlib
    8 import shutil
    9 import sys
   10 import tarfile
   11 import tempfile
   12 
   13 import jinja2
   14 import salt.exceptions
   15 import salt.ext.six
   16 import salt.utils.hashutils
   17 import salt.utils.json
   18 import salt.utils.platform
   19 import salt.utils.stringutils
   20 from salt.ext.six.moves import range
   21 from salt.utils import thin
   22 from salt.utils.stringutils import to_bytes as bts
   23 from tests.support.helpers import TstSuiteLoggingHandler, VirtualEnv
   24 from tests.support.mock import MagicMock, patch
   25 from tests.support.runtests import RUNTIME_VARS
   26 from tests.support.unit import TestCase, skipIf
   27 
   28 try:
   29     import pytest
   30 except ImportError:
   31     pytest = None
   32 
   33 
   34 @skipIf(pytest is None, "PyTest is missing")
   35 class SSHThinTestCase(TestCase):
   36     """
   37     TestCase for SaltSSH-related parts.
   38     """
   39 
   40     def setUp(self):
   41         self.jinja_fp = os.path.dirname(jinja2.__file__)
   42 
   43         self.ext_conf = {
   44             "test": {
   45                 "py-version": [2, 7],
   46                 "path": RUNTIME_VARS.SALT_CODE_DIR,
   47                 "dependencies": {"jinja2": self.jinja_fp},
   48             }
   49         }
   50         self.tops = copy.deepcopy(self.ext_conf)
   51         self.tops["test"]["dependencies"] = [self.jinja_fp]
   52         self.tar = self._tarfile(None).open()
   53         self.digest = salt.utils.hashutils.DigestCollector()
   54         self.exp_files = [
   55             os.path.join("salt", "payload.py"),
   56             os.path.join("jinja2", "__init__.py"),
   57         ]
   58         lib_root = os.path.join(RUNTIME_VARS.TMP, "fake-libs")
   59         self.fake_libs = {
   60             "distro": os.path.join(lib_root, "distro"),
   61             "jinja2": os.path.join(lib_root, "jinja2"),
   62             "yaml": os.path.join(lib_root, "yaml"),
   63             "tornado": os.path.join(lib_root, "tornado"),
   64             "msgpack": os.path.join(lib_root, "msgpack"),
   65         }
   66 
   67         code_dir = pathlib.Path(RUNTIME_VARS.CODE_DIR).resolve()
   68         self.exp_ret = {
   69             "distro": str(code_dir / "distro.py"),
   70             "jinja2": str(code_dir / "jinja2"),
   71             "yaml": str(code_dir / "yaml"),
   72             "tornado": str(code_dir / "tornado"),
   73             "msgpack": str(code_dir / "msgpack"),
   74             "certifi": str(code_dir / "certifi"),
   75             "singledispatch": str(code_dir / "singledispatch.py"),
   76         }
   77         self.exc_libs = ["jinja2", "yaml"]
   78 
   79     def tearDown(self):
   80         for lib, fp in self.fake_libs.items():
   81             if os.path.exists(fp):
   82                 shutil.rmtree(fp)
   83         self.exc_libs = None
   84         self.jinja_fp = None
   85         self.ext_conf = None
   86         self.tops = None
   87         self.tar = None
   88         self.digest = None
   89         self.exp_files = None
   90         self.fake_libs = None
   91         self.exp_ret = None
   92 
   93     def _popen(self, return_value=None, side_effect=None, returncode=0):
   94         """
   95         Fake subprocess.Popen
   96 
   97         :return:
   98         """
   99 
  100         proc = MagicMock()
  101         proc.communicate = MagicMock(return_value=return_value, side_effect=side_effect)
  102         proc.returncode = returncode
  103         popen = MagicMock(return_value=proc)
  104 
  105         return popen
  106 
  107     def _version_info(self, major=None, minor=None):
  108         """
  109         Fake version info.
  110 
  111         :param major:
  112         :param minor:
  113         :return:
  114         """
  115 
  116         class VersionInfo(tuple):
  117             pass
  118 
  119         vi = VersionInfo([major, minor])
  120         vi.major = major or sys.version_info.major
  121         vi.minor = minor or sys.version_info.minor
  122 
  123         return vi
  124 
  125     def _tarfile(self, getinfo=False):
  126         """
  127         Fake tarfile handler.
  128 
  129         :return:
  130         """
  131         spec = ["add", "close"]
  132         if getinfo:
  133             spec.append("getinfo")
  134 
  135         tf = MagicMock()
  136         tf.open = MagicMock(return_value=MagicMock(spec=spec))
  137 
  138         return tf
  139 
  140     @patch("salt.utils.thin.log", MagicMock())
  141     @patch("salt.utils.thin.os.path.isfile", MagicMock(return_value=False))
  142     def test_get_ext_tops_cfg_missing_dependencies(self):
  143         """
  144         Test thin.get_ext_tops contains all required dependencies.
  145 
  146         :return:
  147         """
  148         cfg = {"namespace": {"py-version": [0, 0], "path": "/foo", "dependencies": []}}
  149 
  150         with pytest.raises(salt.exceptions.SaltSystemExit) as err:
  151             thin.get_ext_tops(cfg)
  152         self.assertIn("Missing dependencies", str(err.value))
  153         self.assertTrue(thin.log.error.called)
  154         self.assertIn("Missing dependencies", thin.log.error.call_args[0][0])
  155         self.assertIn("jinja2, yaml, tornado, msgpack", thin.log.error.call_args[0][0])
  156 
  157     @patch("salt.exceptions.SaltSystemExit", Exception)
  158     @patch("salt.utils.thin.log", MagicMock())
  159     @patch("salt.utils.thin.os.path.isfile", MagicMock(return_value=False))
  160     def test_get_ext_tops_cfg_missing_interpreter(self):
  161         """
  162         Test thin.get_ext_tops contains interpreter configuration.
  163 
  164         :return:
  165         """
  166         cfg = {"namespace": {"path": "/foo", "dependencies": []}}
  167         with pytest.raises(salt.exceptions.SaltSystemExit) as err:
  168             thin.get_ext_tops(cfg)
  169         self.assertIn("missing specific locked Python version", str(err.value))
  170 
  171     @patch("salt.exceptions.SaltSystemExit", Exception)
  172     @patch("salt.utils.thin.log", MagicMock())
  173     @patch("salt.utils.thin.os.path.isfile", MagicMock(return_value=False))
  174     def test_get_ext_tops_cfg_wrong_interpreter(self):
  175         """
  176         Test thin.get_ext_tops contains correct interpreter configuration.
  177 
  178         :return:
  179         """
  180         cfg = {"namespace": {"path": "/foo", "py-version": 2, "dependencies": []}}
  181 
  182         with pytest.raises(salt.exceptions.SaltSystemExit) as err:
  183             thin.get_ext_tops(cfg)
  184         self.assertIn(
  185             "specific locked Python version should be a list of " "major/minor version",
  186             str(err.value),
  187         )
  188 
  189     @patch("salt.exceptions.SaltSystemExit", Exception)
  190     @patch("salt.utils.thin.log", MagicMock())
  191     @patch("salt.utils.thin.os.path.isfile", MagicMock(return_value=False))
  192     def test_get_ext_tops_cfg_interpreter(self):
  193         """
  194         Test thin.get_ext_tops interpreter configuration.
  195 
  196         :return:
  197         """
  198         cfg = {
  199             "namespace": {
  200                 "path": "/foo",
  201                 "py-version": [2, 6],
  202                 "dependencies": {
  203                     "jinja2": "",
  204                     "yaml": "",
  205                     "tornado": "",
  206                     "msgpack": "",
  207                 },
  208             }
  209         }
  210 
  211         with pytest.raises(salt.exceptions.SaltSystemExit):
  212             thin.get_ext_tops(cfg)
  213         assert len(thin.log.warning.mock_calls) == 4
  214         assert sorted([x[1][1] for x in thin.log.warning.mock_calls]) == [
  215             "jinja2",
  216             "msgpack",
  217             "tornado",
  218             "yaml",
  219         ]
  220         assert (
  221             "Module test has missing configuration"
  222             == thin.log.warning.mock_calls[0][1][0] % "test"
  223         )
  224 
  225     @patch("salt.utils.thin.log", MagicMock())
  226     @patch("salt.utils.thin.os.path.isfile", MagicMock(return_value=False))
  227     def test_get_ext_tops_dependency_config_check(self):
  228         """
  229         Test thin.get_ext_tops dependencies are importable
  230 
  231         :return:
  232         """
  233         cfg = {
  234             "namespace": {
  235                 "path": "/foo",
  236                 "py-version": [2, 6],
  237                 "dependencies": {
  238                     "jinja2": "/jinja/foo.py",
  239                     "yaml": "/yaml/",
  240                     "tornado": "/tornado/wrong.rb",
  241                     "msgpack": "msgpack.sh",
  242                 },
  243             }
  244         }
  245 
  246         with pytest.raises(salt.exceptions.SaltSystemExit) as err:
  247             thin.get_ext_tops(cfg)
  248 
  249         self.assertIn(
  250             "Missing dependencies for the alternative version in the "
  251             "external configuration",
  252             str(err.value),
  253         )
  254 
  255         messages = {}
  256         for cl in thin.log.warning.mock_calls:
  257             messages[cl[1][1]] = cl[1][0] % (cl[1][1], cl[1][2])
  258         for mod in ["tornado", "yaml", "msgpack"]:
  259             self.assertIn("not a Python importable module", messages[mod])
  260         self.assertIn(
  261             "configured with not a file or does not exist", messages["jinja2"]
  262         )
  263 
  264     @patch("salt.exceptions.SaltSystemExit", Exception)
  265     @patch("salt.utils.thin.log", MagicMock())
  266     @patch("salt.utils.thin.os.path.isfile", MagicMock(return_value=True))
  267     def test_get_ext_tops_config_pass(self):
  268         """
  269         Test thin.get_ext_tops configuration
  270 
  271         :return:
  272         """
  273         cfg = {
  274             "namespace": {
  275                 "path": "/foo",
  276                 "py-version": [2, 6],
  277                 "dependencies": {
  278                     "jinja2": "/jinja/foo.py",
  279                     "yaml": "/yaml/",
  280                     "tornado": "/tornado/tornado.py",
  281                     "msgpack": "msgpack.py",
  282                     "distro": "distro.py",
  283                 },
  284             }
  285         }
  286         out = thin.get_ext_tops(cfg)
  287         assert out["namespace"]["py-version"] == cfg["namespace"]["py-version"]
  288         assert out["namespace"]["path"] == cfg["namespace"]["path"]
  289         assert sorted(out["namespace"]["dependencies"]) == sorted(
  290             [
  291                 "/tornado/tornado.py",
  292                 "/jinja/foo.py",
  293                 "/yaml/",
  294                 "msgpack.py",
  295                 "distro.py",
  296             ]
  297         )
  298 
  299     @patch("salt.utils.thin.sys.argv", [None, '{"foo": "bar"}'])
  300     @patch("salt.utils.thin.get_tops", lambda **kw: kw)
  301     def test_gte(self):
  302         """
  303         Test thin.gte external call for processing the info about tops per interpreter.
  304 
  305         :return:
  306         """
  307         assert salt.utils.json.loads(thin.gte()).get("foo") == "bar"
  308 
  309     def test_add_dep_path(self):
  310         """
  311         Test thin._add_dependency function to setup dependency paths
  312         :return:
  313         """
  314         container = []
  315         for pth in ["/foo/bar.py", "/something/else/__init__.py"]:
  316             thin._add_dependency(container, type("obj", (), {"__file__": pth})())
  317         assert "__init__" not in container[1]
  318         assert container == ["/foo/bar.py", "/something/else"]
  319 
  320     def test_thin_path(self):
  321         """
  322         Test thin.thin_path returns the expected path.
  323 
  324         :return:
  325         """
  326         path = os.sep + os.path.join("path", "to")
  327         expected = os.path.join(path, "thin", "thin.tgz")
  328         self.assertEqual(thin.thin_path(path), expected)
  329 
  330     def test_get_salt_call_script(self):
  331         """
  332         Test get salt-call script rendered.
  333 
  334         :return:
  335         """
  336         out = thin._get_salt_call("foo", "bar", py26=[2, 6], py27=[2, 7], py34=[3, 4])
  337         for line in salt.utils.stringutils.to_str(out).split(os.linesep):
  338             if line.startswith("namespaces = {"):
  339                 data = salt.utils.json.loads(line.replace("namespaces = ", "").strip())
  340                 assert data.get("py26") == [2, 6]
  341                 assert data.get("py27") == [2, 7]
  342                 assert data.get("py34") == [3, 4]
  343             if line.startswith("syspaths = "):
  344                 data = salt.utils.json.loads(line.replace("syspaths = ", ""))
  345                 assert data == ["foo", "bar"]
  346 
  347     def test_get_ext_namespaces_empty(self):
  348         """
  349         Test thin._get_ext_namespaces function returns an empty dictionary on nothing
  350         :return:
  351         """
  352         for obj in [None, {}, []]:
  353             assert thin._get_ext_namespaces(obj) == {}
  354 
  355     def test_get_ext_namespaces(self):
  356         """
  357         Test thin._get_ext_namespaces function returns namespaces properly out of the config.
  358         :return:
  359         """
  360         cfg = {"ns": {"py-version": [2, 7]}}
  361         assert thin._get_ext_namespaces(cfg).get("ns") == (2, 7,)
  362         assert isinstance(thin._get_ext_namespaces(cfg).get("ns"), tuple)
  363 
  364     def test_get_ext_namespaces_failure(self):
  365         """
  366         Test thin._get_ext_namespaces function raises an exception
  367         if python major/minor version is not configured.
  368         :return:
  369         """
  370         with pytest.raises(salt.exceptions.SaltSystemExit):
  371             thin._get_ext_namespaces({"ns": {}})
  372 
  373     @patch(
  374         "salt.utils.thin.distro",
  375         type("distro", (), {"__file__": "/site-packages/distro"}),
  376     )
  377     @patch(
  378         "salt.utils.thin.salt", type("salt", (), {"__file__": "/site-packages/salt"}),
  379     )
  380     @patch(
  381         "salt.utils.thin.jinja2",
  382         type("jinja2", (), {"__file__": "/site-packages/jinja2"}),
  383     )
  384     @patch(
  385         "salt.utils.thin.yaml", type("yaml", (), {"__file__": "/site-packages/yaml"}),
  386     )
  387     @patch(
  388         "salt.utils.thin.tornado",
  389         type("tornado", (), {"__file__": "/site-packages/tornado"}),
  390     )
  391     @patch(
  392         "salt.utils.thin.msgpack",
  393         type("msgpack", (), {"__file__": "/site-packages/msgpack"}),
  394     )
  395     @patch(
  396         "salt.utils.thin.certifi",
  397         type("certifi", (), {"__file__": "/site-packages/certifi"}),
  398     )
  399     @patch(
  400         "salt.utils.thin.singledispatch",
  401         type("singledispatch", (), {"__file__": "/site-packages/sdp"}),
  402     )
  403     @patch(
  404         "salt.utils.thin.singledispatch_helpers",
  405         type("singledispatch_helpers", (), {"__file__": "/site-packages/sdp_hlp"}),
  406     )
  407     @patch(
  408         "salt.utils.thin.ssl_match_hostname",
  409         type("ssl_match_hostname", (), {"__file__": "/site-packages/ssl_mh"}),
  410     )
  411     @patch(
  412         "salt.utils.thin.markupsafe",
  413         type("markupsafe", (), {"__file__": "/site-packages/markupsafe"}),
  414     )
  415     @patch(
  416         "salt.utils.thin.backports_abc",
  417         type("backports_abc", (), {"__file__": "/site-packages/backports_abc"}),
  418     )
  419     @patch(
  420         "salt.utils.thin.concurrent",
  421         type("concurrent", (), {"__file__": "/site-packages/concurrent"}),
  422     )
  423     @patch("salt.utils.thin.log", MagicMock())
  424     def test_get_tops(self):
  425         """
  426         Test thin.get_tops to get top directories, based on the interpreter.
  427         :return:
  428         """
  429         base_tops = [
  430             "/site-packages/distro",
  431             "/site-packages/salt",
  432             "/site-packages/jinja2",
  433             "/site-packages/yaml",
  434             "/site-packages/tornado",
  435             "/site-packages/msgpack",
  436             "/site-packages/certifi",
  437             "/site-packages/sdp",
  438             "/site-packages/sdp_hlp",
  439             "/site-packages/ssl_mh",
  440             "/site-packages/markupsafe",
  441             "/site-packages/backports_abc",
  442             "/site-packages/concurrent",
  443         ]
  444 
  445         tops = thin.get_tops()
  446         assert len(tops) == len(base_tops)
  447         assert sorted(tops) == sorted(base_tops)
  448 
  449     @patch(
  450         "salt.utils.thin.distro",
  451         type("distro", (), {"__file__": "/site-packages/distro"}),
  452     )
  453     @patch(
  454         "salt.utils.thin.salt", type("salt", (), {"__file__": "/site-packages/salt"}),
  455     )
  456     @patch(
  457         "salt.utils.thin.jinja2",
  458         type("jinja2", (), {"__file__": "/site-packages/jinja2"}),
  459     )
  460     @patch(
  461         "salt.utils.thin.yaml", type("yaml", (), {"__file__": "/site-packages/yaml"}),
  462     )
  463     @patch(
  464         "salt.utils.thin.tornado",
  465         type("tornado", (), {"__file__": "/site-packages/tornado"}),
  466     )
  467     @patch(
  468         "salt.utils.thin.msgpack",
  469         type("msgpack", (), {"__file__": "/site-packages/msgpack"}),
  470     )
  471     @patch(
  472         "salt.utils.thin.certifi",
  473         type("certifi", (), {"__file__": "/site-packages/certifi"}),
  474     )
  475     @patch(
  476         "salt.utils.thin.singledispatch",
  477         type("singledispatch", (), {"__file__": "/site-packages/sdp"}),
  478     )
  479     @patch(
  480         "salt.utils.thin.singledispatch_helpers",
  481         type("singledispatch_helpers", (), {"__file__": "/site-packages/sdp_hlp"}),
  482     )
  483     @patch(
  484         "salt.utils.thin.ssl_match_hostname",
  485         type("ssl_match_hostname", (), {"__file__": "/site-packages/ssl_mh"}),
  486     )
  487     @patch(
  488         "salt.utils.thin.markupsafe",
  489         type("markupsafe", (), {"__file__": "/site-packages/markupsafe"}),
  490     )
  491     @patch(
  492         "salt.utils.thin.backports_abc",
  493         type("backports_abc", (), {"__file__": "/site-packages/backports_abc"}),
  494     )
  495     @patch(
  496         "salt.utils.thin.concurrent",
  497         type("concurrent", (), {"__file__": "/site-packages/concurrent"}),
  498     )
  499     @patch("salt.utils.thin.log", MagicMock())
  500     def test_get_tops_extra_mods(self):
  501         """
  502         Test thin.get_tops to get extra-modules alongside the top directories, based on the interpreter.
  503         :return:
  504         """
  505         base_tops = [
  506             "/site-packages/distro",
  507             "/site-packages/salt",
  508             "/site-packages/jinja2",
  509             "/site-packages/yaml",
  510             "/site-packages/tornado",
  511             "/site-packages/msgpack",
  512             "/site-packages/certifi",
  513             "/site-packages/sdp",
  514             "/site-packages/sdp_hlp",
  515             "/site-packages/ssl_mh",
  516             "/site-packages/concurrent",
  517             "/site-packages/markupsafe",
  518             "/site-packages/backports_abc",
  519             os.sep + os.path.join("custom", "foo"),
  520             os.sep + os.path.join("custom", "bar.py"),
  521         ]
  522         builtins = sys.version_info.major == 3 and "builtins" or "__builtin__"
  523         foo = {"__file__": os.sep + os.path.join("custom", "foo", "__init__.py")}
  524         bar = {"__file__": os.sep + os.path.join("custom", "bar")}
  525         with patch(
  526             "{}.__import__".format(builtins),
  527             MagicMock(side_effect=[type("foo", (), foo), type("bar", (), bar)]),
  528         ):
  529             tops = thin.get_tops(extra_mods="foo,bar")
  530         self.assertEqual(len(tops), len(base_tops))
  531         self.assertListEqual(sorted(tops), sorted(base_tops))
  532 
  533     @patch(
  534         "salt.utils.thin.distro",
  535         type("distro", (), {"__file__": "/site-packages/distro"}),
  536     )
  537     @patch(
  538         "salt.utils.thin.salt", type("salt", (), {"__file__": "/site-packages/salt"}),
  539     )
  540     @patch(
  541         "salt.utils.thin.jinja2",
  542         type("jinja2", (), {"__file__": "/site-packages/jinja2"}),
  543     )
  544     @patch(
  545         "salt.utils.thin.yaml", type("yaml", (), {"__file__": "/site-packages/yaml"}),
  546     )
  547     @patch(
  548         "salt.utils.thin.tornado",
  549         type("tornado", (), {"__file__": "/site-packages/tornado"}),
  550     )
  551     @patch(
  552         "salt.utils.thin.msgpack",
  553         type("msgpack", (), {"__file__": "/site-packages/msgpack"}),
  554     )
  555     @patch(
  556         "salt.utils.thin.certifi",
  557         type("certifi", (), {"__file__": "/site-packages/certifi"}),
  558     )
  559     @patch(
  560         "salt.utils.thin.singledispatch",
  561         type("singledispatch", (), {"__file__": "/site-packages/sdp"}),
  562     )
  563     @patch(
  564         "salt.utils.thin.singledispatch_helpers",
  565         type("singledispatch_helpers", (), {"__file__": "/site-packages/sdp_hlp"}),
  566     )
  567     @patch(
  568         "salt.utils.thin.ssl_match_hostname",
  569         type("ssl_match_hostname", (), {"__file__": "/site-packages/ssl_mh"}),
  570     )
  571     @patch(
  572         "salt.utils.thin.markupsafe",
  573         type("markupsafe", (), {"__file__": "/site-packages/markupsafe"}),
  574     )
  575     @patch(
  576         "salt.utils.thin.backports_abc",
  577         type("backports_abc", (), {"__file__": "/site-packages/backports_abc"}),
  578     )
  579     @patch(
  580         "salt.utils.thin.concurrent",
  581         type("concurrent", (), {"__file__": "/site-packages/concurrent"}),
  582     )
  583     @patch("salt.utils.thin.log", MagicMock())
  584     def test_get_tops_so_mods(self):
  585         """
  586         Test thin.get_tops to get extra-modules alongside the top directories, based on the interpreter.
  587         :return:
  588         """
  589         base_tops = [
  590             "/site-packages/distro",
  591             "/site-packages/salt",
  592             "/site-packages/jinja2",
  593             "/site-packages/yaml",
  594             "/site-packages/tornado",
  595             "/site-packages/msgpack",
  596             "/site-packages/certifi",
  597             "/site-packages/sdp",
  598             "/site-packages/sdp_hlp",
  599             "/site-packages/ssl_mh",
  600             "/site-packages/concurrent",
  601             "/site-packages/markupsafe",
  602             "/site-packages/backports_abc",
  603             "/custom/foo.so",
  604             "/custom/bar.so",
  605         ]
  606         builtins = sys.version_info.major == 3 and "builtins" or "__builtin__"
  607         with patch(
  608             "{}.__import__".format(builtins),
  609             MagicMock(
  610                 side_effect=[
  611                     type("salt", (), {"__file__": "/custom/foo.so"}),
  612                     type("salt", (), {"__file__": "/custom/bar.so"}),
  613                 ]
  614             ),
  615         ):
  616             tops = thin.get_tops(so_mods="foo,bar")
  617         assert len(tops) == len(base_tops)
  618         assert sorted(tops) == sorted(base_tops)
  619 
  620     @patch("salt.utils.thin.gen_thin", MagicMock(return_value="/path/to/thin/thin.tgz"))
  621     @patch("salt.utils.hashutils.get_hash", MagicMock(return_value=12345))
  622     def test_thin_sum(self):
  623         """
  624         Test thin.thin_sum function.
  625 
  626         :return:
  627         """
  628         assert thin.thin_sum("/cachedir", form="sha256")[1] == 12345
  629         thin.salt.utils.hashutils.get_hash.assert_called()
  630         assert thin.salt.utils.hashutils.get_hash.call_count == 1
  631 
  632         path, form = thin.salt.utils.hashutils.get_hash.call_args[0]
  633         assert path == "/path/to/thin/thin.tgz"
  634         assert form == "sha256"
  635 
  636     @patch("salt.utils.thin.gen_min", MagicMock(return_value="/path/to/thin/min.tgz"))
  637     @patch("salt.utils.hashutils.get_hash", MagicMock(return_value=12345))
  638     def test_min_sum(self):
  639         """
  640         Test thin.thin_sum function.
  641 
  642         :return:
  643         """
  644         assert thin.min_sum("/cachedir", form="sha256") == 12345
  645         thin.salt.utils.hashutils.get_hash.assert_called()
  646         assert thin.salt.utils.hashutils.get_hash.call_count == 1
  647 
  648         path, form = thin.salt.utils.hashutils.get_hash.call_args[0]
  649         assert path == "/path/to/thin/min.tgz"
  650         assert form == "sha256"
  651 
  652     @patch("salt.utils.thin.sys.version_info", (2, 5))
  653     @patch("salt.exceptions.SaltSystemExit", Exception)
  654     def test_gen_thin_fails_ancient_python_version(self):
  655         """
  656         Test thin.gen_thin function raises an exception
  657         if Python major/minor version is lower than 2.6
  658 
  659         :return:
  660         """
  661         with pytest.raises(salt.exceptions.SaltSystemExit) as err:
  662             thin.sys.exc_clear = lambda: None
  663             thin.gen_thin("")
  664         self.assertIn(
  665             "The minimum required python version to run salt-ssh is " '"3"',
  666             str(err.value),
  667         )
  668 
  669     @skipIf(
  670         salt.utils.platform.is_windows() and thin._six.PY2, "Dies on Python2 on Windows"
  671     )
  672     @patch("salt.exceptions.SaltSystemExit", Exception)
  673     @patch("salt.utils.thin.log", MagicMock())
  674     @patch("salt.utils.thin.os.makedirs", MagicMock())
  675     @patch("salt.utils.files.fopen", MagicMock())
  676     @patch("salt.utils.thin._get_salt_call", MagicMock())
  677     @patch("salt.utils.thin._get_ext_namespaces", MagicMock())
  678     @patch("salt.utils.thin.get_tops", MagicMock(return_value=["/foo3", "/bar3"]))
  679     @patch("salt.utils.thin.get_ext_tops", MagicMock(return_value={}))
  680     @patch("salt.utils.thin.os.path.isfile", MagicMock())
  681     @patch("salt.utils.thin.os.path.isdir", MagicMock(return_value=True))
  682     @patch("salt.utils.thin.log", MagicMock())
  683     @patch("salt.utils.thin.os.remove", MagicMock())
  684     @patch("salt.utils.thin.os.path.exists", MagicMock())
  685     @patch("salt.utils.path.os_walk", MagicMock(return_value=[]))
  686     @patch(
  687         "salt.utils.thin.subprocess.Popen",
  688         _popen(
  689             None,
  690             side_effect=[(bts("2.7"), bts("")), (bts('["/foo27", "/bar27"]'), bts(""))],
  691         ),
  692     )
  693     @patch("salt.utils.thin.tarfile", MagicMock())
  694     @patch("salt.utils.thin.zipfile", MagicMock())
  695     @patch("salt.utils.thin.os.getcwd", MagicMock())
  696     @patch("salt.utils.thin.os.access", MagicMock(return_value=True))
  697     @patch("salt.utils.thin.os.chdir", MagicMock())
  698     @patch("salt.utils.thin.os.close", MagicMock())
  699     @patch("salt.utils.thin.tempfile.mkdtemp", MagicMock())
  700     @patch(
  701         "salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
  702     )
  703     @patch("salt.utils.thin.shutil", MagicMock())
  704     @patch("salt.utils.thin.sys.version_info", _version_info(None, 3, 6))
  705     @patch("salt.utils.path.which", MagicMock(return_value="/usr/bin/python"))
  706     def test_gen_thin_compression_fallback_py3(self):
  707         """
  708         Test thin.gen_thin function if fallbacks to the gzip compression, once setup wrong.
  709         NOTE: Py2 version of this test is not required, as code shares the same spot across the versions.
  710 
  711         :return:
  712         """
  713         thin.gen_thin("", compress="arj")
  714         thin.log.warning.assert_called()
  715         pt, msg = thin.log.warning.mock_calls[0][1]
  716         assert (
  717             pt % msg
  718             == 'Unknown compression type: "arj". Falling back to "gzip" compression.'
  719         )
  720         thin.zipfile.ZipFile.assert_not_called()
  721         thin.tarfile.open.assert_called()
  722 
  723     @patch("salt.exceptions.SaltSystemExit", Exception)
  724     @patch("salt.utils.thin.log", MagicMock())
  725     @patch("salt.utils.thin.os.makedirs", MagicMock())
  726     @patch("salt.utils.files.fopen", MagicMock())
  727     @patch("salt.utils.thin._get_salt_call", MagicMock())
  728     @patch("salt.utils.thin._get_ext_namespaces", MagicMock())
  729     @patch("salt.utils.thin.get_tops", MagicMock(return_value=["/foo3", "/bar3"]))
  730     @patch("salt.utils.thin.get_ext_tops", MagicMock(return_value={}))
  731     @patch("salt.utils.thin.os.path.isfile", MagicMock())
  732     @patch("salt.utils.thin.os.path.isdir", MagicMock(return_value=False))
  733     @patch("salt.utils.thin.log", MagicMock())
  734     @patch("salt.utils.thin.os.remove", MagicMock())
  735     @patch("salt.utils.thin.os.path.exists", MagicMock())
  736     @patch("salt.utils.path.os_walk", MagicMock(return_value=[]))
  737     @patch(
  738         "salt.utils.thin.subprocess.Popen",
  739         _popen(
  740             None,
  741             side_effect=[(bts("2.7"), bts("")), (bts('["/foo27", "/bar27"]'), bts(""))],
  742         ),
  743     )
  744     @patch("salt.utils.thin.tarfile", MagicMock())
  745     @patch("salt.utils.thin.zipfile", MagicMock())
  746     @patch("salt.utils.thin.os.getcwd", MagicMock())
  747     @patch("salt.utils.thin.os.access", MagicMock(return_value=True))
  748     @patch("salt.utils.thin.os.chdir", MagicMock())
  749     @patch("salt.utils.thin.os.close", MagicMock())
  750     @patch("salt.utils.thin.tempfile.mkdtemp", MagicMock(return_value=""))
  751     @patch(
  752         "salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
  753     )
  754     @patch("salt.utils.thin.shutil", MagicMock())
  755     @patch("salt.utils.thin.sys.version_info", _version_info(None, 3, 6))
  756     @patch("salt.utils.path.which", MagicMock(return_value="/usr/bin/python"))
  757     def test_gen_thin_control_files_written_py3(self):
  758         """
  759         Test thin.gen_thin function if control files are written (version, salt-call etc).
  760         :return:
  761         """
  762         thin.gen_thin("")
  763         arc_name, arc_mode = thin.tarfile.method_calls[0][1]
  764         self.assertEqual(arc_name, ".temporary")
  765         self.assertEqual(arc_mode, "w:gz")
  766         for idx, fname in enumerate(
  767             ["version", ".thin-gen-py-version", "salt-call", "supported-versions"]
  768         ):
  769             name = thin.tarfile.open().method_calls[idx + 2][1][0]
  770             self.assertEqual(name, fname)
  771         thin.tarfile.open().close.assert_called()
  772 
  773     @patch("salt.exceptions.SaltSystemExit", Exception)
  774     @patch("salt.utils.thin.log", MagicMock())
  775     @patch("salt.utils.thin.os.makedirs", MagicMock())
  776     @patch("salt.utils.files.fopen", MagicMock())
  777     @patch("salt.utils.thin._get_salt_call", MagicMock())
  778     @patch("salt.utils.thin._get_ext_namespaces", MagicMock())
  779     @patch("salt.utils.thin.get_tops", MagicMock(return_value=["/salt", "/bar3"]))
  780     @patch("salt.utils.thin.get_ext_tops", MagicMock(return_value={}))
  781     @patch("salt.utils.thin.os.path.isfile", MagicMock())
  782     @patch("salt.utils.thin.os.path.isdir", MagicMock(return_value=True))
  783     @patch("salt.utils.thin.log", MagicMock())
  784     @patch("salt.utils.thin.os.remove", MagicMock())
  785     @patch("salt.utils.thin.os.path.exists", MagicMock())
  786     @patch(
  787         "salt.utils.path.os_walk",
  788         MagicMock(
  789             return_value=(
  790                 ("root", [], ["r1", "r2", "r3"]),
  791                 ("root2", [], ["r4", "r5", "r6"]),
  792             )
  793         ),
  794     )
  795     @patch("salt.utils.thin.tarfile", _tarfile(None))
  796     @patch("salt.utils.thin.zipfile", MagicMock())
  797     @patch(
  798         "salt.utils.thin.os.getcwd",
  799         MagicMock(return_value=os.path.join(RUNTIME_VARS.TMP, "fake-cwd")),
  800     )
  801     @patch("salt.utils.thin.os.chdir", MagicMock())
  802     @patch("salt.utils.thin.os.close", MagicMock())
  803     @patch("salt.utils.thin.tempfile.mkdtemp", MagicMock(return_value=""))
  804     @patch(
  805         "salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
  806     )
  807     @patch("salt.utils.thin.shutil", MagicMock())
  808     @patch("salt.utils.thin.sys.version_info", _version_info(None, 3, 6))
  809     @patch("salt.utils.hashutils.DigestCollector", MagicMock())
  810     @patch("salt.utils.path.which", MagicMock(return_value="/usr/bin/python"))
  811     def test_gen_thin_main_content_files_written_py3(self):
  812         """
  813         Test thin.gen_thin function if main content files are written.
  814         NOTE: Py2 version of this test is not required, as code shares the same spot across the versions.
  815 
  816         :return:
  817         """
  818         thin.gen_thin("")
  819         files = []
  820         for py in ("py3", "pyall"):
  821             for i in range(1, 4):
  822                 files.append(os.path.join(py, "root", "r{}".format(i)))
  823             for i in range(4, 7):
  824                 files.append(os.path.join(py, "root2", "r{}".format(i)))
  825         for cl in thin.tarfile.open().method_calls[:-6]:
  826             arcname = cl[2].get("arcname")
  827             self.assertIn(arcname, files)
  828             files.pop(files.index(arcname))
  829         self.assertFalse(files)
  830 
  831     @patch("salt.exceptions.SaltSystemExit", Exception)
  832     @patch("salt.utils.thin.log", MagicMock())
  833     @patch("salt.utils.thin.os.makedirs", MagicMock())
  834     @patch("salt.utils.files.fopen", MagicMock())
  835     @patch("salt.utils.thin._get_salt_call", MagicMock())
  836     @patch("salt.utils.thin._get_ext_namespaces", MagicMock())
  837     @patch("salt.utils.thin.get_tops", MagicMock(return_value=[]))
  838     @patch(
  839         "salt.utils.thin.get_ext_tops",
  840         MagicMock(
  841             return_value={
  842                 "namespace": {
  843                     "py-version": [3, 0],
  844                     "path": "/opt/2015.8/salt",
  845                     "dependencies": ["/opt/certifi", "/opt/whatever"],
  846                 }
  847             }
  848         ),
  849     )
  850     @patch("salt.utils.thin.os.path.isfile", MagicMock())
  851     @patch("salt.utils.thin.os.path.isdir", MagicMock(return_value=True))
  852     @patch("salt.utils.thin.log", MagicMock())
  853     @patch("salt.utils.thin.os.remove", MagicMock())
  854     @patch("salt.utils.thin.os.path.exists", MagicMock())
  855     @patch(
  856         "salt.utils.path.os_walk",
  857         MagicMock(
  858             return_value=(
  859                 ("root", [], ["r1", "r2", "r3"]),
  860                 ("root2", [], ["r4", "r5", "r6"]),
  861             )
  862         ),
  863     )
  864     @patch("salt.utils.thin.tarfile", _tarfile(None))
  865     @patch("salt.utils.thin.zipfile", MagicMock())
  866     @patch(
  867         "salt.utils.thin.os.getcwd",
  868         MagicMock(return_value=os.path.join(RUNTIME_VARS.TMP, "fake-cwd")),
  869     )
  870     @patch("salt.utils.thin.os.chdir", MagicMock())
  871     @patch("salt.utils.thin.os.close", MagicMock())
  872     @patch("salt.utils.thin.tempfile.mkdtemp", MagicMock(return_value=""))
  873     @patch(
  874         "salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
  875     )
  876     @patch("salt.utils.thin.shutil", MagicMock())
  877     @patch("salt.utils.thin.sys.version_info", _version_info(None, 3, 6))
  878     @patch("salt.utils.hashutils.DigestCollector", MagicMock())
  879     @patch("salt.utils.path.which", MagicMock(return_value="/usr/bin/python"))
  880     def test_gen_thin_ext_alternative_content_files_written_py3(self):
  881         """
  882         Test thin.gen_thin function if external alternative content files are written.
  883         :return:
  884         """
  885         ext_conf = {
  886             "namespace": {
  887                 "py-version": [3, 0],
  888                 "path": "/opt/2015.8/salt",
  889                 "dependencies": {
  890                     "certifi": "/opt/certifi",
  891                     "whatever": "/opt/whatever",
  892                 },
  893             }
  894         }
  895 
  896         thin.gen_thin("", extended_cfg=ext_conf)
  897         files = []
  898         for py in ("pyall", "pyall", "py3"):
  899             for i in range(1, 4):
  900                 files.append(os.path.join("namespace", py, "root", "r{}".format(i)))
  901             for i in range(4, 7):
  902                 files.append(os.path.join("namespace", py, "root2", "r{}".format(i)))
  903 
  904         for idx, cl in enumerate(thin.tarfile.open().method_calls[:-6]):
  905             arcname = cl[2].get("arcname")
  906             self.assertIn(arcname, files)
  907             files.pop(files.index(arcname))
  908         self.assertFalse(files)
  909 
  910     def test_get_supported_py_config_typecheck(self):
  911         """
  912         Test collecting proper py-versions. Should return bytes type.
  913         :return:
  914         """
  915         tops = {}
  916         ext_cfg = {}
  917         out = thin._get_supported_py_config(tops=tops, extended_cfg=ext_cfg)
  918         assert type(salt.utils.stringutils.to_bytes("")) == type(out)
  919 
  920     def test_get_supported_py_config_base_tops(self):
  921         """
  922         Test collecting proper py-versions. Should return proper base tops.
  923         :return:
  924         """
  925         tops = {"3": ["/groundkeepers", "/stole"], "2": ["/the-root", "/password"]}
  926         ext_cfg = {}
  927         out = (
  928             salt.utils.stringutils.to_str(
  929                 thin._get_supported_py_config(tops=tops, extended_cfg=ext_cfg)
  930             )
  931             .strip()
  932             .split(os.linesep)
  933         )
  934         self.assertEqual(len(out), 2)
  935         for t_line in ["py3:3:0", "py2:2:7"]:
  936             self.assertIn(t_line, out)
  937 
  938     def test_get_supported_py_config_ext_tops(self):
  939         """
  940         Test collecting proper py-versions. Should return proper ext conf tops.
  941         :return:
  942         """
  943         tops = {}
  944         ext_cfg = {
  945             "solar-interference": {"py-version": [2, 6]},
  946             "second-system-effect": {"py-version": [2, 7]},
  947         }
  948         out = (
  949             salt.utils.stringutils.to_str(
  950                 thin._get_supported_py_config(tops=tops, extended_cfg=ext_cfg)
  951             )
  952             .strip()
  953             .split(os.linesep)
  954         )
  955         for t_line in ["second-system-effect:2:7", "solar-interference:2:6"]:
  956             self.assertIn(t_line, out)
  957 
  958     @patch("salt.exceptions.SaltSystemExit", Exception)
  959     @patch("salt.utils.thin.log", MagicMock())
  960     @patch("salt.utils.thin.os.makedirs", MagicMock())
  961     @patch("salt.utils.files.fopen", MagicMock())
  962     @patch("salt.utils.thin._get_salt_call", MagicMock())
  963     @patch("salt.utils.thin._get_ext_namespaces", MagicMock())
  964     @patch("salt.utils.thin.get_tops", MagicMock(return_value=["/foo3", "/bar3"]))
  965     @patch("salt.utils.thin.get_ext_tops", MagicMock(return_value={}))
  966     @patch("salt.utils.thin.os.path.isfile", MagicMock())
  967     @patch("salt.utils.thin.os.path.isdir", MagicMock(return_value=False))
  968     @patch("salt.utils.thin.log", MagicMock())
  969     @patch("salt.utils.thin.os.remove", MagicMock())
  970     @patch("salt.utils.thin.os.path.exists", MagicMock())
  971     @patch("salt.utils.path.os_walk", MagicMock(return_value=[]))
  972     @patch(
  973         "salt.utils.thin.subprocess.Popen",
  974         _popen(
  975             None,
  976             side_effect=[(bts("2.7"), bts("")), (bts('["/foo27", "/bar27"]'), bts(""))],
  977         ),
  978     )
  979     @patch("salt.utils.thin.tarfile", MagicMock())
  980     @patch("salt.utils.thin.zipfile", MagicMock())
  981     @patch("salt.utils.thin.os.getcwd", MagicMock())
  982     @patch("salt.utils.thin.os.access", MagicMock(return_value=False))
  983     @patch("salt.utils.thin.os.chdir", MagicMock())
  984     @patch("salt.utils.thin.os.close", MagicMock())
  985     @patch("salt.utils.thin.tempfile.mkdtemp", MagicMock(return_value=""))
  986     @patch(
  987         "salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
  988     )
  989     @patch("salt.utils.thin.shutil", MagicMock())
  990     @patch("salt.utils.thin.sys.version_info", _version_info(None, 3, 6))
  991     def test_gen_thin_control_files_written_access_denied_cwd(self):
  992         """
  993         Test thin.gen_thin function if control files are written (version, salt-call etc)
  994         when the current working directory is inaccessible, eg. Salt is configured to run as
  995         a non-root user but the command is executed in a directory that the user does not
  996         have permissions to.  Issue #54317.
  997 
  998         NOTE: Py2 version of this test is not required, as code shares the same spot across the versions.
  999 
 1000         :return:
 1001         """
 1002         thin.gen_thin("")
 1003         arc_name, arc_mode = thin.tarfile.method_calls[0][1]
 1004         self.assertEqual(arc_name, ".temporary")
 1005         self.assertEqual(arc_mode, "w:gz")
 1006         for idx, fname in enumerate(
 1007             ["version", ".thin-gen-py-version", "salt-call", "supported-versions"]
 1008         ):
 1009             name = thin.tarfile.open().method_calls[idx + 2][1][0]
 1010             self.assertEqual(name, fname)
 1011         thin.tarfile.open().close.assert_called()
 1012 
 1013     def test_get_tops_python(self):
 1014         """
 1015         test get_tops_python
 1016         """
 1017         patch_proc = patch(
 1018             "salt.utils.thin.subprocess.Popen",
 1019             self._popen(
 1020                 None,
 1021                 side_effect=[
 1022                     (bts("jinja2/__init__.py"), bts("")),
 1023                     (bts("yaml/__init__.py"), bts("")),
 1024                     (bts("tornado/__init__.py"), bts("")),
 1025                     (bts("msgpack/__init__.py"), bts("")),
 1026                     (bts("certifi/__init__.py"), bts("")),
 1027                     (bts("singledispatch.py"), bts("")),
 1028                     (bts(""), bts("")),
 1029                     (bts(""), bts("")),
 1030                     (bts(""), bts("")),
 1031                     (bts(""), bts("")),
 1032                     (bts(""), bts("")),
 1033                     (bts("distro.py"), bts("")),
 1034                 ],
 1035             ),
 1036         )
 1037 
 1038         patch_os = patch("os.path.exists", return_value=True)
 1039         patch_which = patch("salt.utils.path.which", return_value=True)
 1040         with patch_proc, patch_os, patch_which:
 1041             with TstSuiteLoggingHandler() as log_handler:
 1042                 exp_ret = copy.deepcopy(self.exp_ret)
 1043                 ret = thin.get_tops_python("python3.7", ext_py_ver=[3, 7])
 1044                 if salt.utils.platform.is_windows():
 1045                     for key, value in ret.items():
 1046                         ret[key] = str(pathlib.Path(value).resolve(strict=False))
 1047                     for key, value in exp_ret.items():
 1048                         exp_ret[key] = str(pathlib.Path(value).resolve(strict=False))
 1049                 assert ret == exp_ret
 1050                 assert (
 1051                     "ERROR:Could not auto detect file location for module concurrent for python version python3.7"
 1052                     in log_handler.messages
 1053                 )
 1054 
 1055     def test_get_tops_python_exclude(self):
 1056         """
 1057         test get_tops_python when excluding modules
 1058         """
 1059         patch_proc = patch(
 1060             "salt.utils.thin.subprocess.Popen",
 1061             self._popen(
 1062                 None,
 1063                 side_effect=[
 1064                     (bts("tornado/__init__.py"), bts("")),
 1065                     (bts("msgpack/__init__.py"), bts("")),
 1066                     (bts("certifi/__init__.py"), bts("")),
 1067                     (bts("singledispatch.py"), bts("")),
 1068                     (bts(""), bts("")),
 1069                     (bts(""), bts("")),
 1070                     (bts(""), bts("")),
 1071                     (bts(""), bts("")),
 1072                     (bts(""), bts("")),
 1073                     (bts("distro.py"), bts("")),
 1074                 ],
 1075             ),
 1076         )
 1077         exp_ret = copy.deepcopy(self.exp_ret)
 1078         for lib in self.exc_libs:
 1079             exp_ret.pop(lib)
 1080 
 1081         patch_os = patch("os.path.exists", return_value=True)
 1082         patch_which = patch("salt.utils.path.which", return_value=True)
 1083         with patch_proc, patch_os, patch_which:
 1084             ret = thin.get_tops_python(
 1085                 "python3.7", exclude=self.exc_libs, ext_py_ver=[3, 7]
 1086             )
 1087             if salt.utils.platform.is_windows():
 1088                 for key, value in ret.items():
 1089                     ret[key] = str(pathlib.Path(value).resolve(strict=False))
 1090                 for key, value in exp_ret.items():
 1091                     exp_ret[key] = str(pathlib.Path(value).resolve(strict=False))
 1092             assert ret == exp_ret
 1093 
 1094     def test_pack_alternatives_exclude(self):
 1095         """
 1096         test pack_alternatives when mixing
 1097         manually set dependencies and auto
 1098         detecting other modules.
 1099         """
 1100         patch_proc = patch(
 1101             "salt.utils.thin.subprocess.Popen",
 1102             self._popen(
 1103                 None,
 1104                 side_effect=[
 1105                     (bts(self.fake_libs["distro"]), bts("")),
 1106                     (bts(self.fake_libs["yaml"]), bts("")),
 1107                     (bts(self.fake_libs["tornado"]), bts("")),
 1108                     (bts(self.fake_libs["msgpack"]), bts("")),
 1109                     (bts(""), bts("")),
 1110                     (bts(""), bts("")),
 1111                     (bts(""), bts("")),
 1112                     (bts(""), bts("")),
 1113                     (bts(""), bts("")),
 1114                     (bts(""), bts("")),
 1115                     (bts(""), bts("")),
 1116                 ],
 1117             ),
 1118         )
 1119 
 1120         patch_os = patch("os.path.exists", return_value=True)
 1121         ext_conf = copy.deepcopy(self.ext_conf)
 1122         ext_conf["test"]["auto_detect"] = True
 1123 
 1124         for lib in self.fake_libs.values():
 1125             os.makedirs(lib)
 1126             with salt.utils.files.fopen(os.path.join(lib, "__init__.py"), "w+") as fp_:
 1127                 fp_.write("test")
 1128 
 1129         exp_files = self.exp_files.copy()
 1130         exp_files.extend(
 1131             [
 1132                 os.path.join("yaml", "__init__.py"),
 1133                 os.path.join("tornado", "__init__.py"),
 1134                 os.path.join("msgpack", "__init__.py"),
 1135             ]
 1136         )
 1137 
 1138         patch_which = patch("salt.utils.path.which", return_value=True)
 1139 
 1140         with patch_os, patch_proc, patch_which:
 1141             thin._pack_alternative(ext_conf, self.digest, self.tar)
 1142             calls = self.tar.mock_calls
 1143             for _file in exp_files:
 1144                 assert [x for x in calls if "{}".format(_file) in x[-2]]
 1145 
 1146     def test_pack_alternatives(self):
 1147         """
 1148         test thin._pack_alternatives
 1149         """
 1150         with patch("salt.utils.thin.get_ext_tops", MagicMock(return_value=self.tops)):
 1151             thin._pack_alternative(self.ext_conf, self.digest, self.tar)
 1152             calls = self.tar.mock_calls
 1153             for _file in self.exp_files:
 1154                 assert [x for x in calls if "{}".format(_file) in x[-2]]
 1155                 assert [
 1156                     x
 1157                     for x in calls
 1158                     if os.path.join("test", "pyall", _file) in x[-1]["arcname"]
 1159                 ]
 1160 
 1161     def test_pack_alternatives_not_normalized(self):
 1162         """
 1163         test thin._pack_alternatives when the path
 1164         is not normalized
 1165         """
 1166         tops = copy.deepcopy(self.tops)
 1167         tops["test"]["dependencies"] = [self.jinja_fp + "/"]
 1168         with patch("salt.utils.thin.get_ext_tops", MagicMock(return_value=tops)):
 1169             thin._pack_alternative(self.ext_conf, self.digest, self.tar)
 1170             calls = self.tar.mock_calls
 1171             for _file in self.exp_files:
 1172                 assert [x for x in calls if "{}".format(_file) in x[-2]]
 1173                 assert [
 1174                     x
 1175                     for x in calls
 1176                     if os.path.join("test", "pyall", _file) in x[-1]["arcname"]
 1177                 ]
 1178 
 1179     def test_pack_alternatives_path_doesnot_exist(self):
 1180         """
 1181         test thin._pack_alternatives when the path
 1182         doesnt exist. Check error log message
 1183         and expect that because the directory
 1184         does not exist jinja2 does not get
 1185         added to the tar
 1186         """
 1187         bad_path = os.path.join(tempfile.gettempdir(), "doesnotexisthere")
 1188         tops = copy.deepcopy(self.tops)
 1189         tops["test"]["dependencies"] = [bad_path]
 1190         with patch("salt.utils.thin.get_ext_tops", MagicMock(return_value=tops)):
 1191             with TstSuiteLoggingHandler() as log_handler:
 1192                 thin._pack_alternative(self.ext_conf, self.digest, self.tar)
 1193                 msg = "ERROR:File path {} does not exist. Unable to add to salt-ssh thin".format(
 1194                     bad_path
 1195                 )
 1196                 assert msg in log_handler.messages
 1197         calls = self.tar.mock_calls
 1198         for _file in self.exp_files:
 1199             arg = [x for x in calls if "{}".format(_file) in x[-2]]
 1200             kwargs = [
 1201                 x
 1202                 for x in calls
 1203                 if os.path.join("test", "pyall", _file) in x[-1]["arcname"]
 1204             ]
 1205             if "jinja2" in _file:
 1206                 assert not arg
 1207                 assert not kwargs
 1208             else:
 1209                 assert arg
 1210                 assert kwargs
 1211 
 1212     def test_pack_alternatives_auto_detect(self):
 1213         """
 1214         test thin._pack_alternatives when auto_detect
 1215         is enabled
 1216         """
 1217         ext_conf = copy.deepcopy(self.ext_conf)
 1218         ext_conf["test"]["auto_detect"] = True
 1219 
 1220         for lib in self.fake_libs.values():
 1221             os.makedirs(lib)
 1222             with salt.utils.files.fopen(os.path.join(lib, "__init__.py"), "w+") as fp_:
 1223                 fp_.write("test")
 1224 
 1225         patch_tops_py = patch(
 1226             "salt.utils.thin.get_tops_python", return_value=self.fake_libs
 1227         )
 1228 
 1229         exp_files = self.exp_files.copy()
 1230         exp_files.extend(
 1231             [
 1232                 os.path.join("yaml", "__init__.py"),
 1233                 os.path.join("tornado", "__init__.py"),
 1234                 os.path.join("msgpack", "__init__.py"),
 1235             ]
 1236         )
 1237         with patch_tops_py:
 1238             thin._pack_alternative(ext_conf, self.digest, self.tar)
 1239             calls = self.tar.mock_calls
 1240             for _file in exp_files:
 1241                 assert [x for x in calls if "{}".format(_file) in x[-2]]
 1242 
 1243     def test_pack_alternatives_empty_dependencies(self):
 1244         """
 1245         test _pack_alternatives when dependencies is not
 1246         set in the config.
 1247         """
 1248         ext_conf = copy.deepcopy(self.ext_conf)
 1249         ext_conf["test"]["auto_detect"] = True
 1250         ext_conf["test"].pop("dependencies")
 1251 
 1252         for lib in self.fake_libs.values():
 1253             os.makedirs(lib)
 1254             with salt.utils.files.fopen(os.path.join(lib, "__init__.py"), "w+") as fp_:
 1255                 fp_.write("test")
 1256 
 1257         patch_tops_py = patch(
 1258             "salt.utils.thin.get_tops_python", return_value=self.fake_libs
 1259         )
 1260 
 1261         exp_files = self.exp_files.copy()
 1262         exp_files.extend(
 1263             [
 1264                 os.path.join("yaml", "__init__.py"),
 1265                 os.path.join("tornado", "__init__.py"),
 1266                 os.path.join("msgpack", "__init__.py"),
 1267             ]
 1268         )
 1269         with patch_tops_py:
 1270             thin._pack_alternative(ext_conf, self.digest, self.tar)
 1271             calls = self.tar.mock_calls
 1272             for _file in exp_files:
 1273                 assert [x for x in calls if "{}".format(_file) in x[-2]]
 1274 
 1275     @skipIf(
 1276         salt.utils.platform.is_windows(), "salt-ssh does not deploy to/from windows"
 1277     )
 1278     def test_thin_dir(self):
 1279         """
 1280         Test the thin dir to make sure salt-call can run
 1281 
 1282         Run salt call via a python in a new virtual environment to ensure
 1283         salt-call has all dependencies needed.
 1284         """
 1285         # This was previously an integration test and is now here, as a unit test.
 1286         # Should actually be a functional test
 1287         with VirtualEnv() as venv:
 1288             salt.utils.thin.gen_thin(venv.venv_dir)
 1289             thin_dir = os.path.join(venv.venv_dir, "thin")
 1290             thin_archive = os.path.join(thin_dir, "thin.tgz")
 1291             tar = tarfile.open(thin_archive)
 1292             tar.extractall(thin_dir)
 1293             tar.close()
 1294             ret = venv.run(
 1295                 venv.venv_python,
 1296                 os.path.join(thin_dir, "salt-call"),
 1297                 "--version",
 1298                 check=False,
 1299             )
 1300             assert ret.exitcode == 0, ret