From d7d94524c6e2a9dd7dfe108d4f1be809a81c58d3 Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Thu, 14 Nov 2019 12:39:46 +0100 Subject: [PATCH 1/6] Begin reworking tests Progress on #237 --- tests/test_backups.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/test_backups.py b/tests/test_backups.py index 28ad03a2..04d781a9 100644 --- a/tests/test_backups.py +++ b/tests/test_backups.py @@ -43,6 +43,7 @@ def setup_method(): # Create all dotfiles and dotfolders for file in DOTFILES: + print(f"Creating {file}") with open(file, "w+") as f: f.write(TEST_TEXT_CONTENT) @@ -55,18 +56,20 @@ def setup_method(): @staticmethod def teardown_method(): for folder in DIRS: + print(f"Removing {folder}") shutil.rmtree(folder) def test_backup_dotfiles(self): """ Test backing up dotfiles and dotfolders. """ - dotfiles_path = os.path.join(BACKUP_DIR, "dotfiles") - backup_dotfiles(dotfiles_path, home_path=FAKE_HOME_DIR, skip=True) - assert os.path.isdir(dotfiles_path) + backup_dest_path = os.path.join(BACKUP_DIR, "dotfiles") + backup_dotfiles(backup_dest_path, home_path=FAKE_HOME_DIR, skip=True) + assert os.path.isdir(backup_dest_path) for path in DOTFILES: - print("DOTFILES DIRECTORY CONTENTS:", os.listdir(dotfiles_path)) - print(path + " being backed up.") + print(f"BACKUP DESTINATION DIRECTORY ({backup_dest_path}) CONTENTS:", os.listdir(backup_dest_path)) + print(path + " should already be backed up.") print("CWD:", os.getcwd()) - backed_up_dot = os.path.join(dotfiles_path, os.path.split(path)[-1]) + backed_up_dot = os.path.join(backup_dest_path, os.path.split(path)[-1]) + print(f"Backed up dot: {backed_up_dot}") assert os.path.isfile(backed_up_dot) From 42f4b59445bb6ccc20c0502d343051b3df18484c Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Thu, 14 Nov 2019 14:19:57 +0100 Subject: [PATCH 2/6] Respect XDG Base Directory spec (#239) Fix #236 --- shallow_backup/__main__.py | 3 +++ shallow_backup/config.py | 10 ++++----- shallow_backup/upgrade.py | 46 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 shallow_backup/upgrade.py diff --git a/shallow_backup/__main__.py b/shallow_backup/__main__.py index 1c640f78..16aa57c2 100644 --- a/shallow_backup/__main__.py +++ b/shallow_backup/__main__.py @@ -6,6 +6,8 @@ from .utils import ( mkdir_warn_overwrite, destroy_backup_dir, expand_to_abs_path, new_dir_is_valid) +from .config import * +from .upgrade import upgrade_from_pre_v3 # custom help options @@ -40,6 +42,7 @@ def cli(all, configs, delete_config, destroy_backup, dotfiles, fonts, new_path, Written by Aaron Lichtman (@alichtman). """ + upgrade_from_pre_v3() # Process CLI args admin_action = any([version, delete_config, destroy_backup, show]) diff --git a/shallow_backup/config.py b/shallow_backup/config.py index 6e31ef46..995c4bc0 100644 --- a/shallow_backup/config.py +++ b/shallow_backup/config.py @@ -1,12 +1,13 @@ import sys import json +from os import path, environ from .printing import * from .compatibility import * -from .utils import home_prefix def get_config_path(): - return home_prefix(".shallow-backup") + xdg_config_home = environ.get('XDG_CONFIG_HOME') or path.join(path.expanduser('~'), '.config') + return path.join(xdg_config_home, "shallow-backup", "shallow-backup.conf") def get_config(): @@ -18,7 +19,7 @@ def get_config(): with open(config_path) as f: try: config = json.load(f) - except json.decoder.JSONDecodeError as e: + except json.decoder.JSONDecodeError: print_red_bold(f"ERROR: Invalid syntax in {config_path}") sys.exit(1) return config @@ -44,7 +45,7 @@ def get_default_config(): ".gitconfig", ".profile", ".pypirc", - ".shallow-backup", + f"{get_config_path}", ".tmux.conf", ".vimrc", ".zlogin", @@ -121,4 +122,3 @@ def show_config(): print_red_bold("\n{}: ".format(section.capitalize())) for item in contents: print(" {}".format(item)) - diff --git a/shallow_backup/upgrade.py b/shallow_backup/upgrade.py new file mode 100644 index 00000000..3ae3f479 --- /dev/null +++ b/shallow_backup/upgrade.py @@ -0,0 +1,46 @@ +import os +import sys +from shutil import move +from colorama import Fore +from .config import get_config_path +from .printing import prompt_yes_no, print_green_bold, print_red_bold +from .utils import home_prefix, safe_mkdir + + +def upgrade_from_pre_v3(): + """ + Before v3.0, the config file was stored at ~/.shallow-backup. In v3.0, + the XDG Base Directory specification was adopted and the new config is + stored in either $XDG_CONFIG_HOME/shallow-backup/shallow-backup.conf or + ~/.config/shallow-backup/shallow-backup.conf. This method upgrades from + v < 3.0 to v3.0 if required. + """ + old_config_name = ".shallow-backup" + old_config_path = home_prefix(old_config_name) + if os.path.isfile(old_config_path): + if prompt_yes_no("Config file from a version before v3.0 detected. Would you like to upgrade?", Fore.GREEN): + new_config_path = get_config_path() + print_green_bold(f"Moving {old_config_path} to {new_config_path}") + if os.path.exists(new_config_path): + print_red_bold(f"ERROR: {new_config_path} already exists. Manual intervention is required.") + sys.exit(1) + + safe_mkdir(os.path.split(new_config_path)[0]) + move(old_config_path, new_config_path) + + print_green_bold("Replacing old shallow-backup config path with new config path in config file.") + with open(new_config_path, "r") as f: + contents = f.read() + contents = contents.replace(old_config_name, + new_config_path.replace(os.path.expanduser('~') + "/", "")) + + with open(new_config_path, "w") as f: + f.write(contents) + + print_green_bold("Successful upgrade.") + else: + print_red_bold("Please downgrade to a version of shallow-backup before v3.0 if you do not want to upgrade your config.") + sys.exit() + elif os.path.isdir(old_config_path): + print_red_bold(f"ERROR: {old_config_path} is a directory, when we were expecting a file. Manual intervention is required.") + sys.exit(1) From e9ae617854936bca727aaec36419d391777787d3 Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Thu, 14 Nov 2019 15:33:45 +0100 Subject: [PATCH 3/6] Fix config creation bug --- shallow_backup/config.py | 3 +++ shallow_backup/constants.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/shallow_backup/config.py b/shallow_backup/config.py index 995c4bc0..862db71b 100644 --- a/shallow_backup/config.py +++ b/shallow_backup/config.py @@ -3,6 +3,7 @@ from os import path, environ from .printing import * from .compatibility import * +from .utils import safe_mkdir def get_config_path(): @@ -75,9 +76,11 @@ def safe_create_config(): if not os.path.exists(backup_config_path): print_path_blue("Creating config file at:", backup_config_path) backup_config = get_default_config() + safe_mkdir(os.path.split(backup_config_path)[0]) write_config(backup_config) else: # If it does exist, make sure it's not outdated. + # TODO: Move this to upgrade.py with open(backup_config_path) as config: if "[USER]" in config.readline().strip(): if prompt_yes_no("An outdated config file has been detected. Would you like to update this?", diff --git a/shallow_backup/constants.py b/shallow_backup/constants.py index 195b0042..c5fe1a46 100644 --- a/shallow_backup/constants.py +++ b/shallow_backup/constants.py @@ -1,6 +1,6 @@ class ProjInfo: PROJECT_NAME = 'shallow-backup' - VERSION = '2.8' + VERSION = '3.0' AUTHOR_GITHUB = 'alichtman' AUTHOR_FULL_NAME = 'Aaron Lichtman' DESCRIPTION = "Easily create lightweight backups of installed packages, dotfiles, and more." From b9127a502f5050f5e7812830306ec796a33e04ee Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Fri, 15 Nov 2019 07:07:39 +0100 Subject: [PATCH 4/6] Get all tests to pass Fix #237 --- shallow_backup/config.py | 8 +++++-- shallow_backup/git_wrapper.py | 1 - tests/test_backups.py | 28 +++++----------------- tests/test_copies.py | 40 +++++++++++++++---------------- tests/test_git_folder_moving.py | 23 ++++++++---------- tests/test_utils.py | 42 +++++++++++++++++++++++++++++++++ 6 files changed, 84 insertions(+), 58 deletions(-) create mode 100644 tests/test_utils.py diff --git a/shallow_backup/config.py b/shallow_backup/config.py index 862db71b..62368a31 100644 --- a/shallow_backup/config.py +++ b/shallow_backup/config.py @@ -7,8 +7,12 @@ def get_config_path(): - xdg_config_home = environ.get('XDG_CONFIG_HOME') or path.join(path.expanduser('~'), '.config') - return path.join(xdg_config_home, "shallow-backup", "shallow-backup.conf") + test_config_path = environ.get('SHALLOW_BACKUP_TEST_CONFIG_PATH', None) + if test_config_path: + return test_config_path + else: + xdg_config_home = environ.get('XDG_CONFIG_HOME') or path.join(path.expanduser('~'), '.config') + return path.join(xdg_config_home, "shallow-backup", "shallow-backup.conf") def get_config(): diff --git a/shallow_backup/git_wrapper.py b/shallow_backup/git_wrapper.py index 79724735..adfee7ef 100644 --- a/shallow_backup/git_wrapper.py +++ b/shallow_backup/git_wrapper.py @@ -134,4 +134,3 @@ def move_git_repo(source_path, dest_path): print_blue_bold("Moving git repo to new location.") except FileNotFoundError: pass - diff --git a/tests/test_backups.py b/tests/test_backups.py index 04d781a9..75ff78ea 100644 --- a/tests/test_backups.py +++ b/tests/test_backups.py @@ -1,29 +1,11 @@ import os import sys import shutil +from .test_utils import BACKUP_DEST_DIR, FAKE_HOME_DIR, DIRS, DOTFILES, DOTFOLDERS, setup_env_vars, unset_env_vars, create_config_for_test sys.path.insert(0, "../shallow_backup") from shallow_backup.backup import backup_dotfiles -from shallow_backup.config import safe_create_config -BACKUP_DIR = 'shallow-backup-test-backups-dir' -FAKE_HOME_DIR = 'shallow-backup-test-backups-src-dir' -TEST_TEXT_CONTENT = 'THIS IS TEST CONTENT FOR THE DOTFILE' -DIRS = [BACKUP_DIR, FAKE_HOME_DIR] -DOTFILES = [ - os.path.join(FAKE_HOME_DIR, ".bashrc"), - os.path.join(FAKE_HOME_DIR, ".bash_profile"), - os.path.join(FAKE_HOME_DIR, ".gitconfig"), - os.path.join(FAKE_HOME_DIR, ".profile"), - os.path.join(FAKE_HOME_DIR, ".pypirc"), - os.path.join(FAKE_HOME_DIR, ".shallow-backup"), - os.path.join(FAKE_HOME_DIR, ".vimrc"), - os.path.join(FAKE_HOME_DIR, ".zshrc") -] - -DOTFOLDERS = [ - os.path.join(FAKE_HOME_DIR, ".ssh"), - os.path.join(FAKE_HOME_DIR, ".vim") -] +TEST_TEXT_CONTENT = 'THIS IS TEST CONTENT FOR THE DOTFILES' class TestBackupMethods: @@ -33,7 +15,8 @@ class TestBackupMethods: @staticmethod def setup_method(): - safe_create_config() + setup_env_vars() + create_config_for_test() for directory in DIRS: try: os.mkdir(directory) @@ -58,12 +41,13 @@ def teardown_method(): for folder in DIRS: print(f"Removing {folder}") shutil.rmtree(folder) + unset_env_vars() def test_backup_dotfiles(self): """ Test backing up dotfiles and dotfolders. """ - backup_dest_path = os.path.join(BACKUP_DIR, "dotfiles") + backup_dest_path = os.path.join(BACKUP_DEST_DIR, "dotfiles") backup_dotfiles(backup_dest_path, home_path=FAKE_HOME_DIR, skip=True) assert os.path.isdir(backup_dest_path) for path in DOTFILES: diff --git a/tests/test_copies.py b/tests/test_copies.py index 3bf8dccb..f23153ff 100644 --- a/tests/test_copies.py +++ b/tests/test_copies.py @@ -2,13 +2,11 @@ import sys import pytest import shutil +from .test_utils import setup_env_vars, unset_env_vars, BACKUP_DEST_DIR, FAKE_HOME_DIR, DIRS sys.path.insert(0, "../shallow_backup") from shallow_backup.utils import copy_dir_if_valid -DIR_TO_BACKUP = 'shallow-backup-test-copy-dir' -BACKUP_DIR = 'shallow-backup-test-copy-backup-dir' -TEST_TEXT_FILE = 'test-file.txt' -DIRS = [DIR_TO_BACKUP, BACKUP_DIR] +TEST_TEXT_FILE = os.path.join(FAKE_HOME_DIR, 'test-file.txt') class TestCopyMethods: @@ -18,37 +16,39 @@ class TestCopyMethods: @staticmethod def setup_method(): - for directory in DIRS: - try: - os.mkdir(directory) - except FileExistsError: - shutil.rmtree(directory) - os.mkdir(directory) - f = open(TEST_TEXT_FILE, "w+") - f.close() + setup_env_vars() + try: + os.mkdir(FAKE_HOME_DIR) + except FileExistsError: + shutil.rmtree(FAKE_HOME_DIR) + os.mkdir(FAKE_HOME_DIR) + print(f"Created {TEST_TEXT_FILE}") + open(TEST_TEXT_FILE, "w+").close() @staticmethod def teardown_method(): for directory in DIRS: - shutil.rmtree(directory) - os.remove(TEST_TEXT_FILE) + if os.path.isdir(directory): + shutil.rmtree(directory) + unset_env_vars() def test_copy_dir(self): """ Test that copying a directory works as expected """ # TODO: Test that all subfiles and folders are copied. - test_dir = 'test' - test_path = os.path.join(DIR_TO_BACKUP, test_dir) + test_dir = 'subdir-to-copy' + test_path = os.path.join(FAKE_HOME_DIR, test_dir) os.mkdir(test_path) - copy_dir_if_valid(test_path, BACKUP_DIR) + copy_dir_if_valid(FAKE_HOME_DIR, BACKUP_DEST_DIR) assert os.path.isdir(test_path) - assert os.path.isdir(os.path.join(BACKUP_DIR, test_dir)) + assert os.path.isfile(os.path.join(BACKUP_DEST_DIR, os.path.split(TEST_TEXT_FILE)[1])) + assert os.path.isdir(os.path.join(BACKUP_DEST_DIR, test_dir)) @pytest.mark.parametrize('invalid', {".Trash", ".npm", ".cache", ".rvm"}) def test_copy_dir_invalid(self, invalid): """ Test that attempting to copy an invalid directory fails """ - copy_dir_if_valid(invalid, DIR_TO_BACKUP) - assert not os.path.isdir(os.path.join(BACKUP_DIR, invalid)) + copy_dir_if_valid(invalid, FAKE_HOME_DIR) + assert not os.path.isdir(os.path.join(BACKUP_DEST_DIR, invalid)) diff --git a/tests/test_git_folder_moving.py b/tests/test_git_folder_moving.py index 185a3397..b334bfda 100644 --- a/tests/test_git_folder_moving.py +++ b/tests/test_git_folder_moving.py @@ -1,13 +1,9 @@ import os import sys import shutil +from .test_utils import BACKUP_DEST_DIR, FAKE_HOME_DIR, DIRS, setup_env_vars, create_config_for_test sys.path.insert(0, "../shallow_backup") from shallow_backup.git_wrapper import move_git_repo, safe_git_init, safe_create_gitignore -from shallow_backup.config import safe_create_config - -OLD_BACKUP_DIR = 'shallow-backup-test-git-old-backup-dir' -NEW_BACKUP_DIR = 'shallow-backup-test-git-new-backup-backup-dir' -DIRS = [OLD_BACKUP_DIR, NEW_BACKUP_DIR] class TestGitFolderCopying: @@ -17,7 +13,8 @@ class TestGitFolderCopying: @staticmethod def setup_method(): - safe_create_config() + setup_env_vars() + create_config_for_test() for directory in DIRS: try: os.mkdir(directory) @@ -34,10 +31,10 @@ def test_copy_git_folder(self): """ Test copying the .git folder and .gitignore from an old directory to a new one """ - safe_git_init(OLD_BACKUP_DIR) - safe_create_gitignore(OLD_BACKUP_DIR) - move_git_repo(OLD_BACKUP_DIR, NEW_BACKUP_DIR) - assert os.path.isdir(os.path.join(NEW_BACKUP_DIR, '.git/')) - assert os.path.isfile(os.path.join(NEW_BACKUP_DIR, '.gitignore')) - assert not os.path.isdir(os.path.join(OLD_BACKUP_DIR, '.git/')) - assert not os.path.isfile(os.path.join(OLD_BACKUP_DIR, '.gitignore')) + safe_git_init(FAKE_HOME_DIR) + safe_create_gitignore(FAKE_HOME_DIR) + move_git_repo(FAKE_HOME_DIR, BACKUP_DEST_DIR) + assert os.path.isdir(os.path.join(BACKUP_DEST_DIR, '.git/')) + assert os.path.isfile(os.path.join(BACKUP_DEST_DIR, '.gitignore')) + assert not os.path.isdir(os.path.join(FAKE_HOME_DIR, '.git/')) + assert not os.path.isfile(os.path.join(FAKE_HOME_DIR, '.gitignore')) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..71e5a2a6 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,42 @@ +import os +import sys +sys.path.insert(0, "../shallow_backup") +from shallow_backup.config import safe_create_config + + +def setup_env_vars(): + os.environ["SHALLOW_BACKUP_TEST_DEST_DIR"] = "/tmp/shallow-backup-test-dest-dir" + os.environ["SHALLOW_BACKUP_TEST_SOURCE_DIR"] = "/tmp/shallow-backup-test-source-dir" + os.environ["SHALLOW_BACKUP_TEST_CONFIG_PATH"] = "/tmp/shallow-backup.conf" + + +def unset_env_vars(): + del os.environ["SHALLOW_BACKUP_TEST_DEST_DIR"] + del os.environ["SHALLOW_BACKUP_TEST_SOURCE_DIR"] + del os.environ["SHALLOW_BACKUP_TEST_CONFIG_PATH"] + + +def create_config_for_test(): + os.remove(os.environ["SHALLOW_BACKUP_TEST_CONFIG_PATH"]) + safe_create_config() + + +setup_env_vars() +BACKUP_DEST_DIR = os.environ.get("SHALLOW_BACKUP_TEST_DEST_DIR") +FAKE_HOME_DIR = os.environ.get("SHALLOW_BACKUP_TEST_SOURCE_DIR") +DIRS = [BACKUP_DEST_DIR, FAKE_HOME_DIR] + +DOTFILES = [ + os.path.join(FAKE_HOME_DIR, ".bashrc"), + os.path.join(FAKE_HOME_DIR, ".bash_profile"), + os.path.join(FAKE_HOME_DIR, ".gitconfig"), + os.path.join(FAKE_HOME_DIR, ".profile"), + os.path.join(FAKE_HOME_DIR, ".pypirc"), + os.path.join(FAKE_HOME_DIR, ".vimrc"), + os.path.join(FAKE_HOME_DIR, ".zshrc") +] + +DOTFOLDERS = [ + os.path.join(FAKE_HOME_DIR, ".ssh"), + os.path.join(FAKE_HOME_DIR, ".vim") +] From 6d6884102120868f250e5aada76c85e3c6b40b4d Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Fri, 15 Nov 2019 07:12:26 +0100 Subject: [PATCH 5/6] Apparently travis VMs don't come with /tmp created already --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index db5fd80f..4d8996be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ install: - pipenv install --dev script: + - mkdir /tmp - pytest --cov after_success: From 179886373801931ef3d821c8ff9a89f55d891f8a Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Fri, 15 Nov 2019 07:14:50 +0100 Subject: [PATCH 6/6] Wrong diagnosis of the issue --- .travis.yml | 1 - tests/test_utils.py | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4d8996be..db5fd80f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,6 @@ install: - pipenv install --dev script: - - mkdir /tmp - pytest --cov after_success: diff --git a/tests/test_utils.py b/tests/test_utils.py index 71e5a2a6..b9231f34 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -17,7 +17,9 @@ def unset_env_vars(): def create_config_for_test(): - os.remove(os.environ["SHALLOW_BACKUP_TEST_CONFIG_PATH"]) + config_file = os.environ["SHALLOW_BACKUP_TEST_CONFIG_PATH"] + if os.path.isfile(config_file): + os.remove(config_file) safe_create_config()