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
42 changes: 42 additions & 0 deletions .forgejo/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Tests

# Forgejo Actions: runs the test suites only. The standalone executables and
# the releases are built on GitHub Actions (.github/workflows/build.yml and
# release.yml); .gitlab-ci.yml is the GitLab twin of this file.
#
# Three steps, as on GitHub: the headless suite, a GUI canary, then the
# guarded Gui* suites under Xvfb (the end-to-end layer of this Tk app).
on:
push:
branches: [develop, main]
pull_request:
workflow_dispatch:

jobs:
tests:
runs-on: docker # adapt to the instance's runner label
timeout-minutes: 30 # a stalled Tk suite must not hold the runner
container:
image: python:3.13-bookworm # full image: tkinter + libtk included
env:
XMODIFIERS: "@im=none" # Tk must not wait on the ibus XIM bridge
PIP_DISABLE_PIP_VERSION_CHECK: "1"
steps:
# nodejs: JS actions (checkout) run inside the job container on Forgejo
- name: System deps
run: |
apt-get update
apt-get install -y --no-install-recommends nodejs git xvfb xauth fonts-dejavu-core
- uses: actions/checkout@v4
- name: Install dependencies
run: pip install -r requirements.txt
- name: Unit tests (headless)
run: env -u DISPLAY python -m unittest -v
- name: GUI canary (broken Tk or broken gui module must fail, not skip)
run: xvfb-run -a -s "-screen 0 1920x1200x24" python -c "import tkinter, customtkinter, gui; tkinter.Tk().destroy()"
- name: GUI suites (Xvfb, skips are failures)
timeout-minutes: 20
env:
CACHET_REQUIRE_GUI: "1"
PYTHONFAULTHANDLER: "1" # with timeout -s ABRT: a stalled suite dumps its stacks
run: xvfb-run -a -s "-screen 0 1920x1200x24" timeout -s ABRT 900 python -m unittest -v -k Gui
66 changes: 55 additions & 11 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
# A stalled Tk suite or build must not hold the runner (and, for a
# release, its concurrency group) for GitHub's 6-hour default.
timeout-minutes: 45

steps:
- uses: actions/checkout@v4
Expand All @@ -35,37 +38,72 @@ jobs:

# poppler-utils: only to enable a future smoke-test of the GUI preview;
# not bundled into the binary (runtime dependency on the user's side).
# xvfb/xauth + Tcl/Tk: the virtual display and the Tk runtime of the GUI
# suites below.
- name: Linux system deps
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y poppler-utils
run: sudo apt-get update && sudo apt-get install -y poppler-utils xvfb xauth libtk8.6 libtcl8.6

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-build.txt

# Headless: the GUI tests skip without a display. Linux only — on the
# Windows runner Tk could open real windows and hang the job.
- name: Unit tests
# Tests run on Linux only — on the Windows runner Tk could open real
# windows and hang the job. Three steps: the headless suite (the Gui*
# classes skip themselves without a display), a canary, then the Gui*
# suites under Xvfb — the end-to-end layer of this Tk app.
- name: Unit tests (headless)
if: runner.os == 'Linux'
run: python -m unittest -v
run: env -u DISPLAY python -m unittest -v

# The canary imports gui on purpose: the display guard of the GUI tests
# swallows an ImportError / NameError / SyntaxError of gui.py exactly
# like a missing display.
- name: GUI canary (a broken Tk or a broken gui module must fail, not skip)
if: runner.os == 'Linux'
env:
XMODIFIERS: "@im=none"
run: xvfb-run -a -s "-screen 0 1920x1200x24" python -c "import tkinter, customtkinter, gui; tkinter.Tk().destroy()"

# CACHET_REQUIRE_GUI=1 turns the guard's skip into a failure, so "GUI
# suites green" can never mean "everything skipped".
- name: GUI suites (Xvfb, skips are failures)
if: runner.os == 'Linux'
timeout-minutes: 20
env:
XMODIFIERS: "@im=none"
CACHET_REQUIRE_GUI: "1"
PYTHONFAULTHANDLER: "1" # with timeout -s ABRT: a stalled suite dumps its stacks
run: xvfb-run -a -s "-screen 0 1920x1200x24" timeout -s ABRT 900 python -m unittest -v -k Gui

- name: Build with PyInstaller
run: pyinstaller --noconfirm --clean cachet.spec

# Smoke-test WITHOUT a card: we generate the fixtures with the project's
# generators (make_pdf writes a valid raw PDF; make_png a PNG), then we
# exercise the CLI binary's image mode end to end. We do NOT use Pillow's
# Image.save(".pdf"): its PDF writer requires the JPEG encoder, which is
# absent from some Pillow wheels (KeyError: 'JPEG'). eID mode requires
# hardware -> not tested here.
- name: Smoke test (CLI image mode, no eID hardware)
# exercise the CLI binary's visual signatures end to end: the legacy
# single image, a --text signature and a --signatures file. The last two
# prove that fonts/ and Pillow's FreeType renderer (PIL._imagingft) are
# in the CLI binary. We do NOT use Pillow's Image.save(".pdf"): its PDF
# writer requires the JPEG encoder, which is absent from some Pillow
# wheels (KeyError: 'JPEG'). eID mode requires hardware -> not tested
# here.
- name: Smoke test (CLI visual signatures, no eID hardware)
shell: bash
run: |
python - <<'PY'
import json
from test_sign_pdfs_beid import make_pdf, make_png
make_pdf("smoke.pdf", [(595, 842)])
make_pdf("smoke.pdf", [(595, 842), (595, 842)])
make_png("sig.png")
with open("smoke_sigs.json", "w", encoding="utf-8") as fh:
json.dump([
{"kind": "text", "text": "JD {date}", "font": "caveat", "font_size": 14,
"all_pages": True, "x": 500, "y": 20},
{"kind": "image", "image_path": "sig.png", "width_pt": 120,
"page": 1, "x": 60, "y": 700},
], fh)
print("fixtures written")
PY
CLI=$(ls dist/cachet-cli* | head -1)
Expand All @@ -75,6 +113,12 @@ jobs:
--output smoke_out --page 1 --x 100 --y 100
test -n "$(ls smoke_out/*.pdf 2>/dev/null)" || { echo "FAIL: no output PDF"; exit 1; }
echo "OK: image mode works in the frozen binary."
"$CLI" --mode image --text "Jane Doe" --font great-vibes --color "#1A3FB4" \
--input smoke.pdf --output smoke_text --page 1 --x 100 --y 100
test -n "$(ls smoke_text/*.pdf 2>/dev/null)" || { echo "FAIL: no text-mode output"; exit 1; }
"$CLI" --mode image --signatures smoke_sigs.json --input smoke.pdf --output smoke_multi
test -n "$(ls smoke_multi/*.pdf 2>/dev/null)" || { echo "FAIL: no --signatures output"; exit 1; }
echo "OK: bundled fonts + text rendering work in the frozen binary."

- name: Verify both binaries exist
shell: bash
Expand Down
60 changes: 53 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
# A stalled Tk suite or build must not hold the runner (and, for a
# release, its concurrency group) for GitHub's 6-hour default.
timeout-minutes: 45

steps:
- uses: actions/checkout@v4
Expand All @@ -68,28 +71,65 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip

# xvfb/xauth + Tcl/Tk: the virtual display and the Tk runtime of the GUI
# suites below.
- name: Linux system deps
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y poppler-utils xvfb xauth libtk8.6 libtcl8.6

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-build.txt

# Gate the release on the test suite (Linux only: headless, the GUI
# tests skip without a display; on the Windows runner Tk could open
# real windows and hang the job).
- name: Unit tests
# Gate the release on the test suite (Linux only: on the Windows runner
# Tk could open real windows and hang the job). Same three steps as
# build.yml: headless suite (the Gui* classes skip themselves), a
# canary, then the Gui* suites under Xvfb.
- name: Unit tests (headless)
if: runner.os == 'Linux'
run: env -u DISPLAY python -m unittest -v

# The canary imports gui on purpose: the display guard of the GUI tests
# swallows an ImportError / NameError / SyntaxError of gui.py exactly
# like a missing display.
- name: GUI canary (a broken Tk or a broken gui module must fail, not skip)
if: runner.os == 'Linux'
run: python -m unittest -v
env:
XMODIFIERS: "@im=none"
run: xvfb-run -a -s "-screen 0 1920x1200x24" python -c "import tkinter, customtkinter, gui; tkinter.Tk().destroy()"

# CACHET_REQUIRE_GUI=1 turns the guard's skip into a failure, so "GUI
# suites green" can never mean "everything skipped".
- name: GUI suites (Xvfb, skips are failures)
if: runner.os == 'Linux'
timeout-minutes: 20
env:
XMODIFIERS: "@im=none"
CACHET_REQUIRE_GUI: "1"
PYTHONFAULTHANDLER: "1" # with timeout -s ABRT: a stalled suite dumps its stacks
run: xvfb-run -a -s "-screen 0 1920x1200x24" timeout -s ABRT 900 python -m unittest -v -k Gui

- name: Build with PyInstaller
run: pyinstaller --noconfirm --clean cachet.spec

- name: Smoke test (CLI image mode, no eID hardware)
# Legacy single image, then --text and --signatures: the last two prove
# that fonts/ and PIL._imagingft are in the CLI binary.
- name: Smoke test (CLI visual signatures, no eID hardware)
shell: bash
run: |
python - <<'PY'
import json
from test_sign_pdfs_beid import make_pdf, make_png
make_pdf("smoke.pdf", [(595, 842)])
make_pdf("smoke.pdf", [(595, 842), (595, 842)])
make_png("sig.png")
with open("smoke_sigs.json", "w", encoding="utf-8") as fh:
json.dump([
{"kind": "text", "text": "JD {date}", "font": "caveat", "font_size": 14,
"all_pages": True, "x": 500, "y": 20},
{"kind": "image", "image_path": "sig.png", "width_pt": 120,
"page": 1, "x": 60, "y": 700},
], fh)
print("fixtures written")
PY
CLI=$(ls dist/cachet-cli* | head -1)
Expand All @@ -98,6 +138,12 @@ jobs:
--output smoke_out --page 1 --x 100 --y 100
test -n "$(ls smoke_out/*.pdf 2>/dev/null)" || { echo "FAIL: no output PDF"; exit 1; }
echo "OK: image mode works in the frozen binary."
"$CLI" --mode image --text "Jane Doe" --font great-vibes --color "#1A3FB4" \
--input smoke.pdf --output smoke_text --page 1 --x 100 --y 100
test -n "$(ls smoke_text/*.pdf 2>/dev/null)" || { echo "FAIL: no text-mode output"; exit 1; }
"$CLI" --mode image --signatures smoke_sigs.json --input smoke.pdf --output smoke_multi
test -n "$(ls smoke_multi/*.pdf 2>/dev/null)" || { echo "FAIL: no --signatures output"; exit 1; }
echo "OK: bundled fonts + text rendering work in the frozen binary."

- name: Package (Linux)
if: runner.os == 'Linux'
Expand Down
32 changes: 32 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# GitLab CI: runs the test suites only. The standalone executables and the
# releases are built on GitHub Actions (.github/workflows/build.yml and
# release.yml); .forgejo/workflows/tests.yml is the Forgejo twin of this file.
#
# Three steps, as on GitHub: the headless suite, a GUI canary, then the
# guarded Gui* suites under Xvfb (the end-to-end layer of this Tk app).
stages: [test]

variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
PIP_DISABLE_PIP_VERSION_CHECK: "1"
XMODIFIERS: "@im=none" # Tk must not wait on the ibus XIM bridge

tests:
stage: test
timeout: 30m # a stalled Tk suite must not hold the runner
image: python:3.13-bookworm # full image: tkinter + libtk included
cache:
key: pip-py313
paths: [.cache/pip]
before_script:
- apt-get update
- apt-get install -y --no-install-recommends xvfb xauth fonts-dejavu-core
- pip install -r requirements.txt
script:
# 1. headless: the Gui* suites skip themselves
- env -u DISPLAY python -m unittest -v
# 2. canary: a broken Tk OR a broken gui module must FAIL the job instead of silently skipping
- xvfb-run -a -s "-screen 0 1920x1200x24" python -c "import tkinter, customtkinter, gui; tkinter.Tk().destroy()"
# 3. guarded GUI suites (the e2e layer), one run, sequential; a skipped GUI test is a failure
# (timeout -s ABRT + faulthandler: a stalled suite dumps its stacks)
- CACHET_REQUIRE_GUI=1 PYTHONFAULTHANDLER=1 xvfb-run -a -s "-screen 0 1920x1200x24" timeout -s ABRT 900 python -m unittest -v -k Gui
Loading
Loading