cli_test.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import os
  2. import shutil
  3. import tempfile
  4. from io import StringIO
  5. import docker
  6. import py
  7. import pytest
  8. from docker.constants import DEFAULT_DOCKER_API_VERSION
  9. from .. import mock
  10. from .. import unittest
  11. from ..helpers import build_config
  12. from compose.cli.command import get_project
  13. from compose.cli.command import get_project_name
  14. from compose.cli.docopt_command import NoSuchCommand
  15. from compose.cli.errors import UserError
  16. from compose.cli.main import TopLevelCommand
  17. from compose.const import IS_WINDOWS_PLATFORM
  18. from compose.const import LABEL_SERVICE
  19. from compose.container import Container
  20. from compose.project import Project
  21. class CLITestCase(unittest.TestCase):
  22. def test_default_project_name(self):
  23. test_dir = py._path.local.LocalPath('tests/fixtures/simple-composefile')
  24. with test_dir.as_cwd():
  25. project_name = get_project_name('.')
  26. assert 'simple-composefile' == project_name
  27. def test_project_name_with_explicit_base_dir(self):
  28. base_dir = 'tests/fixtures/simple-composefile'
  29. project_name = get_project_name(base_dir)
  30. assert 'simple-composefile' == project_name
  31. def test_project_name_with_explicit_uppercase_base_dir(self):
  32. base_dir = 'tests/fixtures/UpperCaseDir'
  33. project_name = get_project_name(base_dir)
  34. assert 'uppercasedir' == project_name
  35. def test_project_name_with_explicit_project_name(self):
  36. name = 'explicit-project-name'
  37. project_name = get_project_name(None, project_name=name)
  38. assert 'explicit-project-name' == project_name
  39. @mock.patch.dict(os.environ)
  40. def test_project_name_from_environment_new_var(self):
  41. name = 'namefromenv'
  42. os.environ['COMPOSE_PROJECT_NAME'] = name
  43. project_name = get_project_name(None)
  44. assert project_name == name
  45. def test_project_name_with_empty_environment_var(self):
  46. base_dir = 'tests/fixtures/simple-composefile'
  47. with mock.patch.dict(os.environ):
  48. os.environ['COMPOSE_PROJECT_NAME'] = ''
  49. project_name = get_project_name(base_dir)
  50. assert 'simple-composefile' == project_name
  51. @mock.patch.dict(os.environ)
  52. def test_project_name_with_environment_file(self):
  53. base_dir = tempfile.mkdtemp()
  54. try:
  55. name = 'namefromenvfile'
  56. with open(os.path.join(base_dir, '.env'), 'w') as f:
  57. f.write('COMPOSE_PROJECT_NAME={}'.format(name))
  58. project_name = get_project_name(base_dir)
  59. assert project_name == name
  60. # Environment has priority over .env file
  61. os.environ['COMPOSE_PROJECT_NAME'] = 'namefromenv'
  62. assert get_project_name(base_dir) == os.environ['COMPOSE_PROJECT_NAME']
  63. finally:
  64. shutil.rmtree(base_dir)
  65. def test_get_project(self):
  66. base_dir = 'tests/fixtures/longer-filename-composefile'
  67. project = get_project(base_dir)
  68. assert project.name == 'longer-filename-composefile'
  69. assert project.client
  70. assert project.services
  71. def test_command_help(self):
  72. with mock.patch('sys.stdout', new=StringIO()) as fake_stdout:
  73. TopLevelCommand.help({'COMMAND': 'up'})
  74. assert "Usage: up" in fake_stdout.getvalue()
  75. def test_command_help_nonexistent(self):
  76. with pytest.raises(NoSuchCommand):
  77. TopLevelCommand.help({'COMMAND': 'nonexistent'})
  78. @pytest.mark.xfail(IS_WINDOWS_PLATFORM, reason="requires dockerpty")
  79. @mock.patch('compose.cli.main.RunOperation', autospec=True)
  80. @mock.patch('compose.cli.main.PseudoTerminal', autospec=True)
  81. @mock.patch('compose.service.Container.create')
  82. @mock.patch.dict(os.environ)
  83. def test_run_interactive_passes_logs_false(
  84. self,
  85. mock_container_create,
  86. mock_pseudo_terminal,
  87. mock_run_operation,
  88. ):
  89. os.environ['COMPOSE_INTERACTIVE_NO_CLI'] = 'true'
  90. mock_client = mock.create_autospec(docker.APIClient)
  91. mock_client.api_version = DEFAULT_DOCKER_API_VERSION
  92. mock_client._general_configs = {}
  93. mock_container_create.return_value = Container(mock_client, {
  94. 'Id': '37b35e0ba80d91009d37e16f249b32b84f72bda269985578ed6c75a0a13fcaa8',
  95. 'Config': {
  96. 'Labels': {
  97. LABEL_SERVICE: 'service',
  98. }
  99. },
  100. }, has_been_inspected=True)
  101. project = Project.from_config(
  102. name='composetest',
  103. client=mock_client,
  104. config_data=build_config({
  105. 'service': {'image': 'busybox'}
  106. }),
  107. )
  108. command = TopLevelCommand(project)
  109. with pytest.raises(SystemExit):
  110. command.run({
  111. 'SERVICE': 'service',
  112. 'COMMAND': None,
  113. '-e': [],
  114. '--label': [],
  115. '--user': None,
  116. '--no-deps': None,
  117. '--detach': False,
  118. '-T': None,
  119. '--entrypoint': None,
  120. '--service-ports': None,
  121. '--use-aliases': None,
  122. '--publish': [],
  123. '--volume': [],
  124. '--rm': None,
  125. '--name': None,
  126. '--workdir': None,
  127. })
  128. _, _, call_kwargs = mock_run_operation.mock_calls[0]
  129. assert call_kwargs['logs'] is False
  130. @mock.patch('compose.service.Container.create')
  131. def test_run_service_with_restart_always(self, mock_container_create):
  132. mock_client = mock.create_autospec(docker.APIClient)
  133. mock_client.api_version = DEFAULT_DOCKER_API_VERSION
  134. mock_client._general_configs = {}
  135. mock_container_create.return_value = Container(mock_client, {
  136. 'Id': '37b35e0ba80d91009d37e16f249b32b84f72bda269985578ed6c75a0a13fcaa8',
  137. 'Name': 'composetest_service_37b35',
  138. 'Config': {
  139. 'Labels': {
  140. LABEL_SERVICE: 'service',
  141. }
  142. },
  143. }, has_been_inspected=True)
  144. project = Project.from_config(
  145. name='composetest',
  146. client=mock_client,
  147. config_data=build_config({
  148. 'service': {
  149. 'image': 'busybox',
  150. 'restart': 'always',
  151. }
  152. }),
  153. )
  154. command = TopLevelCommand(project)
  155. command.run({
  156. 'SERVICE': 'service',
  157. 'COMMAND': None,
  158. '-e': [],
  159. '--label': [],
  160. '--user': None,
  161. '--no-deps': None,
  162. '--detach': True,
  163. '-T': None,
  164. '--entrypoint': None,
  165. '--service-ports': None,
  166. '--use-aliases': None,
  167. '--publish': [],
  168. '--volume': [],
  169. '--rm': None,
  170. '--name': None,
  171. '--workdir': None,
  172. })
  173. # NOTE: The "run" command is supposed to be a one-off tool; therefore restart policy "no"
  174. # (the default) is enforced despite explicit wish for "always" in the project
  175. # configuration file
  176. assert not mock_client.create_host_config.call_args[1].get('restart_policy')
  177. command = TopLevelCommand(project)
  178. command.run({
  179. 'SERVICE': 'service',
  180. 'COMMAND': None,
  181. '-e': [],
  182. '--label': [],
  183. '--user': None,
  184. '--no-deps': None,
  185. '--detach': True,
  186. '-T': None,
  187. '--entrypoint': None,
  188. '--service-ports': None,
  189. '--use-aliases': None,
  190. '--publish': [],
  191. '--volume': [],
  192. '--rm': True,
  193. '--name': None,
  194. '--workdir': None,
  195. })
  196. assert not mock_client.create_host_config.call_args[1].get('restart_policy')
  197. def test_command_manual_and_service_ports_together(self):
  198. project = Project.from_config(
  199. name='composetest',
  200. client=None,
  201. config_data=build_config({
  202. 'service': {'image': 'busybox'},
  203. }),
  204. )
  205. command = TopLevelCommand(project)
  206. with pytest.raises(UserError):
  207. command.run({
  208. 'SERVICE': 'service',
  209. 'COMMAND': None,
  210. '-e': [],
  211. '--label': [],
  212. '--user': None,
  213. '--no-deps': None,
  214. '--detach': True,
  215. '-T': None,
  216. '--entrypoint': None,
  217. '--service-ports': True,
  218. '--use-aliases': None,
  219. '--publish': ['80:80'],
  220. '--rm': None,
  221. '--name': None,
  222. })