log_printer_test.py 5.5 KB

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