From cd3ede2134e138f96823eb32704419fe8c67921a Mon Sep 17 00:00:00 2001 From: Hoang Tuan Nguyen Date: Fri, 18 Sep 2026 00:27:35 +0700 Subject: [PATCH] ci: run the unit suite on every PR and push to main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was no CI at all — `.github/` held only FUNDING.yml — so nothing ran the tests until someone ran them by hand. PR #48 reached review carrying an ImportError against `agent.config.USE_BATCH_RPC` and `agent/services/headers.py`, both deleted by a PR that merged while #48 was open. The code could not import on main, and the only reason that surfaced is that someone happened to try. This job is what catches that class of thing. Matrix is 3.10 and 3.13: the floor the README badge advertises and the version development runs on. 3.10 was verified rather than assumed — the full suite runs there, 259 of 259 non-ffmpeg tests pass. Two things the run needs that are easy to get wrong: `fonts-dejavu-core` is installed alongside ffmpeg. The drawtext call in video_reviewer.py passes no `fontfile=`, so it asks fontconfig for a default family; with --no-install-recommends a runner can end up holding an ffmpeg that has drawtext and no font for it to use. The ffmpeg check renders a frame instead of grepping `ffmpeg -filters`. Grep proves the filter is compiled in, not that a font exists, and a stock Homebrew ffmpeg already demonstrates the first failure mode: it ships without --enable-libfreetype, and 13 tests then fail with "Filter not found", which reads like a test bug rather than a missing codec. Rendering one frame through the same filter the tests use catches both modes in the step whose name says what is wrong. Verified in both directions against a good and a drawtext-less ffmpeg, running the script as PyYAML parses it so the escaping is checked too. timeout-minutes is 10 against a ~7s suite. That ceiling only trips on a hang, which this suite has had: an aiosqlite connection left open by a missing mock keeps a non-daemon worker thread alive and the interpreter never exits. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01E5BJVhNgerhFTUgmfnKTSj --- .github/workflows/tests.yml | 74 +++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..229aa5278 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,74 @@ +# Nothing ran the tests on push or PR before this, so a broken main was only +# ever caught by hand. PR #48 reached `main` carrying an ImportError against a +# module a merged PR had deleted; this job is what catches that class of thing. +name: tests + +on: + pull_request: + push: + branches: [main] + +# A new push supersedes the run in flight for the same ref. +concurrency: + group: tests-${{ github.ref }} + cancel-in-progress: true + +jobs: + unit: + runs-on: ubuntu-latest + # The suite takes ~7s. A ceiling this generous only ever trips on a hang, + # which this suite has had before: an aiosqlite connection left open keeps a + # non-daemon worker thread alive and the interpreter never exits. + timeout-minutes: 10 + + strategy: + fail-fast: false + matrix: + # The floor the README advertises, and the version development runs on. + python-version: ["3.10", "3.13"] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: | + requirements.txt + requirements-dev.txt + + - name: Install ffmpeg + # fonts-dejavu-core is not optional. The drawtext call in + # video_reviewer.py passes no fontfile=, so it asks fontconfig for a + # default family; with --no-install-recommends the runner can end up + # with an ffmpeg that has drawtext and no font for it to use. + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends ffmpeg fonts-dejavu-core + + - name: Assert drawtext actually renders + # 13 of these tests burn a timestamp into extracted frames. Checking + # `ffmpeg -filters` only proves the filter is compiled in, not that a + # font exists, so render one frame through the same filter the tests + # use. Both failure modes surface here with a message that says what is + # wrong, instead of as 13 tests failing on "Filter not found". + run: | + ffmpeg -hide_banner -loglevel error \ + -f lavfi -i "testsrc=duration=1:size=320x240:rate=5" \ + -vf "drawtext=text='%{pts\:hms}':x=5:y=5:fontsize=14:fontcolor=white:borderw=1:bordercolor=black" \ + -frames:v 1 -y /tmp/drawtext-smoke.jpg || { + echo "::error::ffmpeg cannot render drawtext — missing --enable-libfreetype, or no font installed." + ffmpeg -hide_banner -version | head -3 + exit 1 + } + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt -r requirements-dev.txt + + - name: Run unit tests + env: + PYTHONUNBUFFERED: "1" + run: python -m pytest tests/unit -q