2
0

test_execute.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  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,expected_result',
  7. (
  8. (['grep'], 2, None, True),
  9. (['grep'], 2, 'borg', True),
  10. (['borg'], 2, 'borg', True),
  11. (['borg1'], 2, 'borg1', True),
  12. (['grep'], 1, None, True),
  13. (['grep'], 1, 'borg', True),
  14. (['borg'], 1, 'borg', False),
  15. (['borg1'], 1, 'borg1', False),
  16. (['grep'], 0, None, False),
  17. (['grep'], 0, 'borg', False),
  18. (['borg'], 0, 'borg', False),
  19. (['borg1'], 0, 'borg1', False),
  20. # -9 exit code occurs when child process get SIGKILLed.
  21. (['grep'], -9, None, True),
  22. (['grep'], -9, 'borg', True),
  23. (['borg'], -9, 'borg', True),
  24. (['borg1'], -9, 'borg1', True),
  25. (['borg'], None, None, False),
  26. ),
  27. )
  28. def test_exit_code_indicates_error_respects_exit_code_and_borg_local_path(
  29. command, exit_code, borg_local_path, expected_result
  30. ):
  31. assert module.exit_code_indicates_error(command, exit_code, borg_local_path) is expected_result
  32. def test_command_for_process_converts_sequence_command_to_string():
  33. process = flexmock(args=['foo', 'bar', 'baz'])
  34. assert module.command_for_process(process) == 'foo bar baz'
  35. def test_command_for_process_passes_through_string_command():
  36. process = flexmock(args='foo bar baz')
  37. assert module.command_for_process(process) == 'foo bar baz'
  38. def test_output_buffer_for_process_returns_stderr_when_stdout_excluded():
  39. stdout = flexmock()
  40. stderr = flexmock()
  41. process = flexmock(stdout=stdout, stderr=stderr)
  42. assert module.output_buffer_for_process(process, exclude_stdouts=[flexmock(), stdout]) == stderr
  43. def test_output_buffer_for_process_returns_stdout_when_not_excluded():
  44. stdout = flexmock()
  45. process = flexmock(stdout=stdout)
  46. assert (
  47. module.output_buffer_for_process(process, exclude_stdouts=[flexmock(), flexmock()])
  48. == stdout
  49. )
  50. def test_append_last_lines_under_max_line_count_appends():
  51. last_lines = ['last']
  52. flexmock(module.logger).should_receive('log').once()
  53. module.append_last_lines(
  54. last_lines, captured_output=flexmock(), line='line', output_log_level=flexmock()
  55. )
  56. assert last_lines == ['last', 'line']
  57. def test_append_last_lines_over_max_line_count_trims_and_appends():
  58. original_last_lines = [str(number) for number in range(0, module.ERROR_OUTPUT_MAX_LINE_COUNT)]
  59. last_lines = list(original_last_lines)
  60. flexmock(module.logger).should_receive('log').once()
  61. module.append_last_lines(
  62. last_lines, captured_output=flexmock(), line='line', output_log_level=flexmock()
  63. )
  64. assert last_lines == original_last_lines[1:] + ['line']
  65. def test_append_last_lines_with_output_log_level_none_appends_captured_output():
  66. last_lines = ['last']
  67. captured_output = ['captured']
  68. flexmock(module.logger).should_receive('log').never()
  69. module.append_last_lines(
  70. last_lines, captured_output=captured_output, line='line', output_log_level=None
  71. )
  72. assert captured_output == ['captured', 'line']
  73. @pytest.mark.parametrize(
  74. 'full_command,input_file,output_file,environment,expected_result',
  75. (
  76. (('foo', 'bar'), None, None, None, 'foo bar'),
  77. (('foo', 'bar'), flexmock(name='input'), None, None, 'foo bar < input'),
  78. (('foo', 'bar'), None, flexmock(name='output'), None, 'foo bar > output'),
  79. (
  80. ('foo', 'bar'),
  81. flexmock(name='input'),
  82. flexmock(name='output'),
  83. None,
  84. 'foo bar < input > output',
  85. ),
  86. (
  87. ('foo', 'bar'),
  88. None,
  89. None,
  90. {'DBPASS': 'secret', 'OTHER': 'thing'},
  91. 'DBPASS=*** OTHER=*** foo bar',
  92. ),
  93. ),
  94. )
  95. def test_log_command_logs_command_constructed_from_arguments(
  96. full_command, input_file, output_file, environment, expected_result
  97. ):
  98. flexmock(module.logger).should_receive('debug').with_args(expected_result).once()
  99. module.log_command(full_command, input_file, output_file, environment)
  100. def test_execute_command_calls_full_command():
  101. full_command = ['foo', 'bar']
  102. flexmock(module).should_receive('log_command')
  103. flexmock(module.os, environ={'a': 'b'})
  104. flexmock(module.subprocess).should_receive('Popen').with_args(
  105. full_command,
  106. stdin=None,
  107. stdout=module.subprocess.PIPE,
  108. stderr=module.subprocess.STDOUT,
  109. shell=False,
  110. env=None,
  111. cwd=None,
  112. ).and_return(flexmock(stdout=None)).once()
  113. flexmock(module).should_receive('log_outputs')
  114. output = module.execute_command(full_command)
  115. assert output is None
  116. def test_execute_command_calls_full_command_with_output_file():
  117. full_command = ['foo', 'bar']
  118. output_file = flexmock(name='test')
  119. flexmock(module).should_receive('log_command')
  120. flexmock(module.os, environ={'a': 'b'})
  121. flexmock(module.subprocess).should_receive('Popen').with_args(
  122. full_command,
  123. stdin=None,
  124. stdout=output_file,
  125. stderr=module.subprocess.PIPE,
  126. shell=False,
  127. env=None,
  128. cwd=None,
  129. ).and_return(flexmock(stderr=None)).once()
  130. flexmock(module).should_receive('log_outputs')
  131. output = module.execute_command(full_command, output_file=output_file)
  132. assert output is None
  133. def test_execute_command_calls_full_command_without_capturing_output():
  134. full_command = ['foo', 'bar']
  135. flexmock(module).should_receive('log_command')
  136. flexmock(module.os, environ={'a': 'b'})
  137. flexmock(module.subprocess).should_receive('Popen').with_args(
  138. full_command, stdin=None, stdout=None, stderr=None, shell=False, env=None, cwd=None
  139. ).and_return(flexmock(wait=lambda: 0)).once()
  140. flexmock(module).should_receive('exit_code_indicates_error').and_return(False)
  141. flexmock(module).should_receive('log_outputs')
  142. output = module.execute_command(full_command, output_file=module.DO_NOT_CAPTURE)
  143. assert output is None
  144. def test_execute_command_calls_full_command_with_input_file():
  145. full_command = ['foo', 'bar']
  146. input_file = flexmock(name='test')
  147. flexmock(module).should_receive('log_command')
  148. flexmock(module.os, environ={'a': 'b'})
  149. flexmock(module.subprocess).should_receive('Popen').with_args(
  150. full_command,
  151. stdin=input_file,
  152. stdout=module.subprocess.PIPE,
  153. stderr=module.subprocess.STDOUT,
  154. shell=False,
  155. env=None,
  156. cwd=None,
  157. ).and_return(flexmock(stdout=None)).once()
  158. flexmock(module).should_receive('log_outputs')
  159. output = module.execute_command(full_command, input_file=input_file)
  160. assert output is None
  161. def test_execute_command_calls_full_command_with_shell():
  162. full_command = ['foo', 'bar']
  163. flexmock(module).should_receive('log_command')
  164. flexmock(module.os, environ={'a': 'b'})
  165. flexmock(module.subprocess).should_receive('Popen').with_args(
  166. ' '.join(full_command),
  167. stdin=None,
  168. stdout=module.subprocess.PIPE,
  169. stderr=module.subprocess.STDOUT,
  170. shell=True,
  171. env=None,
  172. cwd=None,
  173. ).and_return(flexmock(stdout=None)).once()
  174. flexmock(module).should_receive('log_outputs')
  175. output = module.execute_command(full_command, shell=True)
  176. assert output is None
  177. def test_execute_command_calls_full_command_with_extra_environment():
  178. full_command = ['foo', 'bar']
  179. flexmock(module).should_receive('log_command')
  180. flexmock(module.os, environ={'a': 'b'})
  181. flexmock(module.subprocess).should_receive('Popen').with_args(
  182. full_command,
  183. stdin=None,
  184. stdout=module.subprocess.PIPE,
  185. stderr=module.subprocess.STDOUT,
  186. shell=False,
  187. env={'a': 'b', 'c': 'd'},
  188. cwd=None,
  189. ).and_return(flexmock(stdout=None)).once()
  190. flexmock(module).should_receive('log_outputs')
  191. output = module.execute_command(full_command, extra_environment={'c': 'd'})
  192. assert output is None
  193. def test_execute_command_calls_full_command_with_working_directory():
  194. full_command = ['foo', 'bar']
  195. flexmock(module).should_receive('log_command')
  196. flexmock(module.os, environ={'a': 'b'})
  197. flexmock(module.subprocess).should_receive('Popen').with_args(
  198. full_command,
  199. stdin=None,
  200. stdout=module.subprocess.PIPE,
  201. stderr=module.subprocess.STDOUT,
  202. shell=False,
  203. env=None,
  204. cwd='/working',
  205. ).and_return(flexmock(stdout=None)).once()
  206. flexmock(module).should_receive('log_outputs')
  207. output = module.execute_command(full_command, working_directory='/working')
  208. assert output is None
  209. def test_execute_command_without_run_to_completion_returns_process():
  210. full_command = ['foo', 'bar']
  211. process = flexmock()
  212. flexmock(module).should_receive('log_command')
  213. flexmock(module.os, environ={'a': 'b'})
  214. flexmock(module.subprocess).should_receive('Popen').with_args(
  215. full_command,
  216. stdin=None,
  217. stdout=module.subprocess.PIPE,
  218. stderr=module.subprocess.STDOUT,
  219. shell=False,
  220. env=None,
  221. cwd=None,
  222. ).and_return(process).once()
  223. flexmock(module).should_receive('log_outputs')
  224. assert module.execute_command(full_command, run_to_completion=False) == process
  225. def test_execute_command_and_capture_output_returns_stdout():
  226. full_command = ['foo', 'bar']
  227. expected_output = '[]'
  228. flexmock(module).should_receive('log_command')
  229. flexmock(module.os, environ={'a': 'b'})
  230. flexmock(module.subprocess).should_receive('check_output').with_args(
  231. full_command, stderr=None, shell=False, env=None, cwd=None
  232. ).and_return(flexmock(decode=lambda: expected_output)).once()
  233. output = module.execute_command_and_capture_output(full_command)
  234. assert output == expected_output
  235. def test_execute_command_and_capture_output_with_capture_stderr_returns_stderr():
  236. full_command = ['foo', 'bar']
  237. expected_output = '[]'
  238. flexmock(module).should_receive('log_command')
  239. flexmock(module.os, environ={'a': 'b'})
  240. flexmock(module.subprocess).should_receive('check_output').with_args(
  241. full_command, stderr=module.subprocess.STDOUT, shell=False, env=None, cwd=None
  242. ).and_return(flexmock(decode=lambda: expected_output)).once()
  243. output = module.execute_command_and_capture_output(full_command, capture_stderr=True)
  244. assert output == expected_output
  245. def test_execute_command_and_capture_output_returns_output_when_process_error_is_not_considered_an_error():
  246. full_command = ['foo', 'bar']
  247. expected_output = '[]'
  248. err_output = b'[]'
  249. flexmock(module).should_receive('log_command')
  250. flexmock(module.os, environ={'a': 'b'})
  251. flexmock(module.subprocess).should_receive('check_output').with_args(
  252. full_command, stderr=None, shell=False, env=None, cwd=None
  253. ).and_raise(subprocess.CalledProcessError(1, full_command, err_output)).once()
  254. flexmock(module).should_receive('exit_code_indicates_error').and_return(False).once()
  255. output = module.execute_command_and_capture_output(full_command)
  256. assert output == expected_output
  257. def test_execute_command_and_capture_output_raises_when_command_errors():
  258. full_command = ['foo', 'bar']
  259. expected_output = '[]'
  260. flexmock(module).should_receive('log_command')
  261. flexmock(module.os, environ={'a': 'b'})
  262. flexmock(module.subprocess).should_receive('check_output').with_args(
  263. full_command, stderr=None, shell=False, env=None, cwd=None
  264. ).and_raise(subprocess.CalledProcessError(2, full_command, expected_output)).once()
  265. flexmock(module).should_receive('exit_code_indicates_error').and_return(True).once()
  266. with pytest.raises(subprocess.CalledProcessError):
  267. module.execute_command_and_capture_output(full_command)
  268. def test_execute_command_and_capture_output_returns_output_with_shell():
  269. full_command = ['foo', 'bar']
  270. expected_output = '[]'
  271. flexmock(module).should_receive('log_command')
  272. flexmock(module.os, environ={'a': 'b'})
  273. flexmock(module.subprocess).should_receive('check_output').with_args(
  274. 'foo bar', stderr=None, shell=True, env=None, cwd=None
  275. ).and_return(flexmock(decode=lambda: expected_output)).once()
  276. output = module.execute_command_and_capture_output(full_command, shell=True)
  277. assert output == expected_output
  278. def test_execute_command_and_capture_output_returns_output_with_extra_environment():
  279. full_command = ['foo', 'bar']
  280. expected_output = '[]'
  281. flexmock(module).should_receive('log_command')
  282. flexmock(module.os, environ={'a': 'b'})
  283. flexmock(module.subprocess).should_receive('check_output').with_args(
  284. full_command,
  285. stderr=None,
  286. shell=False,
  287. env={'a': 'b', 'c': 'd'},
  288. cwd=None,
  289. ).and_return(flexmock(decode=lambda: expected_output)).once()
  290. output = module.execute_command_and_capture_output(
  291. full_command, shell=False, extra_environment={'c': 'd'}
  292. )
  293. assert output == expected_output
  294. def test_execute_command_and_capture_output_returns_output_with_working_directory():
  295. full_command = ['foo', 'bar']
  296. expected_output = '[]'
  297. flexmock(module).should_receive('log_command')
  298. flexmock(module.os, environ={'a': 'b'})
  299. flexmock(module.subprocess).should_receive('check_output').with_args(
  300. full_command, stderr=None, shell=False, env=None, cwd='/working'
  301. ).and_return(flexmock(decode=lambda: expected_output)).once()
  302. output = module.execute_command_and_capture_output(
  303. full_command, shell=False, working_directory='/working'
  304. )
  305. assert output == expected_output
  306. def test_execute_command_with_processes_calls_full_command():
  307. full_command = ['foo', 'bar']
  308. processes = (flexmock(),)
  309. flexmock(module).should_receive('log_command')
  310. flexmock(module.os, environ={'a': 'b'})
  311. flexmock(module.subprocess).should_receive('Popen').with_args(
  312. full_command,
  313. stdin=None,
  314. stdout=module.subprocess.PIPE,
  315. stderr=module.subprocess.STDOUT,
  316. shell=False,
  317. env=None,
  318. cwd=None,
  319. ).and_return(flexmock(stdout=None)).once()
  320. flexmock(module).should_receive('log_outputs')
  321. output = module.execute_command_with_processes(full_command, processes)
  322. assert output is None
  323. def test_execute_command_with_processes_returns_output_with_output_log_level_none():
  324. full_command = ['foo', 'bar']
  325. processes = (flexmock(),)
  326. flexmock(module).should_receive('log_command')
  327. flexmock(module.os, environ={'a': 'b'})
  328. process = flexmock(stdout=None)
  329. flexmock(module.subprocess).should_receive('Popen').with_args(
  330. full_command,
  331. stdin=None,
  332. stdout=module.subprocess.PIPE,
  333. stderr=module.subprocess.STDOUT,
  334. shell=False,
  335. env=None,
  336. cwd=None,
  337. ).and_return(process).once()
  338. flexmock(module).should_receive('log_outputs').and_return({process: 'out'})
  339. output = module.execute_command_with_processes(full_command, processes, output_log_level=None)
  340. assert output == 'out'
  341. def test_execute_command_with_processes_calls_full_command_with_output_file():
  342. full_command = ['foo', 'bar']
  343. processes = (flexmock(),)
  344. output_file = flexmock(name='test')
  345. flexmock(module).should_receive('log_command')
  346. flexmock(module.os, environ={'a': 'b'})
  347. flexmock(module.subprocess).should_receive('Popen').with_args(
  348. full_command,
  349. stdin=None,
  350. stdout=output_file,
  351. stderr=module.subprocess.PIPE,
  352. shell=False,
  353. env=None,
  354. cwd=None,
  355. ).and_return(flexmock(stderr=None)).once()
  356. flexmock(module).should_receive('log_outputs')
  357. output = module.execute_command_with_processes(full_command, processes, output_file=output_file)
  358. assert output is None
  359. def test_execute_command_with_processes_calls_full_command_without_capturing_output():
  360. full_command = ['foo', 'bar']
  361. processes = (flexmock(),)
  362. flexmock(module).should_receive('log_command')
  363. flexmock(module.os, environ={'a': 'b'})
  364. flexmock(module.subprocess).should_receive('Popen').with_args(
  365. full_command, stdin=None, stdout=None, stderr=None, shell=False, env=None, cwd=None
  366. ).and_return(flexmock(wait=lambda: 0)).once()
  367. flexmock(module).should_receive('exit_code_indicates_error').and_return(False)
  368. flexmock(module).should_receive('log_outputs')
  369. output = module.execute_command_with_processes(
  370. full_command, processes, output_file=module.DO_NOT_CAPTURE
  371. )
  372. assert output is None
  373. def test_execute_command_with_processes_calls_full_command_with_input_file():
  374. full_command = ['foo', 'bar']
  375. processes = (flexmock(),)
  376. input_file = flexmock(name='test')
  377. flexmock(module).should_receive('log_command')
  378. flexmock(module.os, environ={'a': 'b'})
  379. flexmock(module.subprocess).should_receive('Popen').with_args(
  380. full_command,
  381. stdin=input_file,
  382. stdout=module.subprocess.PIPE,
  383. stderr=module.subprocess.STDOUT,
  384. shell=False,
  385. env=None,
  386. cwd=None,
  387. ).and_return(flexmock(stdout=None)).once()
  388. flexmock(module).should_receive('log_outputs')
  389. output = module.execute_command_with_processes(full_command, processes, input_file=input_file)
  390. assert output is None
  391. def test_execute_command_with_processes_calls_full_command_with_shell():
  392. full_command = ['foo', 'bar']
  393. processes = (flexmock(),)
  394. flexmock(module).should_receive('log_command')
  395. flexmock(module.os, environ={'a': 'b'})
  396. flexmock(module.subprocess).should_receive('Popen').with_args(
  397. ' '.join(full_command),
  398. stdin=None,
  399. stdout=module.subprocess.PIPE,
  400. stderr=module.subprocess.STDOUT,
  401. shell=True,
  402. env=None,
  403. cwd=None,
  404. ).and_return(flexmock(stdout=None)).once()
  405. flexmock(module).should_receive('log_outputs')
  406. output = module.execute_command_with_processes(full_command, processes, shell=True)
  407. assert output is None
  408. def test_execute_command_with_processes_calls_full_command_with_extra_environment():
  409. full_command = ['foo', 'bar']
  410. processes = (flexmock(),)
  411. flexmock(module).should_receive('log_command')
  412. flexmock(module.os, environ={'a': 'b'})
  413. flexmock(module.subprocess).should_receive('Popen').with_args(
  414. full_command,
  415. stdin=None,
  416. stdout=module.subprocess.PIPE,
  417. stderr=module.subprocess.STDOUT,
  418. shell=False,
  419. env={'a': 'b', 'c': 'd'},
  420. cwd=None,
  421. ).and_return(flexmock(stdout=None)).once()
  422. flexmock(module).should_receive('log_outputs')
  423. output = module.execute_command_with_processes(
  424. full_command, processes, extra_environment={'c': 'd'}
  425. )
  426. assert output is None
  427. def test_execute_command_with_processes_calls_full_command_with_working_directory():
  428. full_command = ['foo', 'bar']
  429. processes = (flexmock(),)
  430. flexmock(module).should_receive('log_command')
  431. flexmock(module.os, environ={'a': 'b'})
  432. flexmock(module.subprocess).should_receive('Popen').with_args(
  433. full_command,
  434. stdin=None,
  435. stdout=module.subprocess.PIPE,
  436. stderr=module.subprocess.STDOUT,
  437. shell=False,
  438. env=None,
  439. cwd='/working',
  440. ).and_return(flexmock(stdout=None)).once()
  441. flexmock(module).should_receive('log_outputs')
  442. output = module.execute_command_with_processes(
  443. full_command, processes, working_directory='/working'
  444. )
  445. assert output is None
  446. def test_execute_command_with_processes_kills_processes_on_error():
  447. full_command = ['foo', 'bar']
  448. flexmock(module).should_receive('log_command')
  449. process = flexmock(stdout=flexmock(read=lambda count: None))
  450. process.should_receive('poll')
  451. process.should_receive('kill').once()
  452. processes = (process,)
  453. flexmock(module.os, environ={'a': 'b'})
  454. flexmock(module.subprocess).should_receive('Popen').with_args(
  455. full_command,
  456. stdin=None,
  457. stdout=module.subprocess.PIPE,
  458. stderr=module.subprocess.STDOUT,
  459. shell=False,
  460. env=None,
  461. cwd=None,
  462. ).and_raise(subprocess.CalledProcessError(1, full_command, 'error')).once()
  463. flexmock(module).should_receive('log_outputs').never()
  464. with pytest.raises(subprocess.CalledProcessError):
  465. module.execute_command_with_processes(full_command, processes)