name: Integration Tests with Docker Mixnet
on:
pull_request:
branches: [ main, master, update_python_pigeonhole ]
jobs:
test-integration-docker:
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Checkout thinclient repository
uses: actions/checkout@v4
with:
path: thinclient
- name: Checkout katzenpost repository
uses: actions/checkout@v4
with:
repository: katzenpost/katzenpost
ref: 28f28c7286cacc8d0d324206bec95ae87648ce47
path: katzenpost
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Configure podman socket
run: |
systemctl --user start podman.socket
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
- name: Install thinclient dependencies
run: |
cd thinclient
python -m pip install --upgrade pip
pip install -e '.[test,reticulum]'
- name: Log in to GitHub Container Registry
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin || true
- name: Build and start the mixnet
run: |
cd katzenpost/docker && make start wait
- name: Brief pause to ensure mixnet is fully ready
run: sleep 30
- name: Detect pigeonhole-cp-relevant changes
id: smoke_filter
uses: dorny/paths-filter@v3
with:
working-directory: thinclient
filters: |
smoke:
- 'src/bin/**'
- 'src/persistent/**'
- 'src/pigeonhole.rs'
- 'src/core.rs'
- 'tests/smoke_pigeonhole_cp.rs'
- 'Cargo.toml'
- 'Cargo.lock'
- name: Run Python and Rust integration tests in parallel
id: parallel-tests
timeout-minutes: 60
run: |
cd thinclient
# Run Python tests in background. tests/integration is
# excluded here and run serially in its own step afterwards:
# its client-side Reticulum link handshake is starved when
# the runner is saturated by this parallel mixnet + Rust +
# xdist workload.
python -m pytest tests/ --ignore=tests/integration \
-vvv -s --tb=short --timeout=1200 -n 3 \
--durations=25 \
> /tmp/python-test-output.txt 2>&1 &
PYTHON_PID=$!
# Run Rust tests in background
cargo test --test '*' -- --nocapture --test-threads=3 \
> /tmp/rust-test-output.txt 2>&1 &
RUST_PID=$!
# Wait for both and capture exit codes
PYTHON_RC=0
RUST_RC=0
wait $PYTHON_PID || PYTHON_RC=$?
wait $RUST_PID || RUST_RC=$?
# Print outputs
echo "=== Python test output ==="
cat /tmp/python-test-output.txt
echo ""
echo "=== Rust test output ==="
cat /tmp/rust-test-output.txt
echo ""
# Report results
echo "Python tests exit code: $PYTHON_RC"
echo "Rust tests exit code: $RUST_RC"
# Fail if either failed
if [ $PYTHON_RC -ne 0 ] || [ $RUST_RC -ne 0 ]; then
exit 1
fi
- name: Run pkimirror e2e tests serially
if: always()
timeout-minutes: 15
run: |
cd thinclient
# Proven by the captured pkimirror server log: the server
# announces healthily throughout while the client cannot
# establish a Reticulum link within 150s, because the
# client RNS stack is CPU/scheduling-starved by the
# concurrent mixnet + Rust + xdist load. Running this here,
# after the parallel step has finished, gives it a quiet
# runner and a single process (no xdist co-tenancy).
python -m pytest tests/integration -vvv -s --tb=short \
--timeout=1200
- name: Run pigeonhole-cp smoke test
if: steps.smoke_filter.outputs.smoke == 'true'
timeout-minutes: 10
run: |
cd thinclient
cargo test --features cli --test smoke_pigeonhole_cp -- --nocapture
- name: Capture kpclientd container logs
if: always()
run: |
if [ ! -d katzenpost/docker/voting_mixnet ]; then
echo "voting_mixnet directory absent; the mixnet never started, nothing to capture"
exit 0
fi
cd katzenpost/docker/voting_mixnet
DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock" \
podman compose logs --no-color --no-log-prefix kpclientd \
> kpclientd.log 2>&1 || true
- name: Collect pkimirror server logs
if: always()
run: |
mkdir -p thinclient/pkimirror-logs
i=0
for f in $(find /tmp -name pkimirror.log -path '*pkimirror-srv*' 2>/dev/null); do
cp "$f" "thinclient/pkimirror-logs/pkimirror-$i.log" 2>/dev/null || true
i=$((i + 1))
done
echo "collected $i pkimirror server log(s)"
ls -la thinclient/pkimirror-logs/ || true
- name: Stop the mixnet
if: always()
run: |
cd katzenpost/docker && make stop
- name: Upload testnet state
uses: actions/upload-artifact@v4
if: always()
with:
name: mixnet-${{ github.run_id }}-${{ github.job }}
path: |
katzenpost/docker/voting_mixnet/*/*.log
katzenpost/docker/voting_mixnet/kpclientd.log
katzenpost/docker/voting_mixnet/servicenode*/courier/*.log
thinclient/pkimirror-logs/*.log