test_execute.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. import subprocess
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic import execute as module
  5. @pytest.mark.parametrize(
  6. 'command,exit_code,borg_local_path,borg_exit_codes,expected_result',
  7. (
  8. (['grep'], 2, None, None, module.Exit_status.ERROR),
  9. (['grep'], 2, 'borg', None, module.Exit_status.ERROR),
  10. (['borg'], 2, 'borg', None, module.Exit_status.ERROR),
  11. (['borg1'], 2, 'borg1', None, module.Exit_status.ERROR),
  12. (['grep'], 1, None, None, module.Exit_status.ERROR),
  13. (['grep'], 1, 'borg', None, module.Exit_status.ERROR),
  14. (['borg'], 1, 'borg', None, module.Exit_status.WARNING),
  15. (['borg1'], 1, 'borg1', None, module.Exit_status.WARNING),
  16. (['grep'], 100, None, None, module.Exit_status.ERROR),
  17. (['grep'], 100, 'borg', None, module.Exit_status.ERROR),
  18. (['borg'], 100, 'borg', None, module.Exit_status.WARNING),
  19. (['borg1'], 100, 'borg1', None, module.Exit_status.WARNING),
  20. (['grep'], 0, None, None, module.Exit_status.SUCCESS),
  21. (['grep'], 0, 'borg', None, module.Exit_status.SUCCESS),
  22. (['borg'], 0, 'borg', None, module.Exit_status.SUCCESS),
  23. (['borg1'], 0, 'borg1', None, module.Exit_status.SUCCESS),
  24. # -9 exit code occurs when child process get SIGKILLed.
  25. (['grep'], -9, None, None, module.Exit_status.ERROR),
  26. (['grep'], -9, 'borg', None, module.Exit_status.ERROR),
  27. (['borg'], -9, 'borg', None, module.Exit_status.ERROR),
  28. (['borg1'], -9, 'borg1', None, module.Exit_status.ERROR),
  29. (['borg'], None, None, None, module.Exit_status.STILL_RUNNING),
  30. (['borg'], 1, 'borg', [], module.Exit_status.WARNING),
  31. (['borg'], 1, 'borg', [{}], module.Exit_status.WARNING),
  32. (['borg'], 1, 'borg', [{'code': 1}], module.Exit_status.WARNING),
  33. (['grep'], 1, 'borg', [{'code': 100, 'treat_as': 'error'}], module.Exit_status.ERROR),
  34. (['borg'], 1, 'borg', [{'code': 100, 'treat_as': 'error'}], module.Exit_status.WARNING),
  35. (['borg'], 1, 'borg', [{'code': 1, 'treat_as': 'error'}], module.Exit_status.ERROR),
  36. (['borg'], 2, 'borg', [{'code': 99, 'treat_as': 'warning'}], module.Exit_status.ERROR),
  37. (['borg'], 2, 'borg', [{'code': 2, 'treat_as': 'warning'}], module.Exit_status.WARNING),
  38. (['borg'], 100, 'borg', [{'code': 1, 'treat_as': 'error'}], module.Exit_status.WARNING),
  39. (['borg'], 100, 'borg', [{'code': 100, 'treat_as': 'error'}], module.Exit_status.ERROR),
  40. ),
  41. )
  42. def test_interpret_exit_code_respects_exit_code_and_borg_local_path(
  43. command, exit_code, borg_local_path, borg_exit_codes, expected_result
  44. ):
  45. assert (
  46. module.interpret_exit_code(command, exit_code, borg_local_path, borg_exit_codes)
  47. is expected_result
  48. )
  49. def test_command_for_process_converts_sequence_command_to_string():
  50. process = flexmock(args=['foo', 'bar', 'baz'])
  51. assert module.command_for_process(process) == 'foo bar baz'
  52. def test_command_for_process_passes_through_string_command():
  53. process = flexmock(args='foo bar baz')
  54. assert module.command_for_process(process) == 'foo bar baz'
  55. def test_output_buffer_for_process_returns_stderr_when_stdout_excluded():
  56. stdout = flexmock()
  57. stderr = flexmock()
  58. process = flexmock(stdout=stdout, stderr=stderr)
  59. assert module.output_buffer_for_process(process, exclude_stdouts=[flexmock(), stdout]) == stderr
  60. def test_output_buffer_for_process_returns_stdout_when_not_excluded():
  61. stdout = flexmock()
  62. process = flexmock(stdout=stdout)
  63. assert (
  64. module.output_buffer_for_process(process, exclude_stdouts=[flexmock(), flexmock()])
  65. == stdout
  66. )
  67. def test_append_last_lines_under_max_line_count_appends():
  68. last_lines = ['last']
  69. flexmock(module.logger).should_receive('log').once()
  70. module.append_last_lines(
  71. last_lines, captured_output=flexmock(), line='line', output_log_level=flexmock()
  72. )
  73. assert last_lines == ['last', 'line']
  74. def test_append_last_lines_over_max_line_count_trims_and_appends():
  75. original_last_lines = [str(number) for number in range(0, module.ERROR_OUTPUT_MAX_LINE_COUNT)]
  76. last_lines = list(original_last_lines)
  77. flexmock(module.logger).should_receive('log').once()
  78. module.append_last_lines(
  79. last_lines, captured_output=flexmock(), line='line', output_log_level=flexmock()
  80. )
  81. assert last_lines == original_last_lines[1:] + ['line']
  82. def test_append_last_lines_with_output_log_level_none_appends_captured_output():
  83. last_lines = ['last']
  84. captured_output = ['captured']
  85. flexmock(module.logger).should_receive('log').never()
  86. module.append_last_lines(
  87. last_lines, captured_output=captured_output, line='line', output_log_level=None
  88. )
  89. assert captured_output == ['captured', 'line']
  90. def test_mask_command_secrets_masks_password_flag_value():
  91. assert module.mask_command_secrets(('cooldb', '--username', 'bob', '--password', 'pass')) == (
  92. 'cooldb',
  93. '--username',
  94. 'bob',
  95. '--password',
  96. '***',
  97. )
  98. def test_mask_command_secrets_passes_through_other_commands():
  99. assert module.mask_command_secrets(('cooldb', '--username', 'bob')) == (
  100. 'cooldb',
  101. '--username',
  102. 'bob',
  103. )
  104. @pytest.mark.parametrize(
  105. 'full_command,input_file,output_file,environment,expected_result',
  106. (
  107. (('foo', 'bar'), None, None, None, 'foo bar'),
  108. (('foo', 'bar'), flexmock(name='input'), None, None, 'foo bar < input'),
  109. (('foo', 'bar'), None, flexmock(name='output'), None, 'foo bar > output'),
  110. (
  111. ('A',) * module.MAX_LOGGED_COMMAND_LENGTH,
  112. None,
  113. None,
  114. None,
  115. 'A ' * (module.MAX_LOGGED_COMMAND_LENGTH // 2 - 2) + '...',
  116. ),
  117. (
  118. ('foo', 'bar'),
  119. flexmock(name='input'),
  120. flexmock(name='output'),
  121. None,
  122. 'foo bar < input > output',
  123. ),
  124. (
  125. ('foo', 'bar'),
  126. None,
  127. None,
  128. {'DBPASS': 'secret', 'OTHER': 'thing'},
  129. 'DBPASS=*** OTHER=*** foo bar',
  130. ),
  131. ),
  132. )
  133. def test_log_command_logs_command_constructed_from_arguments(
  134. full_command, input_file, output_file, environment, expected_result
  135. ):
  136. flexmock(module).should_receive('mask_command_secrets').replace_with(lambda command: command)
  137. flexmock(module.logger).should_receive('debug').with_args(expected_result).once()
  138. module.log_command(full_command, input_file, output_file, environment)
  139. def test_execute_command_calls_full_command():
  140. full_command = ['foo', 'bar']
  141. flexmock(module).should_receive('log_command')
  142. flexmock(module.os, environ={'a': 'b'})
  143. flexmock(module.subprocess).should_receive('Popen').with_args(
  144. full_command,
  145. stdin=None,
  146. stdout=module.subprocess.PIPE,
  147. stderr=module.subprocess.STDOUT,
  148. shell=False,
  149. env=None,
  150. cwd=None,
  151. close_fds=True,
  152. ).and_return(flexmock(stdout=None)).once()
  153. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  154. flexmock(module).should_receive('log_outputs')
  155. output = module.execute_command(full_command)
  156. assert output is None
  157. def test_execute_command_calls_full_command_with_output_file():
  158. full_command = ['foo', 'bar']
  159. output_file = flexmock(name='test')
  160. flexmock(module).should_receive('log_command')
  161. flexmock(module.os, environ={'a': 'b'})
  162. flexmock(module.subprocess).should_receive('Popen').with_args(
  163. full_command,
  164. stdin=None,
  165. stdout=output_file,
  166. stderr=module.subprocess.PIPE,
  167. shell=False,
  168. env=None,
  169. cwd=None,
  170. close_fds=True,
  171. ).and_return(flexmock(stderr=None)).once()
  172. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  173. flexmock(module).should_receive('log_outputs')
  174. output = module.execute_command(full_command, output_file=output_file)
  175. assert output is None
  176. def test_execute_command_calls_full_command_without_capturing_output():
  177. full_command = ['foo', 'bar']
  178. flexmock(module).should_receive('log_command')
  179. flexmock(module.os, environ={'a': 'b'})
  180. flexmock(module.subprocess).should_receive('Popen').with_args(
  181. full_command,
  182. stdin=None,
  183. stdout=None,
  184. stderr=None,
  185. shell=False,
  186. env=None,
  187. cwd=None,
  188. close_fds=True,
  189. ).and_return(flexmock(wait=lambda: 0)).once()
  190. flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.SUCCESS)
  191. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  192. flexmock(module).should_receive('log_outputs')
  193. output = module.execute_command(full_command, output_file=module.DO_NOT_CAPTURE)
  194. assert output is None
  195. def test_execute_command_calls_full_command_with_input_file():
  196. full_command = ['foo', 'bar']
  197. input_file = flexmock(name='test')
  198. flexmock(module).should_receive('log_command')
  199. flexmock(module.os, environ={'a': 'b'})
  200. flexmock(module.subprocess).should_receive('Popen').with_args(
  201. full_command,
  202. stdin=input_file,
  203. stdout=module.subprocess.PIPE,
  204. stderr=module.subprocess.STDOUT,
  205. shell=False,
  206. env=None,
  207. cwd=None,
  208. close_fds=True,
  209. ).and_return(flexmock(stdout=None)).once()
  210. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  211. flexmock(module).should_receive('log_outputs')
  212. output = module.execute_command(full_command, input_file=input_file)
  213. assert output is None
  214. def test_execute_command_calls_full_command_with_shell():
  215. full_command = ['foo', 'bar']
  216. flexmock(module).should_receive('log_command')
  217. flexmock(module.os, environ={'a': 'b'})
  218. flexmock(module.subprocess).should_receive('Popen').with_args(
  219. ' '.join(full_command),
  220. stdin=None,
  221. stdout=module.subprocess.PIPE,
  222. stderr=module.subprocess.STDOUT,
  223. shell=True,
  224. env=None,
  225. cwd=None,
  226. close_fds=True,
  227. ).and_return(flexmock(stdout=None)).once()
  228. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  229. flexmock(module).should_receive('log_outputs')
  230. output = module.execute_command(full_command, shell=True)
  231. assert output is None
  232. def test_execute_command_calls_full_command_with_extra_environment():
  233. full_command = ['foo', 'bar']
  234. flexmock(module).should_receive('log_command')
  235. flexmock(module.os, environ={'a': 'b'})
  236. flexmock(module.subprocess).should_receive('Popen').with_args(
  237. full_command,
  238. stdin=None,
  239. stdout=module.subprocess.PIPE,
  240. stderr=module.subprocess.STDOUT,
  241. shell=False,
  242. env={'a': 'b', 'c': 'd'},
  243. cwd=None,
  244. close_fds=True,
  245. ).and_return(flexmock(stdout=None)).once()
  246. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  247. flexmock(module).should_receive('log_outputs')
  248. output = module.execute_command(full_command, extra_environment={'c': 'd'})
  249. assert output is None
  250. def test_execute_command_calls_full_command_with_working_directory():
  251. full_command = ['foo', 'bar']
  252. flexmock(module).should_receive('log_command')
  253. flexmock(module.os, environ={'a': 'b'})
  254. flexmock(module.subprocess).should_receive('Popen').with_args(
  255. full_command,
  256. stdin=None,
  257. stdout=module.subprocess.PIPE,
  258. stderr=module.subprocess.STDOUT,
  259. shell=False,
  260. env=None,
  261. cwd='/working',
  262. close_fds=True,
  263. ).and_return(flexmock(stdout=None)).once()
  264. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  265. flexmock(module).should_receive('log_outputs')
  266. output = module.execute_command(full_command, working_directory='/working')
  267. assert output is None
  268. def test_execute_command_with_BORG_PASSPHRASE_FD_leaves_file_descriptors_open():
  269. full_command = ['foo', 'bar']
  270. flexmock(module).should_receive('log_command')
  271. flexmock(module.os, environ={'a': 'b'})
  272. flexmock(module.subprocess).should_receive('Popen').with_args(
  273. full_command,
  274. stdin=None,
  275. stdout=module.subprocess.PIPE,
  276. stderr=module.subprocess.STDOUT,
  277. shell=False,
  278. env={'a': 'b', 'BORG_PASSPHRASE_FD': '4'},
  279. cwd=None,
  280. close_fds=False,
  281. ).and_return(flexmock(stdout=None)).once()
  282. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  283. flexmock(module).should_receive('log_outputs')
  284. output = module.execute_command(full_command, extra_environment={'BORG_PASSPHRASE_FD': '4'})
  285. assert output is None
  286. def test_execute_command_without_run_to_completion_returns_process():
  287. full_command = ['foo', 'bar']
  288. process = flexmock()
  289. flexmock(module).should_receive('log_command')
  290. flexmock(module.os, environ={'a': 'b'})
  291. flexmock(module.subprocess).should_receive('Popen').with_args(
  292. full_command,
  293. stdin=None,
  294. stdout=module.subprocess.PIPE,
  295. stderr=module.subprocess.STDOUT,
  296. shell=False,
  297. env=None,
  298. cwd=None,
  299. close_fds=True,
  300. ).and_return(process).once()
  301. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  302. flexmock(module).should_receive('log_outputs')
  303. assert module.execute_command(full_command, run_to_completion=False) == process
  304. def test_execute_command_and_capture_output_returns_stdout():
  305. full_command = ['foo', 'bar']
  306. expected_output = '[]'
  307. flexmock(module).should_receive('log_command')
  308. flexmock(module.os, environ={'a': 'b'})
  309. flexmock(module.subprocess).should_receive('check_output').with_args(
  310. full_command,
  311. stderr=None,
  312. shell=False,
  313. env=None,
  314. cwd=None,
  315. close_fds=True,
  316. ).and_return(flexmock(decode=lambda: expected_output)).once()
  317. output = module.execute_command_and_capture_output(full_command)
  318. assert output == expected_output
  319. def test_execute_command_and_capture_output_with_capture_stderr_returns_stderr():
  320. full_command = ['foo', 'bar']
  321. expected_output = '[]'
  322. flexmock(module).should_receive('log_command')
  323. flexmock(module.os, environ={'a': 'b'})
  324. flexmock(module.subprocess).should_receive('check_output').with_args(
  325. full_command,
  326. stderr=module.subprocess.STDOUT,
  327. shell=False,
  328. env=None,
  329. cwd=None,
  330. close_fds=True,
  331. ).and_return(flexmock(decode=lambda: expected_output)).once()
  332. output = module.execute_command_and_capture_output(full_command, capture_stderr=True)
  333. assert output == expected_output
  334. def test_execute_command_and_capture_output_returns_output_when_process_error_is_not_considered_an_error():
  335. full_command = ['foo', 'bar']
  336. expected_output = '[]'
  337. err_output = b'[]'
  338. flexmock(module).should_receive('log_command')
  339. flexmock(module.os, environ={'a': 'b'})
  340. flexmock(module.subprocess).should_receive('check_output').with_args(
  341. full_command,
  342. stderr=None,
  343. shell=False,
  344. env=None,
  345. cwd=None,
  346. close_fds=True,
  347. ).and_raise(subprocess.CalledProcessError(1, full_command, err_output)).once()
  348. flexmock(module).should_receive('interpret_exit_code').and_return(
  349. module.Exit_status.SUCCESS
  350. ).once()
  351. output = module.execute_command_and_capture_output(full_command)
  352. assert output == expected_output
  353. def test_execute_command_and_capture_output_raises_when_command_errors():
  354. full_command = ['foo', 'bar']
  355. expected_output = '[]'
  356. flexmock(module).should_receive('log_command')
  357. flexmock(module.os, environ={'a': 'b'})
  358. flexmock(module.subprocess).should_receive('check_output').with_args(
  359. full_command,
  360. stderr=None,
  361. shell=False,
  362. env=None,
  363. cwd=None,
  364. close_fds=True,
  365. ).and_raise(subprocess.CalledProcessError(2, full_command, expected_output)).once()
  366. flexmock(module).should_receive('interpret_exit_code').and_return(
  367. module.Exit_status.ERROR
  368. ).once()
  369. with pytest.raises(subprocess.CalledProcessError):
  370. module.execute_command_and_capture_output(full_command)
  371. def test_execute_command_and_capture_output_returns_output_with_shell():
  372. full_command = ['foo', 'bar']
  373. expected_output = '[]'
  374. flexmock(module).should_receive('log_command')
  375. flexmock(module.os, environ={'a': 'b'})
  376. flexmock(module.subprocess).should_receive('check_output').with_args(
  377. 'foo bar',
  378. stderr=None,
  379. shell=True,
  380. env=None,
  381. cwd=None,
  382. close_fds=True,
  383. ).and_return(flexmock(decode=lambda: expected_output)).once()
  384. output = module.execute_command_and_capture_output(full_command, shell=True)
  385. assert output == expected_output
  386. def test_execute_command_and_capture_output_returns_output_with_extra_environment():
  387. full_command = ['foo', 'bar']
  388. expected_output = '[]'
  389. flexmock(module).should_receive('log_command')
  390. flexmock(module.os, environ={'a': 'b'})
  391. flexmock(module.subprocess).should_receive('check_output').with_args(
  392. full_command,
  393. stderr=None,
  394. shell=False,
  395. env={'a': 'b', 'c': 'd'},
  396. cwd=None,
  397. close_fds=True,
  398. ).and_return(flexmock(decode=lambda: expected_output)).once()
  399. output = module.execute_command_and_capture_output(
  400. full_command, shell=False, extra_environment={'c': 'd'}
  401. )
  402. assert output == expected_output
  403. def test_execute_command_and_capture_output_returns_output_with_working_directory():
  404. full_command = ['foo', 'bar']
  405. expected_output = '[]'
  406. flexmock(module).should_receive('log_command')
  407. flexmock(module.os, environ={'a': 'b'})
  408. flexmock(module.subprocess).should_receive('check_output').with_args(
  409. full_command,
  410. stderr=None,
  411. shell=False,
  412. env=None,
  413. cwd='/working',
  414. close_fds=True,
  415. ).and_return(flexmock(decode=lambda: expected_output)).once()
  416. output = module.execute_command_and_capture_output(
  417. full_command, shell=False, working_directory='/working'
  418. )
  419. assert output == expected_output
  420. def test_execute_command_and_capture_output_with_BORG_PASSPHRASE_FD_leaves_file_descriptors_open():
  421. full_command = ['foo', 'bar']
  422. expected_output = '[]'
  423. flexmock(module).should_receive('log_command')
  424. flexmock(module.os, environ={'a': 'b'})
  425. flexmock(module.subprocess).should_receive('check_output').with_args(
  426. full_command,
  427. stderr=None,
  428. shell=False,
  429. env={'a': 'b', 'BORG_PASSPHRASE_FD': '4'},
  430. cwd=None,
  431. close_fds=False,
  432. ).and_return(flexmock(decode=lambda: expected_output)).once()
  433. output = module.execute_command_and_capture_output(
  434. full_command,
  435. shell=False,
  436. extra_environment={'BORG_PASSPHRASE_FD': '4'},
  437. )
  438. assert output == expected_output
  439. def test_execute_command_with_processes_calls_full_command():
  440. full_command = ['foo', 'bar']
  441. processes = (flexmock(),)
  442. flexmock(module).should_receive('log_command')
  443. flexmock(module.os, environ={'a': 'b'})
  444. flexmock(module.subprocess).should_receive('Popen').with_args(
  445. full_command,
  446. stdin=None,
  447. stdout=module.subprocess.PIPE,
  448. stderr=module.subprocess.STDOUT,
  449. shell=False,
  450. env=None,
  451. cwd=None,
  452. close_fds=True,
  453. ).and_return(flexmock(stdout=None)).once()
  454. flexmock(module).should_receive('log_outputs')
  455. output = module.execute_command_with_processes(full_command, processes)
  456. assert output is None
  457. def test_execute_command_with_processes_returns_output_with_output_log_level_none():
  458. full_command = ['foo', 'bar']
  459. processes = (flexmock(),)
  460. flexmock(module).should_receive('log_command')
  461. flexmock(module.os, environ={'a': 'b'})
  462. process = flexmock(stdout=None)
  463. flexmock(module.subprocess).should_receive('Popen').with_args(
  464. full_command,
  465. stdin=None,
  466. stdout=module.subprocess.PIPE,
  467. stderr=module.subprocess.STDOUT,
  468. shell=False,
  469. env=None,
  470. cwd=None,
  471. close_fds=True,
  472. ).and_return(process).once()
  473. flexmock(module).should_receive('log_outputs').and_return({process: 'out'})
  474. output = module.execute_command_with_processes(full_command, processes, output_log_level=None)
  475. assert output == 'out'
  476. def test_execute_command_with_processes_calls_full_command_with_output_file():
  477. full_command = ['foo', 'bar']
  478. processes = (flexmock(),)
  479. output_file = flexmock(name='test')
  480. flexmock(module).should_receive('log_command')
  481. flexmock(module.os, environ={'a': 'b'})
  482. flexmock(module.subprocess).should_receive('Popen').with_args(
  483. full_command,
  484. stdin=None,
  485. stdout=output_file,
  486. stderr=module.subprocess.PIPE,
  487. shell=False,
  488. env=None,
  489. cwd=None,
  490. close_fds=True,
  491. ).and_return(flexmock(stderr=None)).once()
  492. flexmock(module).should_receive('log_outputs')
  493. output = module.execute_command_with_processes(full_command, processes, output_file=output_file)
  494. assert output is None
  495. def test_execute_command_with_processes_calls_full_command_without_capturing_output():
  496. full_command = ['foo', 'bar']
  497. processes = (flexmock(),)
  498. flexmock(module).should_receive('log_command')
  499. flexmock(module.os, environ={'a': 'b'})
  500. flexmock(module.subprocess).should_receive('Popen').with_args(
  501. full_command,
  502. stdin=None,
  503. stdout=None,
  504. stderr=None,
  505. shell=False,
  506. env=None,
  507. cwd=None,
  508. close_fds=True,
  509. ).and_return(flexmock(wait=lambda: 0)).once()
  510. flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.SUCCESS)
  511. flexmock(module).should_receive('log_outputs')
  512. output = module.execute_command_with_processes(
  513. full_command, processes, output_file=module.DO_NOT_CAPTURE
  514. )
  515. assert output is None
  516. def test_execute_command_with_processes_calls_full_command_with_input_file():
  517. full_command = ['foo', 'bar']
  518. processes = (flexmock(),)
  519. input_file = flexmock(name='test')
  520. flexmock(module).should_receive('log_command')
  521. flexmock(module.os, environ={'a': 'b'})
  522. flexmock(module.subprocess).should_receive('Popen').with_args(
  523. full_command,
  524. stdin=input_file,
  525. stdout=module.subprocess.PIPE,
  526. stderr=module.subprocess.STDOUT,
  527. shell=False,
  528. env=None,
  529. cwd=None,
  530. close_fds=True,
  531. ).and_return(flexmock(stdout=None)).once()
  532. flexmock(module).should_receive('log_outputs')
  533. output = module.execute_command_with_processes(full_command, processes, input_file=input_file)
  534. assert output is None
  535. def test_execute_command_with_processes_calls_full_command_with_shell():
  536. full_command = ['foo', 'bar']
  537. processes = (flexmock(),)
  538. flexmock(module).should_receive('log_command')
  539. flexmock(module.os, environ={'a': 'b'})
  540. flexmock(module.subprocess).should_receive('Popen').with_args(
  541. ' '.join(full_command),
  542. stdin=None,
  543. stdout=module.subprocess.PIPE,
  544. stderr=module.subprocess.STDOUT,
  545. shell=True,
  546. env=None,
  547. cwd=None,
  548. close_fds=True,
  549. ).and_return(flexmock(stdout=None)).once()
  550. flexmock(module).should_receive('log_outputs')
  551. output = module.execute_command_with_processes(full_command, processes, shell=True)
  552. assert output is None
  553. def test_execute_command_with_processes_calls_full_command_with_extra_environment():
  554. full_command = ['foo', 'bar']
  555. processes = (flexmock(),)
  556. flexmock(module).should_receive('log_command')
  557. flexmock(module.os, environ={'a': 'b'})
  558. flexmock(module.subprocess).should_receive('Popen').with_args(
  559. full_command,
  560. stdin=None,
  561. stdout=module.subprocess.PIPE,
  562. stderr=module.subprocess.STDOUT,
  563. shell=False,
  564. env={'a': 'b', 'c': 'd'},
  565. cwd=None,
  566. close_fds=True,
  567. ).and_return(flexmock(stdout=None)).once()
  568. flexmock(module).should_receive('log_outputs')
  569. output = module.execute_command_with_processes(
  570. full_command, processes, extra_environment={'c': 'd'}
  571. )
  572. assert output is None
  573. def test_execute_command_with_processes_calls_full_command_with_working_directory():
  574. full_command = ['foo', 'bar']
  575. processes = (flexmock(),)
  576. flexmock(module).should_receive('log_command')
  577. flexmock(module.os, environ={'a': 'b'})
  578. flexmock(module.subprocess).should_receive('Popen').with_args(
  579. full_command,
  580. stdin=None,
  581. stdout=module.subprocess.PIPE,
  582. stderr=module.subprocess.STDOUT,
  583. shell=False,
  584. env=None,
  585. cwd='/working',
  586. close_fds=True,
  587. ).and_return(flexmock(stdout=None)).once()
  588. flexmock(module).should_receive('log_outputs')
  589. output = module.execute_command_with_processes(
  590. full_command, processes, working_directory='/working'
  591. )
  592. assert output is None
  593. def test_execute_command_with_processes_with_BORG_PASSPHRASE_FD_leaves_file_descriptors_open():
  594. full_command = ['foo', 'bar']
  595. processes = (flexmock(),)
  596. flexmock(module).should_receive('log_command')
  597. flexmock(module.os, environ={'a': 'b'})
  598. flexmock(module.subprocess).should_receive('Popen').with_args(
  599. full_command,
  600. stdin=None,
  601. stdout=module.subprocess.PIPE,
  602. stderr=module.subprocess.STDOUT,
  603. shell=False,
  604. env={'a': 'b', 'BORG_PASSPHRASE_FD': '4'},
  605. cwd=None,
  606. close_fds=False,
  607. ).and_return(flexmock(stdout=None)).once()
  608. flexmock(module).should_receive('log_outputs')
  609. output = module.execute_command_with_processes(
  610. full_command,
  611. processes,
  612. extra_environment={'BORG_PASSPHRASE_FD': '4'},
  613. )
  614. assert output is None
  615. def test_execute_command_with_processes_kills_processes_on_error():
  616. full_command = ['foo', 'bar']
  617. flexmock(module).should_receive('log_command')
  618. process = flexmock(stdout=flexmock(read=lambda count: None))
  619. process.should_receive('poll')
  620. process.should_receive('kill').once()
  621. processes = (process,)
  622. flexmock(module.os, environ={'a': 'b'})
  623. flexmock(module.subprocess).should_receive('Popen').with_args(
  624. full_command,
  625. stdin=None,
  626. stdout=module.subprocess.PIPE,
  627. stderr=module.subprocess.STDOUT,
  628. shell=False,
  629. env=None,
  630. cwd=None,
  631. close_fds=True,
  632. ).and_raise(subprocess.CalledProcessError(1, full_command, 'error')).once()
  633. flexmock(module).should_receive('log_outputs').never()
  634. with pytest.raises(subprocess.CalledProcessError):
  635. module.execute_command_with_processes(full_command, processes)