diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..6deafc2 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 diff --git a/CHANGES b/CHANGES index 9acb665..b06d17a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +3.3.0 + * Support nested ifs in default template (#436) + * Support include and ifs in default template includes (#406) + * Support environment variables in ifs in default template (#488) + * Support != in default template (#358, #477) + * Fix multiple classes in default template on macOS (#437) + 3.2.2 * Support spaces in distro/distro-family (#432) * Fix zsh hanging when tab completing add/checkout (#417) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index c1176e6..dc23b90 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -25,9 +25,9 @@ Paulo Köch Oren Zipori Daniel Gray Paraplegic Racehorse -japm48 Siôn Le Roux Mateusz Piotrowski +japm48 Uroš Golja Satoshi Ohki Jonas @@ -35,18 +35,20 @@ Franciszek Madej Daniel Wagenknecht Stig Palmquist Patrick Hof -con-f-use Samisafool +LFdev +con-f-use Bram Ceulemans Travis A. Everett Sheng Yang Jared Smartt Adam Jimerson -dessert1 -addshore Tim Condit Thomas Luzat Russ Allbery +Patrick Roddy +dessert1 Brayden Banks Alexandre GV +addshore Felipe S. S. Schneider diff --git a/Makefile b/Makefile index 69fa003..5da0918 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTESTS = $(wildcard test/test_*.py) -IMAGE = docker.io/yadm/testbed:2022-01-07 +IMAGE = docker.io/yadm/testbed:2023-07-12 OCI = docker .PHONY: all diff --git a/README.md b/README.md index 21e10aa..41780d3 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ The star count helps others discover yadm. [master-badge]: https://img.shields.io/github/actions/workflow/status/yadm-dev/yadm/test.yml?branch=master [master-commits]: https://github.com/yadm-dev/yadm/commits/master [master-date]: https://img.shields.io/github/last-commit/yadm-dev/yadm/master.svg?label=master -[obs-badge]: https://img.shields.io/badge/OBS-v3.2.2-blue +[obs-badge]: https://img.shields.io/badge/OBS-v3.3.0-blue [obs-link]: https://software.opensuse.org//download.html?project=home%3ATheLocehiliosan%3Ayadm&package=yadm [releases-badge]: https://img.shields.io/github/tag/yadm-dev/yadm.svg?label=latest+release [releases-link]: https://github.com/yadm-dev/yadm/releases diff --git a/pylintrc b/pylintrc deleted file mode 100644 index ba41b74..0000000 --- a/pylintrc +++ /dev/null @@ -1,17 +0,0 @@ -[BASIC] -good-names=pytestmark - -[DESIGN] -max-args=14 -max-locals=28 -max-attributes=8 -max-statements=65 - -[SIMILARITIES] -min-similarity-lines=8 - -[MESSAGES CONTROL] -disable=redefined-outer-name - -[TYPECHECK] -ignored-modules=py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1e51d44 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,31 @@ +[tool.pytest.ini_options] +cache_dir = "/tmp" +addopts = "-ra" +markers = [ + "deprecated", # marks tests for deprecated features (deselect with '-m "not deprecated"') +] + +[tool.pylint.design] +max-args = 14 +max-locals = 28 +max-attributes = 8 +max-statements = 65 + +[tool.pylint.format] +max-line-length = 120 + +[tool.pylint."messages control"] +disable = [ + "redefined-outer-name", +] + +[tool.pylint.similarities] +ignore-imports = "yes" +min-similarity-lines = 8 + +[tool.black] +line-length = 120 + +[tool.isort] +line_length = 120 +profile = "black" diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index d032ea5..0000000 --- a/pytest.ini +++ /dev/null @@ -1,5 +0,0 @@ -[pytest] -cache_dir = /tmp -addopts = -ra -markers = - deprecated: marks tests for deprecated features (deselect with '-m "not deprecated"') diff --git a/test/Dockerfile b/test/Dockerfile index 5485561..29d863e 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -1,9 +1,9 @@ -FROM ubuntu:18.04 +FROM ubuntu:23.04 MAINTAINER Tim Byrne # Shellcheck and esh versions -ARG SC_VER=0.8.0 -ARG ESH_VER=0.3.1 +ARG SC_VER=0.9.0 +ARG ESH_VER=0.3.2 # Install prerequisites and configure UTF-8 locale RUN \ @@ -41,8 +41,8 @@ RUN cd /opt \ # Upgrade pip3 and install requirements COPY test/requirements.txt /tmp/requirements.txt -RUN python3 -m pip install --upgrade pip setuptools \ - && python3 -m pip install --upgrade -r /tmp/requirements.txt \ +RUN python3 -m pip install --break-system-packages --upgrade pip setuptools \ + && python3 -m pip install --break-system-packages --upgrade -r /tmp/requirements.txt \ && rm -f /tmp/requirements # Install esh diff --git a/test/conftest.py b/test/conftest.py index d3bbb76..b18cae4 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -3,11 +3,12 @@ import collections import contextlib import copy -import distutils.dir_util # pylint: disable=no-name-in-module,import-error import os import platform import pwd -from subprocess import Popen, PIPE +import shutil +from subprocess import PIPE, Popen + import py import pytest @@ -22,157 +23,168 @@ def pytest_addoption(parser): ) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def shellcheck_version(): """Version of shellcheck supported""" - return '0.8.0' + return "0.9.0" -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def pylint_version(): """Version of pylint supported""" - return '2.6.0' + return "2.17.0" -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") +def isort_version(): + """Version of isort supported""" + return "5.12.0" + + +@pytest.fixture(scope="session") def flake8_version(): """Version of flake8 supported""" - return '3.8.4' + return "6.0.0" -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") +def black_version(): + """Version of black supported""" + return "23.1.0" + + +@pytest.fixture(scope="session") def yamllint_version(): """Version of yamllint supported""" - return '1.25.0' + return "1.30.0" -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def tst_user(): """Test session's user id""" return pwd.getpwuid(os.getuid()).pw_name -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def tst_host(): """Test session's short hostname value""" - return platform.node().split('.')[0] + return platform.node().split(".")[0] -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def tst_distro(runner): """Test session's distro""" - distro = '' + distro = "" with contextlib.suppress(Exception): - run = runner(command=['lsb_release', '-si'], report=False) + run = runner(command=["lsb_release", "-si"], report=False) distro = run.out.strip() return distro -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def tst_distro_family(runner): """Test session's distro_family""" - family = '' + family = "" with contextlib.suppress(Exception): - run = runner(command=[ - 'grep', '-oP', r'ID_LIKE=\K.+', '/etc/os-release'], report=False) + run = runner(command=["grep", "-oP", r"ID_LIKE=\K.+", "/etc/os-release"], report=False) family = run.out.strip() return family -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def tst_sys(): """Test session's uname value""" return platform.system() -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def tst_arch(): """Test session's uname value""" return platform.machine() -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def supported_commands(): """List of supported commands This list should be updated every time yadm learns a new command. """ return [ - 'alt', - 'bootstrap', - 'clean', - 'clone', - 'config', - 'decrypt', - 'encrypt', - 'enter', - 'git-crypt', - 'gitconfig', - 'help', - 'init', - 'introspect', - 'list', - 'perms', - 'transcrypt', - 'upgrade', - 'version', - ] + "alt", + "bootstrap", + "clean", + "clone", + "config", + "decrypt", + "encrypt", + "enter", + "git-crypt", + "gitconfig", + "help", + "init", + "introspect", + "list", + "perms", + "transcrypt", + "upgrade", + "version", + ] -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def supported_configs(): """List of supported config options This list should be updated every time yadm learns a new config. """ return [ - 'local.arch', - 'local.class', - 'local.hostname', - 'local.os', - 'local.user', - 'yadm.alt-copy', - 'yadm.auto-alt', - 'yadm.auto-exclude', - 'yadm.auto-perms', - 'yadm.auto-private-dirs', - 'yadm.cipher', - 'yadm.git-program', - 'yadm.gpg-perms', - 'yadm.gpg-program', - 'yadm.gpg-recipient', - 'yadm.openssl-ciphername', - 'yadm.openssl-old', - 'yadm.openssl-program', - 'yadm.ssh-perms', - ] + "local.arch", + "local.class", + "local.hostname", + "local.os", + "local.user", + "yadm.alt-copy", + "yadm.auto-alt", + "yadm.auto-exclude", + "yadm.auto-perms", + "yadm.auto-private-dirs", + "yadm.cipher", + "yadm.git-program", + "yadm.gpg-perms", + "yadm.gpg-program", + "yadm.gpg-recipient", + "yadm.openssl-ciphername", + "yadm.openssl-old", + "yadm.openssl-program", + "yadm.ssh-perms", + ] -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def supported_switches(): """List of supported switches This list should be updated every time yadm learns a new switch. """ return [ - '--yadm-archive', - '--yadm-bootstrap', - '--yadm-config', - '--yadm-data', - '--yadm-dir', - '--yadm-encrypt', - '--yadm-repo', - '-Y', - ] + "--yadm-archive", + "--yadm-bootstrap", + "--yadm-config", + "--yadm-data", + "--yadm-dir", + "--yadm-encrypt", + "--yadm-repo", + "-Y", + ] -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def supported_local_configs(supported_configs): """List of supported local config options""" - return [c for c in supported_configs if c.startswith('local.')] + return [c for c in supported_configs if c.startswith("local.")] -class Runner(): +class Runner: """Class for running commands Within yadm tests, this object should be used when running commands that @@ -185,17 +197,9 @@ class Runner(): Other instances of simply running commands should use os.system(). """ - def __init__( - self, - command, - inp=None, - shell=False, - cwd=None, - env=None, - expect=None, - report=True): + def __init__(self, command, inp=None, shell=False, cwd=None, env=None, expect=None, report=True): if shell: - self.command = ' '.join([str(cmd) for cmd in command]) + self.command = " ".join([str(cmd) for cmd in command]) else: self.command = command if env is None: @@ -204,7 +208,7 @@ class Runner(): merged_env.update(env) self.inp = inp self.wrap(expect) - process = Popen( + with Popen( self.command, stdin=PIPE, stdout=PIPE, @@ -212,67 +216,57 @@ class Runner(): shell=shell, cwd=cwd, env=merged_env, - ) - input_bytes = self.inp - if self.inp: - input_bytes = self.inp.encode() - (out_bstream, err_bstream) = process.communicate(input=input_bytes) - self.out = out_bstream.decode() - self.err = err_bstream.decode() - self.code = process.wait() + ) as process: + input_bytes = self.inp + if self.inp: + input_bytes = self.inp.encode() + (out_bstream, err_bstream) = process.communicate(input=input_bytes) + self.out = out_bstream.decode() + self.err = err_bstream.decode() + self.code = process.wait() self.success = self.code == 0 self.failure = self.code != 0 if report: self.report() def __repr__(self): - return f'Runner({self.command})' + return f"Runner({self.command})" def report(self): """Print code/stdout/stderr""" - print(f'{self}') - print(f' RUN: code:{self.code}') + print(f"{self}") + print(f" RUN: code:{self.code}") if self.inp: - print(f' RUN: input:\n{self.inp}') - print(f' RUN: stdout:\n{self.out}') - print(f' RUN: stderr:\n{self.err}') + print(f" RUN: input:\n{self.inp}") + print(f" RUN: stdout:\n{self.out}") + print(f" RUN: stderr:\n{self.err}") def wrap(self, expect): """Wrap command with expect""" if not expect: return - cmdline = ' '.join([f'"{w}"' for w in self.command]) - expect_script = f'set timeout 2\nspawn {cmdline}\n' + cmdline = " ".join([f'"{w}"' for w in self.command]) + expect_script = f"set timeout 2\nspawn {cmdline}\n" for question, answer in expect: - expect_script += ( - 'expect {\n' - f'"{question}" {{send "{answer}\\r"}}\n' - 'timeout {close;exit 128}\n' - '}\n') - expect_script += ( - 'expect eof\n' - 'foreach {pid spawnid os_error_flag value} [wait] break\n' - 'exit $value') + expect_script += "expect {\n" f'"{question}" {{send "{answer}\\r"}}\n' "timeout {close;exit 128}\n" "}\n" + expect_script += "expect eof\n" "foreach {pid spawnid os_error_flag value} [wait] break\n" "exit $value" self.inp = expect_script - print(f'EXPECT:{expect_script}') - self.command = ['expect'] + print(f"EXPECT:{expect_script}") + self.command = ["expect"] -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def runner(): """Class for running commands""" return Runner -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def config_git(): """Configure global git configuration, if missing""" - os.system( - 'git config user.name || ' - 'git config --global user.name "test"') - os.system( - 'git config user.email || ' - 'git config --global user.email "test@test.test"') + os.system("git config init.defaultBranch || git config --global init.defaultBranch master") + os.system('git config user.name || git config --global user.name "test"') + os.system('git config user.email || git config --global user.email "test@test.test"') @pytest.fixture() @@ -282,19 +276,19 @@ def repo_config(runner, paths): def query_func(key): """Query a yadm repo configuration value""" run = runner( - command=('git', 'config', '--local', key), - env={'GIT_DIR': paths.repo}, + command=("git", "config", "--local", key), + env={"GIT_DIR": paths.repo}, report=False, - ) + ) return run.out.rstrip() return query_func -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def yadm(): """Path to yadm program to be tested""" - full_path = os.path.realpath('yadm') + full_path = os.path.realpath("yadm") assert os.path.isfile(full_path), "yadm program file isn't present" return full_path @@ -303,38 +297,40 @@ def yadm(): def paths(tmpdir, yadm): """Function scoped test paths""" - dir_root = tmpdir.mkdir('root') - dir_remote = dir_root.mkdir('remote') - dir_work = dir_root.mkdir('work') - dir_xdg_data = dir_root.mkdir('xdg_data') - dir_xdg_home = dir_root.mkdir('xdg_home') - dir_data = dir_xdg_data.mkdir('yadm') - dir_yadm = dir_xdg_home.mkdir('yadm') - dir_hooks = dir_yadm.mkdir('hooks') - dir_repo = dir_data.mkdir('repo.git') - file_archive = dir_data.join('archive') - file_bootstrap = dir_yadm.join('bootstrap') - file_config = dir_yadm.join('config') - file_encrypt = dir_yadm.join('encrypt') + dir_root = tmpdir.mkdir("root") + dir_remote = dir_root.mkdir("remote") + dir_work = dir_root.mkdir("work") + dir_xdg_data = dir_root.mkdir("xdg_data") + dir_xdg_home = dir_root.mkdir("xdg_home") + dir_data = dir_xdg_data.mkdir("yadm") + dir_yadm = dir_xdg_home.mkdir("yadm") + dir_hooks = dir_yadm.mkdir("hooks") + dir_repo = dir_data.mkdir("repo.git") + file_archive = dir_data.join("archive") + file_bootstrap = dir_yadm.join("bootstrap") + file_config = dir_yadm.join("config") + file_encrypt = dir_yadm.join("encrypt") paths = collections.namedtuple( - 'Paths', [ - 'pgm', - 'root', - 'remote', - 'work', - 'xdg_data', - 'xdg_home', - 'data', - 'yadm', - 'hooks', - 'repo', - 'archive', - 'bootstrap', - 'config', - 'encrypt', - ]) - os.environ['XDG_CONFIG_HOME'] = str(dir_xdg_home) - os.environ['XDG_DATA_HOME'] = str(dir_xdg_data) + "Paths", + [ + "pgm", + "root", + "remote", + "work", + "xdg_data", + "xdg_home", + "data", + "yadm", + "hooks", + "repo", + "archive", + "bootstrap", + "config", + "encrypt", + ], + ) + os.environ["XDG_CONFIG_HOME"] = str(dir_xdg_home) + os.environ["XDG_DATA_HOME"] = str(dir_xdg_data) return paths( yadm, dir_root, @@ -350,19 +346,25 @@ def paths(tmpdir, yadm): file_bootstrap, file_config, file_encrypt, - ) + ) @pytest.fixture() def yadm_cmd(paths): """Generate custom command_list function""" + def command_list(*args): """Produce params for running yadm with -Y""" return [paths.pgm] + list(args) + return command_list -class DataFile(): +class NoRelativePath(Exception): + """Exception when finding relative paths""" + + +class DataFile: """Datafile object""" def __init__(self, path, tracked=True, private=False): @@ -381,7 +383,7 @@ class DataFile(): """Relative path property""" if self.__parent: return self.__parent.join(self.path) - raise BaseException('Unable to provide relative path, no parent') + raise NoRelativePath("Unable to provide relative path, no parent") @property def tracked(self): @@ -398,22 +400,18 @@ class DataFile(): self.__parent = parent -class DataSet(): +class DataSet: """Dataset object""" def __init__(self): - self.__files = list() - self.__dirs = list() - self.__tracked_dirs = list() - self.__private_dirs = list() + self.__files = [] + self.__dirs = [] + self.__tracked_dirs = [] + self.__private_dirs = [] self.__relpath = None def __repr__(self): - return ( - f'[DS with {len(self)} files; ' - f'{len(self.tracked)} tracked, ' - f'{len(self.private)} private]' - ) + return f"[DS with {len(self)} files; " f"{len(self.tracked)} tracked, " f"{len(self.private)} private]" def __iter__(self): return iter(self.__files) @@ -451,17 +449,17 @@ class DataSet(): @property def plain_dirs(self): """List of directories in DataSet not starting with '.'""" - return [d for d in self.dirs if not d.startswith('.')] + return [d for d in self.dirs if not d.startswith(".")] @property def hidden_dirs(self): """List of directories in DataSet starting with '.'""" - return [d for d in self.dirs if d.startswith('.')] + return [d for d in self.dirs if d.startswith(".")] @property def tracked_dirs(self): """List of directories in DataSet not starting with '.'""" - return [d for d in self.__tracked_dirs if not d.startswith('.')] + return [d for d in self.__tracked_dirs if not d.startswith(".")] @property def private_dirs(self): @@ -491,23 +489,23 @@ class DataSet(): datafile.relative_to(self.__relpath) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def ds1_dset(tst_sys): """Meta-data for dataset one files""" dset = DataSet() - dset.add_file('t1') - dset.add_file('d1/t2') - dset.add_file(f'test_alt_copy##os.{tst_sys}') - dset.add_file('u1', tracked=False) - dset.add_file('d2/u2', tracked=False) - dset.add_file('.ssh/p1', tracked=False, private=True) - dset.add_file('.ssh/.p2', tracked=False, private=True) - dset.add_file('.gnupg/p3', tracked=False, private=True) - dset.add_file('.gnupg/.p4', tracked=False, private=True) + dset.add_file("t1") + dset.add_file("d1/t2") + dset.add_file(f"test_alt_copy##os.{tst_sys}") + dset.add_file("u1", tracked=False) + dset.add_file("d2/u2", tracked=False) + dset.add_file(".ssh/p1", tracked=False, private=True) + dset.add_file(".ssh/.p2", tracked=False, private=True) + dset.add_file(".gnupg/p3", tracked=False, private=True) + dset.add_file(".gnupg/.p4", tracked=False, private=True) return dset -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def ds1_data(tmpdir_factory, config_git, ds1_dset, runner): """A set of test data, worktree & repo""" # pylint: disable=unused-argument @@ -515,65 +513,40 @@ def ds1_data(tmpdir_factory, config_git, ds1_dset, runner): # @pytest.mark.usefixtures('config_git') # cannot be applied to another fixture. - data = tmpdir_factory.mktemp('ds1') + data = tmpdir_factory.mktemp("ds1") - work = data.mkdir('work') + work = data.mkdir("work") for datafile in ds1_dset: work.join(datafile.path).write(datafile.path, ensure=True) - repo = data.mkdir('repo.git') + repo = data.mkdir("repo.git") env = os.environ.copy() - env['GIT_DIR'] = str(repo) - runner( - command=['git', 'init', '--shared=0600', '--bare', str(repo)], - report=False) - runner( - command=['git', 'config', 'core.bare', 'false'], - env=env, - report=False) - runner( - command=['git', 'config', 'status.showUntrackedFiles', 'no'], - env=env, - report=False) - runner( - command=['git', 'config', 'yadm.managed', 'true'], - env=env, - report=False) - runner( - command=['git', 'config', 'core.worktree', str(work)], - env=env, - report=False) - runner( - command=['git', 'add'] + - [str(work.join(f.path)) for f in ds1_dset if f.tracked], - env=env) - runner( - command=['git', 'commit', '--allow-empty', '-m', 'Initial commit'], - env=env, - report=False) + env["GIT_DIR"] = str(repo) + runner(command=["git", "init", "--shared=0600", "--bare", str(repo)], report=False) + runner(command=["git", "config", "core.bare", "false"], env=env, report=False) + runner(command=["git", "config", "status.showUntrackedFiles", "no"], env=env, report=False) + runner(command=["git", "config", "yadm.managed", "true"], env=env, report=False) + runner(command=["git", "config", "core.worktree", str(work)], env=env, report=False) + runner(command=["git", "add"] + [str(work.join(f.path)) for f in ds1_dset if f.tracked], env=env) + runner(command=["git", "commit", "--allow-empty", "-m", "Initial commit"], env=env, report=False) - data = collections.namedtuple('Data', ['work', 'repo']) + data = collections.namedtuple("Data", ["work", "repo"]) return data(work, repo) @pytest.fixture() def ds1_work_copy(ds1_data, paths): """Function scoped copy of ds1_data.work""" - distutils.dir_util.copy_tree( # pylint: disable=no-member - str(ds1_data.work), str(paths.work)) + shutil.copytree(str(ds1_data.work), str(paths.work), dirs_exist_ok=True) @pytest.fixture() def ds1_repo_copy(runner, ds1_data, paths): """Function scoped copy of ds1_data.repo""" - distutils.dir_util.copy_tree( # pylint: disable=no-member - str(ds1_data.repo), str(paths.repo)) + shutil.copytree(str(ds1_data.repo), str(paths.repo), dirs_exist_ok=True) env = os.environ.copy() - env['GIT_DIR'] = str(paths.repo) - runner( - command=['git', 'config', 'core.worktree', str(paths.work)], - env=env, - report=False) + env["GIT_DIR"] = str(paths.repo) + runner(command=["git", "config", "core.worktree", str(paths.work)], env=env, report=False) @pytest.fixture() @@ -598,30 +571,27 @@ def ds1(ds1_work_copy, paths, ds1_dset): return dscopy -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def gnupg(tmpdir_factory, runner): """Location of GNUPGHOME""" def register_gpg_password(password): """Publish a new GPG mock password""" - py.path.local('/tmp/mock-password').write(password) + py.path.local("/tmp/mock-password").write(password) - home = tmpdir_factory.mktemp('gnupghome') + home = tmpdir_factory.mktemp("gnupghome") home.chmod(0o700) - conf = home.join('gpg.conf') - conf.write('no-secmem-warning\n') + conf = home.join("gpg.conf") + conf.write("no-secmem-warning\n") conf.chmod(0o600) - agentconf = home.join('gpg-agent.conf') - agentconf.write( - f'pinentry-program {os.path.abspath("test/pinentry-mock")}\n' - 'max-cache-ttl 0\n' - ) + agentconf = home.join("gpg-agent.conf") + agentconf.write(f'pinentry-program {os.path.abspath("test/pinentry-mock")}\n' "max-cache-ttl 0\n") agentconf.chmod(0o600) - data = collections.namedtuple('GNUPG', ['home', 'pw']) + data = collections.namedtuple("GNUPG", ["home", "pw"]) env = os.environ.copy() - env['GNUPGHOME'] = home + env["GNUPGHOME"] = home # this pre-populates std files in the GNUPGHOME - runner(['gpg', '-k'], env=env) + runner(["gpg", "-k"], env=env) return data(home, register_gpg_password) diff --git a/test/pinentry-mock b/test/pinentry-mock index d40033b..39da043 100755 --- a/test/pinentry-mock +++ b/test/pinentry-mock @@ -1,12 +1,20 @@ #!/bin/bash # This program is a custom mock pinentry program -# It always uses whatever password is found in the /tmp directory -password="$(cat /tmp/mock-password 2>/dev/null)" +# It uses whatever password is found in the /tmp directory +# If the password is empty, replies CANCEL causing an error to similate invalid +# credentials echo "OK Pleased to meet you" while read -r line; do if [[ $line =~ GETPIN ]]; then - echo -n "D " - echo "$password" + password="$(cat /tmp/mock-password 2>/dev/null)" + if [ -n "$password" ]; then + echo -n "D " + echo "$password" + echo "OK"; + else + echo "CANCEL"; + fi + else + echo "OK"; fi - echo "OK"; done diff --git a/test/pylintrc b/test/pylintrc deleted file mode 120000 index 05334af..0000000 --- a/test/pylintrc +++ /dev/null @@ -1 +0,0 @@ -../pylintrc \ No newline at end of file diff --git a/test/requirements.txt b/test/requirements.txt index 30da6ae..e71b349 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,6 +1,8 @@ +black==23.1.0 envtpl -flake8==3.8.4 +flake8==6.0.0 +isort==5.12.0 j2cli -pylint==2.6.0 -pytest==6.2.1 -yamllint==1.25.0 +pylint==2.17.0 +pytest==7.2.2 +yamllint==1.30.0 diff --git a/test/test_alt.py b/test/test_alt.py index ddaf374..c429ad4 100644 --- a/test/test_alt.py +++ b/test/test_alt.py @@ -1,6 +1,7 @@ """Test alt""" import os import string + import py import pytest import utils @@ -8,34 +9,34 @@ import utils TEST_PATHS = [utils.ALT_FILE1, utils.ALT_FILE2, utils.ALT_DIR] -@pytest.mark.usefixtures('ds1_copy') -@pytest.mark.parametrize('yadm_alt', [True, False], ids=['alt', 'worktree']) +@pytest.mark.usefixtures("ds1_copy") +@pytest.mark.parametrize("yadm_alt", [True, False], ids=["alt", "worktree"]) @pytest.mark.parametrize( - 'tracked,encrypt,exclude', [ + "tracked,encrypt,exclude", + [ (False, False, False), (True, False, False), (False, True, False), (False, True, True), - ], ids=['untracked', 'tracked', 'encrypted', 'excluded']) -def test_alt_source( - runner, paths, - tracked, encrypt, exclude, - yadm_alt): + ], + ids=["untracked", "tracked", "encrypted", "excluded"], +) +def test_alt_source(runner, paths, tracked, encrypt, exclude, yadm_alt): """Test yadm alt operates on all expected sources of alternates""" yadm_dir, yadm_data = setup_standard_yadm_dir(paths) utils.create_alt_files( - paths, '##default', tracked=tracked, encrypt=encrypt, exclude=exclude, - yadm_alt=yadm_alt, yadm_dir=yadm_dir) - run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt']) + paths, "##default", tracked=tracked, encrypt=encrypt, exclude=exclude, yadm_alt=yadm_alt, yadm_dir=yadm_dir + ) + run = runner([paths.pgm, "-Y", yadm_dir, "--yadm-data", yadm_data, "alt"]) assert run.success - assert run.err == '' + assert run.err == "" linked = utils.parse_alt_output(run.out) - basepath = yadm_dir.join('alt') if yadm_alt else paths.work + basepath = yadm_dir.join("alt") if yadm_alt else paths.work for link_path in TEST_PATHS: - source_file_content = link_path + '##default' + source_file_content = link_path + "##default" source_file = basepath.join(source_file_content) link_file = paths.work.join(link_path) if tracked or (encrypt and not exclude): @@ -45,63 +46,69 @@ def test_alt_source( assert link_file.read() == source_file_content assert str(source_file) in linked else: - assert link_file.join( - utils.CONTAINED).read() == source_file_content + assert link_file.join(utils.CONTAINED).read() == source_file_content assert str(source_file) in linked else: assert not link_file.exists() assert str(source_file) not in linked -@pytest.mark.usefixtures('ds1_copy') -@pytest.mark.parametrize('yadm_alt', [True, False], ids=['alt', 'worktree']) +@pytest.mark.usefixtures("ds1_copy") +@pytest.mark.parametrize("yadm_alt", [True, False], ids=["alt", "worktree"]) def test_relative_link(runner, paths, yadm_alt): """Confirm links created are relative""" yadm_dir, yadm_data = setup_standard_yadm_dir(paths) utils.create_alt_files( - paths, '##default', tracked=True, encrypt=False, exclude=False, - yadm_alt=yadm_alt, yadm_dir=yadm_dir) - run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt']) + paths, "##default", tracked=True, encrypt=False, exclude=False, yadm_alt=yadm_alt, yadm_dir=yadm_dir + ) + run = runner([paths.pgm, "-Y", yadm_dir, "--yadm-data", yadm_data, "alt"]) assert run.success - assert run.err == '' + assert run.err == "" - basepath = yadm_dir.join('alt') if yadm_alt else paths.work + basepath = yadm_dir.join("alt") if yadm_alt else paths.work for link_path in TEST_PATHS: - source_file_content = link_path + '##default' + source_file_content = link_path + "##default" source_file = basepath.join(source_file_content) link_file = paths.work.join(link_path) link = link_file.readlink() - relpath = os.path.relpath( - source_file, start=os.path.dirname(link_file)) + relpath = os.path.relpath(source_file, start=os.path.dirname(link_file)) assert link == relpath -@pytest.mark.usefixtures('ds1_copy') -@pytest.mark.parametrize('suffix', [ - '##default', - '##default,e.txt', '##default,extension.txt', - '##a.$tst_arch', '##arch.$tst_arch', - '##o.$tst_sys', '##os.$tst_sys', - '##d.$tst_distro', '##distro.$tst_distro', - '##f.$tst_distro_family', '##distro_family.$tst_distro_family', - '##c.$tst_class', '##class.$tst_class', - '##h.$tst_host', '##hostname.$tst_host', - '##u.$tst_user', '##user.$tst_user', - ]) -def test_alt_conditions( - runner, paths, - tst_arch, tst_sys, tst_distro, tst_distro_family, tst_host, tst_user, - suffix): +@pytest.mark.usefixtures("ds1_copy") +@pytest.mark.parametrize( + "suffix", + [ + "##default", + "##default,e.txt", + "##default,extension.txt", + "##a.$tst_arch", + "##arch.$tst_arch", + "##o.$tst_sys", + "##os.$tst_sys", + "##d.$tst_distro", + "##distro.$tst_distro", + "##f.$tst_distro_family", + "##distro_family.$tst_distro_family", + "##c.$tst_class", + "##class.$tst_class", + "##h.$tst_host", + "##hostname.$tst_host", + "##u.$tst_user", + "##user.$tst_user", + ], +) +def test_alt_conditions(runner, paths, tst_arch, tst_sys, tst_distro, tst_distro_family, tst_host, tst_user, suffix): """Test conditions supported by yadm alt""" yadm_dir, yadm_data = setup_standard_yadm_dir(paths) # set the class - tst_class = 'testclass' - utils.set_local(paths, 'class', tst_class + ".before") - utils.set_local(paths, 'class', tst_class, add=True) - utils.set_local(paths, 'class', tst_class + ".after", add=True) + tst_class = "testclass" + utils.set_local(paths, "class", tst_class + ".before") + utils.set_local(paths, "class", tst_class, add=True) + utils.set_local(paths, "class", tst_class + ".after", add=True) suffix = string.Template(suffix).substitute( tst_arch=tst_arch, @@ -114,9 +121,9 @@ def test_alt_conditions( ) utils.create_alt_files(paths, suffix) - run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt']) + run = runner([paths.pgm, "-Y", yadm_dir, "--yadm-data", yadm_data, "alt"]) assert run.success - assert run.err == '' + assert run.err == "" linked = utils.parse_alt_output(run.out) for link_path in TEST_PATHS: @@ -127,27 +134,31 @@ def test_alt_conditions( assert paths.work.join(link_path).read() == source_file assert str(paths.work.join(source_file)) in linked else: - assert paths.work.join(link_path).join( - utils.CONTAINED).read() == source_file + assert paths.work.join(link_path).join(utils.CONTAINED).read() == source_file assert str(paths.work.join(source_file)) in linked -@pytest.mark.usefixtures('ds1_copy') +@pytest.mark.usefixtures("ds1_copy") +@pytest.mark.parametrize("kind", ["default", "", None, "envtpl", "j2cli", "j2", "esh"]) @pytest.mark.parametrize( - 'kind', ['default', '', None, 'envtpl', 'j2cli', 'j2', 'esh']) -@pytest.mark.parametrize('label', ['t', 'template', 'yadm', ]) -def test_alt_templates( - runner, paths, kind, label): + "label", + [ + "t", + "template", + "yadm", + ], +) +def test_alt_templates(runner, paths, kind, label): """Test templates supported by yadm alt""" yadm_dir, yadm_data = setup_standard_yadm_dir(paths) - suffix = f'##{label}.{kind}' + suffix = f"##{label}.{kind}" if kind is None: - suffix = f'##{label}' + suffix = f"##{label}" utils.create_alt_files(paths, suffix) - run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt']) + run = runner([paths.pgm, "-Y", yadm_dir, "--yadm-data", yadm_data, "alt"]) assert run.success - assert run.err == '' + assert run.err == "" created = utils.parse_alt_output(run.out, linked=False) for created_path in TEST_PATHS: @@ -158,41 +169,39 @@ def test_alt_templates( assert str(paths.work.join(source_file)) in created -@pytest.mark.usefixtures('ds1_copy') -@pytest.mark.parametrize('autoalt', [None, 'true', 'false']) +@pytest.mark.usefixtures("ds1_copy") +@pytest.mark.parametrize("autoalt", [None, "true", "false"]) def test_auto_alt(runner, yadm_cmd, paths, autoalt): """Test auto alt""" # set the value of auto-alt if autoalt: - os.system(' '.join(yadm_cmd('config', 'yadm.auto-alt', autoalt))) + os.system(" ".join(yadm_cmd("config", "yadm.auto-alt", autoalt))) - utils.create_alt_files(paths, '##default') - run = runner(yadm_cmd('status')) + utils.create_alt_files(paths, "##default") + run = runner(yadm_cmd("status")) assert run.success - assert run.err == '' + assert run.err == "" linked = utils.parse_alt_output(run.out) for link_path in TEST_PATHS: - source_file = link_path + '##default' - if autoalt == 'false': + source_file = link_path + "##default" + if autoalt == "false": assert not paths.work.join(link_path).exists() else: assert paths.work.join(link_path).islink() - target = py.path.local( - os.path.realpath(paths.work.join(link_path))) + target = py.path.local(os.path.realpath(paths.work.join(link_path))) if target.isfile(): assert paths.work.join(link_path).read() == source_file # no linking output when run via auto-alt assert str(paths.work.join(source_file)) not in linked else: - assert paths.work.join(link_path).join( - utils.CONTAINED).read() == source_file + assert paths.work.join(link_path).join(utils.CONTAINED).read() == source_file # no linking output when run via auto-alt assert str(paths.work.join(source_file)) not in linked -@pytest.mark.usefixtures('ds1_copy') +@pytest.mark.usefixtures("ds1_copy") def test_stale_link_removal(runner, yadm_cmd, paths): """Stale links to alternative files are removed @@ -201,48 +210,47 @@ def test_stale_link_removal(runner, yadm_cmd, paths): """ # set the class - tst_class = 'testclass' - utils.set_local(paths, 'class', tst_class) + tst_class = "testclass" + utils.set_local(paths, "class", tst_class) # create files which match the test class - utils.create_alt_files(paths, f'##class.{tst_class}') + utils.create_alt_files(paths, f"##class.{tst_class}") # run alt to trigger linking - run = runner(yadm_cmd('alt')) + run = runner(yadm_cmd("alt")) assert run.success - assert run.err == '' + assert run.err == "" linked = utils.parse_alt_output(run.out) # assert the proper linking has occurred for stale_path in TEST_PATHS: - source_file = stale_path + '##class.' + tst_class + source_file = stale_path + "##class." + tst_class assert paths.work.join(stale_path).islink() target = py.path.local(os.path.realpath(paths.work.join(stale_path))) if target.isfile(): assert paths.work.join(stale_path).read() == source_file assert str(paths.work.join(source_file)) in linked else: - assert paths.work.join(stale_path).join( - utils.CONTAINED).read() == source_file + assert paths.work.join(stale_path).join(utils.CONTAINED).read() == source_file assert str(paths.work.join(source_file)) in linked # change the class so there are no valid alternates - utils.set_local(paths, 'class', 'changedclass') + utils.set_local(paths, "class", "changedclass") # run alt to trigger linking - run = runner(yadm_cmd('alt')) + run = runner(yadm_cmd("alt")) assert run.success - assert run.err == '' + assert run.err == "" linked = utils.parse_alt_output(run.out) # assert the linking is removed for stale_path in TEST_PATHS: - source_file = stale_path + '##class.' + tst_class + source_file = stale_path + "##class." + tst_class assert not paths.work.join(stale_path).exists() assert str(paths.work.join(source_file)) not in linked -@pytest.mark.usefixtures('ds1_repo_copy') +@pytest.mark.usefixtures("ds1_repo_copy") def test_template_overwrite_symlink(runner, yadm_cmd, paths, tst_sys): """Remove symlinks before processing a template @@ -251,45 +259,44 @@ def test_template_overwrite_symlink(runner, yadm_cmd, paths, tst_sys): be removed just before processing a template. """ - target = paths.work.join(f'test_link##os.{tst_sys}') - target.write('target') + target = paths.work.join(f"test_link##os.{tst_sys}") + target.write("target") - link = paths.work.join('test_link') + link = paths.work.join("test_link") link.mksymlinkto(target, absolute=1) - template = paths.work.join('test_link##template.default') - template.write('test-data') + template = paths.work.join("test_link##template.default") + template.write("test-data") - run = runner(yadm_cmd('add', target, template)) + run = runner(yadm_cmd("add", target, template)) assert run.success - assert run.err == '' - assert run.out == '' + assert run.err == "" + assert run.out == "" assert not link.islink() - assert target.read().strip() == 'target' - assert link.read().strip() == 'test-data' + assert target.read().strip() == "target" + assert link.read().strip() == "test-data" -@pytest.mark.usefixtures('ds1_copy') -@pytest.mark.parametrize('style', ['symlink', 'template']) +@pytest.mark.usefixtures("ds1_copy") +@pytest.mark.parametrize("style", ["symlink", "template"]) def test_ensure_alt_path(runner, paths, style): """Test that directories are created before making alternates""" yadm_dir, yadm_data = setup_standard_yadm_dir(paths) - suffix = 'default' if style == 'symlink' else 'template' - filename = 'a/b/c/file' - source = yadm_dir.join(f'alt/{filename}##{suffix}') - source.write('test-data', ensure=True) - run = runner([ - paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'add', source]) + suffix = "default" if style == "symlink" else "template" + filename = "a/b/c/file" + source = yadm_dir.join(f"alt/{filename}##{suffix}") + source.write("test-data", ensure=True) + run = runner([paths.pgm, "-Y", yadm_dir, "--yadm-data", yadm_data, "add", source]) assert run.success - assert run.err == '' - assert run.out == '' - assert paths.work.join(filename).read().strip() == 'test-data' + assert run.err == "" + assert run.out == "" + assert paths.work.join(filename).read().strip() == "test-data" def setup_standard_yadm_dir(paths): """Configure a yadm home within the work tree""" - std_yadm_dir = paths.work.mkdir('.config').mkdir('yadm') - std_yadm_data = paths.work.mkdir('.local').mkdir('share').mkdir('yadm') - std_yadm_data.join('repo.git').mksymlinkto(paths.repo, absolute=1) - std_yadm_dir.join('encrypt').mksymlinkto(paths.encrypt, absolute=1) + std_yadm_dir = paths.work.mkdir(".config").mkdir("yadm") + std_yadm_data = paths.work.mkdir(".local").mkdir("share").mkdir("yadm") + std_yadm_data.join("repo.git").mksymlinkto(paths.repo, absolute=1) + std_yadm_dir.join("encrypt").mksymlinkto(paths.encrypt, absolute=1) return std_yadm_dir, std_yadm_data diff --git a/test/test_alt_copy.py b/test/test_alt_copy.py index fa8e09c..e1beece 100644 --- a/test/test_alt_copy.py +++ b/test/test_alt_copy.py @@ -1,45 +1,46 @@ """Test yadm.alt-copy""" import os + import pytest @pytest.mark.parametrize( - 'setting, expect_link, pre_existing', [ + "setting, expect_link, pre_existing", + [ (None, True, None), (True, False, None), (False, True, None), - (True, False, 'link'), - (True, False, 'file'), + (True, False, "link"), + (True, False, "file"), ], ids=[ - 'unset', - 'true', - 'false', - 'pre-existing symlink', - 'pre-existing file', - ]) -@pytest.mark.usefixtures('ds1_copy') -def test_alt_copy( - runner, yadm_cmd, paths, tst_sys, - setting, expect_link, pre_existing): + "unset", + "true", + "false", + "pre-existing symlink", + "pre-existing file", + ], +) +@pytest.mark.usefixtures("ds1_copy") +def test_alt_copy(runner, yadm_cmd, paths, tst_sys, setting, expect_link, pre_existing): """Test yadm.alt-copy""" if setting is not None: - os.system(' '.join(yadm_cmd('config', 'yadm.alt-copy', str(setting)))) + os.system(" ".join(yadm_cmd("config", "yadm.alt-copy", str(setting)))) - expected_content = f'test_alt_copy##os.{tst_sys}' + expected_content = f"test_alt_copy##os.{tst_sys}" - alt_path = paths.work.join('test_alt_copy') - if pre_existing == 'symlink': + alt_path = paths.work.join("test_alt_copy") + if pre_existing == "symlink": alt_path.mklinkto(expected_content) - elif pre_existing == 'file': - alt_path.write('wrong content') + elif pre_existing == "file": + alt_path.write("wrong content") - run = runner(yadm_cmd('alt')) + run = runner(yadm_cmd("alt")) assert run.success - assert run.err == '' - assert 'Linking' in run.out + assert run.err == "" + assert "Linking" in run.out assert alt_path.read() == expected_content assert alt_path.islink() == expect_link diff --git a/test/test_assert_private_dirs.py b/test/test_assert_private_dirs.py index bfd55ac..0cba1f6 100644 --- a/test/test_assert_private_dirs.py +++ b/test/test_assert_private_dirs.py @@ -2,13 +2,14 @@ import os import re + import pytest -pytestmark = pytest.mark.usefixtures('ds1_copy') -PRIVATE_DIRS = ['.gnupg', '.ssh'] +pytestmark = pytest.mark.usefixtures("ds1_copy") +PRIVATE_DIRS = [".gnupg", ".ssh"] -@pytest.mark.parametrize('home', [True, False], ids=['home', 'not-home']) +@pytest.mark.parametrize("home", [True, False], ids=["home", "not-home"]) def test_pdirs_missing(runner, yadm_cmd, paths, home): """Private dirs (private dirs missing) @@ -24,15 +25,15 @@ def test_pdirs_missing(runner, yadm_cmd, paths, home): path.remove() assert not path.exists() - env = {'DEBUG': 'yes'} + env = {"DEBUG": "yes"} if home: - env['HOME'] = paths.work + env["HOME"] = paths.work # run status - run = runner(command=yadm_cmd('status'), env=env) + run = runner(command=yadm_cmd("status"), env=env) assert run.success - assert run.err == '' - assert 'On branch master' in run.out + assert run.err == "" + assert "On branch master" in run.out # confirm directories are created # and are protected @@ -40,17 +41,15 @@ def test_pdirs_missing(runner, yadm_cmd, paths, home): path = paths.work.join(pdir) if home: assert path.exists() - assert oct(path.stat().mode).endswith('00'), ('Directory is ' - 'not secured') + assert oct(path.stat().mode).endswith("00"), "Directory is " "not secured" else: assert not path.exists() # confirm directories are created before command is run: if home: assert re.search( - (r'Creating.+\.(gnupg|ssh).+Creating.+\.(gnupg|ssh).+' - r'Running git command git status'), - run.out, re.DOTALL), 'directories created before command is run' + r"Creating.+\.(gnupg|ssh).+Creating.+\.(gnupg|ssh).+Running git command git status", run.out, re.DOTALL + ), "directories created before command is run" def test_pdirs_missing_apd_false(runner, yadm_cmd, paths): @@ -70,14 +69,13 @@ def test_pdirs_missing_apd_false(runner, yadm_cmd, paths): assert not path.exists() # set configuration - os.system(' '.join(yadm_cmd( - 'config', '--bool', 'yadm.auto-private-dirs', 'false'))) + os.system(" ".join(yadm_cmd("config", "--bool", "yadm.auto-private-dirs", "false"))) # run status - run = runner(command=yadm_cmd('status')) + run = runner(command=yadm_cmd("status")) assert run.success - assert run.err == '' - assert 'On branch master' in run.out + assert run.err == "" + assert "On branch master" in run.out # confirm directories are STILL missing for pdir in PRIVATE_DIRS: @@ -99,19 +97,18 @@ def test_pdirs_exist_apd_false(runner, yadm_cmd, paths): if not path.isdir(): path.mkdir() path.chmod(0o777) - assert oct(path.stat().mode).endswith('77'), 'Directory is secure.' + assert oct(path.stat().mode).endswith("77"), "Directory is secure." # set configuration - os.system(' '.join(yadm_cmd( - 'config', '--bool', 'yadm.auto-perms', 'false'))) + os.system(" ".join(yadm_cmd("config", "--bool", "yadm.auto-perms", "false"))) # run status - run = runner(command=yadm_cmd('status')) + run = runner(command=yadm_cmd("status")) assert run.success - assert run.err == '' - assert 'On branch master' in run.out + assert run.err == "" + assert "On branch master" in run.out # created directories are STILL permissive for pdir in PRIVATE_DIRS: path = paths.work.join(pdir) - assert oct(path.stat().mode).endswith('77'), 'Directory is secure' + assert oct(path.stat().mode).endswith("77"), "Directory is secure" diff --git a/test/test_bootstrap.py b/test/test_bootstrap.py index 4865ece..099dde8 100644 --- a/test/test_bootstrap.py +++ b/test/test_bootstrap.py @@ -4,32 +4,30 @@ import pytest @pytest.mark.parametrize( - 'exists, executable, code, expect', [ - (False, False, 1, 'Cannot execute bootstrap'), - (True, False, 1, 'is not an executable program'), - (True, True, 123, 'Bootstrap successful'), - ], ids=[ - 'missing', - 'not executable', - 'executable', - ]) -def test_bootstrap( - runner, yadm_cmd, paths, exists, executable, code, expect): + "exists, executable, code, expect", + [ + (False, False, 1, "Cannot execute bootstrap"), + (True, False, 1, "is not an executable program"), + (True, True, 123, "Bootstrap successful"), + ], + ids=[ + "missing", + "not executable", + "executable", + ], +) +def test_bootstrap(runner, yadm_cmd, paths, exists, executable, code, expect): """Test bootstrap command""" if exists: - paths.bootstrap.write('') + paths.bootstrap.write("") if executable: - paths.bootstrap.write( - '#!/bin/bash\n' - f'echo {expect}\n' - f'exit {code}\n' - ) + paths.bootstrap.write("#!/bin/bash\n" f"echo {expect}\n" f"exit {code}\n") paths.bootstrap.chmod(0o775) - run = runner(command=yadm_cmd('bootstrap')) + run = runner(command=yadm_cmd("bootstrap")) assert run.code == code if exists and executable: - assert run.err == '' + assert run.err == "" assert expect in run.out else: assert expect in run.err - assert run.out == '' + assert run.out == "" diff --git a/test/test_clean.py b/test/test_clean.py index 39e7e54..4eaf1e4 100644 --- a/test/test_clean.py +++ b/test/test_clean.py @@ -3,9 +3,9 @@ def test_clean_command(runner, yadm_cmd): """Run with clean command""" - run = runner(command=yadm_cmd('clean')) + run = runner(command=yadm_cmd("clean")) # do nothing, this is a dangerous Git command when managing dot files # report the command as disabled and exit as a failure assert run.failure - assert run.out == '' - assert 'disabled' in run.err + assert run.out == "" + assert "disabled" in run.err diff --git a/test/test_clone.py b/test/test_clone.py index d5da7d9..1cae929 100644 --- a/test/test_clone.py +++ b/test/test_clone.py @@ -2,30 +2,32 @@ import os import re + import pytest BOOTSTRAP_CODE = 123 -BOOTSTRAP_MSG = 'Bootstrap successful' +BOOTSTRAP_MSG = "Bootstrap successful" -@pytest.mark.usefixtures('remote') +@pytest.mark.usefixtures("remote") @pytest.mark.parametrize( - 'good_remote, repo_exists, force, conflicts', [ + "good_remote, repo_exists, force, conflicts", + [ (False, False, False, False), (True, False, False, False), (True, True, False, False), (True, True, True, False), (True, False, False, True), - ], ids=[ - 'bad remote', - 'simple', - 'existing repo', - '-f', - 'conflicts', - ]) -def test_clone( - runner, paths, yadm_cmd, repo_config, ds1, - good_remote, repo_exists, force, conflicts): + ], + ids=[ + "bad remote", + "simple", + "existing repo", + "-f", + "conflicts", + ], +) +def test_clone(runner, paths, yadm_cmd, repo_config, ds1, good_remote, repo_exists, force, conflicts): """Test basic clone operation""" # clear out the work path @@ -33,74 +35,70 @@ def test_clone( paths.work.mkdir() # determine remote url - remote_url = f'file://{paths.remote}' + remote_url = f"file://{paths.remote}" if not good_remote: - remote_url = 'file://bad_remote' + remote_url = "file://bad_remote" old_repo = None if repo_exists: # put a repo in the way paths.repo.mkdir() - old_repo = paths.repo.join('old_repo') - old_repo.write('old_repo') + old_repo = paths.repo.join("old_repo") + old_repo.write("old_repo") if conflicts: - ds1.tracked[0].relative.write('conflict') + ds1.tracked[0].relative.write("conflict") assert ds1.tracked[0].relative.exists() # run the clone command - args = ['clone', '-w', paths.work] + args = ["clone", "-w", paths.work] if force: - args += ['-f'] + args += ["-f"] args += [remote_url] run = runner(command=yadm_cmd(*args)) if not good_remote: # clone should fail assert run.failure - assert run.out == '' - assert 'Unable to clone the repository' in run.err + assert run.out == "" + assert "Unable to clone the repository" in run.err assert not paths.repo.exists() elif repo_exists and not force: # can't overwrite data assert run.failure - assert run.out == '' - assert 'Git repo already exists' in run.err + assert run.out == "" + assert "Git repo already exists" in run.err else: # clone should succeed, and repo should be configured properly assert successful_clone(run, paths, repo_config) # these clones should have master as HEAD - verify_head(paths, 'master') + verify_head(paths, "master") # ensure conflicts are handled properly if conflicts: - assert 'NOTE' in run.out - assert 'Local files with content that differs' in run.out + assert "NOTE" in run.out + assert "Local files with content that differs" in run.out # confirm correct Git origin - run = runner( - command=('git', 'remote', '-v', 'show'), - env={'GIT_DIR': paths.repo}) + run = runner(command=("git", "remote", "-v", "show"), env={"GIT_DIR": paths.repo}) assert run.success - assert run.err == '' - assert f'origin\t{remote_url}' in run.out + assert run.err == "" + assert f"origin\t{remote_url}" in run.out # ensure conflicts are really preserved if conflicts: # test that the conflicts are preserved in the work tree - run = runner( - command=yadm_cmd('status', '-uno', '--porcelain'), - cwd=paths.work) + run = runner(command=yadm_cmd("status", "-uno", "--porcelain"), cwd=paths.work) assert run.success - assert run.err == '' + assert run.err == "" assert str(ds1.tracked[0].path) in run.out # verify content of the conflicts - run = runner(command=yadm_cmd('diff'), cwd=paths.work) + run = runner(command=yadm_cmd("diff"), cwd=paths.work) assert run.success - assert run.err == '' - assert '\n+conflict' in run.out, 'conflict overwritten' + assert run.err == "" + assert "\n+conflict" in run.out, "conflict overwritten" # another force-related assertion if old_repo: @@ -110,54 +108,56 @@ def test_clone( assert old_repo.exists() -@pytest.mark.usefixtures('remote') +@pytest.mark.usefixtures("remote") @pytest.mark.parametrize( - 'bs_exists, bs_param, answer', [ - (False, '--bootstrap', None), - (True, '--bootstrap', None), - (True, '--no-bootstrap', None), - (True, None, 'n'), - (True, None, 'y'), - ], ids=[ - 'force, missing', - 'force, existing', - 'prevent', - 'existing, answer n', - 'existing, answer y', - ]) -def test_clone_bootstrap( - runner, paths, yadm_cmd, repo_config, bs_exists, bs_param, answer): + "bs_exists, bs_param, answer", + [ + (False, "--bootstrap", None), + (True, "--bootstrap", None), + (True, "--no-bootstrap", None), + (True, None, "n"), + (True, None, "y"), + ], + ids=[ + "force, missing", + "force, existing", + "prevent", + "existing, answer n", + "existing, answer y", + ], +) +def test_clone_bootstrap(runner, paths, yadm_cmd, repo_config, bs_exists, bs_param, answer): """Test bootstrap clone features""" # establish a bootstrap create_bootstrap(paths, bs_exists) # run the clone command - args = ['clone', '-w', paths.work] + args = ["clone", "-w", paths.work] if bs_param: args += [bs_param] - args += [f'file://{paths.remote}'] + args += [f"file://{paths.remote}"] expect = [] if answer: - expect.append(('Would you like to execute it now', answer)) + expect.append(("Would you like to execute it now", answer)) run = runner(command=yadm_cmd(*args), expect=expect) if answer: - assert 'Would you like to execute it now' in run.out + assert "Would you like to execute it now" in run.out expected_code = 0 - if bs_exists and bs_param != '--no-bootstrap': + if bs_exists and bs_param != "--no-bootstrap": expected_code = BOOTSTRAP_CODE - if answer == 'y': + if answer == "y": expected_code = BOOTSTRAP_CODE assert BOOTSTRAP_MSG in run.out - elif answer == 'n': + elif answer == "n": expected_code = 0 assert BOOTSTRAP_MSG not in run.out assert successful_clone(run, paths, repo_config, expected_code) - verify_head(paths, 'master') + verify_head(paths, "master") if not bs_exists: assert BOOTSTRAP_MSG not in run.out @@ -166,108 +166,90 @@ def test_clone_bootstrap( def create_bootstrap(paths, exists): """Create bootstrap file for test""" if exists: - paths.bootstrap.write( - '#!/bin/sh\n' - f'echo {BOOTSTRAP_MSG}\n' - f'exit {BOOTSTRAP_CODE}\n') + paths.bootstrap.write("#!/bin/sh\n" f"echo {BOOTSTRAP_MSG}\n" f"exit {BOOTSTRAP_CODE}\n") paths.bootstrap.chmod(0o775) assert paths.bootstrap.exists() else: assert not paths.bootstrap.exists() -@pytest.mark.usefixtures('remote') +@pytest.mark.usefixtures("remote") @pytest.mark.parametrize( - 'private_type, in_repo, in_work', [ - ('ssh', False, True), - ('gnupg', False, True), - ('ssh', True, True), - ('gnupg', True, True), - ('ssh', True, False), - ('gnupg', True, False), - ], ids=[ - 'open ssh, not tracked', - 'open gnupg, not tracked', - 'open ssh, tracked', - 'open gnupg, tracked', - 'missing ssh, tracked', - 'missing gnupg, tracked', - ]) -def test_clone_perms( - runner, yadm_cmd, paths, repo_config, - private_type, in_repo, in_work): + "private_type, in_repo, in_work", + [ + ("ssh", False, True), + ("gnupg", False, True), + ("ssh", True, True), + ("gnupg", True, True), + ("ssh", True, False), + ("gnupg", True, False), + ], + ids=[ + "open ssh, not tracked", + "open gnupg, not tracked", + "open ssh, tracked", + "open gnupg, tracked", + "missing ssh, tracked", + "missing gnupg, tracked", + ], +) +def test_clone_perms(runner, yadm_cmd, paths, repo_config, private_type, in_repo, in_work): """Test clone permission-related functions""" # update remote repo to include private data if in_repo: - rpath = paths.work.mkdir(f'.{private_type}').join('related') - rpath.write('related') + rpath = paths.work.mkdir(f".{private_type}").join("related") + rpath.write("related") os.system(f'GIT_DIR="{paths.remote}" git add {rpath}') os.system(f'GIT_DIR="{paths.remote}" git commit -m "{rpath}"') rpath.remove() # ensure local private data is insecure at the start if in_work: - pdir = paths.work.join(f'.{private_type}') + pdir = paths.work.join(f".{private_type}") if not pdir.exists(): pdir.mkdir() - pfile = pdir.join('existing') - pfile.write('existing') + pfile = pdir.join("existing") + pfile.write("existing") pdir.chmod(0o777) pfile.chmod(0o777) else: paths.work.remove() paths.work.mkdir() - env = {'HOME': paths.work} - run = runner( - yadm_cmd('clone', '-d', '-w', paths.work, f'file://{paths.remote}'), - env=env - ) + env = {"HOME": paths.work} + run = runner(yadm_cmd("clone", "-d", "-w", paths.work, f"file://{paths.remote}"), env=env) assert successful_clone(run, paths, repo_config) - verify_head(paths, 'master') + verify_head(paths, "master") if in_work: # private directories which already exist, should be left as they are, # which in this test is "insecure". - assert re.search( - f'initial private dir perms drwxrwxrwx.+.{private_type}', - run.out) - assert re.search( - f'pre-checkout private dir perms drwxrwxrwx.+.{private_type}', - run.out) - assert re.search( - f'post-checkout private dir perms drwxrwxrwx.+.{private_type}', - run.out) + assert re.search(f"initial private dir perms drwxrwxrwx.+.{private_type}", run.out) + assert re.search(f"pre-checkout private dir perms drwxrwxrwx.+.{private_type}", run.out) + assert re.search(f"post-checkout private dir perms drwxrwxrwx.+.{private_type}", run.out) else: # private directories which are created, should be done prior to # checkout, and with secure permissions. - assert 'initial private dir perms' not in run.out - assert re.search( - f'pre-checkout private dir perms drwx------.+.{private_type}', - run.out) - assert re.search( - f'post-checkout private dir perms drwx------.+.{private_type}', - run.out) + assert "initial private dir perms" not in run.out + assert re.search(f"pre-checkout private dir perms drwx------.+.{private_type}", run.out) + assert re.search(f"post-checkout private dir perms drwx------.+.{private_type}", run.out) # standard perms still apply afterwards unless disabled with auto.perms - assert oct( - paths.work.join(f'.{private_type}').stat().mode).endswith('00'), ( - f'.{private_type} has not been secured by auto.perms') + assert oct(paths.work.join(f".{private_type}").stat().mode).endswith( + "00" + ), f".{private_type} has not been secured by auto.perms" -@pytest.mark.usefixtures('remote') -@pytest.mark.parametrize( - 'branch', ['master', 'default', 'valid', 'invalid']) +@pytest.mark.usefixtures("remote") +@pytest.mark.parametrize("branch", ["master", "default", "valid", "invalid"]) def test_alternate_branch(runner, paths, yadm_cmd, repo_config, branch): """Test cloning a branch other than master""" # add a "valid" branch to the remote os.system(f'GIT_DIR="{paths.remote}" git checkout -b valid') - os.system( - f'GIT_DIR="{paths.remote}" git commit ' - f'--allow-empty -m "This branch is valid"') - if branch != 'default': + os.system(f'GIT_DIR="{paths.remote}" git commit ' f'--allow-empty -m "This branch is valid"') + if branch != "default": # When branch == 'default', the "default" branch of the remote repo # will remain "valid" to validate identification the correct default # branch by inspecting the repo. Otherwise it will be set back to @@ -278,45 +260,43 @@ def test_alternate_branch(runner, paths, yadm_cmd, repo_config, branch): paths.work.remove() paths.work.mkdir() - remote_url = f'file://{paths.remote}' + remote_url = f"file://{paths.remote}" # run the clone command - args = ['clone', '-w', paths.work] - if branch not in ['master', 'default']: - args += ['-b', branch] + args = ["clone", "-w", paths.work] + if branch not in ["master", "default"]: + args += ["-b", branch] args += [remote_url] run = runner(command=yadm_cmd(*args)) - if branch == 'invalid': + if branch == "invalid": assert run.failure - assert 'ERROR: Unable to clone the repository' in run.err + assert "ERROR: Unable to clone the repository" in run.err assert f"Remote branch {branch} not found in upstream" in run.err else: assert successful_clone(run, paths, repo_config) # confirm correct Git origin - run = runner( - command=('git', 'remote', '-v', 'show'), - env={'GIT_DIR': paths.repo}) + run = runner(command=("git", "remote", "-v", "show"), env={"GIT_DIR": paths.repo}) assert run.success - assert run.err == '' - assert f'origin\t{remote_url}' in run.out - run = runner(command=yadm_cmd('show')) - if branch == 'master': - assert 'Initial commit' in run.out - verify_head(paths, 'master') + assert run.err == "" + assert f"origin\t{remote_url}" in run.out + run = runner(command=yadm_cmd("show")) + if branch == "master": + assert "Initial commit" in run.out + verify_head(paths, "master") else: - assert 'This branch is valid' in run.out - verify_head(paths, 'valid') + assert "This branch is valid" in run.out + verify_head(paths, "valid") def successful_clone(run, paths, repo_config, expected_code=0): """Assert clone is successful""" assert run.code == expected_code - assert oct(paths.repo.stat().mode).endswith('00'), 'Repo is not secured' - assert repo_config('core.bare') == 'false' - assert repo_config('status.showUntrackedFiles') == 'no' - assert repo_config('yadm.managed') == 'true' + assert oct(paths.repo.stat().mode).endswith("00"), "Repo is not secured" + assert repo_config("core.bare") == "false" + assert repo_config("status.showUntrackedFiles") == "no" + assert repo_config("yadm.managed") == "true" return True @@ -331,15 +311,18 @@ def remote(paths, ds1_repo_copy): paths.repo.move(paths.remote) -def test_no_repo(runner, yadm_cmd, ): +def test_no_repo( + runner, + yadm_cmd, +): """Test cloning without specifying a repo""" - run = runner(command=yadm_cmd('clone', '-f')) + run = runner(command=yadm_cmd("clone", "-f")) assert run.failure - assert run.out == '' - assert 'ERROR: Unable to clone the repository' in run.err - assert 'repository \'repo.git\' does not exist' in run.err + assert run.out == "" + assert "ERROR: Unable to clone the repository" in run.err + assert "repository 'repo.git' does not exist" in run.err def verify_head(paths, branch): """Assert the local repo has the correct head branch""" - assert paths.repo.join('HEAD').read() == f'ref: refs/heads/{branch}\n' + assert paths.repo.join("HEAD").read() == f"ref: refs/heads/{branch}\n" diff --git a/test/test_config.py b/test/test_config.py index d364128..c43d81c 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -1,13 +1,14 @@ """Test config""" import os + import pytest -TEST_SECTION = 'test' -TEST_ATTRIBUTE = 'attribute' -TEST_KEY = f'{TEST_SECTION}.{TEST_ATTRIBUTE}' -TEST_VALUE = 'testvalue' -TEST_FILE = f'[{TEST_SECTION}]\n\t{TEST_ATTRIBUTE} = {TEST_VALUE}' +TEST_SECTION = "test" +TEST_ATTRIBUTE = "attribute" +TEST_KEY = f"{TEST_SECTION}.{TEST_ATTRIBUTE}" +TEST_VALUE = "testvalue" +TEST_FILE = f"[{TEST_SECTION}]\n\t{TEST_ATTRIBUTE} = {TEST_VALUE}" def test_config_no_params(runner, yadm_cmd, supported_configs): @@ -18,11 +19,11 @@ def test_config_no_params(runner, yadm_cmd, supported_configs): Exit with 0 """ - run = runner(yadm_cmd('config')) + run = runner(yadm_cmd("config")) assert run.success - assert run.err == '' - assert 'Please read the CONFIGURATION section' in run.out + assert run.err == "" + assert "Please read the CONFIGURATION section" in run.out for config in supported_configs: assert config in run.out @@ -34,11 +35,11 @@ def test_config_read_missing(runner, yadm_cmd): Exit with 0 """ - run = runner(yadm_cmd('config', TEST_KEY)) + run = runner(yadm_cmd("config", TEST_KEY)) assert run.success - assert run.err == '' - assert run.out == '' + assert run.err == "" + assert run.out == "" def test_config_write(runner, yadm_cmd, paths): @@ -49,11 +50,11 @@ def test_config_write(runner, yadm_cmd, paths): Exit with 0 """ - run = runner(yadm_cmd('config', TEST_KEY, TEST_VALUE)) + run = runner(yadm_cmd("config", TEST_KEY, TEST_VALUE)) assert run.success - assert run.err == '' - assert run.out == '' + assert run.err == "" + assert run.out == "" assert paths.config.read().strip() == TEST_FILE @@ -65,10 +66,10 @@ def test_config_read(runner, yadm_cmd, paths): """ paths.config.write(TEST_FILE) - run = runner(yadm_cmd('config', TEST_KEY)) + run = runner(yadm_cmd("config", TEST_KEY)) assert run.success - assert run.err == '' + assert run.err == "" assert run.out.strip() == TEST_VALUE @@ -82,16 +83,16 @@ def test_config_update(runner, yadm_cmd, paths): paths.config.write(TEST_FILE) - run = runner(yadm_cmd('config', TEST_KEY, TEST_VALUE + 'extra')) + run = runner(yadm_cmd("config", TEST_KEY, TEST_VALUE + "extra")) assert run.success - assert run.err == '' - assert run.out == '' + assert run.err == "" + assert run.out == "" - assert paths.config.read().strip() == TEST_FILE + 'extra' + assert paths.config.read().strip() == TEST_FILE + "extra" -@pytest.mark.usefixtures('ds1_repo_copy') +@pytest.mark.usefixtures("ds1_repo_copy") def test_config_local_read(runner, yadm_cmd, paths, supported_local_configs): """Read local attribute @@ -101,19 +102,17 @@ def test_config_local_read(runner, yadm_cmd, paths, supported_local_configs): # populate test values for config in supported_local_configs: - os.system( - f'GIT_DIR="{paths.repo}" ' - f'git config --local "{config}" "value_of_{config}"') + os.system(f'GIT_DIR="{paths.repo}" ' f'git config --local "{config}" "value_of_{config}"') # run yadm config for config in supported_local_configs: - run = runner(yadm_cmd('config', config)) + run = runner(yadm_cmd("config", config)) assert run.success - assert run.err == '' - assert run.out.strip() == f'value_of_{config}' + assert run.err == "" + assert run.out.strip() == f"value_of_{config}" -@pytest.mark.usefixtures('ds1_repo_copy') +@pytest.mark.usefixtures("ds1_repo_copy") def test_config_local_write(runner, yadm_cmd, paths, supported_local_configs): """Write local attribute @@ -124,40 +123,37 @@ def test_config_local_write(runner, yadm_cmd, paths, supported_local_configs): # run yadm config for config in supported_local_configs: - run = runner(yadm_cmd('config', config, f'value_of_{config}')) + run = runner(yadm_cmd("config", config, f"value_of_{config}")) assert run.success - assert run.err == '' - assert run.out == '' + assert run.err == "" + assert run.out == "" # verify test values for config in supported_local_configs: - run = runner( - command=('git', 'config', config), - env={'GIT_DIR': paths.repo}) + run = runner(command=("git", "config", config), env={"GIT_DIR": paths.repo}) assert run.success - assert run.err == '' - assert run.out.strip() == f'value_of_{config}' + assert run.err == "" + assert run.out.strip() == f"value_of_{config}" def test_config_without_parent_directory(runner, yadm_cmd, paths): - """Write and read attribute to/from config file with non-existent parent dir + """Write/read attribute to/from config file with non-existent parent dir Update configuration file Display value Exit with 0 """ - config_file = paths.root + '/folder/does/not/exist/config' + config_file = paths.root + "/folder/does/not/exist/config" - run = runner( - yadm_cmd('--yadm-config', config_file, 'config', TEST_KEY, TEST_VALUE)) + run = runner(yadm_cmd("--yadm-config", config_file, "config", TEST_KEY, TEST_VALUE)) assert run.success - assert run.err == '' - assert run.out == '' + assert run.err == "" + assert run.out == "" - run = runner(yadm_cmd('--yadm-config', config_file, 'config', TEST_KEY)) + run = runner(yadm_cmd("--yadm-config", config_file, "config", TEST_KEY)) assert run.success - assert run.err == '' + assert run.err == "" assert run.out.strip() == TEST_VALUE diff --git a/test/test_encryption.py b/test/test_encryption.py index 73f4c35..8c64222 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -1,30 +1,31 @@ """Test encryption""" import os -import pipes +import shlex import time + import pytest -KEY_FILE = 'test/test_key' -KEY_FINGERPRINT = 'F8BBFC746C58945442349BCEBA54FFD04C599B1A' -KEY_NAME = 'yadm-test1' -KEY_TRUST = 'test/ownertrust.txt' -PASSPHRASE = 'ExamplePassword' +KEY_FILE = "test/test_key" +KEY_FINGERPRINT = "F8BBFC746C58945442349BCEBA54FFD04C599B1A" +KEY_NAME = "yadm-test1" +KEY_TRUST = "test/ownertrust.txt" +PASSPHRASE = "ExamplePassword" -pytestmark = pytest.mark.usefixtures('config_git') +pytestmark = pytest.mark.usefixtures("config_git") def add_asymmetric_key(runner, gnupg): """Add asymmetric key""" env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home + env["GNUPGHOME"] = gnupg.home runner( - ['gpg', '--import', pipes.quote(KEY_FILE)], + ["gpg", "--import", shlex.quote(KEY_FILE)], env=env, shell=True, ) runner( - ['gpg', '--import-ownertrust', '<', pipes.quote(KEY_TRUST)], + ["gpg", "--import-ownertrust", "<", shlex.quote(KEY_TRUST)], env=env, shell=True, ) @@ -33,20 +34,14 @@ def add_asymmetric_key(runner, gnupg): def remove_asymmetric_key(runner, gnupg): """Remove asymmetric key""" env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home + env["GNUPGHOME"] = gnupg.home runner( - [ - 'gpg', '--batch', '--yes', - '--delete-secret-keys', pipes.quote(KEY_FINGERPRINT) - ], + ["gpg", "--batch", "--yes", "--delete-secret-keys", shlex.quote(KEY_FINGERPRINT)], env=env, shell=True, ) runner( - [ - 'gpg', '--batch', '--yes', - '--delete-key', pipes.quote(KEY_FINGERPRINT) - ], + ["gpg", "--batch", "--yes", "--delete-key", shlex.quote(KEY_FINGERPRINT)], env=env, shell=True, ) @@ -78,48 +73,48 @@ def encrypt_targets(yadm_cmd, paths): """ # init empty yadm repo - os.system(' '.join(yadm_cmd('init', '-w', str(paths.work), '-f'))) + os.system(" ".join(yadm_cmd("init", "-w", str(paths.work), "-f"))) expected = [] # standard files w/ dirs & spaces - paths.work.join('inc file1').write('inc file1') - expected.append('inc file1') - paths.encrypt.write('inc file1\n') - paths.work.join('inc dir').mkdir() - paths.work.join('inc dir/inc file2').write('inc file2') - expected.append('inc dir/inc file2') - paths.encrypt.write('inc dir/inc file2\n', mode='a') + paths.work.join("inc file1").write("inc file1") + expected.append("inc file1") + paths.encrypt.write("inc file1\n") + paths.work.join("inc dir").mkdir() + paths.work.join("inc dir/inc file2").write("inc file2") + expected.append("inc dir/inc file2") + paths.encrypt.write("inc dir/inc file2\n", mode="a") # standard globs w/ dirs & spaces - paths.work.join('globs file1').write('globs file1') - expected.append('globs file1') - paths.work.join('globs dir').mkdir() - paths.work.join('globs dir/globs file2').write('globs file2') - expected.append('globs dir/globs file2') - paths.encrypt.write('globs*\n', mode='a') + paths.work.join("globs file1").write("globs file1") + expected.append("globs file1") + paths.work.join("globs dir").mkdir() + paths.work.join("globs dir/globs file2").write("globs file2") + expected.append("globs dir/globs file2") + paths.encrypt.write("globs*\n", mode="a") # blank lines - paths.encrypt.write('\n \n\t\n', mode='a') + paths.encrypt.write("\n \n\t\n", mode="a") # comments - paths.work.join('commentfile1').write('commentfile1') - paths.encrypt.write('#commentfile1\n', mode='a') - paths.encrypt.write(' #commentfile1\n', mode='a') + paths.work.join("commentfile1").write("commentfile1") + paths.encrypt.write("#commentfile1\n", mode="a") + paths.encrypt.write(" #commentfile1\n", mode="a") # exclusions - paths.work.join('extest').mkdir() - paths.encrypt.write('extest/*\n', mode='a') # include within extest - paths.work.join('extest/inglob1').write('inglob1') - paths.work.join('extest/exglob1').write('exglob1') - paths.work.join('extest/exglob2').write('exglob2') - paths.encrypt.write('!extest/ex*\n', mode='a') # exclude the ex* - expected.append('extest/inglob1') # should be left with only in* + paths.work.join("extest").mkdir() + paths.encrypt.write("extest/*\n", mode="a") # include within extest + paths.work.join("extest/inglob1").write("inglob1") + paths.work.join("extest/exglob1").write("exglob1") + paths.work.join("extest/exglob2").write("exglob2") + paths.encrypt.write("!extest/ex*\n", mode="a") # exclude the ex* + expected.append("extest/inglob1") # should be left with only in* return expected -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def decrypt_targets(tmpdir_factory, runner, gnupg): """Fixture for setting data to decrypt @@ -128,234 +123,203 @@ def decrypt_targets(tmpdir_factory, runner, gnupg): * creates a list of expected decrypted files """ - tmpdir = tmpdir_factory.mktemp('decrypt_targets') - symmetric = tmpdir.join('symmetric.tar.gz.gpg') - asymmetric = tmpdir.join('asymmetric.tar.gz.gpg') + tmpdir = tmpdir_factory.mktemp("decrypt_targets") + symmetric = tmpdir.join("symmetric.tar.gz.gpg") + asymmetric = tmpdir.join("asymmetric.tar.gz.gpg") expected = [] - tmpdir.join('decrypt1').write('decrypt1') - expected.append('decrypt1') - tmpdir.join('decrypt2').write('decrypt2') - expected.append('decrypt2') - tmpdir.join('subdir').mkdir() - tmpdir.join('subdir/decrypt3').write('subdir/decrypt3') - expected.append('subdir/decrypt3') + tmpdir.join("decrypt1").write("decrypt1") + expected.append("decrypt1") + tmpdir.join("decrypt2").write("decrypt2") + expected.append("decrypt2") + tmpdir.join("subdir").mkdir() + tmpdir.join("subdir/decrypt3").write("subdir/decrypt3") + expected.append("subdir/decrypt3") gnupg.pw(PASSPHRASE) env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home + env["GNUPGHOME"] = gnupg.home run = runner( - ['tar', 'cvf', '-'] + - expected + - ['|', 'gpg', '--batch', '--yes', '-c'] + - ['--output', pipes.quote(str(symmetric))], + ["tar", "cvf", "-"] + + expected + + ["|", "gpg", "--batch", "--yes", "-c"] + + ["--output", shlex.quote(str(symmetric))], cwd=tmpdir, env=env, - shell=True) + shell=True, + ) assert run.success - gnupg.pw('') + gnupg.pw("") add_asymmetric_key(runner, gnupg) run = runner( - ['tar', 'cvf', '-'] + - expected + - ['|', 'gpg', '--batch', '--yes', '-e'] + - ['-r', pipes.quote(KEY_NAME)] + - ['--output', pipes.quote(str(asymmetric))], + ["tar", "cvf", "-"] + + expected + + ["|", "gpg", "--batch", "--yes", "-e"] + + ["-r", shlex.quote(KEY_NAME)] + + ["--output", shlex.quote(str(asymmetric))], cwd=tmpdir, env=env, - shell=True) + shell=True, + ) assert run.success remove_asymmetric_key(runner, gnupg) return { - 'asymmetric': asymmetric, - 'expected': expected, - 'symmetric': symmetric, + "asymmetric": asymmetric, + "expected": expected, + "symmetric": symmetric, } -@pytest.mark.parametrize( - 'bad_phrase', [False, True], - ids=['good_phrase', 'bad_phrase']) -@pytest.mark.parametrize( - 'missing_encrypt', [False, True], - ids=['encrypt_exists', 'encrypt_missing']) -@pytest.mark.parametrize( - 'overwrite', [False, True], - ids=['clean', 'overwrite']) -def test_symmetric_encrypt( - runner, yadm_cmd, paths, encrypt_targets, - gnupg, bad_phrase, overwrite, missing_encrypt): +@pytest.mark.parametrize("bad_phrase", [False, True], ids=["good_phrase", "bad_phrase"]) +@pytest.mark.parametrize("missing_encrypt", [False, True], ids=["encrypt_exists", "encrypt_missing"]) +@pytest.mark.parametrize("overwrite", [False, True], ids=["clean", "overwrite"]) +def test_symmetric_encrypt(runner, yadm_cmd, paths, encrypt_targets, gnupg, bad_phrase, overwrite, missing_encrypt): """Test symmetric encryption""" if missing_encrypt: paths.encrypt.remove() if bad_phrase: - gnupg.pw('') + gnupg.pw("") else: gnupg.pw(PASSPHRASE) if overwrite: - paths.archive.write('existing archive') + paths.archive.write("existing archive") env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home - run = runner(yadm_cmd('encrypt'), env=env) + env["GNUPGHOME"] = gnupg.home + run = runner(yadm_cmd("encrypt"), env=env) if missing_encrypt or bad_phrase: assert run.failure else: assert run.success - assert run.err == '' + assert run.err == "" if missing_encrypt: - assert 'does not exist' in run.err + assert "does not exist" in run.err elif bad_phrase: - assert 'Invalid passphrase' in run.err + assert "Invalid IPC" in run.err else: - assert encrypted_data_valid( - runner, gnupg, paths.archive, encrypt_targets) + assert encrypted_data_valid(runner, gnupg, paths.archive, encrypt_targets) -@pytest.mark.parametrize( - 'bad_phrase', [False, True], - ids=['good_phrase', 'bad_phrase']) -@pytest.mark.parametrize( - 'archive_exists', [True, False], - ids=['archive_exists', 'archive_missing']) -@pytest.mark.parametrize( - 'dolist', [False, True], - ids=['decrypt', 'list']) -def test_symmetric_decrypt( - runner, yadm_cmd, paths, decrypt_targets, gnupg, - dolist, archive_exists, bad_phrase): +@pytest.mark.parametrize("bad_phrase", [False, True], ids=["good_phrase", "bad_phrase"]) +@pytest.mark.parametrize("archive_exists", [True, False], ids=["archive_exists", "archive_missing"]) +@pytest.mark.parametrize("dolist", [False, True], ids=["decrypt", "list"]) +def test_symmetric_decrypt(runner, yadm_cmd, paths, decrypt_targets, gnupg, dolist, archive_exists, bad_phrase): """Test decryption""" # init empty yadm repo - os.system(' '.join(yadm_cmd('init', '-w', str(paths.work), '-f'))) + os.system(" ".join(yadm_cmd("init", "-w", str(paths.work), "-f"))) if bad_phrase: - gnupg.pw('') + gnupg.pw("") time.sleep(1) # allow gpg-agent cache to expire else: gnupg.pw(PASSPHRASE) if archive_exists: - decrypt_targets['symmetric'].copy(paths.archive) + decrypt_targets["symmetric"].copy(paths.archive) # to test overwriting - paths.work.join('decrypt1').write('pre-existing file') + paths.work.join("decrypt1").write("pre-existing file") env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home + env["GNUPGHOME"] = gnupg.home args = [] if dolist: - args.append('-l') - run = runner(yadm_cmd('decrypt') + args, env=env) + args.append("-l") + run = runner(yadm_cmd("decrypt") + args, env=env) if archive_exists and not bad_phrase: assert run.success - assert 'encrypted with 1 passphrase' in run.err + assert "encrypted with 1 passphrase" in run.err if dolist: - for filename in decrypt_targets['expected']: - if filename != 'decrypt1': # this one should exist + for filename in decrypt_targets["expected"]: + if filename != "decrypt1": # this one should exist assert not paths.work.join(filename).exists() assert filename in run.out else: - for filename in decrypt_targets['expected']: + for filename in decrypt_targets["expected"]: assert paths.work.join(filename).read() == filename else: assert run.failure -@pytest.mark.usefixtures('asymmetric_key') -@pytest.mark.parametrize( - 'ask', [False, True], - ids=['no_ask', 'ask']) -@pytest.mark.parametrize( - 'key_exists', [True, False], - ids=['key_exists', 'key_missing']) -@pytest.mark.parametrize( - 'overwrite', [False, True], - ids=['clean', 'overwrite']) -def test_asymmetric_encrypt( - runner, yadm_cmd, paths, encrypt_targets, gnupg, - overwrite, key_exists, ask): +@pytest.mark.usefixtures("asymmetric_key") +@pytest.mark.parametrize("ask", [False, True], ids=["no_ask", "ask"]) +@pytest.mark.parametrize("key_exists", [True, False], ids=["key_exists", "key_missing"]) +@pytest.mark.parametrize("overwrite", [False, True], ids=["clean", "overwrite"]) +def test_asymmetric_encrypt(runner, yadm_cmd, paths, encrypt_targets, gnupg, overwrite, key_exists, ask): """Test asymmetric encryption""" # specify encryption recipient if ask: - os.system(' '.join(yadm_cmd('config', 'yadm.gpg-recipient', 'ASK'))) - expect = [('Enter the user ID', KEY_NAME), ('Enter the user ID', '')] + os.system(" ".join(yadm_cmd("config", "yadm.gpg-recipient", "ASK"))) + expect = [("Enter the user ID", KEY_NAME), ("Enter the user ID", "")] else: - os.system(' '.join(yadm_cmd('config', 'yadm.gpg-recipient', KEY_NAME))) + os.system(" ".join(yadm_cmd("config", "yadm.gpg-recipient", KEY_NAME))) expect = [] if overwrite: - paths.archive.write('existing archive') + paths.archive.write("existing archive") if not key_exists: remove_asymmetric_key(runner, gnupg) env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home + env["GNUPGHOME"] = gnupg.home - run = runner(yadm_cmd('encrypt'), env=env, expect=expect) + run = runner(yadm_cmd("encrypt"), env=env, expect=expect) if key_exists: assert run.success - assert encrypted_data_valid( - runner, gnupg, paths.archive, encrypt_targets) + assert encrypted_data_valid(runner, gnupg, paths.archive, encrypt_targets) else: assert run.failure - assert 'Unable to write' in run.out if expect else run.err + assert "Unable to write" in run.out if expect else run.err if ask: - assert 'Enter the user ID' in run.out + assert "Enter the user ID" in run.out -@pytest.mark.usefixtures('asymmetric_key') -@pytest.mark.usefixtures('encrypt_targets') +@pytest.mark.usefixtures("asymmetric_key") +@pytest.mark.usefixtures("encrypt_targets") def test_multi_key(runner, yadm_cmd, gnupg): """Test multiple recipients""" # specify two encryption recipient - os.system(' '.join(yadm_cmd( - 'config', 'yadm.gpg-recipient', f'"second-key {KEY_NAME}"'))) + os.system(" ".join(yadm_cmd("config", "yadm.gpg-recipient", f'"second-key {KEY_NAME}"'))) env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home + env["GNUPGHOME"] = gnupg.home - run = runner(yadm_cmd('encrypt'), env=env) + run = runner(yadm_cmd("encrypt"), env=env) assert run.failure - assert 'second-key: skipped: No public key' in run.err + assert "second-key: skipped: No public key" in run.err -@pytest.mark.usefixtures('asymmetric_key') -@pytest.mark.parametrize( - 'key_exists', [True, False], - ids=['key_exists', 'key_missing']) -@pytest.mark.parametrize( - 'dolist', [False, True], - ids=['decrypt', 'list']) -def test_asymmetric_decrypt( - runner, yadm_cmd, paths, decrypt_targets, gnupg, - dolist, key_exists): +@pytest.mark.usefixtures("asymmetric_key") +@pytest.mark.parametrize("key_exists", [True, False], ids=["key_exists", "key_missing"]) +@pytest.mark.parametrize("dolist", [False, True], ids=["decrypt", "list"]) +def test_asymmetric_decrypt(runner, yadm_cmd, paths, decrypt_targets, gnupg, dolist, key_exists): """Test decryption""" # init empty yadm repo - os.system(' '.join(yadm_cmd('init', '-w', str(paths.work), '-f'))) + os.system(" ".join(yadm_cmd("init", "-w", str(paths.work), "-f"))) - decrypt_targets['asymmetric'].copy(paths.archive) + decrypt_targets["asymmetric"].copy(paths.archive) # to test overwriting - paths.work.join('decrypt1').write('pre-existing file') + paths.work.join("decrypt1").write("pre-existing file") if not key_exists: remove_asymmetric_key(runner, gnupg) @@ -363,32 +327,28 @@ def test_asymmetric_decrypt( args = [] if dolist: - args.append('-l') + args.append("-l") env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home - run = runner(yadm_cmd('decrypt') + args, env=env) + env["GNUPGHOME"] = gnupg.home + run = runner(yadm_cmd("decrypt") + args, env=env) if key_exists: assert run.success if dolist: - for filename in decrypt_targets['expected']: - if filename != 'decrypt1': # this one should exist + for filename in decrypt_targets["expected"]: + if filename != "decrypt1": # this one should exist assert not paths.work.join(filename).exists() assert filename in run.out else: - for filename in decrypt_targets['expected']: + for filename in decrypt_targets["expected"]: assert paths.work.join(filename).read() == filename else: assert run.failure - assert 'Unable to extract encrypted files' in run.err + assert "Unable to extract encrypted files" in run.err -@pytest.mark.parametrize( - 'untracked', - [False, 'y', 'n'], - ids=['tracked', 'untracked_answer_y', 'untracked_answer_n']) -def test_offer_to_add( - runner, yadm_cmd, paths, encrypt_targets, gnupg, untracked): +@pytest.mark.parametrize("untracked", [False, "y", "n"], ids=["tracked", "untracked_answer_y", "untracked_answer_n"]) +def test_offer_to_add(runner, yadm_cmd, paths, encrypt_targets, gnupg, untracked): """Test offer to add encrypted archive All the other encryption tests use an archive outside of the work tree. @@ -396,85 +356,75 @@ def test_offer_to_add( should be an offer to add it to the repo if it is not tracked. """ - worktree_archive = paths.work.join('worktree-archive.tar.gpg') + worktree_archive = paths.work.join("worktree-archive.tar.gpg") expect = [] gnupg.pw(PASSPHRASE) env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home + env["GNUPGHOME"] = gnupg.home if untracked: - expect.append(('add it now', untracked)) + expect.append(("add it now", untracked)) else: - worktree_archive.write('exists') - os.system(' '.join(yadm_cmd('add', str(worktree_archive)))) + worktree_archive.write("exists") + os.system(" ".join(yadm_cmd("add", str(worktree_archive)))) - run = runner( - yadm_cmd('encrypt', '--yadm-archive', str(worktree_archive)), - env=env, - expect=expect - ) + run = runner(yadm_cmd("encrypt", "--yadm-archive", str(worktree_archive)), env=env, expect=expect) assert run.success - assert run.err == '' - assert encrypted_data_valid( - runner, gnupg, worktree_archive, encrypt_targets) + assert run.err == "" + assert encrypted_data_valid(runner, gnupg, worktree_archive, encrypt_targets) - run = runner( - yadm_cmd('status', '--porcelain', '-uall', str(worktree_archive))) + run = runner(yadm_cmd("status", "--porcelain", "-uall", str(worktree_archive))) assert run.success - assert run.err == '' + assert run.err == "" - if untracked == 'y': + if untracked == "y": # should be added to the index - assert f'A {worktree_archive.basename}' in run.out - elif untracked == 'n': + assert f"A {worktree_archive.basename}" in run.out + elif untracked == "n": # should NOT be added to the index - assert f'?? {worktree_archive.basename}' in run.out + assert f"?? {worktree_archive.basename}" in run.out else: # should appear modified in the index - assert f'AM {worktree_archive.basename}' in run.out + assert f"AM {worktree_archive.basename}" in run.out -@pytest.mark.usefixtures('ds1_copy') +@pytest.mark.usefixtures("ds1_copy") def test_encrypt_added_to_exclude(runner, yadm_cmd, paths, gnupg): """Confirm that .config/yadm/encrypt is added to exclude""" gnupg.pw(PASSPHRASE) env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home + env["GNUPGHOME"] = gnupg.home - exclude_file = paths.repo.join('info/exclude') - paths.encrypt.write('test-encrypt-data\n') - paths.work.join('test-encrypt-data').write('') - exclude_file.write('original-data', ensure=True) + exclude_file = paths.repo.join("info/exclude") + paths.encrypt.write("test-encrypt-data\n") + paths.work.join("test-encrypt-data").write("") + exclude_file.write("original-data", ensure=True) - run = runner(yadm_cmd('encrypt'), env=env) + run = runner(yadm_cmd("encrypt"), env=env) - assert 'test-encrypt-data' in paths.repo.join('info/exclude').read() - assert 'original-data' in paths.repo.join('info/exclude').read() + assert "test-encrypt-data" in paths.repo.join("info/exclude").read() + assert "original-data" in paths.repo.join("info/exclude").read() assert run.success - assert run.err == '' + assert run.err == "" def encrypted_data_valid(runner, gnupg, encrypted, expected): """Verify encrypted data matches expectations""" gnupg.pw(PASSPHRASE) env = os.environ.copy() - env['GNUPGHOME'] = gnupg.home - run = runner([ - 'gpg', - '-d', pipes.quote(str(encrypted)), - '2>/dev/null', - '|', 'tar', 't'], env=env, shell=True, report=False) + env["GNUPGHOME"] = gnupg.home + run = runner( + ["gpg", "-d", shlex.quote(str(encrypted)), "2>/dev/null", "|", "tar", "t"], env=env, shell=True, report=False + ) file_count = 0 for filename in run.out.splitlines(): - if filename.endswith('/'): + if filename.endswith("/"): continue file_count += 1 - assert filename in expected, ( - f'Unexpected file in archive: {filename}') - assert file_count == len(expected), ( - 'Number of files in archive does not match expected') + assert filename in expected, f"Unexpected file in archive: {filename}" + assert file_count == len(expected), "Number of files in archive does not match expected" return True diff --git a/test/test_enter.py b/test/test_enter.py index f5ea2d8..b0626ae 100644 --- a/test/test_enter.py +++ b/test/test_enter.py @@ -1,110 +1,117 @@ """Test enter""" import os + import pytest @pytest.mark.parametrize( - 'shell, success', [ - ('delete', True), # if there is no shell variable, bash creates it - ('', False), - ('/usr/bin/env', True), - ('noexec', False), - ], ids=[ - 'shell-missing', - 'shell-empty', - 'shell-env', - 'shell-noexec', - ]) -@pytest.mark.usefixtures('ds1_copy') + "shell, success", + [ + ("delete", True), # if there is no shell variable, bash creates it + ("", False), + ("/usr/bin/env", True), + ("noexec", False), + ], + ids=[ + "shell-missing", + "shell-empty", + "shell-env", + "shell-noexec", + ], +) +@pytest.mark.usefixtures("ds1_copy") def test_enter(runner, yadm_cmd, paths, shell, success): """Enter tests""" env = os.environ.copy() - if shell == 'delete': + if shell == "delete": # remove shell - if 'SHELL' in env: - del env['SHELL'] - elif shell == 'noexec': + if "SHELL" in env: + del env["SHELL"] + elif shell == "noexec": # specify a non-executable path - noexec = paths.root.join('noexec') - noexec.write('') + noexec = paths.root.join("noexec") + noexec.write("") noexec.chmod(0o664) - env['SHELL'] = str(noexec) + env["SHELL"] = str(noexec) else: - env['SHELL'] = shell + env["SHELL"] = shell - run = runner(command=yadm_cmd('enter'), env=env) + run = runner(command=yadm_cmd("enter"), env=env) assert run.success == success - prompt = f'yadm shell ({paths.repo})' + prompt = f"yadm shell ({paths.repo})" if success: - assert run.out.startswith('Entering yadm repo') - assert run.out.rstrip().endswith('Leaving yadm repo') - assert run.err == '' + assert run.out.startswith("Entering yadm repo") + assert run.out.rstrip().endswith("Leaving yadm repo") + assert run.err == "" else: - assert 'does not refer to an executable' in run.err - if 'env' in shell: - assert f'GIT_DIR={paths.repo}' in run.out - assert f'GIT_WORK_TREE={paths.work}' in run.out - assert f'PROMPT={prompt}' in run.out - assert f'PS1={prompt}' in run.out + assert "does not refer to an executable" in run.err + if "env" in shell: + assert f"GIT_DIR={paths.repo}" in run.out + assert f"GIT_WORK_TREE={paths.work}" in run.out + assert f"PROMPT={prompt}" in run.out + assert f"PS1={prompt}" in run.out @pytest.mark.parametrize( - 'shell, opts, path', [ - ('bash', '--norc', '\\w'), - ('csh', '-f', '%~'), - ('zsh', '-f', '%~'), - ], ids=[ - 'bash', - 'csh', - 'zsh', - ]) + "shell, opts, path", + [ + ("bash", "--norc", "\\w"), + ("csh", "-f", "%~"), + ("zsh", "-f", "%~"), + ], + ids=[ + "bash", + "csh", + "zsh", + ], +) @pytest.mark.parametrize( - 'cmd', - [False, 'cmd', 'cmd-bad-exit'], - ids=['no-cmd', 'cmd', 'cmd-bad-exit']) + "cmd", + [False, "cmd", "cmd-bad-exit"], + ids=["no-cmd", "cmd", "cmd-bad-exit"], +) @pytest.mark.parametrize( - 'term', ['', 'dumb'], - ids=['term-empty', 'term-dumb']) -@pytest.mark.usefixtures('ds1_copy') -def test_enter_shell_ops(runner, yadm_cmd, paths, shell, - opts, path, cmd, term): + "term", + ["", "dumb"], + ids=["term-empty", "term-dumb"], +) +@pytest.mark.usefixtures("ds1_copy") +def test_enter_shell_ops(runner, yadm_cmd, paths, shell, opts, path, cmd, term): """Enter tests for specific shell options""" - change_exit = '\nfalse' if cmd == 'cmd-bad-exit' else '' + change_exit = "\nfalse" if cmd == "cmd-bad-exit" else "" # Create custom shell to detect options passed custom_shell = paths.root.join(shell) - custom_shell.write( - f'#!/bin/sh\necho OPTS=$*\necho PROMPT=$PROMPT{change_exit}' - ) + custom_shell.write(f"#!/bin/sh\necho OPTS=$*\necho PROMPT=$PROMPT{change_exit}") custom_shell.chmod(0o775) - test_cmd = ['test1', 'test2', 'test3'] + test_cmd = ["test1", "test2", "test3"] - enter_cmd = ['enter'] + enter_cmd = ["enter"] if cmd: enter_cmd += test_cmd env = os.environ.copy() - env['TERM'] = term - env['SHELL'] = custom_shell + env["TERM"] = term + env["SHELL"] = custom_shell - if shell == 'zsh' and term == 'dumb': - opts += ' --no-zle' + if shell == "zsh" and term == "dumb": + opts += " --no-zle" run = runner(command=yadm_cmd(*enter_cmd), env=env) - if cmd == 'cmd-bad-exit': + if cmd == "cmd-bad-exit": assert run.failure else: assert run.success - assert run.err == '' - assert f'OPTS={opts}' in run.out - assert f'PROMPT=yadm shell ({paths.repo}) {path} >' in run.out + assert run.err == "" + assert f"OPTS={opts}" in run.out + assert f"PROMPT=yadm shell ({paths.repo}) {path} >" in run.out if cmd: - assert '-c ' + ' '.join(test_cmd) in run.out - assert 'Entering yadm repo' not in run.out - assert 'Leaving yadm repo' not in run.out + assert "-c " + " ".join(test_cmd) in run.out + assert "Entering yadm repo" not in run.out + assert "Leaving yadm repo" not in run.out else: - assert 'Entering yadm repo' in run.out - assert 'Leaving yadm repo' in run.out + assert "Entering yadm repo" in run.out + assert "Leaving yadm repo" in run.out diff --git a/test/test_ext_crypt.py b/test/test_ext_crypt.py index cb74afc..92f703f 100644 --- a/test/test_ext_crypt.py +++ b/test/test_ext_crypt.py @@ -4,28 +4,30 @@ import pytest @pytest.mark.parametrize( - 'crypt', - [False, 'installed', 'installed-but-failed'], - ids=['not-installed', 'installed', 'installed-but-failed'] + "crypt", + [False, "installed", "installed-but-failed"], + ids=["not-installed", "installed", "installed-but-failed"], ) @pytest.mark.parametrize( - 'cmd,var', [ - ['git_crypt', 'GIT_CRYPT_PROGRAM'], - ['transcrypt', 'TRANSCRYPT_PROGRAM'], - ], - ids=['git-crypt', 'transcrypt']) + "cmd,var", + [ + ["git_crypt", "GIT_CRYPT_PROGRAM"], + ["transcrypt", "TRANSCRYPT_PROGRAM"], + ], + ids=["git-crypt", "transcrypt"], +) def test_ext_encryption(runner, yadm, paths, tmpdir, crypt, cmd, var): """External encryption tests""" paths.repo.ensure(dir=True) - bindir = tmpdir.mkdir('bin') - pgm = bindir.join('test-ext-crypt') + bindir = tmpdir.mkdir("bin") + pgm = bindir.join("test-ext-crypt") if crypt: - pgm.write('#!/bin/sh\necho ext-crypt ran\n') + pgm.write("#!/bin/sh\necho ext-crypt ran\n") pgm.chmod(0o775) - if crypt == 'installed-but-failed': - pgm.write('false\n', mode='a') + if crypt == "installed-but-failed": + pgm.write("false\n", mode="a") script = f""" YADM_TEST=1 source {yadm} @@ -34,15 +36,15 @@ def test_ext_encryption(runner, yadm, paths, tmpdir, crypt, cmd, var): {cmd} "param1" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) if crypt: - if crypt == 'installed-but-failed': + if crypt == "installed-but-failed": assert run.failure else: assert run.success - assert run.out.strip() == 'ext-crypt ran' - assert run.err == '' + assert run.out.strip() == "ext-crypt ran" + assert run.err == "" else: assert run.failure assert f"command '{pgm}' cannot be located" in run.err diff --git a/test/test_git.py b/test/test_git.py index 76eccab..dea538d 100644 --- a/test/test_git.py +++ b/test/test_git.py @@ -1,10 +1,11 @@ """Test git""" import re + import pytest -@pytest.mark.usefixtures('ds1_copy') +@pytest.mark.usefixtures("ds1_copy") def test_git(runner, yadm_cmd, paths): """Test series of passthrough git commands @@ -17,42 +18,42 @@ def test_git(runner, yadm_cmd, paths): """ # passthru unknown commands to Git - run = runner(command=yadm_cmd('bogus')) + run = runner(command=yadm_cmd("bogus")) assert run.failure assert "git: 'bogus' is not a git command." in run.err assert "See 'git --help'" in run.err - assert run.out == '' + assert run.out == "" # git command 'add' - badfile - run = runner(command=yadm_cmd('add', '-v', 'does_not_exist')) + run = runner(command=yadm_cmd("add", "-v", "does_not_exist")) assert run.code == 128 assert "pathspec 'does_not_exist' did not match any files" in run.err - assert run.out == '' + assert run.out == "" # git command 'add' - newfile = paths.work.join('test_git') - newfile.write('test_git') - run = runner(command=yadm_cmd('add', '-v', str(newfile))) + newfile = paths.work.join("test_git") + newfile.write("test_git") + run = runner(command=yadm_cmd("add", "-v", str(newfile))) assert run.success - assert run.err == '' + assert run.err == "" assert "add 'test_git'" in run.out # git command 'status' - run = runner(command=yadm_cmd('status')) + run = runner(command=yadm_cmd("status")) assert run.success - assert run.err == '' - assert re.search(r'new file:\s+test_git', run.out) + assert run.err == "" + assert re.search(r"new file:\s+test_git", run.out) # git command 'commit' - run = runner(command=yadm_cmd('commit', '-m', 'Add test_git')) + run = runner(command=yadm_cmd("commit", "-m", "Add test_git")) assert run.success - assert run.err == '' - assert '1 file changed' in run.out - assert '1 insertion' in run.out - assert re.search(r'create mode .+ test_git', run.out) + assert run.err == "" + assert "1 file changed" in run.out + assert "1 insertion" in run.out + assert re.search(r"create mode .+ test_git", run.out) # git command 'log' - run = runner(command=yadm_cmd('log', '--oneline')) + run = runner(command=yadm_cmd("log", "--oneline")) assert run.success - assert run.err == '' - assert 'Add test_git' in run.out + assert run.err == "" + assert "Add test_git" in run.out diff --git a/test/test_help.py b/test/test_help.py index 0d8f2c3..cbfabcc 100644 --- a/test/test_help.py +++ b/test/test_help.py @@ -6,14 +6,14 @@ def test_missing_command(runner, yadm_cmd): """Run without any command""" run = runner(command=yadm_cmd()) assert run.failure - assert run.err == '' - assert run.out.startswith('Usage: yadm') + assert run.err == "" + assert run.out.startswith("Usage: yadm") -@pytest.mark.parametrize('cmd', ['--help', 'help']) +@pytest.mark.parametrize("cmd", ["--help", "help"]) def test_help_command(runner, yadm_cmd, cmd): """Run with help command""" run = runner(command=yadm_cmd(cmd)) assert run.failure - assert run.err == '' - assert run.out.startswith('Usage: yadm') + assert run.err == "" + assert run.out.startswith("Usage: yadm") diff --git a/test/test_hooks.py b/test/test_hooks.py index 704636a..aff3daf 100644 --- a/test/test_hooks.py +++ b/test/test_hooks.py @@ -4,7 +4,8 @@ import pytest @pytest.mark.parametrize( - 'pre, pre_code, post, post_code', [ + "pre, pre_code, post, post_code", + [ (False, 0, False, 0), (True, 0, False, 0), (True, 5, False, 0), @@ -12,83 +13,83 @@ import pytest (False, 0, True, 5), (True, 0, True, 0), (True, 5, True, 5), - ], ids=[ - 'no-hooks', - 'pre-success', - 'pre-fail', - 'post-success', - 'post-fail', - 'pre-post-success', - 'pre-post-fail', - ]) -@pytest.mark.parametrize('cmd', ['--version', 'version']) -def test_hooks( - runner, yadm_cmd, paths, cmd, - pre, pre_code, post, post_code): + ], + ids=[ + "no-hooks", + "pre-success", + "pre-fail", + "post-success", + "post-fail", + "pre-post-success", + "pre-post-fail", + ], +) +@pytest.mark.parametrize("cmd", ["--version", "version"]) +def test_hooks(runner, yadm_cmd, paths, cmd, pre, pre_code, post, post_code): """Test pre/post hook""" # generate hooks if pre: - create_hook(paths, 'pre_version', pre_code) + create_hook(paths, "pre_version", pre_code) if post: - create_hook(paths, 'post_version', post_code) + create_hook(paths, "post_version", post_code) # run yadm run = runner(yadm_cmd(cmd)) # when a pre hook fails, yadm should exit with the hook's code assert run.code == pre_code - assert run.err == '' + assert run.err == "" if pre: - assert 'HOOK:pre_version' in run.out + assert "HOOK:pre_version" in run.out # if pre hook is missing or successful, yadm itself should exit 0 if run.success: if post: - assert 'HOOK:post_version' in run.out + assert "HOOK:post_version" in run.out else: # when a pre hook fails, yadm should not run the command - assert 'version will not be run' in run.out + assert "version will not be run" in run.out # when a pre hook fails, yadm should not run the post hook - assert 'HOOK:post_version' not in run.out + assert "HOOK:post_version" not in run.out # repo fixture is needed to test the population of YADM_HOOK_WORK -@pytest.mark.usefixtures('ds1_repo_copy') +@pytest.mark.usefixtures("ds1_repo_copy") def test_hook_env(runner, yadm_cmd, paths): """Test hook environment""" # test will be done with a non existent "git" passthru command # which should exit with a failing code - cmd = 'passthrucmd' + cmd = "passthrucmd" # write the hook - hook = paths.hooks.join(f'post_{cmd}') - hook.write('#!/bin/bash\nenv\ndeclare\n') + hook = paths.hooks.join(f"post_{cmd}") + hook.write("#!/bin/bash\nenv\ndeclare\n") hook.chmod(0o755) - run = runner(yadm_cmd(cmd, 'extra_args')) + run = runner(yadm_cmd(cmd, "extra_args")) # expect passthru to fail assert run.failure assert f"'{cmd}' is not a git command" in run.err # verify hook environment - assert 'YADM_HOOK_EXIT=1\n' in run.out - assert f'YADM_HOOK_COMMAND={cmd}\n' in run.out - assert f'YADM_HOOK_DIR={paths.yadm}\n' in run.out - assert f'YADM_HOOK_FULL_COMMAND={cmd} extra_args\n' in run.out - assert f'YADM_HOOK_REPO={paths.repo}\n' in run.out - assert f'YADM_HOOK_WORK={paths.work}\n' in run.out - assert 'YADM_ENCRYPT_INCLUDE_FILES=\n' in run.out + assert "YADM_HOOK_EXIT=1\n" in run.out + assert f"YADM_HOOK_COMMAND={cmd}\n" in run.out + assert f"YADM_HOOK_DIR={paths.yadm}\n" in run.out + assert f"YADM_HOOK_FULL_COMMAND={cmd} extra_args\n" in run.out + assert f"YADM_HOOK_REPO={paths.repo}\n" in run.out + assert f"YADM_HOOK_WORK={paths.work}\n" in run.out + assert "YADM_ENCRYPT_INCLUDE_FILES=\n" in run.out # verify the hook environment contains certain exported functions for func in [ - 'builtin_dirname', - 'relative_path', - 'unix_path', - 'mixed_path', + "builtin_dirname", + "relative_path", + "unix_path", + "mixed_path", ]: - assert f'BASH_FUNC_{func}' in run.out + assert f"BASH_FUNC_{func}" in run.out # verify the hook environment contains the list of encrypted files script = f""" @@ -98,10 +99,10 @@ def test_hook_env(runner, yadm_cmd, paths): ENCRYPT_INCLUDE_FILES=(a b c) invoke_hook "post" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert 'YADM_ENCRYPT_INCLUDE_FILES=a\nb\nc\n' in run.out + assert run.err == "" + assert "YADM_ENCRYPT_INCLUDE_FILES=a\nb\nc\n" in run.out def test_escaped(runner, yadm_cmd, paths): @@ -109,35 +110,33 @@ def test_escaped(runner, yadm_cmd, paths): # test will be done with a non existent "git" passthru command # which should exit with a failing code - cmd = 'passthrucmd' + cmd = "passthrucmd" # write the hook - hook = paths.hooks.join(f'post_{cmd}') - hook.write('#!/bin/bash\nenv\n') + hook = paths.hooks.join(f"post_{cmd}") + hook.write("#!/bin/bash\nenv\n") hook.chmod(0o755) - run = runner(yadm_cmd(cmd, 'a b', 'c\td', 'e\\f')) + run = runner(yadm_cmd(cmd, "a b", "c\td", "e\\f")) # expect passthru to fail assert run.failure # verify escaped values - assert ( - f'YADM_HOOK_FULL_COMMAND={cmd} ' - 'a\\ b c\\\td e\\\\f\n') in run.out + assert f"YADM_HOOK_FULL_COMMAND={cmd} a\\ b c\\\td e\\\\f\n" in run.out -@pytest.mark.parametrize('condition', ['exec', 'no-exec', 'mingw']) +@pytest.mark.parametrize("condition", ["exec", "no-exec", "mingw"]) def test_executable(runner, paths, condition): """Verify hook must be exectuable""" - cmd = 'version' - hook = paths.hooks.join(f'pre_{cmd}') - hook.write('#!/bin/sh\necho HOOK\n') + cmd = "version" + hook = paths.hooks.join(f"pre_{cmd}") + hook.write("#!/bin/sh\necho HOOK\n") hook.chmod(0o644) - if condition == 'exec': + if condition == "exec": hook.chmod(0o755) - mingw = 'OPERATING_SYSTEM="MINGWx"' if condition == 'mingw' else '' + mingw = 'OPERATING_SYSTEM="MINGWx"' if condition == "mingw" else "" script = f""" YADM_TEST=1 source {paths.pgm} YADM_HOOKS="{paths.hooks}" @@ -145,27 +144,23 @@ def test_executable(runner, paths, condition): {mingw} invoke_hook "pre" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) - if condition != 'mingw': + if condition != "mingw": assert run.success - assert run.err == '' + assert run.err == "" else: assert run.failure - assert 'Permission denied' in run.err + assert "Permission denied" in run.err - if condition == 'exec': - assert 'HOOK' in run.out - elif condition == 'no-exec': - assert 'HOOK' not in run.out + if condition == "exec": + assert "HOOK" in run.out + elif condition == "no-exec": + assert "HOOK" not in run.out def create_hook(paths, name, code): """Create hook""" hook = paths.hooks.join(name) - hook.write( - '#!/bin/sh\n' - f'echo HOOK:{name}\n' - f'exit {code}\n' - ) + hook.write("#!/bin/sh\n" f"echo HOOK:{name}\n" f"exit {code}\n") hook.chmod(0o755) diff --git a/test/test_init.py b/test/test_init.py index 5542332..a8ba493 100644 --- a/test/test_init.py +++ b/test/test_init.py @@ -4,22 +4,24 @@ import pytest @pytest.mark.parametrize( - 'alt_work, repo_present, force', [ + "alt_work, repo_present, force", + [ (False, False, False), (True, False, False), (False, True, False), (False, True, True), (True, True, True), - ], ids=[ - 'simple', - '-w', - 'existing repo', - '-f', - '-w & -f', - ]) -@pytest.mark.usefixtures('ds1_work_copy') -def test_init( - runner, yadm_cmd, paths, repo_config, alt_work, repo_present, force): + ], + ids=[ + "simple", + "-w", + "existing repo", + "-f", + "-w & -f", + ], +) +@pytest.mark.usefixtures("ds1_work_copy") +def test_init(runner, yadm_cmd, paths, repo_config, alt_work, repo_present, force): """Test init Repos should have attribs: @@ -31,54 +33,54 @@ def test_init( """ # these tests will assume this for $HOME - home = str(paths.root.mkdir('HOME')) + home = str(paths.root.mkdir("HOME")) # ds1_work_copy comes WITH an empty repo dir present. - old_repo = paths.repo.join('old_repo') + old_repo = paths.repo.join("old_repo") if repo_present: # Let's put some data in it, so we can confirm that data is gone when # forced to be overwritten. - old_repo.write('old repo data') + old_repo.write("old repo data") assert old_repo.isfile() else: paths.repo.remove() # command args - args = ['init'] + args = ["init"] cwd = None if alt_work: if force: cwd = paths.work.dirname - args.extend(['-w', paths.work.basename]) + args.extend(["-w", paths.work.basename]) else: - args.extend(['-w', paths.work]) + args.extend(["-w", paths.work]) if force: - args.append('-f') + args.append("-f") # run init - run = runner(yadm_cmd(*args), env={'HOME': home}, cwd=cwd) + runner(["git", "config", "--global", "init.defaultBranch", "master"], env={"HOME": home}, cwd=cwd) + run = runner(yadm_cmd(*args), env={"HOME": home}, cwd=cwd) if repo_present and not force: assert run.failure - assert 'repo already exists' in run.err - assert old_repo.isfile(), 'Missing original repo' + assert "repo already exists" in run.err + assert old_repo.isfile(), "Missing original repo" else: assert run.success - assert 'Initialized empty shared Git repository' in run.out + assert "Initialized empty shared Git repository" in run.out if repo_present: - assert not old_repo.isfile(), 'Original repo still exists' + assert not old_repo.isfile(), "Original repo still exists" else: - assert run.err == '' + assert run.err == "" if alt_work: - assert repo_config('core.worktree') == paths.work + assert repo_config("core.worktree") == paths.work else: - assert repo_config('core.worktree') == home + assert repo_config("core.worktree") == home # uniform repo assertions - assert oct(paths.repo.stat().mode).endswith('00'), ( - 'Repo is not secure') - assert repo_config('core.bare') == 'false' - assert repo_config('status.showUntrackedFiles') == 'no' - assert repo_config('yadm.managed') == 'true' + assert oct(paths.repo.stat().mode).endswith("00"), "Repo is not secure" + assert repo_config("core.bare") == "false" + assert repo_config("status.showUntrackedFiles") == "no" + assert repo_config("yadm.managed") == "true" diff --git a/test/test_introspect.py b/test/test_introspect.py index b292bd4..3a4fb46 100644 --- a/test/test_introspect.py +++ b/test/test_introspect.py @@ -4,38 +4,38 @@ import pytest @pytest.mark.parametrize( - 'name', [ - '', - 'invalid', - 'commands', - 'configs', - 'repo', - 'switches', - ]) -def test_introspect_category( - runner, yadm_cmd, paths, name, - supported_commands, supported_configs, supported_switches): + "name", + [ + "", + "invalid", + "commands", + "configs", + "repo", + "switches", + ], +) +def test_introspect_category(runner, yadm_cmd, paths, name, supported_commands, supported_configs, supported_switches): """Validate introspection category""" if name: - run = runner(command=yadm_cmd('introspect', name)) + run = runner(command=yadm_cmd("introspect", name)) else: - run = runner(command=yadm_cmd('introspect')) + run = runner(command=yadm_cmd("introspect")) assert run.success - assert run.err == '' + assert run.err == "" expected = [] - if name == 'commands': + if name == "commands": expected = supported_commands - elif name == 'configs': + elif name == "configs": expected = supported_configs - elif name == 'switches': + elif name == "switches": expected = supported_switches # assert values - if name in ('', 'invalid'): - assert run.out == '' - if name == 'repo': + if name in ("", "invalid"): + assert run.out == "" + if name == "repo": assert run.out.rstrip() == paths.repo # make sure every expected value is present diff --git a/test/test_list.py b/test/test_list.py index dcfe500..afcea6f 100644 --- a/test/test_list.py +++ b/test/test_list.py @@ -1,31 +1,34 @@ """Test list""" import os + import pytest @pytest.mark.parametrize( - 'location', [ - 'work', - 'outside', - 'subdir', - ]) -@pytest.mark.usefixtures('ds1_copy') + "location", + [ + "work", + "outside", + "subdir", + ], +) +@pytest.mark.usefixtures("ds1_copy") def test_list(runner, yadm_cmd, paths, ds1, location): """List tests""" - if location == 'work': + if location == "work": run_dir = paths.work - elif location == 'outside': - run_dir = paths.work.join('..') - elif location == 'subdir': + elif location == "outside": + run_dir = paths.work.join("..") + elif location == "subdir": # first directory with tracked data run_dir = paths.work.join(ds1.tracked_dirs[0]) with run_dir.as_cwd(): # test with '-a' # should get all tracked files, relative to the work path - run = runner(command=yadm_cmd('list', '-a')) + run = runner(command=yadm_cmd("list", "-a")) assert run.success - assert run.err == '' + assert run.err == "" returned_files = set(run.out.splitlines()) expected_files = {e.path for e in ds1 if e.tracked} assert returned_files == expected_files @@ -33,16 +36,14 @@ def test_list(runner, yadm_cmd, paths, ds1, location): # should get all tracked files, relative to the work path unless in a # subdir, then those should be a limited set of files, relative to the # subdir - run = runner(command=yadm_cmd('list')) + run = runner(command=yadm_cmd("list")) assert run.success - assert run.err == '' + assert run.err == "" returned_files = set(run.out.splitlines()) - if location == 'subdir': + if location == "subdir": basepath = os.path.basename(os.getcwd()) # only expect files within the subdir # names should be relative to subdir - expected_files = { - e.path[len(basepath)+1:] - for e in ds1 if e.tracked and e.path.startswith(basepath) - } + index = len(basepath) + 1 + expected_files = {e.path[index:] for e in ds1 if e.tracked and e.path.startswith(basepath)} assert returned_files == expected_files diff --git a/test/test_perms.py b/test/test_perms.py index 4f052bd..1491c16 100644 --- a/test/test_perms.py +++ b/test/test_perms.py @@ -1,21 +1,21 @@ """Test perms""" import os + import pytest -@pytest.mark.parametrize('autoperms', ['notest', 'unset', 'true', 'false']) -@pytest.mark.usefixtures('ds1_copy') +@pytest.mark.parametrize("autoperms", ["notest", "unset", "true", "false"]) +@pytest.mark.usefixtures("ds1_copy") def test_perms(runner, yadm_cmd, paths, ds1, autoperms): """Test perms""" # set the value of auto-perms - if autoperms != 'notest': - if autoperms != 'unset': - os.system(' '.join( - yadm_cmd('config', 'yadm.auto-perms', autoperms))) + if autoperms != "notest": + if autoperms != "unset": + os.system(" ".join(yadm_cmd("config", "yadm.auto-perms", autoperms))) # privatepaths will hold all paths that should become secured - privatepaths = [paths.work.join('.ssh'), paths.work.join('.gnupg')] + privatepaths = [paths.work.join(".ssh"), paths.work.join(".gnupg")] privatepaths += [paths.work.join(private.path) for private in ds1.private] # create an archive file @@ -23,82 +23,71 @@ def test_perms(runner, yadm_cmd, paths, ds1, autoperms): privatepaths.append(paths.archive) # create encrypted file test data - efile1 = paths.work.join('efile1') - efile1.write('efile1') - efile2 = paths.work.join('efile2') - efile2.write('efile2') - paths.encrypt.write('efile1\nefile2\n!efile1\n') + efile1 = paths.work.join("efile1") + efile1.write("efile1") + efile2 = paths.work.join("efile2") + efile2.write("efile2") + paths.encrypt.write("efile1\nefile2\n!efile1\n") insecurepaths = [efile1] privatepaths.append(efile2) # assert these paths begin unsecured for private in privatepaths + insecurepaths: - assert not oct(private.stat().mode).endswith('00'), ( - 'Path started secured') + assert not oct(private.stat().mode).endswith("00"), "Path started secured" - cmd = 'perms' - if autoperms != 'notest': - cmd = 'status' - run = runner(yadm_cmd(cmd), env={'HOME': paths.work}) + cmd = "perms" + if autoperms != "notest": + cmd = "status" + run = runner(yadm_cmd(cmd), env={"HOME": paths.work}) assert run.success - assert run.err == '' - if cmd == 'perms': - assert run.out == '' + assert run.err == "" + if cmd == "perms": + assert run.out == "" # these paths should be secured if processing perms for private in privatepaths: - if autoperms == 'false': - assert not oct(private.stat().mode).endswith('00'), ( - 'Path should not be secured') + if autoperms == "false": + assert not oct(private.stat().mode).endswith("00"), "Path should not be secured" else: - assert oct(private.stat().mode).endswith('00'), ( - 'Path has not been secured') + assert oct(private.stat().mode).endswith("00"), "Path has not been secured" # these paths should never be secured for private in insecurepaths: - assert not oct(private.stat().mode).endswith('00'), ( - 'Path should not be secured') + assert not oct(private.stat().mode).endswith("00"), "Path should not be secured" -@pytest.mark.parametrize('sshperms', [None, 'true', 'false']) -@pytest.mark.parametrize('gpgperms', [None, 'true', 'false']) -@pytest.mark.usefixtures('ds1_copy') +@pytest.mark.parametrize("sshperms", [None, "true", "false"]) +@pytest.mark.parametrize("gpgperms", [None, "true", "false"]) +@pytest.mark.usefixtures("ds1_copy") def test_perms_control(runner, yadm_cmd, paths, ds1, sshperms, gpgperms): """Test fine control of perms""" # set the value of ssh-perms if sshperms: - os.system(' '.join(yadm_cmd('config', 'yadm.ssh-perms', sshperms))) + os.system(" ".join(yadm_cmd("config", "yadm.ssh-perms", sshperms))) # set the value of gpg-perms if gpgperms: - os.system(' '.join(yadm_cmd('config', 'yadm.gpg-perms', gpgperms))) + os.system(" ".join(yadm_cmd("config", "yadm.gpg-perms", gpgperms))) # privatepaths will hold all paths that should become secured - privatepaths = [paths.work.join('.ssh'), paths.work.join('.gnupg')] + privatepaths = [paths.work.join(".ssh"), paths.work.join(".gnupg")] privatepaths += [paths.work.join(private.path) for private in ds1.private] # assert these paths begin unsecured for private in privatepaths: - assert not oct(private.stat().mode).endswith('00'), ( - 'Path started secured') + assert not oct(private.stat().mode).endswith("00"), "Path started secured" - run = runner(yadm_cmd('perms'), env={'HOME': paths.work}) + run = runner(yadm_cmd("perms"), env={"HOME": paths.work}) assert run.success - assert run.err == '' - assert run.out == '' + assert run.err == "" + assert run.out == "" # these paths should be secured if processing perms for private in privatepaths: - if ( - (sshperms == 'false' and 'ssh' in str(private)) - or - (gpgperms == 'false' and 'gnupg' in str(private)) - ): - assert not oct(private.stat().mode).endswith('00'), ( - 'Path should not be secured') + if (sshperms == "false" and "ssh" in str(private)) or (gpgperms == "false" and "gnupg" in str(private)): + assert not oct(private.stat().mode).endswith("00"), "Path should not be secured" else: - assert oct(private.stat().mode).endswith('00'), ( - 'Path has not been secured') + assert oct(private.stat().mode).endswith("00"), "Path has not been secured" # verify permissions aren't changed for the worktree - assert oct(paths.work.stat().mode).endswith('0755') + assert oct(paths.work.stat().mode).endswith("0755") diff --git a/test/test_syntax.py b/test/test_syntax.py index 72f4ccc..6549822 100644 --- a/test/test_syntax.py +++ b/test/test_syntax.py @@ -1,65 +1,83 @@ """Syntax checks""" import os + import pytest def test_yadm_syntax(runner, yadm): """Is syntactically valid""" - run = runner(command=['bash', '-n', yadm]) + run = runner(command=["bash", "-n", yadm]) assert run.success def test_shellcheck(pytestconfig, runner, yadm, shellcheck_version): """Passes shellcheck""" if not pytestconfig.getoption("--force-linters"): - run = runner(command=['shellcheck', '-V'], report=False) - if f'version: {shellcheck_version}' not in run.out: - pytest.skip('Unsupported shellcheck version') - run = runner(command=['shellcheck', '-s', 'bash', yadm]) + run = runner(command=["shellcheck", "-V"], report=False) + if f"version: {shellcheck_version}" not in run.out: + pytest.skip("Unsupported shellcheck version") + run = runner(command=["shellcheck", "-s", "bash", yadm]) assert run.success def test_pylint(pytestconfig, runner, pylint_version): """Passes pylint""" if not pytestconfig.getoption("--force-linters"): - run = runner(command=['pylint', '--version'], report=False) - if f'pylint {pylint_version}' not in run.out: - pytest.skip('Unsupported pylint version') - pyfiles = list() - for tfile in os.listdir('test'): - if tfile.endswith('.py'): - pyfiles.append(f'test/{tfile}') - run = runner(command=['pylint'] + pyfiles) + run = runner(command=["pylint", "--version"], report=False) + if f"pylint {pylint_version}" not in run.out: + pytest.skip("Unsupported pylint version") + pyfiles = [] + for tfile in os.listdir("test"): + if tfile.endswith(".py"): + pyfiles.append(f"test/{tfile}") + run = runner(command=["pylint"] + pyfiles) + assert run.success + + +def test_isort(pytestconfig, runner, isort_version): + """Passes isort""" + if not pytestconfig.getoption("--force-linters"): + run = runner(command=["isort", "--version"], report=False) + if isort_version not in run.out: + pytest.skip("Unsupported isort version") + run = runner(command=["isort", "-c", "test"]) assert run.success def test_flake8(pytestconfig, runner, flake8_version): """Passes flake8""" if not pytestconfig.getoption("--force-linters"): - run = runner(command=['flake8', '--version'], report=False) + run = runner(command=["flake8", "--version"], report=False) if not run.out.startswith(flake8_version): - pytest.skip('Unsupported flake8 version') - run = runner(command=['flake8', 'test']) + pytest.skip("Unsupported flake8 version") + run = runner(command=["flake8", "test"]) + assert run.success + + +def test_black(pytestconfig, runner, black_version): + """Passes black""" + if not pytestconfig.getoption("--force-linters"): + run = runner(command=["black", "--version"], report=False) + if black_version not in run.out: + pytest.skip("Unsupported black version") + run = runner(command=["black", "--check", "test"]) assert run.success def test_yamllint(pytestconfig, runner, yamllint_version): """Passes yamllint""" if not pytestconfig.getoption("--force-linters"): - run = runner(command=['yamllint', '--version'], report=False) + run = runner(command=["yamllint", "--version"], report=False) if not run.out.strip().endswith(yamllint_version): - pytest.skip('Unsupported yamllint version') - run = runner( - command=['yamllint', '-s', '$(find . -name \\*.yml)'], - shell=True) + pytest.skip("Unsupported yamllint version") + run = runner(command=["yamllint", "-s", "$(find . -name \\*.yml)"], shell=True) assert run.success def test_man(runner): """Check for warnings from man""" - run = runner( - command=['man', '--warnings', './yadm.1']) + run = runner(command=["man.REAL", "--warnings", "./yadm.1"]) assert run.success - assert run.err == '' - assert 'yadm - Yet Another Dotfiles Manager' in run.out + assert run.err == "" + assert "yadm - Yet Another Dotfiles Manager" in run.out diff --git a/test/test_unit_bootstrap_available.py b/test/test_unit_bootstrap_available.py index f37ac08..6ab8df8 100644 --- a/test/test_unit_bootstrap_available.py +++ b/test/test_unit_bootstrap_available.py @@ -8,14 +8,14 @@ def test_bootstrap_missing(runner, paths): def test_bootstrap_no_exec(runner, paths): """Test result of bootstrap_available, when bootstrap not executable""" - paths.bootstrap.write('') + paths.bootstrap.write("") paths.bootstrap.chmod(0o644) run_test(runner, paths, False) def test_bootstrap_exec(runner, paths): """Test result of bootstrap_available, when bootstrap executable""" - paths.bootstrap.write('') + paths.bootstrap.write("") paths.bootstrap.chmod(0o775) run_test(runner, paths, True) @@ -27,7 +27,7 @@ def run_test(runner, paths, success): YADM_BOOTSTRAP='{paths.bootstrap}' bootstrap_available """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success == success - assert run.err == '' - assert run.out == '' + assert run.err == "" + assert run.out == "" diff --git a/test/test_unit_choose_template_cmd.py b/test/test_unit_choose_template_cmd.py index 536735d..cf7600b 100644 --- a/test/test_unit_choose_template_cmd.py +++ b/test/test_unit_choose_template_cmd.py @@ -2,20 +2,20 @@ import pytest -@pytest.mark.parametrize('label', ['', 'default', 'other']) -@pytest.mark.parametrize('awk', [True, False], ids=['awk', 'no-awk']) +@pytest.mark.parametrize("label", ["", "default", "other"]) +@pytest.mark.parametrize("awk", [True, False], ids=["awk", "no-awk"]) def test_kind_default(runner, yadm, awk, label): """Test kind: default""" - expected = 'template_default' - awk_avail = 'true' + expected = "template_default" + awk_avail = "true" if not awk: - awk_avail = 'false' - expected = '' + awk_avail = "false" + expected = "" - if label == 'other': - expected = '' + if label == "other": + expected = "" script = f""" YADM_TEST=1 source {yadm} @@ -23,30 +23,30 @@ def test_kind_default(runner, yadm, awk, label): template="$(choose_template_cmd "{label}")" echo "TEMPLATE:$template" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert f'TEMPLATE:{expected}\n' in run.out + assert run.err == "" + assert f"TEMPLATE:{expected}\n" in run.out -@pytest.mark.parametrize('label', ['envtpl', 'j2cli', 'j2', 'other']) -@pytest.mark.parametrize('envtpl', [True, False], ids=['envtpl', 'no-envtpl']) -@pytest.mark.parametrize('j2cli', [True, False], ids=['j2cli', 'no-j2cli']) +@pytest.mark.parametrize("label", ["envtpl", "j2cli", "j2", "other"]) +@pytest.mark.parametrize("envtpl", [True, False], ids=["envtpl", "no-envtpl"]) +@pytest.mark.parametrize("j2cli", [True, False], ids=["j2cli", "no-j2cli"]) def test_kind_j2cli_envtpl(runner, yadm, envtpl, j2cli, label): """Test kind: j2 (both j2cli & envtpl) j2cli is preferred over envtpl if available. """ - envtpl_avail = 'true' if envtpl else 'false' - j2cli_avail = 'true' if j2cli else 'false' + envtpl_avail = "true" if envtpl else "false" + j2cli_avail = "true" if j2cli else "false" - if label in ('j2cli', 'j2') and j2cli: - expected = 'template_j2cli' - elif label in ('envtpl', 'j2') and envtpl: - expected = 'template_envtpl' + if label in ("j2cli", "j2") and j2cli: + expected = "template_j2cli" + elif label in ("envtpl", "j2") and envtpl: + expected = "template_envtpl" else: - expected = '' + expected = "" script = f""" YADM_TEST=1 source {yadm} @@ -55,7 +55,7 @@ def test_kind_j2cli_envtpl(runner, yadm, envtpl, j2cli, label): template="$(choose_template_cmd "{label}")" echo "TEMPLATE:$template" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert f'TEMPLATE:{expected}\n' in run.out + assert run.err == "" + assert f"TEMPLATE:{expected}\n" in run.out diff --git a/test/test_unit_configure_paths.py b/test/test_unit_configure_paths.py index 8ecd0ea..d2a680e 100644 --- a/test/test_unit_configure_paths.py +++ b/test/test_unit_configure_paths.py @@ -2,52 +2,56 @@ import pytest -ARCHIVE = 'archive' -BOOTSTRAP = 'bootstrap' -CONFIG = 'config' -ENCRYPT = 'encrypt' -HOME = '/testhome' -REPO = 'repo.git' -YDIR = '.config/yadm' -YDATA = '.local/share/yadm' +ARCHIVE = "archive" +BOOTSTRAP = "bootstrap" +CONFIG = "config" +ENCRYPT = "encrypt" +HOME = "/testhome" +REPO = "repo.git" +YDIR = ".config/yadm" +YDATA = ".local/share/yadm" @pytest.mark.parametrize( - 'override, expect', [ + "override, expect", + [ (None, {}), - ('-Y', {'yadm': 'YADM_DIR'}), - ('--yadm-data', {'data': 'YADM_DATA'}), - ('--yadm-repo', {'repo': 'YADM_REPO', 'git': 'GIT_DIR'}), - ('--yadm-config', {'config': 'YADM_CONFIG'}), - ('--yadm-encrypt', {'encrypt': 'YADM_ENCRYPT'}), - ('--yadm-archive', {'archive': 'YADM_ARCHIVE'}), - ('--yadm-bootstrap', {'bootstrap': 'YADM_BOOTSTRAP'}), - ], ids=[ - 'default', - 'override yadm dir', - 'override yadm data', - 'override repo', - 'override config', - 'override encrypt', - 'override archive', - 'override bootstrap', - ]) + ("-Y", {"yadm": "YADM_DIR"}), + ("--yadm-data", {"data": "YADM_DATA"}), + ("--yadm-repo", {"repo": "YADM_REPO", "git": "GIT_DIR"}), + ("--yadm-config", {"config": "YADM_CONFIG"}), + ("--yadm-encrypt", {"encrypt": "YADM_ENCRYPT"}), + ("--yadm-archive", {"archive": "YADM_ARCHIVE"}), + ("--yadm-bootstrap", {"bootstrap": "YADM_BOOTSTRAP"}), + ], + ids=[ + "default", + "override yadm dir", + "override yadm data", + "override repo", + "override config", + "override encrypt", + "override archive", + "override bootstrap", + ], +) @pytest.mark.parametrize( - 'path', ['.', './override', 'override', '.override', '/override'], ids=[ - 'cwd', './relative', 'relative', 'hidden relative', 'absolute' - ]) + "path", + [".", "./override", "override", ".override", "/override"], + ids=["cwd", "./relative", "relative", "hidden relative", "absolute"], +) def test_config(runner, paths, override, expect, path): """Test configure_paths""" - if path.startswith('/'): + if path.startswith("/"): expected_path = path else: expected_path = str(paths.root.join(path)) args = [override, path] if override else [] - if override == '-Y': + if override == "-Y": matches = match_map(expected_path) - elif override == '--yadm-data': + elif override == "--yadm-data": matches = match_map(None, expected_path) else: matches = match_map() @@ -61,23 +65,23 @@ def test_config(runner, paths, override, expect, path): def match_map(yadm_dir=None, yadm_data=None): """Create a dictionary of matches, relative to yadm_dir""" if not yadm_dir: - yadm_dir = '/'.join([HOME, YDIR]) + yadm_dir = "/".join([HOME, YDIR]) if not yadm_data: - yadm_data = '/'.join([HOME, YDATA]) + yadm_data = "/".join([HOME, YDATA]) return { - 'yadm': f'YADM_DIR="{yadm_dir}"', - 'repo': f'YADM_REPO="{yadm_data}/{REPO}"', - 'config': f'YADM_CONFIG="{yadm_dir}/{CONFIG}"', - 'encrypt': f'YADM_ENCRYPT="{yadm_dir}/{ENCRYPT}"', - 'archive': f'YADM_ARCHIVE="{yadm_data}/{ARCHIVE}"', - 'bootstrap': f'YADM_BOOTSTRAP="{yadm_dir}/{BOOTSTRAP}"', - 'git': f'GIT_DIR="{yadm_data}/{REPO}"', - } + "yadm": f'YADM_DIR="{yadm_dir}"', + "repo": f'YADM_REPO="{yadm_data}/{REPO}"', + "config": f'YADM_CONFIG="{yadm_dir}/{CONFIG}"', + "encrypt": f'YADM_ENCRYPT="{yadm_dir}/{ENCRYPT}"', + "archive": f'YADM_ARCHIVE="{yadm_data}/{ARCHIVE}"', + "bootstrap": f'YADM_BOOTSTRAP="{yadm_dir}/{BOOTSTRAP}"', + "git": f'GIT_DIR="{yadm_data}/{REPO}"', + } def run_test(runner, paths, args, expected_matches, cwd=None): """Run proces global args, and run configure_paths""" - argstring = ' '.join(['"'+a+'"' for a in args]) + argstring = " ".join(['"' + a + '"' for a in args]) script = f""" YADM_TEST=1 HOME="{HOME}" source {paths.pgm} process_global_args {argstring} @@ -87,8 +91,8 @@ def run_test(runner, paths, args, expected_matches, cwd=None): configure_paths declare -p | grep -E '(YADM|GIT)_' """ - run = runner(command=['bash'], inp=script, cwd=cwd) + run = runner(command=["bash"], inp=script, cwd=cwd) assert run.success - assert run.err == '' + assert run.err == "" for match in expected_matches: assert match in run.out diff --git a/test/test_unit_copy_perms.py b/test/test_unit_copy_perms.py index 3c79768..c0ea04f 100644 --- a/test/test_unit_copy_perms.py +++ b/test/test_unit_copy_perms.py @@ -1,43 +1,42 @@ """Unit tests: copy_perms""" import os + import pytest -OCTAL = '7654' -NON_OCTAL = '9876' +OCTAL = "7654" +NON_OCTAL = "9876" -@pytest.mark.parametrize( - 'stat_broken', [True, False], ids=['normal', 'stat broken']) +@pytest.mark.parametrize("stat_broken", [True, False], ids=["normal", "stat broken"]) def test_copy_perms(runner, yadm, tmpdir, stat_broken): """Test function copy_perms""" src_mode = 0o754 dst_mode = 0o644 - source = tmpdir.join('source') - source.write('test', ensure=True) + source = tmpdir.join("source") + source.write("test", ensure=True) source.chmod(src_mode) - dest = tmpdir.join('dest') - dest.write('test', ensure=True) + dest = tmpdir.join("dest") + dest.write("test", ensure=True) dest.chmod(dst_mode) - override_stat = '' + override_stat = "" if stat_broken: - override_stat = 'function stat() { echo broken; }' + override_stat = "function stat() { echo broken; }" script = f""" YADM_TEST=1 source {yadm} {override_stat} copy_perms "{source}" "{dest}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert run.out == '' + assert run.err == "" + assert run.out == "" expected = dst_mode if stat_broken else src_mode assert oct(os.stat(dest).st_mode)[-3:] == oct(expected)[-3:] -@pytest.mark.parametrize( - 'stat_output', [OCTAL, NON_OCTAL], ids=['octal', 'non-octal']) +@pytest.mark.parametrize("stat_output", [OCTAL, NON_OCTAL], ids=["octal", "non-octal"]) def test_get_mode(runner, yadm, stat_output): """Test function get_mode""" script = f""" @@ -46,8 +45,8 @@ def test_get_mode(runner, yadm, stat_output): mode=$(get_mode abc) echo "MODE:$mode" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" expected = OCTAL if stat_output == OCTAL else "" - assert f'MODE:{expected}\n' in run.out + assert f"MODE:{expected}\n" in run.out diff --git a/test/test_unit_encryption.py b/test/test_unit_encryption.py index ab03c62..098d5f6 100644 --- a/test/test_unit_encryption.py +++ b/test/test_unit_encryption.py @@ -3,12 +3,12 @@ import pytest -@pytest.mark.parametrize('condition', ['default', 'override']) +@pytest.mark.parametrize("condition", ["default", "override"]) def test_get_cipher(runner, paths, condition): """Test _get_cipher()""" - if condition == 'override': - paths.config.write('[yadm]\n\tcipher = override-cipher') + if condition == "override": + paths.config.write("[yadm]\n\tcipher = override-cipher") script = f""" YADM_TEST=1 source {paths.pgm} @@ -19,18 +19,18 @@ def test_get_cipher(runner, paths, condition): echo "output_archive:$output_archive" echo "yadm_cipher:$yadm_cipher" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert 'output_archive:test-archive' in run.out - if condition == 'override': - assert 'yadm_cipher:override-cipher' in run.out + assert run.err == "" + assert "output_archive:test-archive" in run.out + if condition == "override": + assert "yadm_cipher:override-cipher" in run.out else: - assert 'yadm_cipher:gpg' in run.out + assert "yadm_cipher:gpg" in run.out -@pytest.mark.parametrize('cipher', ['gpg', 'openssl', 'bad']) -@pytest.mark.parametrize('mode', ['_encrypt_to', '_decrypt_from']) +@pytest.mark.parametrize("cipher", ["gpg", "openssl", "bad"]) +@pytest.mark.parametrize("mode", ["_encrypt_to", "_decrypt_from"]) def test_encrypt_decrypt(runner, paths, cipher, mode): """Test _encrypt_to() & _decrypt_from""" @@ -49,24 +49,24 @@ def test_encrypt_decrypt(runner, paths, cipher, mode): GPG_PROGRAM=mock_gpg {mode} {paths.archive} """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) - if cipher != 'bad': + if cipher != "bad": assert run.success assert run.out.startswith(cipher) assert str(paths.archive) in run.out - assert run.err == '' + assert run.err == "" else: assert run.failure - assert 'Unknown cipher' in run.err + assert "Unknown cipher" in run.err -@pytest.mark.parametrize('condition', ['default', 'override']) +@pytest.mark.parametrize("condition", ["default", "override"]) def test_get_openssl_ciphername(runner, paths, condition): """Test _get_openssl_ciphername()""" - if condition == 'override': - paths.config.write('[yadm]\n\topenssl-ciphername = override-cipher') + if condition == "override": + paths.config.write("[yadm]\n\topenssl-ciphername = override-cipher") script = f""" YADM_TEST=1 source {paths.pgm} @@ -76,21 +76,21 @@ def test_get_openssl_ciphername(runner, paths, condition): result=$(_get_openssl_ciphername) echo "result:$result" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - if condition == 'override': - assert run.out.strip() == 'result:override-cipher' + assert run.err == "" + if condition == "override": + assert run.out.strip() == "result:override-cipher" else: - assert run.out.strip() == 'result:aes-256-cbc' + assert run.out.strip() == "result:aes-256-cbc" -@pytest.mark.parametrize('condition', ['old', 'not-old']) +@pytest.mark.parametrize("condition", ["old", "not-old"]) def test_set_openssl_options(runner, paths, condition): """Test _set_openssl_options()""" - if condition == 'old': - paths.config.write('[yadm]\n\topenssl-old = true') + if condition == "old": + paths.config.write("[yadm]\n\topenssl-old = true") script = f""" YADM_TEST=1 source {paths.pgm} @@ -101,20 +101,20 @@ def test_set_openssl_options(runner, paths, condition): _set_openssl_options echo "result:${{OPENSSL_OPTS[@]}}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - if condition == 'old': - assert '-testcipher -salt -md md5' in run.out + assert run.err == "" + if condition == "old": + assert "-testcipher -salt -md md5" in run.out else: - assert '-testcipher -salt -pbkdf2 -iter 100000 -md sha512' in run.out + assert "-testcipher -salt -pbkdf2 -iter 100000 -md sha512" in run.out -@pytest.mark.parametrize('recipient', ['ASK', 'present', '']) +@pytest.mark.parametrize("recipient", ["ASK", "present", ""]) def test_set_gpg_options(runner, paths, recipient): """Test _set_gpg_options()""" - paths.config.write(f'[yadm]\n\tgpg-recipient = {recipient}') + paths.config.write(f"[yadm]\n\tgpg-recipient = {recipient}") script = f""" YADM_TEST=1 source {paths.pgm} @@ -124,12 +124,12 @@ def test_set_gpg_options(runner, paths, recipient): _set_gpg_options echo "result:${{GPG_OPTS[@]}}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - if recipient == 'ASK': - assert run.out.strip() == 'result:--no-default-recipient -e' - elif recipient != '': - assert run.out.strip() == f'result:-e -r {recipient}' + assert run.err == "" + if recipient == "ASK": + assert run.out.strip() == "result:--no-default-recipient -e" + elif recipient != "": + assert run.out.strip() == f"result:-e -r {recipient}" else: - assert run.out.strip() == 'result:-c' + assert run.out.strip() == "result:-c" diff --git a/test/test_unit_exclude_encrypted.py b/test/test_unit_exclude_encrypted.py index 9d9a074..8937a8b 100644 --- a/test/test_unit_exclude_encrypted.py +++ b/test/test_unit_exclude_encrypted.py @@ -2,38 +2,28 @@ import pytest -@pytest.mark.parametrize( - 'exclude', ['missing', 'outdated', 'up-to-date']) -@pytest.mark.parametrize( - 'encrypt_exists', [True, False], ids=['encrypt', 'no-encrypt']) -@pytest.mark.parametrize( - 'auto_exclude', [True, False], ids=['enabled', 'disabled']) -def test_exclude_encrypted( - runner, tmpdir, yadm, encrypt_exists, auto_exclude, exclude): +@pytest.mark.parametrize("exclude", ["missing", "outdated", "up-to-date"]) +@pytest.mark.parametrize("encrypt_exists", [True, False], ids=["encrypt", "no-encrypt"]) +@pytest.mark.parametrize("auto_exclude", [True, False], ids=["enabled", "disabled"]) +def test_exclude_encrypted(runner, tmpdir, yadm, encrypt_exists, auto_exclude, exclude): """Test exclude_encrypted()""" - header = ( - "# yadm-auto-excludes\n" - "# This section is managed by yadm.\n" - "# Any edits below will be lost.\n" - ) + header = "# yadm-auto-excludes\n# This section is managed by yadm.\n# Any edits below will be lost.\n" config_function = 'function config() { echo "false";}' if auto_exclude: - config_function = 'function config() { return; }' + config_function = "function config() { return; }" - encrypt_file = tmpdir.join('encrypt_file') - repo_dir = tmpdir.join('repodir') - exclude_file = repo_dir.join('info/exclude') + encrypt_file = tmpdir.join("encrypt_file") + repo_dir = tmpdir.join("repodir") + exclude_file = repo_dir.join("info/exclude") if encrypt_exists: - encrypt_file.write('test-encrypt-data\n', ensure=True) - if exclude == 'outdated': - exclude_file.write( - f'original-exclude\n{header}outdated\n', ensure=True) - elif exclude == 'up-to-date': - exclude_file.write( - f'original-exclude\n{header}test-encrypt-data\n', ensure=True) + encrypt_file.write("test-encrypt-data\n", ensure=True) + if exclude == "outdated": + exclude_file.write(f"original-exclude\n{header}outdated\n", ensure=True) + elif exclude == "up-to-date": + exclude_file.write(f"original-exclude\n{header}test-encrypt-data\n", ensure=True) script = f""" YADM_TEST=1 source {yadm} @@ -43,24 +33,22 @@ def test_exclude_encrypted( YADM_REPO="{repo_dir}" exclude_encrypted """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" if auto_exclude: if encrypt_exists: assert exclude_file.exists() - if exclude == 'missing': - assert exclude_file.read() == f'{header}test-encrypt-data\n' + if exclude == "missing": + assert exclude_file.read() == f"{header}test-encrypt-data\n" else: - assert exclude_file.read() == ( - 'original-exclude\n' - f'{header}test-encrypt-data\n') - if exclude != 'up-to-date': - assert f'Updating {exclude_file}' in run.out + assert exclude_file.read() == ("original-exclude\n" f"{header}test-encrypt-data\n") + if exclude != "up-to-date": + assert f"Updating {exclude_file}" in run.out else: - assert run.out == '' + assert run.out == "" else: - assert run.out == '' + assert run.out == "" else: - assert run.out == '' + assert run.out == "" diff --git a/test/test_unit_issue_legacy_path_warning.py b/test/test_unit_issue_legacy_path_warning.py index e43228b..faae7fa 100644 --- a/test/test_unit_issue_legacy_path_warning.py +++ b/test/test_unit_issue_legacy_path_warning.py @@ -3,25 +3,24 @@ import pytest @pytest.mark.parametrize( - 'legacy_path', [ + "legacy_path", + [ None, - 'repo.git', - 'files.gpg', - ], - ) -@pytest.mark.parametrize( - 'override', [True, False], ids=['override', 'no-override']) -@pytest.mark.parametrize( - 'upgrade', [True, False], ids=['upgrade', 'no-upgrade']) + "repo.git", + "files.gpg", + ], +) +@pytest.mark.parametrize("override", [True, False], ids=["override", "no-override"]) +@pytest.mark.parametrize("upgrade", [True, False], ids=["upgrade", "no-upgrade"]) def test_legacy_warning(tmpdir, runner, yadm, upgrade, override, legacy_path): """Use issue_legacy_path_warning""" - home = tmpdir.mkdir('home') + home = tmpdir.mkdir("home") if legacy_path: - home.ensure(f'.config/yadm/{str(legacy_path)}') + home.ensure(f".config/yadm/{str(legacy_path)}") - override = 'YADM_OVERRIDE_REPO=override' if override else '' - main_args = 'MAIN_ARGS=("upgrade")' if upgrade else '' + override = "YADM_OVERRIDE_REPO=override" if override else "" + main_args = 'MAIN_ARGS=("upgrade")' if upgrade else "" script = f""" XDG_CONFIG_HOME= XDG_DATA_HOME= @@ -32,10 +31,10 @@ def test_legacy_warning(tmpdir, runner, yadm, upgrade, override, legacy_path): set_yadm_dirs issue_legacy_path_warning """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.out == '' + assert run.out == "" if legacy_path and (not upgrade) and (not override): - assert 'Legacy paths have been detected' in run.err + assert "Legacy paths have been detected" in run.err else: - assert 'Legacy paths have been detected' not in run.err + assert "Legacy paths have been detected" not in run.err diff --git a/test/test_unit_parse_encrypt.py b/test/test_unit_parse_encrypt.py index ec3a6ee..6a5c23b 100644 --- a/test/test_unit_parse_encrypt.py +++ b/test/test_unit_parse_encrypt.py @@ -7,136 +7,137 @@ def test_not_called(runner, paths): """Test parse_encrypt (not called)""" run = run_parse_encrypt(runner, paths, skip_parse=True) assert run.success - assert run.err == '' - assert 'EIF:unparsed' in run.out, 'EIF should be unparsed' - assert 'EIF_COUNT:1' in run.out, 'Only value of EIF should be unparsed' + assert run.err == "" + assert "EIF:unparsed" in run.out, "EIF should be unparsed" + assert "EIF_COUNT:1" in run.out, "Only value of EIF should be unparsed" def test_short_circuit(runner, paths): """Test parse_encrypt (short-circuit)""" run = run_parse_encrypt(runner, paths, twice=True) assert run.success - assert run.err == '' - assert 'PARSE_ENCRYPT_SHORT=parse_encrypt() not reprocessed' in run.out, ( - 'parse_encrypt() should short-circuit') + assert run.err == "" + assert "PARSE_ENCRYPT_SHORT=parse_encrypt() not reprocessed" in run.out, "parse_encrypt() should short-circuit" @pytest.mark.parametrize( - 'encrypt', [ - ('missing'), - ('empty'), - ]) + "encrypt", + [ + ("missing"), + ("empty"), + ], +) def test_empty(runner, paths, encrypt): """Test parse_encrypt (file missing/empty)""" # write encrypt file - if encrypt == 'missing': - assert not paths.encrypt.exists(), 'Encrypt should be missing' + if encrypt == "missing": + assert not paths.encrypt.exists(), "Encrypt should be missing" else: - paths.encrypt.write('') - assert paths.encrypt.exists(), 'Encrypt should exist' - assert paths.encrypt.size() == 0, 'Encrypt should be empty' + paths.encrypt.write("") + assert paths.encrypt.exists(), "Encrypt should exist" + assert paths.encrypt.size() == 0, "Encrypt should be empty" # run parse_encrypt run = run_parse_encrypt(runner, paths) assert run.success - assert run.err == '' + assert run.err == "" # validate parsing result - assert 'EIF_COUNT:0' in run.out, 'EIF should be empty' + assert "EIF_COUNT:0" in run.out, "EIF should be empty" def create_test_encrypt_data(paths): """Generate test data for testing encrypt""" - edata = '' + edata = "" expected = set() # empty line - edata += '\n' + edata += "\n" # simple comments - edata += '# a simple comment\n' - edata += ' # a comment with leading space\n' + edata += "# a simple comment\n" + edata += " # a comment with leading space\n" # unreferenced directory - paths.work.join('unreferenced').mkdir() + paths.work.join("unreferenced").mkdir() # simple files - edata += 'simple_file\n' - edata += 'simple.file\n' - paths.work.join('simple_file').write('') - paths.work.join('simple.file').write('') - paths.work.join('simple_file2').write('') - paths.work.join('simple.file2').write('') - expected.add('simple_file') - expected.add('simple.file') + edata += "simple_file\n" + edata += "simple.file\n" + paths.work.join("simple_file").write("") + paths.work.join("simple.file").write("") + paths.work.join("simple_file2").write("") + paths.work.join("simple.file2").write("") + expected.add("simple_file") + expected.add("simple.file") # simple files in directories - edata += 'simple_dir/simple_file\n' - paths.work.join('simple_dir/simple_file').write('', ensure=True) - paths.work.join('simple_dir/simple_file2').write('', ensure=True) - expected.add('simple_dir/simple_file') + edata += "simple_dir/simple_file\n" + paths.work.join("simple_dir/simple_file").write("", ensure=True) + paths.work.join("simple_dir/simple_file2").write("", ensure=True) + expected.add("simple_dir/simple_file") # paths with spaces - edata += 'with space/with space\n' - paths.work.join('with space/with space').write('', ensure=True) - paths.work.join('with space/with space2').write('', ensure=True) - expected.add('with space/with space') + edata += "with space/with space\n" + paths.work.join("with space/with space").write("", ensure=True) + paths.work.join("with space/with space2").write("", ensure=True) + expected.add("with space/with space") # hidden files - edata += '.hidden\n' - paths.work.join('.hidden').write('') - expected.add('.hidden') + edata += ".hidden\n" + paths.work.join(".hidden").write("") + expected.add(".hidden") # hidden files in directories - edata += '.hidden_dir/.hidden_file\n' - paths.work.join('.hidden_dir/.hidden_file').write('', ensure=True) - expected.add('.hidden_dir/.hidden_file') + edata += ".hidden_dir/.hidden_file\n" + paths.work.join(".hidden_dir/.hidden_file").write("", ensure=True) + expected.add(".hidden_dir/.hidden_file") # wildcards - edata += 'wild*\n' - paths.work.join('wildcard1').write('', ensure=True) - paths.work.join('wildcard2').write('', ensure=True) - expected.add('wildcard1') - expected.add('wildcard2') + edata += "wild*\n" + paths.work.join("wildcard1").write("", ensure=True) + paths.work.join("wildcard2").write("", ensure=True) + expected.add("wildcard1") + expected.add("wildcard2") - edata += 'dirwild*\n' - paths.work.join('dirwildcard/file1').write('', ensure=True) - paths.work.join('dirwildcard/file2').write('', ensure=True) - expected.add('dirwildcard') + edata += "dirwild*\n" + paths.work.join("dirwildcard/file1").write("", ensure=True) + paths.work.join("dirwildcard/file2").write("", ensure=True) + expected.add("dirwildcard") # excludes - edata += 'exclude*\n' - edata += 'ex ex/*\n' - paths.work.join('exclude_file1').write('') - paths.work.join('exclude_file2.ex').write('') - paths.work.join('exclude_file3.ex3').write('') - expected.add('exclude_file1') - expected.add('exclude_file3.ex3') - edata += '!*.ex\n' - edata += '!ex ex/*.txt\n' - paths.work.join('ex ex/file4').write('', ensure=True) - paths.work.join('ex ex/file5.txt').write('', ensure=True) - paths.work.join('ex ex/file6.text').write('', ensure=True) - expected.add('ex ex/file4') - expected.add('ex ex/file6.text') + edata += "exclude*\n" + edata += "ex ex/*\n" + paths.work.join("exclude_file1").write("") + paths.work.join("exclude_file2.ex").write("") + paths.work.join("exclude_file3.ex3").write("") + expected.add("exclude_file1") + expected.add("exclude_file3.ex3") + edata += "!*.ex\n" + edata += "!ex ex/*.txt\n" + paths.work.join("ex ex/file4").write("", ensure=True) + paths.work.join("ex ex/file5.txt").write("", ensure=True) + paths.work.join("ex ex/file6.text").write("", ensure=True) + expected.add("ex ex/file4") + expected.add("ex ex/file6.text") # double star - edata += 'doublestar/**/file*\n' - edata += '!**/file3\n' - paths.work.join('doublestar/a/b/file1').write('', ensure=True) - paths.work.join('doublestar/c/d/file2').write('', ensure=True) - paths.work.join('doublestar/e/f/file3').write('', ensure=True) - paths.work.join('doublestar/g/h/nomatch').write('', ensure=True) - expected.add('doublestar/a/b/file1') - expected.add('doublestar/c/d/file2') + edata += "doublestar/**/file*\n" + edata += "!**/file3\n" + paths.work.join("doublestar/a/b/file1").write("", ensure=True) + paths.work.join("doublestar/c/d/file2").write("", ensure=True) + paths.work.join("doublestar/e/f/file3").write("", ensure=True) + paths.work.join("doublestar/g/h/nomatch").write("", ensure=True) + expected.add("doublestar/a/b/file1") + expected.add("doublestar/c/d/file2") # doublestar/e/f/file3 is excluded return edata, expected -@pytest.mark.usefixtures('ds1_repo_copy') +@pytest.mark.usefixtures("ds1_repo_copy") def test_file_parse_encrypt(runner, paths): """Test parse_encrypt @@ -147,39 +148,35 @@ def test_file_parse_encrypt(runner, paths): edata, expected = create_test_encrypt_data(paths) # write encrypt file - print(f'ENCRYPT:\n---\n{edata}---\n') + print(f"ENCRYPT:\n---\n{edata}---\n") paths.encrypt.write(edata) assert paths.encrypt.isfile() # run parse_encrypt run = run_parse_encrypt(runner, paths) assert run.success - assert run.err == '' + assert run.err == "" - assert f'EIF_COUNT:{len(expected)}' in run.out, 'EIF count wrong' + assert f"EIF_COUNT:{len(expected)}" in run.out, "EIF count wrong" for expected_file in expected: - assert f'EIF:{expected_file}\n' in run.out + assert f"EIF:{expected_file}\n" in run.out - sorted_expectations = '\n'.join( - [f'EIF:{exp}' for exp in sorted(expected)]) + sorted_expectations = "\n".join([f"EIF:{exp}" for exp in sorted(expected)]) assert sorted_expectations in run.out -def run_parse_encrypt( - runner, paths, - skip_parse=False, - twice=False): +def run_parse_encrypt(runner, paths, skip_parse=False, twice=False): """Run parse_encrypt A count of ENCRYPT_INCLUDE_FILES will be reported as EIF_COUNT:X. All values of ENCRYPT_INCLUDE_FILES will be reported as individual EIF:value lines. """ - parse_cmd = 'parse_encrypt' + parse_cmd = "parse_encrypt" if skip_parse: - parse_cmd = '' + parse_cmd = "" if twice: - parse_cmd = 'parse_encrypt; parse_encrypt' + parse_cmd = "parse_encrypt; parse_encrypt" script = f""" YADM_TEST=1 source {paths.pgm} YADM_ENCRYPT={paths.encrypt} @@ -197,5 +194,5 @@ def run_parse_encrypt( echo "EIF:$value" done """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) return run diff --git a/test/test_unit_private_dirs.py b/test/test_unit_private_dirs.py index 4f182da..58bd39c 100644 --- a/test/test_unit_private_dirs.py +++ b/test/test_unit_private_dirs.py @@ -3,15 +3,15 @@ import pytest @pytest.mark.parametrize( - 'gnupghome', + "gnupghome", [True, False], - ids=['gnupghome-set', 'gnupghome-unset'], + ids=["gnupghome-set", "gnupghome-unset"], ) -@pytest.mark.parametrize('param', ['all', 'gnupg']) +@pytest.mark.parametrize("param", ["all", "gnupg"]) def test_relative_path(runner, paths, gnupghome, param): """Test translate_to_relative""" - alt_gnupghome = 'alt/gnupghome' + alt_gnupghome = "alt/gnupghome" env_gnupghome = paths.work.join(alt_gnupghome) script = f""" @@ -22,13 +22,13 @@ def test_relative_path(runner, paths, gnupghome, param): env = {} if gnupghome: - env['GNUPGHOME'] = env_gnupghome + env["GNUPGHOME"] = env_gnupghome - expected = alt_gnupghome if gnupghome else '.gnupg' - if param == 'all': - expected = f'.ssh {expected}' + expected = alt_gnupghome if gnupghome else ".gnupg" + if param == "all": + expected = f".ssh {expected}" - run = runner(command=['bash'], inp=script, env=env) + run = runner(command=["bash"], inp=script, env=env) assert run.success - assert run.err == '' + assert run.err == "" assert run.out.strip() == expected diff --git a/test/test_unit_query_distro.py b/test/test_unit_query_distro.py index 4f46501..c32760b 100644 --- a/test/test_unit_query_distro.py +++ b/test/test_unit_query_distro.py @@ -2,18 +2,16 @@ import pytest -@pytest.mark.parametrize( - 'condition', ['lsb_release', 'os-release', 'os-release-quotes', 'missing']) +@pytest.mark.parametrize("condition", ["lsb_release", "os-release", "os-release-quotes", "missing"]) def test_query_distro(runner, yadm, tst_distro, tmp_path, condition): """Match lsb_release -si when present""" - test_release = 'testrelease' - lsb_release = '' - os_release = tmp_path.joinpath('os-release') - if 'os-release' in condition: - quotes = '"' if 'quotes' in condition else '' - os_release.write_text( - f"testing\nID={quotes}{test_release}{quotes}\nrelease") - if condition != 'lsb_release': + test_release = "testrelease" + lsb_release = "" + os_release = tmp_path.joinpath("os-release") + if "os-release" in condition: + quotes = '"' if "quotes" in condition else "" + os_release.write_text(f"testing\nID={quotes}{test_release}{quotes}\nrelease") + if condition != "lsb_release": lsb_release = 'LSB_RELEASE_PROGRAM="missing_lsb_release"' script = f""" YADM_TEST=1 source {yadm} @@ -21,12 +19,12 @@ def test_query_distro(runner, yadm, tst_distro, tmp_path, condition): OS_RELEASE="{os_release}" query_distro """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - if condition == 'lsb_release': + assert run.err == "" + if condition == "lsb_release": assert run.out.rstrip() == tst_distro - elif 'os-release' in condition: + elif "os-release" in condition: assert run.out.rstrip() == test_release else: - assert run.out.rstrip() == '' + assert run.out.rstrip() == "" diff --git a/test/test_unit_query_distro_family.py b/test/test_unit_query_distro_family.py index bf68319..1935bf6 100644 --- a/test/test_unit_query_distro_family.py +++ b/test/test_unit_query_distro_family.py @@ -2,25 +2,23 @@ import pytest -@pytest.mark.parametrize( - 'condition', ['os-release', 'os-release-quotes', 'missing']) +@pytest.mark.parametrize("condition", ["os-release", "os-release-quotes", "missing"]) def test_query_distro_family(runner, yadm, tmp_path, condition): """Match ID_LIKE when present""" - test_family = 'testfamily' - os_release = tmp_path.joinpath('os-release') - if 'os-release' in condition: - quotes = '"' if 'quotes' in condition else '' - os_release.write_text( - f"testing\nID_LIKE={quotes}{test_family}{quotes}\nfamily") + test_family = "testfamily" + os_release = tmp_path.joinpath("os-release") + if "os-release" in condition: + quotes = '"' if "quotes" in condition else "" + os_release.write_text(f"testing\nID_LIKE={quotes}{test_family}{quotes}\nfamily") script = f""" YADM_TEST=1 source {yadm} OS_RELEASE="{os_release}" query_distro_family """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - if 'os-release' in condition: + assert run.err == "" + if "os-release" in condition: assert run.out.rstrip() == test_family else: - assert run.out.rstrip() == '' + assert run.out.rstrip() == "" diff --git a/test/test_unit_record_score.py b/test/test_unit_record_score.py index 78596e1..a82046c 100644 --- a/test/test_unit_record_score.py +++ b/test/test_unit_record_score.py @@ -30,13 +30,13 @@ def test_dont_record_zeros(runner, yadm): record_score "0" "testtgt" "testsrc" {REPORT_RESULTS} """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert 'SIZE:0\n' in run.out - assert 'SCORES:\n' in run.out - assert 'TARGETS:\n' in run.out - assert 'SOURCES:\n' in run.out + assert run.err == "" + assert "SIZE:0\n" in run.out + assert "SCORES:\n" in run.out + assert "TARGETS:\n" in run.out + assert "SOURCES:\n" in run.out def test_new_scores(runner, yadm): @@ -50,29 +50,29 @@ def test_new_scores(runner, yadm): record_score "4" "tgt_three" "src_three" {REPORT_RESULTS} """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert 'SIZE:3\n' in run.out - assert 'SCORES:1 2 4\n' in run.out - assert 'TARGETS:tgt_one tgt_two tgt_three\n' in run.out - assert 'SOURCES:src_one src_two src_three\n' in run.out + assert run.err == "" + assert "SIZE:3\n" in run.out + assert "SCORES:1 2 4\n" in run.out + assert "TARGETS:tgt_one tgt_two tgt_three\n" in run.out + assert "SOURCES:src_one src_two src_three\n" in run.out -@pytest.mark.parametrize('difference', ['lower', 'equal', 'higher']) +@pytest.mark.parametrize("difference", ["lower", "equal", "higher"]) def test_existing_scores(runner, yadm, difference): """Test existing scores""" - expected_score = '2' - expected_src = 'existing_src' - if difference == 'lower': - score = '1' - elif difference == 'equal': - score = '2' + expected_score = "2" + expected_src = "existing_src" + if difference == "lower": + score = "1" + elif difference == "equal": + score = "2" else: - score = '4' - expected_score = '4' - expected_src = 'new_src' + score = "4" + expected_score = "4" + expected_src = "new_src" script = f""" YADM_TEST=1 source {yadm} @@ -83,13 +83,13 @@ def test_existing_scores(runner, yadm, difference): record_score "{score}" "testtgt" "new_src" {REPORT_RESULTS} """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert 'SIZE:1\n' in run.out - assert f'SCORES:{expected_score}\n' in run.out - assert 'TARGETS:testtgt\n' in run.out - assert f'SOURCES:{expected_src}\n' in run.out + assert run.err == "" + assert "SIZE:1\n" in run.out + assert f"SCORES:{expected_score}\n" in run.out + assert "TARGETS:testtgt\n" in run.out + assert f"SOURCES:{expected_src}\n" in run.out def test_existing_template(runner, yadm): @@ -105,19 +105,19 @@ def test_existing_template(runner, yadm): record_score "2" "testtgt" "new_src" {REPORT_RESULTS} """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert 'SIZE:1\n' in run.out - assert 'SCORES:1\n' in run.out - assert 'TARGETS:testtgt\n' in run.out - assert 'SOURCES:\n' in run.out + assert run.err == "" + assert "SIZE:1\n" in run.out + assert "SCORES:1\n" in run.out + assert "TARGETS:testtgt\n" in run.out + assert "SOURCES:\n" in run.out def test_config_first(runner, yadm): """Verify YADM_CONFIG is always processed first""" - config = 'yadm_config_file' + config = "yadm_config_file" script = f""" YADM_TEST=1 source {yadm} {INIT_VARS} @@ -130,12 +130,12 @@ def test_config_first(runner, yadm): echo "CMD_VALUE:${{alt_template_cmds[@]}}" echo "CMD_INDEX:${{!alt_template_cmds[@]}}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert 'SIZE:3\n' in run.out - assert 'SCORES:2 1 3\n' in run.out - assert f'TARGETS:{config} tgt_before tgt_tmp tgt_after\n' in run.out - assert 'SOURCES:src_config src_before src_tmp src_after\n' in run.out - assert 'CMD_VALUE:cmd_tmp\n' in run.out - assert 'CMD_INDEX:2\n' in run.out + assert run.err == "" + assert "SIZE:3\n" in run.out + assert "SCORES:2 1 3\n" in run.out + assert f"TARGETS:{config} tgt_before tgt_tmp tgt_after\n" in run.out + assert "SOURCES:src_config src_before src_tmp src_after\n" in run.out + assert "CMD_VALUE:cmd_tmp\n" in run.out + assert "CMD_INDEX:2\n" in run.out diff --git a/test/test_unit_record_template.py b/test/test_unit_record_template.py index 6bfd012..4f3c3e8 100644 --- a/test/test_unit_record_template.py +++ b/test/test_unit_record_template.py @@ -25,13 +25,13 @@ def test_new_template(runner, yadm): record_template "tgt_three" "cmd_three" "src_three" {REPORT_RESULTS} """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert 'SIZE:3\n' in run.out - assert 'TARGETS:tgt_one tgt_two tgt_three\n' in run.out - assert 'CMDS:cmd_one cmd_two cmd_three\n' in run.out - assert 'SOURCES:src_one src_two src_three\n' in run.out + assert run.err == "" + assert "SIZE:3\n" in run.out + assert "TARGETS:tgt_one tgt_two tgt_three\n" in run.out + assert "CMDS:cmd_one cmd_two cmd_three\n" in run.out + assert "SOURCES:src_one src_two src_three\n" in run.out def test_existing_template(runner, yadm): @@ -46,10 +46,10 @@ def test_existing_template(runner, yadm): record_template "testtgt" "new_cmd" "new_src" {REPORT_RESULTS} """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert 'SIZE:1\n' in run.out - assert 'TARGETS:testtgt\n' in run.out - assert 'CMDS:new_cmd\n' in run.out - assert 'SOURCES:new_src\n' in run.out + assert run.err == "" + assert "SIZE:1\n" in run.out + assert "TARGETS:testtgt\n" in run.out + assert "CMDS:new_cmd\n" in run.out + assert "SOURCES:new_src\n" in run.out diff --git a/test/test_unit_relative_path.py b/test/test_unit_relative_path.py index f723c84..e0b32f5 100644 --- a/test/test_unit_relative_path.py +++ b/test/test_unit_relative_path.py @@ -3,7 +3,7 @@ import pytest @pytest.mark.parametrize( - 'base,full_path,expected', + "base,full_path,expected", [ ("/A/B/C", "/A", "../.."), ("/A/B/C", "/A/B", ".."), @@ -25,7 +25,7 @@ def test_relative_path(runner, paths, base, full_path, expected): relative_path "{base}" "{full_path}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert run.out.strip() == expected diff --git a/test/test_unit_remove_stale_links.py b/test/test_unit_remove_stale_links.py index 0bd960b..f389ed8 100644 --- a/test/test_unit_remove_stale_links.py +++ b/test/test_unit_remove_stale_links.py @@ -1,23 +1,24 @@ """Unit tests: remove_stale_links""" import os + import pytest -@pytest.mark.parametrize('linked', [True, False]) -@pytest.mark.parametrize('kind', ['file', 'symlink']) +@pytest.mark.parametrize("linked", [True, False]) +@pytest.mark.parametrize("kind", ["file", "symlink"]) def test_remove_stale_links(runner, yadm, tmpdir, kind, linked): """Test remove_stale_links()""" - source_file = tmpdir.join('source_file') - source_file.write('source file', ensure=True) - link = tmpdir.join('link') + source_file = tmpdir.join("source_file") + source_file.write("source file", ensure=True) + link = tmpdir.join("link") - if kind == 'file': - link.write('link file', ensure=True) + if kind == "file": + link.write("link file", ensure=True) else: - os.system(f'ln -s {source_file} {link}') + os.system(f"ln -s {source_file} {link}") - alt_linked = '' + alt_linked = "" if linked: alt_linked = source_file @@ -29,9 +30,9 @@ def test_remove_stale_links(runner, yadm, tmpdir, kind, linked): remove_stale_links """ - run = runner(command=['bash'], inp=script) - assert run.err == '' - if kind == 'symlink' and not linked: - assert f'rm -f {link}' in run.out + run = runner(command=["bash"], inp=script) + assert run.err == "" + if kind == "symlink" and not linked: + assert f"rm -f {link}" in run.out else: - assert run.out == '' + assert run.out == "" diff --git a/test/test_unit_report_invalid_alts.py b/test/test_unit_report_invalid_alts.py index 8730d61..996b1ef 100644 --- a/test/test_unit_report_invalid_alts.py +++ b/test/test_unit_report_invalid_alts.py @@ -2,15 +2,15 @@ import pytest -@pytest.mark.parametrize('valid', [True, False], ids=['valid', 'no_valid']) -@pytest.mark.parametrize('previous', [True, False], ids=['prev', 'no_prev']) +@pytest.mark.parametrize("valid", [True, False], ids=["valid", "no_valid"]) +@pytest.mark.parametrize("previous", [True, False], ids=["prev", "no_prev"]) def test_report_invalid_alts(runner, yadm, valid, previous): """Use report_invalid_alts""" - lwi = '' - alts = 'INVALID_ALT=()' + lwi = "" + alts = "INVALID_ALT=()" if previous: - lwi = 'LEGACY_WARNING_ISSUED=1' + lwi = "LEGACY_WARNING_ISSUED=1" if not valid: alts = 'INVALID_ALT=("file##invalid")' @@ -20,11 +20,11 @@ def test_report_invalid_alts(runner, yadm, valid, previous): {alts} report_invalid_alts """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.out == '' + assert run.out == "" if not valid and not previous: - assert 'WARNING' in run.err - assert 'file##invalid' in run.err + assert "WARNING" in run.err + assert "file##invalid" in run.err else: - assert run.err == '' + assert run.err == "" diff --git a/test/test_unit_score_file.py b/test/test_unit_score_file.py index e59c128..c84fb1e 100644 --- a/test/test_unit_score_file.py +++ b/test/test_unit_score_file.py @@ -2,40 +2,40 @@ import pytest CONDITION = { - 'default': { - 'labels': ['default'], - 'modifier': 0, - }, - 'arch': { - 'labels': ['a', 'arch'], - 'modifier': 1, - }, - 'system': { - 'labels': ['o', 'os'], - 'modifier': 2, - }, - 'distro': { - 'labels': ['d', 'distro'], - 'modifier': 4, - }, - 'distro_family': { - 'labels': ['f', 'distro_family'], - 'modifier': 8, - }, - 'class': { - 'labels': ['c', 'class'], - 'modifier': 16, - }, - 'hostname': { - 'labels': ['h', 'hostname'], - 'modifier': 32, - }, - 'user': { - 'labels': ['u', 'user'], - 'modifier': 64, - }, - } -TEMPLATE_LABELS = ['t', 'template', 'yadm'] + "default": { + "labels": ["default"], + "modifier": 0, + }, + "arch": { + "labels": ["a", "arch"], + "modifier": 1, + }, + "system": { + "labels": ["o", "os"], + "modifier": 2, + }, + "distro": { + "labels": ["d", "distro"], + "modifier": 4, + }, + "distro_family": { + "labels": ["f", "distro_family"], + "modifier": 8, + }, + "class": { + "labels": ["c", "class"], + "modifier": 16, + }, + "hostname": { + "labels": ["h", "hostname"], + "modifier": 32, + }, + "user": { + "labels": ["u", "user"], + "modifier": 64, + }, +} +TEMPLATE_LABELS = ["t", "template", "yadm"] def calculate_score(filename): @@ -43,48 +43,48 @@ def calculate_score(filename): # pylint: disable=too-many-branches score = 0 - _, conditions = filename.split('##', 1) + _, conditions = filename.split("##", 1) - for condition in conditions.split(','): + for condition in conditions.split(","): label = condition value = None - if '.' in condition: - label, value = condition.split('.', 1) - if label in CONDITION['default']['labels']: + if "." in condition: + label, value = condition.split(".", 1) + if label in CONDITION["default"]["labels"]: score += 1000 - elif label in CONDITION['arch']['labels']: - if value == 'testarch': - score += 1000 + CONDITION['arch']['modifier'] + elif label in CONDITION["arch"]["labels"]: + if value == "testarch": + score += 1000 + CONDITION["arch"]["modifier"] else: score = 0 break - elif label in CONDITION['system']['labels']: - if value == 'testsystem': - score += 1000 + CONDITION['system']['modifier'] + elif label in CONDITION["system"]["labels"]: + if value == "testsystem": + score += 1000 + CONDITION["system"]["modifier"] else: score = 0 break - elif label in CONDITION['distro']['labels']: - if value == 'testdistro': - score += 1000 + CONDITION['distro']['modifier'] + elif label in CONDITION["distro"]["labels"]: + if value == "testdistro": + score += 1000 + CONDITION["distro"]["modifier"] else: score = 0 break - elif label in CONDITION['class']['labels']: - if value == 'testclass': - score += 1000 + CONDITION['class']['modifier'] + elif label in CONDITION["class"]["labels"]: + if value == "testclass": + score += 1000 + CONDITION["class"]["modifier"] else: score = 0 break - elif label in CONDITION['hostname']['labels']: - if value == 'testhost': - score += 1000 + CONDITION['hostname']['modifier'] + elif label in CONDITION["hostname"]["labels"]: + if value == "testhost": + score += 1000 + CONDITION["hostname"]["modifier"] else: score = 0 break - elif label in CONDITION['user']['labels']: - if value == 'testuser': - score += 1000 + CONDITION['user']['modifier'] + elif label in CONDITION["user"]["labels"]: + if value == "testuser": + score += 1000 + CONDITION["user"]["modifier"] else: score = 0 break @@ -94,111 +94,85 @@ def calculate_score(filename): return score -@pytest.mark.parametrize( - 'default', ['default', None], ids=['default', 'no-default']) -@pytest.mark.parametrize( - 'arch', ['arch', None], ids=['arch', 'no-arch']) -@pytest.mark.parametrize( - 'system', ['system', None], ids=['system', 'no-system']) -@pytest.mark.parametrize( - 'distro', ['distro', None], ids=['distro', 'no-distro']) -@pytest.mark.parametrize( - 'cla', ['class', None], ids=['class', 'no-class']) -@pytest.mark.parametrize( - 'host', ['hostname', None], ids=['hostname', 'no-host']) -@pytest.mark.parametrize( - 'user', ['user', None], ids=['user', 'no-user']) -def test_score_values( - runner, yadm, default, arch, system, distro, cla, host, user): +@pytest.mark.parametrize("default", ["default", None], ids=["default", "no-default"]) +@pytest.mark.parametrize("arch", ["arch", None], ids=["arch", "no-arch"]) +@pytest.mark.parametrize("system", ["system", None], ids=["system", "no-system"]) +@pytest.mark.parametrize("distro", ["distro", None], ids=["distro", "no-distro"]) +@pytest.mark.parametrize("cla", ["class", None], ids=["class", "no-class"]) +@pytest.mark.parametrize("host", ["hostname", None], ids=["hostname", "no-host"]) +@pytest.mark.parametrize("user", ["user", None], ids=["user", "no-user"]) +def test_score_values(runner, yadm, default, arch, system, distro, cla, host, user): """Test score results""" # pylint: disable=too-many-branches - local_class = 'testclass' - local_arch = 'testarch' - local_system = 'testsystem' - local_distro = 'testdistro' - local_host = 'testhost' - local_user = 'testuser' - filenames = {'filename##': 0} + local_class = "testclass" + local_arch = "testarch" + local_system = "testsystem" + local_distro = "testdistro" + local_host = "testhost" + local_user = "testuser" + filenames = {"filename##": 0} if default: for filename in list(filenames): - for label in CONDITION[default]['labels']: + for label in CONDITION[default]["labels"]: newfile = filename - if not newfile.endswith('##'): - newfile += ',' + if not newfile.endswith("##"): + newfile += "," newfile += label filenames[newfile] = calculate_score(newfile) if arch: for filename in list(filenames): for match in [True, False]: - for label in CONDITION[arch]['labels']: + for label in CONDITION[arch]["labels"]: newfile = filename - if not newfile.endswith('##'): - newfile += ',' - newfile += '.'.join([ - label, - local_arch if match else 'badarch' - ]) + if not newfile.endswith("##"): + newfile += "," + newfile += ".".join([label, local_arch if match else "badarch"]) filenames[newfile] = calculate_score(newfile) if system: for filename in list(filenames): for match in [True, False]: - for label in CONDITION[system]['labels']: + for label in CONDITION[system]["labels"]: newfile = filename - if not newfile.endswith('##'): - newfile += ',' - newfile += '.'.join([ - label, - local_system if match else 'badsys' - ]) + if not newfile.endswith("##"): + newfile += "," + newfile += ".".join([label, local_system if match else "badsys"]) filenames[newfile] = calculate_score(newfile) if distro: for filename in list(filenames): for match in [True, False]: - for label in CONDITION[distro]['labels']: + for label in CONDITION[distro]["labels"]: newfile = filename - if not newfile.endswith('##'): - newfile += ',' - newfile += '.'.join([ - label, - local_distro if match else 'baddistro' - ]) + if not newfile.endswith("##"): + newfile += "," + newfile += ".".join([label, local_distro if match else "baddistro"]) filenames[newfile] = calculate_score(newfile) if cla: for filename in list(filenames): for match in [True, False]: - for label in CONDITION[cla]['labels']: + for label in CONDITION[cla]["labels"]: newfile = filename - if not newfile.endswith('##'): - newfile += ',' - newfile += '.'.join([ - label, - local_class if match else 'badclass' - ]) + if not newfile.endswith("##"): + newfile += "," + newfile += ".".join([label, local_class if match else "badclass"]) filenames[newfile] = calculate_score(newfile) if host: for filename in list(filenames): for match in [True, False]: - for label in CONDITION[host]['labels']: + for label in CONDITION[host]["labels"]: newfile = filename - if not newfile.endswith('##'): - newfile += ',' - newfile += '.'.join([ - label, - local_host if match else 'badhost' - ]) + if not newfile.endswith("##"): + newfile += "," + newfile += ".".join([label, local_host if match else "badhost"]) filenames[newfile] = calculate_score(newfile) if user: for filename in list(filenames): for match in [True, False]: - for label in CONDITION[user]['labels']: + for label in CONDITION[user]["labels"]: newfile = filename - if not newfile.endswith('##'): - newfile += ',' - newfile += '.'.join([ - label, - local_user if match else 'baduser' - ]) + if not newfile.endswith("##"): + newfile += "," + newfile += ".".join([label, local_user if match else "baduser"]) filenames[newfile] = calculate_score(newfile) script = f""" @@ -212,29 +186,29 @@ def test_score_values( local_host={local_host} local_user={local_user} """ - expected = '' - for filename in filenames: + expected = "" + for filename, score in filenames.items(): script += f""" score_file "{filename}" echo "{filename}" echo "$score" """ - expected += filename + '\n' - expected += str(filenames[filename]) + '\n' - run = runner(command=['bash'], inp=script) + expected += filename + "\n" + expected += str(score) + "\n" + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert run.out == expected -@pytest.mark.parametrize('ext', [None, 'e', 'extension']) +@pytest.mark.parametrize("ext", [None, "e", "extension"]) def test_extensions(runner, yadm, ext): """Verify extensions do not effect scores""" - local_user = 'testuser' - filename = f'filename##u.{local_user}' + local_user = "testuser" + filename = f"filename##u.{local_user}" if ext: - filename += f',{ext}.xyz' - expected = '' + filename += f",{ext}.xyz" + expected = "" script = f""" YADM_TEST=1 source {yadm} score=0 @@ -243,28 +217,28 @@ def test_extensions(runner, yadm, ext): echo "$score" """ expected = f'{1000 + CONDITION["user"]["modifier"]}\n' - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert run.out == expected def test_score_values_templates(runner, yadm): """Test score results""" - local_class = 'testclass' - local_arch = 'arch' - local_system = 'testsystem' - local_distro = 'testdistro' - local_host = 'testhost' - local_user = 'testuser' - filenames = {'filename##': 0} + local_class = "testclass" + local_arch = "arch" + local_system = "testsystem" + local_distro = "testdistro" + local_host = "testhost" + local_user = "testuser" + filenames = {"filename##": 0} for filename in list(filenames): for label in TEMPLATE_LABELS: newfile = filename - if not newfile.endswith('##'): - newfile += ',' - newfile += '.'.join([label, 'testtemplate']) + if not newfile.endswith("##"): + newfile += "," + newfile += ".".join([label, "testtemplate"]) filenames[newfile] = calculate_score(newfile) script = f""" @@ -277,33 +251,30 @@ def test_score_values_templates(runner, yadm): local_host={local_host} local_user={local_user} """ - expected = '' - for filename in filenames: + expected = "" + for filename, score in filenames.items(): script += f""" score_file "{filename}" echo "{filename}" echo "$score" """ - expected += filename + '\n' - expected += str(filenames[filename]) + '\n' - run = runner(command=['bash'], inp=script) + expected += filename + "\n" + expected += str(score) + "\n" + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert run.out == expected -@pytest.mark.parametrize( - 'cmd_generated', - [True, False], - ids=['supported-template', 'unsupported-template']) +@pytest.mark.parametrize("cmd_generated", [True, False], ids=["supported-template", "unsupported-template"]) def test_template_recording(runner, yadm, cmd_generated): """Template should be recorded if choose_template_cmd outputs a command""" - mock = 'function choose_template_cmd() { return; }' - expected = '' + mock = "function choose_template_cmd() { return; }" + expected = "" if cmd_generated: mock = 'function choose_template_cmd() { echo "test_cmd"; }' - expected = 'template recorded' + expected = "template recorded" script = f""" YADM_TEST=1 source {yadm} @@ -311,24 +282,24 @@ def test_template_recording(runner, yadm, cmd_generated): {mock} score_file "testfile##template.kind" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert run.out.rstrip() == expected def test_underscores_in_distro_and_family(runner, yadm): """Test replacing spaces in distro / distro_family with underscores""" - local_distro = 'test distro' - local_distro_family = 'test family' + local_distro = "test distro" + local_distro_family = "test family" filenames = { - 'filename##distro.test distro': 1004, - 'filename##distro.test-distro': 0, - 'filename##distro.test_distro': 1004, - 'filename##distro_family.test family': 1008, - 'filename##distro_family.test-family': 0, - 'filename##distro_family.test_family': 1008, - } + "filename##distro.test distro": 1004, + "filename##distro.test-distro": 0, + "filename##distro.test_distro": 1004, + "filename##distro_family.test family": 1008, + "filename##distro_family.test-family": 0, + "filename##distro_family.test_family": 1008, + } script = f""" YADM_TEST=1 source {yadm} @@ -336,16 +307,16 @@ def test_underscores_in_distro_and_family(runner, yadm): local_distro="{local_distro}" local_distro_family="{local_distro_family}" """ - expected = '' - for filename in filenames: + expected = "" + for filename, score in filenames.items(): script += f""" score_file "{filename}" echo "{filename}" echo "$score" """ - expected += filename + '\n' - expected += str(filenames[filename]) + '\n' - run = runner(command=['bash'], inp=script) + expected += filename + "\n" + expected += str(score) + "\n" + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert run.out == expected diff --git a/test/test_unit_set_local_alt_values.py b/test/test_unit_set_local_alt_values.py index 449850c..fa5749d 100644 --- a/test/test_unit_set_local_alt_values.py +++ b/test/test_unit_set_local_alt_values.py @@ -4,26 +4,26 @@ import utils @pytest.mark.parametrize( - 'override', [ + "override", + [ False, - 'class', - 'arch', - 'os', - 'hostname', - 'user', - ], + "class", + "arch", + "os", + "hostname", + "user", + ], ids=[ - 'no-override', - 'override-class', - 'override-arch', - 'override-os', - 'override-hostname', - 'override-user', - ] - ) -@pytest.mark.usefixtures('ds1_copy') -def test_set_local_alt_values( - runner, yadm, paths, tst_arch, tst_sys, tst_host, tst_user, override): + "no-override", + "override-class", + "override-arch", + "override-os", + "override-hostname", + "override-user", + ], +) +@pytest.mark.usefixtures("ds1_copy") +def test_set_local_alt_values(runner, yadm, paths, tst_arch, tst_sys, tst_host, tst_user, override): """Use issue_legacy_path_warning""" script = f""" YADM_TEST=1 source {yadm} && @@ -37,37 +37,37 @@ def test_set_local_alt_values( echo "user='$local_user'" """ - if override == 'class': - utils.set_local(paths, override, 'first') - utils.set_local(paths, override, 'override', add=True) + if override == "class": + utils.set_local(paths, override, "first") + utils.set_local(paths, override, "override", add=True) elif override: - utils.set_local(paths, override, 'override') + utils.set_local(paths, override, "override") - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" - if override == 'class': + if override == "class": assert "class='override'" in run.out else: assert "class=''" in run.out - if override == 'arch': + if override == "arch": assert "arch='override'" in run.out else: assert f"arch='{tst_arch}'" in run.out - if override == 'os': + if override == "os": assert "os='override'" in run.out else: assert f"os='{tst_sys}'" in run.out - if override == 'hostname': + if override == "hostname": assert "host='override'" in run.out else: assert f"host='{tst_host}'" in run.out - if override == 'user': + if override == "user": assert "user='override'" in run.out else: assert f"user='{tst_user}'" in run.out @@ -85,8 +85,8 @@ def test_distro_and_family(runner, yadm): echo "distro='$local_distro'" echo "distro_family='$local_distro_family'" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert "distro='testdistro'" in run.out assert "distro_family='testfamily'" in run.out diff --git a/test/test_unit_set_os.py b/test/test_unit_set_os.py index 936986f..ac61de2 100644 --- a/test/test_unit_set_os.py +++ b/test/test_unit_set_os.py @@ -4,25 +4,27 @@ import pytest @pytest.mark.parametrize( - 'proc_value, expected_os', [ - ('missing', 'uname'), - ('has microsoft inside', 'WSL'), # case insensitive - ('has Microsoft inside', 'WSL'), # case insensitive - ('another value', 'uname'), - ], ids=[ - '/proc/version missing', - '/proc/version includes ms', - '/proc/version excludes Ms', - 'another value', - ]) -def test_set_operating_system( - runner, paths, tst_sys, proc_value, expected_os): + "proc_value, expected_os", + [ + ("missing", "uname"), + ("has microsoft inside", "WSL"), # case insensitive + ("has Microsoft inside", "WSL"), # case insensitive + ("another value", "uname"), + ], + ids=[ + "/proc/version missing", + "/proc/version includes ms", + "/proc/version excludes Ms", + "another value", + ], +) +def test_set_operating_system(runner, paths, tst_sys, proc_value, expected_os): """Run set_operating_system and test result""" # Normally /proc/version (set in PROC_VERSION) is inspected to identify # WSL. During testing, we will override that value. - proc_version = paths.root.join('proc_version') - if proc_value != 'missing': + proc_version = paths.root.join("proc_version") + if proc_value != "missing": proc_version.write(proc_value) script = f""" YADM_TEST=1 source {paths.pgm} @@ -30,9 +32,9 @@ def test_set_operating_system( set_operating_system echo $OPERATING_SYSTEM """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - if expected_os == 'uname': + assert run.err == "" + if expected_os == "uname": expected_os = tst_sys assert run.out.rstrip() == expected_os diff --git a/test/test_unit_set_yadm_dir.py b/test/test_unit_set_yadm_dir.py index 32af8bf..b56c98d 100644 --- a/test/test_unit_set_yadm_dir.py +++ b/test/test_unit_set_yadm_dir.py @@ -3,25 +3,20 @@ import pytest @pytest.mark.parametrize( - 'condition', [ - 'basic', - 'override', - 'override_data', - 'xdg_config_home', - 'xdg_data_home' - ], - ) + "condition", + ["basic", "override", "override_data", "xdg_config_home", "xdg_data_home"], +) def test_set_yadm_dirs(runner, yadm, condition): """Test set_yadm_dirs""" - setup = '' - if condition == 'override': - setup = 'YADM_DIR=/override' - elif condition == 'override_data': - setup = 'YADM_DATA=/override' - elif condition == 'xdg_config_home': - setup = 'XDG_CONFIG_HOME=/xdg' - elif condition == 'xdg_data_home': - setup = 'XDG_DATA_HOME=/xdg' + setup = "" + if condition == "override": + setup = "YADM_DIR=/override" + elif condition == "override_data": + setup = "YADM_DATA=/override" + elif condition == "xdg_config_home": + setup = "XDG_CONFIG_HOME=/xdg" + elif condition == "xdg_data_home": + setup = "XDG_DATA_HOME=/xdg" script = f""" HOME=/testhome YADM_TEST=1 source {yadm} @@ -32,17 +27,17 @@ def test_set_yadm_dirs(runner, yadm, condition): echo "YADM_DIR=$YADM_DIR" echo "YADM_DATA=$YADM_DATA" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - if condition == 'basic': - assert 'YADM_DIR=/testhome/.config/yadm' in run.out - assert 'YADM_DATA=/testhome/.local/share/yadm' in run.out - elif condition == 'override': - assert 'YADM_DIR=/override' in run.out - elif condition == 'override_data': - assert 'YADM_DATA=/override' in run.out - elif condition == 'xdg_config_home': - assert 'YADM_DIR=/xdg/yadm' in run.out - elif condition == 'xdg_data_home': - assert 'YADM_DATA=/xdg/yadm' in run.out + assert run.err == "" + if condition == "basic": + assert "YADM_DIR=/testhome/.config/yadm" in run.out + assert "YADM_DATA=/testhome/.local/share/yadm" in run.out + elif condition == "override": + assert "YADM_DIR=/override" in run.out + elif condition == "override_data": + assert "YADM_DATA=/override" in run.out + elif condition == "xdg_config_home": + assert "YADM_DIR=/xdg/yadm" in run.out + elif condition == "xdg_data_home": + assert "YADM_DATA=/xdg/yadm" in run.out diff --git a/test/test_unit_template_default.py b/test/test_unit_template_default.py index 729784e..aaae0fe 100644 --- a/test/test_unit_template_default.py +++ b/test/test_unit_template_default.py @@ -1,4 +1,5 @@ """Unit tests: template_default""" + import os FILE_MODE = 0o754 @@ -12,7 +13,8 @@ LOCAL_HOST = "default_Test+@-!^Host" LOCAL_USER = "default_Test+@-!^User" LOCAL_DISTRO = "default_Test+@-!^Distro" LOCAL_DISTRO_FAMILY = "default_Test+@-!^Family" -TEMPLATE = f''' +ENV_VAR = "default_Test+@-!^Env" +TEMPLATE = f""" start of template default class = >{{{{yadm.class}}}}< default arch = >{{{{yadm.arch}}}}< @@ -30,6 +32,9 @@ Included section from else {{% if yadm.class == "wrongclass1" %}} wrong class 1 {{% endif %}} +{{% if yadm.class != "wronglcass" %}} +Included section from != +{{% endif\t\t %}} {{% if yadm.class == "{LOCAL_CLASS}" %}} Included section for class = {{{{yadm.class}}}} ({{{{yadm.class}}}} repeated) Multiple lines @@ -97,9 +102,16 @@ Included section for distro_family = \ {{% if yadm.distro_family == "wrongfamily2" %}} wrong family 2 {{% endif %}} +{{% if env.VAR == "{ENV_VAR}" %}} +Included section for env.VAR = {{{{env.VAR}}}} ({{{{env.VAR}}}} again) +{{% endif %}} +{{% if env.VAR == "wrongenvvar" %}} +wrong env.VAR +{{% endif %}} +yadm.no_such_var="{{{{ yadm.no_such_var }}}}" and env.NO_SUCH_VAR="{{{{ env.NO_SUCH_VAR }}}}" end of template -''' -EXPECTED = f''' +""" +EXPECTED = f""" start of template default class = >{LOCAL_CLASS}< default arch = >{LOCAL_ARCH}< @@ -111,6 +123,7 @@ default distro_family = >{LOCAL_DISTRO_FAMILY}< classes = >{LOCAL_CLASS2} {LOCAL_CLASS}< Included section from else +Included section from != Included section for class = {LOCAL_CLASS} ({LOCAL_CLASS} repeated) Multiple lines Included section for second class @@ -121,28 +134,30 @@ Included section for user = {LOCAL_USER} ({LOCAL_USER} repeated) Included section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again) Included section for distro_family = \ {LOCAL_DISTRO_FAMILY} ({LOCAL_DISTRO_FAMILY} again) +Included section for env.VAR = {ENV_VAR} ({ENV_VAR} again) +yadm.no_such_var="" and env.NO_SUCH_VAR="" end of template -''' +""" -INCLUDE_BASIC = 'basic\n' -INCLUDE_VARIABLES = '''\ +INCLUDE_BASIC = "basic\n" +INCLUDE_VARIABLES = """\ included <{{ yadm.class }}> file empty line above -''' -INCLUDE_NESTED = 'no newline at the end' +""" +INCLUDE_NESTED = "no newline at the end" -TEMPLATE_INCLUDE = '''\ +TEMPLATE_INCLUDE = """\ The first line {% include empty %} An empty file removes the line above {%include basic%} {% include "./variables.{{ yadm.os }}" %} -{% include dir/nested %} + {% include dir/nested %} Include basic again: {% include basic %} -''' -EXPECTED_INCLUDE = f'''\ +""" +EXPECTED_INCLUDE = f"""\ The first line An empty file removes the line above basic @@ -152,21 +167,57 @@ empty line above no newline at the end Include basic again: basic -''' +""" + +TEMPLATE_NESTED_IFS = """\ +{% if yadm.user == "me" %} + print1 + {% if yadm.user == "me" %} + print2 + {% else %} + no print1 + {% endif %} +{% else %} + {% if yadm.user == "me" %} + no print2 + {% else %} + no print3 + {% endif %} +{% endif %} +{% if yadm.user != "me" %} + no print4 + {% if yadm.user == "me" %} + no print5 + {% else %} + no print6 + {% endif %} +{% else %} + {% if yadm.user == "me" %} + print3 + {% else %} + no print7 + {% endif %} +{% endif %} +""" +EXPECTED_NESTED_IFS = """\ + print1 + print2 + print3 +""" def test_template_default(runner, yadm, tmpdir): """Test template_default""" - input_file = tmpdir.join('input') + input_file = tmpdir.join("input") input_file.write(TEMPLATE, ensure=True) input_file.chmod(FILE_MODE) - output_file = tmpdir.join('output') + output_file = tmpdir.join("output") # ensure overwrite works when file exists as read-only (there is some # special processing when this is encountered because some environments do # not properly overwrite read-only files) - output_file.write('existing') + output_file.write("existing") output_file.chmod(0o400) script = f""" @@ -182,9 +233,9 @@ def test_template_default(runner, yadm, tmpdir): local_distro_family="{LOCAL_DISTRO_FAMILY}" template_default "{input_file}" "{output_file}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script, env={"VAR": ENV_VAR}) assert run.success - assert run.err == '' + assert run.err == "" assert output_file.read() == EXPECTED assert os.stat(output_file).st_mode == os.stat(input_file).st_mode @@ -192,19 +243,19 @@ def test_template_default(runner, yadm, tmpdir): def test_source(runner, yadm, tmpdir): """Test yadm.source""" - input_file = tmpdir.join('input') - input_file.write('{{yadm.source}}', ensure=True) + input_file = tmpdir.join("input") + input_file.write("{{yadm.source}}", ensure=True) input_file.chmod(FILE_MODE) - output_file = tmpdir.join('output') + output_file = tmpdir.join("output") script = f""" YADM_TEST=1 source {yadm} set_awk template_default "{input_file}" "{output_file}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert output_file.read().strip() == str(input_file) assert os.stat(output_file).st_mode == os.stat(input_file).st_mode @@ -212,22 +263,22 @@ def test_source(runner, yadm, tmpdir): def test_include(runner, yadm, tmpdir): """Test include""" - empty_file = tmpdir.join('empty') - empty_file.write('', ensure=True) + empty_file = tmpdir.join("empty") + empty_file.write("", ensure=True) - basic_file = tmpdir.join('basic') + basic_file = tmpdir.join("basic") basic_file.write(INCLUDE_BASIC) - variables_file = tmpdir.join(f'variables.{LOCAL_SYSTEM}') + variables_file = tmpdir.join(f"variables.{LOCAL_SYSTEM}") variables_file.write(INCLUDE_VARIABLES) - nested_file = tmpdir.join('dir').join('nested') + nested_file = tmpdir.join("dir").join("nested") nested_file.write(INCLUDE_NESTED, ensure=True) - input_file = tmpdir.join('input') + input_file = tmpdir.join("input") input_file.write(TEMPLATE_INCLUDE) input_file.chmod(FILE_MODE) - output_file = tmpdir.join('output') + output_file = tmpdir.join("output") script = f""" YADM_TEST=1 source {yadm} @@ -236,27 +287,45 @@ def test_include(runner, yadm, tmpdir): local_system="{LOCAL_SYSTEM}" template_default "{input_file}" "{output_file}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert output_file.read() == EXPECTED_INCLUDE assert os.stat(output_file).st_mode == os.stat(input_file).st_mode +def test_nested_ifs(runner, yadm, tmpdir): + """Test nested if statements""" + + input_file = tmpdir.join("input") + input_file.write(TEMPLATE_NESTED_IFS, ensure=True) + output_file = tmpdir.join("output") + + script = f""" + YADM_TEST=1 source {yadm} + set_awk + local_user="me" + template_default "{input_file}" "{output_file}" + """ + run = runner(command=["bash"], inp=script) + assert run.success + assert run.err == "" + assert output_file.read() == EXPECTED_NESTED_IFS + + def test_env(runner, yadm, tmpdir): """Test env""" - input_file = tmpdir.join('input') - input_file.write('{{env.PWD}}', ensure=True) - input_file.chmod(FILE_MODE) - output_file = tmpdir.join('output') + input_file = tmpdir.join("input") + input_file.write("{{env.PWD}}", ensure=True) + output_file = tmpdir.join("output") script = f""" YADM_TEST=1 source {yadm} set_awk template_default "{input_file}" "{output_file}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - assert output_file.read().strip() == os.environ['PWD'] + assert run.err == "" + assert output_file.read().strip() == os.environ["PWD"] diff --git a/test/test_unit_template_esh.py b/test/test_unit_template_esh.py index 7f2f2b9..2c91c20 100644 --- a/test/test_unit_template_esh.py +++ b/test/test_unit_template_esh.py @@ -11,7 +11,7 @@ LOCAL_HOST = "esh_Test+@-!^Host" LOCAL_USER = "esh_Test+@-!^User" LOCAL_DISTRO = "esh_Test+@-!^Distro" LOCAL_DISTRO_FAMILY = "esh_Test+@-!^Family" -TEMPLATE = f''' +TEMPLATE = f""" start of template esh class = ><%=$YADM_CLASS%>< esh arch = ><%=$YADM_ARCH%>< @@ -90,8 +90,8 @@ Included esh section for distro_family = \ wrong family 2 <% fi -%> end of template -''' -EXPECTED = f''' +""" +EXPECTED = f""" start of template esh class = >{LOCAL_CLASS}< esh arch = >{LOCAL_ARCH}< @@ -111,21 +111,22 @@ Included esh section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again) Included esh section for distro_family = \ {LOCAL_DISTRO_FAMILY} ({LOCAL_DISTRO_FAMILY} again) end of template -''' +""" def test_template_esh(runner, yadm, tmpdir): """Test processing by esh""" + # pylint: disable=duplicate-code - input_file = tmpdir.join('input') + input_file = tmpdir.join("input") input_file.write(TEMPLATE, ensure=True) input_file.chmod(FILE_MODE) - output_file = tmpdir.join('output') + output_file = tmpdir.join("output") # ensure overwrite works when file exists as read-only (there is some # special processing when this is encountered because some environments do # not properly overwrite read-only files) - output_file.write('existing') + output_file.write("existing") output_file.chmod(0o400) script = f""" @@ -140,9 +141,9 @@ def test_template_esh(runner, yadm, tmpdir): local_distro_family="{LOCAL_DISTRO_FAMILY}" template_esh "{input_file}" "{output_file}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert output_file.read().strip() == str(EXPECTED).strip() assert os.stat(output_file).st_mode == os.stat(input_file).st_mode @@ -150,17 +151,17 @@ def test_template_esh(runner, yadm, tmpdir): def test_source(runner, yadm, tmpdir): """Test YADM_SOURCE""" - input_file = tmpdir.join('input') - input_file.write('<%= $YADM_SOURCE %>', ensure=True) + input_file = tmpdir.join("input") + input_file.write("<%= $YADM_SOURCE %>", ensure=True) input_file.chmod(FILE_MODE) - output_file = tmpdir.join('output') + output_file = tmpdir.join("output") script = f""" YADM_TEST=1 source {yadm} template_esh "{input_file}" "{output_file}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert output_file.read().strip() == str(input_file) assert os.stat(output_file).st_mode == os.stat(input_file).st_mode diff --git a/test/test_unit_template_j2.py b/test/test_unit_template_j2.py index 4042a2d..750ee8c 100644 --- a/test/test_unit_template_j2.py +++ b/test/test_unit_template_j2.py @@ -1,5 +1,6 @@ """Unit tests: template_j2cli & template_envtpl""" import os + import pytest FILE_MODE = 0o754 @@ -12,7 +13,7 @@ LOCAL_HOST = "j2_Test+@-!^Host" LOCAL_USER = "j2_Test+@-!^User" LOCAL_DISTRO = "j2_Test+@-!^Distro" LOCAL_DISTRO_FAMILY = "j2_Test+@-!^Family" -TEMPLATE = f''' +TEMPLATE = f""" start of template j2 class = >{{{{YADM_CLASS}}}}< j2 arch = >{{{{YADM_ARCH}}}}< @@ -93,8 +94,8 @@ Included j2 section for distro_family = \ wrong family 2 {{%- endif %}} end of template -''' -EXPECTED = f''' +""" +EXPECTED = f""" start of template j2 class = >{LOCAL_CLASS}< j2 arch = >{LOCAL_ARCH}< @@ -115,22 +116,23 @@ Included j2 section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again) Included j2 section for distro_family = \ {LOCAL_DISTRO_FAMILY} ({LOCAL_DISTRO_FAMILY} again) end of template -''' +""" -@pytest.mark.parametrize('processor', ('j2cli', 'envtpl')) +@pytest.mark.parametrize("processor", ("j2cli", "envtpl")) def test_template_j2(runner, yadm, tmpdir, processor): """Test processing by j2cli & envtpl""" + # pylint: disable=duplicate-code - input_file = tmpdir.join('input') + input_file = tmpdir.join("input") input_file.write(TEMPLATE, ensure=True) input_file.chmod(FILE_MODE) - output_file = tmpdir.join('output') + output_file = tmpdir.join("output") # ensure overwrite works when file exists as read-only (there is some # special processing when this is encountered because some environments do # not properly overwrite read-only files) - output_file.write('existing') + output_file.write("existing") output_file.chmod(0o400) script = f""" @@ -145,28 +147,28 @@ def test_template_j2(runner, yadm, tmpdir, processor): local_distro_family="{LOCAL_DISTRO_FAMILY}" template_{processor} "{input_file}" "{output_file}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert output_file.read() == EXPECTED assert os.stat(output_file).st_mode == os.stat(input_file).st_mode -@pytest.mark.parametrize('processor', ('j2cli', 'envtpl')) +@pytest.mark.parametrize("processor", ("j2cli", "envtpl")) def test_source(runner, yadm, tmpdir, processor): """Test YADM_SOURCE""" - input_file = tmpdir.join('input') - input_file.write('{{YADM_SOURCE}}', ensure=True) + input_file = tmpdir.join("input") + input_file.write("{{YADM_SOURCE}}", ensure=True) input_file.chmod(FILE_MODE) - output_file = tmpdir.join('output') + output_file = tmpdir.join("output") script = f""" YADM_TEST=1 source {yadm} template_{processor} "{input_file}" "{output_file}" """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' + assert run.err == "" assert output_file.read().strip() == str(input_file) assert os.stat(output_file).st_mode == os.stat(input_file).st_mode diff --git a/test/test_unit_upgrade.py b/test/test_unit_upgrade.py index 3463740..cf4f6f4 100644 --- a/test/test_unit_upgrade.py +++ b/test/test_unit_upgrade.py @@ -2,21 +2,21 @@ import pytest -@pytest.mark.parametrize('condition', ['override', 'equal', 'existing_repo']) +@pytest.mark.parametrize("condition", ["override", "equal", "existing_repo"]) def test_upgrade_errors(tmpdir, runner, yadm, condition): """Test upgrade() error conditions""" - home = tmpdir.mkdir('home') - yadm_dir = home.join('.config/yadm') - yadm_data = home.join('.local/share/yadm') - override = '' - if condition == 'override': - override = 'override' - if condition == 'equal': + home = tmpdir.mkdir("home") + yadm_dir = home.join(".config/yadm") + yadm_data = home.join(".local/share/yadm") + override = "" + if condition == "override": + override = "override" + if condition == "equal": yadm_data = yadm_dir - if condition == 'existing_repo': - yadm_dir.ensure_dir('repo.git') - yadm_data.ensure_dir('repo.git') + if condition == "existing_repo": + yadm_dir.ensure_dir("repo.git") + yadm_data.ensure_dir("repo.git") script = f""" YADM_TEST=1 source {yadm} @@ -27,17 +27,16 @@ def test_upgrade_errors(tmpdir, runner, yadm, condition): YADM_OVERRIDE_REPO="{override}" upgrade """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.failure - assert 'Unable to upgrade' in run.err - if condition in ['override', 'equal']: - assert 'Paths have been overridden' in run.err - elif condition == 'existing_repo': - assert 'already exists' in run.err + assert "Unable to upgrade" in run.err + if condition in ["override", "equal"]: + assert "Paths have been overridden" in run.err + elif condition == "existing_repo": + assert "already exists" in run.err -@pytest.mark.parametrize( - 'condition', ['no-paths', 'untracked', 'tracked', 'submodules']) +@pytest.mark.parametrize("condition", ["no-paths", "untracked", "tracked", "submodules"]) def test_upgrade(tmpdir, runner, yadm, condition): """Test upgrade() @@ -45,21 +44,21 @@ def test_upgrade(tmpdir, runner, yadm, condition): mock for git. echo will return true, simulating a positive result from "git ls-files". Also echo will report the parameters for "git mv". """ - legacy_paths = ('config', 'encrypt', 'bootstrap', 'hooks/pre_cmd') - home = tmpdir.mkdir('home') - yadm_dir = home.join('.config/yadm') - yadm_data = home.join('.local/share/yadm') - yadm_legacy = home.join('.yadm') + legacy_paths = ("config", "encrypt", "bootstrap", "hooks/pre_cmd") + home = tmpdir.mkdir("home") + yadm_dir = home.join(".config/yadm") + yadm_data = home.join(".local/share/yadm") + yadm_legacy = home.join(".yadm") - if condition != 'no-paths': - yadm_dir.join('repo.git/config').write('test-repo', ensure=True) - yadm_dir.join('files.gpg').write('files.gpg', ensure=True) + if condition != "no-paths": + yadm_dir.join("repo.git/config").write("test-repo", ensure=True) + yadm_dir.join("files.gpg").write("files.gpg", ensure=True) for path in legacy_paths: yadm_legacy.join(path).write(path, ensure=True) mock_git = "" - if condition != 'no-paths': - mock_git = f''' + if condition != "no-paths": + mock_git = f""" function git() {{ echo "$@" if [[ "$*" = *"submodule status" ]]; then @@ -71,7 +70,7 @@ def test_upgrade(tmpdir, runner, yadm, condition): fi return 0 }} - ''' + """ script = f""" YADM_TEST=1 source {yadm} @@ -85,38 +84,30 @@ def test_upgrade(tmpdir, runner, yadm, condition): function cd {{ echo "$@";}} upgrade """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success - assert run.err == '' - if condition == 'no-paths': - assert 'Upgrade is not necessary' in run.out + assert run.err == "" + if condition == "no-paths": + assert "Upgrade is not necessary" in run.out else: - for (lpath, npath) in [ - ('repo.git', 'repo.git'), ('files.gpg', 'archive')]: - expected = ( - f'Moving {yadm_dir.join(lpath)} ' - f'to {yadm_data.join(npath)}') + for lpath, npath in [("repo.git", "repo.git"), ("files.gpg", "archive")]: + expected = f"Moving {yadm_dir.join(lpath)} " f"to {yadm_data.join(npath)}" assert expected in run.out for path in legacy_paths: - expected = ( - f'Moving {yadm_legacy.join(path)} ' - f'to {yadm_dir.join(path)}') + expected = f"Moving {yadm_legacy.join(path)} " f"to {yadm_dir.join(path)}" assert expected in run.out - if condition == 'untracked': - assert 'test-repo' in yadm_data.join('repo.git/config').read() - assert 'files.gpg' in yadm_data.join('archive').read() + if condition == "untracked": + assert "test-repo" in yadm_data.join("repo.git/config").read() + assert "files.gpg" in yadm_data.join("archive").read() for path in legacy_paths: assert path in yadm_dir.join(path).read() - elif condition in ['tracked', 'submodules']: - expected = ( - f'mv {yadm_dir.join("files.gpg")} ' - f'{yadm_data.join("archive")}') + elif condition in ["tracked", "submodules"]: + expected = f'mv {yadm_dir.join("files.gpg")} ' f'{yadm_data.join("archive")}' assert expected in run.out - assert 'files tracked by yadm have been renamed' in run.out - if condition == 'submodules': - assert 'submodule deinit -- mymodule' in run.out - assert 'submodule update --init --recursive -- mymodule' \ - in run.out + assert "files tracked by yadm have been renamed" in run.out + if condition == "submodules": + assert "submodule deinit -- mymodule" in run.out + assert "submodule update --init --recursive -- mymodule" in run.out else: - assert 'submodule deinit' not in run.out - assert 'submodule update --init --recursive' not in run.out + assert "submodule deinit" not in run.out + assert "submodule update --init --recursive" not in run.out diff --git a/test/test_unit_x_program.py b/test/test_unit_x_program.py index 8302f3c..33b8473 100644 --- a/test/test_unit_x_program.py +++ b/test/test_unit_x_program.py @@ -1,28 +1,30 @@ """Unit tests: yadm.[git,gpg]-program""" import os + import pytest @pytest.mark.parametrize( - 'executable, success, value, match', [ - (None, True, 'program', None), - ('cat', True, 'cat', None), - ('badprogram', False, None, 'badprogram'), - ], ids=[ - 'executable missing', - 'valid alternative', - 'invalid alternative', - ]) -@pytest.mark.parametrize('program', ['git', 'gpg']) -def test_x_program( - runner, yadm_cmd, paths, program, executable, success, value, match): + "executable, success, value, match", + [ + (None, True, "program", None), + ("cat", True, "cat", None), + ("badprogram", False, None, "badprogram"), + ], + ids=[ + "executable missing", + "valid alternative", + "invalid alternative", + ], +) +@pytest.mark.parametrize("program", ["git", "gpg"]) +def test_x_program(runner, yadm_cmd, paths, program, executable, success, value, match): """Set yadm.X-program, and test result of require_X""" # set configuration if executable: - os.system(' '.join(yadm_cmd( - 'config', f'yadm.{program}-program', executable))) + os.system(" ".join(yadm_cmd("config", f"yadm.{program}-program", executable))) # test require_[git,gpg] script = f""" @@ -32,11 +34,11 @@ def test_x_program( require_{program} echo ${program.upper()}_PROGRAM """ - run = runner(command=['bash'], inp=script) + run = runner(command=["bash"], inp=script) assert run.success == success # [GIT,GPG]_PROGRAM set correctly - if value == 'program': + if value == "program": assert run.out.rstrip() == program elif value: assert run.out.rstrip() == value @@ -45,4 +47,4 @@ def test_x_program( if match: assert match in run.err else: - assert run.err == '' + assert run.err == "" diff --git a/test/test_upgrade.py b/test/test_upgrade.py index 2d4d697..8ab1e94 100644 --- a/test/test_upgrade.py +++ b/test/test_upgrade.py @@ -1,129 +1,131 @@ """Test upgrade""" import os + import pytest @pytest.mark.parametrize( - 'versions', [ - ('1.12.0', '2.5.0'), - ('1.12.0',), - ('2.5.0',), - ], ids=[ - '1.12.0 -> 2.5.0 -> latest', - '1.12.0 -> latest', - '2.5.0 -> latest', - ]) -@pytest.mark.parametrize( - 'submodule', [False, True], - ids=['no submodule', 'with submodules']) + "versions", + [ + ("1.12.0", "2.5.0"), + ("1.12.0",), + ("2.5.0",), + ], + ids=[ + "1.12.0 -> 2.5.0 -> latest", + "1.12.0 -> latest", + "2.5.0 -> latest", + ], +) +@pytest.mark.parametrize("submodule", [False, True], ids=["no submodule", "with submodules"]) def test_upgrade(tmpdir, runner, versions, submodule): """Upgrade tests""" # pylint: disable=too-many-statements - home = tmpdir.mkdir('HOME') - env = {'HOME': str(home)} + home = tmpdir.mkdir("HOME") + env = {"HOME": str(home)} + runner(["git", "config", "--global", "init.defaultBranch", "master"], env=env) + runner(["git", "config", "--global", "protocol.file.allow", "always"], env=env) if submodule: - ext_repo = tmpdir.mkdir('ext_repo') - ext_repo.join('afile').write('some data') + ext_repo = tmpdir.mkdir("ext_repo") + ext_repo.join("afile").write("some data") - for cmd in (('init',), ('add', 'afile'), ('commit', '-m', 'test')): - run = runner(['git', '-C', str(ext_repo), *cmd]) + for cmd in (("init",), ("add", "afile"), ("commit", "-m", "test")): + run = runner(["git", "-C", str(ext_repo), *cmd]) assert run.success - os.environ.pop('XDG_CONFIG_HOME', None) - os.environ.pop('XDG_DATA_HOME', None) + os.environ.pop("XDG_CONFIG_HOME", None) + os.environ.pop("XDG_DATA_HOME", None) def run_version(version, *args, check_stderr=True): - yadm = 'yadm-%s' % version if version else '/yadm/yadm' + yadm = f"yadm-{version}" if version else "/yadm/yadm" run = runner([yadm, *args], shell=True, cwd=str(home), env=env) assert run.success if check_stderr: - assert run.err == '' + assert run.err == "" return run # Initialize the repo with the first version first = versions[0] - run_version(first, 'init') + run_version(first, "init") - home.join('file').write('some data') - run_version(first, 'add', 'file') - run_version(first, 'commit', '-m', '"First commit"') + home.join("file").write("some data") + run_version(first, "add", "file") + run_version(first, "commit", "-m", '"First commit"') if submodule: # When upgrading via 2.5.0 we can't have a submodule that's been added # after being cloned as 2.5.0 fails the upgrade in that case. - can_upgrade_cloned_submodule = '2.5.0' not in versions[1:] + can_upgrade_cloned_submodule = "2.5.0" not in versions[1:] if can_upgrade_cloned_submodule: # Check out a repo and then add it as a submodule - run = runner(['git', '-C', str(home), 'clone', str(ext_repo), 'b']) + run = runner(["git", "-C", str(home), "clone", str(ext_repo), "b"]) assert run.success - run_version(first, 'submodule', 'add', str(ext_repo), 'b') + run_version(first, "submodule", "add", str(ext_repo), "b") # Add submodule without first checking it out - run_version(first, 'submodule', 'add', str(ext_repo), 'a', - check_stderr=False) - run_version(first, 'submodule', 'add', str(ext_repo), 'c', - check_stderr=False) + run_version(first, "submodule", "add", str(ext_repo), "a", check_stderr=False) + run_version(first, "submodule", "add", str(ext_repo), "c", check_stderr=False) - run_version(first, 'commit', '-m', '"Add submodules"') + run_version(first, "commit", "-m", '"Add submodules"') - for path in ('.yadm', '.config/yadm'): + for path in (".yadm", ".config/yadm"): yadm_dir = home.join(path) if yadm_dir.exists(): break - yadm_dir.join('bootstrap').write('init stuff') - run_version(first, 'add', yadm_dir.join('bootstrap')) - run_version(first, 'commit', '-m', 'bootstrap') + yadm_dir.join("bootstrap").write("init stuff") + run_version(first, "add", yadm_dir.join("bootstrap")) + run_version(first, "commit", "-m", "bootstrap") - yadm_dir.join('encrypt').write('secret') + yadm_dir.join("encrypt").write("secret") - hooks_dir = yadm_dir.mkdir('hooks') - hooks_dir.join('pre_status').write('status') - hooks_dir.join('post_commit').write('commit') + hooks_dir = yadm_dir.mkdir("hooks") + hooks_dir.join("pre_status").write("status") + hooks_dir.join("post_commit").write("commit") - run_version(first, 'config', 'local.class', 'test') - run_version(first, 'config', 'foo.bar', 'true') + run_version(first, "config", "local.class", "test") + run_version(first, "config", "foo.bar", "true") # Run upgrade with intermediate versions and latest latest = None for version in versions[1:] + (latest,): - run = run_version(version, 'upgrade', check_stderr=not submodule) + run = run_version(version, "upgrade", check_stderr=not submodule) if submodule: lines = run.err.splitlines() if can_upgrade_cloned_submodule: - assert 'Migrating git directory of' in lines[0] - assert str(home.join('b/.git')) in lines[1] - assert str(yadm_dir.join('repo.git/modules/b')) in lines[2] + assert "Migrating git directory of" in lines[0] + assert str(home.join("b/.git")) in lines[1] + assert str(yadm_dir.join("repo.git/modules/b")) in lines[2] del lines[:3] for line in lines: - assert line.startswith('Submodule') - assert 'registered for path' in line + assert line.startswith("Submodule") + assert "registered for path" in line # Verify result for the final upgrade - run_version(latest, 'status') + run_version(latest, "status") - run = run_version(latest, 'show', 'HEAD:file') - assert run.out == 'some data' + run = run_version(latest, "show", "HEAD:file") + assert run.out == "some data" if submodule: if can_upgrade_cloned_submodule: - assert home.join('b/afile').read() == 'some data' - assert home.join('a/afile').read() == 'some data' - assert home.join('c/afile').read() == 'some data' + assert home.join("b/afile").read() == "some data" + assert home.join("a/afile").read() == "some data" + assert home.join("c/afile").read() == "some data" - yadm_dir = home.join('.config/yadm') + yadm_dir = home.join(".config/yadm") - assert yadm_dir.join('bootstrap').read() == 'init stuff' - assert yadm_dir.join('encrypt').read() == 'secret' + assert yadm_dir.join("bootstrap").read() == "init stuff" + assert yadm_dir.join("encrypt").read() == "secret" - hooks_dir = yadm_dir.join('hooks') - assert hooks_dir.join('pre_status').read() == 'status' - assert hooks_dir.join('post_commit').read() == 'commit' + hooks_dir = yadm_dir.join("hooks") + assert hooks_dir.join("pre_status").read() == "status" + assert hooks_dir.join("post_commit").read() == "commit" - run = run_version(latest, 'config', 'local.class') - assert run.out.rstrip() == 'test' + run = run_version(latest, "config", "local.class") + assert run.out.rstrip() == "test" - run = run_version(latest, 'config', 'foo.bar') - assert run.out.rstrip() == 'true' + run = run_version(latest, "config", "foo.bar") + assert run.out.rstrip() == "true" diff --git a/test/test_version.py b/test/test_version.py index d440d3b..52b18ab 100644 --- a/test/test_version.py +++ b/test/test_version.py @@ -1,38 +1,36 @@ """Test version""" import re + import pytest -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def expected_version(yadm): """ Expected semantic version number. This is taken directly out of yadm, searching for the VERSION= string. """ - yadm_version = re.findall( - r'VERSION=([^\n]+)', - open(yadm).read()) + with open(yadm, encoding="utf-8") as source_file: + yadm_version = re.findall(r"VERSION=([^\n]+)", source_file.read()) if yadm_version: return yadm_version[0] - pytest.fail(f'version not found in {yadm}') - return 'not found' + pytest.fail(f"version not found in {yadm}") + return "not found" def test_semantic_version(expected_version): """Version is semantic""" # semantic version conforms to MAJOR.MINOR.PATCH - assert re.search(r'^\d+\.\d+\.\d+$', expected_version), ( - 'does not conform to MAJOR.MINOR.PATCH') + assert re.search(r"^\d+\.\d+\.\d+$", expected_version), "does not conform to MAJOR.MINOR.PATCH" -@pytest.mark.parametrize('cmd', ['--version', 'version']) -def test_reported_version( - runner, yadm_cmd, cmd, expected_version): +@pytest.mark.parametrize("cmd", ["--version", "version"]) +def test_reported_version(runner, yadm_cmd, cmd, expected_version): """Report correct version and bash/git versions""" run = runner(command=yadm_cmd(cmd)) assert run.success - assert run.err == '' - assert 'bash version' in run.out - assert 'git version' in run.out - assert run.out.endswith(f'\nyadm version {expected_version}\n') + assert run.err == "" + assert "bash version" in run.out + assert "git version" in run.out + assert run.out.endswith(f"\nyadm version {expected_version}\n") diff --git a/test/utils.py b/test/utils.py index 67a9e53..c36ecac 100644 --- a/test/utils.py +++ b/test/utils.py @@ -3,38 +3,42 @@ This module holds values/functions common to multiple tests. """ -import re import os +import re -ALT_FILE1 = 'test_alt' -ALT_FILE2 = 'test alt/test alt' -ALT_DIR = 'test alt/test alt dir' +ALT_FILE1 = "test_alt" +ALT_FILE2 = "test alt/test alt" +ALT_DIR = "test alt/test alt dir" # Directory based alternates must have a tracked contained file. # This will be the test contained file name -CONTAINED = 'contained_file' +CONTAINED = "contained_file" # These variables are used for making include files which will be processed # within jinja templates -INCLUDE_FILE = 'inc_file' -INCLUDE_DIRS = ['', 'test alt'] -INCLUDE_CONTENT = '8780846c02e34c930d0afd127906668f' +INCLUDE_FILE = "inc_file" +INCLUDE_DIRS = ["", "test alt"] +INCLUDE_CONTENT = "8780846c02e34c930d0afd127906668f" def set_local(paths, variable, value, add=False): """Set local override""" add = "--add" if add else "" - os.system( - f'GIT_DIR={str(paths.repo)} ' - f'git config --local {add} "local.{variable}" "{value}"' - ) + os.system(f"GIT_DIR={str(paths.repo)} " f'git config --local {add} "local.{variable}" "{value}"') -def create_alt_files(paths, suffix, - preserve=False, tracked=True, - encrypt=False, exclude=False, - content=None, includefile=False, - yadm_alt=False, yadm_dir=None): +def create_alt_files( + paths, + suffix, + preserve=False, + tracked=True, + encrypt=False, + exclude=False, + content=None, + includefile=False, + yadm_alt=False, + yadm_dir=None, +): """Create new files, and add to the repo This is used for testing alternate files. In each case, a suffix is @@ -42,7 +46,7 @@ def create_alt_files(paths, suffix, repo handling are dependent upon the function arguments. """ - basepath = yadm_dir.join('alt') if yadm_alt else paths.work + basepath = yadm_dir.join("alt") if yadm_alt else paths.work if not preserve: for remove_path in (ALT_FILE1, ALT_FILE2, ALT_DIR): @@ -60,28 +64,28 @@ def create_alt_files(paths, suffix, # Do not test directory support for jinja alternates test_paths = [new_file1, new_file2] test_names = [ALT_FILE1, ALT_FILE2] - if not re.match(r'##(t$|t\.|template|yadm)', suffix): + if not re.match(r"##(t$|t\.|template|yadm)", suffix): test_paths += [new_dir] test_names += [ALT_DIR] for test_path in test_paths: if content: - test_path.write('\n' + content, mode='a', ensure=True) + test_path.write("\n" + content, mode="a", ensure=True) assert test_path.exists() _create_includefiles(includefile, test_paths, basepath) _create_tracked(tracked, test_paths, paths) - prefix = '.config/yadm/alt/' if yadm_alt else '' + prefix = ".config/yadm/alt/" if yadm_alt else "" _create_encrypt(encrypt, test_names, suffix, paths, exclude, prefix) def parse_alt_output(output, linked=True): """Parse output of 'alt', and return list of linked files""" - regex = r'Creating (.+) from template (.+)$' + regex = r"Creating (.+) from template (.+)$" if linked: - regex = r'Linking (.+) to (.+)$' - parsed_list = dict() + regex = r"Linking (.+) to (.+)$" + parsed_list = {} for line in output.splitlines(): match = re.match(regex, line) if match: @@ -95,7 +99,7 @@ def parse_alt_output(output, linked=True): def _create_includefiles(includefile, test_paths, basepath): if includefile: for dpath in INCLUDE_DIRS: - incfile = basepath.join(dpath + '/' + INCLUDE_FILE) + incfile = basepath.join(dpath + "/" + INCLUDE_FILE) incfile.write(INCLUDE_CONTENT, ensure=True) test_paths += [incfile] @@ -110,8 +114,6 @@ def _create_tracked(tracked, test_paths, paths): def _create_encrypt(encrypt, test_names, suffix, paths, exclude, prefix): if encrypt: for encrypt_name in test_names: - paths.encrypt.write( - f'{prefix + encrypt_name + suffix}\n', mode='a') + paths.encrypt.write(f"{prefix + encrypt_name + suffix}\n", mode="a") if exclude: - paths.encrypt.write( - f'!{prefix + encrypt_name + suffix}\n', mode='a') + paths.encrypt.write(f"!{prefix + encrypt_name + suffix}\n", mode="a") diff --git a/yadm b/yadm index 003d954..547121b 100755 --- a/yadm +++ b/yadm @@ -1,6 +1,6 @@ #!/bin/sh # yadm - Yet Another Dotfiles Manager -# Copyright (C) 2015-2023 Tim Byrne +# Copyright (C) 2015-2024 Tim Byrne # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,7 +21,7 @@ if [ -z "$BASH_VERSION" ]; then [ "$YADM_TEST" != 1 ] && exec bash "$0" "$@" fi -VERSION=3.2.2 +VERSION=3.3.0 YADM_WORK="$HOME" YADM_DIR= @@ -289,18 +289,18 @@ function record_score() { # increase the index of any existing alt_template_cmds new_cmds=() for cmd_index in "${!alt_template_cmds[@]}"; do - new_cmds[$((cmd_index+1))]="${alt_template_cmds[$cmd_index]}" + new_cmds[cmd_index+1]="${alt_template_cmds[$cmd_index]}" done alt_template_cmds=() for cmd_index in "${!new_cmds[@]}"; do - alt_template_cmds[$cmd_index]="${new_cmds[$cmd_index]}" + alt_template_cmds[cmd_index]="${new_cmds[$cmd_index]}" done else alt_targets+=("$tgt") # set index to the last index (newly created one) for index in "${!alt_targets[@]}"; do :; done # and set its initial score to zero - alt_scores[$index]=0 + alt_scores[index]=0 fi fi @@ -309,8 +309,8 @@ function record_score() { # record higher scoring sources if [ "$score" -gt "${alt_scores[$index]}" ]; then - alt_scores[$index]="$score" - alt_sources[$index]="$src" + alt_scores[index]="$score" + alt_sources[index]="$src" fi } @@ -336,8 +336,8 @@ function record_template() { fi # record the template command, last one wins - alt_template_cmds[$index]="$cmd" - alt_sources[$index]="$src" + alt_template_cmds[index]="$cmd" + alt_sources[index]="$src" } @@ -368,87 +368,114 @@ function template_default() { # the explicit "space + tab" character class used below is used because not # all versions of awk seem to support the POSIX character classes [[:blank:]] read -r -d '' awk_pgm << "EOF" -# built-in default template processor BEGIN { - blank = "[ ]" - c["class"] = class - c["classes"] = classes - c["arch"] = arch - c["os"] = os - c["hostname"] = host - c["user"] = user - c["distro"] = distro - c["distro_family"] = distro_family - c["source"] = source - ifs = "^{%" blank "*if" - els = "^{%" blank "*else" blank "*%}$" - end = "^{%" blank "*endif" blank "*%}$" - skp = "^{%" blank "*(if|else|endif)" - vld = conditions() - inc_start = "^{%" blank "*include" blank "+\"?" - inc_end = "\"?" blank "*%}$" - inc = inc_start ".+" inc_end - prt = 1 - err = 0 -} -END { exit err } -{ replace_vars() } # variable replacements -$0 ~ vld, $0 ~ end { - if ($0 ~ vld || $0 ~ end) prt=1; - if ($0 ~ els) prt=0; - if ($0 ~ skp) next; -} -($0 ~ ifs && $0 !~ vld), $0 ~ end { - if ($0 ~ ifs && $0 !~ vld) prt=0; - if ($0 ~ els || $0 ~ end) prt=1; - if ($0 ~ skp) next; -} -{ if (!prt) next } -$0 ~ inc { - file = $0 - sub(inc_start, "", file) - sub(inc_end, "", file) - sub(/^[^\/].*$/, source_dir "/&", file) + classes = ARGV[2] + for (i = 3; i < ARGC; ++i) { + classes = classes "\n" ARGV[i] + } + yadm["class"] = class + yadm["classes"] = classes + yadm["arch"] = arch + yadm["os"] = os + yadm["hostname"] = host + yadm["user"] = user + yadm["distro"] = distro + yadm["distro_family"] = distro_family + yadm["source"] = source - while ((res = getline 0) { - replace_vars() - print + VARIABLE = "(env|yadm)\\.[a-zA-Z0-9_]+" + + current = 0 + filename[current] = ARGV[1] + line[current] = 0 + + level = 0 + skip[level] = 0 + + for (; current >= 0; --current) { + while ((res = getline 0) { + ++line[current] + if ($0 ~ "^[ \t]*\\{%[ \t]*if[ \t]+" VARIABLE "[ \t]*[!=]=[ \t]*\".*\"[ \t]*%\\}$") { + if (skip[level]) { skip[++level] = 1; continue } + + match($0, VARIABLE) + lhs = substr($0, RSTART, RLENGTH) + match($0, /[!=]=/) + op = substr($0, RSTART, RLENGTH) + match($0, /".*"/) + rhs = replace_vars(substr($0, RSTART + 1, RLENGTH - 2)) + + if (lhs == "yadm.class") { + lhs = "not" rhs + split(classes, cls_array, "\n") + for (idx in cls_array) { + if (rhs == cls_array[idx]) { lhs = rhs; break } + } + } + else { + lhs = replace_vars("{{" lhs "}}") + } + + if (op == "==") { skip[++level] = lhs != rhs } + else { skip[++level] = lhs == rhs } + } + else if (/^[ \t]*\{%[ \t]*else[ \t]*%\}$/) { + if (level == 0 || skip[level] < 0) { error("else without matching if") } + skip[level] = skip[level] ? skip[level - 1] : -1 + } + else if (/^[ \t]*\{%[ \t]*endif[ \t]*%\}$/) { + if (--level < 0) { error("endif without matching if") } + } + else if (!skip[level]) { + $0 = replace_vars($0) + if (match($0, /^[ \t]*\{%[ \t]*include[ \t]+("[^"]+"|[^"]+)[ \t]*%\}$/)) { + include = $0 + sub(/^[ \t]*\{%[ \t]*include[ \t]+"?/, "", include) + sub(/"?[ \t]*%\}$/, "", include) + if (index(include, "/") != 1) { + include = source_dir "/" include + } + filename[++current] = include + line[current] = 0 + } + else { print } + } + } + if (res >= 0) { close(filename[current]) } + else if (current == 0) { error("could not read input file") } + else { --current; error("could not read include file '" filename[current + 1] "'") } } - if (res < 0) { - printf "%s:%d: error: could not read '%s'\n", FILENAME, NR, file | "cat 1>&2" - err = 1 + if (level > 0) { + current = 0 + error("unterminated if") } - close(file) - next + exit 0 } -{ print } -function replace_vars() { - for (label in c) { - gsub(("{{" blank "*yadm\\." label blank "*}}"), c[label]) - } - for (label in ENVIRON) { - gsub(("{{" blank "*env\\." label blank "*}}"), ENVIRON[label]) - } +function error(text) { + printf "%s:%d: error: %s\n", + filename[current], line[current], text > "/dev/stderr" + exit 1 } -function condition_helper(label, value) { - gsub(/[\\.^$(){}\[\]|*+?]/, "\\\\&", value) - return sprintf("yadm\\.%s" blank "*==" blank "*\"%s\"", label, value) -} -function conditions() { - pattern = ifs blank "+(" - for (label in c) { - if (label != "class") { - value = c[label] - pattern = sprintf("%s%s|", pattern, condition_helper(label, value)); +function replace_vars(input) { + output = "" + while (match(input, "\\{\\{[ \t]*" VARIABLE "[ \t]*\\}\\}")) { + if (RSTART > 1) { + output = output substr(input, 0, RSTART - 1) + } + data = substr(input, RSTART + 2, RLENGTH - 4) + input = substr(input, RSTART + RLENGTH) + + gsub(/[ \t]+/, "", data) + split(data, fields, /\./) + + if (fields[1] == "env") { + output = output ENVIRON[fields[2]] + } + else { + output = output yadm[fields[2]] } } - split(classes, cls_array, "\n") - for (idx in cls_array) { - value = cls_array[idx] - pattern = sprintf("%s%s|", pattern, condition_helper("class", value)); - } - sub(/\|$/, ")" blank "*%}$", pattern) - return pattern + return output input } EOF @@ -462,9 +489,8 @@ EOF -v distro_family="$local_distro_family" \ -v source="$input" \ -v source_dir="$(dirname "$input")" \ - -v classes="$(join_string $'\n' "${local_classes[@]}")" \ "$awk_pgm" \ - "$input" > "$temp_file" || rm -f "$temp_file" + "$input" "${local_classes[@]}" > "$temp_file" || rm -f "$temp_file" move_file "$input" "$output" "$temp_file" } diff --git a/yadm.1 b/yadm.1 index 6a639be..5dd08fd 100644 --- a/yadm.1 +++ b/yadm.1 @@ -1,5 +1,5 @@ .\" vim: set spell so=8: -.TH yadm 1 "23 January 2023" "3.2.2" +.TH yadm 1 "8 November 2024" "3.3.0" .SH NAME diff --git a/yadm.md b/yadm.md index db607c2..41cf531 100644 --- a/yadm.md +++ b/yadm.md @@ -1,6 +1,4 @@ - - ## NAME yadm - Yet Another Dotfiles Manager @@ -56,17 +54,17 @@ aged repository. The working directory for Git commands will be the configured work-tree (usually $HOME). - Dotfiles are managed by using standard git commands; add, com‐ + Dotfiles are managed by using standard git commands; add, com‐ mit, push, pull, etc. - The config command is not passed directly through. Instead use + The config command is not passed directly through. Instead use the gitconfig command (see below). - alt Create symbolic links and process templates for any managed - files matching the naming rules described in the ALTERNATES and - TEMPLATES sections. It is usually unnecessary to run this com‐ - mand, as yadm automatically processes alternates by default. - This automatic behavior can be disabled by setting the configu‐ + alt Create symbolic links and process templates for any managed + files matching the naming rules described in the ALTERNATES and + TEMPLATES sections. It is usually unnecessary to run this com‐ + mand, as yadm automatically processes alternates by default. + This automatic behavior can be disabled by setting the configu‐ ration yadm.auto-alt to "false". bootstrap @@ -82,12 +80,12 @@ The repository is stored in $HOME/.local/share/yadm/repo.git. By default, $HOME will be used as the work-tree, but this can be - overridden with the -w option. yadm can be forced to overwrite - an existing repository by providing the -f option. If you want - to use a branch other than the remote HEAD branch you can spec‐ - ify it using the -b option. By default yadm will ask the user - if the bootstrap program should be run (if it exists). The op‐ - tions --bootstrap or --no-bootstrap will either force the boot‐ + overridden with the -w option. yadm can be forced to overwrite + an existing repository by providing the -f option. If you want + to use a branch other than the remote HEAD branch you can spec‐ + ify it using the -b option. By default yadm will ask the user + if the bootstrap program should be run (if it exists). The op‐ + tions --bootstrap or --no-bootstrap will either force the boot‐ strap to be run, or prevent it from being run, without prompting the user. @@ -96,8 +94,8 @@ section for more details. decrypt - Decrypt all files stored in $HOME/.local/share/yadm/archive. - Files decrypted will be relative to the configured work-tree + Decrypt all files stored in $HOME/.local/share/yadm/archive. + Files decrypted will be relative to the configured work-tree (usually $HOME). Using the -l option will list the files stored without extracting them. @@ -135,28 +133,28 @@ use the yadm repository. git-crypt enables transparent encryption and decryption of files - in a git repository. You can read https://github.com/AGWA/git- + in a git repository. You can read https://github.com/AGWA/git- crypt for details. gitconfig - Pass options to the git config command. Since yadm already uses - the config command to manage its own configurations, this com‐ + Pass options to the git config command. Since yadm already uses + the config command to manage its own configurations, this com‐ mand is provided as a way to change configurations of the repos‐ itory managed by yadm. One useful case might be to configure the repository so untracked files are shown in status commands. yadm initially configures its repository so that untracked files - are not shown. If you wish use the default Git behavior (to - show untracked files and directories), you can remove this con‐ + are not shown. If you wish use the default Git behavior (to + show untracked files and directories), you can remove this con‐ figuration. yadm gitconfig --unset status.showUntrackedFiles help Print a summary of yadm commands. - init Initialize a new, empty repository for tracking dotfiles. The - repository is stored in $HOME/.local/share/yadm/repo.git. By - default, $HOME will be used as the work-tree, but this can be - overridden with the -w option. yadm can be forced to overwrite + init Initialize a new, empty repository for tracking dotfiles. The + repository is stored in $HOME/.local/share/yadm/repo.git. By + default, $HOME will be used as the work-tree, but this can be + overridden with the -w option. yadm can be forced to overwrite an existing repository by providing the -f option. list Print a list of files managed by yadm. The -a option will cause @@ -168,14 +166,14 @@ configs, repo, and switches. The purpose of introspection is to support command line completion. - perms Update permissions as described in the PERMISSIONS section. It - is usually unnecessary to run this command, as yadm automati‐ - cally processes permissions by default. This automatic behavior - can be disabled by setting the configuration yadm.auto-perms to + perms Update permissions as described in the PERMISSIONS section. It + is usually unnecessary to run this command, as yadm automati‐ + cally processes permissions by default. This automatic behavior + can be disabled by setting the configuration yadm.auto-perms to "false". transcrypt options - If transcrypt is installed, this command allows you to pass op‐ + If transcrypt is installed, this command allows you to pass op‐ tions directly to transcrypt, with the environment configured to use the yadm repository. @@ -187,16 +185,16 @@ Version 3 of yadm uses a different directory for storing data. When you start to use version 3 for the first time, you may see warnings about moving your data to this new directory. The eas‐ - iest way to accomplish this is by running "yadm upgrade". This - command will start by moving your yadm repo to the new path. - Next it will move any archive data. If the archive is tracked + iest way to accomplish this is by running "yadm upgrade". This + command will start by moving your yadm repo to the new path. + Next it will move any archive data. If the archive is tracked within your yadm repo, this command will "stage" the renaming of that file in the repo's index. Upgrading will attempt to de-initialize and re-initialize your submodules. If your submodules cannot be de-initialized, the up‐ - grade will fail. The most common reason submodules will fail to - de-initialize is because they have local modifications. If you + grade will fail. The most common reason submodules will fail to + de-initialize is because they have local modifications. If you are willing to lose the local modifications to those submodules, you can use the -f option with the "upgrade" command to force the de-initialization. @@ -213,20 +211,20 @@ ## OPTIONS - yadm supports a set of universal options that alter the paths it uses. - The default paths are documented in the FILES section. Any path speci‐ - fied by these options must be fully qualified. If you always want to - override one or more of these paths, it may be useful to create an - alias for the yadm command. For example, the following alias could be + yadm supports a set of universal options that alter the paths it uses. + The default paths are documented in the FILES section. Any path speci‐ + fied by these options must be fully qualified. If you always want to + override one or more of these paths, it may be useful to create an + alias for the yadm command. For example, the following alias could be used to override the repository directory. alias yadm='yadm --yadm-repo /alternate/path/to/repo' - The following is the full list of universal options. Each option + The following is the full list of universal options. Each option should be followed by a path. -Y,--yadm-dir - Override the yadm directory. yadm stores its configurations + Override the yadm directory. yadm stores its configurations relative to this directory. --yadm-data @@ -291,40 +289,40 @@ TION. yadm.git-program - Specify an alternate program to use instead of "git". By de‐ + Specify an alternate program to use instead of "git". By de‐ fault, the first "git" found in $PATH is used. yadm.gpg-perms - Disable the permission changes to $HOME/.gnupg/*. This feature + Disable the permission changes to $HOME/.gnupg/*. This feature is enabled by default. yadm.gpg-program - Specify an alternate program to use instead of "gpg". By de‐ + Specify an alternate program to use instead of "gpg". By de‐ fault, the first "gpg" found in $PATH is used. yadm.gpg-recipient Asymmetrically encrypt files with a gpg public/private key pair. Provide a "key ID" to specify which public key to encrypt with. The key must exist in your public keyrings. Multiple recipients - can be specified (separated by space). If left blank or not - provided, symmetric encryption is used instead. If set to - "ASK", gpg will interactively ask for recipients. See the EN‐ - CRYPTION section for more details. This feature is disabled by + can be specified (separated by space). If left blank or not + provided, symmetric encryption is used instead. If set to + "ASK", gpg will interactively ask for recipients. See the EN‐ + CRYPTION section for more details. This feature is disabled by default. yadm.openssl-ciphername - Specify which cipher should be used by openssl. "aes-256-cbc" + Specify which cipher should be used by openssl. "aes-256-cbc" is used by default. yadm.openssl-old - Newer versions of openssl support the pbkdf2 key derivation - function. This is used by default. If this configuration is set - to "true", openssl operations will use options compatible with - older versions of openssl. If you change this option, you will + Newer versions of openssl support the pbkdf2 key derivation + function. This is used by default. If this configuration is set + to "true", openssl operations will use options compatible with + older versions of openssl. If you change this option, you will need to recreate your encrypted archive. yadm.openssl-program - Specify an alternate program to use instead of "openssl". By + Specify an alternate program to use instead of "openssl". By default, the first "openssl" found in $PATH is used. yadm.ssh-perms @@ -343,11 +341,11 @@ yadm config --add local.class local.arch - Override the architecture for the purpose of symlinking alter‐ + Override the architecture for the purpose of symlinking alter‐ nate files. local.hostname - Override the hostname for the purpose of symlinking alternate + Override the hostname for the purpose of symlinking alternate files. local.os @@ -362,9 +360,9 @@ to have an automated way of choosing an alternate version of a file for a different operating system, host, user, etc. - yadm will automatically create a symbolic link to the appropriate ver‐ - sion of a file, when a valid suffix is appended to the filename. The - suffix contains the conditions that must be met for that file to be + yadm will automatically create a symbolic link to the appropriate ver‐ + sion of a file, when a valid suffix is appended to the filename. The + suffix contains the conditions that must be met for that file to be used. The suffix begins with "##", followed by any number of conditions sepa‐ @@ -384,11 +382,11 @@ template, t - Valid when the value matches a supported template processor. + Valid when the value matches a supported template processor. See the TEMPLATES section for more details. user, u - Valid if the value matches the current user. Current user is + Valid if the value matches the current user. Current user is calculated by running id -u -n. hostname, h @@ -407,14 +405,14 @@ lease. distro_family, f - Valid if the value matches the distro family. Distro family is + Valid if the value matches the distro family. Distro family is calculated by inspecting the ID_LIKE line from /etc/os-release. - os, o Valid if the value matches the OS. OS is calculated by running + os, o Valid if the value matches the OS. OS is calculated by running uname -s. arch, a - Valid if the value matches the architecture. Architecture is + Valid if the value matches the architecture. Architecture is calculated by running uname -m. default @@ -423,16 +421,16 @@ extension, e A special "condition" that doesn't affect the selection process. Its purpose is instead to allow the alternate file to end with a - certain extension to e.g. make editors highlight the content + certain extension to e.g. make editors highlight the content properly. - NOTE: The OS for "Windows Subsystem for Linux" is reported as "WSL", + NOTE: The OS for "Windows Subsystem for Linux" is reported as "WSL", even though uname identifies as "Linux". - You may use any number of conditions, in any order. An alternate will - only be used if ALL conditions are valid. For all files managed by - yadm's repository or listed in $HOME/.config/yadm/encrypt, if they - match this naming convention, symbolic links will be created for the + You may use any number of conditions, in any order. An alternate will + only be used if ALL conditions are valid. For all files managed by + yadm's repository or listed in $HOME/.config/yadm/encrypt, if they + match this naming convention, symbolic links will be created for the most appropriate version. The "most appropriate" version is determined by calculating a score for @@ -491,12 +489,12 @@ have at least one yadm managed file within them (at the top level). yadm will automatically create these links by default. This can be dis‐ - abled using the yadm.auto-alt configuration. Even if disabled, links + abled using the yadm.auto-alt configuration. Even if disabled, links can be manually created by running yadm alt. - Class is a special value which is stored locally on each host (inside - the local repository). To use alternate symlinks using class, you must - set the value of class using the configuration local.class. This is + Class is a special value which is stored locally on each host (inside + the local repository). To use alternate symlinks using class, you must + set the value of class using the configuration local.class. This is set like any other yadm configuration with the yadm config command. The following sets the class to be "Work". @@ -515,11 +513,11 @@ Supported template processors: default - This is yadm's built-in template processor. This processor is - very basic, with a Jinja-like syntax. The advantage of this pro‐ - cessor is that it only depends upon awk, which is available on - most *nix systems. To use this processor, specify the value of - "default" or just leave the value off (e.g. "##template"). + This is yadm's built-in template processor. This processor is + very basic, with a Jinja-like syntax. The advantage of this + processor is that it only depends upon awk, which is available + on most *nix systems. To use this processor, specify the value + of "default" or just leave the value off (e.g. "##template"). ESH ESH is a template processor written in POSIX compliant shell. It allows executing shell commands within templates. This can be @@ -610,8 +608,8 @@ It can be useful to manage confidential files, like SSH or GPG keys, across multiple systems. However, doing so would put plain text data into a Git repository, which often resides on a public system. yadm can - make it easy to encrypt and decrypt a set of files so the encrypted - version can be maintained in the Git repository. This feature will + make it easy to encrypt and decrypt a set of files so the encrypted + version can be maintained in the Git repository. This feature will only work if a supported tool is available. Both gpg(1) and openssl(1) are supported. gpg is used by default, but openssl can be configured with the yadm.cipher configuration. @@ -624,7 +622,7 @@ .gnupg/*.gpg Standard filename expansions (*, ?, [) are supported. If you have Bash - version 4, you may use "**" to match all subdirectories. Other shell + version 4, you may use "**" to match all subdirectories. Other shell expansions like brace and tilde are not supported. Spaces in paths are supported, and should not be quoted. If a directory is specified, its contents will be included, but not recursively. Paths beginning with a @@ -637,13 +635,13 @@ tory so they are available across multiple systems. To decrypt these files later, or on another system run yadm decrypt and - provide the correct password. After files are decrypted, permissions + provide the correct password. After files are decrypted, permissions are automatically updated as described in the PERMISSIONS section. - Symmetric encryption is used by default, but asymmetric encryption may + Symmetric encryption is used by default, but asymmetric encryption may be enabled using the yadm.gpg-recipient configuration. - NOTE: It is recommended that you use a private repository when keeping + NOTE: It is recommended that you use a private repository when keeping confidential files, even though they are encrypted. Patterns found in $HOME/.config/yadm/encrypt are automatically added to @@ -653,10 +651,10 @@ Using transcrypt or git-crypt - A completely separate option for encrypting data is to install and use - transcrypt or git-crypt. Once installed, you can use these tools by - running yadm transcrypt or yadm git-crypt. These tools enables trans‐ - parent encryption and decryption of files in a git repository. See the + A completely separate option for encrypting data is to install and use + transcrypt or git-crypt. Once installed, you can use these tools by + running yadm transcrypt or yadm git-crypt. These tools enables trans‐ + parent encryption and decryption of files in a git repository. See the following web sites for more information: - https://github.com/elasticdog/transcrypt @@ -664,9 +662,9 @@ - https://github.com/AGWA/git-crypt ## PERMISSIONS - When files are checked out of a Git repository, their initial permis‐ - sions are dependent upon the user's umask. Because of this, yadm will - automatically update the permissions of some file paths. The "group" + When files are checked out of a Git repository, their initial permis‐ + sions are dependent upon the user's umask. Because of this, yadm will + automatically update the permissions of some file paths. The "group" and "others" permissions will be removed from the following files: - $HOME/.local/share/yadm/archive @@ -696,21 +694,21 @@ ## HOOKS - For every command yadm supports, a program can be provided to run be‐ - fore or after that command. These are referred to as "hooks". yadm - looks for hooks in the directory $HOME/.config/yadm/hooks. Each hook + For every command yadm supports, a program can be provided to run be‐ + fore or after that command. These are referred to as "hooks". yadm + looks for hooks in the directory $HOME/.config/yadm/hooks. Each hook is named using a prefix of pre_ or post_, followed by the command which should trigger the hook. For example, to create a hook which is run af‐ ter every yadm pull command, create a hook named post_pull. Hooks must have the executable file permission set. If a pre_ hook is defined, and the hook terminates with a non-zero exit - status, yadm will refuse to run the yadm command. For example, if a - pre_commit hook is defined, but that command ends with a non-zero exit - status, the yadm commit will never be run. This allows one to "short- + status, yadm will refuse to run the yadm command. For example, if a + pre_commit hook is defined, but that command ends with a non-zero exit + status, the yadm commit will never be run. This allows one to "short- circuit" any operation using a pre_ hook. - Hooks have the following environment variables available to them at + Hooks have the following environment variables available to them at runtime: YADM_HOOK_COMMAND @@ -732,10 +730,10 @@ ## FILES - All of yadm's configurations are relative to the "yadm directory". - yadm uses the "XDG Base Directory Specification" to determine this di‐ - rectory. If the environment variable $XDG_CONFIG_HOME is defined as a - fully qualified path, this directory will be $XDG_CONFIG_HOME/yadm. + All of yadm's configurations are relative to the "yadm directory". + yadm uses the "XDG Base Directory Specification" to determine this di‐ + rectory. If the environment variable $XDG_CONFIG_HOME is defined as a + fully qualified path, this directory will be $XDG_CONFIG_HOME/yadm. Otherwise it will be $HOME/.config/yadm. Similarly, yadm's data files are relative to the "yadm data directory". @@ -744,7 +742,7 @@ fully qualified path, this directory will be $XDG_DATA_HOME/yadm. Oth‐ erwise it will be $HOME/.local/share/yadm. - The following are the default paths yadm uses for its own data. Most + The following are the default paths yadm uses for its own data. Most of these paths can be altered using universal options. See the OPTIONS section for details. @@ -753,16 +751,16 @@ tive to this directory. $HOME/.local/share/yadm - The yadm data directory. By default, all data yadm stores is + The yadm data directory. By default, all data yadm stores is relative to this directory. $YADM_DIR/config Configuration file for yadm. $YADM_DIR/alt - This is a directory to keep "alternate files" without having - them side-by-side with the resulting symlink or processed tem‐ - plate. Alternate files placed in this directory will be created + This is a directory to keep "alternate files" without having + them side-by-side with the resulting symlink or processed tem‐ + plate. Alternate files placed in this directory will be created relative to $HOME instead. $YADM_DATA/repo.git @@ -810,5 +808,3 @@ https://yadm.io/ - - diff --git a/yadm.spec b/yadm.spec index f01dea5..f1d4a76 100644 --- a/yadm.spec +++ b/yadm.spec @@ -1,7 +1,7 @@ %{!?_pkgdocdir: %global _pkgdocdir %{_docdir}/%{name}-%{version}} Name: yadm Summary: Yet Another Dotfiles Manager -Version: 3.2.2 +Version: 3.3.0 Group: Development/Tools Release: 1%{?dist} URL: https://yadm.io