Fix error when a source directory doesn't exist and databases are configured (#655).
Merge pull request #56 from diivi/fix/no-error-on-database-backup-without-source-dirs
This commit is contained in:
commit
ab64b7ef67
3 changed files with 69 additions and 35 deletions
|
@ -11,7 +11,7 @@ ERROR_OUTPUT_MAX_LINE_COUNT = 25
|
||||||
BORG_ERROR_EXIT_CODE = 2
|
BORG_ERROR_EXIT_CODE = 2
|
||||||
|
|
||||||
|
|
||||||
def exit_code_indicates_error(process, exit_code, borg_local_path=None):
|
def exit_code_indicates_error(command, exit_code, borg_local_path=None):
|
||||||
'''
|
'''
|
||||||
Return True if the given exit code from running a command corresponds to an error. If a Borg
|
Return True if the given exit code from running a command corresponds to an error. If a Borg
|
||||||
local path is given and matches the process' command, then treat exit code 1 as a warning
|
local path is given and matches the process' command, then treat exit code 1 as a warning
|
||||||
|
@ -20,8 +20,6 @@ def exit_code_indicates_error(process, exit_code, borg_local_path=None):
|
||||||
if exit_code is None:
|
if exit_code is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
command = process.args.split(' ') if isinstance(process.args, str) else process.args
|
|
||||||
|
|
||||||
if borg_local_path and command[0] == borg_local_path:
|
if borg_local_path and command[0] == borg_local_path:
|
||||||
return bool(exit_code < 0 or exit_code >= BORG_ERROR_EXIT_CODE)
|
return bool(exit_code < 0 or exit_code >= BORG_ERROR_EXIT_CODE)
|
||||||
|
|
||||||
|
@ -121,8 +119,9 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path):
|
||||||
if exit_code is None:
|
if exit_code is None:
|
||||||
still_running = True
|
still_running = True
|
||||||
|
|
||||||
|
command = process.args.split(' ') if isinstance(process.args, str) else process.args
|
||||||
# If any process errors, then raise accordingly.
|
# If any process errors, then raise accordingly.
|
||||||
if exit_code_indicates_error(process, exit_code, borg_local_path):
|
if exit_code_indicates_error(command, exit_code, borg_local_path):
|
||||||
# If an error occurs, include its output in the raised exception so that we don't
|
# If an error occurs, include its output in the raised exception so that we don't
|
||||||
# inadvertently hide error output.
|
# inadvertently hide error output.
|
||||||
output_buffer = output_buffer_for_process(process, exclude_stdouts)
|
output_buffer = output_buffer_for_process(process, exclude_stdouts)
|
||||||
|
@ -228,13 +227,20 @@ def execute_command_and_capture_output(
|
||||||
environment = {**os.environ, **extra_environment} if extra_environment else None
|
environment = {**os.environ, **extra_environment} if extra_environment else None
|
||||||
command = ' '.join(full_command) if shell else full_command
|
command = ' '.join(full_command) if shell else full_command
|
||||||
|
|
||||||
output = subprocess.check_output(
|
try:
|
||||||
command,
|
output = subprocess.check_output(
|
||||||
stderr=subprocess.STDOUT if capture_stderr else None,
|
command,
|
||||||
shell=shell,
|
stderr=subprocess.STDOUT if capture_stderr else None,
|
||||||
env=environment,
|
shell=shell,
|
||||||
cwd=working_directory,
|
env=environment,
|
||||||
)
|
cwd=working_directory,
|
||||||
|
)
|
||||||
|
logger.warning('Command output: {}'.format(output))
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
if exit_code_indicates_error(command, error.returncode):
|
||||||
|
raise
|
||||||
|
output = error.output
|
||||||
|
logger.warning('Command output: {}'.format(output))
|
||||||
|
|
||||||
return output.decode() if output is not None else None
|
return output.decode() if output is not None else None
|
||||||
|
|
||||||
|
|
|
@ -138,10 +138,10 @@ def test_log_outputs_kills_other_processes_when_one_errors():
|
||||||
|
|
||||||
process = subprocess.Popen(['grep'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
process = subprocess.Popen(['grep'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
flexmock(module).should_receive('exit_code_indicates_error').with_args(
|
flexmock(module).should_receive('exit_code_indicates_error').with_args(
|
||||||
process, None, 'borg'
|
['grep'], None, 'borg'
|
||||||
).and_return(False)
|
).and_return(False)
|
||||||
flexmock(module).should_receive('exit_code_indicates_error').with_args(
|
flexmock(module).should_receive('exit_code_indicates_error').with_args(
|
||||||
process, 2, 'borg'
|
['grep'], 2, 'borg'
|
||||||
).and_return(True)
|
).and_return(True)
|
||||||
other_process = subprocess.Popen(
|
other_process = subprocess.Popen(
|
||||||
['sleep', '2'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
['sleep', '2'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
||||||
|
@ -245,10 +245,10 @@ def test_log_outputs_truncates_long_error_output():
|
||||||
|
|
||||||
process = subprocess.Popen(['grep'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
process = subprocess.Popen(['grep'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
flexmock(module).should_receive('exit_code_indicates_error').with_args(
|
flexmock(module).should_receive('exit_code_indicates_error').with_args(
|
||||||
process, None, 'borg'
|
['grep'], None, 'borg'
|
||||||
).and_return(False)
|
).and_return(False)
|
||||||
flexmock(module).should_receive('exit_code_indicates_error').with_args(
|
flexmock(module).should_receive('exit_code_indicates_error').with_args(
|
||||||
process, 2, 'borg'
|
['grep'], 2, 'borg'
|
||||||
).and_return(True)
|
).and_return(True)
|
||||||
flexmock(module).should_receive('output_buffer_for_process').and_return(process.stdout)
|
flexmock(module).should_receive('output_buffer_for_process').and_return(process.stdout)
|
||||||
|
|
||||||
|
|
|
@ -7,32 +7,32 @@ from borgmatic import execute as module
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'process,exit_code,borg_local_path,expected_result',
|
'command,exit_code,borg_local_path,expected_result',
|
||||||
(
|
(
|
||||||
(flexmock(args=['grep']), 2, None, True),
|
(['grep'], 2, None, True),
|
||||||
(flexmock(args=['grep']), 2, 'borg', True),
|
(['grep'], 2, 'borg', True),
|
||||||
(flexmock(args=['borg']), 2, 'borg', True),
|
(['borg'], 2, 'borg', True),
|
||||||
(flexmock(args=['borg1']), 2, 'borg1', True),
|
(['borg1'], 2, 'borg1', True),
|
||||||
(flexmock(args=['grep']), 1, None, True),
|
(['grep'], 1, None, True),
|
||||||
(flexmock(args=['grep']), 1, 'borg', True),
|
(['grep'], 1, 'borg', True),
|
||||||
(flexmock(args=['borg']), 1, 'borg', False),
|
(['borg'], 1, 'borg', False),
|
||||||
(flexmock(args=['borg1']), 1, 'borg1', False),
|
(['borg1'], 1, 'borg1', False),
|
||||||
(flexmock(args=['grep']), 0, None, False),
|
(['grep'], 0, None, False),
|
||||||
(flexmock(args=['grep']), 0, 'borg', False),
|
(['grep'], 0, 'borg', False),
|
||||||
(flexmock(args=['borg']), 0, 'borg', False),
|
(['borg'], 0, 'borg', False),
|
||||||
(flexmock(args=['borg1']), 0, 'borg1', False),
|
(['borg1'], 0, 'borg1', False),
|
||||||
# -9 exit code occurs when child process get SIGKILLed.
|
# -9 exit code occurs when child process get SIGKILLed.
|
||||||
(flexmock(args=['grep']), -9, None, True),
|
(['grep'], -9, None, True),
|
||||||
(flexmock(args=['grep']), -9, 'borg', True),
|
(['grep'], -9, 'borg', True),
|
||||||
(flexmock(args=['borg']), -9, 'borg', True),
|
(['borg'], -9, 'borg', True),
|
||||||
(flexmock(args=['borg1']), -9, 'borg1', True),
|
(['borg1'], -9, 'borg1', True),
|
||||||
(flexmock(args=['borg']), None, None, False),
|
(['borg'], None, None, False),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_exit_code_indicates_error_respects_exit_code_and_borg_local_path(
|
def test_exit_code_indicates_error_respects_exit_code_and_borg_local_path(
|
||||||
process, exit_code, borg_local_path, expected_result
|
command, exit_code, borg_local_path, expected_result
|
||||||
):
|
):
|
||||||
assert module.exit_code_indicates_error(process, exit_code, borg_local_path) is expected_result
|
assert module.exit_code_indicates_error(command, exit_code, borg_local_path) is expected_result
|
||||||
|
|
||||||
|
|
||||||
def test_command_for_process_converts_sequence_command_to_string():
|
def test_command_for_process_converts_sequence_command_to_string():
|
||||||
|
@ -239,6 +239,34 @@ def test_execute_command_and_capture_output_with_capture_stderr_returns_stderr()
|
||||||
assert output == expected_output
|
assert output == expected_output
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_command_and_capture_output_returns_output_when_error_code_is_one():
|
||||||
|
full_command = ['foo', 'bar']
|
||||||
|
expected_output = '[]'
|
||||||
|
err_output = b'[]'
|
||||||
|
flexmock(module.os, environ={'a': 'b'})
|
||||||
|
flexmock(module.subprocess).should_receive('check_output').with_args(
|
||||||
|
full_command, stderr=None, shell=False, env=None, cwd=None
|
||||||
|
).and_raise(subprocess.CalledProcessError(1, full_command, err_output)).once()
|
||||||
|
flexmock(module).should_receive('exit_code_indicates_error').and_return(False).once()
|
||||||
|
|
||||||
|
output = module.execute_command_and_capture_output(full_command)
|
||||||
|
|
||||||
|
assert output == expected_output
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_command_and_capture_output_raises_when_command_errors():
|
||||||
|
full_command = ['foo', 'bar']
|
||||||
|
expected_output = '[]'
|
||||||
|
flexmock(module.os, environ={'a': 'b'})
|
||||||
|
flexmock(module.subprocess).should_receive('check_output').with_args(
|
||||||
|
full_command, stderr=None, shell=False, env=None, cwd=None
|
||||||
|
).and_raise(subprocess.CalledProcessError(2, full_command, expected_output)).once()
|
||||||
|
flexmock(module).should_receive('exit_code_indicates_error').and_return(True).once()
|
||||||
|
|
||||||
|
with pytest.raises(subprocess.CalledProcessError):
|
||||||
|
module.execute_command_and_capture_output(full_command)
|
||||||
|
|
||||||
|
|
||||||
def test_execute_command_and_capture_output_returns_output_with_shell():
|
def test_execute_command_and_capture_output_returns_output_with_shell():
|
||||||
full_command = ['foo', 'bar']
|
full_command = ['foo', 'bar']
|
||||||
expected_output = '[]'
|
expected_output = '[]'
|
||||||
|
|
Loading…
Reference in a new issue