1
0
Fork 0
mirror of synced 2024-12-04 14:45:36 -05:00

Compare commits

...

3 commits

Author SHA1 Message Date
Erik Flodin
2a9b8e9537
Merge 80ca4a993a into 30fa6f08a4 2024-11-14 08:43:07 +01:00
Erik Flodin
30fa6f08a4
Update testbed docker image
* Update base image to Ubuntu 24.10. This uses a python version where j2cli no
  longer works when installed using pip so use the version from Ubuntu instead
  which has been patched to work.

* Update shellcheck, pylint, pytest, isort, flake8, black and yamllint to the
  latest versions. This closes #502.

* Use a longer expect timeout to fix tests failing when gpg is killed due to
  this timeout.

* Explicitly flush gpg-agent's cached passwords to fix failing tests with
  latest gnupg. Also clean up after tests to avoid having gpg-agents running
  after the test (e.g. when running tests directly without docker).
2024-11-11 22:30:41 +01:00
Erik Flodin
80ca4a993a
Add support for running external program to "score" alt files
This is a draft implementation for #283.
2021-01-03 16:32:48 +01:00
28 changed files with 85 additions and 39 deletions

View file

@ -1,5 +1,5 @@
PYTESTS = $(wildcard test/test_*.py) PYTESTS = $(wildcard test/test_*.py)
IMAGE = docker.io/yadm/testbed:2023-07-12 IMAGE = docker.io/yadm/testbed:2024-11-11
OCI = docker OCI = docker
.PHONY: all .PHONY: all

View file

@ -7,6 +7,7 @@ markers = [
[tool.pylint.design] [tool.pylint.design]
max-args = 14 max-args = 14
max-positional-arguments = 10
max-locals = 28 max-locals = 28
max-attributes = 8 max-attributes = 8
max-statements = 65 max-statements = 65

View file

@ -1,8 +1,7 @@
FROM ubuntu:23.04 FROM ubuntu:24.10
MAINTAINER Tim Byrne <sultan@locehilios.com>
# Shellcheck and esh versions # Shellcheck and esh versions
ARG SC_VER=0.9.0 ARG SC_VER=0.10.0
ARG ESH_VER=0.3.2 ARG ESH_VER=0.3.2
# Install prerequisites and configure UTF-8 locale # Install prerequisites and configure UTF-8 locale
@ -14,6 +13,7 @@ RUN \
expect \ expect \
git \ git \
gnupg \ gnupg \
j2cli \
locales \ locales \
lsb-release \ lsb-release \
make \ make \
@ -39,10 +39,9 @@ RUN cd /opt \
&& rm -f 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 && ln -s /opt/shellcheck-v$SC_VER/shellcheck /usr/local/bin
# Upgrade pip3 and install requirements # Install requirements
COPY test/requirements.txt /tmp/requirements.txt COPY test/requirements.txt /tmp/requirements.txt
RUN python3 -m pip install --break-system-packages --upgrade pip setuptools \ RUN python3 -m pip install --break-system-packages -r /tmp/requirements.txt \
&& python3 -m pip install --break-system-packages --upgrade -r /tmp/requirements.txt \
&& rm -f /tmp/requirements && rm -f /tmp/requirements
# Install esh # Install esh

View file

@ -9,7 +9,6 @@ import pwd
import shutil import shutil
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
import py
import pytest import pytest
@ -26,37 +25,37 @@ def pytest_addoption(parser):
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def shellcheck_version(): def shellcheck_version():
"""Version of shellcheck supported""" """Version of shellcheck supported"""
return "0.9.0" return "0.10.0"
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def pylint_version(): def pylint_version():
"""Version of pylint supported""" """Version of pylint supported"""
return "2.17.0" return "3.3.1"
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def isort_version(): def isort_version():
"""Version of isort supported""" """Version of isort supported"""
return "5.12.0" return "5.13.2"
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def flake8_version(): def flake8_version():
"""Version of flake8 supported""" """Version of flake8 supported"""
return "6.0.0" return "7.1.1"
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def black_version(): def black_version():
"""Version of black supported""" """Version of black supported"""
return "23.1.0" return "24.10.0"
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def yamllint_version(): def yamllint_version():
"""Version of yamllint supported""" """Version of yamllint supported"""
return "1.30.0" return "1.35.1"
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
@ -246,7 +245,7 @@ class Runner:
if not expect: if not expect:
return return
cmdline = " ".join([f'"{w}"' for w in self.command]) cmdline = " ".join([f'"{w}"' for w in self.command])
expect_script = f"set timeout 2\nspawn {cmdline}\n" expect_script = f"set timeout 5\nspawn {cmdline}\n"
for question, answer in expect: for question, answer in expect:
expect_script += "expect {\n" f'"{question}" {{send "{answer}\\r"}}\n' "timeout {close;exit 128}\n" "}\n" expect_script += "expect {\n" f'"{question}" {{send "{answer}\\r"}}\n' "timeout {close;exit 128}\n" "}\n"
expect_script += "expect eof\n" "foreach {pid spawnid os_error_flag value} [wait] break\n" "exit $value" expect_script += "expect eof\n" "foreach {pid spawnid os_error_flag value} [wait] break\n" "exit $value"
@ -575,17 +574,21 @@ def ds1(ds1_work_copy, paths, ds1_dset):
def gnupg(tmpdir_factory, runner): def gnupg(tmpdir_factory, runner):
"""Location of GNUPGHOME""" """Location of GNUPGHOME"""
def register_gpg_password(password):
"""Publish a new GPG mock password"""
py.path.local("/tmp/mock-password").write(password)
home = tmpdir_factory.mktemp("gnupghome") home = tmpdir_factory.mktemp("gnupghome")
home.chmod(0o700) home.chmod(0o700)
conf = home.join("gpg.conf") conf = home.join("gpg.conf")
conf.write("no-secmem-warning\n") conf.write("no-secmem-warning\n")
conf.chmod(0o600) conf.chmod(0o600)
agentconf = home.join("gpg-agent.conf") agentconf = home.join("gpg-agent.conf")
agentconf.write(f'pinentry-program {os.path.abspath("test/pinentry-mock")}\n' "max-cache-ttl 0\n") agentconf.write(
f"""\
pinentry-program {os.path.abspath("test/pinentry-mock")}
max-cache-ttl 0
browser-socket none
extra-socket none
disable-scdaemon
"""
)
agentconf.chmod(0o600) agentconf.chmod(0o600)
data = collections.namedtuple("GNUPG", ["home", "pw"]) data = collections.namedtuple("GNUPG", ["home", "pw"])
env = os.environ.copy() env = os.environ.copy()
@ -594,4 +597,12 @@ def gnupg(tmpdir_factory, runner):
# this pre-populates std files in the GNUPGHOME # this pre-populates std files in the GNUPGHOME
runner(["gpg", "-k"], env=env) runner(["gpg", "-k"], env=env)
return data(home, register_gpg_password) def register_gpg_password(password):
"""Publish a new GPG mock password and flush cached passwords"""
home.join("mock-password").write(password)
runner(["gpgconf", "--reload", "gpg-agent"], env=env)
yield data(home, register_gpg_password)
runner(["gpgconf", "--kill", "gpg-agent"], env=env)
runner(["gpgconf", "--remove-socketdir", "gpg-agent"], env=env)

View file

@ -6,10 +6,9 @@
echo "OK Pleased to meet you" echo "OK Pleased to meet you"
while read -r line; do while read -r line; do
if [[ $line =~ GETPIN ]]; then if [[ $line =~ GETPIN ]]; then
password="$(cat /tmp/mock-password 2>/dev/null)" password="$(cat "$GNUPGHOME/mock-password" 2>/dev/null)"
if [ -n "$password" ]; then if [ -n "$password" ]; then
echo -n "D " echo "D $password"
echo "$password"
echo "OK"; echo "OK";
else else
echo "CANCEL"; echo "CANCEL";

View file

@ -1,8 +1,8 @@
black==23.1.0 black==24.10.0
envtpl envtpl
flake8==6.0.0 flake8==7.1.1
isort==5.12.0 isort==5.13.2
j2cli j2cli
pylint==2.17.0 pylint==3.3.1
pytest==7.2.2 pytest==8.3.3
yamllint==1.30.0 yamllint==1.35.1

View file

@ -1,4 +1,5 @@
"""Test alt""" """Test alt"""
import os import os
import string import string

View file

@ -2,7 +2,6 @@
import os import os
import shlex import shlex
import time
import pytest import pytest
@ -219,7 +218,6 @@ def test_symmetric_decrypt(runner, yadm_cmd, paths, decrypt_targets, gnupg, doli
if bad_phrase: if bad_phrase:
gnupg.pw("") gnupg.pw("")
time.sleep(1) # allow gpg-agent cache to expire
else: else:
gnupg.pw(PASSPHRASE) gnupg.pw(PASSPHRASE)

View file

@ -1,4 +1,5 @@
"""Test help""" """Test help"""
import pytest import pytest

View file

@ -20,7 +20,8 @@ def test_list(runner, yadm_cmd, paths, ds1, location):
run_dir = paths.work run_dir = paths.work
elif location == "outside": elif location == "outside":
run_dir = paths.work.join("..") run_dir = paths.work.join("..")
elif location == "subdir": else:
assert location == "subdir"
# first directory with tracked data # first directory with tracked data
run_dir = paths.work.join(ds1.tracked_dirs[0]) run_dir = paths.work.join(ds1.tracked_dirs[0])
with run_dir.as_cwd(): with run_dir.as_cwd():

View file

@ -1,4 +1,5 @@
"""Unit tests: choose_template_cmd""" """Unit tests: choose_template_cmd"""
import pytest import pytest
@ -19,7 +20,7 @@ def test_kind_default(runner, yadm, awk, label):
script = f""" script = f"""
YADM_TEST=1 source {yadm} YADM_TEST=1 source {yadm}
function awk_available {{ { awk_avail}; }} function awk_available {{ {awk_avail}; }}
template="$(choose_template_cmd "{label}")" template="$(choose_template_cmd "{label}")"
echo "TEMPLATE:$template" echo "TEMPLATE:$template"
""" """
@ -50,8 +51,8 @@ def test_kind_j2cli_envtpl(runner, yadm, envtpl, j2cli, label):
script = f""" script = f"""
YADM_TEST=1 source {yadm} YADM_TEST=1 source {yadm}
function envtpl_available {{ { envtpl_avail}; }} function envtpl_available {{ {envtpl_avail}; }}
function j2cli_available {{ { j2cli_avail}; }} function j2cli_available {{ {j2cli_avail}; }}
template="$(choose_template_cmd "{label}")" template="$(choose_template_cmd "{label}")"
echo "TEMPLATE:$template" echo "TEMPLATE:$template"
""" """

View file

@ -1,4 +1,5 @@
"""Unit tests: copy_perms""" """Unit tests: copy_perms"""
import os import os
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: exclude_encrypted""" """Unit tests: exclude_encrypted"""
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: issue_legacy_path_warning""" """Unit tests: issue_legacy_path_warning"""
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: private_dirs""" """Unit tests: private_dirs"""
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: query_distro""" """Unit tests: query_distro"""
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: query_distro_family""" """Unit tests: query_distro_family"""
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: record_score""" """Unit tests: record_score"""
import pytest import pytest
INIT_VARS = """ INIT_VARS = """

View file

@ -1,4 +1,5 @@
"""Unit tests: relative_path""" """Unit tests: relative_path"""
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: remove_stale_links""" """Unit tests: remove_stale_links"""
import os import os
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: report_invalid_alts""" """Unit tests: report_invalid_alts"""
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: score_file""" """Unit tests: score_file"""
import pytest import pytest
CONDITION = { CONDITION = {

View file

@ -1,4 +1,5 @@
"""Unit tests: set_local_alt_values""" """Unit tests: set_local_alt_values"""
import pytest import pytest
import utils import utils

View file

@ -1,4 +1,5 @@
"""Unit tests: set_yadm_dirs""" """Unit tests: set_yadm_dirs"""
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: template_esh""" """Unit tests: template_esh"""
import os import os
FILE_MODE = 0o754 FILE_MODE = 0o754

View file

@ -1,4 +1,5 @@
"""Unit tests: template_j2cli & template_envtpl""" """Unit tests: template_j2cli & template_envtpl"""
import os import os
import pytest import pytest

View file

@ -1,4 +1,5 @@
"""Unit tests: upgrade""" """Unit tests: upgrade"""
import pytest import pytest
@ -62,11 +63,10 @@ def test_upgrade(tmpdir, runner, yadm, condition):
function git() {{ function git() {{
echo "$@" echo "$@"
if [[ "$*" = *"submodule status" ]]; then if [[ "$*" = *"submodule status" ]]; then
{ 'echo " 1234567 mymodule (1.0)"' {'echo " 1234567 mymodule (1.0)"' if condition == 'submodules' else ':'}
if condition == 'submodules' else ':' }
fi fi
if [[ "$*" = *ls-files* ]]; then if [[ "$*" = *ls-files* ]]; then
return { 1 if condition == 'untracked' else 0 } return {1 if condition == 'untracked' else 0}
fi fi
return 0 return 0
}} }}

19
yadm
View file

@ -36,6 +36,7 @@ YADM_ENCRYPT="encrypt"
YADM_BOOTSTRAP="bootstrap" YADM_BOOTSTRAP="bootstrap"
YADM_HOOKS="hooks" YADM_HOOKS="hooks"
YADM_ALT="alt" YADM_ALT="alt"
YADM_CHECK_CONDITION="check_condition"
# these are the default paths relative to YADM_DATA # these are the default paths relative to YADM_DATA
YADM_REPO="repo.git" YADM_REPO="repo.git"
@ -175,6 +176,7 @@ function score_file() {
fi fi
score=0 score=0
declare -gA external_cache
IFS=',' read -ra fields <<< "$conditions" IFS=',' read -ra fields <<< "$conditions"
for field in "${fields[@]}"; do for field in "${fields[@]}"; do
label=${field%%.*} label=${field%%.*}
@ -238,6 +240,22 @@ function score_file() {
score=0 score=0
return return
fi fi
elif [[ "$label" =~ ^(x|external)$ ]]; then
if [[ -z "${external_cache[x$value]:-}" ]]; then
if [[ -x "$YADM_CHECK_CONDITION" ]]; then
"$YADM_CHECK_CONDITION" "$value"
external_cache[x$value]=$?
else
debug "No program found to check condition for $src"
[ -n "$loud" ] && echo "No program found to check condition for $src"
fi
fi
if [[ ${external_cache[x$value]:-1} -eq 0 ]]; then
score=$((score + 32))
else
score=0
return
fi
# templates # templates
elif [[ "$label" =~ ^(t|template|yadm)$ ]]; then elif [[ "$label" =~ ^(t|template|yadm)$ ]]; then
score=0 score=0
@ -1720,6 +1738,7 @@ function configure_paths() {
YADM_BOOTSTRAP="$YADM_DIR/$YADM_BOOTSTRAP" YADM_BOOTSTRAP="$YADM_DIR/$YADM_BOOTSTRAP"
YADM_HOOKS="$YADM_DIR/$YADM_HOOKS" YADM_HOOKS="$YADM_DIR/$YADM_HOOKS"
YADM_ALT="$YADM_DIR/$YADM_ALT" YADM_ALT="$YADM_DIR/$YADM_ALT"
YADM_CHECK_CONDITION="$YADM_DIR/$YADM_CHECK_CONDITION"
# change paths to be relative to YADM_DATA # change paths to be relative to YADM_DATA
YADM_REPO="$YADM_DATA/$YADM_REPO" YADM_REPO="$YADM_DATA/$YADM_REPO"