diff options
Diffstat (limited to 'test')
39 files changed, 1115 insertions, 966 deletions
diff --git a/test/Dockerfile b/test/Dockerfile new file mode 100644 index 0000000..3e5a299 --- /dev/null +++ b/test/Dockerfile @@ -0,0 +1,73 @@ +FROM ubuntu:18.04 +MAINTAINER Tim Byrne <sultan@locehilios.com> + +# Shellcheck and esh versions +ARG SC_VER=0.7.1 +ARG ESH_VER=0.3.0 + +# Install prerequisites and configure UTF-8 locale +RUN \ + echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \ + && apt-get update \ + && DEBIAN_FRONTEND=noninteractive \ + apt-get install -y --no-install-recommends \ + expect \ + git \ + gnupg \ + locales \ + lsb-release \ + make \ + man \ + python3-pip \ + vim-tiny \ + xz-utils \ + && rm -rf /var/lib/apt/lists/* \ + && update-locale LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' + +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' + +# Convenience settings for the testbed's root account +RUN echo 'set -o vi' >> /root/.bashrc + +# Create a flag to identify when running inside the yadm testbed +RUN touch /.yadmtestbed + +# Install shellcheck +ADD https://github.com/koalaman/shellcheck/releases/download/v$SC_VER/shellcheck-v$SC_VER.linux.x86_64.tar.xz /opt +RUN cd /opt \ + && tar xf shellcheck-v$SC_VER.linux.x86_64.tar.xz \ + && rm -f shellcheck-v$SC_VER.linux.x86_64.tar.xz \ + && ln -s /opt/shellcheck-v$SC_VER/shellcheck /usr/local/bin + +# 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 \ + && rm -f /tmp/requirements + +# Install esh +ADD https://raw.githubusercontent.com/jirutka/esh/v$ESH_VER/esh /usr/local/bin +RUN chmod +x /usr/local/bin/esh + +# Create workdir and dummy Makefile to be used if no /yadm volume is mounted +RUN mkdir /yadm \ + && echo "test:" > /yadm/Makefile \ + && echo "\t@echo 'The yadm project must be mounted at /yadm'" >> /yadm/Makefile \ + && echo "\t@echo 'Try using a docker parameter like -v \"\$\$PWD:/yadm:ro\"'" >> /yadm/Makefile \ + && echo "\t@false" >> /yadm/Makefile + +# Include released versions of yadm to test upgrades +ADD https://raw.githubusercontent.com/TheLocehiliosan/yadm/1.12.0/yadm /usr/local/bin/yadm-1.12.0 +ADD https://raw.githubusercontent.com/TheLocehiliosan/yadm/2.5.0/yadm /usr/local/bin/yadm-2.5.0 +RUN chmod +x /usr/local/bin/yadm-* + +# Configure git to make it easier to test yadm manually +RUN git config --system user.email "test@yadm.io" \ + && git config --system user.name "Yadm Test" + +# /yadm will be the work directory for all tests +# docker commands should mount the local yadm project as /yadm +WORKDIR /yadm + +# By default, run all tests defined +CMD make test diff --git a/test/conftest.py b/test/conftest.py index 31d872b..38228a1 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -25,25 +25,25 @@ def pytest_addoption(parser): @pytest.fixture(scope='session') def shellcheck_version(): """Version of shellcheck supported""" - return '0.4.6' + return '0.7.1' @pytest.fixture(scope='session') def pylint_version(): """Version of pylint supported""" - return '2.4.1' + return '2.6.0' @pytest.fixture(scope='session') def flake8_version(): """Version of flake8 supported""" - return '3.7.8' + return '3.8.4' @pytest.fixture(scope='session') def yamllint_version(): """Version of yamllint supported""" - return '1.17.0' + return '1.25.0' @pytest.fixture(scope='session') @@ -96,6 +96,7 @@ def supported_commands(): 'introspect', 'list', 'perms', + 'transcrypt', 'upgrade', 'version', ] @@ -117,10 +118,14 @@ def supported_configs(): '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', ] @@ -135,6 +140,7 @@ def supported_switches(): '--yadm-archive', '--yadm-bootstrap', '--yadm-config', + '--yadm-data', '--yadm-dir', '--yadm-encrypt', '--yadm-repo', @@ -174,6 +180,10 @@ class Runner(): self.command = ' '.join([str(cmd) for cmd in command]) else: self.command = command + if env is None: + env = {} + merged_env = os.environ.copy() + merged_env.update(env) self.inp = inp self.wrap(expect) process = Popen( @@ -183,7 +193,7 @@ class Runner(): stderr=PIPE, shell=shell, cwd=cwd, - env=env, + env=merged_env, ) input_bytes = self.inp if self.inp: @@ -274,13 +284,17 @@ def yadm(): @pytest.fixture() 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_yadm = dir_root.mkdir('yadm') - dir_repo = dir_yadm.mkdir('repo.git') + 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_remote = dir_root.mkdir('remote') - file_archive = dir_yadm.join('files.gpg') + 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') @@ -288,24 +302,32 @@ def paths(tmpdir, yadm): 'Paths', [ 'pgm', 'root', + 'remote', 'work', + 'xdg_data', + 'xdg_home', + 'data', 'yadm', - 'repo', 'hooks', - 'remote', + '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, + dir_remote, dir_work, + dir_xdg_data, + dir_xdg_home, + dir_data, dir_yadm, - dir_repo, dir_hooks, - dir_remote, + dir_repo, file_archive, file_bootstrap, file_config, @@ -314,11 +336,11 @@ def paths(tmpdir, yadm): @pytest.fixture() -def yadm_y(paths): +def yadm_cmd(paths): """Generate custom command_list function""" def command_list(*args): """Produce params for running yadm with -Y""" - return [paths.pgm, '-Y', str(paths.yadm)] + list(args) + return [paths.pgm] + list(args) return command_list diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 0000000..30da6ae --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1,6 @@ +envtpl +flake8==3.8.4 +j2cli +pylint==2.6.0 +pytest==6.2.1 +yamllint==1.25.0 diff --git a/test/test_alt.py b/test/test_alt.py index 359f32d..b18e6cb 100644 --- a/test/test_alt.py +++ b/test/test_alt.py @@ -22,12 +22,12 @@ def test_alt_source( tracked, encrypt, exclude, yadm_alt): """Test yadm alt operates on all expected sources of alternates""" - yadm_dir = setup_standard_yadm_dir(paths) + 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, 'alt']) + run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt']) assert run.success assert run.err == '' linked = utils.parse_alt_output(run.out) @@ -57,12 +57,12 @@ def test_alt_source( @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 = setup_standard_yadm_dir(paths) + 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, 'alt']) + run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt']) assert run.success assert run.err == '' @@ -81,6 +81,7 @@ def test_relative_link(runner, paths, yadm_alt): @pytest.mark.usefixtures('ds1_copy') @pytest.mark.parametrize('suffix', [ '##default', + '##default,e.txt', '##default,extension.txt', '##o.$tst_sys', '##os.$tst_sys', '##d.$tst_distro', '##distro.$tst_distro', '##c.$tst_class', '##class.$tst_class', @@ -91,7 +92,7 @@ def test_alt_conditions( runner, paths, tst_sys, tst_distro, tst_host, tst_user, suffix): """Test conditions supported by yadm alt""" - yadm_dir = setup_standard_yadm_dir(paths) + yadm_dir, yadm_data = setup_standard_yadm_dir(paths) # set the class tst_class = 'testclass' @@ -106,7 +107,7 @@ def test_alt_conditions( ) utils.create_alt_files(paths, suffix) - run = runner([paths.pgm, '-Y', yadm_dir, 'alt']) + run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt']) assert run.success assert run.err == '' linked = utils.parse_alt_output(run.out) @@ -126,18 +127,18 @@ def test_alt_conditions( @pytest.mark.usefixtures('ds1_copy') @pytest.mark.parametrize( - 'kind', ['default', '', None, 'envtpl', 'j2cli', 'j2']) + 'kind', ['default', '', None, 'envtpl', 'j2cli', 'j2', 'esh']) @pytest.mark.parametrize('label', ['t', 'template', 'yadm', ]) def test_alt_templates( runner, paths, kind, label): """Test templates supported by yadm alt""" - yadm_dir = setup_standard_yadm_dir(paths) + yadm_dir, yadm_data = setup_standard_yadm_dir(paths) suffix = f'##{label}.{kind}' if kind is None: suffix = f'##{label}' utils.create_alt_files(paths, suffix) - run = runner([paths.pgm, '-Y', yadm_dir, 'alt']) + run = runner([paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'alt']) assert run.success assert run.err == '' created = utils.parse_alt_output(run.out, linked=False) @@ -152,15 +153,15 @@ def test_alt_templates( @pytest.mark.usefixtures('ds1_copy') @pytest.mark.parametrize('autoalt', [None, 'true', 'false']) -def test_auto_alt(runner, yadm_y, paths, autoalt): +def test_auto_alt(runner, yadm_cmd, paths, autoalt): """Test auto alt""" # set the value of auto-alt if autoalt: - os.system(' '.join(yadm_y('config', 'yadm.auto-alt', autoalt))) + os.system(' '.join(yadm_cmd('config', 'yadm.auto-alt', autoalt))) utils.create_alt_files(paths, '##default') - run = runner(yadm_y('status')) + run = runner(yadm_cmd('status')) assert run.success assert run.err == '' linked = utils.parse_alt_output(run.out) @@ -185,7 +186,7 @@ def test_auto_alt(runner, yadm_y, paths, autoalt): @pytest.mark.usefixtures('ds1_copy') -def test_stale_link_removal(runner, yadm_y, paths): +def test_stale_link_removal(runner, yadm_cmd, paths): """Stale links to alternative files are removed This test ensures that when an already linked alternative becomes invalid @@ -200,7 +201,7 @@ def test_stale_link_removal(runner, yadm_y, paths): utils.create_alt_files(paths, f'##class.{tst_class}') # run alt to trigger linking - run = runner(yadm_y('alt')) + run = runner(yadm_cmd('alt')) assert run.success assert run.err == '' linked = utils.parse_alt_output(run.out) @@ -222,7 +223,7 @@ def test_stale_link_removal(runner, yadm_y, paths): utils.set_local(paths, 'class', 'changedclass') # run alt to trigger linking - run = runner(yadm_y('alt')) + run = runner(yadm_cmd('alt')) assert run.success assert run.err == '' linked = utils.parse_alt_output(run.out) @@ -235,7 +236,7 @@ def test_stale_link_removal(runner, yadm_y, paths): @pytest.mark.usefixtures('ds1_repo_copy') -def test_template_overwrite_symlink(runner, yadm_y, paths, tst_sys): +def test_template_overwrite_symlink(runner, yadm_cmd, paths, tst_sys): """Remove symlinks before processing a template If a symlink is in the way of the output of a template, the target of the @@ -252,7 +253,7 @@ def test_template_overwrite_symlink(runner, yadm_y, paths, tst_sys): template = paths.work.join('test_link##template.default') template.write('test-data') - run = runner(yadm_y('add', target, template)) + run = runner(yadm_cmd('add', target, template)) assert run.success assert run.err == '' assert run.out == '' @@ -265,12 +266,13 @@ def test_template_overwrite_symlink(runner, yadm_y, paths, tst_sys): @pytest.mark.parametrize('style', ['symlink', 'template']) def test_ensure_alt_path(runner, paths, style): """Test that directories are created before making alternates""" - yadm_dir = setup_standard_yadm_dir(paths) + 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, 'add', source]) + run = runner([ + paths.pgm, '-Y', yadm_dir, '--yadm-data', yadm_data, 'add', source]) assert run.success assert run.err == '' assert run.out == '' @@ -280,6 +282,7 @@ def test_ensure_alt_path(runner, paths, style): 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_dir.join('repo.git').mksymlinkto(paths.repo, absolute=1) + 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 + return std_yadm_dir, std_yadm_data diff --git a/test/test_alt_copy.py b/test/test_alt_copy.py index c808348..fa8e09c 100644 --- a/test/test_alt_copy.py +++ b/test/test_alt_copy.py @@ -5,10 +5,6 @@ import pytest @pytest.mark.parametrize( - 'cygwin', - [pytest.param(True, marks=pytest.mark.deprecated), False], - ids=['cygwin', 'no-cygwin']) -@pytest.mark.parametrize( 'setting, expect_link, pre_existing', [ (None, True, None), (True, False, None), @@ -25,15 +21,12 @@ import pytest ]) @pytest.mark.usefixtures('ds1_copy') def test_alt_copy( - runner, yadm_y, paths, tst_sys, - setting, expect_link, pre_existing, - cygwin): + runner, yadm_cmd, paths, tst_sys, + setting, expect_link, pre_existing): """Test yadm.alt-copy""" - option = 'yadm.cygwin-copy' if cygwin else 'yadm.alt-copy' - if setting is not None: - os.system(' '.join(yadm_y('config', option, str(setting)))) + os.system(' '.join(yadm_cmd('config', 'yadm.alt-copy', str(setting)))) expected_content = f'test_alt_copy##os.{tst_sys}' @@ -43,7 +36,7 @@ def test_alt_copy( elif pre_existing == 'file': alt_path.write('wrong content') - run = runner(yadm_y('alt')) + run = runner(yadm_cmd('alt')) assert run.success assert run.err == '' assert 'Linking' in run.out diff --git a/test/test_assert_private_dirs.py b/test/test_assert_private_dirs.py index 606012f..bfd55ac 100644 --- a/test/test_assert_private_dirs.py +++ b/test/test_assert_private_dirs.py @@ -9,7 +9,7 @@ PRIVATE_DIRS = ['.gnupg', '.ssh'] @pytest.mark.parametrize('home', [True, False], ids=['home', 'not-home']) -def test_pdirs_missing(runner, yadm_y, paths, home): +def test_pdirs_missing(runner, yadm_cmd, paths, home): """Private dirs (private dirs missing) When a git command is run @@ -29,7 +29,7 @@ def test_pdirs_missing(runner, yadm_y, paths, home): env['HOME'] = paths.work # run status - run = runner(command=yadm_y('status'), env=env) + run = runner(command=yadm_cmd('status'), env=env) assert run.success assert run.err == '' assert 'On branch master' in run.out @@ -53,7 +53,7 @@ def test_pdirs_missing(runner, yadm_y, paths, home): run.out, re.DOTALL), 'directories created before command is run' -def test_pdirs_missing_apd_false(runner, yadm_y, paths): +def test_pdirs_missing_apd_false(runner, yadm_cmd, paths): """Private dirs (private dirs missing / yadm.auto-private-dirs=false) When a git command is run @@ -70,11 +70,11 @@ def test_pdirs_missing_apd_false(runner, yadm_y, paths): assert not path.exists() # set configuration - os.system(' '.join(yadm_y( + os.system(' '.join(yadm_cmd( 'config', '--bool', 'yadm.auto-private-dirs', 'false'))) # run status - run = runner(command=yadm_y('status')) + run = runner(command=yadm_cmd('status')) assert run.success assert run.err == '' assert 'On branch master' in run.out @@ -84,7 +84,7 @@ def test_pdirs_missing_apd_false(runner, yadm_y, paths): assert not paths.work.join(pdir).exists() -def test_pdirs_exist_apd_false(runner, yadm_y, paths): +def test_pdirs_exist_apd_false(runner, yadm_cmd, paths): """Private dirs (private dirs exist / yadm.auto-perms=false) When a git command is run @@ -102,11 +102,11 @@ def test_pdirs_exist_apd_false(runner, yadm_y, paths): assert oct(path.stat().mode).endswith('77'), 'Directory is secure.' # set configuration - os.system(' '.join(yadm_y( + os.system(' '.join(yadm_cmd( 'config', '--bool', 'yadm.auto-perms', 'false'))) # run status - run = runner(command=yadm_y('status')) + run = runner(command=yadm_cmd('status')) assert run.success assert run.err == '' assert 'On branch master' in run.out diff --git a/test/test_bootstrap.py b/test/test_bootstrap.py index 2adbe33..4865ece 100644 --- a/test/test_bootstrap.py +++ b/test/test_bootstrap.py @@ -14,7 +14,7 @@ import pytest 'executable', ]) def test_bootstrap( - runner, yadm_y, paths, exists, executable, code, expect): + runner, yadm_cmd, paths, exists, executable, code, expect): """Test bootstrap command""" if exists: paths.bootstrap.write('') @@ -25,7 +25,11 @@ def test_bootstrap( f'exit {code}\n' ) paths.bootstrap.chmod(0o775) - run = runner(command=yadm_y('bootstrap')) + run = runner(command=yadm_cmd('bootstrap')) assert run.code == code - assert run.err == '' - assert expect in run.out + if exists and executable: + assert run.err == '' + assert expect in run.out + else: + assert expect in run.err + assert run.out == '' diff --git a/test/test_clean.py b/test/test_clean.py index 9a2221a..39e7e54 100644 --- a/test/test_clean.py +++ b/test/test_clean.py @@ -1,11 +1,11 @@ """Test clean""" -def test_clean_command(runner, yadm_y): +def test_clean_command(runner, yadm_cmd): """Run with clean command""" - run = runner(command=yadm_y('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.err == '' - assert 'disabled' in run.out + assert run.out == '' + assert 'disabled' in run.err diff --git a/test/test_clone.py b/test/test_clone.py index a6df6d0..b024e9c 100644 --- a/test/test_clone.py +++ b/test/test_clone.py @@ -24,7 +24,7 @@ BOOTSTRAP_MSG = 'Bootstrap successful' 'conflicts', ]) def test_clone( - runner, paths, yadm_y, repo_config, ds1, + runner, paths, yadm_cmd, repo_config, ds1, good_remote, repo_exists, force, conflicts): """Test basic clone operation""" @@ -53,23 +53,26 @@ def test_clone( if force: args += ['-f'] args += [remote_url] - run = runner(command=yadm_y(*args)) + run = runner(command=yadm_cmd(*args)) if not good_remote: # clone should fail assert run.failure - assert run.err != '' - assert 'Unable to fetch origin' in run.out + assert run.out != '' + assert 'Unable to fetch origin' in run.err assert not paths.repo.exists() elif repo_exists and not force: # can't overwrite data assert run.failure - assert run.err == '' - assert 'Git repo already exists' in run.out + 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') + # ensure conflicts are handled properly if conflicts: assert 'NOTE' in run.out @@ -88,20 +91,21 @@ def test_clone( if conflicts: # test to see if the work tree is actually "clean" run = runner( - command=yadm_y('status', '-uno', '--porcelain'), + command=yadm_cmd('status', '-uno', '--porcelain'), cwd=paths.work) assert run.success assert run.err == '' assert run.out == '', 'worktree has unexpected changes' # test to see if the conflicts are stashed - run = runner(command=yadm_y('stash', 'list'), cwd=paths.work) + run = runner(command=yadm_cmd('stash', 'list'), cwd=paths.work) assert run.success assert run.err == '' assert 'Conflicts preserved' in run.out, 'conflicts not stashed' # verify content of the stashed conflicts - run = runner(command=yadm_y('stash', 'show', '-p'), cwd=paths.work) + run = runner( + command=yadm_cmd('stash', 'show', '-p'), cwd=paths.work) assert run.success assert run.err == '' assert '\n+conflict' in run.out, 'conflicts not stashed' @@ -130,7 +134,7 @@ def test_clone( 'existing, answer y', ]) def test_clone_bootstrap( - runner, paths, yadm_y, repo_config, bs_exists, bs_param, answer): + runner, paths, yadm_cmd, repo_config, bs_exists, bs_param, answer): """Test bootstrap clone features""" # establish a bootstrap @@ -144,7 +148,7 @@ def test_clone_bootstrap( expect = [] if answer: expect.append(('Would you like to execute it now', answer)) - run = runner(command=yadm_y(*args), expect=expect) + run = runner(command=yadm_cmd(*args), expect=expect) if answer: assert 'Would you like to execute it now' in run.out @@ -161,6 +165,7 @@ def test_clone_bootstrap( assert BOOTSTRAP_MSG not in run.out assert successful_clone(run, paths, repo_config, expected_code) + verify_head(paths, 'master') if not bs_exists: assert BOOTSTRAP_MSG not in run.out @@ -197,7 +202,7 @@ def create_bootstrap(paths, exists): 'missing gnupg, tracked', ]) def test_clone_perms( - runner, yadm_y, paths, repo_config, + runner, yadm_cmd, paths, repo_config, private_type, in_repo, in_work): """Test clone permission-related functions""" @@ -224,11 +229,12 @@ def test_clone_perms( env = {'HOME': paths.work} run = runner( - yadm_y('clone', '-d', '-w', paths.work, f'file://{paths.remote}'), + yadm_cmd('clone', '-d', '-w', paths.work, f'file://{paths.remote}'), env=env ) assert successful_clone(run, paths, repo_config) + verify_head(paths, 'master') if in_work: # private directories which already exist, should be left as they are, # which in this test is "insecure". @@ -259,8 +265,9 @@ def test_clone_perms( @pytest.mark.usefixtures('remote') -@pytest.mark.parametrize('branch', ['master', 'valid', 'invalid']) -def test_alternate_branch(runner, paths, yadm_y, repo_config, branch): +@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 @@ -268,6 +275,12 @@ def test_alternate_branch(runner, paths, yadm_y, repo_config, branch): 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 + # "master" + os.system(f'GIT_DIR="{paths.remote}" git checkout master') # clear out the work path paths.work.remove() @@ -277,15 +290,15 @@ def test_alternate_branch(runner, paths, yadm_y, repo_config, branch): # run the clone command args = ['clone', '-w', paths.work] - if branch != 'master': + if branch not in ['master', 'default']: args += ['-b', branch] args += [remote_url] - run = runner(command=yadm_y(*args)) + run = runner(command=yadm_cmd(*args)) if branch == 'invalid': assert run.failure - assert 'ERROR: Clone failed' in run.out - assert f"'origin/{branch}' does not exist in {remote_url}" in run.out + assert 'ERROR: Clone failed' in run.err + assert f"'origin/{branch}' does not exist in {remote_url}" in run.err else: assert successful_clone(run, paths, repo_config) @@ -296,11 +309,13 @@ def test_alternate_branch(runner, paths, yadm_y, repo_config, branch): assert run.success assert run.err == '' assert f'origin\t{remote_url}' in run.out - run = runner(command=yadm_y('show')) - if branch == 'valid': - assert 'This branch is valid' in run.out - else: + 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') def successful_clone(run, paths, repo_config, expected_code=0): @@ -323,3 +338,16 @@ def remote(paths, ds1_repo_copy): # cannot be applied to another fixture. paths.remote.remove() paths.repo.move(paths.remote) + + +def test_no_repo(runner, yadm_cmd, ): + """Test cloning without specifying a repo""" + run = runner(command=yadm_cmd('clone')) + assert run.failure + assert run.out == '' + assert 'ERROR: No repository provided' 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' diff --git a/test/test_compat_alt.py b/test/test_compat_alt.py deleted file mode 100644 index da7a8cf..0000000 --- a/test/test_compat_alt.py +++ /dev/null @@ -1,453 +0,0 @@ -"""Test alt""" - -import os -import string -import py -import pytest -import utils - -# These tests are for the alternate processing in YADM_COMPATIBILITY=1 mode -pytestmark = pytest.mark.deprecated - -# These test IDs are broken. During the writing of these tests, problems have -# been discovered in the way yadm orders matching files. -BROKEN_TEST_IDS = [ - 'test_wild[tracked-##C.S.H.U-C-S%-H%-U]', - 'test_wild[tracked-##C.S.H.U-C-S-H%-U]', - 'test_wild[encrypted-##C.S.H.U-C-S%-H%-U]', - 'test_wild[encrypted-##C.S.H.U-C-S-H%-U]', - ] - -PRECEDENCE = [ - '##', - '##$tst_sys', - '##$tst_sys.$tst_host', - '##$tst_sys.$tst_host.$tst_user', - '##$tst_class', - '##$tst_class.$tst_sys', - '##$tst_class.$tst_sys.$tst_host', - '##$tst_class.$tst_sys.$tst_host.$tst_user', - ] - -WILD_TEMPLATES = [ - '##$tst_class', - '##$tst_class.$tst_sys', - '##$tst_class.$tst_sys.$tst_host', - '##$tst_class.$tst_sys.$tst_host.$tst_user', - ] - -TEST_PATHS = [utils.ALT_FILE1, utils.ALT_FILE2, utils.ALT_DIR] - -WILD_TESTED = set() - - -@pytest.mark.parametrize('precedence_index', range(len(PRECEDENCE))) -@pytest.mark.parametrize( - 'tracked, encrypt, exclude', [ - (False, False, False), - (True, False, False), - (False, True, False), - (False, True, True), - ], ids=[ - 'untracked', - 'tracked', - 'encrypted', - 'excluded', - ]) -@pytest.mark.usefixtures('ds1_copy') -def test_alt(runner, yadm_y, paths, - tst_sys, tst_host, tst_user, - tracked, encrypt, exclude, - precedence_index): - """Test alternate linking - - This test is done by iterating for the number of templates in PRECEDENCE. - With each iteration, another file is left off the list. So with each - iteration, the template with the "highest precedence" is left out. The file - using the highest precedence should be the one linked. - """ - - # set the class - tst_class = 'testclass' - utils.set_local(paths, 'class', tst_class) - - # process the templates in PRECEDENCE - precedence = list() - for template in PRECEDENCE: - precedence.append( - string.Template(template).substitute( - tst_class=tst_class, - tst_host=tst_host, - tst_sys=tst_sys, - tst_user=tst_user, - ) - ) - - # create files using a subset of files - for suffix in precedence[0:precedence_index+1]: - utils.create_alt_files(paths, suffix, tracked=tracked, - encrypt=encrypt, exclude=exclude) - - # run alt to trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('alt'), env=env) - assert run.success - assert run.err == '' - linked = utils.parse_alt_output(run.out) - - # assert the proper linking has occurred - for file_path in TEST_PATHS: - source_file = file_path + precedence[precedence_index] - if tracked or (encrypt and not exclude): - assert paths.work.join(file_path).islink() - target = py.path.local( - os.path.realpath(paths.work.join(file_path))) - if target.isfile(): - assert paths.work.join(file_path).read() == source_file - assert str(paths.work.join(source_file)) in linked - else: - assert paths.work.join(file_path).join( - utils.CONTAINED).read() == source_file - assert str(paths.work.join(source_file)) in linked - else: - assert not paths.work.join(file_path).exists() - assert str(paths.work.join(source_file)) not in linked - - -def short_template(template): - """Translate template into something short for test IDs""" - return string.Template(template).substitute( - tst_class='C', - tst_host='H', - tst_sys='S', - tst_user='U', - ) - - -@pytest.mark.parametrize('wild_user', [True, False], ids=['U%', 'U']) -@pytest.mark.parametrize('wild_host', [True, False], ids=['H%', 'H']) -@pytest.mark.parametrize('wild_sys', [True, False], ids=['S%', 'S']) -@pytest.mark.parametrize('wild_class', [True, False], ids=['C%', 'C']) -@pytest.mark.parametrize('template', WILD_TEMPLATES, ids=short_template) -@pytest.mark.parametrize( - 'tracked, encrypt', [ - (True, False), - (False, True), - ], ids=[ - 'tracked', - 'encrypted', - ]) -@pytest.mark.usefixtures('ds1_copy') -def test_wild(request, runner, yadm_y, paths, - tst_sys, tst_host, tst_user, - tracked, encrypt, - wild_class, wild_host, wild_sys, wild_user, - template): - """Test wild linking - - These tests are done by creating permutations of the possible files using - WILD_TEMPLATES. Each case is then tested (while skipping the already tested - permutations for efficiency). - """ - - if request.node.name in BROKEN_TEST_IDS: - pytest.xfail( - 'This test is known to be broken. ' - 'This bug only affects deprecated features.') - - tst_class = 'testclass' - - # determine the "wild" version of the suffix - str_class = '%' if wild_class else tst_class - str_host = '%' if wild_host else tst_host - str_sys = '%' if wild_sys else tst_sys - str_user = '%' if wild_user else tst_user - wild_suffix = string.Template(template).substitute( - tst_class=str_class, - tst_host=str_host, - tst_sys=str_sys, - tst_user=str_user, - ) - - # determine the "standard" version of the suffix - std_suffix = string.Template(template).substitute( - tst_class=tst_class, - tst_host=tst_host, - tst_sys=tst_sys, - tst_user=tst_user, - ) - - # skip over duplicate tests (this seems to be the simplest way to cover the - # permutations of tests, while skipping duplicates.) - test_key = f'{tracked}{encrypt}{wild_suffix}{std_suffix}' - if test_key in WILD_TESTED: - return - WILD_TESTED.add(test_key) - - # set the class - utils.set_local(paths, 'class', tst_class) - - # create files using the wild suffix - utils.create_alt_files(paths, wild_suffix, tracked=tracked, - encrypt=encrypt, exclude=False) - - # run alt to trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('alt'), env=env) - assert run.success - assert run.err == '' - linked = utils.parse_alt_output(run.out) - - # assert the proper linking has occurred - for file_path in TEST_PATHS: - source_file = file_path + wild_suffix - assert paths.work.join(file_path).islink() - target = py.path.local(os.path.realpath(paths.work.join(file_path))) - if target.isfile(): - assert paths.work.join(file_path).read() == source_file - assert str(paths.work.join(source_file)) in linked - else: - assert paths.work.join(file_path).join( - utils.CONTAINED).read() == source_file - assert str(paths.work.join(source_file)) in linked - - # create files using the standard suffix - utils.create_alt_files(paths, std_suffix, tracked=tracked, - encrypt=encrypt, exclude=False) - - # run alt to trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('alt'), env=env) - assert run.success - assert run.err == '' - linked = utils.parse_alt_output(run.out) - - # assert the proper linking has occurred - for file_path in TEST_PATHS: - source_file = file_path + std_suffix - assert paths.work.join(file_path).islink() - target = py.path.local(os.path.realpath(paths.work.join(file_path))) - if target.isfile(): - assert paths.work.join(file_path).read() == source_file - assert str(paths.work.join(source_file)) in linked - else: - assert paths.work.join(file_path).join( - utils.CONTAINED).read() == source_file - assert str(paths.work.join(source_file)) in linked - - -@pytest.mark.usefixtures('ds1_copy') -def test_local_override(runner, yadm_y, paths, - tst_sys, tst_host, tst_user): - """Test local overrides""" - - # define local overrides - utils.set_local(paths, 'class', 'or-class') - utils.set_local(paths, 'hostname', 'or-hostname') - utils.set_local(paths, 'os', 'or-os') - utils.set_local(paths, 'user', 'or-user') - - # create files, the first would normally be the most specific version - # however, the second is the overridden version which should be preferred. - utils.create_alt_files( - paths, f'##or-class.{tst_sys}.{tst_host}.{tst_user}') - utils.create_alt_files( - paths, '##or-class.or-os.or-hostname.or-user') - - # run alt to trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('alt'), env=env) - assert run.success - assert run.err == '' - linked = utils.parse_alt_output(run.out) - - # assert the proper linking has occurred - for file_path in TEST_PATHS: - source_file = file_path + '##or-class.or-os.or-hostname.or-user' - assert paths.work.join(file_path).islink() - target = py.path.local(os.path.realpath(paths.work.join(file_path))) - if target.isfile(): - assert paths.work.join(file_path).read() == source_file - assert str(paths.work.join(source_file)) in linked - else: - assert paths.work.join(file_path).join( - utils.CONTAINED).read() == source_file - assert str(paths.work.join(source_file)) in linked - - -@pytest.mark.parametrize('suffix', ['AAA', 'ZZZ', 'aaa', 'zzz']) -@pytest.mark.usefixtures('ds1_copy') -def test_class_case(runner, yadm_y, paths, tst_sys, suffix): - """Test range of class cases""" - - # set the class - utils.set_local(paths, 'class', suffix) - - # create files - endings = [suffix] - if tst_sys == 'Linux': - # Only create all of these side-by-side on Linux, which is - # unquestionably case-sensitive. This would break tests on - # case-insensitive systems. - endings = ['AAA', 'ZZZ', 'aaa', 'zzz'] - for ending in endings: - utils.create_alt_files(paths, f'##{ending}') - - # run alt to trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('alt'), env=env) - assert run.success - assert run.err == '' - linked = utils.parse_alt_output(run.out) - - # assert the proper linking has occurred - for file_path in TEST_PATHS: - source_file = file_path + f'##{suffix}' - assert paths.work.join(file_path).islink() - target = py.path.local(os.path.realpath(paths.work.join(file_path))) - if target.isfile(): - assert paths.work.join(file_path).read() == source_file - assert str(paths.work.join(source_file)) in linked - else: - assert paths.work.join(file_path).join( - utils.CONTAINED).read() == source_file - assert str(paths.work.join(source_file)) in linked - - -@pytest.mark.parametrize('autoalt', [None, 'true', 'false']) -@pytest.mark.usefixtures('ds1_copy') -def test_auto_alt(runner, yadm_y, paths, autoalt): - """Test setting auto-alt""" - - # set the value of auto-alt - if autoalt: - os.system(' '.join(yadm_y('config', 'yadm.auto-alt', autoalt))) - - # create file - suffix = '##' - utils.create_alt_files(paths, suffix) - - # run status to possibly trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('status'), env=env) - assert run.success - assert run.err == '' - linked = utils.parse_alt_output(run.out) - - # assert the proper linking has occurred - for file_path in TEST_PATHS: - source_file = file_path + suffix - if autoalt == 'false': - assert not paths.work.join(file_path).exists() - else: - assert paths.work.join(file_path).islink() - target = py.path.local( - os.path.realpath(paths.work.join(file_path))) - if target.isfile(): - assert paths.work.join(file_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(file_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.parametrize('delimiter', ['.', '_']) -@pytest.mark.usefixtures('ds1_copy') -def test_delimiter(runner, yadm_y, paths, - tst_sys, tst_host, tst_user, delimiter): - """Test delimiters used""" - - suffix = '##' + delimiter.join([tst_sys, tst_host, tst_user]) - - # create file - utils.create_alt_files(paths, suffix) - - # run alt to trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('alt'), env=env) - assert run.success - assert run.err == '' - linked = utils.parse_alt_output(run.out) - - # assert the proper linking has occurred - # only a delimiter of '.' is valid - for file_path in TEST_PATHS: - source_file = file_path + suffix - if delimiter == '.': - assert paths.work.join(file_path).islink() - target = py.path.local( - os.path.realpath(paths.work.join(file_path))) - if target.isfile(): - assert paths.work.join(file_path).read() == source_file - assert str(paths.work.join(source_file)) in linked - else: - assert paths.work.join(file_path).join( - utils.CONTAINED).read() == source_file - assert str(paths.work.join(source_file)) in linked - else: - assert not paths.work.join(file_path).exists() - assert str(paths.work.join(source_file)) not in linked - - -@pytest.mark.usefixtures('ds1_copy') -def test_invalid_links_removed(runner, yadm_y, paths): - """Links to invalid alternative files are removed - - This test ensures that when an already linked alternative becomes invalid - due to a change in class, the alternate link is removed. - """ - - # set the class - tst_class = 'testclass' - utils.set_local(paths, 'class', tst_class) - - # create files which match the test class - utils.create_alt_files(paths, f'##{tst_class}') - - # run alt to trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('alt'), env=env) - assert run.success - assert run.err == '' - linked = utils.parse_alt_output(run.out) - - # assert the proper linking has occurred - for file_path in TEST_PATHS: - source_file = file_path + '##' + tst_class - assert paths.work.join(file_path).islink() - target = py.path.local(os.path.realpath(paths.work.join(file_path))) - if target.isfile(): - assert paths.work.join(file_path).read() == source_file - assert str(paths.work.join(source_file)) in linked - else: - assert paths.work.join(file_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') - - # run alt to trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('alt'), env=env) - assert run.success - assert run.err == '' - linked = utils.parse_alt_output(run.out) - - # assert the linking is removed - for file_path in TEST_PATHS: - source_file = file_path + '##' + tst_class - assert not paths.work.join(file_path).exists() - assert str(paths.work.join(source_file)) not in linked diff --git a/test/test_compat_jinja.py b/test/test_compat_jinja.py deleted file mode 100644 index 7e2b766..0000000 --- a/test/test_compat_jinja.py +++ /dev/null @@ -1,198 +0,0 @@ -"""Test jinja""" - -import os -import pytest -import utils - -# These tests are for the template processing in YADM_COMPATIBILITY=1 mode -pytestmark = pytest.mark.deprecated - - -@pytest.fixture(scope='module') -def envtpl_present(runner): - """Is envtpl present and working?""" - try: - run = runner(command=['envtpl', '-h']) - if run.success: - return True - except OSError: - pass - return False - - -@pytest.mark.usefixtures('ds1_copy') -def test_local_override(runner, yadm_y, paths, - tst_distro, envtpl_present): - """Test local overrides""" - if not envtpl_present: - pytest.skip('Unable to test without envtpl.') - - # define local overrides - utils.set_local(paths, 'class', 'or-class') - utils.set_local(paths, 'hostname', 'or-hostname') - utils.set_local(paths, 'os', 'or-os') - utils.set_local(paths, 'user', 'or-user') - - template = ( - 'j2-{{ YADM_CLASS }}-' - '{{ YADM_OS }}-{{ YADM_HOSTNAME }}-' - '{{ YADM_USER }}-{{ YADM_DISTRO }}' - '-{%- ' - f"include '{utils.INCLUDE_FILE}'" - ' -%}' - ) - expected = ( - f'j2-or-class-or-os-or-hostname-or-user-{tst_distro}' - f'-{utils.INCLUDE_CONTENT}' - ) - - utils.create_alt_files(paths, '##yadm.j2', content=template, - includefile=True) - - # os.system(f'find {paths.work}' + ' -name *j2 -ls -exec cat \'{}\' ";"') - # os.system(f'find {paths.work}') - # run alt to trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('alt'), env=env) - assert run.success - assert run.err == '' - created = utils.parse_alt_output(run.out, linked=False) - - # assert the proper creation has occurred - for file_path in (utils.ALT_FILE1, utils.ALT_FILE2): - source_file = file_path + '##yadm.j2' - assert paths.work.join(file_path).isfile() - lines = paths.work.join(file_path).readlines(cr=False) - assert lines[0] == source_file - assert lines[1] == expected - assert str(paths.work.join(source_file)) in created - - -@pytest.mark.parametrize('autoalt', [None, 'true', 'false']) -@pytest.mark.usefixtures('ds1_copy') -def test_auto_alt(runner, yadm_y, paths, autoalt, tst_sys, - envtpl_present): - """Test setting auto-alt""" - - if not envtpl_present: - pytest.skip('Unable to test without envtpl.') - - # set the value of auto-alt - if autoalt: - os.system(' '.join(yadm_y('config', 'yadm.auto-alt', autoalt))) - - # create file - jinja_suffix = '##yadm.j2' - utils.create_alt_files(paths, jinja_suffix, content='{{ YADM_OS }}') - - # run status to possibly trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('status'), env=env) - assert run.success - assert run.err == '' - created = utils.parse_alt_output(run.out, linked=False) - - # assert the proper creation has occurred - for file_path in (utils.ALT_FILE1, utils.ALT_FILE2): - source_file = file_path + jinja_suffix - if autoalt == 'false': - assert not paths.work.join(file_path).exists() - else: - assert paths.work.join(file_path).isfile() - lines = paths.work.join(file_path).readlines(cr=False) - assert lines[0] == source_file - assert lines[1] == tst_sys - # no created output when run via auto-alt - assert str(paths.work.join(source_file)) not in created - - -@pytest.mark.usefixtures('ds1_copy') -def test_jinja_envtpl_missing(runner, paths): - """Test operation when envtpl is missing""" - - script = f""" - YADM_TEST=1 source {paths.pgm} - process_global_args -Y "{paths.yadm}" - set_operating_system - configure_paths - YADM_COMPATIBILITY=1 - ENVTPL_PROGRAM='envtpl_missing' main alt - """ - - utils.create_alt_files(paths, '##yadm.j2') - - run = runner(command=['bash'], inp=script) - assert run.success - assert run.err == '' - assert f'envtpl not available, not creating' in run.out - - -@pytest.mark.parametrize( - 'tracked, encrypt, exclude', [ - (False, False, False), - (True, False, False), - (False, True, False), - (False, True, True), - ], ids=[ - 'untracked', - 'tracked', - 'encrypted', - 'excluded', - ]) -@pytest.mark.usefixtures('ds1_copy') -def test_jinja(runner, yadm_y, paths, - tst_sys, tst_host, tst_user, tst_distro, - tracked, encrypt, exclude, - envtpl_present): - """Test jinja processing""" - - if not envtpl_present: - pytest.skip('Unable to test without envtpl.') - - jinja_suffix = '##yadm.j2' - - # set the class - tst_class = 'testclass' - utils.set_local(paths, 'class', tst_class) - - template = ( - 'j2-{{ YADM_CLASS }}-' - '{{ YADM_OS }}-{{ YADM_HOSTNAME }}-' - '{{ YADM_USER }}-{{ YADM_DISTRO }}' - '-{%- ' - f"include '{utils.INCLUDE_FILE}'" - ' -%}' - ) - expected = ( - f'j2-{tst_class}-' - f'{tst_sys}-{tst_host}-' - f'{tst_user}-{tst_distro}' - f'-{utils.INCLUDE_CONTENT}' - ) - - utils.create_alt_files(paths, jinja_suffix, content=template, - tracked=tracked, encrypt=encrypt, exclude=exclude, - includefile=True) - - # run alt to trigger linking - env = os.environ.copy() - env['YADM_COMPATIBILITY'] = '1' - run = runner(yadm_y('alt'), env=env) - assert run.success - assert run.err == '' - created = utils.parse_alt_output(run.out, linked=False) - - # assert the proper creation has occurred - for file_path in (utils.ALT_FILE1, utils.ALT_FILE2): - source_file = file_path + jinja_suffix - if tracked or (encrypt and not exclude): - assert paths.work.join(file_path).isfile() - lines = paths.work.join(file_path).readlines(cr=False) - assert lines[0] == source_file - assert lines[1] == expected - assert str(paths.work.join(source_file)) in created - else: - assert not paths.work.join(file_path).exists() - assert str(paths.work.join(source_file)) not in created diff --git a/test/test_config.py b/test/test_config.py index 4e44b1c..d364128 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -10,7 +10,7 @@ TEST_VALUE = 'testvalue' TEST_FILE = f'[{TEST_SECTION}]\n\t{TEST_ATTRIBUTE} = {TEST_VALUE}' -def test_config_no_params(runner, yadm_y, supported_configs): +def test_config_no_params(runner, yadm_cmd, supported_configs): """No parameters Display instructions @@ -18,7 +18,7 @@ def test_config_no_params(runner, yadm_y, supported_configs): Exit with 0 """ - run = runner(yadm_y('config')) + run = runner(yadm_cmd('config')) assert run.success assert run.err == '' @@ -27,21 +27,21 @@ def test_config_no_params(runner, yadm_y, supported_configs): assert config in run.out -def test_config_read_missing(runner, yadm_y): +def test_config_read_missing(runner, yadm_cmd): """Read missing attribute Display an empty value Exit with 0 """ - run = runner(yadm_y('config', TEST_KEY)) + run = runner(yadm_cmd('config', TEST_KEY)) assert run.success assert run.err == '' assert run.out == '' -def test_config_write(runner, yadm_y, paths): +def test_config_write(runner, yadm_cmd, paths): """Write attribute Display no output @@ -49,7 +49,7 @@ def test_config_write(runner, yadm_y, paths): Exit with 0 """ - run = runner(yadm_y('config', TEST_KEY, TEST_VALUE)) + run = runner(yadm_cmd('config', TEST_KEY, TEST_VALUE)) assert run.success assert run.err == '' @@ -57,7 +57,7 @@ def test_config_write(runner, yadm_y, paths): assert paths.config.read().strip() == TEST_FILE -def test_config_read(runner, yadm_y, paths): +def test_config_read(runner, yadm_cmd, paths): """Read attribute Display value @@ -65,14 +65,14 @@ def test_config_read(runner, yadm_y, paths): """ paths.config.write(TEST_FILE) - run = runner(yadm_y('config', TEST_KEY)) + run = runner(yadm_cmd('config', TEST_KEY)) assert run.success assert run.err == '' assert run.out.strip() == TEST_VALUE -def test_config_update(runner, yadm_y, paths): +def test_config_update(runner, yadm_cmd, paths): """Update attribute Display no output @@ -82,7 +82,7 @@ def test_config_update(runner, yadm_y, paths): paths.config.write(TEST_FILE) - run = runner(yadm_y('config', TEST_KEY, TEST_VALUE + 'extra')) + run = runner(yadm_cmd('config', TEST_KEY, TEST_VALUE + 'extra')) assert run.success assert run.err == '' @@ -92,7 +92,7 @@ def test_config_update(runner, yadm_y, paths): @pytest.mark.usefixtures('ds1_repo_copy') -def test_config_local_read(runner, yadm_y, paths, supported_local_configs): +def test_config_local_read(runner, yadm_cmd, paths, supported_local_configs): """Read local attribute Display value from the repo config @@ -107,14 +107,14 @@ def test_config_local_read(runner, yadm_y, paths, supported_local_configs): # run yadm config for config in supported_local_configs: - run = runner(yadm_y('config', config)) + run = runner(yadm_cmd('config', config)) assert run.success assert run.err == '' assert run.out.strip() == f'value_of_{config}' @pytest.mark.usefixtures('ds1_repo_copy') -def test_config_local_write(runner, yadm_y, paths, supported_local_configs): +def test_config_local_write(runner, yadm_cmd, paths, supported_local_configs): """Write local attribute Display no output @@ -124,7 +124,7 @@ def test_config_local_write(runner, yadm_y, paths, supported_local_configs): # run yadm config for config in supported_local_configs: - run = runner(yadm_y('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 == '' @@ -137,3 +137,27 @@ def test_config_local_write(runner, yadm_y, paths, supported_local_configs): assert run.success 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 + + Update configuration file + Display value + Exit with 0 + """ + + config_file = paths.root + '/folder/does/not/exist/config' + + run = runner( + yadm_cmd('--yadm-config', config_file, 'config', TEST_KEY, TEST_VALUE)) + + assert run.success + assert run.err == '' + assert run.out == '' + + run = runner(yadm_cmd('--yadm-config', config_file, 'config', TEST_KEY)) + + assert run.success + assert run.err == '' + assert run.out.strip() == TEST_VALUE diff --git a/test/test_default_remote_branch.py b/test/test_default_remote_branch.py new file mode 100644 index 0000000..6405417 --- /dev/null +++ b/test/test_default_remote_branch.py @@ -0,0 +1,27 @@ +"""Unit tests: _default_remote_branch()""" +import pytest + + +@pytest.mark.parametrize('condition', ['found', 'missing']) +def test(runner, paths, condition): + """Test _default_remote_branch()""" + test_branch = 'test/branch' + output = f'ref: refs/heads/{test_branch}\\tHEAD\\n' + if condition == 'missing': + output = 'output that is missing ref' + script = f""" + YADM_TEST=1 source {paths.pgm} + function git() {{ + printf '{output}'; + printf 'mock stderr\\n' 1>&2 + }} + _default_remote_branch URL + """ + print(condition) + run = runner(command=['bash'], inp=script) + assert run.success + assert run.err == '' + if condition == 'found': + assert run.out.strip() == test_branch + else: + assert run.out.strip() == 'master' diff --git a/test/test_encryption.py b/test/test_encryption.py index b0d00de..fea2ff0 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -61,7 +61,7 @@ def asymmetric_key(runner, gnupg): @pytest.fixture -def encrypt_targets(yadm_y, paths): +def encrypt_targets(yadm_cmd, paths): """Fixture for setting up data to encrypt This fixture: @@ -78,7 +78,7 @@ def encrypt_targets(yadm_y, paths): """ # init empty yadm repo - os.system(' '.join(yadm_y('init', '-w', str(paths.work), '-f'))) + os.system(' '.join(yadm_cmd('init', '-w', str(paths.work), '-f'))) expected = [] @@ -186,7 +186,7 @@ def decrypt_targets(tmpdir_factory, runner, gnupg): 'overwrite', [False, True], ids=['clean', 'overwrite']) def test_symmetric_encrypt( - runner, yadm_y, paths, encrypt_targets, + runner, yadm_cmd, paths, encrypt_targets, gnupg, bad_phrase, overwrite, missing_encrypt): """Test symmetric encryption""" @@ -203,7 +203,7 @@ def test_symmetric_encrypt( env = os.environ.copy() env['GNUPGHOME'] = gnupg.home - run = runner(yadm_y('encrypt'), env=env) + run = runner(yadm_cmd('encrypt'), env=env) if missing_encrypt or bad_phrase: assert run.failure @@ -212,7 +212,7 @@ def test_symmetric_encrypt( assert run.err == '' if missing_encrypt: - assert 'does not exist' in run.out + assert 'does not exist' in run.err elif bad_phrase: assert 'Invalid passphrase' in run.err else: @@ -230,12 +230,12 @@ def test_symmetric_encrypt( 'dolist', [False, True], ids=['decrypt', 'list']) def test_symmetric_decrypt( - runner, yadm_y, paths, decrypt_targets, gnupg, + runner, yadm_cmd, paths, decrypt_targets, gnupg, dolist, archive_exists, bad_phrase): """Test decryption""" # init empty yadm repo - os.system(' '.join(yadm_y('init', '-w', str(paths.work), '-f'))) + os.system(' '.join(yadm_cmd('init', '-w', str(paths.work), '-f'))) if bad_phrase: gnupg.pw('') @@ -256,7 +256,7 @@ def test_symmetric_decrypt( if dolist: args.append('-l') - run = runner(yadm_y('decrypt') + args, env=env) + run = runner(yadm_cmd('decrypt') + args, env=env) if archive_exists and not bad_phrase: assert run.success @@ -284,16 +284,16 @@ def test_symmetric_decrypt( 'overwrite', [False, True], ids=['clean', 'overwrite']) def test_asymmetric_encrypt( - runner, yadm_y, paths, encrypt_targets, gnupg, + runner, yadm_cmd, paths, encrypt_targets, gnupg, overwrite, key_exists, ask): """Test asymmetric encryption""" # specify encryption recipient if ask: - os.system(' '.join(yadm_y('config', 'yadm.gpg-recipient', 'ASK'))) + 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_y('config', 'yadm.gpg-recipient', KEY_NAME))) + os.system(' '.join(yadm_cmd('config', 'yadm.gpg-recipient', KEY_NAME))) expect = [] if overwrite: @@ -305,7 +305,7 @@ def test_asymmetric_encrypt( env = os.environ.copy() env['GNUPGHOME'] = gnupg.home - run = runner(yadm_y('encrypt'), env=env, expect=expect) + run = runner(yadm_cmd('encrypt'), env=env, expect=expect) if key_exists: assert run.success @@ -313,7 +313,7 @@ def test_asymmetric_encrypt( runner, gnupg, paths.archive, encrypt_targets) else: assert run.failure - assert 'Unable to write' in run.out + assert 'Unable to write' in run.out if expect else run.err if ask: assert 'Enter the user ID' in run.out @@ -321,17 +321,17 @@ def test_asymmetric_encrypt( @pytest.mark.usefixtures('asymmetric_key') @pytest.mark.usefixtures('encrypt_targets') -def test_multi_key(runner, yadm_y, gnupg): +def test_multi_key(runner, yadm_cmd, gnupg): """Test multiple recipients""" # specify two encryption recipient - os.system(' '.join(yadm_y( + os.system(' '.join(yadm_cmd( 'config', 'yadm.gpg-recipient', f'"{KEY_NAME} second-key"'))) env = os.environ.copy() env['GNUPGHOME'] = gnupg.home - run = runner(yadm_y('encrypt'), env=env) + run = runner(yadm_cmd('encrypt'), env=env) assert run.failure assert 'second-key: skipped: No public key' in run.err @@ -345,12 +345,12 @@ def test_multi_key(runner, yadm_y, gnupg): 'dolist', [False, True], ids=['decrypt', 'list']) def test_asymmetric_decrypt( - runner, yadm_y, paths, decrypt_targets, gnupg, + runner, yadm_cmd, paths, decrypt_targets, gnupg, dolist, key_exists): """Test decryption""" # init empty yadm repo - os.system(' '.join(yadm_y('init', '-w', str(paths.work), '-f'))) + os.system(' '.join(yadm_cmd('init', '-w', str(paths.work), '-f'))) decrypt_targets['asymmetric'].copy(paths.archive) @@ -366,7 +366,7 @@ def test_asymmetric_decrypt( args.append('-l') env = os.environ.copy() env['GNUPGHOME'] = gnupg.home - run = runner(yadm_y('decrypt') + args, env=env) + run = runner(yadm_cmd('decrypt') + args, env=env) if key_exists: assert run.success @@ -380,7 +380,7 @@ def test_asymmetric_decrypt( assert paths.work.join(filename).read() == filename else: assert run.failure - assert 'Unable to extract encrypted files' in run.out + assert 'Unable to extract encrypted files' in run.err @pytest.mark.parametrize( @@ -388,7 +388,7 @@ def test_asymmetric_decrypt( [False, 'y', 'n'], ids=['tracked', 'untracked_answer_y', 'untracked_answer_n']) def test_offer_to_add( - runner, yadm_y, paths, encrypt_targets, gnupg, untracked): + 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. @@ -408,10 +408,10 @@ def test_offer_to_add( expect.append(('add it now', untracked)) else: worktree_archive.write('exists') - os.system(' '.join(yadm_y('add', str(worktree_archive)))) + os.system(' '.join(yadm_cmd('add', str(worktree_archive)))) run = runner( - yadm_y('encrypt', '--yadm-archive', str(worktree_archive)), + yadm_cmd('encrypt', '--yadm-archive', str(worktree_archive)), env=env, expect=expect ) @@ -422,7 +422,7 @@ def test_offer_to_add( runner, gnupg, worktree_archive, encrypt_targets) run = runner( - yadm_y('status', '--porcelain', '-uall', str(worktree_archive))) + yadm_cmd('status', '--porcelain', '-uall', str(worktree_archive))) assert run.success assert run.err == '' @@ -438,7 +438,7 @@ def test_offer_to_add( @pytest.mark.usefixtures('ds1_copy') -def test_encrypt_added_to_exclude(runner, yadm_y, paths, gnupg): +def test_encrypt_added_to_exclude(runner, yadm_cmd, paths, gnupg): """Confirm that .config/yadm/encrypt is added to exclude""" gnupg.pw(PASSPHRASE) @@ -450,7 +450,7 @@ def test_encrypt_added_to_exclude(runner, yadm_y, paths, gnupg): paths.work.join('test-encrypt-data').write('') exclude_file.write('original-data', ensure=True) - run = runner(yadm_y('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() diff --git a/test/test_enter.py b/test/test_enter.py index d1f65d0..f5ea2d8 100644 --- a/test/test_enter.py +++ b/test/test_enter.py @@ -17,7 +17,7 @@ import pytest 'shell-noexec', ]) @pytest.mark.usefixtures('ds1_copy') -def test_enter(runner, yadm_y, paths, shell, success): +def test_enter(runner, yadm_cmd, paths, shell, success): """Enter tests""" env = os.environ.copy() if shell == 'delete': @@ -33,15 +33,15 @@ def test_enter(runner, yadm_y, paths, shell, success): else: env['SHELL'] = shell - run = runner(command=yadm_y('enter'), env=env) + run = runner(command=yadm_cmd('enter'), env=env) assert run.success == success - assert run.err == '' prompt = f'yadm shell ({paths.repo})' if success: assert run.out.startswith('Entering yadm repo') assert run.out.rstrip().endswith('Leaving yadm repo') - if not success: - assert 'does not refer to an executable' in run.out + 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 @@ -63,8 +63,12 @@ def test_enter(runner, yadm_y, paths, shell, success): '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_y, paths, shell, opts, path, cmd): +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 '' @@ -83,9 +87,13 @@ def test_enter_shell_ops(runner, yadm_y, paths, shell, opts, path, cmd): enter_cmd += test_cmd env = os.environ.copy() + env['TERM'] = term env['SHELL'] = custom_shell - run = runner(command=yadm_y(*enter_cmd), env=env) + if shell == 'zsh' and term == 'dumb': + opts += ' --no-zle' + + run = runner(command=yadm_cmd(*enter_cmd), env=env) if cmd == 'cmd-bad-exit': assert run.failure else: diff --git a/test/test_git_crypt.py b/test/test_ext_crypt.py index 6b92de9..cb74afc 100644 --- a/test/test_git_crypt.py +++ b/test/test_ext_crypt.py @@ -1,4 +1,4 @@ -"""Test git-crypt""" +"""Test external encryption commands""" import pytest @@ -8,15 +8,21 @@ import pytest [False, 'installed', 'installed-but-failed'], ids=['not-installed', 'installed', 'installed-but-failed'] ) -def test_git_crypt(runner, yadm, paths, tmpdir, crypt): - """git-crypt tests""" +@pytest.mark.parametrize( + '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-git-crypt') + pgm = bindir.join('test-ext-crypt') if crypt: - pgm.write(f'#!/bin/sh\necho git-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') @@ -24,8 +30,8 @@ def test_git_crypt(runner, yadm, paths, tmpdir, crypt): script = f""" YADM_TEST=1 source {yadm} YADM_REPO={paths.repo} - GIT_CRYPT_PROGRAM="{pgm}" - git_crypt "param1" + {var}="{pgm}" + {cmd} "param1" """ run = runner(command=['bash'], inp=script) @@ -35,8 +41,8 @@ def test_git_crypt(runner, yadm, paths, tmpdir, crypt): assert run.failure else: assert run.success - assert run.out.strip() == 'git-crypt ran' + assert run.out.strip() == 'ext-crypt ran' + assert run.err == '' else: assert run.failure - assert f"command '{pgm}' cannot be located" in run.out - assert run.err == '' + assert f"command '{pgm}' cannot be located" in run.err diff --git a/test/test_git.py b/test/test_git.py index 427c54a..76eccab 100644 --- a/test/test_git.py +++ b/test/test_git.py @@ -5,7 +5,7 @@ import pytest @pytest.mark.usefixtures('ds1_copy') -def test_git(runner, yadm_y, paths): +def test_git(runner, yadm_cmd, paths): """Test series of passthrough git commands Passthru unknown commands to Git @@ -17,14 +17,14 @@ def test_git(runner, yadm_y, paths): """ # passthru unknown commands to Git - run = runner(command=yadm_y('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 == '' # git command 'add' - badfile - run = runner(command=yadm_y('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 == '' @@ -32,19 +32,19 @@ def test_git(runner, yadm_y, paths): # git command 'add' newfile = paths.work.join('test_git') newfile.write('test_git') - run = runner(command=yadm_y('add', '-v', str(newfile))) + run = runner(command=yadm_cmd('add', '-v', str(newfile))) assert run.success assert run.err == '' assert "add 'test_git'" in run.out # git command 'status' - run = runner(command=yadm_y('status')) + run = runner(command=yadm_cmd('status')) assert run.success assert run.err == '' assert re.search(r'new file:\s+test_git', run.out) # git command 'commit' - run = runner(command=yadm_y('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 @@ -52,7 +52,7 @@ def test_git(runner, yadm_y, paths): assert re.search(r'create mode .+ test_git', run.out) # git command 'log' - run = runner(command=yadm_y('log', '--oneline')) + run = runner(command=yadm_cmd('log', '--oneline')) assert run.success assert run.err == '' assert 'Add test_git' in run.out diff --git a/test/test_help.py b/test/test_help.py index 79a7652..0d8f2c3 100644 --- a/test/test_help.py +++ b/test/test_help.py @@ -1,17 +1,19 @@ """Test help""" +import pytest -def test_missing_command(runner, yadm_y): +def test_missing_command(runner, yadm_cmd): """Run without any command""" - run = runner(command=yadm_y()) + run = runner(command=yadm_cmd()) assert run.failure assert run.err == '' assert run.out.startswith('Usage: yadm') -def test_help_command(runner, yadm_y): +@pytest.mark.parametrize('cmd', ['--help', 'help']) +def test_help_command(runner, yadm_cmd, cmd): """Run with help command""" - run = runner(command=yadm_y('help')) + run = runner(command=yadm_cmd(cmd)) assert run.failure assert run.err == '' assert run.out.startswith('Usage: yadm') diff --git a/test/test_hooks.py b/test/test_hooks.py index 36f3d98..704636a 100644 --- a/test/test_hooks.py +++ b/test/test_hooks.py @@ -21,8 +21,9 @@ import pytest 'pre-post-success', 'pre-post-fail', ]) +@pytest.mark.parametrize('cmd', ['--version', 'version']) def test_hooks( - runner, yadm_y, paths, + runner, yadm_cmd, paths, cmd, pre, pre_code, post, post_code): """Test pre/post hook""" @@ -33,7 +34,7 @@ def test_hooks( create_hook(paths, 'post_version', post_code) # run yadm - run = runner(yadm_y('version')) + 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 == '' @@ -53,7 +54,7 @@ def test_hooks( # repo fixture is needed to test the population of YADM_HOOK_WORK @pytest.mark.usefixtures('ds1_repo_copy') -def test_hook_env(runner, yadm_y, paths): +def test_hook_env(runner, yadm_cmd, paths): """Test hook environment""" # test will be done with a non existent "git" passthru command @@ -65,7 +66,7 @@ def test_hook_env(runner, yadm_y, paths): hook.write('#!/bin/bash\nenv\ndeclare\n') hook.chmod(0o755) - run = runner(yadm_y(cmd, 'extra_args')) + run = runner(yadm_cmd(cmd, 'extra_args')) # expect passthru to fail assert run.failure @@ -78,7 +79,7 @@ def test_hook_env(runner, yadm_y, paths): 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 f'YADM_ENCRYPT_INCLUDE_FILES=\n' in run.out + assert 'YADM_ENCRYPT_INCLUDE_FILES=\n' in run.out # verify the hook environment contains certain exported functions for func in [ @@ -103,7 +104,7 @@ def test_hook_env(runner, yadm_y, paths): assert 'YADM_ENCRYPT_INCLUDE_FILES=a\nb\nc\n' in run.out -def test_escaped(runner, yadm_y, paths): +def test_escaped(runner, yadm_cmd, paths): """Test escaped values in YADM_HOOK_FULL_COMMAND""" # test will be done with a non existent "git" passthru command @@ -115,7 +116,7 @@ def test_escaped(runner, yadm_y, paths): hook.write('#!/bin/bash\nenv\n') hook.chmod(0o755) - run = runner(yadm_y(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 @@ -126,6 +127,39 @@ def test_escaped(runner, yadm_y, paths): 'a\\ b c\\\td e\\\\f\n') in run.out +@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') + hook.chmod(0o644) + if condition == 'exec': + hook.chmod(0o755) + + mingw = 'OPERATING_SYSTEM="MINGWx"' if condition == 'mingw' else '' + script = f""" + YADM_TEST=1 source {paths.pgm} + YADM_HOOKS="{paths.hooks}" + HOOK_COMMAND="{cmd}" + {mingw} + invoke_hook "pre" + """ + run = runner(command=['bash'], inp=script) + + if condition != 'mingw': + assert run.success + assert run.err == '' + else: + assert run.failure + assert 'Permission denied' in run.err + + 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) diff --git a/test/test_init.py b/test/test_init.py index 1519b38..c738a02 100644 --- a/test/test_init.py +++ b/test/test_init.py @@ -19,7 +19,7 @@ import pytest ]) @pytest.mark.usefixtures('ds1_work_copy') def test_init( - runner, yadm_y, paths, repo_config, alt_work, repo_present, force): + runner, yadm_cmd, paths, repo_config, alt_work, repo_present, force): """Test init Repos should have attribs: @@ -51,16 +51,16 @@ def test_init( args.append('-f') # run init - run = runner(yadm_y(*args), env={'HOME': home}) - assert run.err == '' + run = runner(yadm_cmd(*args), env={'HOME': home}) if repo_present and not force: assert run.failure - assert 'repo already exists' in run.out + 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 run.err == '' if repo_present: assert not old_repo.isfile(), 'Original repo still exists' diff --git a/test/test_introspect.py b/test/test_introspect.py index fcadf14..b292bd4 100644 --- a/test/test_introspect.py +++ b/test/test_introspect.py @@ -13,13 +13,13 @@ import pytest 'switches', ]) def test_introspect_category( - runner, yadm_y, paths, name, + runner, yadm_cmd, paths, name, supported_commands, supported_configs, supported_switches): """Validate introspection category""" if name: - run = runner(command=yadm_y('introspect', name)) + run = runner(command=yadm_cmd('introspect', name)) else: - run = runner(command=yadm_y('introspect')) + run = runner(command=yadm_cmd('introspect')) assert run.success assert run.err == '' diff --git a/test/test_list.py b/test/test_list.py index c2d8631..dcfe500 100644 --- a/test/test_list.py +++ b/test/test_list.py @@ -11,7 +11,7 @@ import pytest 'subdir', ]) @pytest.mark.usefixtures('ds1_copy') -def test_list(runner, yadm_y, paths, ds1, location): +def test_list(runner, yadm_cmd, paths, ds1, location): """List tests""" if location == 'work': run_dir = paths.work @@ -23,7 +23,7 @@ def test_list(runner, yadm_y, paths, ds1, location): with run_dir.as_cwd(): # test with '-a' # should get all tracked files, relative to the work path - run = runner(command=yadm_y('list', '-a')) + run = runner(command=yadm_cmd('list', '-a')) assert run.success assert run.err == '' returned_files = set(run.out.splitlines()) @@ -33,7 +33,7 @@ def test_list(runner, yadm_y, 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_y('list')) + run = runner(command=yadm_cmd('list')) assert run.success assert run.err == '' returned_files = set(run.out.splitlines()) diff --git a/test/test_perms.py b/test/test_perms.py index 0eb8add..4f052bd 100644 --- a/test/test_perms.py +++ b/test/test_perms.py @@ -6,12 +6,13 @@ import pytest @pytest.mark.parametrize('autoperms', ['notest', 'unset', 'true', 'false']) @pytest.mark.usefixtures('ds1_copy') -def test_perms(runner, yadm_y, paths, ds1, autoperms): +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_y('config', 'yadm.auto-perms', autoperms))) + 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')] @@ -38,7 +39,7 @@ def test_perms(runner, yadm_y, paths, ds1, autoperms): cmd = 'perms' if autoperms != 'notest': cmd = 'status' - run = runner(yadm_y(cmd), env={'HOME': paths.work}) + run = runner(yadm_cmd(cmd), env={'HOME': paths.work}) assert run.success assert run.err == '' if cmd == 'perms': @@ -62,15 +63,15 @@ def test_perms(runner, yadm_y, paths, ds1, autoperms): @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_y, paths, ds1, sshperms, gpgperms): +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_y('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_y('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')] @@ -81,7 +82,7 @@ def test_perms_control(runner, yadm_y, paths, ds1, sshperms, gpgperms): assert not oct(private.stat().mode).endswith('00'), ( 'Path started secured') - run = runner(yadm_y('perms'), env={'HOME': paths.work}) + run = runner(yadm_cmd('perms'), env={'HOME': paths.work}) assert run.success assert run.err == '' assert run.out == '' diff --git a/test/test_unit_configure_paths.py b/test/test_unit_configure_paths.py index 332277d..1a6fea9 100644 --- a/test/test_unit_configure_paths.py +++ b/test/test_unit_configure_paths.py @@ -2,19 +2,21 @@ import pytest -ARCHIVE = 'files.gpg' +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', [ (None, {}), - ('-Y', {}), + ('-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'}), @@ -23,6 +25,7 @@ YDIR = '.config/yadm' ], ids=[ 'default', 'override yadm dir', + 'override yadm data', 'override repo', 'override config', 'override encrypt', @@ -36,6 +39,8 @@ def test_config(runner, paths, override, expect): args = [] if override == '-Y': matches = match_map('/' + opath) + if override == '--yadm-data': + matches = match_map(None, '/' + opath) if override: args = [override, '/' + opath] @@ -49,18 +54,20 @@ def test_config(runner, paths, override, expect): run_test(runner, paths, args, matches.values(), 0) -def match_map(yadm_dir=None): +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]) + if not yadm_data: + yadm_data = '/'.join([HOME, YDATA]) return { 'yadm': f'YADM_DIR="{yadm_dir}"', - 'repo': f'YADM_REPO="{yadm_dir}/{REPO}"', + '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_dir}/{ARCHIVE}"', + 'archive': f'YADM_ARCHIVE="{yadm_data}/{ARCHIVE}"', 'bootstrap': f'YADM_BOOTSTRAP="{yadm_dir}/{BOOTSTRAP}"', - 'git': f'GIT_DIR="{yadm_dir}/{REPO}"', + 'git': f'GIT_DIR="{yadm_data}/{REPO}"', } @@ -70,12 +77,15 @@ def run_test(runner, paths, args, expected_matches, expected_code=0): script = f""" YADM_TEST=1 HOME="{HOME}" source {paths.pgm} process_global_args {argstring} - HOME="{HOME}" set_yadm_dir + XDG_CONFIG_HOME= + XDG_DATA_HOME= + HOME="{HOME}" set_yadm_dirs configure_paths declare -p | grep -E '(YADM|GIT)_' """ run = runner(command=['bash'], inp=script) assert run.code == expected_code - assert run.err == '' + assert run.success == (run.code == 0) + assert (run.err if run.success else run.out) == '' for match in expected_matches: - assert match in run.out + assert match in run.out if run.success else run.err diff --git a/test/test_unit_copy_perms.py b/test/test_unit_copy_perms.py new file mode 100644 index 0000000..3c79768 --- /dev/null +++ b/test/test_unit_copy_perms.py @@ -0,0 +1,53 @@ +"""Unit tests: copy_perms""" +import os +import pytest + +OCTAL = '7654' +NON_OCTAL = '9876' + + +@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.chmod(src_mode) + + dest = tmpdir.join('dest') + dest.write('test', ensure=True) + dest.chmod(dst_mode) + + override_stat = '' + if stat_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) + assert run.success + 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']) +def test_get_mode(runner, yadm, stat_output): + """Test function get_mode""" + script = f""" + YADM_TEST=1 source {yadm} + function stat() {{ echo {stat_output}; }} + mode=$(get_mode abc) + echo "MODE:$mode" + """ + run = runner(command=['bash'], inp=script) + assert run.success + assert run.err == '' + expected = OCTAL if stat_output == OCTAL else "" + assert f'MODE:{expected}\n' in run.out diff --git a/test/test_unit_encryption.py b/test/test_unit_encryption.py new file mode 100644 index 0000000..ab03c62 --- /dev/null +++ b/test/test_unit_encryption.py @@ -0,0 +1,135 @@ +"""Unit tests: encryption functions""" + +import pytest + + +@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') + + script = f""" + YADM_TEST=1 source {paths.pgm} + YADM_DIR="{paths.yadm}" + set_yadm_dirs + configure_paths + _get_cipher test-archive + echo "output_archive:$output_archive" + echo "yadm_cipher:$yadm_cipher" + """ + 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 + else: + assert 'yadm_cipher:gpg' in run.out + + +@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""" + + script = f""" + YADM_TEST=1 source {paths.pgm} + YADM_DIR="{paths.yadm}" + set_yadm_dirs + configure_paths + function mock_openssl() {{ echo openssl $*; }} + function mock_gpg() {{ echo gpg $*; }} + function _get_cipher() {{ + output_archive="$1" + yadm_cipher="{cipher}" + }} + OPENSSL_PROGRAM=mock_openssl + GPG_PROGRAM=mock_gpg + {mode} {paths.archive} + """ + run = runner(command=['bash'], inp=script) + + if cipher != 'bad': + assert run.success + assert run.out.startswith(cipher) + assert str(paths.archive) in run.out + assert run.err == '' + else: + assert run.failure + assert 'Unknown cipher' in run.err + + +@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') + + script = f""" + YADM_TEST=1 source {paths.pgm} + YADM_DIR="{paths.yadm}" + set_yadm_dirs + configure_paths + result=$(_get_openssl_ciphername) + echo "result:$result" + """ + run = runner(command=['bash'], inp=script) + assert run.success + assert run.err == '' + if condition == 'override': + assert run.out.strip() == 'result:override-cipher' + else: + assert run.out.strip() == 'result:aes-256-cbc' + + +@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') + + script = f""" + YADM_TEST=1 source {paths.pgm} + YADM_DIR="{paths.yadm}" + set_yadm_dirs + configure_paths + function _get_openssl_ciphername() {{ echo "testcipher"; }} + _set_openssl_options + echo "result:${{OPENSSL_OPTS[@]}}" + """ + run = runner(command=['bash'], inp=script) + assert run.success + 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 + + +@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}') + + script = f""" + YADM_TEST=1 source {paths.pgm} + YADM_DIR="{paths.yadm}" + set_yadm_dirs + configure_paths + _set_gpg_options + echo "result:${{GPG_OPTS[@]}}" + """ + 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}' + else: + assert run.out.strip() == 'result:-c' diff --git a/test/test_unit_issue_legacy_path_warning.py b/test/test_unit_issue_legacy_path_warning.py index 3f5cd6f..e43228b 100644 --- a/test/test_unit_issue_legacy_path_warning.py +++ b/test/test_unit_issue_legacy_path_warning.py @@ -6,36 +6,36 @@ import pytest 'legacy_path', [ None, 'repo.git', - 'config', - 'encrypt', 'files.gpg', - 'bootstrap', - 'hooks/pre_command', - 'hooks/post_command', ], ) @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, legacy_path): +def test_legacy_warning(tmpdir, runner, yadm, upgrade, override, legacy_path): """Use issue_legacy_path_warning""" home = tmpdir.mkdir('home') if legacy_path: - home.mkdir(f'.yadm').ensure(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 '' script = f""" + XDG_CONFIG_HOME= + XDG_DATA_HOME= HOME={home} YADM_TEST=1 source {yadm} {main_args} + {override} + set_yadm_dirs issue_legacy_path_warning - echo "LWI:$LEGACY_WARNING_ISSUED" """ run = runner(command=['bash'], inp=script) assert run.success - assert run.err == '' - if legacy_path and not upgrade: - assert 'Legacy configuration paths have been detected' in run.out - assert 'LWI:1' in run.out + assert run.out == '' + if legacy_path and (not upgrade) and (not override): + assert 'Legacy paths have been detected' in run.err else: - assert run.out.rstrip() == 'LWI:0' + assert 'Legacy paths have been detected' not in run.err diff --git a/test/test_unit_record_score.py b/test/test_unit_record_score.py index 525c967..78596e1 100644 --- a/test/test_unit_record_score.py +++ b/test/test_unit_record_score.py @@ -112,3 +112,30 @@ def test_existing_template(runner, yadm): 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' + script = f""" + YADM_TEST=1 source {yadm} + {INIT_VARS} + YADM_CONFIG={config} + record_score "1" "tgt_before" "src_before" + record_template "tgt_tmp" "cmd_tmp" "src_tmp" + record_score "2" "{config}" "src_config" + record_score "3" "tgt_after" "src_after" + {REPORT_RESULTS} + echo "CMD_VALUE:${{alt_template_cmds[@]}}" + echo "CMD_INDEX:${{!alt_template_cmds[@]}}" + """ + 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 diff --git a/test/test_unit_report_invalid_alts.py b/test/test_unit_report_invalid_alts.py index 7aa93bb..8730d61 100644 --- a/test/test_unit_report_invalid_alts.py +++ b/test/test_unit_report_invalid_alts.py @@ -2,38 +2,29 @@ import pytest -@pytest.mark.parametrize( - 'condition', [ - 'compat', - 'previous-message', - 'invalid-alts', - 'no-invalid-alts', - ]) -def test_report_invalid_alts(runner, yadm, condition): +@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""" - compat = '' - previous = '' + lwi = '' alts = 'INVALID_ALT=()' - if condition == 'compat': - compat = 'YADM_COMPATIBILITY=1' - if condition == 'previous-message': - previous = 'LEGACY_WARNING_ISSUED=1' - if condition == 'invalid-alts': + if previous: + lwi = 'LEGACY_WARNING_ISSUED=1' + if not valid: alts = 'INVALID_ALT=("file##invalid")' script = f""" YADM_TEST=1 source {yadm} - {compat} - {previous} + {lwi} {alts} report_invalid_alts """ run = runner(command=['bash'], inp=script) assert run.success - assert run.err == '' - if condition == 'invalid-alts': - assert 'WARNING' in run.out - assert 'file##invalid' in run.out + assert run.out == '' + if not valid and not previous: + assert 'WARNING' in run.err + assert 'file##invalid' in run.err else: - assert run.out == '' + assert run.err == '' diff --git a/test/test_unit_score_file.py b/test/test_unit_score_file.py index 679229f..450c154 100644 --- a/test/test_unit_score_file.py +++ b/test/test_unit_score_file.py @@ -196,6 +196,28 @@ def test_score_values( assert run.out == expected +@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}' + if ext: + filename += f',{ext}.xyz' + expected = '' + script = f""" + YADM_TEST=1 source {yadm} + score=0 + local_user={local_user} + score_file "{filename}" + echo "$score" + """ + expected = f'{1000 + CONDITION["user"]["modifier"]}\n' + run = runner(command=['bash'], inp=script) + assert run.success + assert run.err == '' + assert run.out == expected + + def test_score_values_templates(runner, yadm): """Test score results""" local_class = 'testclass' @@ -260,19 +282,3 @@ def test_template_recording(runner, yadm, cmd_generated): assert run.success assert run.err == '' assert run.out.rstrip() == expected - - -def test_invalid(runner, yadm): - """Verify invalid alternates are noted in INVALID_ALT""" - - invalid_file = "file##invalid" - - script = f""" - YADM_TEST=1 source {yadm} - score_file "{invalid_file}" - echo "INVALID:${{INVALID_ALT[@]}}" - """ - run = runner(command=['bash'], inp=script) - assert run.success - assert run.err == '' - assert run.out.rstrip() == f'INVALID:{invalid_file}' diff --git a/test/test_unit_set_local_alt_values.py b/test/test_unit_set_local_alt_values.py index d3d2447..e49d055 100644 --- a/test/test_unit_set_local_alt_values.py +++ b/test/test_unit_set_local_alt_values.py @@ -26,7 +26,7 @@ def test_set_local_alt_values( script = f""" YADM_TEST=1 source {yadm} && set_operating_system && - YADM_DIR={paths.yadm} configure_paths && + YADM_DIR={paths.yadm} YADM_DATA={paths.data} configure_paths && set_local_alt_values echo "class='$local_class'" echo "os='$local_system'" @@ -52,12 +52,12 @@ def test_set_local_alt_values( assert f"os='{tst_sys}'" in run.out if override == 'hostname': - assert f"host='override'" in run.out + assert "host='override'" in run.out else: assert f"host='{tst_host}'" in run.out if override == 'user': - assert f"user='override'" in run.out + assert "user='override'" in run.out else: assert f"user='{tst_user}'" in run.out @@ -67,6 +67,7 @@ def test_distro(runner, yadm): script = f""" YADM_TEST=1 source {yadm} + function config() {{ echo "$1"; }} function query_distro() {{ echo "testdistro"; }} set_local_alt_values echo "distro='$local_distro'" diff --git a/test/test_unit_set_yadm_dir.py b/test/test_unit_set_yadm_dir.py index 65459f8..32af8bf 100644 --- a/test/test_unit_set_yadm_dir.py +++ b/test/test_unit_set_yadm_dir.py @@ -1,35 +1,48 @@ -"""Unit tests: set_yadm_dir""" +"""Unit tests: set_yadm_dirs""" import pytest @pytest.mark.parametrize( - 'condition', - ['basic', 'override', 'xdg_config_home', 'legacy'], + 'condition', [ + 'basic', + 'override', + 'override_data', + 'xdg_config_home', + 'xdg_data_home' + ], ) -def test_set_yadm_dir(runner, yadm, condition): - """Test set_yadm_dir""" +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 == 'legacy': - setup = 'YADM_COMPATIBILITY=1' + elif condition == 'xdg_data_home': + setup = 'XDG_DATA_HOME=/xdg' script = f""" HOME=/testhome YADM_TEST=1 source {yadm} + XDG_CONFIG_HOME= + XDG_DATA_HOME= {setup} - set_yadm_dir - echo "$YADM_DIR" + set_yadm_dirs + echo "YADM_DIR=$YADM_DIR" + echo "YADM_DATA=$YADM_DATA" """ run = runner(command=['bash'], inp=script) assert run.success assert run.err == '' if condition == 'basic': - assert run.out.rstrip() == '/testhome/.config/yadm' + assert 'YADM_DIR=/testhome/.config/yadm' in run.out + assert 'YADM_DATA=/testhome/.local/share/yadm' in run.out elif condition == 'override': - assert run.out.rstrip() == '/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 run.out.rstrip() == '/xdg/yadm' - elif condition == 'legacy': - assert run.out.rstrip() == '/testhome/.yadm' + 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 42464b8..639cb29 100644 --- a/test/test_unit_template_default.py +++ b/test/test_unit_template_default.py @@ -1,4 +1,7 @@ """Unit tests: template_default""" +import os + +FILE_MODE = 0o754 # these values are also testing the handling of bizarre characters LOCAL_CLASS = "default_Test+@-!^Class" @@ -85,12 +88,43 @@ Included section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again) end of template ''' +INCLUDE_BASIC = 'basic\n' +INCLUDE_VARIABLES = '''\ +included <{{ yadm.class }}> file + +empty line above +''' +INCLUDE_NESTED = 'no newline at the end' + +TEMPLATE_INCLUDE = '''\ +The first line +{% include empty %} +An empty file removes the line above +{%include basic%} +{% include "./variables.{{ yadm.os }}" %} +{% include dir/nested %} +Include basic again: +{% include basic %} +''' +EXPECTED_INCLUDE = f'''\ +The first line +An empty file removes the line above +basic +included <{LOCAL_CLASS}> file + +empty line above +no newline at the end +Include basic again: +basic +''' + def test_template_default(runner, yadm, tmpdir): """Test template_default""" input_file = tmpdir.join('input') input_file.write(TEMPLATE, ensure=True) + input_file.chmod(FILE_MODE) output_file = tmpdir.join('output') script = f""" @@ -107,6 +141,7 @@ def test_template_default(runner, yadm, tmpdir): assert run.success assert run.err == '' assert output_file.read() == EXPECTED + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode def test_source(runner, yadm, tmpdir): @@ -114,6 +149,7 @@ def test_source(runner, yadm, tmpdir): input_file = tmpdir.join('input') input_file.write('{{yadm.source}}', ensure=True) + input_file.chmod(FILE_MODE) output_file = tmpdir.join('output') script = f""" @@ -125,3 +161,38 @@ def test_source(runner, yadm, tmpdir): assert run.success assert run.err == '' assert output_file.read().strip() == str(input_file) + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode + + +def test_include(runner, yadm, tmpdir): + """Test include""" + + empty_file = tmpdir.join('empty') + empty_file.write('', ensure=True) + + basic_file = tmpdir.join('basic') + basic_file.write(INCLUDE_BASIC) + + variables_file = tmpdir.join(f'variables.{LOCAL_SYSTEM}') + variables_file.write(INCLUDE_VARIABLES) + + nested_file = tmpdir.join('dir').join('nested') + nested_file.write(INCLUDE_NESTED, ensure=True) + + input_file = tmpdir.join('input') + input_file.write(TEMPLATE_INCLUDE) + input_file.chmod(FILE_MODE) + output_file = tmpdir.join('output') + + script = f""" + YADM_TEST=1 source {yadm} + set_awk + local_class="{LOCAL_CLASS}" + local_system="{LOCAL_SYSTEM}" + template_default "{input_file}" "{output_file}" + """ + run = runner(command=['bash'], inp=script) + assert run.success + assert run.err == '' + assert output_file.read() == EXPECTED_INCLUDE + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode diff --git a/test/test_unit_template_esh.py b/test/test_unit_template_esh.py new file mode 100644 index 0000000..e975152 --- /dev/null +++ b/test/test_unit_template_esh.py @@ -0,0 +1,121 @@ +"""Unit tests: template_esh""" +import os + +FILE_MODE = 0o754 + +LOCAL_CLASS = "esh_Test+@-!^Class" +LOCAL_SYSTEM = "esh_Test+@-!^System" +LOCAL_HOST = "esh_Test+@-!^Host" +LOCAL_USER = "esh_Test+@-!^User" +LOCAL_DISTRO = "esh_Test+@-!^Distro" +TEMPLATE = f''' +start of template +esh class = ><%=$YADM_CLASS%>< +esh os = ><%=$YADM_OS%>< +esh host = ><%=$YADM_HOSTNAME%>< +esh user = ><%=$YADM_USER%>< +esh distro = ><%=$YADM_DISTRO%>< +<% if [ "$YADM_CLASS" = "wrongclass1" ]; then -%> +wrong class 1 +<% fi -%> +<% if [ "$YADM_CLASS" = "{LOCAL_CLASS}" ]; then -%> +Included section for class = <%=$YADM_CLASS%> (<%=$YADM_CLASS%> repeated) +<% fi -%> +<% if [ "$YADM_CLASS" = "wrongclass2" ]; then -%> +wrong class 2 +<% fi -%> +<% if [ "$YADM_OS" = "wrongos1" ]; then -%> +wrong os 1 +<% fi -%> +<% if [ "$YADM_OS" = "{LOCAL_SYSTEM}" ]; then -%> +Included section for os = <%=$YADM_OS%> (<%=$YADM_OS%> repeated) +<% fi -%> +<% if [ "$YADM_OS" = "wrongos2" ]; then -%> +wrong os 2 +<% fi -%> +<% if [ "$YADM_HOSTNAME" = "wronghost1" ]; then -%> +wrong host 1 +<% fi -%> +<% if [ "$YADM_HOSTNAME" = "{LOCAL_HOST}" ]; then -%> +Included section for host = <%=$YADM_HOSTNAME%> (<%=$YADM_HOSTNAME%> again) +<% fi -%> +<% if [ "$YADM_HOSTNAME" = "wronghost2" ]; then -%> +wrong host 2 +<% fi -%> +<% if [ "$YADM_USER" = "wronguser1" ]; then -%> +wrong user 1 +<% fi -%> +<% if [ "$YADM_USER" = "{LOCAL_USER}" ]; then -%> +Included section for user = <%=$YADM_USER%> (<%=$YADM_USER%> repeated) +<% fi -%> +<% if [ "$YADM_USER" = "wronguser2" ]; then -%> +wrong user 2 +<% fi -%> +<% if [ "$YADM_DISTRO" = "wrongdistro1" ]; then -%> +wrong distro 1 +<% fi -%> +<% if [ "$YADM_DISTRO" = "{LOCAL_DISTRO}" ]; then -%> +Included section for distro = <%=$YADM_DISTRO%> (<%=$YADM_DISTRO%> again) +<% fi -%> +<% if [ "$YADM_DISTRO" = "wrongdistro2" ]; then -%> +wrong distro 2 +<% fi -%> +end of template +''' +EXPECTED = f''' +start of template +esh class = >{LOCAL_CLASS}< +esh os = >{LOCAL_SYSTEM}< +esh host = >{LOCAL_HOST}< +esh user = >{LOCAL_USER}< +esh distro = >{LOCAL_DISTRO}< +Included section for class = {LOCAL_CLASS} ({LOCAL_CLASS} repeated) +Included section for os = {LOCAL_SYSTEM} ({LOCAL_SYSTEM} repeated) +Included section for host = {LOCAL_HOST} ({LOCAL_HOST} again) +Included section for user = {LOCAL_USER} ({LOCAL_USER} repeated) +Included section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again) +end of template +''' + + +def test_template_esh(runner, yadm, tmpdir): + """Test processing by esh""" + + input_file = tmpdir.join('input') + input_file.write(TEMPLATE, ensure=True) + input_file.chmod(FILE_MODE) + output_file = tmpdir.join('output') + + script = f""" + YADM_TEST=1 source {yadm} + local_class="{LOCAL_CLASS}" + local_system="{LOCAL_SYSTEM}" + local_host="{LOCAL_HOST}" + local_user="{LOCAL_USER}" + local_distro="{LOCAL_DISTRO}" + template_esh "{input_file}" "{output_file}" + """ + run = runner(command=['bash'], inp=script) + assert run.success + assert run.err == '' + assert output_file.read().strip() == str(EXPECTED).strip() + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode + + +def test_source(runner, yadm, tmpdir): + """Test YADM_SOURCE""" + + input_file = tmpdir.join('input') + input_file.write('<%= $YADM_SOURCE %>', ensure=True) + input_file.chmod(FILE_MODE) + output_file = tmpdir.join('output') + + script = f""" + YADM_TEST=1 source {yadm} + template_esh "{input_file}" "{output_file}" + """ + run = runner(command=['bash'], inp=script) + assert run.success + 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 85c6822..f81f4c6 100644 --- a/test/test_unit_template_j2.py +++ b/test/test_unit_template_j2.py @@ -1,6 +1,9 @@ """Unit tests: template_j2cli & template_envtpl""" +import os import pytest +FILE_MODE = 0o754 + LOCAL_CLASS = "j2_Test+@-!^Class" LOCAL_SYSTEM = "j2_Test+@-!^System" LOCAL_HOST = "j2_Test+@-!^Host" @@ -82,6 +85,7 @@ def test_template_j2(runner, yadm, tmpdir, processor): input_file = tmpdir.join('input') input_file.write(TEMPLATE, ensure=True) + input_file.chmod(FILE_MODE) output_file = tmpdir.join('output') script = f""" @@ -97,6 +101,7 @@ def test_template_j2(runner, yadm, tmpdir, processor): assert run.success 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')) @@ -105,6 +110,7 @@ def test_source(runner, yadm, tmpdir, processor): input_file = tmpdir.join('input') input_file.write('{{YADM_SOURCE}}', ensure=True) + input_file.chmod(FILE_MODE) output_file = tmpdir.join('output') script = f""" @@ -115,3 +121,4 @@ def test_source(runner, yadm, tmpdir, processor): assert run.success 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 73f4cac..3463740 100644 --- a/test/test_unit_upgrade.py +++ b/test/test_unit_upgrade.py @@ -1,53 +1,39 @@ """Unit tests: upgrade""" import pytest -LEGACY_PATHS = [ - 'config', - 'encrypt', - 'files.gpg', - 'bootstrap', - 'hooks/pre_command', - 'hooks/post_command', -] -# used: -# YADM_COMPATIBILITY -# YADM_DIR -# YADM_LEGACY_DIR -# GIT_PROGRAM -@pytest.mark.parametrize('condition', ['compat', 'equal', 'existing_repo']) +@pytest.mark.parametrize('condition', ['override', 'equal', 'existing_repo']) def test_upgrade_errors(tmpdir, runner, yadm, condition): """Test upgrade() error conditions""" - compatibility = 'YADM_COMPATIBILITY=1' if condition == 'compat' else '' - home = tmpdir.mkdir('home') yadm_dir = home.join('.config/yadm') - legacy_dir = home.join('.yadm') + yadm_data = home.join('.local/share/yadm') + override = '' + if condition == 'override': + override = 'override' if condition == 'equal': - legacy_dir = yadm_dir + yadm_data = yadm_dir if condition == 'existing_repo': yadm_dir.ensure_dir('repo.git') - legacy_dir.ensure_dir('repo.git') + yadm_data.ensure_dir('repo.git') script = f""" YADM_TEST=1 source {yadm} - {compatibility} YADM_DIR="{yadm_dir}" - YADM_REPO="{yadm_dir}/repo.git" - YADM_LEGACY_DIR="{legacy_dir}" + YADM_DATA="{yadm_data}" + YADM_REPO="{yadm_data}/repo.git" + YADM_LEGACY_ARCHIVE="files.gpg" + YADM_OVERRIDE_REPO="{override}" upgrade """ run = runner(command=['bash'], inp=script) assert run.failure - assert run.err == '' - assert 'Unable to upgrade' in run.out - if condition == 'compat': - assert 'YADM_COMPATIBILITY' in run.out - if condition == 'equal': - assert 'has been resolved as' in run.out - if condition == 'existing_repo': - assert 'already exists' in run.out + 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( @@ -59,22 +45,29 @@ 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') - legacy_dir = home.join('.yadm') + yadm_data = home.join('.local/share/yadm') + yadm_legacy = home.join('.yadm') if condition != 'no-paths': - legacy_dir.join('repo.git/config').write('test-repo', ensure=True) - for lpath in LEGACY_PATHS: - legacy_dir.join(lpath).write(lpath, ensure=True) + 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 in ['tracked', 'submodules']: + if condition != 'no-paths': mock_git = f''' function git() {{ echo "$@" - if [[ "$*" == *.gitmodules* ]]; then - return { '0' if condition == 'submodules' else '1' } + if [[ "$*" = *"submodule status" ]]; then + { 'echo " 1234567 mymodule (1.0)"' + if condition == 'submodules' else ':' } + fi + if [[ "$*" = *ls-files* ]]; then + return { 1 if condition == 'untracked' else 0 } fi return 0 }} @@ -82,9 +75,11 @@ def test_upgrade(tmpdir, runner, yadm, condition): script = f""" YADM_TEST=1 source {yadm} + YADM_LEGACY_DIR="{yadm_legacy}" YADM_DIR="{yadm_dir}" - YADM_REPO="{yadm_dir}/repo.git" - YADM_LEGACY_DIR="{legacy_dir}" + YADM_DATA="{yadm_data}" + YADM_REPO="{yadm_data}/repo.git" + YADM_ARCHIVE="{yadm_data}/archive" GIT_PROGRAM="git" {mock_git} function cd {{ echo "$@";}} @@ -96,25 +91,32 @@ def test_upgrade(tmpdir, runner, yadm, condition): if condition == 'no-paths': assert 'Upgrade is not necessary' in run.out else: - for lpath in LEGACY_PATHS + ['repo.git']: + 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 {legacy_dir.join(lpath)} ' - f'to {yadm_dir.join(lpath)}') + 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_dir.join('repo.git/config').read() - for lpath in LEGACY_PATHS: - assert lpath in yadm_dir.join(lpath).read() + 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']: - for lpath in LEGACY_PATHS: - expected = ( - f'mv {legacy_dir.join(lpath)} ' - f'{yadm_dir.join(lpath)}') - assert expected in run.out + 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 -f .' in run.out - assert 'submodule update --init --recursive' in run.out + assert 'submodule deinit -- mymodule' in run.out + assert 'submodule update --init --recursive -- mymodule' \ + in run.out else: - assert 'submodule deinit -f .' 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 3233a3d..8302f3c 100644 --- a/test/test_unit_x_program.py +++ b/test/test_unit_x_program.py @@ -16,24 +16,24 @@ import pytest ]) @pytest.mark.parametrize('program', ['git', 'gpg']) def test_x_program( - runner, yadm_y, paths, program, executable, success, value, match): + 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_y( + os.system(' '.join(yadm_cmd( 'config', f'yadm.{program}-program', executable))) # test require_[git,gpg] script = f""" YADM_TEST=1 source {paths.pgm} - YADM_CONFIG="{paths.config}" + YADM_OVERRIDE_CONFIG="{paths.config}" + configure_paths require_{program} echo ${program.upper()}_PROGRAM """ run = runner(command=['bash'], inp=script) assert run.success == success - assert run.err == '' # [GIT,GPG]_PROGRAM set correctly if value == 'program': @@ -43,4 +43,6 @@ def test_x_program( # error reported about bad config if match: - assert match in run.out + assert match in run.err + else: + assert run.err == '' diff --git a/test/test_upgrade.py b/test/test_upgrade.py new file mode 100644 index 0000000..1ccf075 --- /dev/null +++ b/test/test_upgrade.py @@ -0,0 +1,129 @@ +"""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']) +def test_upgrade(tmpdir, runner, versions, submodule): + """Upgrade tests""" + # pylint: disable=too-many-statements + home = tmpdir.mkdir('HOME') + env = {'HOME': str(home)} + + if submodule: + 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]) + assert run.success + + 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' + run = runner([yadm, *args], shell=True, cwd=str(home), env=env) + assert run.success + if check_stderr: + assert run.err == '' + return run + + # Initialize the repo with the first version + first = versions[0] + run_version(first, 'init') + + 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_upgraded_cloned_submodule = '2.5.0' not in versions[1:] + if can_upgraded_cloned_submodule: + # Check out a repo and then add it as a submodule + run = runner(['git', '-C', str(home), 'clone', str(ext_repo), 'b']) + assert run.success + 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, 'commit', '-m', '"Add submodules"') + + 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('encrypt').write('secret') + + 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 upgrade with intermediate versions and latest + latest = None + for version in versions[1:] + (latest,): + run = run_version(version, 'upgrade', check_stderr=not submodule) + if submodule: + lines = run.err.splitlines() + if can_upgraded_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] + del lines[:3] + for line in lines: + assert line.startswith('Submodule') + assert 'registered for path' in line + + # Verify result for the final upgrade + run_version(latest, 'status') + + run = run_version(latest, 'show', 'HEAD:file') + assert run.out == 'some data' + + if submodule: + if can_upgraded_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' + + yadm_dir = home.join('.config/yadm') + + 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' + + run = run_version(latest, 'config', 'local.class') + assert run.out.rstrip() == 'test' + + 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 023eb82..08bebe1 100644 --- a/test/test_version.py +++ b/test/test_version.py @@ -26,10 +26,11 @@ def test_semantic_version(expected_version): 'does not conform to MAJOR.MINOR.PATCH') +@pytest.mark.parametrize('cmd', ['--version', 'version']) def test_reported_version( - runner, yadm_y, expected_version): + runner, yadm_cmd, cmd, expected_version): """Report correct version""" - run = runner(command=yadm_y('version')) + run = runner(command=yadm_cmd(cmd)) assert run.success assert run.err == '' assert run.out == f'yadm {expected_version}\n' |