Compare commits
23 Commits
Author | SHA1 | Date |
---|---|---|
Dan Helfman | 5752373009 | |
Dan Helfman | fecae39fcd | |
Dan Helfman | 38bc4fbfe2 | |
Dan Helfman | 92ed7573d4 | |
Dan Helfman | 80f0e92462 | |
Dan Helfman | 5f10b1b2ca | |
Dan Helfman | 4f83b1e6b3 | |
Codimp | 15d5a687fb | |
Codimp | eb1fce3787 | |
Dan Helfman | 7f735cbe59 | |
Dan Helfman | a690ea4016 | |
Dan Helfman | 7a110c7acd | |
estebanthilliez | 407bb33359 | |
estebanthilliez | 4b7f7bba04 | |
estebanthilliez | cfdc0a1f2a | |
Dan Helfman | f926055e67 | |
Dan Helfman | 058af95d70 | |
Dan Helfman | 54facdc391 | |
estebanthi | 2e4c0cc7e7 | |
Dan Helfman | cb2fd7c5e8 | |
Dan Helfman | 94133cc8b1 | |
Dan Helfman | dcec89be90 | |
Dan Helfman | fefd5d1d0e |
12
NEWS
12
NEWS
|
@ -1,3 +1,15 @@
|
||||||
|
1.8.12.dev0
|
||||||
|
* #860: Fix interaction between environment variable interpolation in constants and shell escaping.
|
||||||
|
* #863: When color output is disabled (explicitly or implicitly), don't prefix each log line with
|
||||||
|
the log level.
|
||||||
|
|
||||||
|
1.8.11
|
||||||
|
* #815: Add optional Healthchecks auto-provisioning via "create_slug" option.
|
||||||
|
* #851: Fix lack of file extraction when using "extract --strip-components all" on a path with a
|
||||||
|
leading slash.
|
||||||
|
* #854: Fix a traceback when the "data" consistency check is used.
|
||||||
|
* #857: Fix a traceback with "check --only spot" when the "spot" check is unconfigured.
|
||||||
|
|
||||||
1.8.10
|
1.8.10
|
||||||
* #656 (beta): Add a "spot" consistency check that compares file counts and contents between your
|
* #656 (beta): Add a "spot" consistency check that compares file counts and contents between your
|
||||||
source files and the latest archive, ensuring they fall within configured tolerances. This can
|
source files and the latest archive, ensuring they fall within configured tolerances. This can
|
||||||
|
|
|
@ -480,7 +480,13 @@ def spot_check(
|
||||||
'''
|
'''
|
||||||
log_label = f'{repository.get("label", repository["path"])}'
|
log_label = f'{repository.get("label", repository["path"])}'
|
||||||
logger.debug(f'{log_label}: Running spot check')
|
logger.debug(f'{log_label}: Running spot check')
|
||||||
spot_check_config = next(check for check in config['checks'] if check['name'] == 'spot')
|
|
||||||
|
try:
|
||||||
|
spot_check_config = next(
|
||||||
|
check for check in config.get('checks', ()) if check.get('name') == 'spot'
|
||||||
|
)
|
||||||
|
except StopIteration:
|
||||||
|
raise ValueError('Cannot run spot check because it is unconfigured')
|
||||||
|
|
||||||
if spot_check_config['data_tolerance_percentage'] > spot_check_config['data_sample_percentage']:
|
if spot_check_config['data_tolerance_percentage'] > spot_check_config['data_sample_percentage']:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|
|
@ -52,8 +52,8 @@ def make_archive_filter_flags(local_borg_version, config, checks, check_argument
|
||||||
|
|
||||||
def make_check_flags(checks, archive_filter_flags):
|
def make_check_flags(checks, archive_filter_flags):
|
||||||
'''
|
'''
|
||||||
Given a parsed sequence of checks and a sequence of flags to filter archives, transform the
|
Given a parsed checks set and a sequence of flags to filter archives,
|
||||||
checks into tuple of command-line check flags.
|
transform the checks into tuple of command-line check flags.
|
||||||
|
|
||||||
For example, given parsed checks of:
|
For example, given parsed checks of:
|
||||||
|
|
||||||
|
@ -68,13 +68,13 @@ def make_check_flags(checks, archive_filter_flags):
|
||||||
'''
|
'''
|
||||||
if 'data' in checks:
|
if 'data' in checks:
|
||||||
data_flags = ('--verify-data',)
|
data_flags = ('--verify-data',)
|
||||||
checks += ('archives',)
|
checks.update({'archives'})
|
||||||
else:
|
else:
|
||||||
data_flags = ()
|
data_flags = ()
|
||||||
|
|
||||||
common_flags = (archive_filter_flags if 'archives' in checks else ()) + data_flags
|
common_flags = (archive_filter_flags if 'archives' in checks else ()) + data_flags
|
||||||
|
|
||||||
if {'repository', 'archives'}.issubset(set(checks)):
|
if {'repository', 'archives'}.issubset(checks):
|
||||||
return common_flags
|
return common_flags
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -104,8 +104,13 @@ def extract_archive(
|
||||||
if not paths:
|
if not paths:
|
||||||
raise ValueError('The --strip-components flag with "all" requires at least one --path')
|
raise ValueError('The --strip-components flag with "all" requires at least one --path')
|
||||||
|
|
||||||
# Calculate the maximum number of leading path components of the given paths.
|
# Calculate the maximum number of leading path components of the given paths. "if piece"
|
||||||
strip_components = max(0, *(len(path.split(os.path.sep)) - 1 for path in paths))
|
# ignores empty path components, e.g. those resulting from a leading slash. And the "- 1"
|
||||||
|
# is so this doesn't count the final path component, e.g. the filename itself.
|
||||||
|
strip_components = max(
|
||||||
|
0,
|
||||||
|
*(len(tuple(piece for piece in path.split(os.path.sep) if piece)) - 1 for path in paths)
|
||||||
|
)
|
||||||
|
|
||||||
full_command = (
|
full_command = (
|
||||||
(local_path, 'extract')
|
(local_path, 'extract')
|
||||||
|
|
|
@ -50,12 +50,15 @@ def apply_constants(value, constants, shell_escape=False):
|
||||||
value[index] = apply_constants(list_value, constants, shell_escape)
|
value[index] = apply_constants(list_value, constants, shell_escape)
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
for option_name, option_value in value.items():
|
for option_name, option_value in value.items():
|
||||||
shell_escape = (
|
value[option_name] = apply_constants(
|
||||||
shell_escape
|
option_value,
|
||||||
or option_name.startswith('before_')
|
constants,
|
||||||
or option_name.startswith('after_')
|
shell_escape=(
|
||||||
or option_name == 'on_error'
|
shell_escape
|
||||||
|
or option_name.startswith('before_')
|
||||||
|
or option_name.startswith('after_')
|
||||||
|
or option_name == 'on_error'
|
||||||
|
),
|
||||||
)
|
)
|
||||||
value[option_name] = apply_constants(option_value, constants, shell_escape)
|
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -269,7 +269,8 @@ properties:
|
||||||
compression:
|
compression:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
Type of compression to use when creating archives. See
|
Type of compression to use when creating archives. (Compression
|
||||||
|
level can be added separated with a comma, like "zstd,7".) See
|
||||||
http://borgbackup.readthedocs.io/en/stable/usage/create.html for
|
http://borgbackup.readthedocs.io/en/stable/usage/create.html for
|
||||||
details. Defaults to "lz4".
|
details. Defaults to "lz4".
|
||||||
example: lz4
|
example: lz4
|
||||||
|
@ -1662,6 +1663,14 @@ properties:
|
||||||
states.
|
states.
|
||||||
example:
|
example:
|
||||||
- finish
|
- finish
|
||||||
|
create_slug:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
Create the check if it does not exist. Only works with
|
||||||
|
the slug URL scheme (https://hc-ping.com/<ping-key>/<slug>
|
||||||
|
as opposed to https://hc-ping.com/<uuid>).
|
||||||
|
Defaults to false.
|
||||||
|
example: true
|
||||||
description: |
|
description: |
|
||||||
Configuration for a monitoring integration with Healthchecks. Create
|
Configuration for a monitoring integration with Healthchecks. Create
|
||||||
an account at https://healthchecks.io (or self-host Healthchecks) if
|
an account at https://healthchecks.io (or self-host Healthchecks) if
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -59,10 +60,20 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
ping_url_is_uuid = re.search(r'\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$', ping_url)
|
||||||
|
|
||||||
healthchecks_state = MONITOR_STATE_TO_HEALTHCHECKS.get(state)
|
healthchecks_state = MONITOR_STATE_TO_HEALTHCHECKS.get(state)
|
||||||
if healthchecks_state:
|
if healthchecks_state:
|
||||||
ping_url = f'{ping_url}/{healthchecks_state}'
|
ping_url = f'{ping_url}/{healthchecks_state}'
|
||||||
|
|
||||||
|
if hook_config.get('create_slug'):
|
||||||
|
if ping_url_is_uuid:
|
||||||
|
logger.warning(
|
||||||
|
f'{config_filename}: Healthchecks UUIDs do not support auto provisionning; ignoring'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ping_url = f'{ping_url}?create=1'
|
||||||
|
|
||||||
logger.info(f'{config_filename}: Pinging Healthchecks {state.name.lower()}{dry_run_label}')
|
logger.info(f'{config_filename}: Pinging Healthchecks {state.name.lower()}{dry_run_label}')
|
||||||
logger.debug(f'{config_filename}: Using Healthchecks ping URL {ping_url}')
|
logger.debug(f'{config_filename}: Using Healthchecks ping URL {ping_url}')
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,11 @@ class Multi_stream_handler(logging.Handler):
|
||||||
handler.setLevel(level)
|
handler.setLevel(level)
|
||||||
|
|
||||||
|
|
||||||
|
class Console_no_color_formatter(logging.Formatter):
|
||||||
|
def format(self, record):
|
||||||
|
return record.msg
|
||||||
|
|
||||||
|
|
||||||
class Console_color_formatter(logging.Formatter):
|
class Console_color_formatter(logging.Formatter):
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
add_custom_log_levels()
|
add_custom_log_levels()
|
||||||
|
@ -198,6 +203,8 @@ def configure_logging(
|
||||||
|
|
||||||
if color_enabled:
|
if color_enabled:
|
||||||
console_handler.setFormatter(Console_color_formatter())
|
console_handler.setFormatter(Console_color_formatter())
|
||||||
|
else:
|
||||||
|
console_handler.setFormatter(Console_no_color_formatter())
|
||||||
|
|
||||||
console_handler.setLevel(console_log_level)
|
console_handler.setLevel(console_log_level)
|
||||||
|
|
||||||
|
|
|
@ -437,19 +437,28 @@ borgmatic's own configuration file. So include your configuration file in
|
||||||
backups to avoid getting caught without a way to restore a database.
|
backups to avoid getting caught without a way to restore a database.
|
||||||
3. borgmatic does not currently support backing up or restoring multiple
|
3. borgmatic does not currently support backing up or restoring multiple
|
||||||
databases that share the exact same name on different hosts.
|
databases that share the exact same name on different hosts.
|
||||||
4. Because database hooks implicitly enable the `read_special` configuration,
|
4. When database hooks are enabled, borgmatic instructs Borg to consume
|
||||||
any special files are excluded from backups (named pipes, block devices,
|
special files (via `--read-special`) to support database dump
|
||||||
character devices, and sockets) to prevent hanging. Try a command like `find
|
streaming—regardless of the value of your `read_special` configuration option.
|
||||||
/your/source/path -type b -or -type c -or -type p -or -type s` to find such
|
And because this can cause Borg to hang, borgmatic also automatically excludes
|
||||||
files. Common directories to exclude are `/dev` and `/run`, but that may not
|
special files (and symlinks to them) that Borg may get stuck on. Even so,
|
||||||
be exhaustive. <span class="minilink minilink-addedin">New in version
|
there are still potential edge cases in which applications on your system
|
||||||
1.7.3</span> When database hooks are enabled, borgmatic automatically excludes
|
create new special files *after* borgmatic constructs its exclude list,
|
||||||
special files (and symlinks to special files) that may cause Borg to hang, so
|
resulting in Borg hangs. If that occurs, you can resort to manually excluding
|
||||||
generally you no longer need to manually exclude them. There are potential
|
those files. And if you explicitly set the `read-special` option to `true`,
|
||||||
edge cases though in which applications on your system create new special files
|
borgmatic will opt you out of the auto-exclude feature entirely, but will
|
||||||
*after* borgmatic constructs its exclude list, resulting in Borg hangs. If that
|
still instruct Borg to consume special files—you will just be on your own to
|
||||||
occurs, you can resort to the manual excludes described above. And to opt out
|
exclude them. <span class="minilink minilink-addedin">Prior to version
|
||||||
of the auto-exclude feature entirely, explicitly set `read_special` to true.
|
1.7.3</span>Special files were not auto-excluded, and you were responsible for
|
||||||
|
excluding them yourself. Common directories to exclude are `/dev` and `/run`,
|
||||||
|
but that may not be exhaustive.
|
||||||
|
5. Database hooks also implicitly enable the `one_file_system` option, which
|
||||||
|
means Borg won't cross filesystem boundaries when looking for files to backup.
|
||||||
|
This is especially important when running borgmatic in a container, as
|
||||||
|
container volumes are mounted as separate filesystems. One work-around is to
|
||||||
|
explicitly add each mounted volume you'd like to backup to
|
||||||
|
`source_directories` instead of relying on Borg to include them implicitly via
|
||||||
|
a parent directory.
|
||||||
|
|
||||||
|
|
||||||
### Manual restoration
|
### Manual restoration
|
||||||
|
|
|
@ -121,7 +121,7 @@ incorrect excludes, inadvertent deletes, files changed by malware, etc.
|
||||||
|
|
||||||
However, because an exhaustive comparison of all source files against the
|
However, because an exhaustive comparison of all source files against the
|
||||||
latest archive might be too slow, the spot check supports *sampling* a
|
latest archive might be too slow, the spot check supports *sampling* a
|
||||||
percentage of your source files for the comparison, ensuring it falls within
|
percentage of your source files for the comparison, ensuring they fall within
|
||||||
configured tolerances.
|
configured tolerances.
|
||||||
|
|
||||||
Here's how it works. Start by installing the `xxhash` OS package if you don't
|
Here's how it works. Start by installing the `xxhash` OS package if you don't
|
||||||
|
@ -149,12 +149,11 @@ fail.)
|
||||||
The `data_sample_percentage` is the percentage of total files in the source
|
The `data_sample_percentage` is the percentage of total files in the source
|
||||||
directories to randomly sample and compare to their corresponding files in the
|
directories to randomly sample and compare to their corresponding files in the
|
||||||
latest backup archive. A higher value allows a more accurate check—and a
|
latest backup archive. A higher value allows a more accurate check—and a
|
||||||
slower one. The comparison is performed by hashing the selected files in each
|
slower one. The comparison is performed by hashing the selected source files
|
||||||
of the source paths and counting hashes that don't match the latest archive.
|
and counting hashes that don't match the latest archive. For instance, if you
|
||||||
For instance, if you have 1,000 source files and your sample percentage is 1%,
|
have 1,000 source files and your sample percentage is 1%, then only 10 source
|
||||||
then only 10 source files will be compared against the latest archive. These
|
files will be compared against the latest archive. These sampled files are
|
||||||
sampled files are selected randomly each time, so in effect the spot check is
|
selected randomly each time, so in effect the spot check is probabilistic.
|
||||||
probabilistic.
|
|
||||||
|
|
||||||
The `data_tolerance_percentage` is the percentage of total files in the source
|
The `data_tolerance_percentage` is the percentage of total files in the source
|
||||||
directories that can fail a spot check data comparison without failing the
|
directories that can fail a spot check data comparison without failing the
|
||||||
|
@ -175,6 +174,11 @@ want the spot check to fail the next time it's run? Run `borgmatic create` to
|
||||||
create a new backup, thereby allowing the next spot check to run against an
|
create a new backup, thereby allowing the next spot check to run against an
|
||||||
archive that contains your recent changes.
|
archive that contains your recent changes.
|
||||||
|
|
||||||
|
Because the spot check only looks at the most recent archive, you may not want
|
||||||
|
to run it immediately after a `create` action (borgmatic's default behavior).
|
||||||
|
Instead, it may make more sense to run the spot check on a separate schedule
|
||||||
|
from `create`.
|
||||||
|
|
||||||
As long as the spot check feature is in beta, it may be subject to breaking
|
As long as the spot check feature is in beta, it may be subject to breaking
|
||||||
changes. But feel free to use it in production if you're okay with that
|
changes. But feel free to use it in production if you're okay with that
|
||||||
caveat, and please [provide any
|
caveat, and please [provide any
|
||||||
|
|
|
@ -420,7 +420,8 @@ pipx](https://torsion.org/borgmatic/docs/how-to/set-up-backups/#installation),
|
||||||
run the following to install Apprise so borgmatic can use it:
|
run the following to install Apprise so borgmatic can use it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo pipx install --force borgmatic[Apprise]
|
sudo pipx uninstall borgmatic
|
||||||
|
sudo pipx install borgmatic[Apprise]
|
||||||
```
|
```
|
||||||
|
|
||||||
Omit `sudo` if borgmatic is installed as a non-root user.
|
Omit `sudo` if borgmatic is installed as a non-root user.
|
||||||
|
@ -435,11 +436,16 @@ apprise:
|
||||||
label: gotify
|
label: gotify
|
||||||
- url: mastodons://access_key@hostname/@user
|
- url: mastodons://access_key@hostname/@user
|
||||||
label: mastodon
|
label: mastodon
|
||||||
|
states:
|
||||||
|
- start
|
||||||
|
- finish
|
||||||
|
- fail
|
||||||
```
|
```
|
||||||
|
|
||||||
With this configuration, borgmatic pings each of the configured Apprise
|
With this configuration, borgmatic pings each of the configured Apprise
|
||||||
services when a backup begins, ends, or errors, but only when any of the
|
services when a backup begins, ends, or errors, but only when any of the
|
||||||
`prune`, `compact`, `create`, or `check` actions are run.
|
`prune`, `compact`, `create`, or `check` actions are run. (By default, if
|
||||||
|
`states` is not specified, Apprise services are only pinged on error.)
|
||||||
|
|
||||||
You can optionally customize the contents of the default messages sent to
|
You can optionally customize the contents of the default messages sent to
|
||||||
these services:
|
these services:
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -1,6 +1,6 @@
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
VERSION = '1.8.10'
|
VERSION = '1.8.12.dev0'
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
|
|
@ -769,6 +769,36 @@ def test_compare_spot_check_hashes_considers_non_existent_path_as_not_matching()
|
||||||
) == ('/bar',)
|
) == ('/bar',)
|
||||||
|
|
||||||
|
|
||||||
|
def test_spot_check_without_spot_configuration_errors():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.spot_check(
|
||||||
|
repository={'path': 'repo'},
|
||||||
|
config={
|
||||||
|
'checks': [
|
||||||
|
{
|
||||||
|
'name': 'archives',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
local_borg_version=flexmock(),
|
||||||
|
global_arguments=flexmock(),
|
||||||
|
local_path=flexmock(),
|
||||||
|
remote_path=flexmock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_spot_check_without_any_configuration_errors():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.spot_check(
|
||||||
|
repository={'path': 'repo'},
|
||||||
|
config={},
|
||||||
|
local_borg_version=flexmock(),
|
||||||
|
global_arguments=flexmock(),
|
||||||
|
local_path=flexmock(),
|
||||||
|
remote_path=flexmock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_spot_check_data_tolerance_percenatge_greater_than_data_sample_percentage_errors():
|
def test_spot_check_data_tolerance_percenatge_greater_than_data_sample_percentage_errors():
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
module.spot_check(
|
module.spot_check(
|
||||||
|
|
|
@ -223,25 +223,25 @@ def test_make_archive_filter_flags_with_default_checks_and_prefix_includes_match
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_flags_with_repository_check_returns_flag():
|
def test_make_check_flags_with_repository_check_returns_flag():
|
||||||
flags = module.make_check_flags(('repository',), ())
|
flags = module.make_check_flags({'repository'}, ())
|
||||||
|
|
||||||
assert flags == ('--repository-only',)
|
assert flags == ('--repository-only',)
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_flags_with_archives_check_returns_flag():
|
def test_make_check_flags_with_archives_check_returns_flag():
|
||||||
flags = module.make_check_flags(('archives',), ())
|
flags = module.make_check_flags({'archives'}, ())
|
||||||
|
|
||||||
assert flags == ('--archives-only',)
|
assert flags == ('--archives-only',)
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_flags_with_archives_check_and_archive_filter_flags_includes_those_flags():
|
def test_make_check_flags_with_archives_check_and_archive_filter_flags_includes_those_flags():
|
||||||
flags = module.make_check_flags(('archives',), ('--match-archives', 'sh:foo-*'))
|
flags = module.make_check_flags({'archives'}, ('--match-archives', 'sh:foo-*'))
|
||||||
|
|
||||||
assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
|
assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_flags_without_archives_check_and_with_archive_filter_flags_includes_those_flags():
|
def test_make_check_flags_without_archives_check_and_with_archive_filter_flags_includes_those_flags():
|
||||||
flags = module.make_check_flags(('repository',), ('--match-archives', 'sh:foo-*'))
|
flags = module.make_check_flags({'repository'}, ('--match-archives', 'sh:foo-*'))
|
||||||
|
|
||||||
assert flags == ('--repository-only',)
|
assert flags == ('--repository-only',)
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ def test_make_check_flags_with_data_check_returns_flag_and_implies_archives():
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
|
|
||||||
flags = module.make_check_flags(('data',), ())
|
flags = module.make_check_flags({'data'}, ())
|
||||||
|
|
||||||
assert flags == (
|
assert flags == (
|
||||||
'--archives-only',
|
'--archives-only',
|
||||||
|
@ -262,7 +262,7 @@ def test_make_check_flags_with_extract_omits_extract_flag():
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
|
|
||||||
flags = module.make_check_flags(('extract',), ())
|
flags = module.make_check_flags({'extract'}, ())
|
||||||
|
|
||||||
assert flags == ()
|
assert flags == ()
|
||||||
|
|
||||||
|
@ -272,10 +272,10 @@ def test_make_check_flags_with_repository_and_data_checks_does_not_return_reposi
|
||||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
|
|
||||||
flags = module.make_check_flags(
|
flags = module.make_check_flags(
|
||||||
(
|
{
|
||||||
'repository',
|
'repository',
|
||||||
'data',
|
'data',
|
||||||
),
|
},
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -507,6 +507,39 @@ def test_extract_archive_calls_borg_with_strip_components_calculated_from_all():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_archive_calls_borg_with_strip_components_calculated_from_all_with_leading_slash():
|
||||||
|
flexmock(module.os.path).should_receive('abspath').and_return('repo')
|
||||||
|
insert_execute_command_mock(
|
||||||
|
(
|
||||||
|
'borg',
|
||||||
|
'extract',
|
||||||
|
'--strip-components',
|
||||||
|
'2',
|
||||||
|
'repo::archive',
|
||||||
|
'/foo/bar/baz.txt',
|
||||||
|
'/foo/bar.txt',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
|
('repo::archive',)
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.config.validate).should_receive(
|
||||||
|
'normalize_repository_path'
|
||||||
|
).and_return('repo')
|
||||||
|
|
||||||
|
module.extract_archive(
|
||||||
|
dry_run=False,
|
||||||
|
repository='repo',
|
||||||
|
archive='archive',
|
||||||
|
paths=['/foo/bar/baz.txt', '/foo/bar.txt'],
|
||||||
|
config={},
|
||||||
|
local_borg_version='1.2.3',
|
||||||
|
global_arguments=flexmock(log_json=False),
|
||||||
|
strip_components='all',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_extract_archive_with_strip_components_all_and_no_paths_raises():
|
def test_extract_archive_with_strip_components_all_and_no_paths_raises():
|
||||||
flexmock(module.os.path).should_receive('abspath').and_return('repo')
|
flexmock(module.os.path).should_receive('abspath').and_return('repo')
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
|
|
|
@ -50,6 +50,16 @@ def test_apply_constants_with_empty_constants_passes_through_value():
|
||||||
({'before_backup': '{inject}'}, {'before_backup': "'echo hi; naughty-command'"}),
|
({'before_backup': '{inject}'}, {'before_backup': "'echo hi; naughty-command'"}),
|
||||||
({'after_backup': '{inject}'}, {'after_backup': "'echo hi; naughty-command'"}),
|
({'after_backup': '{inject}'}, {'after_backup': "'echo hi; naughty-command'"}),
|
||||||
({'on_error': '{inject}'}, {'on_error': "'echo hi; naughty-command'"}),
|
({'on_error': '{inject}'}, {'on_error': "'echo hi; naughty-command'"}),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
'before_backup': '{env_pass}',
|
||||||
|
'postgresql_databases': [{'name': 'users', 'password': '{env_pass}'}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'before_backup': "'${PASS}'",
|
||||||
|
'postgresql_databases': [{'name': 'users', 'password': '${PASS}'}],
|
||||||
|
},
|
||||||
|
),
|
||||||
(3, 3),
|
(3, 3),
|
||||||
(True, True),
|
(True, True),
|
||||||
(False, False),
|
(False, False),
|
||||||
|
@ -63,6 +73,7 @@ def test_apply_constants_makes_string_substitutions(value, expected_value):
|
||||||
'int': 3,
|
'int': 3,
|
||||||
'bool': True,
|
'bool': True,
|
||||||
'inject': 'echo hi; naughty-command',
|
'inject': 'echo hi; naughty-command',
|
||||||
|
'env_pass': '${PASS}',
|
||||||
}
|
}
|
||||||
|
|
||||||
assert module.apply_constants(value, constants) == expected_value
|
assert module.apply_constants(value, constants) == expected_value
|
||||||
|
|
|
@ -264,6 +264,72 @@ def test_ping_monitor_hits_ping_url_when_states_matching():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ping_monitor_adds_create_query_parameter_when_create_slug_true():
|
||||||
|
flexmock(module.borgmatic.hooks.logs).should_receive('Forgetful_buffering_handler').never()
|
||||||
|
hook_config = {'ping_url': 'https://example.com', 'create_slug': True}
|
||||||
|
flexmock(module.requests).should_receive('post').with_args(
|
||||||
|
'https://example.com/start?create=1', data=''.encode('utf-8'), verify=True
|
||||||
|
).and_return(flexmock(ok=True))
|
||||||
|
|
||||||
|
module.ping_monitor(
|
||||||
|
hook_config,
|
||||||
|
{},
|
||||||
|
'config.yaml',
|
||||||
|
state=module.monitor.State.START,
|
||||||
|
monitoring_log_level=1,
|
||||||
|
dry_run=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ping_monitor_does_not_add_create_query_parameter_when_create_slug_false():
|
||||||
|
flexmock(module.borgmatic.hooks.logs).should_receive('Forgetful_buffering_handler').never()
|
||||||
|
hook_config = {'ping_url': 'https://example.com', 'create_slug': False}
|
||||||
|
flexmock(module.requests).should_receive('post').with_args(
|
||||||
|
'https://example.com/start', data=''.encode('utf-8'), verify=True
|
||||||
|
).and_return(flexmock(ok=True))
|
||||||
|
|
||||||
|
module.ping_monitor(
|
||||||
|
hook_config,
|
||||||
|
{},
|
||||||
|
'config.yaml',
|
||||||
|
state=module.monitor.State.START,
|
||||||
|
monitoring_log_level=1,
|
||||||
|
dry_run=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ping_monitor_does_not_add_create_query_parameter_when_ping_url_is_uuid():
|
||||||
|
hook_config = {'ping_url': 'b3611b24-df9c-4d36-9203-fa292820bf2a', 'create_slug': True}
|
||||||
|
flexmock(module.requests).should_receive('post').with_args(
|
||||||
|
f"https://hc-ping.com/{hook_config['ping_url']}",
|
||||||
|
data=''.encode('utf-8'),
|
||||||
|
verify=True,
|
||||||
|
).and_return(flexmock(ok=True))
|
||||||
|
|
||||||
|
module.ping_monitor(
|
||||||
|
hook_config,
|
||||||
|
{},
|
||||||
|
'config.yaml',
|
||||||
|
state=module.monitor.State.FINISH,
|
||||||
|
monitoring_log_level=1,
|
||||||
|
dry_run=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ping_monitor_issues_warning_when_ping_url_is_uuid_and_create_slug_true():
|
||||||
|
hook_config = {'ping_url': 'b3611b24-df9c-4d36-9203-fa292820bf2a', 'create_slug': True}
|
||||||
|
flexmock(module.logger).should_receive('warning').once()
|
||||||
|
|
||||||
|
module.ping_monitor(
|
||||||
|
hook_config,
|
||||||
|
{},
|
||||||
|
'config.yaml',
|
||||||
|
state=module.monitor.State.FINISH,
|
||||||
|
monitoring_log_level=1,
|
||||||
|
dry_run=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_with_connection_error_logs_warning():
|
def test_ping_monitor_with_connection_error_logs_warning():
|
||||||
flexmock(module.borgmatic.hooks.logs).should_receive('Forgetful_buffering_handler').never()
|
flexmock(module.borgmatic.hooks.logs).should_receive('Forgetful_buffering_handler').never()
|
||||||
hook_config = {'ping_url': 'https://example.com'}
|
hook_config = {'ping_url': 'https://example.com'}
|
||||||
|
|
|
@ -217,10 +217,11 @@ def test_add_logging_level_skips_global_setting_if_already_set():
|
||||||
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_linux():
|
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_linux():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').once()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
flexmock(module).should_receive('Console_color_formatter')
|
|
||||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
level=logging.DEBUG, handlers=list
|
level=logging.DEBUG, handlers=list
|
||||||
|
@ -237,10 +238,11 @@ def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_linux(
|
||||||
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_macos():
|
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_macos():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').once()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
flexmock(module).should_receive('Console_color_formatter')
|
|
||||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
level=logging.DEBUG, handlers=list
|
level=logging.DEBUG, handlers=list
|
||||||
|
@ -258,10 +260,11 @@ def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_macos(
|
||||||
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_freebsd():
|
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_freebsd():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').once()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
flexmock(module).should_receive('Console_color_formatter')
|
|
||||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
level=logging.DEBUG, handlers=list
|
level=logging.DEBUG, handlers=list
|
||||||
|
@ -280,10 +283,11 @@ def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_freebs
|
||||||
def test_configure_logging_without_syslog_log_level_skips_syslog():
|
def test_configure_logging_without_syslog_log_level_skips_syslog():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').once()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
flexmock(module).should_receive('Console_color_formatter')
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
level=logging.INFO, handlers=list
|
level=logging.INFO, handlers=list
|
||||||
)
|
)
|
||||||
|
@ -296,10 +300,11 @@ def test_configure_logging_without_syslog_log_level_skips_syslog():
|
||||||
def test_configure_logging_skips_syslog_if_not_found():
|
def test_configure_logging_skips_syslog_if_not_found():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').once()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
flexmock(module).should_receive('Console_color_formatter')
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
level=logging.INFO, handlers=list
|
level=logging.INFO, handlers=list
|
||||||
)
|
)
|
||||||
|
@ -312,8 +317,10 @@ def test_configure_logging_skips_syslog_if_not_found():
|
||||||
def test_configure_logging_skips_log_file_if_log_file_logging_is_disabled():
|
def test_configure_logging_skips_log_file_if_log_file_logging_is_disabled():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).DISABLED = module.DISABLED
|
flexmock(module.logging).DISABLED = module.DISABLED
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').once()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
|
@ -331,8 +338,10 @@ def test_configure_logging_skips_log_file_if_log_file_logging_is_disabled():
|
||||||
def test_configure_logging_to_log_file_instead_of_syslog():
|
def test_configure_logging_to_log_file_instead_of_syslog():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').once()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
|
@ -356,8 +365,10 @@ def test_configure_logging_to_log_file_instead_of_syslog():
|
||||||
def test_configure_logging_to_both_log_file_and_syslog():
|
def test_configure_logging_to_both_log_file_and_syslog():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').once()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
|
@ -387,8 +398,10 @@ def test_configure_logging_to_log_file_formats_with_custom_log_format():
|
||||||
flexmock(module.logging).should_receive('Formatter').with_args(
|
flexmock(module.logging).should_receive('Formatter').with_args(
|
||||||
'{message}', style='{' # noqa: FS003
|
'{message}', style='{' # noqa: FS003
|
||||||
).once()
|
).once()
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').once()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||||
|
@ -413,8 +426,10 @@ def test_configure_logging_to_log_file_formats_with_custom_log_format():
|
||||||
def test_configure_logging_skips_log_file_if_argument_is_none():
|
def test_configure_logging_skips_log_file_if_argument_is_none():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').once()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
|
@ -426,11 +441,14 @@ def test_configure_logging_skips_log_file_if_argument_is_none():
|
||||||
module.configure_logging(console_log_level=logging.INFO, log_file=None)
|
module.configure_logging(console_log_level=logging.INFO, log_file=None)
|
||||||
|
|
||||||
|
|
||||||
def test_configure_logging_skips_console_color_formatter_if_color_disabled():
|
def test_configure_logging_uses_console_no_color_formatter_if_color_disabled():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
|
fake_formatter = flexmock()
|
||||||
|
flexmock(module).should_receive('Console_color_formatter').never()
|
||||||
|
flexmock(module).should_receive('Console_no_color_formatter').and_return(fake_formatter)
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').never()
|
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
|
|
Loading…
Reference in New Issue