| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 | import ioimport osimport stringimport sysimport pytestfrom flexmock import flexmockfrom borgmatic.config import validate as moduledef test_schema_filename_returns_plausible_path():    schema_path = module.schema_filename()    assert schema_path.endswith('/schema.yaml')def mock_config_and_schema(config_yaml, schema_yaml=None):    '''    Set up mocks for the given config config YAML string and the schema YAML string, or the default    schema if no schema is provided. The idea is that that the code under test consumes these mocks    when parsing the configuration.    '''    config_stream = io.StringIO(config_yaml)    config_stream.name = 'config.yaml'    if schema_yaml is None:        schema_stream = open(module.schema_filename())    else:        schema_stream = io.StringIO(schema_yaml)        schema_stream.name = 'schema.yaml'    builtins = flexmock(sys.modules['builtins'])    flexmock(module.os).should_receive('getcwd').and_return('/tmp')    flexmock(module.os.path).should_receive('isabs').and_return(False)    flexmock(module.os.path).should_receive('exists').and_return(True)    builtins.should_receive('open').with_args('/tmp/config.yaml').and_return(config_stream)    builtins.should_receive('open').with_args('/tmp/schema.yaml').and_return(schema_stream)def test_parse_configuration_transforms_file_into_mapping():    mock_config_and_schema(        '''        source_directories:            - /home            - /etc        repositories:            - path: hostname.borg        keep_minutely: 60        keep_hourly: 24        keep_daily: 7        checks:            - name: repository            - name: archives        '''    )    config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')    assert config == {        'source_directories': ['/home', '/etc'],        'repositories': [{'path': 'hostname.borg'}],        'keep_daily': 7,        'keep_hourly': 24,        'keep_minutely': 60,        'checks': [{'name': 'repository'}, {'name': 'archives'}],        'bootstrap': {},    }    assert config_paths == {'/tmp/config.yaml'}    assert logs == []def test_parse_configuration_passes_through_quoted_punctuation():    escaped_punctuation = string.punctuation.replace('\\', r'\\').replace('"', r'\"')    mock_config_and_schema(        f'''        source_directories:            - "/home/{escaped_punctuation}"        repositories:            - path: test.borg        '''    )    config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')    assert config == {        'source_directories': [f'/home/{string.punctuation}'],        'repositories': [{'path': 'test.borg'}],        'bootstrap': {},    }    assert config_paths == {'/tmp/config.yaml'}    assert logs == []def test_parse_configuration_with_schema_lacking_examples_does_not_raise():    mock_config_and_schema(        '''        source_directories:            - /home        repositories:            - path: hostname.borg        ''',        '''        map:            source_directories:                required: true                seq:                    - type: scalar            repositories:                required: true                seq:                    - type: scalar        ''',    )    module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')def test_parse_configuration_inlines_include_inside_deprecated_section():    mock_config_and_schema(        '''        source_directories:            - /home        repositories:            - path: hostname.borg        retention:            !include include.yaml        '''    )    builtins = flexmock(sys.modules['builtins'])    include_file = io.StringIO(        '''        keep_daily: 7        keep_hourly: 24        '''    )    include_file.name = 'include.yaml'    builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)    config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')    assert config == {        'source_directories': ['/home'],        'repositories': [{'path': 'hostname.borg'}],        'keep_daily': 7,        'keep_hourly': 24,        'bootstrap': {},    }    assert config_paths == {'/tmp/include.yaml', '/tmp/config.yaml'}    assert len(logs) == 1def test_parse_configuration_merges_include():    mock_config_and_schema(        '''        source_directories:            - /home        repositories:            - path: hostname.borg        keep_daily: 1        <<: !include include.yaml        '''    )    builtins = flexmock(sys.modules['builtins'])    include_file = io.StringIO(        '''        keep_daily: 7        keep_hourly: 24        '''    )    include_file.name = 'include.yaml'    builtins.should_receive('open').with_args('/tmp/include.yaml').and_return(include_file)    config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')    assert config == {        'source_directories': ['/home'],        'repositories': [{'path': 'hostname.borg'}],        'keep_daily': 1,        'keep_hourly': 24,        'bootstrap': {},    }    assert config_paths == {'/tmp/include.yaml', '/tmp/config.yaml'}    assert logs == []def test_parse_configuration_raises_for_missing_config_file():    with pytest.raises(FileNotFoundError):        module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')def test_parse_configuration_raises_for_missing_schema_file():    mock_config_and_schema('')    builtins = flexmock(sys.modules['builtins'])    builtins.should_receive('open').with_args('/tmp/config.yaml').and_return(        io.StringIO('foo: bar')    )    builtins.should_receive('open').with_args('/tmp/schema.yaml').and_raise(FileNotFoundError)    with pytest.raises(FileNotFoundError):        module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')def test_parse_configuration_raises_for_syntax_error():    mock_config_and_schema('foo:\nbar')    with pytest.raises(ValueError):        module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')def test_parse_configuration_raises_for_validation_error():    mock_config_and_schema(        '''        source_directories: yes        repositories:            - path: hostname.borg        '''    )    with pytest.raises(module.Validation_error):        module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')def test_parse_configuration_applies_overrides():    mock_config_and_schema(        '''        source_directories:            - /home        repositories:            - path: hostname.borg        local_path: borg1        '''    )    config, config_paths, logs = module.parse_configuration(        '/tmp/config.yaml', '/tmp/schema.yaml', overrides=['local_path=borg2']    )    assert config == {        'source_directories': ['/home'],        'repositories': [{'path': 'hostname.borg'}],        'local_path': 'borg2',        'bootstrap': {},    }    assert config_paths == {'/tmp/config.yaml'}    assert logs == []def test_parse_configuration_applies_normalization_after_environment_variable_interpolation():    mock_config_and_schema(        '''        location:            source_directories:                - /home            repositories:                - ${NO_EXIST:-user@hostname:repo}            exclude_if_present: .nobackup        '''    )    flexmock(os).should_receive('getenv').replace_with(lambda variable_name, default: default)    config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')    assert config == {        'source_directories': ['/home'],        'repositories': [{'path': 'ssh://user@hostname/./repo'}],        'exclude_if_present': ['.nobackup'],        'bootstrap': {},    }    assert config_paths == {'/tmp/config.yaml'}    assert logs
 |