1
0

log_printer_test.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import itertools
  4. import pytest
  5. import requests
  6. import six
  7. from docker.errors import APIError
  8. from six.moves.queue import Queue
  9. from compose.cli.log_printer import build_log_generator
  10. from compose.cli.log_printer import build_log_presenters
  11. from compose.cli.log_printer import build_no_log_generator
  12. from compose.cli.log_printer import consume_queue
  13. from compose.cli.log_printer import QueueItem
  14. from compose.cli.log_printer import wait_on_exit
  15. from compose.cli.log_printer import watch_events
  16. from compose.container import Container
  17. from tests import mock
  18. @pytest.fixture
  19. def output_stream():
  20. output = six.StringIO()
  21. output.flush = mock.Mock()
  22. return output
  23. @pytest.fixture
  24. def mock_container():
  25. return mock.Mock(spec=Container, name_without_project='web_1')
  26. class TestLogPresenter(object):
  27. def test_monochrome(self, mock_container):
  28. presenters = build_log_presenters(['foo', 'bar'], True)
  29. presenter = next(presenters)
  30. actual = presenter.present(mock_container, "this line")
  31. assert actual == "web_1 | this line"
  32. def test_polychrome(self, mock_container):
  33. presenters = build_log_presenters(['foo', 'bar'], False)
  34. presenter = next(presenters)
  35. actual = presenter.present(mock_container, "this line")
  36. assert '\033[' in actual
  37. def test_wait_on_exit():
  38. exit_status = 3
  39. mock_container = mock.Mock(
  40. spec=Container,
  41. name='cname',
  42. wait=mock.Mock(return_value=exit_status))
  43. expected = '{} exited with code {}\n'.format(mock_container.name, exit_status)
  44. assert expected == wait_on_exit(mock_container)
  45. def test_wait_on_exit_raises():
  46. status_code = 500
  47. def mock_wait():
  48. resp = requests.Response()
  49. resp.status_code = status_code
  50. raise APIError('Bad server', resp)
  51. mock_container = mock.Mock(
  52. spec=Container,
  53. name='cname',
  54. wait=mock_wait
  55. )
  56. expected = 'Unexpected API error for {} (HTTP code {})\n'.format(
  57. mock_container.name, status_code,
  58. )
  59. assert expected in wait_on_exit(mock_container)
  60. def test_build_no_log_generator(mock_container):
  61. mock_container.has_api_logs = False
  62. mock_container.log_driver = 'none'
  63. output, = build_no_log_generator(mock_container, None)
  64. assert "WARNING: no logs are available with the 'none' log driver\n" in output
  65. assert "exited with code" not in output
  66. class TestBuildLogGenerator(object):
  67. def test_no_log_stream(self, mock_container):
  68. mock_container.log_stream = None
  69. mock_container.logs.return_value = iter([b"hello\nworld"])
  70. log_args = {'follow': True}
  71. generator = build_log_generator(mock_container, log_args)
  72. assert next(generator) == "hello\n"
  73. assert next(generator) == "world"
  74. mock_container.logs.assert_called_once_with(
  75. stdout=True,
  76. stderr=True,
  77. stream=True,
  78. **log_args)
  79. def test_with_log_stream(self, mock_container):
  80. mock_container.log_stream = iter([b"hello\nworld"])
  81. log_args = {'follow': True}
  82. generator = build_log_generator(mock_container, log_args)
  83. assert next(generator) == "hello\n"
  84. assert next(generator) == "world"
  85. def test_unicode(self, output_stream):
  86. glyph = u'\u2022\n'
  87. mock_container.log_stream = iter([glyph.encode('utf-8')])
  88. generator = build_log_generator(mock_container, {})
  89. assert next(generator) == glyph
  90. @pytest.fixture
  91. def thread_map():
  92. return {'cid': mock.Mock()}
  93. @pytest.fixture
  94. def mock_presenters():
  95. return itertools.cycle([mock.Mock()])
  96. class TestWatchEvents(object):
  97. def test_stop_event(self, thread_map, mock_presenters):
  98. event_stream = [{'action': 'stop', 'id': 'cid'}]
  99. watch_events(thread_map, event_stream, mock_presenters, ())
  100. assert not thread_map
  101. def test_start_event(self, thread_map, mock_presenters):
  102. container_id = 'abcd'
  103. event = {'action': 'start', 'id': container_id, 'container': mock.Mock()}
  104. event_stream = [event]
  105. thread_args = 'foo', 'bar'
  106. with mock.patch(
  107. 'compose.cli.log_printer.build_thread',
  108. autospec=True
  109. ) as mock_build_thread:
  110. watch_events(thread_map, event_stream, mock_presenters, thread_args)
  111. mock_build_thread.assert_called_once_with(
  112. event['container'],
  113. next(mock_presenters),
  114. *thread_args)
  115. assert container_id in thread_map
  116. def test_other_event(self, thread_map, mock_presenters):
  117. container_id = 'abcd'
  118. event_stream = [{'action': 'create', 'id': container_id}]
  119. watch_events(thread_map, event_stream, mock_presenters, ())
  120. assert container_id not in thread_map
  121. class TestConsumeQueue(object):
  122. def test_item_is_an_exception(self):
  123. class Problem(Exception):
  124. pass
  125. queue = Queue()
  126. error = Problem('oops')
  127. for item in QueueItem.new('a'), QueueItem.new('b'), QueueItem.exception(error):
  128. queue.put(item)
  129. generator = consume_queue(queue, False)
  130. assert next(generator) == 'a'
  131. assert next(generator) == 'b'
  132. with pytest.raises(Problem):
  133. next(generator)
  134. def test_item_is_stop_without_cascade_stop(self):
  135. queue = Queue()
  136. for item in QueueItem.stop(), QueueItem.new('a'), QueueItem.new('b'):
  137. queue.put(item)
  138. generator = consume_queue(queue, False)
  139. assert next(generator) == 'a'
  140. assert next(generator) == 'b'
  141. def test_item_is_stop_with_cascade_stop(self):
  142. """Return the name of the container that caused the cascade_stop"""
  143. queue = Queue()
  144. for item in QueueItem.stop('foobar-1'), QueueItem.new('a'), QueueItem.new('b'):
  145. queue.put(item)
  146. generator = consume_queue(queue, True)
  147. assert next(generator) == 'foobar-1'
  148. def test_item_is_none_when_timeout_is_hit(self):
  149. queue = Queue()
  150. generator = consume_queue(queue, False)
  151. assert next(generator) is None