"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