test_execute.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import logging
  2. import subprocess
  3. import pytest
  4. from flexmock import flexmock
  5. from borgmatic import execute as module
  6. def test_log_outputs_logs_each_line_separately():
  7. flexmock(module.logger).should_receive('log').with_args(logging.INFO, 'hi').once()
  8. flexmock(module.logger).should_receive('log').with_args(logging.INFO, 'there').once()
  9. flexmock(module).should_receive('exit_code_indicates_error').and_return(False)
  10. hi_process = subprocess.Popen(['echo', 'hi'], stdout=subprocess.PIPE)
  11. flexmock(module).should_receive('output_buffer_for_process').with_args(
  12. hi_process, ()
  13. ).and_return(hi_process.stdout)
  14. there_process = subprocess.Popen(['echo', 'there'], stdout=subprocess.PIPE)
  15. flexmock(module).should_receive('output_buffer_for_process').with_args(
  16. there_process, ()
  17. ).and_return(there_process.stdout)
  18. module.log_outputs(
  19. (hi_process, there_process),
  20. exclude_stdouts=(),
  21. output_log_level=logging.INFO,
  22. borg_local_path='borg',
  23. )
  24. def test_log_outputs_skips_logs_for_process_with_none_stdout():
  25. flexmock(module.logger).should_receive('log').with_args(logging.INFO, 'hi').never()
  26. flexmock(module.logger).should_receive('log').with_args(logging.INFO, 'there').once()
  27. flexmock(module).should_receive('exit_code_indicates_error').and_return(False)
  28. hi_process = subprocess.Popen(['echo', 'hi'], stdout=None)
  29. flexmock(module).should_receive('output_buffer_for_process').with_args(
  30. hi_process, ()
  31. ).and_return(hi_process.stdout)
  32. there_process = subprocess.Popen(['echo', 'there'], stdout=subprocess.PIPE)
  33. flexmock(module).should_receive('output_buffer_for_process').with_args(
  34. there_process, ()
  35. ).and_return(there_process.stdout)
  36. module.log_outputs(
  37. (hi_process, there_process),
  38. exclude_stdouts=(),
  39. output_log_level=logging.INFO,
  40. borg_local_path='borg',
  41. )
  42. def test_log_outputs_includes_error_output_in_exception():
  43. flexmock(module.logger).should_receive('log')
  44. flexmock(module).should_receive('exit_code_indicates_error').and_return(True)
  45. flexmock(module).should_receive('command_for_process').and_return('grep')
  46. process = subprocess.Popen(['grep'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  47. flexmock(module).should_receive('output_buffer_for_process').and_return(process.stdout)
  48. with pytest.raises(subprocess.CalledProcessError) as error:
  49. module.log_outputs(
  50. (process,), exclude_stdouts=(), output_log_level=logging.INFO, borg_local_path='borg'
  51. )
  52. assert error.value.output
  53. def test_log_outputs_skips_error_output_in_exception_for_process_with_none_stdout():
  54. flexmock(module.logger).should_receive('log')
  55. flexmock(module).should_receive('exit_code_indicates_error').and_return(True)
  56. flexmock(module).should_receive('command_for_process').and_return('grep')
  57. process = subprocess.Popen(['grep'], stdout=None)
  58. flexmock(module).should_receive('output_buffer_for_process').and_return(process.stdout)
  59. with pytest.raises(subprocess.CalledProcessError) as error:
  60. module.log_outputs(
  61. (process,), exclude_stdouts=(), output_log_level=logging.INFO, borg_local_path='borg'
  62. )
  63. assert error.value.returncode == 2
  64. assert not error.value.output
  65. def test_log_outputs_kills_other_processes_when_one_errors():
  66. flexmock(module.logger).should_receive('log')
  67. flexmock(module).should_receive('command_for_process').and_return('grep')
  68. process = subprocess.Popen(['grep'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  69. flexmock(module).should_receive('exit_code_indicates_error').with_args(
  70. process, None, 'borg'
  71. ).and_return(False)
  72. flexmock(module).should_receive('exit_code_indicates_error').with_args(
  73. process, 2, 'borg'
  74. ).and_return(True)
  75. other_process = subprocess.Popen(
  76. ['sleep', '2'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
  77. )
  78. flexmock(module).should_receive('exit_code_indicates_error').with_args(
  79. other_process, None, 'borg'
  80. ).and_return(False)
  81. flexmock(module).should_receive('output_buffer_for_process').with_args(process, ()).and_return(
  82. process.stdout
  83. )
  84. flexmock(module).should_receive('output_buffer_for_process').with_args(
  85. other_process, ()
  86. ).and_return(other_process.stdout)
  87. flexmock(other_process).should_receive('kill').once()
  88. with pytest.raises(subprocess.CalledProcessError) as error:
  89. module.log_outputs(
  90. (process, other_process),
  91. exclude_stdouts=(),
  92. output_log_level=logging.INFO,
  93. borg_local_path='borg',
  94. )
  95. assert error.value.returncode == 2
  96. assert error.value.output
  97. def test_log_outputs_vents_other_processes_when_one_exits():
  98. '''
  99. Execute a command to generate a longish random string and pipe it into another command that
  100. exits quickly. The test is basically to ensure we don't hang forever waiting for the exited
  101. process to read the pipe, and that the string-generating process eventually gets vented and
  102. exits.
  103. '''
  104. flexmock(module.logger).should_receive('log')
  105. flexmock(module).should_receive('command_for_process').and_return('grep')
  106. process = subprocess.Popen(
  107. ['xxd', '-l', '40000', '-p', '/dev/urandom'], stdout=subprocess.PIPE, stderr=subprocess.PIPE
  108. )
  109. other_process = subprocess.Popen(
  110. ['true'], stdin=process.stdout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
  111. )
  112. flexmock(module).should_receive('output_buffer_for_process').with_args(
  113. process, (process.stdout,)
  114. ).and_return(process.stderr)
  115. flexmock(module).should_receive('output_buffer_for_process').with_args(
  116. other_process, (process.stdout,)
  117. ).and_return(other_process.stdout)
  118. flexmock(process.stdout).should_call('readline').at_least().once()
  119. module.log_outputs(
  120. (process, other_process),
  121. exclude_stdouts=(process.stdout,),
  122. output_log_level=logging.INFO,
  123. borg_local_path='borg',
  124. )
  125. def test_log_outputs_truncates_long_error_output():
  126. flexmock(module).ERROR_OUTPUT_MAX_LINE_COUNT = 0
  127. flexmock(module.logger).should_receive('log')
  128. flexmock(module).should_receive('command_for_process').and_return('grep')
  129. process = subprocess.Popen(['grep'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  130. flexmock(module).should_receive('exit_code_indicates_error').with_args(
  131. process, None, 'borg'
  132. ).and_return(False)
  133. flexmock(module).should_receive('exit_code_indicates_error').with_args(
  134. process, 2, 'borg'
  135. ).and_return(True)
  136. flexmock(module).should_receive('output_buffer_for_process').and_return(process.stdout)
  137. with pytest.raises(subprocess.CalledProcessError) as error:
  138. module.log_outputs(
  139. (process,), exclude_stdouts=(), output_log_level=logging.INFO, borg_local_path='borg'
  140. )
  141. assert error.value.returncode == 2
  142. assert error.value.output.startswith('...')
  143. def test_log_outputs_with_no_output_logs_nothing():
  144. flexmock(module.logger).should_receive('log').never()
  145. flexmock(module).should_receive('exit_code_indicates_error').and_return(False)
  146. process = subprocess.Popen(['true'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  147. flexmock(module).should_receive('output_buffer_for_process').and_return(process.stdout)
  148. module.log_outputs(
  149. (process,), exclude_stdouts=(), output_log_level=logging.INFO, borg_local_path='borg'
  150. )
  151. def test_log_outputs_with_unfinished_process_re_polls():
  152. flexmock(module.logger).should_receive('log').never()
  153. flexmock(module).should_receive('exit_code_indicates_error').and_return(False)
  154. process = subprocess.Popen(['true'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  155. flexmock(process).should_receive('poll').and_return(None).and_return(0).twice()
  156. flexmock(module).should_receive('output_buffer_for_process').and_return(process.stdout)
  157. module.log_outputs(
  158. (process,), exclude_stdouts=(), output_log_level=logging.INFO, borg_local_path='borg'
  159. )