main_test.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import logging
  4. import docker
  5. import pytest
  6. from compose import container
  7. from compose.cli.errors import UserError
  8. from compose.cli.formatter import ConsoleWarningFormatter
  9. from compose.cli.main import build_one_off_container_options
  10. from compose.cli.main import call_docker
  11. from compose.cli.main import convergence_strategy_from_opts
  12. from compose.cli.main import filter_attached_containers
  13. from compose.cli.main import get_docker_start_call
  14. from compose.cli.main import setup_console_handler
  15. from compose.cli.main import warn_for_swarm_mode
  16. from compose.service import ConvergenceStrategy
  17. from tests import mock
  18. def mock_container(service, number):
  19. return mock.create_autospec(
  20. container.Container,
  21. service=service,
  22. number=number,
  23. name_without_project='{0}_{1}'.format(service, number))
  24. @pytest.fixture
  25. def logging_handler():
  26. stream = mock.Mock()
  27. stream.isatty.return_value = True
  28. return logging.StreamHandler(stream=stream)
  29. class TestCLIMainTestCase(object):
  30. def test_filter_attached_containers(self):
  31. containers = [
  32. mock_container('web', 1),
  33. mock_container('web', 2),
  34. mock_container('db', 1),
  35. mock_container('other', 1),
  36. mock_container('another', 1),
  37. ]
  38. service_names = ['web', 'db']
  39. actual = filter_attached_containers(containers, service_names)
  40. assert actual == containers[:3]
  41. def test_filter_attached_containers_with_dependencies(self):
  42. containers = [
  43. mock_container('web', 1),
  44. mock_container('web', 2),
  45. mock_container('db', 1),
  46. mock_container('other', 1),
  47. mock_container('another', 1),
  48. ]
  49. service_names = ['web', 'db']
  50. actual = filter_attached_containers(containers, service_names, attach_dependencies=True)
  51. assert actual == containers
  52. def test_filter_attached_containers_all(self):
  53. containers = [
  54. mock_container('web', 1),
  55. mock_container('db', 1),
  56. mock_container('other', 1),
  57. ]
  58. service_names = []
  59. actual = filter_attached_containers(containers, service_names)
  60. assert actual == containers
  61. def test_warning_in_swarm_mode(self):
  62. mock_client = mock.create_autospec(docker.APIClient)
  63. mock_client.info.return_value = {'Swarm': {'LocalNodeState': 'active'}}
  64. with mock.patch('compose.cli.main.log') as fake_log:
  65. warn_for_swarm_mode(mock_client)
  66. assert fake_log.warning.call_count == 1
  67. def test_build_one_off_container_options(self):
  68. command = 'build myservice'
  69. detach = False
  70. options = {
  71. '-e': ['MYVAR=MYVALUE'],
  72. '-T': True,
  73. '--label': ['MYLABEL'],
  74. '--entrypoint': 'bash',
  75. '--user': 'MYUSER',
  76. '--service-ports': [],
  77. '--publish': '',
  78. '--name': 'MYNAME',
  79. '--workdir': '.',
  80. '--volume': [],
  81. 'stdin_open': False,
  82. }
  83. expected_container_options = {
  84. 'command': command,
  85. 'tty': False,
  86. 'stdin_open': False,
  87. 'detach': detach,
  88. 'entrypoint': 'bash',
  89. 'environment': {'MYVAR': 'MYVALUE'},
  90. 'labels': {'MYLABEL': ''},
  91. 'name': 'MYNAME',
  92. 'ports': [],
  93. 'restart': None,
  94. 'user': 'MYUSER',
  95. 'working_dir': '.',
  96. }
  97. container_options = build_one_off_container_options(options, detach, command)
  98. assert container_options == expected_container_options
  99. def test_get_docker_start_call(self):
  100. container_id = 'my_container_id'
  101. mock_container_options = {'detach': False, 'stdin_open': True}
  102. expected_docker_start_call = ['start', '--attach', '--interactive', container_id]
  103. docker_start_call = get_docker_start_call(mock_container_options, container_id)
  104. assert expected_docker_start_call == docker_start_call
  105. mock_container_options = {'detach': False, 'stdin_open': False}
  106. expected_docker_start_call = ['start', '--attach', container_id]
  107. docker_start_call = get_docker_start_call(mock_container_options, container_id)
  108. assert expected_docker_start_call == docker_start_call
  109. mock_container_options = {'detach': True, 'stdin_open': True}
  110. expected_docker_start_call = ['start', '--interactive', container_id]
  111. docker_start_call = get_docker_start_call(mock_container_options, container_id)
  112. assert expected_docker_start_call == docker_start_call
  113. mock_container_options = {'detach': True, 'stdin_open': False}
  114. expected_docker_start_call = ['start', container_id]
  115. docker_start_call = get_docker_start_call(mock_container_options, container_id)
  116. assert expected_docker_start_call == docker_start_call
  117. class TestSetupConsoleHandlerTestCase(object):
  118. def test_with_tty_verbose(self, logging_handler):
  119. setup_console_handler(logging_handler, True)
  120. assert type(logging_handler.formatter) == ConsoleWarningFormatter
  121. assert '%(name)s' in logging_handler.formatter._fmt
  122. assert '%(funcName)s' in logging_handler.formatter._fmt
  123. def test_with_tty_not_verbose(self, logging_handler):
  124. setup_console_handler(logging_handler, False)
  125. assert type(logging_handler.formatter) == ConsoleWarningFormatter
  126. assert '%(name)s' not in logging_handler.formatter._fmt
  127. assert '%(funcName)s' not in logging_handler.formatter._fmt
  128. def test_with_not_a_tty(self, logging_handler):
  129. logging_handler.stream.isatty.return_value = False
  130. setup_console_handler(logging_handler, False)
  131. assert type(logging_handler.formatter) == logging.Formatter
  132. class TestConvergeStrategyFromOptsTestCase(object):
  133. def test_invalid_opts(self):
  134. options = {'--force-recreate': True, '--no-recreate': True}
  135. with pytest.raises(UserError):
  136. convergence_strategy_from_opts(options)
  137. def test_always(self):
  138. options = {'--force-recreate': True, '--no-recreate': False}
  139. assert (
  140. convergence_strategy_from_opts(options) ==
  141. ConvergenceStrategy.always
  142. )
  143. def test_never(self):
  144. options = {'--force-recreate': False, '--no-recreate': True}
  145. assert (
  146. convergence_strategy_from_opts(options) ==
  147. ConvergenceStrategy.never
  148. )
  149. def test_changed(self):
  150. options = {'--force-recreate': False, '--no-recreate': False}
  151. assert (
  152. convergence_strategy_from_opts(options) ==
  153. ConvergenceStrategy.changed
  154. )
  155. def mock_find_executable(exe):
  156. return exe
  157. @mock.patch('compose.cli.main.find_executable', mock_find_executable)
  158. class TestCallDocker(object):
  159. def test_simple_no_options(self):
  160. with mock.patch('subprocess.call') as fake_call:
  161. call_docker(['ps'], {}, {})
  162. assert fake_call.call_args[0][0] == ['docker', 'ps']
  163. def test_simple_tls_option(self):
  164. with mock.patch('subprocess.call') as fake_call:
  165. call_docker(['ps'], {'--tls': True}, {})
  166. assert fake_call.call_args[0][0] == ['docker', '--tls', 'ps']
  167. def test_advanced_tls_options(self):
  168. with mock.patch('subprocess.call') as fake_call:
  169. call_docker(['ps'], {
  170. '--tls': True,
  171. '--tlscacert': './ca.pem',
  172. '--tlscert': './cert.pem',
  173. '--tlskey': './key.pem',
  174. }, {})
  175. assert fake_call.call_args[0][0] == [
  176. 'docker', '--tls', '--tlscacert', './ca.pem', '--tlscert',
  177. './cert.pem', '--tlskey', './key.pem', 'ps'
  178. ]
  179. def test_with_host_option(self):
  180. with mock.patch('subprocess.call') as fake_call:
  181. call_docker(['ps'], {'--host': 'tcp://mydocker.net:2333'}, {})
  182. assert fake_call.call_args[0][0] == [
  183. 'docker', '--host', 'tcp://mydocker.net:2333', 'ps'
  184. ]
  185. def test_with_http_host(self):
  186. with mock.patch('subprocess.call') as fake_call:
  187. call_docker(['ps'], {'--host': 'http://mydocker.net:2333'}, {})
  188. assert fake_call.call_args[0][0] == [
  189. 'docker', '--host', 'tcp://mydocker.net:2333', 'ps',
  190. ]
  191. def test_with_host_option_shorthand_equal(self):
  192. with mock.patch('subprocess.call') as fake_call:
  193. call_docker(['ps'], {'--host': '=tcp://mydocker.net:2333'}, {})
  194. assert fake_call.call_args[0][0] == [
  195. 'docker', '--host', 'tcp://mydocker.net:2333', 'ps'
  196. ]
  197. def test_with_env(self):
  198. with mock.patch('subprocess.call') as fake_call:
  199. call_docker(['ps'], {}, {'DOCKER_HOST': 'tcp://mydocker.net:2333'})
  200. assert fake_call.call_args[0][0] == [
  201. 'docker', 'ps'
  202. ]
  203. assert fake_call.call_args[1]['env'] == {'DOCKER_HOST': 'tcp://mydocker.net:2333'}