test_execute.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  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. {'UNKNOWN': 'secret', 'OTHER': 'thing'},
  129. 'foo bar',
  130. ),
  131. (
  132. ('foo', 'bar'),
  133. None,
  134. None,
  135. {'PGTHING': 'secret', 'BORG_OTHER': 'thing'},
  136. 'PGTHING=*** BORG_OTHER=*** foo bar',
  137. ),
  138. ),
  139. )
  140. def test_log_command_logs_command_constructed_from_arguments(
  141. full_command, input_file, output_file, environment, expected_result
  142. ):
  143. flexmock(module).should_receive('mask_command_secrets').replace_with(lambda command: command)
  144. flexmock(module.logger).should_receive('debug').with_args(expected_result).once()
  145. module.log_command(full_command, input_file, output_file, environment)
  146. def test_execute_command_calls_full_command():
  147. full_command = ['foo', 'bar']
  148. flexmock(module).should_receive('log_command')
  149. flexmock(module.subprocess).should_receive('Popen').with_args(
  150. full_command,
  151. stdin=None,
  152. stdout=module.subprocess.PIPE,
  153. stderr=module.subprocess.STDOUT,
  154. shell=False,
  155. env=None,
  156. cwd=None,
  157. close_fds=True,
  158. ).and_return(flexmock(stdout=None)).once()
  159. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  160. flexmock(module).should_receive('log_outputs')
  161. output = module.execute_command(full_command)
  162. assert output is None
  163. def test_execute_command_calls_full_command_with_output_file():
  164. full_command = ['foo', 'bar']
  165. output_file = flexmock(name='test')
  166. flexmock(module).should_receive('log_command')
  167. flexmock(module.subprocess).should_receive('Popen').with_args(
  168. full_command,
  169. stdin=None,
  170. stdout=output_file,
  171. stderr=module.subprocess.PIPE,
  172. shell=False,
  173. env=None,
  174. cwd=None,
  175. close_fds=True,
  176. ).and_return(flexmock(stderr=None)).once()
  177. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  178. flexmock(module).should_receive('log_outputs')
  179. output = module.execute_command(full_command, output_file=output_file)
  180. assert output is None
  181. def test_execute_command_calls_full_command_without_capturing_output():
  182. full_command = ['foo', 'bar']
  183. flexmock(module).should_receive('log_command')
  184. flexmock(module.subprocess).should_receive('Popen').with_args(
  185. full_command,
  186. stdin=None,
  187. stdout=None,
  188. stderr=None,
  189. shell=False,
  190. env=None,
  191. cwd=None,
  192. close_fds=True,
  193. ).and_return(flexmock(wait=lambda: 0)).once()
  194. flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.SUCCESS)
  195. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  196. flexmock(module).should_receive('log_outputs')
  197. output = module.execute_command(full_command, output_file=module.DO_NOT_CAPTURE)
  198. assert output is None
  199. def test_execute_command_calls_full_command_with_input_file():
  200. full_command = ['foo', 'bar']
  201. input_file = flexmock(name='test')
  202. flexmock(module).should_receive('log_command')
  203. flexmock(module.subprocess).should_receive('Popen').with_args(
  204. full_command,
  205. stdin=input_file,
  206. stdout=module.subprocess.PIPE,
  207. stderr=module.subprocess.STDOUT,
  208. shell=False,
  209. env=None,
  210. cwd=None,
  211. close_fds=True,
  212. ).and_return(flexmock(stdout=None)).once()
  213. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  214. flexmock(module).should_receive('log_outputs')
  215. output = module.execute_command(full_command, input_file=input_file)
  216. assert output is None
  217. def test_execute_command_calls_full_command_with_shell():
  218. full_command = ['foo', 'bar']
  219. flexmock(module).should_receive('log_command')
  220. flexmock(module.subprocess).should_receive('Popen').with_args(
  221. ' '.join(full_command),
  222. stdin=None,
  223. stdout=module.subprocess.PIPE,
  224. stderr=module.subprocess.STDOUT,
  225. shell=True,
  226. env=None,
  227. cwd=None,
  228. close_fds=True,
  229. ).and_return(flexmock(stdout=None)).once()
  230. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  231. flexmock(module).should_receive('log_outputs')
  232. output = module.execute_command(full_command, shell=True)
  233. assert output is None
  234. def test_execute_command_calls_full_command_with_environment():
  235. full_command = ['foo', 'bar']
  236. flexmock(module).should_receive('log_command')
  237. flexmock(module.subprocess).should_receive('Popen').with_args(
  238. full_command,
  239. stdin=None,
  240. stdout=module.subprocess.PIPE,
  241. stderr=module.subprocess.STDOUT,
  242. shell=False,
  243. env={'a': 'b'},
  244. cwd=None,
  245. close_fds=True,
  246. ).and_return(flexmock(stdout=None)).once()
  247. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  248. flexmock(module).should_receive('log_outputs')
  249. output = module.execute_command(full_command, environment={'a': 'b'})
  250. assert output is None
  251. def test_execute_command_calls_full_command_with_working_directory():
  252. full_command = ['foo', 'bar']
  253. flexmock(module).should_receive('log_command')
  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.subprocess).should_receive('Popen').with_args(
  272. full_command,
  273. stdin=None,
  274. stdout=module.subprocess.PIPE,
  275. stderr=module.subprocess.STDOUT,
  276. shell=False,
  277. env={'BORG_PASSPHRASE_FD': '4'},
  278. cwd=None,
  279. close_fds=False,
  280. ).and_return(flexmock(stdout=None)).once()
  281. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  282. flexmock(module).should_receive('log_outputs')
  283. output = module.execute_command(full_command, environment={'BORG_PASSPHRASE_FD': '4'})
  284. assert output is None
  285. def test_execute_command_without_run_to_completion_returns_process():
  286. full_command = ['foo', 'bar']
  287. process = flexmock()
  288. flexmock(module).should_receive('log_command')
  289. flexmock(module.subprocess).should_receive('Popen').with_args(
  290. full_command,
  291. stdin=None,
  292. stdout=module.subprocess.PIPE,
  293. stderr=module.subprocess.STDOUT,
  294. shell=False,
  295. env=None,
  296. cwd=None,
  297. close_fds=True,
  298. ).and_return(process).once()
  299. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  300. flexmock(module).should_receive('log_outputs')
  301. assert module.execute_command(full_command, run_to_completion=False) == process
  302. def test_execute_command_and_capture_output_returns_stdout():
  303. full_command = ['foo', 'bar']
  304. expected_output = '[]'
  305. flexmock(module).should_receive('log_command')
  306. flexmock(module.subprocess).should_receive('check_output').with_args(
  307. full_command,
  308. stderr=None,
  309. shell=False,
  310. env=None,
  311. cwd=None,
  312. close_fds=True,
  313. ).and_return(flexmock(decode=lambda: expected_output)).once()
  314. output = module.execute_command_and_capture_output(full_command)
  315. assert output == expected_output
  316. def test_execute_command_and_capture_output_with_capture_stderr_returns_stderr():
  317. full_command = ['foo', 'bar']
  318. expected_output = '[]'
  319. flexmock(module).should_receive('log_command')
  320. flexmock(module.subprocess).should_receive('check_output').with_args(
  321. full_command,
  322. stderr=module.subprocess.STDOUT,
  323. shell=False,
  324. env=None,
  325. cwd=None,
  326. close_fds=True,
  327. ).and_return(flexmock(decode=lambda: expected_output)).once()
  328. output = module.execute_command_and_capture_output(full_command, capture_stderr=True)
  329. assert output == expected_output
  330. def test_execute_command_and_capture_output_returns_output_when_process_error_is_not_considered_an_error():
  331. full_command = ['foo', 'bar']
  332. expected_output = '[]'
  333. err_output = b'[]'
  334. flexmock(module).should_receive('log_command')
  335. flexmock(module.subprocess).should_receive('check_output').with_args(
  336. full_command,
  337. stderr=None,
  338. shell=False,
  339. env=None,
  340. cwd=None,
  341. close_fds=True,
  342. ).and_raise(subprocess.CalledProcessError(1, full_command, err_output)).once()
  343. flexmock(module).should_receive('interpret_exit_code').and_return(
  344. module.Exit_status.SUCCESS
  345. ).once()
  346. output = module.execute_command_and_capture_output(full_command)
  347. assert output == expected_output
  348. def test_execute_command_and_capture_output_raises_when_command_errors():
  349. full_command = ['foo', 'bar']
  350. expected_output = '[]'
  351. flexmock(module).should_receive('log_command')
  352. flexmock(module.subprocess).should_receive('check_output').with_args(
  353. full_command,
  354. stderr=None,
  355. shell=False,
  356. env=None,
  357. cwd=None,
  358. close_fds=True,
  359. ).and_raise(subprocess.CalledProcessError(2, full_command, expected_output)).once()
  360. flexmock(module).should_receive('interpret_exit_code').and_return(
  361. module.Exit_status.ERROR
  362. ).once()
  363. with pytest.raises(subprocess.CalledProcessError):
  364. module.execute_command_and_capture_output(full_command)
  365. def test_execute_command_and_capture_output_returns_output_with_shell():
  366. full_command = ['foo', 'bar']
  367. expected_output = '[]'
  368. flexmock(module).should_receive('log_command')
  369. flexmock(module.subprocess).should_receive('check_output').with_args(
  370. 'foo bar',
  371. stderr=None,
  372. shell=True,
  373. env=None,
  374. cwd=None,
  375. close_fds=True,
  376. ).and_return(flexmock(decode=lambda: expected_output)).once()
  377. output = module.execute_command_and_capture_output(full_command, shell=True)
  378. assert output == expected_output
  379. def test_execute_command_and_capture_output_returns_output_with_environment():
  380. full_command = ['foo', 'bar']
  381. expected_output = '[]'
  382. flexmock(module).should_receive('log_command')
  383. flexmock(module.subprocess).should_receive('check_output').with_args(
  384. full_command,
  385. stderr=None,
  386. shell=False,
  387. env={'a': 'b'},
  388. cwd=None,
  389. close_fds=True,
  390. ).and_return(flexmock(decode=lambda: expected_output)).once()
  391. output = module.execute_command_and_capture_output(
  392. full_command, shell=False, environment={'a': 'b'}
  393. )
  394. assert output == expected_output
  395. def test_execute_command_and_capture_output_returns_output_with_working_directory():
  396. full_command = ['foo', 'bar']
  397. expected_output = '[]'
  398. flexmock(module).should_receive('log_command')
  399. flexmock(module.subprocess).should_receive('check_output').with_args(
  400. full_command,
  401. stderr=None,
  402. shell=False,
  403. env=None,
  404. cwd='/working',
  405. close_fds=True,
  406. ).and_return(flexmock(decode=lambda: expected_output)).once()
  407. output = module.execute_command_and_capture_output(
  408. full_command, shell=False, working_directory='/working'
  409. )
  410. assert output == expected_output
  411. def test_execute_command_and_capture_output_with_BORG_PASSPHRASE_FD_leaves_file_descriptors_open():
  412. full_command = ['foo', 'bar']
  413. expected_output = '[]'
  414. flexmock(module).should_receive('log_command')
  415. flexmock(module.subprocess).should_receive('check_output').with_args(
  416. full_command,
  417. stderr=None,
  418. shell=False,
  419. env={'BORG_PASSPHRASE_FD': '4'},
  420. cwd=None,
  421. close_fds=False,
  422. ).and_return(flexmock(decode=lambda: expected_output)).once()
  423. output = module.execute_command_and_capture_output(
  424. full_command,
  425. shell=False,
  426. environment={'BORG_PASSPHRASE_FD': '4'},
  427. )
  428. assert output == expected_output
  429. def test_execute_command_with_processes_calls_full_command():
  430. full_command = ['foo', 'bar']
  431. processes = (flexmock(),)
  432. flexmock(module).should_receive('log_command')
  433. flexmock(module.subprocess).should_receive('Popen').with_args(
  434. full_command,
  435. stdin=None,
  436. stdout=module.subprocess.PIPE,
  437. stderr=module.subprocess.STDOUT,
  438. shell=False,
  439. env=None,
  440. cwd=None,
  441. close_fds=True,
  442. ).and_return(flexmock(stdout=None)).once()
  443. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  444. flexmock(module).should_receive('log_outputs')
  445. output = module.execute_command_with_processes(full_command, processes)
  446. assert output is None
  447. def test_execute_command_with_processes_returns_output_with_output_log_level_none():
  448. full_command = ['foo', 'bar']
  449. processes = (flexmock(),)
  450. flexmock(module).should_receive('log_command')
  451. process = flexmock(stdout=None)
  452. flexmock(module.subprocess).should_receive('Popen').with_args(
  453. full_command,
  454. stdin=None,
  455. stdout=module.subprocess.PIPE,
  456. stderr=module.subprocess.STDOUT,
  457. shell=False,
  458. env=None,
  459. cwd=None,
  460. close_fds=True,
  461. ).and_return(process).once()
  462. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  463. flexmock(module).should_receive('log_outputs').and_return({process: 'out'})
  464. output = module.execute_command_with_processes(full_command, processes, output_log_level=None)
  465. assert output == 'out'
  466. def test_execute_command_with_processes_calls_full_command_with_output_file():
  467. full_command = ['foo', 'bar']
  468. processes = (flexmock(),)
  469. output_file = flexmock(name='test')
  470. flexmock(module).should_receive('log_command')
  471. flexmock(module.subprocess).should_receive('Popen').with_args(
  472. full_command,
  473. stdin=None,
  474. stdout=output_file,
  475. stderr=module.subprocess.PIPE,
  476. shell=False,
  477. env=None,
  478. cwd=None,
  479. close_fds=True,
  480. ).and_return(flexmock(stderr=None)).once()
  481. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  482. flexmock(module).should_receive('log_outputs')
  483. output = module.execute_command_with_processes(full_command, processes, output_file=output_file)
  484. assert output is None
  485. def test_execute_command_with_processes_calls_full_command_without_capturing_output():
  486. full_command = ['foo', 'bar']
  487. processes = (flexmock(),)
  488. flexmock(module).should_receive('log_command')
  489. flexmock(module.subprocess).should_receive('Popen').with_args(
  490. full_command,
  491. stdin=None,
  492. stdout=None,
  493. stderr=None,
  494. shell=False,
  495. env=None,
  496. cwd=None,
  497. close_fds=True,
  498. ).and_return(flexmock(wait=lambda: 0)).once()
  499. flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.SUCCESS)
  500. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  501. flexmock(module).should_receive('log_outputs')
  502. output = module.execute_command_with_processes(
  503. full_command, processes, output_file=module.DO_NOT_CAPTURE
  504. )
  505. assert output is None
  506. def test_execute_command_with_processes_calls_full_command_with_input_file():
  507. full_command = ['foo', 'bar']
  508. processes = (flexmock(),)
  509. input_file = flexmock(name='test')
  510. flexmock(module).should_receive('log_command')
  511. flexmock(module.subprocess).should_receive('Popen').with_args(
  512. full_command,
  513. stdin=input_file,
  514. stdout=module.subprocess.PIPE,
  515. stderr=module.subprocess.STDOUT,
  516. shell=False,
  517. env=None,
  518. cwd=None,
  519. close_fds=True,
  520. ).and_return(flexmock(stdout=None)).once()
  521. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  522. flexmock(module).should_receive('log_outputs')
  523. output = module.execute_command_with_processes(full_command, processes, input_file=input_file)
  524. assert output is None
  525. def test_execute_command_with_processes_calls_full_command_with_shell():
  526. full_command = ['foo', 'bar']
  527. processes = (flexmock(),)
  528. flexmock(module).should_receive('log_command')
  529. flexmock(module.subprocess).should_receive('Popen').with_args(
  530. ' '.join(full_command),
  531. stdin=None,
  532. stdout=module.subprocess.PIPE,
  533. stderr=module.subprocess.STDOUT,
  534. shell=True,
  535. env=None,
  536. cwd=None,
  537. close_fds=True,
  538. ).and_return(flexmock(stdout=None)).once()
  539. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  540. flexmock(module).should_receive('log_outputs')
  541. output = module.execute_command_with_processes(full_command, processes, shell=True)
  542. assert output is None
  543. def test_execute_command_with_processes_calls_full_command_with_environment():
  544. full_command = ['foo', 'bar']
  545. processes = (flexmock(),)
  546. flexmock(module).should_receive('log_command')
  547. flexmock(module.subprocess).should_receive('Popen').with_args(
  548. full_command,
  549. stdin=None,
  550. stdout=module.subprocess.PIPE,
  551. stderr=module.subprocess.STDOUT,
  552. shell=False,
  553. env={'a': 'b'},
  554. cwd=None,
  555. close_fds=True,
  556. ).and_return(flexmock(stdout=None)).once()
  557. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  558. flexmock(module).should_receive('log_outputs')
  559. output = module.execute_command_with_processes(full_command, processes, environment={'a': 'b'})
  560. assert output is None
  561. def test_execute_command_with_processes_calls_full_command_with_working_directory():
  562. full_command = ['foo', 'bar']
  563. processes = (flexmock(),)
  564. flexmock(module).should_receive('log_command')
  565. flexmock(module.subprocess).should_receive('Popen').with_args(
  566. full_command,
  567. stdin=None,
  568. stdout=module.subprocess.PIPE,
  569. stderr=module.subprocess.STDOUT,
  570. shell=False,
  571. env=None,
  572. cwd='/working',
  573. close_fds=True,
  574. ).and_return(flexmock(stdout=None)).once()
  575. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  576. flexmock(module).should_receive('log_outputs')
  577. output = module.execute_command_with_processes(
  578. full_command, processes, working_directory='/working'
  579. )
  580. assert output is None
  581. def test_execute_command_with_processes_with_BORG_PASSPHRASE_FD_leaves_file_descriptors_open():
  582. full_command = ['foo', 'bar']
  583. processes = (flexmock(),)
  584. flexmock(module).should_receive('log_command')
  585. flexmock(module.subprocess).should_receive('Popen').with_args(
  586. full_command,
  587. stdin=None,
  588. stdout=module.subprocess.PIPE,
  589. stderr=module.subprocess.STDOUT,
  590. shell=False,
  591. env={'BORG_PASSPHRASE_FD': '4'},
  592. cwd=None,
  593. close_fds=False,
  594. ).and_return(flexmock(stdout=None)).once()
  595. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  596. flexmock(module).should_receive('log_outputs')
  597. output = module.execute_command_with_processes(
  598. full_command,
  599. processes,
  600. environment={'BORG_PASSPHRASE_FD': '4'},
  601. )
  602. assert output is None
  603. def test_execute_command_with_processes_kills_processes_on_error():
  604. full_command = ['foo', 'bar']
  605. flexmock(module).should_receive('log_command')
  606. process = flexmock(stdout=flexmock(read=lambda count: None))
  607. process.should_receive('poll')
  608. process.should_receive('kill').once()
  609. processes = (process,)
  610. flexmock(module.subprocess).should_receive('Popen').with_args(
  611. full_command,
  612. stdin=None,
  613. stdout=module.subprocess.PIPE,
  614. stderr=module.subprocess.STDOUT,
  615. shell=False,
  616. env=None,
  617. cwd=None,
  618. close_fds=True,
  619. ).and_raise(subprocess.CalledProcessError(1, full_command, 'error')).once()
  620. flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
  621. flexmock(module).should_receive('log_outputs').never()
  622. with pytest.raises(subprocess.CalledProcessError):
  623. module.execute_command_with_processes(full_command, processes)