Hop3 Testing Cheat Sheet¶
Updated by ADR 043. The pytest pyramid is now three layers —
a_unit(fast, no Docker) ·b_integration(in-process, real in-memory DB, no Docker) ·c_e2e(Docker/real-deploy, renamed fromd_e2e). The oldc_systemlayer is dissolved. A test's layer is decided by whether it needs Docker/root/host-mutation, not by complexity; coverage is measured ona_unit+b_integrationonly (e2e runs out-of-process). Markers (fast/integration/e2e/needs_docker) are stamped from the directory layer (rootconftest.py), sopytest -m fast/-m "not needs_docker"work everywhere.
Quick reference for developers running tests.
Quick Commands¶
| What | Command |
|---|---|
| Fast unit tests (< 1 min) | make test-fast |
| Check tier (unit + integration, no Docker) | make test |
| Docker e2e (real deploys, backups, git-push) | make test-e2e |
| App tests (Docker) | make test-apps |
| Lint & type check | make lint |
| System tests (Docker) | hop3-test system --docker |
| Hetzner Cloud test | hop3-test hetzner --image ubuntu-24.04 |
| Multi-distro test | hop3-test multi-distro |
hop3-test CLI¶
The unified test runner for Hop3 deployment testing.
System Testing (Testing Hop3 Itself)¶
Deploys Hop3 using hop3-deploy, then runs tests against it.
# Deploy local code to Docker and test
hop3-test system --docker
# Deploy from git branch
hop3-test system --docker --deploy-from git --branch main
# Deploy from PyPI
hop3-test system --docker --deploy-from pypi
# Clean install (remove existing)
hop3-test system --docker --clean
# Reuse existing deployment (skip deploy)
hop3-test system --docker --reuse
# Or equivalently:
hop3-test system --docker --deploy-from none
# Remote server via SSH
hop3-test system --ssh --host server.example.com
# SSH using HOP3_TEST_HOST env var
export HOP3_TEST_HOST=server.example.com
hop3-test system --ssh
# Test mode: dev (fast) or ci (more thorough)
hop3-test system --docker --mode ci
# Keep target running after tests
hop3-test system --docker --keep
# Generate HTML report
hop3-test system --docker --report html
Deploy-from Options¶
| Option | Description |
|---|---|
--deploy-from local |
Upload and install local code (default) |
--deploy-from git |
Clone and install from git branch |
--deploy-from pypi |
Install from PyPI |
--deploy-from none |
Skip deployment, use existing |
--reuse |
Alias for --deploy-from none |
App Testing (Testing Apps, Not Hop3)¶
Uses a pre-built Docker image with Hop3 already installed.
# First, build the ready image (one-time)
hop3-test build-ready-image
# Test all apps
hop3-test apps
# Test specific app
hop3-test apps 010-flask-pip-wsgi
# Test by category
hop3-test apps --category python
# Keep apps deployed after testing
hop3-test apps --keep
# Against remote server
hop3-test apps --target remote --host server.example.com
Listing and Inspecting Tests¶
# List all tests
hop3-test list
# Filter by category
hop3-test list --category deployment
# Filter by tier
hop3-test list --tier fast
# Show test details
hop3-test show 010-flask-pip-wsgi
Package Validation¶
For package authors testing their apps against stable Hop3.
Building Docker Images¶
# Build ready image (pre-installed Hop3)
hop3-test build-ready-image
hop3-test build-ready-image --tag my-hop3:v1
hop3-test build-ready-image --no-cache
# Build test image (for system tests)
hop3-test build-test-image
hop3-test build-test-image --no-cache
Quick Modes¶
# Developer mode (fast P0 tests only)
hop3-test dev
# CI mode (fast + medium P0 tests)
hop3-test ci
# Nightly mode (all tiers, all priorities)
hop3-test nightly
Hetzner Cloud Testing¶
Run E2E tests on real Hetzner Cloud infrastructure. Requires HETZNER_API_TOKEN.
# List available images
hop3-test multi-distro --list-images
# Single distribution test
hop3-test hetzner --image ubuntu-24.04 --suites test-apps
# Multi-distribution test
hop3-test multi-distro --images ubuntu-24.04 debian-13 fedora-42
# Use local code instead of git
hop3-test hetzner --use-local-repo
# Skip phases for debugging
hop3-test hetzner --skip-reset # Keep existing server state
hop3-test hetzner --skip-deploy # Use existing Hop3 installation
Pytest Tests¶
Run by Layer¶
There are three layers under each package's tests/. A test's layer is decided by what it needs (Docker / root / host-mutation), not by complexity; duplication across layers is allowed.
# Unit tests — no Docker, counts toward coverage (tier: fast)
uv run pytest packages/hop3-server/tests/a_unit
# Integration tests — in-process, real in-memory DB, no Docker, counts toward
# coverage (tier: check)
uv run pytest packages/hop3-server/tests/b_integration
# E2E tests — real Docker deploy, NO coverage (check (Docker) + nightly)
uv run pytest packages/hop3-server/tests/c_e2e
# CLI tests
uv run pytest packages/hop3-cli/tests
Markers are stamped from the directory by the root conftest.py, so you can select layers anywhere:
uv run pytest -m fast # a_unit + flat unit suites
uv run pytest -m "not needs_docker" # everything except the Docker e2e layer
uv run pytest packages/hop3-server/tests/c_e2e # the Docker e2e layer
Run Specific Tests¶
# Single file
uv run pytest packages/hop3-server/tests/a_unit/test_app_config.py
# Single test
uv run pytest packages/hop3-server/tests/a_unit/test_app_config.py::test_function_name
# By keyword
uv run pytest -k "backup" packages/hop3-server/tests
# By marker (fast / integration / e2e / needs_docker)
uv run pytest -m "not needs_docker" packages/hop3-server/tests
Useful Flags¶
# Verbose output
uv run pytest -v
# Stop on first failure
uv run pytest -x
# Show print statements
uv run pytest -s
# Parallel execution (faster)
uv run pytest -n 4
# Show slowest tests
uv run pytest --durations=10
# Coverage report
uv run pytest --cov=hop3 --cov-report=term-missing
Common Workflows¶
Before Committing¶
make lint # Check formatting and types
make test-fast # Fast unit tests (the inner loop, < 1 min)
make test # Check tier: unit + integration, all packages, no Docker
Quick Validation (Developer)¶
Full Validation¶
# Check tier (in-process, no Docker) plus the Docker e2e layer
make test
make test-e2e
# Deploy the real app catalog on Docker
make test-apps
# Or run the system suite manually
hop3-test system --docker --mode ci --report html
Debug a Failing Test¶
# Run with verbose output
uv run pytest -v -s path/to/test.py::test_name
# Keep target running for inspection
hop3-test apps --keep 010-flask-pip-wsgi
# Run system tests and keep target
hop3-test system --docker --keep
# Reuse container for fast iteration
hop3-test system --docker --reuse --keep
# Generate HTML report for analysis
hop3-test system --docker --report html
Test Coverage¶
make test-with-coverage
# HTML report
uv run pytest --cov=hop3 --cov-report=html
open htmlcov/index.html
Test Directory Structure¶
packages/hop3-server/tests/
├── a_unit/ # Unit; no Docker; counts toward coverage (tier: fast)
├── b_integration/ # In-process, real in-memory DB; no Docker; coverage (tier: check)
└── c_e2e/ # End-to-end; real Docker deploy; no coverage (check (Docker) + nightly)
packages/hop3-testing/ # Test framework
├── src/hop3_testing/
│ ├── catalog/ # Test catalog (reads [test] section from hop3.toml)
│ ├── cli/ # CLI commands
│ ├── runners/ # Test runners
│ ├── results/ # Result storage and reporting
│ ├── selector/ # Test selection logic
│ └── targets/ # Deployment targets
apps/test-apps/ # Test applications
├── 000-static/
├── 010-flask-pip-wsgi/
├── 020-nodejs-express/
├── 030-golang-gin/
├── 040-sinatra/
├── 100-flask-gunicorn-pip/
├── 110-flask-gunicorn-poetry/
└── 130-golang-minimal/
Test Configuration ([test] in hop3.toml)¶
Tests are configured via a [test] section in the app's hop3.toml. Most fields are derived from other sections (metadata, build, addons, healthcheck); the [test] block only holds the test-framework-specific bits:
[test]
priority = "P0" # P0 | P1 | P2
tier = "fast" # report label only — no longer drives timeouts
targets = ["docker", "remote"]
covers = ["python", "flask", "pip", "uwsgi"]
[[test.validations]]
path = "/"
status = 200
Legacy standalone test.toml files are still used by procfile-only test apps (apps/test-apps-procfile/*/), negative-test cases, demos, and tutorials — anywhere there's no sibling hop3.toml. See config.md for the full reference.
Environment Variables¶
| Variable | Purpose |
|---|---|
HOP3_DEV_HOST |
SSH target for deployment |
HOP3_TEST_HOST |
SSH target for --ssh without --host |
HOP3_TEST_SSH_KEY |
SSH key for remote tests |
HOP3_UNSAFE=true |
Disable auth in Docker tests |
HETZNER_API_TOKEN |
Hetzner Cloud API token (for hetzner/multi-distro) |
Troubleshooting¶
Docker Tests Fail¶
# Check if container is running
docker ps -a | grep hop3
# View container logs
docker logs hop3-test
# Rebuild test image
hop3-test build-test-image --no-cache
App Tests Fail (Ready Image)¶
# Rebuild ready image
hop3-test build-ready-image --no-cache
# Test with verbose output
hop3-test apps -v 010-flask-pip-wsgi
# Check HTML report
hop3-test apps --report html
System Tests Timeout¶
# Reuse existing container to debug
hop3-test system --docker --reuse --keep
# Inspect the diagnostic bundle collected on a failed Docker e2e/app run
hop3-test why <run-id>
Remote Tests Fail¶
# Verify SSH connection
ssh hop3@$HOP3_TEST_HOST "hop3 --version"
# Check server status
ssh root@$HOP3_TEST_HOST "systemctl status hop3-server"
Target Types¶
| Target | Use Case | Speed |
|---|---|---|
--docker |
System tests with fresh deploy | Slow (~5 min startup) |
ready |
App tests with pre-built image | Fast (~30s startup) |
--ssh |
Tests against real servers | Variable |
When to Use Each¶
hop3-test system --docker: Testing Hop3 changes (deploys Hop3 first)hop3-test system --docker --reuse: Fast iteration on existing containerhop3-test system --ssh --host X: Testing against remote servershop3-test apps: Testing app configurations (uses pre-built image)