1
0
mirror of https://github.com/pi-hole/pi-hole synced 2024-12-22 06:48:07 +00:00

Update python tests and add black code formatter action (#4926)

This commit is contained in:
Adam Warner 2022-09-26 20:00:27 +01:00 committed by GitHub
commit 1b0b0ca445
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 758 additions and 649 deletions

View File

@ -12,29 +12,33 @@ jobs:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
-
name: Checkout repository
uses: actions/checkout@v3.0.2
-
name: Check scripts in repository are executable
run: |
IFS=$'\n';
for f in $(find . -name '*.sh'); do if [[ ! -x $f ]]; then echo "$f is not executable" && FAIL=1; fi ;done
unset IFS;
# If FAIL is 1 then we fail.
[[ $FAIL == 1 ]] && exit 1 || echo "Scripts are executable!"
-
name: Spell-Checking
uses: codespell-project/actions-codespell@master
with:
ignore_words_file: .codespellignore
-
name: Get editorconfig-checker
uses: editorconfig-checker/action-editorconfig-checker@main # tag v1.0.0 is really out of date
-
name: Run editorconfig-checker
run: editorconfig-checker
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Check scripts in repository are executable
run: |
IFS=$'\n';
for f in $(find . -name '*.sh'); do if [[ ! -x $f ]]; then echo "$f is not executable" && FAIL=1; fi ;done
unset IFS;
# If FAIL is 1 then we fail.
[[ $FAIL == 1 ]] && exit 1 || echo "Scripts are executable!"
- name: Spell-Checking
uses: codespell-project/actions-codespell@master
with:
ignore_words_file: .codespellignore
- name: Get editorconfig-checker
uses: editorconfig-checker/action-editorconfig-checker@main # tag v1.0.0 is really out of date
- name: Run editorconfig-checker
run: editorconfig-checker
- name: Check python code formatting with black
uses: psf/black@stable
with:
src: "./test"
options: "--check --diff --color"
distro-test:
if: github.event.pull_request.draft == false
@ -43,21 +47,21 @@ jobs:
strategy:
fail-fast: false
matrix:
distro: [debian_10, debian_11, ubuntu_20, ubuntu_22, centos_8, fedora_34]
distro:
[debian_10, debian_11, ubuntu_20, ubuntu_22, centos_8, fedora_34]
env:
DISTRO: ${{matrix.distro}}
steps:
-
name: Checkout repository
uses: actions/checkout@v3.0.2
-
name: Set up Python 3.10
uses: actions/setup-python@v4.2.0
with:
python-version: '3.10'
-
name: Install dependencies
run: pip install -r test/requirements.txt
-
name: Test with tox
run: tox -c test/tox.${DISTRO}.ini
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Set up Python 3.10
uses: actions/setup-python@v4.2.0
with:
python-version: "3.10"
- name: Install dependencies
run: pip install -r test/requirements.txt
- name: Test with tox
run: tox -c test/tox.${DISTRO}.ini

View File

@ -6,12 +6,12 @@ from textwrap import dedent
SETUPVARS = {
'PIHOLE_INTERFACE': 'eth99',
'PIHOLE_DNS_1': '4.2.2.1',
'PIHOLE_DNS_2': '4.2.2.2'
"PIHOLE_INTERFACE": "eth99",
"PIHOLE_DNS_1": "4.2.2.1",
"PIHOLE_DNS_2": "4.2.2.2",
}
IMAGE = 'pytest_pihole:test_container'
IMAGE = "pytest_pihole:test_container"
tick_box = "[\x1b[1;32m\u2713\x1b[0m]"
cross_box = "[\x1b[1;31m\u2717\x1b[0m]"
@ -38,132 +38,187 @@ testinfra.backend.docker.DockerBackend.run = run_bash
@pytest.fixture
def host():
# run a container
docker_id = subprocess.check_output(
['docker', 'run', '-t', '-d', '--cap-add=ALL', IMAGE]).decode().strip()
docker_id = (
subprocess.check_output(["docker", "run", "-t", "-d", "--cap-add=ALL", IMAGE])
.decode()
.strip()
)
# return a testinfra connection to the container
docker_host = testinfra.get_host("docker://" + docker_id)
yield docker_host
# at the end of the test suite, destroy the container
subprocess.check_call(['docker', 'rm', '-f', docker_id])
subprocess.check_call(["docker", "rm", "-f", docker_id])
# Helper functions
def mock_command(script, args, container):
'''
"""
Allows for setup of commands we don't really want to have to run for real
in unit tests
'''
full_script_path = '/usr/local/bin/{}'.format(script)
mock_script = dedent(r'''\
"""
full_script_path = "/usr/local/bin/{}".format(script)
mock_script = dedent(
r"""\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1" in'''.format(script=script))
case "\$1" in""".format(
script=script
)
)
for k, v in args.items():
case = dedent('''
case = dedent(
"""
{arg})
echo {res}
exit {retcode}
;;'''.format(arg=k, res=v[0], retcode=v[1]))
;;""".format(
arg=k, res=v[0], retcode=v[1]
)
)
mock_script += case
mock_script += dedent('''
esac''')
container.run('''
mock_script += dedent(
"""
esac"""
)
container.run(
"""
cat <<EOF> {script}\n{content}\nEOF
chmod +x {script}
rm -f /var/log/{scriptlog}'''.format(script=full_script_path,
content=mock_script,
scriptlog=script))
rm -f /var/log/{scriptlog}""".format(
script=full_script_path, content=mock_script, scriptlog=script
)
)
def mock_command_passthrough(script, args, container):
'''
"""
Per other mock_command* functions, allows intercepting of commands we don't want to run for real
in unit tests, however also allows only specific arguments to be mocked. Anything not defined will
be passed through to the actual command.
Example use-case: mocking `git pull` but still allowing `git clone` to work as intended
'''
orig_script_path = container.check_output('command -v {}'.format(script))
full_script_path = '/usr/local/bin/{}'.format(script)
mock_script = dedent(r'''\
"""
orig_script_path = container.check_output("command -v {}".format(script))
full_script_path = "/usr/local/bin/{}".format(script)
mock_script = dedent(
r"""\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1" in'''.format(script=script))
case "\$1" in""".format(
script=script
)
)
for k, v in args.items():
case = dedent('''
case = dedent(
"""
{arg})
echo {res}
exit {retcode}
;;'''.format(arg=k, res=v[0], retcode=v[1]))
;;""".format(
arg=k, res=v[0], retcode=v[1]
)
)
mock_script += case
mock_script += dedent(r'''
mock_script += dedent(
r"""
*)
{orig_script_path} "\$@"
;;'''.format(orig_script_path=orig_script_path))
mock_script += dedent('''
esac''')
container.run('''
;;""".format(
orig_script_path=orig_script_path
)
)
mock_script += dedent(
"""
esac"""
)
container.run(
"""
cat <<EOF> {script}\n{content}\nEOF
chmod +x {script}
rm -f /var/log/{scriptlog}'''.format(script=full_script_path,
content=mock_script,
scriptlog=script))
rm -f /var/log/{scriptlog}""".format(
script=full_script_path, content=mock_script, scriptlog=script
)
)
def mock_command_run(script, args, container):
'''
"""
Allows for setup of commands we don't really want to have to run for real
in unit tests
'''
full_script_path = '/usr/local/bin/{}'.format(script)
mock_script = dedent(r'''\
"""
full_script_path = "/usr/local/bin/{}".format(script)
mock_script = dedent(
r"""\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1 \$2" in'''.format(script=script))
case "\$1 \$2" in""".format(
script=script
)
)
for k, v in args.items():
case = dedent('''
case = dedent(
"""
\"{arg}\")
echo {res}
exit {retcode}
;;'''.format(arg=k, res=v[0], retcode=v[1]))
;;""".format(
arg=k, res=v[0], retcode=v[1]
)
)
mock_script += case
mock_script += dedent('''
esac''')
container.run('''
mock_script += dedent(
"""
esac"""
)
container.run(
"""
cat <<EOF> {script}\n{content}\nEOF
chmod +x {script}
rm -f /var/log/{scriptlog}'''.format(script=full_script_path,
content=mock_script,
scriptlog=script))
rm -f /var/log/{scriptlog}""".format(
script=full_script_path, content=mock_script, scriptlog=script
)
)
def mock_command_2(script, args, container):
'''
"""
Allows for setup of commands we don't really want to have to run for real
in unit tests
'''
full_script_path = '/usr/local/bin/{}'.format(script)
mock_script = dedent(r'''\
"""
full_script_path = "/usr/local/bin/{}".format(script)
mock_script = dedent(
r"""\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1 \$2" in'''.format(script=script))
case "\$1 \$2" in""".format(
script=script
)
)
for k, v in args.items():
case = dedent('''
case = dedent(
"""
\"{arg}\")
echo \"{res}\"
exit {retcode}
;;'''.format(arg=k, res=v[0], retcode=v[1]))
;;""".format(
arg=k, res=v[0], retcode=v[1]
)
)
mock_script += case
mock_script += dedent('''
esac''')
container.run('''
mock_script += dedent(
"""
esac"""
)
container.run(
"""
cat <<EOF> {script}\n{content}\nEOF
chmod +x {script}
rm -f /var/log/{scriptlog}'''.format(script=full_script_path,
content=mock_script,
scriptlog=script))
rm -f /var/log/{scriptlog}""".format(
script=full_script_path, content=mock_script, scriptlog=script
)
)
def run_script(Pihole, script):

View File

@ -1,6 +1,5 @@
docker-compose
pytest
pytest-xdist
pytest-cov
pytest-testinfra
tox

View File

@ -2,6 +2,6 @@ from setuptools import setup
setup(
py_modules=[],
setup_requires=['pytest-runner'],
tests_require=['pytest'],
setup_requires=["pytest-runner"],
tests_require=["pytest"],
)

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,27 @@
def test_key_val_replacement_works(host):
''' Confirms addOrEditKeyValPair either adds or replaces a key value pair in a given file '''
host.run('''
"""Confirms addOrEditKeyValPair either adds or replaces a key value pair in a given file"""
host.run(
"""
source /opt/pihole/utils.sh
addOrEditKeyValPair "./testoutput" "KEY_ONE" "value1"
addOrEditKeyValPair "./testoutput" "KEY_TWO" "value2"
addOrEditKeyValPair "./testoutput" "KEY_ONE" "value3"
addOrEditKeyValPair "./testoutput" "KEY_FOUR" "value4"
''')
output = host.run('''
"""
)
output = host.run(
"""
cat ./testoutput
''')
expected_stdout = 'KEY_ONE=value3\nKEY_TWO=value2\nKEY_FOUR=value4\n'
"""
)
expected_stdout = "KEY_ONE=value3\nKEY_TWO=value2\nKEY_FOUR=value4\n"
assert expected_stdout == output.stdout
def test_key_addition_works(host):
''' Confirms addKey adds a key (no value) to a file without duplicating it '''
host.run('''
"""Confirms addKey adds a key (no value) to a file without duplicating it"""
host.run(
"""
source /opt/pihole/utils.sh
addKey "./testoutput" "KEY_ONE"
addKey "./testoutput" "KEY_ONE"
@ -24,17 +29,21 @@ def test_key_addition_works(host):
addKey "./testoutput" "KEY_TWO"
addKey "./testoutput" "KEY_THREE"
addKey "./testoutput" "KEY_THREE"
''')
output = host.run('''
"""
)
output = host.run(
"""
cat ./testoutput
''')
expected_stdout = 'KEY_ONE\nKEY_TWO\nKEY_THREE\n'
"""
)
expected_stdout = "KEY_ONE\nKEY_TWO\nKEY_THREE\n"
assert expected_stdout == output.stdout
def test_key_removal_works(host):
''' Confirms removeKey removes a key or key/value pair '''
host.run('''
"""Confirms removeKey removes a key or key/value pair"""
host.run(
"""
source /opt/pihole/utils.sh
addOrEditKeyValPair "./testoutput" "KEY_ONE" "value1"
addOrEditKeyValPair "./testoutput" "KEY_TWO" "value2"
@ -42,81 +51,100 @@ def test_key_removal_works(host):
addKey "./testoutput" "KEY_FOUR"
removeKey "./testoutput" "KEY_TWO"
removeKey "./testoutput" "KEY_FOUR"
''')
output = host.run('''
"""
)
output = host.run(
"""
cat ./testoutput
''')
expected_stdout = 'KEY_ONE=value1\nKEY_THREE=value3\n'
"""
)
expected_stdout = "KEY_ONE=value1\nKEY_THREE=value3\n"
assert expected_stdout == output.stdout
def test_getFTLAPIPortFile_default(host):
''' Confirms getFTLAPIPortFile returns the default API port file path '''
output = host.run('''
"""Confirms getFTLAPIPortFile returns the default API port file path"""
output = host.run(
"""
source /opt/pihole/utils.sh
getFTLAPIPortFile
''')
expected_stdout = '/run/pihole-FTL.port\n'
"""
)
expected_stdout = "/run/pihole-FTL.port\n"
assert expected_stdout == output.stdout
def test_getFTLAPIPort_default(host):
''' Confirms getFTLAPIPort returns the default API port '''
output = host.run('''
"""Confirms getFTLAPIPort returns the default API port"""
output = host.run(
"""
source /opt/pihole/utils.sh
getFTLAPIPort "/run/pihole-FTL.port"
''')
expected_stdout = '4711\n'
"""
)
expected_stdout = "4711\n"
assert expected_stdout == output.stdout
def test_getFTLAPIPortFile_and_getFTLAPIPort_custom(host):
''' Confirms getFTLAPIPort returns a custom API port in a custom PORTFILE location '''
host.run('''
"""Confirms getFTLAPIPort returns a custom API port in a custom PORTFILE location"""
host.run(
"""
tmpfile=$(mktemp)
echo "PORTFILE=${tmpfile}" > /etc/pihole/pihole-FTL.conf
echo "1234" > ${tmpfile}
''')
output = host.run('''
"""
)
output = host.run(
"""
source /opt/pihole/utils.sh
FTL_API_PORT_FILE=$(getFTLAPIPortFile)
getFTLAPIPort "${FTL_API_PORT_FILE}"
''')
expected_stdout = '1234\n'
"""
)
expected_stdout = "1234\n"
assert expected_stdout == output.stdout
def test_getFTLPIDFile_default(host):
''' Confirms getFTLPIDFile returns the default PID file path '''
output = host.run('''
"""Confirms getFTLPIDFile returns the default PID file path"""
output = host.run(
"""
source /opt/pihole/utils.sh
getFTLPIDFile
''')
expected_stdout = '/run/pihole-FTL.pid\n'
"""
)
expected_stdout = "/run/pihole-FTL.pid\n"
assert expected_stdout == output.stdout
def test_getFTLPID_default(host):
''' Confirms getFTLPID returns the default value if FTL is not running '''
output = host.run('''
"""Confirms getFTLPID returns the default value if FTL is not running"""
output = host.run(
"""
source /opt/pihole/utils.sh
getFTLPID
''')
expected_stdout = '-1\n'
"""
)
expected_stdout = "-1\n"
assert expected_stdout == output.stdout
def test_getFTLPIDFile_and_getFTLPID_custom(host):
''' Confirms getFTLPIDFile returns a custom PID file path '''
host.run('''
"""Confirms getFTLPIDFile returns a custom PID file path"""
host.run(
"""
tmpfile=$(mktemp)
echo "PIDFILE=${tmpfile}" > /etc/pihole/pihole-FTL.conf
echo "1234" > ${tmpfile}
''')
output = host.run('''
"""
)
output = host.run(
"""
source /opt/pihole/utils.sh
FTL_PID_FILE=$(getFTLPIDFile)
getFTLPID "${FTL_PID_FILE}"
''')
expected_stdout = '1234\n'
"""
)
expected_stdout = "1234\n"
assert expected_stdout == output.stdout

View File

@ -8,17 +8,20 @@ from .conftest import (
def test_enable_epel_repository_centos(host):
'''
"""
confirms the EPEL package repository is enabled when installed on CentOS
'''
package_manager_detect = host.run('''
"""
package_manager_detect = host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
''')
expected_stdout = info_box + (' Enabling EPEL package repository '
'(https://fedoraproject.org/wiki/EPEL)')
"""
)
expected_stdout = info_box + (
" Enabling EPEL package repository " "(https://fedoraproject.org/wiki/EPEL)"
)
assert expected_stdout in package_manager_detect.stdout
expected_stdout = tick_box + ' Installed'
expected_stdout = tick_box + " Installed"
assert expected_stdout in package_manager_detect.stdout
epel_package = host.package('epel-release')
epel_package = host.package("epel-release")
assert epel_package.is_installed

View File

@ -6,60 +6,70 @@ from .conftest import (
def mock_selinux_config(state, host):
'''
"""
Creates a mock SELinux config file with expected content
'''
"""
# validate state string
valid_states = ['enforcing', 'permissive', 'disabled']
valid_states = ["enforcing", "permissive", "disabled"]
assert state in valid_states
# getenforce returns the running state of SELinux
mock_command('getenforce', {'*': (state.capitalize(), '0')}, host)
mock_command("getenforce", {"*": (state.capitalize(), "0")}, host)
# create mock configuration with desired content
host.run('''
host.run(
"""
mkdir /etc/selinux
echo "SELINUX={state}" > /etc/selinux/config
'''.format(state=state.lower()))
""".format(
state=state.lower()
)
)
def test_selinux_enforcing_exit(host):
'''
"""
confirms installer prompts to exit when SELinux is Enforcing by default
'''
"""
mock_selinux_config("enforcing", host)
check_selinux = host.run('''
check_selinux = host.run(
"""
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = cross_box + ' Current SELinux: enforcing'
"""
)
expected_stdout = cross_box + " Current SELinux: enforcing"
assert expected_stdout in check_selinux.stdout
expected_stdout = 'SELinux Enforcing detected, exiting installer'
expected_stdout = "SELinux Enforcing detected, exiting installer"
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 1
def test_selinux_permissive(host):
'''
"""
confirms installer continues when SELinux is Permissive
'''
"""
mock_selinux_config("permissive", host)
check_selinux = host.run('''
check_selinux = host.run(
"""
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = tick_box + ' Current SELinux: permissive'
"""
)
expected_stdout = tick_box + " Current SELinux: permissive"
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0
def test_selinux_disabled(host):
'''
"""
confirms installer continues when SELinux is Disabled
'''
"""
mock_selinux_config("disabled", host)
check_selinux = host.run('''
check_selinux = host.run(
"""
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = tick_box + ' Current SELinux: disabled'
"""
)
expected_stdout = tick_box + " Current SELinux: disabled"
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0

View File

@ -1,13 +1,15 @@
def test_epel_and_remi_not_installed_fedora(host):
'''
"""
confirms installer does not attempt to install EPEL/REMI repositories
on Fedora
'''
package_manager_detect = host.run('''
"""
package_manager_detect = host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
''')
assert package_manager_detect.stdout == ''
"""
)
assert package_manager_detect.stdout == ""
epel_package = host.package('epel-release')
epel_package = host.package("epel-release")
assert not epel_package.is_installed

View File

@ -2,7 +2,7 @@
envlist = py3
[testenv]
whitelist_externals = docker
allowlist_externals = docker
deps = -rrequirements.txt
commands = docker build -f _centos_8.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py ./test_centos_fedora_common_support.py ./test_centos_common_support.py

View File

@ -2,7 +2,7 @@
envlist = py3
[testenv]
whitelist_externals = docker
allowlist_externals = docker
deps = -rrequirements.txt
commands = docker build -f _debian_10.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py

View File

@ -2,7 +2,7 @@
envlist = py3
[testenv]
whitelist_externals = docker
allowlist_externals = docker
deps = -rrequirements.txt
commands = docker build -f _debian_11.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py

View File

@ -2,7 +2,7 @@
envlist = py3
[testenv]
whitelist_externals = docker
allowlist_externals = docker
deps = -rrequirements.txt
commands = docker build -f _fedora_34.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py ./test_centos_fedora_common_support.py ./test_fedora_support.py

View File

@ -2,7 +2,7 @@
envlist = py3
[testenv]
whitelist_externals = docker
allowlist_externals = docker
deps = -rrequirements.txt
commands = docker build -f _ubuntu_20.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py

View File

@ -2,7 +2,7 @@
envlist = py3
[testenv]
whitelist_externals = docker
allowlist_externals = docker
deps = -rrequirements.txt
commands = docker build -f _ubuntu_22.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py