Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Tests

# CPU-only checks: everything here runs on a GitHub-hosted runner with no GPU
# and no pycolmap/torch build, so it covers a subset of AGENTS.md's "Tests"
# list. Skipped on purpose: queue/test_api.py (needs a running service),
# scripts/test_fisheye.py (needs pycolmap), and the GPU half of
# scripts/test_benchmark.py (skips itself when there is no CUDA device).

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

concurrency:
group: tests-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
queue:
name: Queue service tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
cache-dependency-path: queue/requirements.txt

- name: Install queue dependencies
run: pip install -r queue/requirements.txt

- name: Install ffmpeg
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends ffmpeg

# Each suite sets its own SPLAT_ROOT/QUEUE_ROOT/QUEUE_GPUS before
# importing app.* (AGENTS.md, "Tests") -- nothing to configure here.
- name: test_stages.py
run: python3 queue/test_stages.py
- name: test_worker.py
run: python3 queue/test_worker.py
- name: test_regressions.py
run: python3 queue/test_regressions.py
- name: test_distance.py
run: python3 queue/test_distance.py
- name: test_telemetry.py
run: python3 queue/test_telemetry.py
- name: test_remote.py
run: python3 queue/test_remote.py
- name: test_benchmark.py
run: python3 queue/test_benchmark.py
- name: test_hoststats.py
run: python3 queue/test_hoststats.py

scripts:
name: Pipeline script tests (CPU)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip

# numpy + OpenCV only: the same subset venv_gs/venv carry that these
# suites actually touch. No torch/gsplat/pycolmap here, so the GPU
# half of test_benchmark.py skips itself and test_fisheye.py is not run.
- name: Install script dependencies
run: pip install numpy opencv-python-headless

- name: test_avata_motion.py
run: python3 scripts/test_avata_motion.py
- name: test_imu_select.py
run: python3 scripts/test_imu_select.py
- name: test_upright.py
run: python3 scripts/test_upright.py
- name: test_benchmark.py (CPU part; GPU part skips without CUDA)
run: python3 scripts/test_benchmark.py

guardrails:
name: queue/app stays free of heavy deps
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4

# AGENTS.md: "The service's own venv has no numpy/OpenCV, so heavy
# work belongs in a script, not in queue/app/." Cheap to check for
# every push instead of finding out at deploy time.
- name: No numpy/cv2/torch imports in queue/app
run: |
if grep -rEn '^\s*(import|from)\s+(numpy|cv2|torch)\b' queue/app --include='*.py'; then
echo "::error::queue/app imported a heavy dependency; that work belongs in scripts/ (AGENTS.md)"
exit 1
fi
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ Every test must set `SPLAT_ROOT`/`QUEUE_ROOT` to a temporary directory and
`QUEUE_GPUS=""` **before** importing `app.*` — `config.py` reads them at import
time, and a test that forgot once wrote into a live queue database.

**CI** (`.github/workflows/tests.yml`) runs everything above except
`test_api.py` (needs a running service) and `test_fisheye.py` (needs
pycolmap) on every push and pull request, on a GitHub-hosted runner with no
GPU — `scripts/test_benchmark.py`'s GPU half skips itself there, same as
locally. A separate job greps `queue/app/` for `numpy`/`cv2`/`torch` imports
to keep the layout rule above honest.

## Rules

- **Cache keys.** Each stage is cached by a hash of the options that affect
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# OSVplat

[![Tests](https://github.com/pgodlews/OSVplat/actions/workflows/tests.yml/badge.svg)](https://github.com/pgodlews/OSVplat/actions/workflows/tests.yml)

**Raw DJI `.OSV` dual-fisheye → Gaussian splat. No stitch.**

OSVplat turns a DJI Osmo 360 or Avata 360 `.OSV` into a flyable 3D Gaussian
Expand Down
26 changes: 23 additions & 3 deletions queue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ POST /api/cache/gc[?dry_run&budget_gb]

## Tests

Five suites, installed by `deploy.sh`. Only the API suite needs a running
Ten suites, installed by `deploy.sh`. Only the API suite needs a running
service and reads `QUEUE_TOKEN` from the service's own env file:

```bash
Expand All @@ -352,10 +352,22 @@ set -a; . ~/splat/queue_app/.queue_env; set +a; python3 ~/splat/queue_app/test_a
~/splat/queue_app/venv/bin/python ~/splat/queue_app/test_stages.py
~/splat/queue_app/venv/bin/python ~/splat/queue_app/test_worker.py
~/splat/queue_app/venv/bin/python ~/splat/queue_app/test_regressions.py
~/splat/queue_app/venv/bin/python ~/splat/queue_app/test_distance.py
~/splat/queue_app/venv/bin/python ~/splat/queue_app/test_telemetry.py
~/splat/queue_app/venv/bin/python ~/splat/queue_app/test_remote.py
~/splat/queue_app/venv/bin/python ~/splat/queue_app/test_benchmark.py
~/splat/queue_app/venv/bin/python ~/splat/queue_app/test_hoststats.py
~/splat/venv/bin/python ~/splat/scripts/test_fisheye.py
```

None of the nine non-API suites need a GPU, and every one of them sets its
own `SPLAT_ROOT`/`QUEUE_ROOT`/`QUEUE_GPUS` to a scratch directory before
importing `app.*`, so they never touch a live queue. `.github/workflows/tests.yml`
runs the CPU-only subset (everything above except `test_api.py` and
`test_fisheye.py`, which need a running service and pycolmap respectively) on
every push and pull request; see [AGENTS.md](../AGENTS.md#tests) for the full
list, including the `scripts/` suites that need `venv_gs`.

`test_worker.py` covers the lifecycle paths that used to be reasoned about and
never exercised, each one a way a job could end up holding a GPU or a cache
directory could end up with two writers: a cancel racing the dispatcher, a
Expand All @@ -377,8 +389,16 @@ key while each variant gets a distinct `train` key.

`test_regressions.py` uses ffmpeg and an isolated database to check dual-lens
trimming, concurrent thumbnails, scheduling estimates, pagination and fitted
history. `scripts/test_fisheye.py` uses numpy and pycolmap on CPU to check stale
model rejection and the distortion refit; neither suite starts GPU work.
history. `test_distance.py` covers the Avata distance-selection option's cache
keys and API surface (see below). `test_remote.py` covers CPU/GPU discovery and
the output upload for rented-GPU runs (docs/cloud.md), against a local HTTP
server standing in for S3. `test_benchmark.py` covers the host-benchmark
service side (the machine hold, the run record); the workload itself
(`scripts/benchmark.py`) is tested by `scripts/test_benchmark.py`, which needs
`venv_gs`. `test_hoststats.py` covers the machine-load counters and the
per-job time series against a faked `/proc` and cgroup tree. `scripts/test_fisheye.py`
uses numpy and pycolmap on CPU to check stale model rejection and the
distortion refit; none of these suites starts GPU work.

## Validated end to end

Expand Down
4 changes: 3 additions & 1 deletion queue/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ rsync -az "$REPO/queue/requirements.txt" "$DEST$APP/"
# have to be there; test_api.py is stdlib-only and runs from anywhere.
rsync -az "$REPO/queue/test_stages.py" "$REPO/queue/test_worker.py" \
"$REPO/queue/test_api.py" "$REPO/queue/test_regressions.py" \
"$REPO/queue/test_distance.py" "$DEST$APP/"
"$REPO/queue/test_distance.py" "$REPO/queue/test_telemetry.py" \
"$REPO/queue/test_remote.py" "$REPO/queue/test_benchmark.py" \
"$REPO/queue/test_hoststats.py" "$DEST$APP/"
# summarize_sweep.py reads the queue over HTTP and is meant to be run on the
# target, where it can pick the token out of .queue_env; it was never shipped there.
rsync -az "$REPO/queue/summarize_sweep.py" "$DEST$APP/"
Expand Down
Loading