From f581f4b8d988029a4b17e9da064919b0c89d329f Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Sun, 9 Jul 2017 11:41:55 -0700 Subject: [PATCH] More test coverage, and simplification of config generation. --- .hgignore | 1 + borgmatic/commands/generate_config.py | 39 ++++++++++ borgmatic/config/generate.py | 72 +++++++++---------- .../tests/integration/config/test_generate.py | 43 +++++++++++ borgmatic/tests/unit/config/test_generate.py | 49 +++++++++++++ setup.py | 1 + 6 files changed, 165 insertions(+), 40 deletions(-) create mode 100644 borgmatic/commands/generate_config.py create mode 100644 borgmatic/tests/integration/config/test_generate.py create mode 100644 borgmatic/tests/unit/config/test_generate.py diff --git a/.hgignore b/.hgignore index f41ba93..fd7c96d 100644 --- a/.hgignore +++ b/.hgignore @@ -3,6 +3,7 @@ syntax: glob *.pyc *.swp .cache +.coverage .tox build dist diff --git a/borgmatic/commands/generate_config.py b/borgmatic/commands/generate_config.py new file mode 100644 index 0000000..f326d6a --- /dev/null +++ b/borgmatic/commands/generate_config.py @@ -0,0 +1,39 @@ +from __future__ import print_function +from argparse import ArgumentParser +import os +from subprocess import CalledProcessError +import sys + +from ruamel import yaml + +from borgmatic import borg +from borgmatic.config import convert, generate, validate + + +DEFAULT_DESTINATION_CONFIG_FILENAME = '/etc/borgmatic/config.yaml' + + +def parse_arguments(*arguments): + ''' + Given command-line arguments with which this script was invoked, parse the arguments and return + them as an ArgumentParser instance. + ''' + parser = ArgumentParser(description='Generate a sample borgmatic YAML configuration file.') + parser.add_argument( + '-d', '--destination', + dest='destination_filename', + default=DEFAULT_DESTINATION_CONFIG_FILENAME, + help='Destination YAML configuration filename. Default: {}'.format(DEFAULT_DESTINATION_CONFIG_FILENAME), + ) + + return parser.parse_args(arguments) + + +def main(): + try: + args = parse_arguments(*sys.argv[1:]) + + generate.generate_sample_configuration(args.destination_filename, validate.schema_filename()) + except (ValueError, OSError) as error: + print(error, file=sys.stderr) + sys.exit(1) diff --git a/borgmatic/config/generate.py b/borgmatic/config/generate.py index cf4861b..66d6c44 100644 --- a/borgmatic/config/generate.py +++ b/borgmatic/config/generate.py @@ -6,15 +6,6 @@ from ruamel import yaml INDENT = 4 -def write_configuration(config_filename, config): - ''' - Given a target config filename and a config data structure of nested OrderedDicts, write out the - config to file as YAML. - ''' - with open(config_filename, 'w') as config_file: - config_file.write(yaml.round_trip_dump(config, indent=INDENT, block_seq_indent=INDENT)) - - def _insert_newline_before_comment(config, field_name): ''' Using some ruamel.yaml black magic, insert a blank line in the config right befor the given @@ -26,6 +17,37 @@ def _insert_newline_before_comment(config, field_name): ) +def _schema_to_sample_configuration(schema, level=0): + ''' + Given a loaded configuration schema, generate and return sample config for it. Include comments + for each section based on the schema "desc" description. + ''' + example = schema.get('example') + if example: + return example + + config = yaml.comments.CommentedMap([ + ( + section_name, + _schema_to_sample_configuration(section_schema, level + 1), + ) + for section_name, section_schema in schema['map'].items() + ]) + + add_comments_to_configuration(config, schema, indent=(level * INDENT)) + + return config + + +def write_configuration(config_filename, config): + ''' + Given a target config filename and a config data structure of nested OrderedDicts, write out the + config to file as YAML. + ''' + with open(config_filename, 'w') as config_file: + config_file.write(yaml.round_trip_dump(config, indent=INDENT, block_seq_indent=INDENT)) + + def add_comments_to_configuration(config, schema, indent=0): ''' Using descriptions from a schema as a source, add those descriptions as comments to the given @@ -37,7 +59,7 @@ def add_comments_to_configuration(config, schema, indent=0): description = field_schema.get('desc') # No description to use? Skip it. - if not schema or not description: + if not field_schema or not description: continue config.yaml_set_comment_before_after_key( @@ -49,36 +71,6 @@ def add_comments_to_configuration(config, schema, indent=0): _insert_newline_before_comment(config, field_name) -def _section_schema_to_sample_configuration(section_schema): - ''' - Given the schema for a particular config section, generate and return sample config for that - section. Include comments for each field based on the schema "desc" description. - ''' - section_config = yaml.comments.CommentedMap([ - (field_name, field_schema['example']) - for field_name, field_schema in section_schema['map'].items() - ]) - - add_comments_to_configuration(section_config, section_schema, indent=INDENT) - - return section_config - - -def _schema_to_sample_configuration(schema): - ''' - Given a loaded configuration schema, generate and return sample config for it. Include comments - for each section based on the schema "desc" description. - ''' - config = yaml.comments.CommentedMap([ - (section_name, _section_schema_to_sample_configuration(section_schema)) - for section_name, section_schema in schema['map'].items() - ]) - - add_comments_to_configuration(config, schema) - - return config - - def generate_sample_configuration(config_filename, schema_filename): ''' Given a target config filename and the path to a schema filename in pykwalify YAML schema diff --git a/borgmatic/tests/integration/config/test_generate.py b/borgmatic/tests/integration/config/test_generate.py new file mode 100644 index 0000000..89d8467 --- /dev/null +++ b/borgmatic/tests/integration/config/test_generate.py @@ -0,0 +1,43 @@ +from io import StringIO +import sys + +from flexmock import flexmock + +from borgmatic.config import generate as module + + +def test_insert_newline_before_comment_does_not_raise(): + field_name = 'foo' + config = module.yaml.comments.CommentedMap([(field_name, 33)]) + config.yaml_set_comment_before_after_key(key=field_name, before='Comment',) + + module._insert_newline_before_comment(config, field_name) + + +def test_write_configuration_does_not_raise(): + builtins = flexmock(sys.modules['builtins']) + builtins.should_receive('open').and_return(StringIO()) + + module.write_configuration('config.yaml', {}) + + +def test_add_comments_to_configuration_does_not_raise(): + # Ensure that it can deal with fields both in the schema and missing from the schema. + config = module.yaml.comments.CommentedMap([('foo', 33), ('bar', 44), ('baz', 55)]) + schema = { + 'map': { + 'foo': {'desc': 'Foo'}, + 'bar': {'desc': 'Bar'}, + } + } + + module.add_comments_to_configuration(config, schema) + + +def test_generate_sample_configuration_does_not_raise(): + builtins = flexmock(sys.modules['builtins']) + builtins.should_receive('open').with_args('schema.yaml').and_return('') + flexmock(module).should_receive('write_configuration') + flexmock(module).should_receive('_schema_to_sample_configuration') + + module.generate_sample_configuration('config.yaml', 'schema.yaml') diff --git a/borgmatic/tests/unit/config/test_generate.py b/borgmatic/tests/unit/config/test_generate.py new file mode 100644 index 0000000..3536f23 --- /dev/null +++ b/borgmatic/tests/unit/config/test_generate.py @@ -0,0 +1,49 @@ +from collections import OrderedDict + +from flexmock import flexmock + +from borgmatic.config import generate as module + + +def test_schema_to_sample_configuration_generates_config_with_examples(): + flexmock(module.yaml.comments).should_receive('CommentedMap').replace_with(OrderedDict) + flexmock(module).should_receive('add_comments_to_configuration') + schema = { + 'map': OrderedDict([ + ( + 'section1', { + 'map': { + 'field1': OrderedDict([ + ('example', 'Example 1') + ]), + }, + }, + ), + ( + 'section2', { + 'map': OrderedDict([ + ('field2', {'example': 'Example 2'}), + ('field3', {'example': 'Example 3'}), + ]), + } + ), + ]) + } + + config = module._schema_to_sample_configuration(schema) + + assert config == OrderedDict([ + ( + 'section1', + OrderedDict([ + ('field1', 'Example 1'), + ]), + ), + ( + 'section2', + OrderedDict([ + ('field2', 'Example 2'), + ('field3', 'Example 3'), + ]), + ) + ]) diff --git a/setup.py b/setup.py index 8ffeaba..6e9d53a 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ setup( 'console_scripts': [ 'borgmatic = borgmatic.commands.borgmatic:main', 'convert-borgmatic-config = borgmatic.commands.convert_config:main', + 'generate-borgmatic-config = borgmatic.commands.generate_config:main', ] }, obsoletes=[