From 1c7180fc80928508e712bf961a31caa3c698f7e8 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Sun, 17 Sep 2023 22:49:37 +0200 Subject: [PATCH] tests --- dotdrop/dotdrop.py | 1 + dotdrop/uninstaller.py | 76 ++++++++++++++++--- tests-ng/uninstall-symlink.sh | 100 ------------------------- tests-ng/uninstall.sh | 117 +++++++---------------------- tests-ng/uninstall_ | 135 ++++++++++++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 204 deletions(-) delete mode 100755 tests-ng/uninstall-symlink.sh create mode 100755 tests-ng/uninstall_ diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 48fc1b9..058c481 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -645,6 +645,7 @@ def cmd_uninstall(opts): uninst = Uninstaller(base=opts.dotpath, workdir=opts.workdir, dry=opts.dry, + safe=opts.safe, debug=opts.debug, backup_suffix=opts.install_backup_suffix) uninstalled = 0 diff --git a/dotdrop/uninstaller.py b/dotdrop/uninstaller.py index 0436661..28aa173 100644 --- a/dotdrop/uninstaller.py +++ b/dotdrop/uninstaller.py @@ -14,16 +14,16 @@ class Uninstaller: """dotfile uninstaller""" def __init__(self, base='.', workdir='~/.config/dotdrop', - dry=False, debug=False, backup_suffix='.dotdropbak'): + dry=False, safe=True, debug=False, + backup_suffix='.dotdropbak'): """ @base: directory path where to search for templates @workdir: where to install template before symlinking @dry: just simulate @debug: enable debug @backup_suffix: suffix for dotfile backup file + @safe: ask for any action """ - # TODO dry - # TODO force base = os.path.expanduser(base) base = os.path.normpath(base) self.base = base @@ -31,9 +31,9 @@ class Uninstaller: workdir = os.path.normpath(workdir) self.workdir = workdir self.dry = dry + self.safe = safe self.debug = debug self.backup_suffix = backup_suffix - self.log = Logger(debug=self.debug) def uninstall(self, src, dst, linktype): @@ -56,6 +56,10 @@ class Uninstaller: path = os.path.normpath(path) path = path.rstrip(os.sep) + if not os.path.isfile(path) and not os.path.isdir(path): + msg = f'cannot uninstall special file {path}' + return False, msg + if not os.path.exists(path): self.log.dbg(f'cannot uninstall non existing {path}') return True, None @@ -68,13 +72,27 @@ class Uninstaller: self.log.sub(f'uninstall {dst}') return ret, msg - def _remove(self, path): - """remove path""" - # TODO handle symlink - backup = f'{path}{self.backup_suffix}' - if os.path.exists(backup): - self.log.dbg(f'backup exists for {path}: {backup}') - return self._replace(path, backup) + def _descend(self, dirpath): + ret = True + for root, dirs, files in os.walk(dirpath): + for file in files: + fpath = os.path.join(root, file) + subret, _ = self._remove(fpath) + if not subret: + ret = False + for directory in dirs: + dpath = os.path.join(root, directory) + self._descend(dpath) + if not os.listdir(dirpath): + # empty + if self.dry: + self.log.dry(f'would \"rm -r {dirpath}\"') + return True, '' + return self._remove_path(dirpath) + return ret, '' + + def _remove_path(self, path): + """remove a file""" try: removepath(path, self.log) except OSError as exc: @@ -82,11 +100,45 @@ class Uninstaller: return False, err return True, '' + def _remove(self, path): + """remove path""" + # TODO handle symlink + self.log.dbg(f'handling uninstall of {path}') + if path.endswith(self.backup_suffix): + self.log.dbg(f'skip {path} ignored') + return True, '' + backup = f'{path}{self.backup_suffix}' + self.log.dbg(f'potential backup file {backup}') + if os.path.exists(backup): + self.log.dbg(f'backup exists for {path}: {backup}') + return self._replace(path, backup) + + if os.path.isdir(path): + self.log.dbg(f'{path} is a directory') + return self._descend(path) + + if self.dry: + self.log.dry(f'would \"rm {path}\"') + return True, '' + + msg = f'Remove {path}?' + if self.safe and not self.log.ask(msg): + return False, 'user refused' + return self._remove_path(path) + def _replace(self, path, backup): """replace path by backup""" + if self.dry: + self.log.dry(f'would \"mv {backup} {path}\"') + return True, '' + + msg = f'Restore {path} from {backup}?' + if self.safe and not self.log.ask(msg): + return False, 'user refused' + try: self.log.dbg(f'mv {backup} {path}') - os.replace(path, backup) + os.replace(backup, path) except OSError as exc: err = f'replacing \"{path}\" by \"{backup}\" failed: {exc}' return False, err diff --git a/tests-ng/uninstall-symlink.sh b/tests-ng/uninstall-symlink.sh deleted file mode 100755 index 2a50ed8..0000000 --- a/tests-ng/uninstall-symlink.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash -# author: deadc0de6 (https://github.com/deadc0de6) -# Copyright (c) 2023, deadc0de6 -# -# test uninstall with symlink -# returns 1 in case of error -# - -## start-cookie -set -e -cur=$(cd "$(dirname "${0}")" && pwd) -ddpath="${cur}/../" -export PYTHONPATH="${ddpath}:${PYTHONPATH}" -altbin="python3 -m dotdrop.dotdrop" -if hash coverage 2>/dev/null; then - mkdir -p coverages/ - altbin="coverage run -p --data-file coverages/coverage --source=dotdrop -m dotdrop.dotdrop" -fi -bin="${DT_BIN:-${altbin}}" -# shellcheck source=tests-ng/helpers -source "${cur}"/helpers -echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sgr0)" -## end-cookie - -################################################################ -# this is the test -################################################################ -# dotdrop directory -basedir=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) -mkdir -p "${basedir}"/dotfiles -echo "[+] dotdrop dir: ${basedir}" -echo "[+] dotpath dir: ${basedir}/dotfiles" -tmpd=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) - -clear_on_exit "${basedir}" -clear_on_exit "${tmpd}" - -# TODO handle -# - symlink file (absolute, relative) -# - symlink directory (absolute, relative, link_children) -# - transformation -# - template - -echo "modified" > "${basedir}"/dotfiles/x - -# create the config file -cfg="${basedir}/config.yaml" -cat > "${cfg}" << _EOF -config: - backup: true - create: true - dotpath: dotfiles -dotfiles: - f_x: - src: x - dst: ${tmpd}/x -profiles: - p1: - dotfiles: - - f_x -_EOF - -######################### -## no original -######################### - -# install -echo "[+] install (1)" -cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^1 dotfile(s) installed.$' -[ "$?" != "0" ] && exit 1 -[ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 -grep 'modified' "${tmpd}"/x - -# uninstall -echo "[+] uninstall" -cd "${ddpath}" | ${bin} uninstall -c "${cfg}" -f -p p1 --verbose -[ "$?" != "0" ] && exit 1 -[ -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 - -######################### -## with original -######################### -echo 'original' > "${tmpd}"/x - -# install -echo "[+] install (2)" -cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^1 dotfile(s) installed.$' -[ "$?" != "0" ] && exit 1 -[ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 -grep 'modified' "${tmpd}"/x - -# uninstall -echo "[+] uninstall" -cd "${ddpath}" | ${bin} uninstall -c "${cfg}" -f -p p1 --verbose -[ "$?" != "0" ] && exit 1 -[ -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 -grep 'original' "${tmpd}"/x - -echo "OK" -exit 0 diff --git a/tests-ng/uninstall.sh b/tests-ng/uninstall.sh index 147c7b3..38115b9 100755 --- a/tests-ng/uninstall.sh +++ b/tests-ng/uninstall.sh @@ -25,102 +25,35 @@ echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sg ################################################################ # this is the test ################################################################ +export DOTDROP_TEST_NG_UNINSTALL_DDPATH="${ddpath}" +export DOTDROP_TEST_NG_UNINSTALL_BIN="${bin}" +source "${cur}"/uninstall_ -# $1 pattern -# $2 path -grep_or_fail() -{ - grep "${1}" "${2}" >/dev/null 2>&1 || (echo "pattern \"${1}\" not found in ${2}" && exit 1) -} +export DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE="nolink" +echo "[+] testing uninstall link:${DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE}..." +uninstall_with_link +echo "[+] uninstall link ${DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE} OK" -# dotdrop directory -basedir=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) -mkdir -p "${basedir}"/dotfiles -echo "[+] dotdrop dir: ${basedir}" -echo "[+] dotpath dir: ${basedir}/dotfiles" -tmpd=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) +export DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE="absolute" +echo "[+] testing uninstall link:${DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE}..." +uninstall_with_link +echo "[+] uninstall link ${DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE} OK" -clear_on_exit "${basedir}" -clear_on_exit "${tmpd}" +export DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE="relative" +echo "[+] testing uninstall link:${DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE}..." +uninstall_with_link +echo "[+] uninstall link ${DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE} OK" -echo "modified" > "${basedir}"/dotfiles/x -mkdir -p "${basedir}"/dotfiles/y -echo "modified" > "${basedir}"/dotfiles/y/file +export DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE="link_children" +echo "[+] testing uninstall link:${DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE}..." +uninstall_with_link +echo "[+] uninstall link ${DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE} OK" -# create the config file -cfg="${basedir}/config.yaml" -cat > "${cfg}" << _EOF -config: - backup: true - create: true - dotpath: dotfiles -dotfiles: - f_x: - src: x - dst: ${tmpd}/x - d_y: - src: y - dst: ${tmpd}/y -profiles: - p1: - dotfiles: - - f_x - - d_y -_EOF - -######################### -## no original -######################### - -# install -echo "[+] install (1)" -cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^2 dotfile(s) installed.$' - -# tests -[ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 -[ ! -e "${tmpd}"/y/file ] && echo "d_y not installed" && exit 1 -grep_or_fail 'modified' "${tmpd}"/x -grep_or_fail 'modified' "${tmpd}"/y/file - -# uninstall -echo "[+] uninstall (1)" -cd "${ddpath}" | ${bin} uninstall -c "${cfg}" -f -p p1 --verbose -[ "$?" != "0" ] && exit 1 - -# tests -[ -e "${tmpd}"/x ] && echo "f_x not uninstalled" && exit 1 -[ -d "${tmpd}"/y ] && echo "d_y not uninstalled" && exit 1 -[ -e "${tmpd}"/y/file ] && echo "d_y file not uninstalled" && exit 1 - -######################### -## with original -######################### -echo 'original' > "${tmpd}"/x -mkdir -p "${tmpd}"/y -echo "original" > "${tmpd}"/y/file - -# install -echo "[+] install (2)" -cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 --verbose | grep '^2 dotfile(s) installed.$' - -# tests -[ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 -[ ! -d "${tmpd}"/y ] && echo "d_y not installed" && exit 1 -[ ! -e "${tmpd}"/y/file ] && echo "d_y file not installed" && exit 1 -grep_or_fail 'modified' "${tmpd}"/x -grep_or_fail 'modified' "${tmpd}"/y/file - -# uninstall -echo "[+] uninstall (2)" -cd "${ddpath}" | ${bin} uninstall -c "${cfg}" -f -p p1 --verbose -[ "$?" != "0" ] && exit 1 - -# tests -[ ! -e "${tmpd}"/x ] && echo "f_x backup not restored" && exit 1 -[ ! -d "${tmpd}"/y ] && echo "d_y backup not restored" && exit 1 -[ ! -e "${tmpd}"/y/file ] && echo "d_y backup not restored" && exit 1 -grep_or_fail 'original' "${tmpd}"/x -grep_or_fail 'original' "${tmpd}"/y/file +# TODO test +# - symlink file (absolute, relative) +# - symlink directory (absolute, relative, link_children) +# - transformation +# - template echo "OK" -exit 0 +exit 0 \ No newline at end of file diff --git a/tests-ng/uninstall_ b/tests-ng/uninstall_ new file mode 100755 index 0000000..24bb37c --- /dev/null +++ b/tests-ng/uninstall_ @@ -0,0 +1,135 @@ +################################################################ +# this is the test +################################################################ + +# $1 pattern +# $2 path +grep_or_fail() +{ + grep "${1}" "${2}" >/dev/null 2>&1 || (echo "pattern \"${1}\" not found in ${2}" && exit 1) +} + +# $1: basedir +# $2: content +create_hierarchy() +{ + echo "${2}" > "${1}"/x + mkdir -p "${1}"/y + echo "${2}" > "${1}"/y/file + mkdir -p "${1}"/y/subdir + echo "${2}" > "${1}"/y/subdir/subfile +} + +# $1: basedir +clean_hierarchy() +{ + rm -f "${1}"/x + rm -rf "${1}"/y +} + +uninstall_with_link() +{ + ddpath="${DOTDROP_TEST_NG_UNINSTALL_DDPATH}" + bin="${DOTDROP_TEST_NG_UNINSTALL_BIN}" + + # dotdrop directory + basedir=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) + mkdir -p "${basedir}"/dotfiles + echo "[+] dotdrop dir: ${basedir}" + echo "[+] dotpath dir: ${basedir}/dotfiles" + tmpd=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) + + clear_on_exit "${basedir}/dotfiles" + clear_on_exit "${tmpd}" + + create_hierarchy "${basedir}/dotfiles" "modified" + + # create the config file + cfg="${basedir}/config.yaml" + LINK_TYPE="${DOTDROP_TEST_NG_UNINSTALL_LINK_TYPE:-nolink}" + cat > "${cfg}" << _EOF +config: + backup: true + create: true + dotpath: dotfiles + link_dotfile_default: ${LINK_TYPE} +dotfiles: + f_x: + src: x + dst: ${tmpd}/x + d_y: + src: y + dst: ${tmpd}/y +profiles: + p1: + dotfiles: + - f_x + - d_y +_EOF + + ######################### + ## no original + ######################### + + # install + echo "[+] install (1)" + cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 | grep '^2 dotfile(s) installed.$' + + # tests + [ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 + [ ! -e "${tmpd}"/y/file ] && echo "d_y not installed" && exit 1 + [ ! -e "${tmpd}"/y/subdir/subfile ] && echo "d_y not installed" && exit 1 + grep_or_fail 'modified' "${tmpd}"/x + grep_or_fail 'modified' "${tmpd}"/y/file + + # uninstall + echo "[+] uninstall (1)" + cd "${ddpath}" | ${bin} uninstall -c "${cfg}" -f -p p1 --verbose + [ "$?" != "0" ] && exit 1 + + # tests + [ ! -d "${basedir}"/dotfiles ] && echo "dotpath removed" && exit 1 + [ -e "${tmpd}"/x ] && echo "f_x not uninstalled" && exit 1 + [ -d "${tmpd}"/y ] && echo "d_y dir not uninstalled" && exit 1 + [ -e "${tmpd}"/y/file ] && echo "d_y file not uninstalled" && exit 1 + [ -e "${tmpd}"/y/subdir/subfile ] && echo "d_y subfile not uninstalled" && exit 1 + + ######################### + ## with original + ######################### + # clean + clean_hierarchy "${tmpd}" + + # recreate + create_hierarchy "${tmpd}" "original" + + # install + echo "[+] install (2)" + cd "${ddpath}" | ${bin} install -c "${cfg}" -f -p p1 | grep '^2 dotfile(s) installed.$' + + # tests + [ ! -e "${tmpd}"/x ] && echo "f_x not installed" && exit 1 + [ ! -d "${tmpd}"/y ] && echo "d_y not installed" && exit 1 + [ ! -e "${tmpd}"/y/file ] && echo "d_y file not installed" && exit 1 + [ ! -e "${tmpd}"/y/subdir/subfile ] && echo "d_y subfile not installed" && exit 1 + grep_or_fail 'modified' "${tmpd}"/x + grep_or_fail 'modified' "${tmpd}"/y/file + + # uninstall + echo "[+] uninstall (2)" + cd "${ddpath}" | ${bin} uninstall -c "${cfg}" -f -p p1 --verbose + [ "$?" != "0" ] && exit 1 + + tree "${tmpd}" + # tests + [ ! -d "${basedir}"/dotfiles ] && echo "dotpath removed" && exit 1 + [ ! -e "${tmpd}"/x ] && echo "f_x backup not restored" && exit 1 + [ -e "${tmpd}"/x.dotdropbak ] && echo "f_x backup not removed" && exit 1 + [ ! -d "${tmpd}"/y ] && echo "d_y backup not restored" && exit 1 + [ ! -e "${tmpd}"/y/file ] && echo "d_y backup not restored" && exit 1 + [ -e "${tmpd}"/y/file.dotdropbak ] && echo "d_y backup not removed" && exit 1 + [ ! -e "${tmpd}"/y/subdir/subfile ] && echo "d_y sub backup not restored" && exit 1 + [ -e "${tmpd}"/y/subdir/subfile.dotdropbak ] && echo "d_y sub backup not removed" && exit 1 + grep_or_fail 'original' "${tmpd}"/x + grep_or_fail 'original' "${tmpd}"/y/file +} \ No newline at end of file