PRD 0002: Test pipeline on Gitea Actions (#3)
test / run tests/run_tests.py (push) Successful in 20s
test / run tests/run_tests.py (push) Successful in 20s
This commit was merged in pull request #3.
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
# Run the project's full test suite on every PR push and on push to main.
|
||||||
|
#
|
||||||
|
# The suite uses stdlib `unittest` (see tests/run_tests.py) — no external
|
||||||
|
# Python dependencies are required to execute it. Integration tests need a
|
||||||
|
# reachable Docker daemon; if Docker is unavailable on the runner those
|
||||||
|
# tests skip cleanly via tests/_docker.py:skip_unless_docker, so the job
|
||||||
|
# still passes (with skips visible in the run output).
|
||||||
|
#
|
||||||
|
# This workflow assumes the Gitea Actions runner exposes the host Docker
|
||||||
|
# socket to the job container so `docker` commands inside the job can
|
||||||
|
# reach the daemon. If that's not yet configured on the runner the
|
||||||
|
# integration tests will skip rather than fail.
|
||||||
|
|
||||||
|
name: test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: run tests/run_tests.py
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Show environment
|
||||||
|
run: |
|
||||||
|
python3 --version
|
||||||
|
if command -v docker >/dev/null 2>&1; then
|
||||||
|
docker version || true
|
||||||
|
else
|
||||||
|
echo "docker not on PATH — integration tests will skip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run full test suite
|
||||||
|
run: python3 tests/run_tests.py
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# claude-bottle
|
# claude-bottle
|
||||||
|
|
||||||
|
[](https://gitea.dideric.is/didericis/claude-bottle/actions?workflow=test.yml)
|
||||||
|
|
||||||
Spins up an isolated container for running Claude Code with a curated set of skills and env vars.
|
Spins up an isolated container for running Claude Code with a curated set of skills and env vars.
|
||||||
|
|
||||||
## Why "claude-bottle"?
|
## Why "claude-bottle"?
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
# CI
|
||||||
|
|
||||||
|
The test workflow lives at [`.gitea/workflows/test.yml`](../.gitea/workflows/test.yml).
|
||||||
|
It runs `tests/run_tests.py` (full suite — unit + integration) on:
|
||||||
|
|
||||||
|
- every push to a branch with an open pull request, and
|
||||||
|
- every push to `main`.
|
||||||
|
|
||||||
|
Integration tests need Docker on the runner; they skip cleanly via
|
||||||
|
`tests/_docker.skip_unless_docker` when no daemon is reachable.
|
||||||
|
|
||||||
|
A small subset of integration tests skip when running specifically
|
||||||
|
under Gitea Actions (`GITEA_ACTIONS=true`), because `act_runner` runs
|
||||||
|
the job inside a container with the host's `/var/run/docker.sock`
|
||||||
|
mounted in. That topology breaks two assumptions those tests make:
|
||||||
|
|
||||||
|
- networks created via the host daemon aren't always visible to a
|
||||||
|
same-process `docker network ls` call from inside the job container,
|
||||||
|
and
|
||||||
|
- ports published by sibling containers land on the host's loopback,
|
||||||
|
not on the job container's `127.0.0.1` — so HTTP probes against
|
||||||
|
`http://127.0.0.1:<host_port>` from inside the job time out.
|
||||||
|
|
||||||
|
The affected tests (`test_orphan_cleanup.test_create_and_remove`,
|
||||||
|
`test_pipelock_sidecar_smoke.test_smoke`) still run locally where the
|
||||||
|
test process and Docker daemon share a host. Making them work in CI
|
||||||
|
is a follow-up: either re-write them to discover container IPs via
|
||||||
|
`docker inspect`, or reconfigure the runner with host networking.
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# PRD 0002: Test pipeline on Gitea Actions
|
||||||
|
|
||||||
|
- **Status:** Draft
|
||||||
|
- **Author:** didericis
|
||||||
|
- **Created:** 2026-05-08
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Run the project's test suite on every push to a PR via Gitea Actions, surfacing pass/fail on the PR.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
There is no automated test run today — tests only run when the author remembers to invoke them locally before pushing or merging. The CI loop is missing: nothing reruns the suite on each push, and there's no shared signal for whether a branch is green.
|
||||||
|
|
||||||
|
## Goals / Success Criteria
|
||||||
|
|
||||||
|
- Every PR shows a passing/failing tests check from Gitea Actions, updated per push.
|
||||||
|
- Pushing a fix to a red PR re-runs the workflow automatically and turns it green without manual re-trigger.
|
||||||
|
- The workflow file is committed in-tree.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Pipeline speed / wall-clock optimization.
|
||||||
|
- Matrix testing across Python versions or operating systems.
|
||||||
|
- Notifications (Slack, email, etc.) on failure.
|
||||||
|
- Caching dependencies between runs.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
### In scope
|
||||||
|
|
||||||
|
- A Gitea Actions workflow that runs `tests/run_tests.py` (full suite — unit + integration where the runner's docker topology supports it) on every push event affecting a PR, plus pushes to `main`.
|
||||||
|
- A status badge in the README so contributors can see CI state at a glance.
|
||||||
|
- Whatever dependency-manifest changes are needed to make the runner execute `tests/run_tests.py` cleanly.
|
||||||
|
|
||||||
|
### Out of scope
|
||||||
|
|
||||||
|
- Branch-protection rules / merge gating on `main`.
|
||||||
|
- Deploy / release pipeline (publishing images, tagging releases, etc.).
|
||||||
|
- Coverage reporting or quality gates.
|
||||||
|
- Lint / format checks beyond the test suite.
|
||||||
|
|
||||||
|
## Proposed Design
|
||||||
|
|
||||||
|
### New services / components
|
||||||
|
|
||||||
|
- `.gitea/workflows/test.yml` — workflow definition. Triggers on `pull_request` and `push` to `main`. Runs `tests/run_tests.py` (stdlib `unittest`; no external test deps required).
|
||||||
|
|
||||||
|
### Existing code touched
|
||||||
|
|
||||||
|
- `tests/` — a small number of integration tests are skipped under `GITEA_ACTIONS=true` because act_runner's docker socket mount breaks their host-loopback assumptions. Skips are local to the affected tests.
|
||||||
|
- `README.md` — adds a CI status badge.
|
||||||
|
|
||||||
|
### Data model changes
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### External dependencies
|
||||||
|
|
||||||
|
- Relies on a Gitea Actions runner registered to (or instance-scoped above) the repo on `gitea.dideric.is`.
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
|
||||||
|
- The two `GITEA_ACTIONS`-skipped integration tests could be rewritten to discover the container's IP via `docker inspect` rather than relying on host port mapping; that would let them pass under the socket-mount topology too. Filed as a follow-up, not in this PRD.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `tests/run_tests.py` — the runner CI will invoke.
|
||||||
|
- PRD 0001 (`docs/prds/0001-per-agent-egress-proxy-via-pipelock.md`) — prior PRD for repo numbering reference.
|
||||||
@@ -36,6 +36,11 @@ class TestOrphanCleanup(unittest.TestCase):
|
|||||||
# Returning True == idempotent success.
|
# Returning True == idempotent success.
|
||||||
self.assertTrue(network_remove(f"claude-bottle-net-{self.slug}-does-not-exist"))
|
self.assertTrue(network_remove(f"claude-bottle-net-{self.slug}-does-not-exist"))
|
||||||
|
|
||||||
|
@unittest.skipIf(
|
||||||
|
os.environ.get("GITEA_ACTIONS") == "true",
|
||||||
|
"skipped under act_runner: docker socket mount topology breaks "
|
||||||
|
"in-process visibility of networks created on the host daemon",
|
||||||
|
)
|
||||||
def test_create_and_remove(self):
|
def test_create_and_remove(self):
|
||||||
self.internal_name = network_create_internal(self.slug)
|
self.internal_name = network_create_internal(self.slug)
|
||||||
self.egress_name = network_create_egress(self.slug)
|
self.egress_name = network_create_egress(self.slug)
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ class TestPipelockSidecarSmoke(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
shutil.rmtree(self.work_dir, ignore_errors=True)
|
shutil.rmtree(self.work_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
@unittest.skipIf(
|
||||||
|
os.environ.get("GITEA_ACTIONS") == "true",
|
||||||
|
"skipped under act_runner: published port is on the host's "
|
||||||
|
"loopback, not reachable from the job container's 127.0.0.1",
|
||||||
|
)
|
||||||
def test_smoke(self):
|
def test_smoke(self):
|
||||||
yaml_path = self.work_dir / "pipelock.yaml"
|
yaml_path = self.work_dir / "pipelock.yaml"
|
||||||
pipelock_write_yaml(fixture_minimal(), "dev", yaml_path)
|
pipelock_write_yaml(fixture_minimal(), "dev", yaml_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user