import os
import pytest
import stat
import socket
from configparser import ConfigParser
from pathlib import Path

import backup as bckup
import utils.errors as errs
from backup import Backup
from utils.config_parse_helper import ConfigSchema

TEST_DEFAULT_CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'testing/backup_test.config')


def make_default_config( tmpdir: str
                       , backup_targs: [str] = ['bck1', 'bck2']
                       , verbose: bool = False
                       ) -> ConfigParser:
    config = ConfigParser()

    # make the paths:
    for b in backup_targs:
        os.makedirs(os.path.join(tmpdir, b))

    # Get the correct formats for the config
    backup_paths = ','.join([os.path.join(tmpdir, b) for b in backup_targs])
    source_machine = socket.gethostname()
    config['DEFAULT'] = \
        { 'backup_paths'   : backup_paths
        , 'source_machine' : source_machine
        , 'verbose'        : verbose
        }
    return config


class TestArguments(object):

    def test_default_config_file_load_default(self, tmpdir):
        backy = Backup(config_file=TEST_DEFAULT_CONFIG_FILE)
        # Default
        assert backy.default_dict['backup_paths'] == \
            [ "/export/ippops4.0/atlassian_backups"
            , "/data/ippops3.0/atlassian_backups"
            , "/data/ippops5.0/atlassian_backups"
            ]
        assert backy.default_dict['source_machine'] == "hostname_of_computer"
        assert backy.default_dict['verbose'] is True

    def test_copy_config_file_load(self):
        backy = Backup(config_file=TEST_DEFAULT_CONFIG_FILE)
        assert backy.copy_dict['sources'] == \
            [ "/some_dir_to_copy_from/"
            , "/some_dir/and_specific_file.jpeg"
            ]
        assert backy.copy_dict['additional_args'] == ['-v']

    def test_tar_config_file_load(self):
        backy = Backup(config_file=TEST_DEFAULT_CONFIG_FILE)
        assert backy.tar_dict['sources'] == \
            [ "/this_dir/"
            , "/another_dir/"
            , "/and_a_couple_files/subdir/file1.txt"
            , "/and_a_couple_files/subdir/file2.png"
            , "/and_a_couple_files/subdir/file3.pdf"
            ]
        assert backy.tar_dict['additional_args'] == [ "--verbose"
                                            , "-z"
                                            , "-p"
                                            ]
        assert backy.tar_dict['target_filename'] == "cool_tar"
        assert backy.tar_dict['prefix_date'] is True

    def test_rsync_config_file_load(self):
        backy = Backup(config_file=TEST_DEFAULT_CONFIG_FILE)
        # [Rsync]
        assert backy.rsync_dict['sources'] == [ "/dir/to/sync"]
        assert backy.rsync_dict['additional_args'] == \
            [ "--progress"
            , "-r"
            ]

    def test_mysqldump_config_file_load(self):
        backy = Backup(config_file=TEST_DEFAULT_CONFIG_FILE)
        # [Mysqldump]
        assert backy.mysqldump_dict['user'] == "dumper"
        assert backy.mysqldump_dict['password'] == "not_a_real_password"
        assert backy.mysqldump_dict['db_name'] == "jiradb"
        assert backy.mysqldump_dict['additional_args'] == ['']
        assert backy.mysqldump_dict['target_filename'] == "itsa_mysqldump"
        assert backy.mysqldump_dict['prefix_date'] is True

    def test_loading_only_defaults(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'], False)
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        backy = Backup(config, schema)

        assert backy.default_dict['backup_paths'] == \
            [ os.path.join(tmpdir, 'bck1')
            , os.path.join(tmpdir, 'bck2')
            ]
        assert backy.default_dict['source_machine'] == socket.gethostname()
        assert backy.default_dict['verbose'] is False

    def test_failure_if_no_defaults_provided(self):
        config = ConfigParser()
        with pytest.raises(errs.ConfigError) as e_empty:
            Backup(config_file=config)
        assert "is required but was not found in config" in str(e_empty)

        with pytest.raises(errs.ValidationError) as e_none:
            Backup(config_file=None)
        assert "ValidationError: Backup class must be given a config" in str(e_none)

        with pytest.raises(errs.ValidationError) as e_nofile:
            Backup(config_file="/not/a/real/file.config")
        assert "ValidationError: Config filepath does not exist" in str(e_nofile)

    def test_nothing_should_be_set_to_run_if_not_present(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'], False)
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        backy = Backup(config, schema)

        assert backy.should_run_copy is False
        assert backy.should_run_tar is False
        assert backy.should_run_rsync is False
        assert backy.should_run_mysqldump is False

    def test_hostname_assertion_fails(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        backy = Backup(config, schema)

        backy.default_dict['source_machine'] = 'not_a_real_hostname'
        assert backy.hostname_is_valid() is False

    def test_hostname_assertion_passes(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        backy = Backup(config, schema)

        actual_hostname = socket.gethostname()
        backy.default_dict['source_machine'] = actual_hostname
        assert backy.hostname_is_valid() is True


class TestFilepathVerifications(object):

    def test_default_target_paths_do_not_exist_in_testing(self):
        backy = Backup(config_file=TEST_DEFAULT_CONFIG_FILE)
        assert backy.target_backup_paths_ok() is False

    def test_raising_an_error_if_a_backup_path_does_not_exist(self):
        with pytest.raises(errs.ValidationError) as e:
            backy = Backup(config_file=TEST_DEFAULT_CONFIG_FILE)
            backy.target_backup_paths_ok(raise_error=True)
        assert "ValidationError: target path does not exist" in str(e)

    def test_an_incorrect_path_then_correct_path(self, tmpdir):
        backy = Backup(config_file=TEST_DEFAULT_CONFIG_FILE)

        backy.default_dict['backup_paths'] = ["/not/real/path"]
        assert backy.target_backup_paths_ok() is False

        backy.default_dict['backup_paths'] = [tmpdir]
        assert backy.target_backup_paths_ok() is True

    def test_backup_paths_created_if_make_dirs_given(self, tmpdir):
        config = ConfigParser()
        config['DEFAULT'] = \
            { 'backup_paths'   : f"{tmpdir}/bck1/bobby/backup, {tmpdir}/bck2"
            , 'source_machine' : 'rocket'
            , 'verbose'        : 'True'
            }
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        bck1_path   = os.path.join(tmpdir, 'bck1')
        bck1b_path  = os.path.join(tmpdir, 'bck1/bobby')
        bck1bb_path = os.path.join(tmpdir, 'bck1/bobby/backup')
        bck2_path   = os.path.join(tmpdir, 'bck2')
        assert not os.path.exists(bck1_path)
        assert not os.path.exists(bck1b_path)
        assert not os.path.exists(bck1bb_path)
        assert not os.path.exists(bck2_path)
        # backup dirs not created automatically
        Backup(config, schema)
        assert not os.path.exists(bck1_path)
        assert not os.path.exists(bck1b_path)
        assert not os.path.exists(bck1bb_path)
        assert not os.path.exists(bck2_path)

        # make_dirs included in config - when loaded dirs created.
        config['DEFAULT'] = \
            { 'backup_paths'   : f"{tmpdir}/bck1/bobby/backup, {tmpdir}/bck2"
            , 'source_machine' : 'rocket'
            , 'verbose'        : 'True'
            , 'make_dirs'      : 'True'
            }
        backy = Backup(config, schema)
        assert backy.default_dict['make_dirs'] is True
        assert os.path.exists(bck1_path)
        assert os.path.exists(bck1b_path)
        assert os.path.exists(bck1bb_path)
        assert os.path.exists(bck2_path)

        # Check permissions are 777 too
        new_stats = os.stat(bck2_path)
        assert bool(new_stats.st_mode & stat.S_IRUSR) is True
        assert bool(new_stats.st_mode & stat.S_IWUSR) is True
        assert bool(new_stats.st_mode & stat.S_IXUSR) is True
        assert bool(new_stats.st_mode & stat.S_IRGRP) is True
        assert bool(new_stats.st_mode & stat.S_IWGRP) is True
        assert bool(new_stats.st_mode & stat.S_IXGRP) is True
        assert bool(new_stats.st_mode & stat.S_IROTH) is True
        assert bool(new_stats.st_mode & stat.S_IWOTH) is True
        assert bool(new_stats.st_mode & stat.S_IXOTH) is True


class TestRunningCopy():

    def test_simple_copy(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source = os.path.join(tmpdir, "and_specific_file.jpeg")
        config['COPY'] = {'sources' : f"{source}"}
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.COPY_SCHEMA_SECTION)

        # make file to be copied
        Path(source).touch()

        backy = Backup(config, schema)
        assert backy.should_run_copy is True

        expected1file = os.path.join(tmpdir, 'bck1', 'and_specific_file.jpeg')
        expected2file = os.path.join(tmpdir, 'bck2', 'and_specific_file.jpeg')
        assert not os.path.exists(expected1file)
        assert not os.path.exists(expected2file)
        backy._run_copy()
        assert os.path.exists(expected1file)
        assert os.path.exists(expected2file)

    def test_copy_with_additional_args(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source1 = os.path.join(tmpdir, "some_dir")
        source2 = os.path.join(tmpdir, "and_specific_file.jpeg")
        config['COPY'] = \
        { 'sources' : f"{source1}, {source2}"
        , 'additional_args' : "-v --preserve -r"
        }
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.COPY_SCHEMA_SECTION)

        os.makedirs(source1)
        Path(source2).touch()

        backy = Backup(config, schema)

        expected1dir  = os.path.join(tmpdir, 'bck1', 'some_dir')
        expected1file = os.path.join(tmpdir, 'bck1', 'and_specific_file.jpeg')
        expected2dir  = os.path.join(tmpdir, 'bck2', 'some_dir')
        expected2file = os.path.join(tmpdir, 'bck2', 'and_specific_file.jpeg')
        assert not os.path.exists(expected1dir)
        assert not os.path.exists(expected1file)
        assert not os.path.exists(expected2dir)
        assert not os.path.exists(expected2file)
        backy._run_copy()
        assert os.path.exists(expected1dir)
        assert os.path.exists(expected1file)
        assert os.path.exists(expected2dir)
        assert os.path.exists(expected2file)

    def test_copy_fails_if_source_file_does_not_exist(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source = os.path.join(tmpdir, "and_specific_file.jpeg")
        config['COPY'] = \
        { 'sources' : f"{source}"
        , 'additional_args' : '-r'
        }
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.COPY_SCHEMA_SECTION)

        assert not os.path.exists(source)
        backy = Backup(config, schema)
        with pytest.raises(errs.ValidationError):
            backy._run_copy()

    def test_copy_fails_if_destination_does_not_exist(self, tmpdir):
        config = ConfigParser()

        backup_does_not_exist = os.path.join(tmpdir, 'does_not_exist')

        config['DEFAULT'] = \
        { 'backup_paths' : backup_does_not_exist
        , 'source_machine': socket.gethostname()
        }
        source = os.path.join(tmpdir, "and_specific_file.jpeg")
        Path(source).touch()
        config['COPY'] = {'sources' : f"{source}"}
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.COPY_SCHEMA_SECTION)

        assert not os.path.exists(backup_does_not_exist)
        backy = Backup(config, schema)
        with pytest.raises(errs.ValidationError):
            backy._run_copy()

    def test_copy_requires_dash_r_arg_for_copying_dirs(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source = os.path.join(tmpdir, "some_dir")
        config['COPY'] = \
        { 'sources' : f"{source}"
        }
        os.makedirs(source)
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.COPY_SCHEMA_SECTION)

        with pytest.raises(errs.SubprocessError):
            backy = Backup(config, schema)
            backy._run_copy()

        config['COPY'] = \
        { 'sources' : f"{source}"
        , 'additional_args' : '-r'
        }
        backy = Backup(config, schema)

        expected1dir  = os.path.join(tmpdir, 'bck1', 'some_dir')
        expected2dir  = os.path.join(tmpdir, 'bck2', 'some_dir')
        assert not os.path.exists(expected1dir)
        assert not os.path.exists(expected2dir)
        backy._run_copy()
        assert os.path.exists(expected1dir)
        assert os.path.exists(expected2dir)


class TestRunningTar():

    def test_simply_tar(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1'])
        source = os.path.join(tmpdir, "folder_to_tar")
        os.makedirs(source)
        # put a file in it
        Path(os.path.join(source, "a_file.txt"))

        config['TAR'] = \
        { 'sources' : f"{source}"
        , 'additional_args' : "-cf"
        , 'target_filename' : 'cool_tar.tar'
        , 'prefix_date' : 'False'
        }

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.TAR_SCHEMA_SECTION)

        backy = Backup(config, schema)

        expected_tar1 = os.path.join(tmpdir, 'bck1', 'cool_tar.tar')
        print(expected_tar1)
        assert not os.path.exists(expected_tar1)
        backy._run_tar()
        assert os.path.exists(expected_tar1)

    def test_tar_with_multiple_sources_and_date(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source1 = os.path.join(tmpdir, "folder_to_tar")
        source2 = os.path.join(tmpdir, "another_folder_to_tar")
        os.makedirs(source1)
        os.makedirs(source2)
        # put a file in it
        Path(os.path.join(source1, "a_file.txt"))
        Path(os.path.join(source2, "other.file"))

        config['TAR'] = \
        { 'sources' : f"{source1}, {source2}"
        , 'additional_args' : "-cf"
        , 'target_filename' : 'cool_tar.tar'
        , 'prefix_date' : 'True'
        }

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.TAR_SCHEMA_SECTION)

        backy = Backup(config, schema)
        assert backy.should_run_tar is True

        date = bckup.get_date()

        expected_tar1 = os.path.join(tmpdir, 'bck1', f'{date}_cool_tar.tar')
        expected_tar2 = os.path.join(tmpdir, 'bck2', f'{date}_cool_tar.tar')
        assert not os.path.exists(expected_tar1)
        assert not os.path.exists(expected_tar2)
        backy._run_tar()
        assert os.path.exists(expected_tar1)
        assert os.path.exists(expected_tar2)


class TestRunningRsync():

    def test_simple_rsync(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source = os.path.join(tmpdir, "and_specific_file.jpeg")
        config['RSYNC'] = {'sources' : f"{source}"}
        # make file to be copied
        Path(source).touch()

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.RSYNC_SCHEMA_SECTION)

        backy = Backup(config, schema)

        expected1file = os.path.join(tmpdir, 'bck1', 'and_specific_file.jpeg')
        expected2file = os.path.join(tmpdir, 'bck2', 'and_specific_file.jpeg')
        assert not os.path.exists(expected1file)
        assert not os.path.exists(expected2file)
        backy._run_rsync()
        assert os.path.exists(expected1file)
        assert os.path.exists(expected2file)

    def test_rsync_with_additional_args(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source1 = os.path.join(tmpdir, "some_dir")
        source2 = os.path.join(tmpdir, "and_specific_file.jpeg")
        config['RSYNC'] = \
        { 'sources' : f"{source1}, {source2}"
        , 'additional_args' : "-v -z -r --progress"
        }

        os.makedirs(source1)
        Path(source2).touch()

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.RSYNC_SCHEMA_SECTION)

        backy = Backup(config, schema)

        expected1dir  = os.path.join(tmpdir, 'bck1', 'some_dir')
        expected1file = os.path.join(tmpdir, 'bck1', 'and_specific_file.jpeg')
        expected2dir  = os.path.join(tmpdir, 'bck2', 'some_dir')
        expected2file = os.path.join(tmpdir, 'bck2', 'and_specific_file.jpeg')
        assert not os.path.exists(expected1dir)
        assert not os.path.exists(expected1file)
        assert not os.path.exists(expected2dir)
        assert not os.path.exists(expected2file)
        backy._run_rsync()
        assert os.path.exists(expected1dir)
        assert os.path.exists(expected1file)
        assert os.path.exists(expected2dir)
        assert os.path.exists(expected2file)

    def test_rsync_fails_if_source_file_does_not_exist(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source = os.path.join(tmpdir, "and_specific_file.jpeg")
        config['RSYNC'] = \
        { 'sources' : f"{source}"
        , 'additional_args' : '-r'
        }

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.RSYNC_SCHEMA_SECTION)

        assert not os.path.exists(source)
        backy = Backup(config, schema)
        with pytest.raises(errs.ValidationError):
            backy._run_rsync()

    def test_rsync_fails_if_destination_does_not_exist(self, tmpdir):
        config = ConfigParser()

        backup_does_not_exist = os.path.join(tmpdir, 'does_not_exist')

        config['DEFAULT'] = \
        { 'backup_paths' : backup_does_not_exist
        , 'source_machine': socket.gethostname()
        }
        source = os.path.join(tmpdir, "and_specific_file.jpeg")
        Path(source).touch()
        config['RSYNC'] = {'sources' : f"{source}"}

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.RSYNC_SCHEMA_SECTION)

        assert not os.path.exists(backup_does_not_exist)
        backy = Backup(config, schema)
        with pytest.raises(errs.ValidationError):
            backy._run_rsync()

    def test_rsync_requires_dash_r_arg_for_copying_only_dirs(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source = os.path.join(tmpdir, "some_dir")
        config['RSYNC'] = \
        { 'sources' : f"{source}"
        }
        os.makedirs(source)

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.RSYNC_SCHEMA_SECTION)

        backy = Backup(config, schema)

        expected1dir  = os.path.join(tmpdir, 'bck1', 'some_dir')
        expected2dir  = os.path.join(tmpdir, 'bck2', 'some_dir')
        assert not os.path.exists(expected1dir)
        assert not os.path.exists(expected2dir)
        backy._run_rsync()
        assert not os.path.exists(expected1dir)
        assert not os.path.exists(expected2dir)

        config['RSYNC'] = \
        { 'sources' : f"{source}"
        , 'additional_args' : '-r'
        }

        backy = Backup(config, schema)
        expected1dir  = os.path.join(tmpdir, 'bck1', 'some_dir')
        expected2dir  = os.path.join(tmpdir, 'bck2', 'some_dir')
        assert not os.path.exists(expected1dir)
        assert not os.path.exists(expected2dir)
        backy._run_rsync()
        assert os.path.exists(expected1dir)
        assert os.path.exists(expected2dir)

    def test_rsync_with_delete_after_options(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source_dir = os.path.join(tmpdir, "some_dir")
        source_file1 = os.path.join(source_dir, "file1")
        source_file2 = os.path.join(source_dir, "file2")
        source_file3 = os.path.join(source_dir, "file3")
        config['RSYNC'] = \
        { 'sources' : f"{source_dir}"
        , 'additional_args' : "-v -z -r --progress"
        }

        os.makedirs(source_dir)
        Path(source_file1).touch()
        Path(source_file2).touch()
        Path(source_file3).touch()

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.RSYNC_SCHEMA_SECTION)

        backy = Backup(config, schema)

        expected1dir  = os.path.join(tmpdir, 'bck1', 'some_dir', 'file1')
        expected2dir  = os.path.join(tmpdir, 'bck1', 'some_dir', 'file2')
        expected3dir  = os.path.join(tmpdir, 'bck1', 'some_dir', 'file3')
        assert not os.path.exists(expected1dir)
        assert not os.path.exists(expected2dir)
        assert not os.path.exists(expected3dir)
        backy._run_rsync()
        print(expected1dir)
        assert os.path.exists(expected1dir)
        assert os.path.exists(expected2dir)
        assert os.path.exists(expected3dir)

        # Delete two files, create a new one.
        os.remove(source_file2)
        os.remove(source_file3)
        source_file4 = os.path.join(source_dir, "file4")
        Path(source_file4).touch()
        expected4dir = os.path.join(tmpdir, 'bck1', 'some_dir', 'file4')

        # Rsync will add 4, and 2 and 3 should still exist
        backy._run_rsync()
        assert os.path.exists(expected1dir)
        assert os.path.exists(expected2dir)
        assert os.path.exists(expected3dir)
        assert os.path.exists(expected4dir)

        # with delete after option removed files are cleaned up
        config['RSYNC'] = \
        { 'sources' : f"{source_dir}"
        , 'additional_args' : '-v -z -r --delete-after'
        }
        backy = Backup(config, schema)
        backy._run_rsync()
        assert os.path.exists(expected1dir)
        assert not os.path.exists(expected2dir)
        assert not os.path.exists(expected3dir)
        assert os.path.exists(expected4dir)

    def test_rsync_with_subdirs_option(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source1 = os.path.join(tmpdir, "some_dir")
        source2 = os.path.join(tmpdir, "some_other_dir")
        file_in_dir = os.path.join(source1, "and_specific_file.jpeg")
        file_in_other_dir = os.path.join(source2, "another_file.jpeg")
        os.makedirs(source1)
        os.makedirs(source2)
        Path(file_in_dir).touch()
        Path(file_in_other_dir).touch()

        config['RSYNC'] = \
        { 'sources' : f"{source1}, {source2}"
        , 'additional_args' : "-a --progress -v"
        , 'sub-dirs' : "subdir_with_better_name1, subdir_with_better_name2"
        }

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.RSYNC_SCHEMA_SECTION)

        backy = Backup(config, schema)

        expected_bak1_src1_dir  = os.path.join(tmpdir, 'bck1', 'subdir_with_better_name1', 'some_dir')
        expected_bak1_src1_file = os.path.join(tmpdir, 'bck1', 'subdir_with_better_name1', 'some_dir', 'and_specific_file.jpeg')
        expected_bak1_src2_dir  = os.path.join(tmpdir, 'bck1', 'subdir_with_better_name2', 'some_other_dir')
        expected_bak1_src2_file = os.path.join(tmpdir, 'bck1', 'subdir_with_better_name2', 'some_other_dir', 'another_file.jpeg')
        expected_bak2_src1_dir  = os.path.join(tmpdir, 'bck2', 'subdir_with_better_name1', 'some_dir')
        expected_bak2_src1_file = os.path.join(tmpdir, 'bck2', 'subdir_with_better_name1', 'some_dir', 'and_specific_file.jpeg')
        expected_bak2_src2_dir  = os.path.join(tmpdir, 'bck2', 'subdir_with_better_name2', 'some_other_dir')
        expected_bak2_src2_file = os.path.join(tmpdir, 'bck2', 'subdir_with_better_name2', 'some_other_dir', 'another_file.jpeg')
        assert not os.path.exists(expected_bak1_src1_dir)
        assert not os.path.exists(expected_bak1_src1_file)
        assert not os.path.exists(expected_bak1_src2_dir)
        assert not os.path.exists(expected_bak1_src2_file)
        assert not os.path.exists(expected_bak2_src1_dir)
        assert not os.path.exists(expected_bak2_src1_file)
        assert not os.path.exists(expected_bak2_src2_dir)
        assert not os.path.exists(expected_bak2_src2_file)
        backy._run_rsync()
        assert os.path.exists(expected_bak1_src1_dir)
        assert os.path.exists(expected_bak1_src1_file)
        assert os.path.exists(expected_bak1_src2_dir)
        assert os.path.exists(expected_bak1_src2_file)
        assert os.path.exists(expected_bak2_src1_dir)
        assert os.path.exists(expected_bak2_src1_file)
        assert os.path.exists(expected_bak2_src2_dir)
        assert os.path.exists(expected_bak2_src2_file)

@pytest.mark.skip(reason="Requires a fake database to be setup")
class TestMysqlBackup(object):
    """ The mySQL dump tests require the machine on which they are
running to have a mysql database running with a user called
'dumper' and a password set to 'not_a_real_password'.

In production, a proper password should be set on the server and in the config
file. DO NOT USE THE ABOVE IN PRODUCTION

To change the password:
UPDATE mysql.user SET authentication_string = PASSWORD('NEW_USER_PASSWORD')WHERE User = 'user-name' AND Host = 'localhost';FLUSH PRIVILEGES;
"""

    def test_mysql_dump_is_ok(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        config['MYSQLDUMP'] = \
        { 'user' : 'dumper'
        , 'password' : 'not_a_real_password'
        , 'db_name' : 'jiradb'
        , 'target_filename' : "cool_dump.sql"
        , 'prefix_date' : True
        }

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.MYSQLDUMP_SCHEMA_SECTION)

        backy = Backup(config, schema)
        assert backy.should_run_mysqldump is True

        date = bckup.get_date()

        expected_dump1 = os.path.join(tmpdir, 'bck1', f'{date}_cool_dump.sql')
        expected_dump2 = os.path.join(tmpdir, 'bck2', f'{date}_cool_dump.sql')
        assert not os.path.exists(expected_dump1)
        assert not os.path.exists(expected_dump2)
        result = backy._run_mysqldump()
        assert os.path.exists(expected_dump1)
        assert os.path.exists(expected_dump2)

        assert result == 0

    def test_mysql_dump_fails_with_incorrect_password(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        config['MYSQLDUMP'] = \
        { 'user' : 'dumper'
        , 'password' : 'incorrect_password'
        , 'db_name' : 'jiradb'
        , 'target_filename' : "cool_dump.sql"
        , 'prefix_date' : True
        }

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.MYSQLDUMP_SCHEMA_SECTION)

        backy = Backup(config, schema)
        with pytest.raises(errs.SubprocessError):
            backy._run_mysqldump()


# class TestRunningAll(object):

#     def test_running_copy_tar_and_rsyc(self):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])


class TestSimpleLogWriting(object):

    def test_log_writing_success(self, tmpdir):
        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)

        backy = Backup(config, schema)

        expected_log1 = os.path.join(tmpdir, 'bck1', 'latest_run.log')
        expected_log2 = os.path.join(tmpdir, 'bck2', 'latest_run.log')
        assert not os.path.exists(expected_log1)
        assert not os.path.exists(expected_log2)
        backy.run_backup_processes()
        assert os.path.exists(expected_log1)
        assert os.path.exists(expected_log2)

        # Read file
        with open(expected_log1, "r") as logf:
            lines = logf.readlines()
            assert len(lines) == 2
            assert "Run started at: " in lines[0]
            assert "Last successful backup completed at :" in lines[1]

        with open(expected_log2, "r") as logf:
            lines = logf.readlines()
            assert len(lines) == 2
            assert "Run started at: " in lines[0]
            assert "Last successful backup completed at :" in lines[1]

    def test_log_writes_only_attempted_if_failure_encountered(self, tmpdir):

        config = make_default_config(tmpdir, ['bck1', 'bck2'])
        source = os.path.join(tmpdir, "and_specific_file.jpeg")
        config['COPY'] = \
        { 'sources' : f"{source}"
        , 'additional_args' : '-r'
        }
        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.COPY_SCHEMA_SECTION)

        backy = Backup(config, schema)

        expected_log1 = os.path.join(tmpdir, 'bck1', 'latest_run.log')
        expected_log2 = os.path.join(tmpdir, 'bck2', 'latest_run.log')
        assert not os.path.exists(expected_log1)
        assert not os.path.exists(expected_log2)
        # fails due to missing source file in copy
        with pytest.raises(errs.ValidationError):
            backy.run_backup_processes()

        assert os.path.exists(expected_log1)
        assert os.path.exists(expected_log2)

        with open(expected_log1, "r") as logf:
            lines = logf.readlines()
            assert len(lines) == 1
            assert "Run started at: " in lines[0]

        with open(expected_log2, "r") as logf:
            lines = logf.readlines()
            assert len(lines) == 1
            assert "Run started at: " in lines[0]
