mirror of
https://github.com/Klipper4FlashForge/firmware.git
synced 2026-10-23 13:52:16 -05:00
E -- values and counts. The START_PRINT prologue has no M400 and an idle timeout of 864000, not 1800000. The nozzle-clean temperature comes from the material table FIRST, so NOZZLE=240 with no per-tool temp cleans at 220 -- documented backwards before. TOOLS is the paired 0:220,2:240 form. The wipe Z is the raw eddy frame, not the homed one. The runout M117 strings are "T<n> clog" / "T<n> out of filament", not E-codes. Grab-verify raises E0051+tool, not E0143. Docks are at X~297; 250/280 are staging waypoints. The grab sequence is documented in the order _grab actually emits it. ff_print exposes seven status fields, not nine, and never emits TOOLS=. Package sizes reconciled against the real artifacts (62MB built, ~93MB stock, ~370MB of CI assets, ~60s not 90s). testing.md's sample output block matched no header the code prints, so it is replaced with the real ones; its pytest row listed two of six suites. F -- stale claims. _FF_PREFLIGHT is not "missing". SDCARD_SET_CHANNEL is not still issued (764af2a). The app and Moonraker do not coexist; the app is gone. TOOLCHANGE_STATUS has nothing to compare against since the JSON reader was removed. The JSON is re-imported every boot until a nozzle is saved, not once. The 23-check render harness was deleted in91604c2. PURGE now wipes by default. 50-print-lifecycle.md gained the section on how the sequences are actually entered now -- [ff_print], not the slicer. The dead "profiles" concept is gone from three files, one of which printed it on every replica run. G -- overclaims. The README no longer says a stock reflash puts the printer back "exactly as it was": it says what is restored and what survives inert, which is what test-recovery actually asserts. The random ssh password is conditional on ROOT_PW_HASH being unset. verify.sh no longer warns "ssh will not start" on every build for a dropbear we deliberately do not ship -- the check now reads the way round it means. case-install.sh's read-only block says it asserts nothing. An uncalibrated tool contributes z_adjust, not zero, in three operator-facing places. offset_base does not cancel out in X/Y. The safe-moves file's Z50 promise is scoped to its own lines, with the implicit prepare's G1 Z10 spelled out. 48 passed, 5 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
224 lines
10 KiB
YAML
224 lines
10 KiB
YAML
name: ci
|
|
|
|
# The stock FlashForge firmware is proprietary and ~93MB, so it is never in
|
|
# the repo. The `test` job runs against a synthetic fixture that reproduces
|
|
# the package STRUCTURE (see test/integration/make-stock-fixture.sh) -- enough
|
|
# for the static and packaging checks, but NOT enough to prove a package will
|
|
# not brick a printer.
|
|
#
|
|
# That proof needs the printer's real rootfs, and so it needs the stock
|
|
# package. `printer-sim` below now always runs, because the packages it needs
|
|
# are publicly mirrored (see env: below) rather than hidden behind a secret
|
|
# nobody had set. It used to be gated on STOCK_URL_CREATOR5PRO, which meant
|
|
# that for its entire existence it never ran once: a grey dash beside two
|
|
# green ticks, reading as "CI passed" while the only gates that decide
|
|
# whether a package bricks a printer were never executed.
|
|
on:
|
|
push:
|
|
branches: [ main, master ]
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
# The stock packages and the factory image, from ghzserg/FF -- a public
|
|
# archive of FlashForge firmware that this repo already points at in
|
|
# docs/printer-replica.md. Nothing proprietary is redistributed from here; the
|
|
# workflow fetches what is already published elsewhere.
|
|
#
|
|
# Every one is pinned by sha256, and the checksum is enforced, not advisory.
|
|
# An unpinned download would let the archive change under us and the suite
|
|
# would keep reporting green while testing a different firmware than the one
|
|
# these gates were written against -- the same class of silent drift as a gate
|
|
# that skips without saying so. Verified byte-for-byte against the maintainer's
|
|
# local copies on 2026-08-24.
|
|
env:
|
|
STOCK_URL_CREATOR5PRO: https://github.com/ghzserg/FF/releases/download/R/Creator5Pro-1.9.7-1.2.9-20260810.tgz
|
|
STOCK_SHA_CREATOR5PRO: 1f3252feaae6d8fc72d7b0fd1ba832cc63d369659f8b2d2896efcf7adab2976b
|
|
STOCK_URL_CREATOR5: https://github.com/ghzserg/FF/releases/download/R/Creator5-1.9.7-1.2.9-20260810.tgz
|
|
STOCK_SHA_CREATOR5: 72009be183e74a4562050a927787ba686c6b558a075f00958892197ac52659cc
|
|
FACTORY_URL: https://github.com/ghzserg/FF/releases/download/R/Creator5Pro-factory.tar.xz
|
|
FACTORY_SHA: 45657924298a41b5cd48c0f4412b4b2fb67df624af666b494d6bcea969cc5b7b
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Install build dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends \
|
|
openssl xz-utils unzip binutils file squashfs-tools python3-pytest python3-jinja2
|
|
|
|
# Syntax and the brick lint are the first thing run-tests.py does, so
|
|
# they are not repeated here.
|
|
- name: shellcheck
|
|
run: |
|
|
sudo apt-get install -y shellcheck
|
|
# Errors are a gate. These used to end in `|| true`, which meant
|
|
# shellcheck could report anything it liked and the job stayed green.
|
|
# payload scripts run under busybox ash on the printer.
|
|
shellcheck -s sh -S error payload/*.sh payload/init.d/S* payload/firmwareExe
|
|
shellcheck -s bash -S error bin/*.sh test/integration/*.sh
|
|
# Warnings are advisory: printed, not enforced.
|
|
shellcheck -s sh -S warning payload/*.sh payload/init.d/S* payload/firmwareExe || true
|
|
|
|
# This job has no stock package on purpose, so exactly two gates cannot
|
|
# run: the tests that read the real rootfs, and the replica itself. The
|
|
# suite refuses to call a gate it did not run a pass, so they have to be
|
|
# accepted explicitly -- BY NAME. `ALLOW_SKIP=1` would accept them and
|
|
# also every future skip, which would make this job green while it
|
|
# quietly stopped testing things. Naming them means a third skip fails
|
|
# here, and both are still printed and annotated on every run.
|
|
#
|
|
# The job that must not skip at all is printer-sim below, and
|
|
# release.yml; both set REQUIRE_PRINTER_SIM=1 and no ALLOW_SKIP.
|
|
- name: Full test suite (fixture-based)
|
|
run: ALLOW_SKIP="pytest,the printer replica" ./test/run-tests.py
|
|
|
|
- name: Upload built fixture packages
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: fixture-packages
|
|
path: work/out/*.tgz
|
|
if-no-files-found: ignore
|
|
retention-days: 7
|
|
|
|
# The real check: install the package into a replica of the printer -- the
|
|
# actual rootfs.squashfs under qemu-mipsel, on the printer's own busybox --
|
|
# and assert the machine still boots. No `if:` guard: this job is the point
|
|
# of the repo, and a version of it that can quietly not run is worse than
|
|
# not having it.
|
|
printer-sim:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# ~370MB of somebody else's release assets per run (two ~93MB stock
|
|
# packages and the 182MB factory image), and they never
|
|
# change -- the cache key is their checksums, so a new pin misses and
|
|
# re-fetches, and an unchanged pin costs the archive nothing.
|
|
- name: Cache the stock firmware
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: stock
|
|
key: stock-${{ env.STOCK_SHA_CREATOR5PRO }}-${{ env.STOCK_SHA_CREATOR5 }}-${{ env.FACTORY_SHA }}
|
|
|
|
- name: Fetch the stock firmware
|
|
env:
|
|
# Secrets override the public pins, for a fork testing its own
|
|
# firmware. Override the URL and you must override the checksum too,
|
|
# or the fetch refuses to run -- an unverified download is exactly
|
|
# what this is meant to prevent.
|
|
SECRET_PRO: ${{ secrets.STOCK_URL_CREATOR5PRO }}
|
|
SECRET_PRO_SHA: ${{ secrets.STOCK_SHA256_CREATOR5PRO }}
|
|
SECRET_STD: ${{ secrets.STOCK_URL_CREATOR5 }}
|
|
SECRET_STD_SHA: ${{ secrets.STOCK_SHA256_CREATOR5 }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p stock
|
|
|
|
get() { # name url sha dest
|
|
if [ -z "$2" ]; then
|
|
echo "::error::no URL for $1"; exit 1
|
|
fi
|
|
if [ -z "$3" ]; then
|
|
echo "::error title=Unverified firmware download::$1 has a URL but no sha256. Set the matching STOCK_SHA256_* secret; an unpinned firmware would let this suite report green against bytes nobody checked."
|
|
exit 1
|
|
fi
|
|
if [ -f "$4" ] && echo "$3 $4" | sha256sum -c - >/dev/null 2>&1; then
|
|
echo " $1: cache hit, checksum ok"
|
|
return
|
|
fi
|
|
echo " $1: fetching $2"
|
|
curl -fsSL "$2" -o "$4"
|
|
echo "$3 $4" | sha256sum -c -
|
|
}
|
|
|
|
# Written out rather than nested in one expansion: a URL and its
|
|
# checksum must come from the SAME source, and a clever ${x:+y}${x:-z}
|
|
# pair silently pastes the URL in where the checksum belongs.
|
|
PRO_URL="${SECRET_PRO:-$STOCK_URL_CREATOR5PRO}"
|
|
if [ -n "${SECRET_PRO:-}" ]; then PRO_SHA="${SECRET_PRO_SHA:-}"
|
|
else PRO_SHA="$STOCK_SHA_CREATOR5PRO"; fi
|
|
|
|
STD_URL="${SECRET_STD:-$STOCK_URL_CREATOR5}"
|
|
if [ -n "${SECRET_STD:-}" ]; then STD_SHA="${SECRET_STD_SHA:-}"
|
|
else STD_SHA="$STOCK_SHA_CREATOR5"; fi
|
|
|
|
get Creator5Pro "$PRO_URL" "$PRO_SHA" stock/Creator5Pro-stock.tgz
|
|
get Creator5 "$STD_URL" "$STD_SHA" stock/Creator5-stock.tgz
|
|
|
|
- name: Write test.env (the replica's /usr/prog)
|
|
env:
|
|
SECRET_FACTORY: ${{ secrets.FACTORY_URL }}
|
|
SECRET_FACTORY_SHA: ${{ secrets.FACTORY_SHA256 }}
|
|
# Optional repository variable: a prebuilt replica image, e.g.
|
|
# monstrofil/creator5-printer:latest. It carries the factory
|
|
# partitions AND the stock package already installed, which is about
|
|
# a minute of setup saved on every replica case.
|
|
PRINTER_IMAGE: ${{ vars.PRINTER_IMAGE }}
|
|
run: |
|
|
set -euo pipefail
|
|
: > test.env
|
|
|
|
URL="${SECRET_FACTORY:-$FACTORY_URL}"
|
|
if [ -n "${SECRET_FACTORY:-}" ]; then SHA="${SECRET_FACTORY_SHA:-}"
|
|
else SHA="$FACTORY_SHA"; fi
|
|
|
|
if [ -z "$SHA" ]; then
|
|
echo "::error title=Unverified factory image::FACTORY_URL is set without FACTORY_SHA256."
|
|
exit 1
|
|
fi
|
|
if [ -f stock/factory.tar.xz ] && \
|
|
echo "$SHA stock/factory.tar.xz" | sha256sum -c - >/dev/null 2>&1; then
|
|
echo " factory image: cache hit, checksum ok"
|
|
else
|
|
curl -fsSL "$URL" -o stock/factory.tar.xz
|
|
echo "$SHA stock/factory.tar.xz" | sha256sum -c -
|
|
fi
|
|
echo "PROG_DUMP=\"$PWD/stock/factory.tar.xz\"" >> test.env
|
|
|
|
[ -z "${PRINTER_IMAGE:-}" ] || echo "PRINTER_IMAGE=\"$PRINTER_IMAGE\"" >> test.env
|
|
cat test.env
|
|
|
|
- name: Write config.env
|
|
run: |
|
|
cat > config.env <<CFG
|
|
MOD_NAME=anvil
|
|
MOD_VER=ci
|
|
SW_VER=""
|
|
TARGET_MACHINE=Creator5Pro
|
|
STOCK_TGZ_CREATOR5PRO="$PWD/stock/Creator5Pro-stock.tgz"
|
|
STOCK_TGZ_CREATOR5="$PWD/stock/Creator5-stock.tgz"
|
|
STOCK_TGZ=""
|
|
KLIPPER_FORK=""
|
|
ROOT_PW_HASH='\$6\$ci\$abcdefghijklmnopqrstuvwxyz'
|
|
FF_KEY='FFP0331&*%root'
|
|
CFG
|
|
|
|
# The replica gates run ./bin/patch.sh, which needs the real Mainsail
|
|
# zip and HelixScreen tarball in vendor/ -- patch.sh does not fetch them
|
|
# itself, bin/build.sh does, and the gates do not go through build.sh.
|
|
# Nothing here noticed until this job ran for the first time, because
|
|
# every local run already had a populated vendor/. Both are public,
|
|
# sha256-pinned in versions.env, and re-download only on a version bump.
|
|
- name: Fetch the pinned Mainsail and HelixScreen assets
|
|
run: make vendor ASSET_ROOT=$PWD
|
|
|
|
- name: Extract the printer rootfs
|
|
run: make rootfs ASSET_ROOT=$PWD
|
|
|
|
# The replica starts sibling containers through the docker socket and
|
|
# needs loop devices for the simulated USB stick; both work on a stock
|
|
# ubuntu-latest runner.
|
|
- name: Brick-safety suite on the printer replica
|
|
run: |
|
|
docker build -q -t creator5-fw-build -f docker/Dockerfile.build docker
|
|
docker run --rm \
|
|
-v "$PWD":"$PWD" -w "$PWD" \
|
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
-e REQUIRE_PRINTER_SIM=1 \
|
|
creator5-fw-build ./test/run-tests.py
|