Merge branch 'development' into april-os

This commit is contained in:
darkexplosiveqwx
2026-05-21 22:24:50 +02:00
51 changed files with 973 additions and 1086 deletions
+1
View File
@@ -0,0 +1 @@
test/libs/
-40
View File
@@ -1,40 +0,0 @@
name: "CodeQL"
on:
push:
branches:
- master
- development
pull_request:
branches:
- master
- development
schedule:
- cron: '32 11 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
-
name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
# Initializes the CodeQL tools for scanning.
-
name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 #v4.35.2
with:
languages: 'python'
-
name: Autobuild
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 #v4.35.2
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 #v4.35.2
+4 -28
View File
@@ -7,11 +7,6 @@ on:
permissions:
contents: read
env:
FORCE_COLOR: 1
PYTHONUNBUFFERED: 1
PYTHONUTF8: 1
jobs:
smoke-tests:
if: github.event.pull_request.draft == false
@@ -25,7 +20,7 @@ jobs:
- 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
for f in $(find . -name '*.sh' -o -name '*.bats'); 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!"
@@ -37,7 +32,7 @@ jobs:
display-engine: sarif-fmt
- name: Secret Scanning with TruffleHog
uses: trufflesecurity/trufflehog@17456f8c7d042d8c82c9a8ca9e937231f9f42e26 #v3.95.2
uses: trufflesecurity/trufflehog@37b77001d0174ebec2fcca2bd83ff83a6d45a3ab #v3.95.3
with:
extra_args: --results=verified,unknown
@@ -52,12 +47,6 @@ jobs:
- name: Run editorconfig-checker
run: editorconfig-checker
- name: Check python code formatting with black
uses: psf/black@c6755bb741b6481d6b3d3bb563c83fa060db96c9 #26.3.1
with:
src: "./test"
options: "--check --diff --color"
distro-test:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
@@ -85,22 +74,9 @@ jobs:
alpine_3_22,
alpine_3_23,
]
env:
DISTRO: ${{matrix.distro}}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0
with:
python-version: "3.13"
- name: Install wheel
run: pip install wheel
- name: Install dependencies
run: pip install -r test/requirements.txt
- name: Test with tox
run: tox -c test/tox.${DISTRO}.ini
- name: Run BATS test suite for ${{ matrix.distro }}
run: DISTRO=${{ matrix.distro }} bash test/run.sh
+1 -9
View File
@@ -1,15 +1,7 @@
.DS_Store
*.pyc
*.swp
__pycache__
.cache
.pytest_cache
.tox
.eggs
*.egg-info
.idea/
*.iml
.vscode/
.venv/
.fleet/
.cache/
test/libs/
+2 -1
View File
@@ -162,7 +162,7 @@ EOM
)
# List of required packages on APK based systems
PIHOLE_META_VERSION_APK=0.2
PIHOLE_META_VERSION_APK=0.3
PIHOLE_META_DEPS_APK=(
bash
bash-completion
@@ -172,6 +172,7 @@ PIHOLE_META_DEPS_APK=(
cronie
curl
dialog
gawk
git
grep
iproute2-minimal # piholeARPTable.sh
+56 -51
View File
@@ -772,18 +772,22 @@ gravity_DownloadBlocklistFromUrl() {
# Define the generic error message
curlOutputFormat='%{http_code};No message available. Non supported curl version.'
# Check if the installed curl version supports the "-w %{errormsg}" option (available as of curl 7.75.0)
# (https://github.com/pi-hole/pi-hole/pull/6605#discussion_r3112153347)
# First we get the current curl version.
# Get the current installed curl version.
curlVersion=$(curl --version | awk '{print $2;exit}')
# After that, we pipe the current version along with the string '7.75' (the minimum version supporting the required option.)
# Then we sort the list in natural (version) order and return the first item which will be the lowest version seen.
# If it is "7.75" then the current version is greater than or equal to "7.75.0".
# Compatibility notes:
# Busybox doesn't support some long flags:
# - "sort -V" is short form of "sort --version-sort"
# - "head -n1" is short form of "head --lines=1"
if [[ "$(printf '%s\n' "${curlVersion}" "7.75" | sort -V | head -n1)" == 7.75 ]]; then
# Check if the installed curl version supports the "-w %{errormsg}" option.
# The minimum curl version supporting this option is 7.75.0.
# (https://github.com/pi-hole/pi-hole/pull/6605#discussion_r3112153347)
#
# We use "awk" to compare versions by subtracting 7.75 from the version number.
# If the result is greater than or equal to zero, the option is supported.
# (see https://github.com/pi-hole/pi-hole/issues/6615)
#
# Notes:
# - Use parameter expansion to get only Major and Minor version parts (containing only one dot).
# - The comparison result will be true or false. We use it as exit code.
# - awk considers "true=1". We negate the comparison to exit with "0" when a desired version is found.
if echo "${curlVersion%.*}" | awk '{exit !($1 - 7.75 >= 0)}'; then
# Use the error message returned by curl
curlOutputFormat='%{http_code};%{errormsg}'
fi
@@ -796,48 +800,49 @@ gravity_DownloadBlocklistFromUrl() {
# a generic message is returned.
curlOutput=$(curl --connect-timeout ${curl_connect_timeout} -s --fail -L ${compression:+${compression}} ${customUpstreamResolver:+${customUpstreamResolver}} "${modifiedOptions[@]}" -w "${curlOutputFormat}" "${url}" -o "${listCurlBuffer}")
curlExitCode="$?"
# Retrieve http_code and errormsg values, returned by curl command
IFS=";" read -r httpCode curlErrorMsg <<<"$curlOutput"
case $url in
# Did we "download" a local file?
"file"*)
if [[ -s "${listCurlBuffer}" ]]; then
echo -e "${OVER} ${TICK} ${str} Retrieval successful"
success=true
else
echo -e "${OVER} ${CROSS} ${str} Retrieval failed / empty list"
fi
;;
# Did we "download" a remote file?
*)
# Use the exit code to determine if curl was successful or not.
# Use HTTP code only to select the correct error message.
if [[ "${curlExitCode}" == "0" ]]; then
case "${httpCode}" in
"200") echo -e "${OVER} ${TICK} ${str} Retrieval successful" ;;
"304") echo -e "${OVER} ${TICK} ${str} No changes detected" ;;
*) echo -e "${OVER} ${TICK} ${str} Success (http_code=${COL_CYAN}${httpCode}${COL_NC})" ;;
esac
success=true
else
case "${httpCode}" in
"403") echo -e "${OVER} ${CROSS} ${str} Forbidden" ;;
"404") echo -e "${OVER} ${CROSS} ${str} Not found" ;;
"408") echo -e "${OVER} ${CROSS} ${str} Time-out" ;;
"451") echo -e "${OVER} ${CROSS} ${str} Unavailable For Legal Reasons" ;;
"500") echo -e "${OVER} ${CROSS} ${str} Internal Server Error" ;;
"504") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Gateway)" ;;
"521") echo -e "${OVER} ${CROSS} ${str} Web Server Is Down (Cloudflare)" ;;
"522") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Cloudflare)" ;;
*) echo -e "${OVER} ${CROSS} ${str} Retrieval failed (exit_code=${COL_CYAN}${curlExitCode}${COL_NC} Msg: ${COL_CYAN}${curlErrorMsg}${COL_NC})" ;;
esac
fi
;;
esac
fi
# Retrieve http_code and errormsg values, returned by curl command
IFS=";" read -r httpCode curlErrorMsg <<<"$curlOutput"
case $url in
# Did we "download" a local file?
"file"*)
if [[ -s "${listCurlBuffer}" ]]; then
echo -e "${OVER} ${TICK} ${str} Retrieval successful"
success=true
else
echo -e "${OVER} ${CROSS} ${str} Retrieval failed / empty list"
fi
;;
# Did we "download" a remote file?
*)
# Use the exit code to determine if curl was successful or not.
# Use HTTP code only to select the correct error message.
if [[ "${curlExitCode}" == "0" ]]; then
case "${httpCode}" in
"200") echo -e "${OVER} ${TICK} ${str} Retrieval successful" ;;
"304") echo -e "${OVER} ${TICK} ${str} No changes detected" ;;
*) echo -e "${OVER} ${TICK} ${str} Success (http_code=${COL_CYAN}${httpCode}${COL_NC})" ;;
esac
success=true
else
case "${httpCode}" in
"403") echo -e "${OVER} ${CROSS} ${str} Forbidden" ;;
"404") echo -e "${OVER} ${CROSS} ${str} Not found" ;;
"408") echo -e "${OVER} ${CROSS} ${str} Time-out" ;;
"451") echo -e "${OVER} ${CROSS} ${str} Unavailable For Legal Reasons" ;;
"500") echo -e "${OVER} ${CROSS} ${str} Internal Server Error" ;;
"504") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Gateway)" ;;
"521") echo -e "${OVER} ${CROSS} ${str} Web Server Is Down (Cloudflare)" ;;
"522") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Cloudflare)" ;;
*) echo -e "${OVER} ${CROSS} ${str} Retrieval failed (exit_code=${COL_CYAN}${curlExitCode}${COL_NC} Msg: ${COL_CYAN}${curlErrorMsg}${COL_NC})" ;;
esac
fi
;;
esac
local done="false"
# Determine if the blocklist was downloaded and saved correctly
if [[ "${success}" == true ]]; then
+37 -14
View File
@@ -1,25 +1,48 @@
# Recommended way to run tests
Make sure you have Docker and Python w/pip package manager.
The test suite is implemented with BATS and runs inside distro-specific Docker containers.
From command line all you need to do is:
## Requirements
- `pip install tox`
- `tox`
- Docker (with buildx support)
- Bash shell
Tox handles setting up a virtual environment for python dependencies, installing dependencies, building the docker images used by tests, and finally running tests. It's an easy way to have travis-ci like build behavior locally.
## Run tests
## Alternative py.test method of running tests
From the repository root, run:
You're responsible for setting up your virtual env and dependencies in this situation.
```
py.test -vv -n auto -m "build_stage"
py.test -vv -n auto -m "not build_stage"
```bash
bash test/run.sh --distro debian_12
```
The build_stage tests have to run first to create the docker images, followed by the actual tests which utilize said images. Unless you're changing your dockerfiles you shouldn't have to run the build_stage every time - but it's a good idea to rebuild at least once a day in case the base Docker images or packages change.
`test/run.sh` will:
# How do I debug python?
- Build the distro test image from `test/_<distro>.Dockerfile`
- Run the mock/function BATS suite in a fresh container
- Run the fresh-install BATS suite in a separate fresh container
Highly recommended: Setup PyCharm on a **Docker enabled** machine. Having a python debugger like PyCharm changes your life if you've never used it :)
## Available distros
If you are unsure which distro names are valid, run:
```bash
bash test/run.sh --help
```
The help output includes the current list of supported distros.
## Optional: override BATS library versions
`test/run.sh` accepts optional environment variable overrides when building test images:
- `BATS_CORE_VER`
- `BATS_SUPPORT_VER`
- `BATS_ASSERT_VER`
- `BATS_MOCK_VER`
- `BATS_FILE_VER`
Example:
```bash
BATS_CORE_VER=v1.14.0 DISTRO=debian_12 bash test/run.sh
```
View File
+12 -2
View File
@@ -3,7 +3,7 @@ FROM alpine:3.21
ENV GITDIR=/etc/.pihole
ENV SCRIPTDIR=/opt/pihole
RUN sed -i 's/#\(.*\/community\)/\1/' /etc/apk/repositories
RUN apk --no-cache add bash coreutils curl git jq openrc shadow
RUN apk --no-cache add bash coreutils curl git jq ncurses openrc shadow
RUN mkdir -p $GITDIR $SCRIPTDIR /etc/pihole
ADD . $GITDIR
@@ -13,6 +13,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+12 -2
View File
@@ -3,7 +3,7 @@ FROM alpine:3.22
ENV GITDIR=/etc/.pihole
ENV SCRIPTDIR=/opt/pihole
RUN sed -i 's/#\(.*\/community\)/\1/' /etc/apk/repositories
RUN apk --no-cache add bash coreutils curl git jq openrc shadow
RUN apk --no-cache add bash coreutils curl git jq ncurses openrc shadow
RUN mkdir -p $GITDIR $SCRIPTDIR /etc/pihole
ADD . $GITDIR
@@ -13,6 +13,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+12 -2
View File
@@ -3,7 +3,7 @@ FROM alpine:3.23
ENV GITDIR=/etc/.pihole
ENV SCRIPTDIR=/opt/pihole
RUN sed -i 's/#\(.*\/community\)/\1/' /etc/apk/repositories
RUN apk --no-cache add bash coreutils curl git jq openrc shadow
RUN apk --no-cache add bash coreutils curl git jq ncurses openrc shadow
RUN mkdir -p $GITDIR $SCRIPTDIR /etc/pihole
ADD . $GITDIR
@@ -13,6 +13,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -14,6 +14,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -14,6 +14,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -11,6 +11,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -11,6 +11,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -11,6 +11,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -12,6 +12,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -12,6 +12,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -12,6 +12,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -12,6 +12,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -11,6 +11,16 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -12,6 +12,16 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
+11 -1
View File
@@ -12,6 +12,16 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN true && \
chmod +x $SCRIPTDIR/*
ARG BATS_CORE_VER
ARG BATS_SUPPORT_VER
ARG BATS_ASSERT_VER
ARG BATS_MOCK_VER
ARG BATS_FILE_VER
RUN git clone --depth=1 --single-branch --branch "${BATS_CORE_VER}" https://github.com/bats-core/bats-core $GITDIR/test/libs/bats && \
git clone --depth=1 --single-branch --branch "${BATS_SUPPORT_VER}" https://github.com/bats-core/bats-support $GITDIR/test/libs/bats-support && \
git clone --depth=1 --single-branch --branch "${BATS_ASSERT_VER}" https://github.com/bats-core/bats-assert $GITDIR/test/libs/bats-assert && \
git clone --depth=1 --single-branch --branch "${BATS_MOCK_VER}" https://github.com/jasonkarns/bats-mock $GITDIR/test/libs/bats-mock && \
git clone --depth=1 --single-branch --branch "${BATS_FILE_VER}" https://github.com/bats-core/bats-file $GITDIR/test/libs/bats-file
ENV SKIP_INSTALL=true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \
-175
View File
@@ -1,175 +0,0 @@
import pytest
import testinfra
import testinfra.backend.docker
import subprocess
from textwrap import dedent
IMAGE = "pytest_pihole:test_container"
tick_box = "[✓]"
cross_box = "[✗]"
info_box = "[i]"
# Monkeypatch sh to bash, if they ever support non hard code /bin/sh this can go away
# https://github.com/pytest-dev/pytest-testinfra/blob/master/testinfra/backend/docker.py
def run_bash(self, command, *args, **kwargs):
cmd = self.get_command(command, *args)
if self.user is not None:
out = self.run_local(
"docker exec -u %s %s /bin/bash -c %s", self.user, self.name, cmd
)
else:
out = self.run_local("docker exec %s /bin/bash -c %s", self.name, cmd)
out.command = self.encode(cmd)
return out
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()
)
# 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])
# 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"""\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1" in""".format(script=script))
for k, v in args.items():
case = dedent("""
{arg})
echo {res}
exit {retcode}
;;""".format(arg=k, res=v[0], retcode=v[1]))
mock_script += case
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
)
)
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"""\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1" in""".format(script=script))
for k, v in args.items():
case = dedent("""
{arg})
echo {res}
exit {retcode}
;;""".format(arg=k, res=v[0], retcode=v[1]))
mock_script += case
mock_script += dedent(r"""
*)
{orig_script_path} "\$@"
;;""".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
)
)
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"""\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1 \$2" in""".format(script=script))
for k, v in args.items():
case = dedent("""
\"{arg}\")
echo {res}
exit {retcode}
;;""".format(arg=k, res=v[0], retcode=v[1]))
mock_script += case
mock_script += dedent(r"""
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
)
)
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"""\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1 \$2" in""".format(script=script))
for k, v in args.items():
case = dedent("""
\"{arg}\")
echo \"{res}\"
exit {retcode}
;;""".format(arg=k, res=v[0], retcode=v[1]))
mock_script += case
mock_script += dedent(r"""
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
)
)
def run_script(Pihole, script):
result = Pihole.run(script)
assert result.rc == 0
return result
-6
View File
@@ -1,6 +0,0 @@
pyyaml == 6.0.3
pytest == 9.0.3
pytest-xdist == 3.8.0
pytest-testinfra == 10.2.2
tox == 4.52.1
pytest-clarity == 1.0.1
Executable
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${SCRIPT_DIR}"
# ---------------------------------------------------------------------------
# CLI arguments (optional)
# ---------------------------------------------------------------------------
usage() {
echo "Usage:"
echo " DISTRO=<name> bash test/run.sh"
echo " bash test/run.sh --distro <name>"
echo ""
echo "Options:"
echo " -d, --distro <name> Distro to test (e.g., debian_12)"
echo " -h, --help Show this help"
}
list_distros() {
find . -maxdepth 1 -name '_*.Dockerfile' | sed 's|^\./||;s/^_//;s/\.Dockerfile$//' | sort
}
while [[ $# -gt 0 ]]; do
case "$1" in
-d|--distro)
shift
if [[ $# -eq 0 ]]; then
echo "Error: --distro requires a value"
usage
exit 1
fi
DISTRO="$1"
;;
-h|--help)
usage
echo ""
echo "Available distros:"
list_distros
exit 0
;;
*)
echo "Error: Unknown option '$1'"
usage
exit 1
;;
esac
shift
done
# ---------------------------------------------------------------------------
# Distro selection
# ---------------------------------------------------------------------------
if [[ -z "${DISTRO:-}" ]]; then
echo "Error: DISTRO is required."
echo "Example: DISTRO=debian_12 bash test/run.sh"
echo "or: bash test/run.sh --distro debian_12"
echo ""
echo "Available distros:"
list_distros
exit 1
fi
DOCKERFILE="_${DISTRO}.Dockerfile"
if [[ ! -f "${DOCKERFILE}" ]]; then
echo "Error: Unknown distro '${DISTRO}'. Available distros:"
list_distros | sed 's/^/ /'
exit 1
fi
# Determine distro family to select which test files to run.
# rhel: CentOS/Fedora — includes SELinux tests
# alpine: Alpine Linux
# debian: Debian/Ubuntu (default)
distro_family() {
case "$1" in
centos_* | fedora_*) echo "rhel" ;;
alpine_*) echo "alpine" ;;
*) echo "debian" ;;
esac
}
DISTRO_FAMILY=$(distro_family "${DISTRO}")
# ---------------------------------------------------------------------------
# Suite definitions
#
# Suite 1 — mock/function tests: run together in one container. Each test
# cleans up its own state via setup()/teardown(); no full install occurs.
#
# Suite 2 — fresh install: runs alone in its own container so the installer
# can mutate the filesystem freely without needing any teardown.
# ---------------------------------------------------------------------------
SUITE_1=(
test_automated_install.bats
test_installer_ftl.bats
test_network.bats
test_utils.bats
)
[[ "${DISTRO_FAMILY}" == "rhel" ]] && SUITE_1+=(test_selinux.bats)
SUITE_2=(test_fresh_install.bats)
# ---------------------------------------------------------------------------
# BATS library versions — single source of truth, passed to Docker as build
# args so the Dockerfiles themselves stay version-agnostic. Override any of
# these by setting the corresponding environment variable before calling this
# script, e.g. BATS_CORE_VER=v1.14.0 bash test/run.sh --distro debian_12
# ---------------------------------------------------------------------------
BATS_CORE_VER="${BATS_CORE_VER:-v1.13.0}"
BATS_SUPPORT_VER="${BATS_SUPPORT_VER:-v0.3.0}"
BATS_ASSERT_VER="${BATS_ASSERT_VER:-v2.2.4}"
BATS_MOCK_VER="${BATS_MOCK_VER:-v1.2.5}"
BATS_FILE_VER="${BATS_FILE_VER:-v0.4.0}"
# ---------------------------------------------------------------------------
# Build the test image (once, shared by both suites)
# ---------------------------------------------------------------------------
IMAGE_TAG="pihole_test:${DISTRO}"
docker buildx build \
--load \
--progress plain \
--build-arg "BATS_CORE_VER=${BATS_CORE_VER}" \
--build-arg "BATS_SUPPORT_VER=${BATS_SUPPORT_VER}" \
--build-arg "BATS_ASSERT_VER=${BATS_ASSERT_VER}" \
--build-arg "BATS_MOCK_VER=${BATS_MOCK_VER}" \
--build-arg "BATS_FILE_VER=${BATS_FILE_VER}" \
-f "${DOCKERFILE}" \
-t "${IMAGE_TAG}" \
../
# ---------------------------------------------------------------------------
# run_suite <label> <file>...
# Spin up a fresh container and run the named BATS files inside it.
# ---------------------------------------------------------------------------
run_suite() {
local label="$1"; shift
local files=("$@")
printf '\n=== Suite: %s ===\n' "${label}"
docker run --rm -t "${IMAGE_TAG}" \
bash -euo pipefail -c '
cd /etc/.pihole/test
exec libs/bats/bin/bats -p --print-output-on-failure "$@"
' bash "${files[@]}"
}
# ---------------------------------------------------------------------------
# Run both suites; collect exit codes so both always run even if one fails.
# ---------------------------------------------------------------------------
rc=0
run_suite "mock and function tests" "${SUITE_1[@]}" || rc=$?
run_suite "fresh install" "${SUITE_2[@]}" || rc=$?
exit ${rc}
-7
View File
@@ -1,7 +0,0 @@
from setuptools import setup
setup(
py_modules=[],
setup_requires=["pytest-runner"],
tests_require=["pytest"],
)
-472
View File
@@ -1,472 +0,0 @@
import pytest
from textwrap import dedent
import re
from .conftest import (
tick_box,
info_box,
cross_box,
mock_command,
mock_command_2,
mock_command_passthrough,
)
FTL_BRANCH = "development"
def test_supported_package_manager(host):
"""
confirm installer exits when no supported package manager found
"""
# break supported package managers
host.run("rm -rf /usr/bin/apt-get")
host.run("rm -rf /usr/bin/rpm")
host.run("rm -rf /sbin/apk")
package_manager_detect = host.run("""
source /opt/pihole/basic-install.sh
package_manager_detect
""")
expected_stdout = cross_box + " No supported package manager found"
assert expected_stdout in package_manager_detect.stdout
# assert package_manager_detect.rc == 1
def test_selinux_not_detected(host):
"""
confirms installer continues when SELinux configuration file does not exist
"""
check_selinux = host.run("""
rm -f /etc/selinux/config
source /opt/pihole/basic-install.sh
checkSelinux
""")
expected_stdout = info_box + " SELinux not detected"
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0
def get_directories_recursive(host, directory):
if directory is None:
return directory
# returns all non-hidden subdirs of 'directory'
dirs_raw = host.run("find {} -type d -not -path '*/.*'".format(directory))
dirs = list(filter(bool, dirs_raw.stdout.splitlines()))
return dirs
def test_installPihole_fresh_install_readableFiles(host):
"""
confirms all necessary files are readable by pihole user
"""
# dialog returns Cancel for user prompt
mock_command("dialog", {"*": ("", "0")}, host)
# mock git pull
mock_command_passthrough("git", {"pull": ("", "0")}, host)
# mock systemctl to not start FTL
mock_command_2(
"systemctl",
{
"enable pihole-FTL": ("", "0"),
"restart pihole-FTL": ("", "0"),
"start pihole-FTL": ("", "0"),
"*": ('echo "systemctl call with $@"', "0"),
},
host,
)
mock_command_2(
"rc-service",
{
"rc-service pihole-FTL enable": ("", "0"),
"rc-service pihole-FTL restart": ("", "0"),
"rc-service pihole-FTL start": ("", "0"),
"*": ('echo "rc-service call with $@"', "0"),
},
host,
)
# try to install man
host.run("command -v apt-get > /dev/null && apt-get install -qq man")
host.run("command -v dnf > /dev/null && dnf install -y man")
host.run("command -v yum > /dev/null && yum install -y man")
host.run("command -v apk > /dev/null && apk add mandoc man-pages")
# Workaround to get FTLv6 installed until it reaches master branch
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
install = host.run("""
export TERM=xterm
export DEBIAN_FRONTEND=noninteractive
umask 0027
runUnattended=true
source /opt/pihole/basic-install.sh > /dev/null
runUnattended=true
main
/opt/pihole/pihole-FTL-prestart.sh
""")
assert 0 == install.rc
maninstalled = True
if (info_box + " man not installed") in install.stdout:
maninstalled = False
if (info_box + " man pages not installed") in install.stdout:
maninstalled = False
piholeuser = "pihole"
exit_status_success = 0
test_cmd = 'su -s /bin/bash -c "test -{0} {1}" -p {2}'
# check files in /etc/pihole for read, write and execute permission
check_etc = test_cmd.format("r", "/etc/pihole", piholeuser)
actual_rc = host.run(check_etc).rc
assert exit_status_success == actual_rc
check_etc = test_cmd.format("x", "/etc/pihole", piholeuser)
actual_rc = host.run(check_etc).rc
assert exit_status_success == actual_rc
# readable and writable dhcp.leases
check_leases = test_cmd.format("r", "/etc/pihole/dhcp.leases", piholeuser)
actual_rc = host.run(check_leases).rc
assert exit_status_success == actual_rc
check_leases = test_cmd.format("w", "/etc/pihole/dhcp.leases", piholeuser)
actual_rc = host.run(check_leases).rc
# readable install.log
check_install = test_cmd.format("r", "/etc/pihole/install.log", piholeuser)
actual_rc = host.run(check_install).rc
assert exit_status_success == actual_rc
# readable versions
check_localversion = test_cmd.format("r", "/etc/pihole/versions", piholeuser)
actual_rc = host.run(check_localversion).rc
assert exit_status_success == actual_rc
# readable macvendor.db
check_macvendor = test_cmd.format("r", "/etc/pihole/macvendor.db", piholeuser)
actual_rc = host.run(check_macvendor).rc
assert exit_status_success == actual_rc
# check readable and executable /etc/init.d/pihole-FTL
check_init = test_cmd.format("x", "/etc/init.d/pihole-FTL", piholeuser)
actual_rc = host.run(check_init).rc
assert exit_status_success == actual_rc
check_init = test_cmd.format("r", "/etc/init.d/pihole-FTL", piholeuser)
actual_rc = host.run(check_init).rc
assert exit_status_success == actual_rc
# check readable and executable manpages
if maninstalled is True:
check_man = test_cmd.format("x", "/usr/local/share/man", piholeuser)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
check_man = test_cmd.format("r", "/usr/local/share/man", piholeuser)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
check_man = test_cmd.format("x", "/usr/local/share/man/man8", piholeuser)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
check_man = test_cmd.format("r", "/usr/local/share/man/man8", piholeuser)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
check_man = test_cmd.format(
"r", "/usr/local/share/man/man8/pihole.8", piholeuser
)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
# check not readable cron file
check_sudo = test_cmd.format("x", "/etc/cron.d/", piholeuser)
actual_rc = host.run(check_sudo).rc
assert exit_status_success == actual_rc
check_sudo = test_cmd.format("r", "/etc/cron.d/", piholeuser)
actual_rc = host.run(check_sudo).rc
assert exit_status_success == actual_rc
check_sudo = test_cmd.format("r", "/etc/cron.d/pihole", piholeuser)
actual_rc = host.run(check_sudo).rc
assert exit_status_success == actual_rc
directories = get_directories_recursive(host, "/etc/.pihole/")
for directory in directories:
check_pihole = test_cmd.format("r", directory, piholeuser)
actual_rc = host.run(check_pihole).rc
check_pihole = test_cmd.format("x", directory, piholeuser)
actual_rc = host.run(check_pihole).rc
findfiles = 'find "{}" -maxdepth 1 -type f -exec echo {{}} \\;;'
filelist = host.run(findfiles.format(directory))
files = list(filter(bool, filelist.stdout.splitlines()))
for file in files:
check_pihole = test_cmd.format("r", file, piholeuser)
actual_rc = host.run(check_pihole).rc
def test_update_package_cache_success_no_errors(host):
"""
confirms package cache was updated without any errors
"""
updateCache = host.run("""
source /opt/pihole/basic-install.sh
package_manager_detect
update_package_cache
""")
expected_stdout = tick_box + " Update local cache of available packages"
assert expected_stdout in updateCache.stdout
assert "error" not in updateCache.stdout.lower()
def test_update_package_cache_failure_no_errors(host):
"""
confirms package cache was not updated
"""
mock_command("apt-get", {"update": ("", "1")}, host)
updateCache = host.run("""
source /opt/pihole/basic-install.sh
package_manager_detect
update_package_cache
""")
expected_stdout = cross_box + " Update local cache of available packages"
assert expected_stdout in updateCache.stdout
assert "Error: Unable to update package cache." in updateCache.stdout
@pytest.mark.parametrize(
"arch,detected_string,supported",
[
("aarch64", "AArch64 (64 Bit ARM)", True),
("armv6", "ARMv6", True),
("armv7l", "ARMv7 (or newer)", True),
("armv7", "ARMv7 (or newer)", True),
("armv8a", "ARMv7 (or newer)", True),
("x86_64", "x86_64", True),
("riscv64", "riscv64", True),
("mips", "mips", False),
],
)
def test_FTL_detect_no_errors(host, arch, detected_string, supported):
"""
confirms only correct package is downloaded for FTL engine
"""
# mock uname to return passed platform
mock_command("uname", {"-m": (arch, "0")}, host)
# mock readelf to respond with passed CPU architecture
mock_command_2(
"readelf",
{
"-A /bin/sh": ("Tag_CPU_arch: " + arch, "0"),
"-A /usr/bin/sh": ("Tag_CPU_arch: " + arch, "0"),
"-A /usr/sbin/sh": ("Tag_CPU_arch: " + arch, "0"),
},
host,
)
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
detectPlatform = host.run("""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
""")
if supported:
expected_stdout = info_box + " FTL Checks..."
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Detected " + detected_string + " architecture"
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Downloading and Installing FTL"
assert expected_stdout in detectPlatform.stdout
else:
expected_stdout = (
"Not able to detect architecture (unknown: " + detected_string + ")"
)
assert expected_stdout in detectPlatform.stdout
def test_FTL_development_binary_installed_and_responsive_no_errors(host):
"""
confirms FTL development binary is copied and functional in installed location
"""
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
host.run("""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
""")
version_check = host.run("""
VERSION=$(pihole-FTL version)
echo ${VERSION:0:1}
""")
expected_stdout = "v"
assert expected_stdout in version_check.stdout
def test_IPv6_only_link_local(host):
"""
confirms IPv6 blocking is disabled for Link-local address
"""
# mock ip -6 address to return Link-local address
mock_command_2(
"ip",
{"-6 address": ("inet6 fe80::d210:52fa:fe00:7ad7/64 scope link", "0")},
host,
)
detectPlatform = host.run("""
source /opt/pihole/basic-install.sh
find_IPv6_information
""")
expected_stdout = "Unable to find IPv6 ULA/GUA address"
assert expected_stdout in detectPlatform.stdout
def test_IPv6_only_ULA(host):
"""
confirms IPv6 blocking is enabled for ULA addresses
"""
# mock ip -6 address to return ULA address
mock_command_2(
"ip",
{
"-6 address": (
"inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global",
"0",
)
},
host,
)
detectPlatform = host.run("""
source /opt/pihole/basic-install.sh
find_IPv6_information
""")
expected_stdout = "Found IPv6 ULA address"
assert expected_stdout in detectPlatform.stdout
def test_IPv6_only_GUA(host):
"""
confirms IPv6 blocking is enabled for GUA addresses
"""
# mock ip -6 address to return GUA address
mock_command_2(
"ip",
{
"-6 address": (
"inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global",
"0",
)
},
host,
)
detectPlatform = host.run("""
source /opt/pihole/basic-install.sh
find_IPv6_information
""")
expected_stdout = "Found IPv6 GUA address"
assert expected_stdout in detectPlatform.stdout
def test_IPv6_GUA_ULA_test(host):
"""
confirms IPv6 blocking is enabled for GUA and ULA addresses
"""
# mock ip -6 address to return GUA and ULA addresses
mock_command_2(
"ip",
{
"-6 address": (
"inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global\n"
"inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global",
"0",
)
},
host,
)
detectPlatform = host.run("""
source /opt/pihole/basic-install.sh
find_IPv6_information
""")
expected_stdout = "Found IPv6 ULA address"
assert expected_stdout in detectPlatform.stdout
def test_IPv6_ULA_GUA_test(host):
"""
confirms IPv6 blocking is enabled for GUA and ULA addresses
"""
# mock ip -6 address to return ULA and GUA addresses
mock_command_2(
"ip",
{
"-6 address": (
"inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global\n"
"inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global",
"0",
)
},
host,
)
detectPlatform = host.run("""
source /opt/pihole/basic-install.sh
find_IPv6_information
""")
expected_stdout = "Found IPv6 ULA address"
assert expected_stdout in detectPlatform.stdout
def test_validate_ip(host):
"""
Tests valid_ip for various IP addresses
"""
def test_address(addr, success=True):
output = host.run("""
source /opt/pihole/basic-install.sh
valid_ip "{addr}"
""".format(addr=addr))
assert output.rc == 0 if success else 1
test_address("192.168.1.1")
test_address("127.0.0.1")
test_address("255.255.255.255")
test_address("255.255.255.256", False)
test_address("255.255.256.255", False)
test_address("255.256.255.255", False)
test_address("256.255.255.255", False)
test_address("1092.168.1.1", False)
test_address("not an IP", False)
test_address("8.8.8.8#", False)
test_address("8.8.8.8#0")
test_address("8.8.8.8#1")
test_address("8.8.8.8#42")
test_address("8.8.8.8#888")
test_address("8.8.8.8#1337")
test_address("8.8.8.8#65535")
test_address("8.8.8.8#65536", False)
test_address("8.8.8.8#-1", False)
test_address("00.0.0.0", False)
test_address("010.0.0.0", False)
test_address("001.0.0.0", False)
test_address("0.0.0.0#00", False)
test_address("0.0.0.0#01", False)
test_address("0.0.0.0#001", False)
test_address("0.0.0.0#0001", False)
test_address("0.0.0.0#00001", False)
def test_package_manager_has_pihole_deps(host):
"""Confirms OS is able to install the required packages for Pi-hole"""
mock_command("dialog", {"*": ("", "0")}, host)
output = host.run("""
source /opt/pihole/basic-install.sh
package_manager_detect
update_package_cache
build_dependency_package
install_dependent_packages
""")
assert "No package" not in output.stdout
assert output.rc == 0
def test_meta_package_uninstall(host):
"""Confirms OS is able to install and uninstall the Pi-hole meta package"""
mock_command("dialog", {"*": ("", "0")}, host)
install = host.run("""
source /opt/pihole/basic-install.sh
package_manager_detect
update_package_cache
build_dependency_package
install_dependent_packages
""")
assert install.rc == 0
uninstall = host.run("""
source /opt/pihole/uninstall.sh
removeMetaPackage
""")
assert uninstall.rc == 0
-50
View File
@@ -1,50 +0,0 @@
def test_key_val_replacement_works(host):
"""Confirms addOrEditKeyValPair either adds or replaces a key value pair in a given file"""
host.run("""
source /opt/pihole/utils.sh
touch ./testoutput
addOrEditKeyValPair "./testoutput" "KEY_ONE" "value1"
addOrEditKeyValPair "./testoutput" "KEY_TWO" "value2"
addOrEditKeyValPair "./testoutput" "KEY_ONE" "value3"
addOrEditKeyValPair "./testoutput" "KEY_FOUR" "value4"
""")
output = host.run("""
cat ./testoutput
""")
expected_stdout = "KEY_ONE=value3\nKEY_TWO=value2\nKEY_FOUR=value4\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("""
source /opt/pihole/utils.sh
getFTLPID
""")
expected_stdout = "-1\n"
assert expected_stdout == output.stdout
def test_setFTLConfigValue_getFTLConfigValue(host):
"""
Confirms getFTLConfigValue works (also assumes setFTLConfigValue works)
Requires FTL to be installed, so we do that first
(taken from test_FTL_development_binary_installed_and_responsive_no_errors)
"""
host.run("""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
echo "development" > /etc/pihole/ftlbranch
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
""")
output = host.run("""
source /opt/pihole/utils.sh
setFTLConfigValue "dns.upstreams" '["9.9.9.9"]' > /dev/null
getFTLConfigValue "dns.upstreams"
""")
assert "[ 9.9.9.9 ]" in output.stdout
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env bats
# Core installer tests — package manager, cache, dependencies
load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load 'libs/bats-mock/stub'
TICK="[✓]"
CROSS="[✗]"
INFO="[i]"
@test "installer exits when no supported package manager found" {
[[ -e /usr/bin/apt-get ]] && mv /usr/bin/apt-get /usr/bin/apt-get.disabled
[[ -e /usr/bin/rpm ]] && mv /usr/bin/rpm /usr/bin/rpm.disabled
[[ -e /sbin/apk ]] && mv /sbin/apk /sbin/apk.disabled
run bash -c "
source /opt/pihole/basic-install.sh
package_manager_detect
"
assert_output --partial "${CROSS} No supported package manager found"
assert_failure
# Restore package managers for other tests
[[ -e /usr/bin/apt-get.disabled ]] && mv -f /usr/bin/apt-get.disabled /usr/bin/apt-get || true
[[ -e /usr/bin/rpm.disabled ]] && mv -f /usr/bin/rpm.disabled /usr/bin/rpm || true
[[ -e /sbin/apk.disabled ]] && mv -f /sbin/apk.disabled /sbin/apk || true
}
@test "installer continues when SELinux config file does not exist" {
run bash -c "
rm -f /etc/selinux/config
source /opt/pihole/basic-install.sh
checkSelinux
"
assert_output --partial "${INFO} SELinux not detected"
assert_success
}
@test "package cache update succeeds without errors" {
run bash -c "
source /opt/pihole/basic-install.sh
package_manager_detect
update_package_cache
"
assert_output --partial "${TICK} Update local cache of available packages"
refute_output --partial "error"
}
@test "package cache update reports failure correctly" {
stub apt-get "update : return 1"
run bash -c "
source /opt/pihole/basic-install.sh
package_manager_detect
update_package_cache
"
assert_output --partial "${CROSS} Update local cache of available packages"
assert_output --partial "Error: Unable to update package cache."
unstub apt-get 2>/dev/null || true
}
@test "OS can install required Pi-hole dependency packages" {
run bash -c "
source /opt/pihole/basic-install.sh
package_manager_detect
update_package_cache
build_dependency_package
install_dependent_packages
"
refute_output --partial "No package"
assert_success
}
@test "OS can install and uninstall the Pi-hole meta package" {
run bash -c "
export DEBIAN_FRONTEND=noninteractive
source /opt/pihole/basic-install.sh
package_manager_detect
update_package_cache
build_dependency_package
install_dependent_packages
"
assert_success
run bash -c "
export DEBIAN_FRONTEND=noninteractive
source /opt/pihole/basic-install.sh
package_manager_detect
eval \"\${PKG_REMOVE}\" pihole-meta
"
assert_success
}
-65
View File
@@ -1,65 +0,0 @@
from .conftest import (
tick_box,
cross_box,
mock_command,
)
def mock_selinux_config(state, host):
"""
Creates a mock SELinux config file with expected content
"""
# validate state string
valid_states = ["enforcing", "permissive", "disabled"]
assert state in valid_states
# getenforce returns the running state of SELinux
mock_command("getenforce", {"*": (state.capitalize(), "0")}, host)
# create mock configuration with desired content
host.run("""
mkdir /etc/selinux
echo "SELINUX={state}" > /etc/selinux/config
""".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("""
source /opt/pihole/basic-install.sh
checkSelinux
""")
expected_stdout = cross_box + " Current SELinux: enforcing"
assert expected_stdout in check_selinux.stdout
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("""
source /opt/pihole/basic-install.sh
checkSelinux
""")
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("""
source /opt/pihole/basic-install.sh
checkSelinux
""")
expected_stdout = tick_box + " Current SELinux: disabled"
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env bats
# Full-install test — runs in a dedicated container so no teardown is needed.
# Verifies that all files written by the installer are readable by the pihole user.
load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load 'libs/bats-file/load'
load 'libs/bats-mock/stub'
INFO="[i]"
FTL_BRANCH="development"
@test "fresh install: all necessary files are readable by pihole user" {
# bats-mock prepends $BATS_MOCK_BINDIR to PATH at load time but only
# creates the directory on the first stub call. We write scripts directly
# so create it ourselves.
mkdir -p "${BATS_MOCK_BINDIR}"
# dialog — suppress any TUI calls; with a TTY allocated an uncaught dialog
# invocation would block the test waiting for input
printf '#!/bin/bash\nexit 0\n' > "${BATS_MOCK_BINDIR}/dialog"
chmod +x "${BATS_MOCK_BINDIR}/dialog"
# git — let every subcommand run for real except 'pull', which we suppress
# so the test has no dependency on outbound network access
local real_git
real_git="$(type -P git)"
cat > "${BATS_MOCK_BINDIR}/git" <<EOF
#!/bin/bash
case "\$1" in
pull) exit 0 ;;
*) exec "${real_git}" "\$@" ;;
esac
EOF
chmod +x "${BATS_MOCK_BINDIR}/git"
# systemctl / rc-service — accept any service-management call silently
printf '#!/bin/bash\nexit 0\n' > "${BATS_MOCK_BINDIR}/systemctl"
chmod +x "${BATS_MOCK_BINDIR}/systemctl"
printf '#!/bin/bash\nexit 0\n' > "${BATS_MOCK_BINDIR}/rc-service"
chmod +x "${BATS_MOCK_BINDIR}/rc-service"
command -v apt-get > /dev/null && apt-get install -qq man || true
command -v dnf > /dev/null && dnf install -y man || true
command -v yum > /dev/null && yum install -y man || true
command -v apk > /dev/null && apk add mandoc man-pages || true
echo "${FTL_BRANCH}" > /etc/pihole/ftlbranch
run bash -c "
export TERM=xterm
export DEBIAN_FRONTEND=noninteractive
umask 0027
source /opt/pihole/basic-install.sh > /dev/null
runUnattended=true
main
/opt/pihole/pihole-FTL-prestart.sh
"
assert_success
local maninstalled=true
if [[ "${output}" == *"${INFO} man not installed"* ]] || [[ "${output}" == *"${INFO} man pages not installed"* ]]; then
maninstalled=false
fi
# Verify files exist before checking user-level read permission.
assert_dir_exists /etc/pihole
assert_file_exists /etc/pihole/dhcp.leases
assert_file_exists /etc/pihole/install.log
assert_file_exists /etc/pihole/versions
assert_file_exists /etc/pihole/macvendor.db
assert_file_exists /etc/init.d/pihole-FTL
if [[ "${maninstalled}" == "true" ]]; then
assert_dir_exists /usr/local/share/man
assert_dir_exists /usr/local/share/man/man8
assert_file_exists /usr/local/share/man/man8/pihole.8
fi
assert_file_exists /etc/cron.d/pihole
# Verify the pihole user can actually read the files (bats-file checks as
# the current process user; _check_perm runs the test as the pihole user).
local piholeuser="pihole"
_check_perm() { su -s /bin/bash -c "test -${1} ${2}" -p ${piholeuser}; }
run _check_perm r /etc/pihole; assert_success
run _check_perm x /etc/pihole; assert_success
run _check_perm r /etc/pihole/dhcp.leases; assert_success
run _check_perm r /etc/pihole/install.log; assert_success
run _check_perm r /etc/pihole/versions; assert_success
run _check_perm r /etc/pihole/macvendor.db; assert_success
run _check_perm x /etc/init.d/pihole-FTL; assert_success
run _check_perm r /etc/init.d/pihole-FTL; assert_success
if [[ "${maninstalled}" == "true" ]]; then
run _check_perm x /usr/local/share/man; assert_success
run _check_perm r /usr/local/share/man; assert_success
run _check_perm x /usr/local/share/man/man8; assert_success
run _check_perm r /usr/local/share/man/man8; assert_success
run _check_perm r /usr/local/share/man/man8/pihole.8; assert_success
fi
run _check_perm x /etc/cron.d/; assert_success
run _check_perm r /etc/cron.d/; assert_success
run _check_perm r /etc/cron.d/pihole; assert_success
local dirs
dirs=$(find /etc/.pihole/ -type d -not -path '*/.*' 2>/dev/null || true)
while IFS= read -r dir; do
[[ -z "${dir}" ]] && continue
assert_dir_exists "${dir}"
run _check_perm r "${dir}"; assert_success
run _check_perm x "${dir}"; assert_success
local files
files=$(find "${dir}" -maxdepth 1 -type f -exec echo {} \; 2>/dev/null || true)
while IFS= read -r file; do
[[ -z "${file}" ]] && continue
assert_file_exists "${file}"
run _check_perm r "${file}"; assert_success
done <<< "${files}"
done <<< "${dirs}"
}
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env bats
# Installer tests for FTL architecture detection and binary installation
load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load 'libs/bats-mock/stub'
TICK="[✓]"
INFO="[i]"
FTL_BRANCH="development"
# ---------------------------------------------------------------------------
# Installer FTL architecture detection — one @test per arch
# ---------------------------------------------------------------------------
_test_ftl_arch() {
local arch="$1" detected_string="$2" supported="$3"
# Resolve the sh binary path the installer will interrogate so we stub
# exactly the call that will be made, rather than all possible paths.
local sh_path
sh_path="$(command -v sh)"
stub uname "-m : echo '${arch}'"
stub readelf "-A ${sh_path} : echo 'Tag_CPU_arch: ${arch}'"
echo "${FTL_BRANCH}" > /etc/pihole/ftlbranch
run bash -c "
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=\$(get_binary_name)
binary=\"pihole-FTL\${funcOutput##*pihole-FTL}\"
theRest=\"\${funcOutput%pihole-FTL*}\"
FTLdetect \"\${binary}\" \"\${theRest}\"
"
if [[ "${supported}" == "true" ]]; then
assert_output --partial "${INFO} FTL Checks..."
assert_output --partial "${TICK} Detected ${detected_string} architecture"
if [[ "${output}" != *"Downloading and Installing FTL"* && "${output}" != *"Local binary up-to-date. No need to download!"* ]]; then
echo "Expected either download or up-to-date path, got:" >&2
echo "${output}" >&2
false
fi
else
assert_output --partial "Not able to detect architecture (unknown: ${detected_string})"
fi
unstub uname 2>/dev/null || true
unstub readelf 2>/dev/null || true
}
@test "installer detects aarch64 architecture for FTL" {
_test_ftl_arch "aarch64" "AArch64 (64 Bit ARM)" "true"
}
@test "installer detects ARMv6 architecture for FTL" {
_test_ftl_arch "armv6" "ARMv6" "true"
}
@test "installer detects ARMv7l architecture for FTL" {
_test_ftl_arch "armv7l" "ARMv7 (or newer)" "true"
}
@test "installer detects ARMv7 architecture for FTL" {
_test_ftl_arch "armv7" "ARMv7 (or newer)" "true"
}
@test "installer detects ARMv8a architecture for FTL" {
_test_ftl_arch "armv8a" "ARMv7 (or newer)" "true"
}
@test "installer detects x86_64 architecture for FTL" {
_test_ftl_arch "x86_64" "x86_64" "true"
}
@test "installer detects riscv64 architecture for FTL" {
_test_ftl_arch "riscv64" "riscv64" "true"
}
@test "installer reports unsupported architecture for FTL" {
_test_ftl_arch "mips" "mips" "false"
}
@test "installer provides a responsive FTL development binary" {
echo "${FTL_BRANCH}" > /etc/pihole/ftlbranch
bash -c "
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=\$(get_binary_name)
binary=\"pihole-FTL\${funcOutput##*pihole-FTL}\"
theRest=\"\${funcOutput%pihole-FTL*}\"
FTLdetect \"\${binary}\" \"\${theRest}\"
"
run bash -c '
VERSION=$(pihole-FTL version)
echo "${VERSION:0:1}"
'
assert_output --partial "v"
}
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env bats
# Network detection tests — IPv6 address detection and IP validation
load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load 'libs/bats-mock/stub'
# ---------------------------------------------------------------------------
# IPv6 detection
# ---------------------------------------------------------------------------
@test "IPv6 link-local only: blocking disabled" {
stub ip "-6 address : echo 'inet6 fe80::d210:52fa:fe00:7ad7/64 scope link'"
run bash -c "
source /opt/pihole/basic-install.sh
find_IPv6_information
"
assert_output --partial "Unable to find IPv6 ULA/GUA address"
unstub ip 2>/dev/null || true
}
@test "IPv6 ULA only: blocking enabled" {
stub ip "-6 address : echo 'inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global'"
run bash -c "
source /opt/pihole/basic-install.sh
find_IPv6_information
"
assert_output --partial "Found IPv6 ULA address"
unstub ip 2>/dev/null || true
}
@test "IPv6 GUA only: blocking enabled" {
stub ip "-6 address : echo 'inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global'"
run bash -c "
source /opt/pihole/basic-install.sh
find_IPv6_information
"
assert_output --partial "Found IPv6 GUA address"
unstub ip 2>/dev/null || true
}
@test "IPv6 GUA + ULA: ULA takes precedence" {
stub ip "-6 address : printf 'inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global\ninet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global\n'"
run bash -c "
source /opt/pihole/basic-install.sh
find_IPv6_information
"
assert_output --partial "Found IPv6 ULA address"
unstub ip 2>/dev/null || true
}
@test "IPv6 ULA + GUA: ULA takes precedence" {
stub ip "-6 address : printf 'inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global\ninet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global\n'"
run bash -c "
source /opt/pihole/basic-install.sh
find_IPv6_information
"
assert_output --partial "Found IPv6 ULA address"
unstub ip 2>/dev/null || true
}
# ---------------------------------------------------------------------------
# IP address validation
# ---------------------------------------------------------------------------
@test "valid_ip accepts and rejects addresses correctly" {
_valid() {
run bash -c "source /opt/pihole/basic-install.sh; valid_ip '${1}'"
assert_success
}
_invalid() {
run bash -c "source /opt/pihole/basic-install.sh; valid_ip '${1}'"
assert_failure
}
_valid "192.168.1.1"
_valid "127.0.0.1"
_valid "255.255.255.255"
_invalid "255.255.255.256"
_invalid "255.255.256.255"
_invalid "255.256.255.255"
_invalid "256.255.255.255"
_invalid "1092.168.1.1"
_invalid "not an IP"
_invalid "8.8.8.8#"
_valid "8.8.8.8#0"
_valid "8.8.8.8#1"
_valid "8.8.8.8#42"
_valid "8.8.8.8#888"
_valid "8.8.8.8#1337"
_valid "8.8.8.8#65535"
_invalid "8.8.8.8#65536"
_invalid "8.8.8.8#-1"
_invalid "00.0.0.0"
_invalid "010.0.0.0"
_invalid "001.0.0.0"
_invalid "0.0.0.0#00"
_invalid "0.0.0.0#01"
_invalid "0.0.0.0#001"
_invalid "0.0.0.0#0001"
_invalid "0.0.0.0#00001"
}
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bats
# Tests for SELinux handling in basic-install.sh.
# Only runs on rhel family (CentOS/Fedora) — selected by run.sh.
load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load 'libs/bats-mock/stub'
TICK="[✓]"
CROSS="[✗]"
_mock_selinux_config() {
local state="$1" # enforcing, permissive, or disabled
local capitalized
capitalized=$(echo "${state}" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')
stub getenforce ": echo '${capitalized}'"
mkdir -p /etc/selinux
echo "SELINUX=${state}" > /etc/selinux/config
}
@test "SELinux enforcing: installer exits with error" {
_mock_selinux_config "enforcing"
run bash -c "
source /opt/pihole/basic-install.sh
checkSelinux
"
assert_output --partial "${CROSS} Current SELinux: enforcing"
assert_output --partial "SELinux Enforcing detected, exiting installer"
assert_failure
unstub getenforce 2>/dev/null || true
}
@test "SELinux permissive: installer continues" {
_mock_selinux_config "permissive"
run bash -c "
source /opt/pihole/basic-install.sh
checkSelinux
"
assert_output --partial "${TICK} Current SELinux: permissive"
assert_success
unstub getenforce 2>/dev/null || true
}
@test "SELinux disabled: installer continues" {
_mock_selinux_config "disabled"
run bash -c "
source /opt/pihole/basic-install.sh
checkSelinux
"
assert_output --partial "${TICK} Current SELinux: disabled"
assert_success
unstub getenforce 2>/dev/null || true
}
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bats
# Tests for utils.sh
load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load 'libs/bats-file/load'
setup() {
TEST_TEMP_DIR="$(temp_make)"
}
teardown() {
temp_del "${TEST_TEMP_DIR}"
}
# ---------------------------------------------------------------------------
@test "addOrEditKeyValPair adds and replaces key-value pairs correctly" {
local outfile="${TEST_TEMP_DIR}/testoutput"
bash -c "
source /opt/pihole/utils.sh
addOrEditKeyValPair '${outfile}' 'KEY_ONE' 'value1'
addOrEditKeyValPair '${outfile}' 'KEY_TWO' 'value2'
addOrEditKeyValPair '${outfile}' 'KEY_ONE' 'value3'
addOrEditKeyValPair '${outfile}' 'KEY_FOUR' 'value4'
"
assert_file_exists "${outfile}"
assert_file_contains "${outfile}" "KEY_ONE=value3"
assert_file_contains "${outfile}" "KEY_TWO=value2"
assert_file_contains "${outfile}" "KEY_FOUR=value4"
assert_file_not_contains "${outfile}" "KEY_ONE=value1"
}
@test "getFTLPID returns -1 when FTL is not running" {
run bash -c "
source /opt/pihole/utils.sh
getFTLPID
"
assert_output "-1"
}
@test "setFTLConfigValue and getFTLConfigValue round-trip" {
# FTL must be installed for this test
bash -c "
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=\$(get_binary_name)
echo 'development' > /etc/pihole/ftlbranch
binary=\"pihole-FTL\${funcOutput##*pihole-FTL}\"
theRest=\"\${funcOutput%pihole-FTL*}\"
FTLdetect \"\${binary}\" \"\${theRest}\"
"
run bash -c "
source /opt/pihole/utils.sh
setFTLConfigValue 'dns.upstreams' '[\"9.9.9.9\"]' > /dev/null
getFTLConfigValue 'dns.upstreams'
"
assert_output --partial "[ 9.9.9.9 ]"
}
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _alpine_3_21.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _alpine_3_22.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _alpine_3_23.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _centos_10.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
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _centos_9.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
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _debian_11.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _debian_12.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _debian_13.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _fedora_40.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
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _fedora_41.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
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _fedora_42.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
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _fedora_43.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
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _ubuntu_20.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _ubuntu_22.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py
-10
View File
@@ -1,10 +0,0 @@
[tox]
envlist = py3
[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
setenv =
COLUMNS=120
commands = docker buildx build --load --progress plain -f _ubuntu_24.Dockerfile -t pytest_pihole:test_container ../
pytest {posargs:-vv -n auto} ./test_any_automated_install.py ./test_any_utils.py