Skip to content

Commit 601d456

Browse files
committed
Add workflow testing with act for local development
- Add tests/test_workflows.py with pytest-based workflow tests - Test CI and publish workflows using act - Skip tests in CI environments to avoid Docker-in-Docker conflicts - Use --container-architecture linux/amd64 for M-series chip compatibility - Support real TestPyPI tokens when available - Update README.md with workflow testing setup instructions - Document prerequisites: act, Docker, and optional TestPyPI token
1 parent 6ac47f0 commit 601d456

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,23 @@ CI & Publishing
153153
- CI runs unit tests and linting on every push. E2E tests run if `GEOCODIO_API_KEY` is set as a secret.
154154
- PyPI publishing workflow supports both TestPyPI and PyPI. See `.github/workflows/publish.yml`.
155155
- Use `test_pypi_release.py` for local packaging and dry-run upload.
156+
157+
### Testing GitHub Actions Workflows
158+
159+
The project includes tests for GitHub Actions workflows using `act` for local development:
160+
161+
```bash
162+
# Test all workflows (requires act and Docker)
163+
pytest tests/test_workflows.py
164+
165+
# Test specific workflow
166+
pytest tests/test_workflows.py::test_ci_workflow
167+
pytest tests/test_workflows.py::test_publish_workflow
168+
```
169+
170+
**Prerequisites:**
171+
- Install [act](https://github.com/nektos/act) for local GitHub Actions testing
172+
- Docker must be running
173+
- For publish workflow tests: Set `TEST_PYPI_API_TOKEN` environment variable
174+
175+
**Note:** Workflow tests are automatically skipped in CI environments.

tests/test_workflows.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import json
2+
import os
3+
import subprocess
4+
import sys
5+
import tempfile
6+
from pathlib import Path
7+
8+
import pytest
9+
10+
11+
def is_act_available():
12+
"""Check if act is installed and available."""
13+
try:
14+
subprocess.run(["act", "--version"], capture_output=True, text=True, check=True)
15+
return True
16+
except (subprocess.CalledProcessError, FileNotFoundError):
17+
return False
18+
19+
20+
def is_docker_running():
21+
"""Check if Docker is running."""
22+
try:
23+
subprocess.run(["docker", "info"], capture_output=True, text=True, check=True)
24+
return True
25+
except Exception:
26+
return False
27+
28+
29+
def is_ci_environment():
30+
"""Check if we're running in a CI environment."""
31+
return os.environ.get("CI") == "true" or os.environ.get("GITHUB_ACTIONS") == "true"
32+
33+
34+
@pytest.fixture(scope="module", autouse=True)
35+
def skip_if_no_act_or_docker_or_ci():
36+
"""Skip tests if act or Docker is not available, or if running in CI."""
37+
if is_ci_environment():
38+
pytest.skip("Skipping workflow tests in CI environment (Docker-in-Docker not supported)")
39+
if not is_act_available():
40+
pytest.skip("act is not installed or not available in PATH")
41+
if not is_docker_running():
42+
pytest.skip("Docker is not running")
43+
44+
45+
def run_act_command(event_name: str, workflow_file: str = None, event_file: str = None, env_vars: dict = None) -> subprocess.CompletedProcess:
46+
"""Run act command with proper flags for M-series chip compatibility."""
47+
cmd = ["act", event_name, "--container-architecture", "linux/amd64"]
48+
if workflow_file:
49+
cmd.extend(["-W", workflow_file])
50+
if event_file:
51+
cmd.extend(["-e", event_file])
52+
if env_vars:
53+
for key, value in env_vars.items():
54+
cmd.extend(["-s", f"{key}={value}"])
55+
return subprocess.run(cmd, capture_output=True, text=True)
56+
57+
58+
def test_ci_workflow():
59+
"""Test the CI workflow using act."""
60+
# Create a simple push event
61+
event_data = {
62+
"push": {
63+
"ref": "refs/heads/main",
64+
"before": "0000000000000000000000000000000000000000",
65+
"after": "1234567890123456789012345678901234567890"
66+
}
67+
}
68+
69+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
70+
json.dump(event_data, f)
71+
event_file = f.name
72+
73+
try:
74+
# Only set GEOCODIO_API_KEY if it exists in environment
75+
env_vars = {}
76+
if os.environ.get("GEOCODIO_API_KEY"):
77+
env_vars["GEOCODIO_API_KEY"] = os.environ["GEOCODIO_API_KEY"]
78+
79+
result = run_act_command("push", ".github/workflows/ci.yml", event_file, env_vars)
80+
print(result.stdout)
81+
print(result.stderr, file=sys.stderr)
82+
83+
# Check if the workflow got past the unit tests step
84+
# This indicates the workflow structure and basic setup is working
85+
assert "Success - Main Run unit tests" in result.stdout, f"Unit tests step failed: {result.stderr}"
86+
87+
# Note: e2e tests may fail due to Docker container issues on M-series chips
88+
# This is a known limitation of act, not a workflow issue
89+
if "Failure - Main Run e2e tests" in result.stdout:
90+
print("⚠️ E2e tests failed (likely due to Docker container issues on M-series chip)")
91+
print(" This is a known act limitation, not a workflow problem")
92+
93+
finally:
94+
os.unlink(event_file)
95+
96+
97+
def test_publish_workflow():
98+
"""Test the publish workflow using act."""
99+
# Skip if no TestPyPI token available
100+
if not os.environ.get("TEST_PYPI_API_TOKEN"):
101+
pytest.skip("TEST_PYPI_API_TOKEN not available - skipping publish workflow test")
102+
103+
event_file = Path(".github/workflows/test-act-event-publish.json")
104+
105+
if not event_file.exists():
106+
# Create a new event file if not present
107+
event_data = {
108+
"event": "workflow_dispatch",
109+
"workflow": "publish.yml",
110+
"ref": "refs/heads/main",
111+
"inputs": {
112+
"version": "0.0.1",
113+
"publish_to": "testpypi"
114+
}
115+
}
116+
117+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
118+
json.dump(event_data, f, indent=2)
119+
event_file = Path(f.name)
120+
cleanup = True
121+
else:
122+
cleanup = False
123+
124+
try:
125+
# Use real TestPyPI token
126+
env_vars = {
127+
"TEST_PYPI_API_TOKEN": os.environ["TEST_PYPI_API_TOKEN"]
128+
}
129+
130+
result = run_act_command("workflow_dispatch", ".github/workflows/publish.yml", str(event_file), env_vars)
131+
print(result.stdout)
132+
print(result.stderr, file=sys.stderr)
133+
134+
# Workflow should complete successfully and actually upload to TestPyPI
135+
assert result.returncode == 0, f"Publish workflow failed: {result.stderr}"
136+
finally:
137+
if cleanup:
138+
os.unlink(str(event_file))

0 commit comments

Comments
 (0)