Testing the Hop3 Installers¶
This document explains how to test the Hop3 installers (install-cli.py and install-server.py) using the end-to-end test suite in the hop3-installer package.
Overview¶
Installer testing is a pytest E2E suite living in packages/hop3-installer/tests/c_e2e/. It bundles the single-file installers with hop3-install bundle, then runs them against a fresh target and validates the result. The suite supports three backends, selected by command-line options:
| Backend | Option | Use Case |
|---|---|---|
| Docker | --docker (default) |
Quick iteration, CI/CD pipelines |
| SSH | --ssh-host HOST |
Production-like testing on real servers |
| Vagrant | --vagrant |
Full system testing with systemd in a VM |
If no backend option is given, the suite defaults to Docker only. A remote host is used only when --ssh-host HOST is passed explicitly — it is never taken from an env var (HOP3_TEST_HOST / HOP3_DEV_HOST are taboo for pytest; the root conftest.py strips them, per ADR 043). When any backend option is given, only the requested backends run.
Quick Start¶
# Run installer E2E tests on Docker (default backend)
uv run pytest packages/hop3-installer/tests/c_e2e --docker
# Run against a remote server via SSH
uv run pytest packages/hop3-installer/tests/c_e2e --ssh-host root@server.example.com
# Run in a Vagrant VM (slow; full systemd)
uv run pytest packages/hop3-installer/tests/c_e2e --vagrant
A make shortcut builds the bundles and runs the installer tests:
Test Layers¶
Installer tests follow the three-layer model from ADR 043. Each layer is decided by what a test needs (Docker, root, host mutation), and lives in its own directory under packages/hop3-installer/tests/:
| Layer | Location | Docker / VM? | What it covers |
|---|---|---|---|
| Unit | a_unit/ |
no | Bundler, common helpers, per-toolchain install logic |
| Integration | b_integration/ |
no | Bundler integration (validates the generated single-file scripts) |
| E2E | c_e2e/ |
yes | Full installer runs against Docker, SSH, or Vagrant targets |
The a_unit and b_integration layers run as part of make test and make test-fast; the c_e2e layer needs Docker (or an SSH/Vagrant target) and is the subject of this page.
Backend Options¶
These options are added by the E2E suite's conftest.py and apply to the whole c_e2e directory:
| Option | Description |
|---|---|
--docker |
Enable the Docker backend (the default with no flags) |
--ssh-host HOST |
Enable the SSH backend against HOST in user@hostname form (the only way to run against a real box) |
--vagrant |
Enable the Vagrant backend (slow; starts VMs) |
Docker Backend¶
The Docker backend starts a throwaway container, uploads the bundled installer, runs it, and validates the result. It is the default and the right choice for fast, isolated iteration and CI.
Prerequisites¶
- Docker installed and running locally
Distros¶
The Docker backend can target several base images (ubuntu is the default):
| Distro | Image |
|---|---|
ubuntu |
ubuntu:24.04 |
debian |
debian:12 |
fedora |
fedora:40 |
Systemd tests¶
Service-level checks (PostgreSQL, nginx, the hop3-server systemd unit) require an init system. These tests use a docker-systemd backend variant and are marked slow. Plain Docker containers without systemd skip them.
SSH Backend¶
The SSH backend runs the installer on a remote machine. This is the most production-like environment because it exercises a real init system and a real filesystem layout.
Prerequisites¶
- SSH access to a target server (key-based authentication recommended)
- Python 3 installed on the target server
- Root or passwordless-sudo access for server-installer tests
Usage¶
# Provide the host on the command line (the only way to select a remote target)
uv run pytest packages/hop3-installer/tests/c_e2e --ssh-host root@server.example.com
The host comes only from --ssh-host — it is never read from an env var. HOP3_SSH_USER sets the user when the --ssh-host value has no user@ prefix (it defaults to root).
Vagrant Backend¶
The Vagrant backend provisions a VM, giving full systemd behavior without touching a remote server. It is slower than Docker and is never enabled by default — pass --vagrant explicitly.
Prerequisites¶
- Vagrant installed
- A Vagrant provider (for example VirtualBox or libvirt)
Usage¶
The Vagrantfiles used by the suite live under packages/hop3-installer/tests/c_e2e/vagrant/.
Installation Methods¶
The bundled installers (install-cli.py, install-server.py) accept a few source-selection flags. The E2E tests exercise the --git and --local-path paths:
| Flag | What it tests |
|---|---|
--git --branch BRANCH |
Install from the git repository (default branch: devel) |
--local-path PATH |
Install from a local package directory uploaded to the target (used during development) |
Other installer flags used by the suite to keep runs fast and offline:
| Flag | Effect |
|---|---|
--skip-acme |
Skip Let's Encrypt certificate issuance (avoids quota and external dependencies in tests) |
--skip-postgres |
Skip PostgreSQL setup to speed up the basic install test |
--no-modify-path |
Don't edit shell rc files (CLI installer) |
--verbose |
Show detailed installer output |
What Gets Tested¶
CLI installer (test_cli_installer.py):
- Virtual environment creation at
~/.hop3-cli/venv - Package installation (
hop3-cli) - The
hop3(orhop) command exists in the venv - The command runs (
hop3 version)
Server installer (test_server_installer.py):
- System user/group creation (
hop3) - Virtual environment at
/home/hop3/venv - Package installation (
hop3-server) and a workinghop3-servercommand - Required directories (
/home/hop3/apps) - With systemd (the
slowtests): PostgreSQL service andhop3role, nginx service and a valid config (nginx -t), and thehop3-serversystemd unit being enabled
Developer deploy tool (test_deployer.py):
hop3-deploy-server --from localagainst a Docker or SSH target, end to end
Markers¶
Installer E2E tests carry the e2e marker (and needs_docker when stamped from the c_e2e layer; see the root conftest.py). The heavier systemd/service and deploy tests add the slow marker:
# Run only the faster E2E tests, skipping the slow service/deploy ones
uv run pytest packages/hop3-installer/tests/c_e2e --docker -m "not slow"
Environment Variables¶
| Variable | Description |
|---|---|
HOP3_SSH_USER |
SSH user when the --ssh-host value has no user@ prefix (default: root) |
HOP3_TEST_HOST/HOP3_DEV_HOSTare retired as SSH-target selectors (ADR 043): the SSH backend is enabled only by passing--ssh-host HOST. The rootconftest.pystrips those vars so a stray value can't redirect a run at a real box.
CI/CD Integration¶
For CI pipelines, use the Docker backend:
# Example GitHub Actions
test-installers:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: uv sync
- name: Test installers on Docker
run: uv run pytest packages/hop3-installer/tests/c_e2e --docker
For more thorough coverage, run the same suite against a real SSH server:
test-installers-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: uv sync
- name: Test installers on a server
run: uv run pytest packages/hop3-installer/tests/c_e2e --ssh-host ${{ secrets.TEST_SERVER_HOST }}
Troubleshooting¶
Docker not available¶
If Docker isn't running, the E2E suite skips the Docker backend (and, with no other backend configured, reports "No backends available"). Verify Docker is up:
SSH connection failed¶
- Verify SSH access:
ssh user@server echo ok - Set up key-based auth:
ssh-copy-id user@server - Check firewall rules and that
--ssh-hostis passed with the right host
Server tests need root¶
Server-installer tests run the installer with sudo. Use a root target or a user with passwordless sudo on the SSH backend.
Vagrant VM won't start¶
- Verify Vagrant and a provider are installed
- Check the provider's settings
- Inspect the Vagrantfiles under
packages/hop3-installer/tests/c_e2e/vagrant/
Architecture¶
The E2E suite is organized around a single Backend interface with one implementation per target:
packages/hop3-installer/tests/c_e2e/
├── conftest.py # CLI options (--docker/--ssh-host/--vagrant), fixtures
├── test_cli_installer.py # CLI installer tests
├── test_server_installer.py # Server installer tests (incl. slow systemd/service tests)
├── test_deployer.py # hop3-deploy-server tests
├── utils/
│ ├── installers.py # bundle_installers(), get_packages_dir()
│ └── backends/
│ ├── base.py # Backend interface
│ ├── discovery.py # get_backend(), availability probes
│ ├── docker.py # DockerBackend (distro images)
│ ├── docker_systemd.py # DockerSystemdBackend (systemd-in-Docker)
│ ├── ssh.py # SSHBackend
│ └── vagrant.py # VagrantBackend
└── vagrant/ # Vagrantfiles for the Vagrant backend
Each backend implements the same setup, run, upload, upload_dir, and cleanup methods, so adding a new target means adding one backend class and wiring it into get_backend.