Skip to content

Commit

Permalink
Initial run of a py.test collector for the existing klippy .test fo…
Browse files Browse the repository at this point in the history
…rmat, rework CI to do more validation (#503)

* Replace scripts/test_klippy.py with py.test

* Revise tests

Gcode unknown commands and command errors are now test exceptions
`ASSERT TEST="{printer.whatever} is False"` in tests allows template
 evaluated runtime assertions

fix a couple broken tests that did not error because of the above

* Use tool.uv.dev-dependencies

* Rework PR CI builds

If there are changes that may affect the firmware, rebuild the docker image
  (Pull the existing image first to re-use the layer cache)

Klippy changes, test across all supported python versions

* Revisions to github actions

- Build and push the docker image on pushes to main
- Ensure scripts/*requirements*.txt are up to date
- Separate python versions into individual steps for better clarity

* Update klippy-requirements and requirements_dev

---------

Co-authored-by: Rogerio Goncalves <[email protected]>
  • Loading branch information
kageurufu and rogerlz authored Feb 1, 2025
1 parent 949eaf0 commit fe89074
Show file tree
Hide file tree
Showing 20 changed files with 661 additions and 232 deletions.
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
ci_build/
ci_cache/

.venv/
dict/
scripts/Dockerfile*
out/
.pytest_cache/
.ruff_cache/
51 changes: 40 additions & 11 deletions .github/workflows/ci-build_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,48 @@ jobs:
id: changes
with:
filters: |
klippy:
- 'klippy/**'
- 'test/**'
- 'scripts/**'
dependencies:
- 'pyproject.toml'
- 'scripts/klippy-requirements.txt'
- 'scripts/requirements_dev.txt'
klipper:
- 'pyproject.toml'
- 'Makefile'
- 'src/**'
- 'lib/**'
- 'test/**'
- 'test/configs/**'
- 'scripts/Dockerfile-build'
- 'scripts/buildcommands.py'
- 'scripts/check-gcc.sh'
- name: Test Klippy Only
if: steps.changes.outputs.klippy == 'true' && steps.changes.outputs.klipper == 'false'
run: docker run -v $PWD:/klipper dangerklippers/klipper-build:latest "./scripts/ci-build.sh" 2>&1
# Always pull the latest image to take advantage of layer caching
- name: Pull docker image
run: docker pull dangerklippers/klipper-build:latest

- name: Test Klipper Full
if: steps.changes.outputs.klippy == 'true' && steps.changes.outputs.klipper == 'true'
run: docker run -v $PWD:/klipper dangerklippers/klipper-build:latest "./scripts/ci-build.sh" compile 2>&1
- name: Test Klipper build (changes to the firmware)
if: steps.changes.outputs.klipper == 'true'
run: docker build -f scripts/Dockerfile-build -t dangerklippers/klipper-build:latest .

- name: Ensure scripts/klippy-dependencies.txt is up to date
if: steps.changes.outputs.dependencies == 'true'
uses: nickcharlton/diff-check@main
with:
command: |
docker run -v $PWD:/klipper dangerklippers/klipper-build:latest uv export --frozen -o scripts/klippy-requirements.txt --no-dev --no-hashes
docker run -v $PWD:/klipper dangerklippers/klipper-build:latest uv export --frozen -o scripts/requirements_dev.txt --only-dev --no-hashes
# This *could* be done with a matrix, but then we would have to figure out sharing the docker image between builds
- name: Test Klippy (Python 3.9)
run: docker run -v $PWD:/klipper dangerklippers/klipper-build:latest --python 3.9 py.test -n auto

- name: Test Klippy (Python 3.10)
run: docker run -v $PWD:/klipper dangerklippers/klipper-build:latest --python 3.10 py.test -n auto

- name: Test Klippy (Python 3.11)
run: docker run -v $PWD:/klipper dangerklippers/klipper-build:latest --python 3.11 py.test -n auto

- name: Test Klippy (Python 3.12)
run: docker run -v $PWD:/klipper dangerklippers/klipper-build:latest --python 3.12 py.test -n auto

- name: Test Klippy (Python 3.13)
run: docker run -v $PWD:/klipper dangerklippers/klipper-build:latest --python 3.13 py.test -n auto
17 changes: 15 additions & 2 deletions .github/workflows/ci-builder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,21 @@ name: Klipper Container Build and Push

on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 0" # at 00.00 on Sunday
push:
branches:
- main
# This should mirror the klipper paths-filter in ci-build_test.yaml
paths:
- "Makefile"
- "pyproject.toml"
- "src/**"
- "lib/**"
- "test/configs/**"
- "scripts/Dockerfile-build"
- "scripts/buildcommands.py"
- "scripts/check-gcc.sh"
- "scripts/ci-build.sh"
- ".github/workflows/ci-builder.yaml"

jobs:
login:
Expand Down
1 change: 1 addition & 0 deletions klippy/gcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ def cmd_default(self, gcmd):
# Don't warn about requests to turn off fan when fan not present
return
gcmd.respond_info('Unknown command:"%s"' % (cmd,))
self.printer.send_event("gcode:unknown_command", cmd)

def _cmd_mux(self, command, gcmd):
key, values = self.mux_commands[command]
Expand Down
2 changes: 2 additions & 0 deletions klippy/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ def _read_config(self):
"telemetry",
]:
self.load_object(config, section_config, None)
if self.get_start_args().get("debuginput") is not None:
self.load_object(config, "testing", None)
for m in [toolhead]:
m.add_printer_objects(config)
# Validate that there are no undefined parameters in the config file
Expand Down
22 changes: 16 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ dependencies = [
"python-can==3.3.4",
]

[tool.uv]
dev-dependencies = [
"ruff>=0.9.3",
"pre-commit>=4.0.1",
"pytest-xdist>=3.6.1",
"pytest>=8.3.4",
]

[project.urls]
homepage = "https://kalico.gg/"
source = "https://github.com/KalicoCrew/kalico"
Expand All @@ -29,7 +37,11 @@ indent-width = 4
exclude = [".github", ".history", "config", "docs", "lib", "src"]

[tool.ruff.lint]
extend-select = ["B006"]
extend-select = [
"B006", # Checks for uses of mutable objects as function argument defaults
"FA100", # Detect when type annotations could be made better with PEP563
"FA102", # Detect type annotations that would require `from __future__ import annotations`
]
ignore = [
"E401", # Multiple imports on one line
"C901", # Function is too complex
Expand All @@ -41,8 +53,6 @@ ignore = [
"E721", # Do not compare types, use 'isinstance()'
]

[dependency-groups]
dev = [
"ruff>=0.9.3",
"pre-commit>=4.0.1",
]
[tool.pytest.ini_options]
pythonpath = ["."]
testpaths = ["test"]
29 changes: 24 additions & 5 deletions scripts/Dockerfile-build
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,31 @@ ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=UTC

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt update && apt install -y sudo git curl
RUN mkdir /ci_build

# Install dependencies
RUN apt update \
&& apt install -y sudo git curl \
python3 python3-dev python3-venv libffi-dev build-essential \
gcc-avr avr-libc \
libnewlib-arm-none-eabi gcc-arm-none-eabi binutils-arm-none-eabi \
pv libmpfr-dev libgmp-dev libmpc-dev texinfo bison flex \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Install or1k-linux-musl toolchain
RUN --mount=type=cache,target=/ci_cache \
curl https://more.musl.cc/10/x86_64-linux-musl/or1k-linux-musl-cross.tgz -o /ci_cache/or1k-linux-musl-cross-10.tgz && \
tar -x -C /ci_build -f /ci_cache/or1k-linux-musl-cross-10.tgz

# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

COPY . /klipper
WORKDIR /klipper

RUN ./scripts/ci-install.sh
RUN ./scripts/ci-build.sh
ENV PATH="/ci_build/python-env/bin:$PATH"
RUN pip install -r ./scripts/requirements_dev.txt
ENV DICTDIR=/ci_build/dict
RUN uv run ./scripts/ci-build.sh

ENTRYPOINT [ "/bin/uv", "run" ]
CMD [ "py.test" ]
15 changes: 6 additions & 9 deletions scripts/ci-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ compile()
finish_test mcu_compile "$TARGET"
cp out/klipper.dict ${1}/$(basename ${TARGET} .config).dict
done
make clean
make distclean
}

DICTDIR=${BUILD_DIR}/dict
export DICTDIR=${DICTDIR:-${BUILD_DIR}/dict}

if [ ! -d "${DICTDIR}" ]; then
mkdir -p ${DICTDIR}
Expand All @@ -62,11 +64,6 @@ fi
# Verify klippy host software
######################################################################

start_test klippy "Test klippy import (Python3)"
# I'm leaving this with klippy/klippy.py so we test that compatibility
$PYTHON klippy/klippy.py --import-test
finish_test klippy "Test klippy import (Python3)"

start_test klippy "Test invoke klippy (Python3)"
$PYTHON scripts/test_klippy.py -d ${DICTDIR} test/klippy/*.test
finish_test klippy "Test invoke klippy (Python3)"
start_test klippy "py.test suite"
py.test
finish_test klippy "py.test suite"
50 changes: 0 additions & 50 deletions scripts/ci-install.sh

This file was deleted.

10 changes: 5 additions & 5 deletions scripts/klippy-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command:
# uv export -o scripts/klippy-requirements.txt --no-dev --no-hashes
# uv export --frozen -o scripts/klippy-requirements.txt --no-dev --no-hashes
aenum==3.1.15
cffi==1.15.1 ; python_full_version < '3.13'
cffi==1.17.1 ; python_full_version >= '3.13'
Expand All @@ -9,9 +9,9 @@ greenlet==3.1.0 ; python_full_version >= '3.13'
jinja2==3.1.5
markupsafe==2.1.5
numpy==2.0.2 ; python_full_version < '3.10'
numpy==2.2.0 ; python_full_version >= '3.10'
pycparser==2.21
numpy==2.2.2 ; python_full_version >= '3.10'
pycparser==2.22
pyserial==3.4
python-can==3.3.4
windows-curses==2.4.0 ; sys_platform == 'win32'
wrapt==1.16.0
windows-curses==2.4.1 ; sys_platform == 'win32'
wrapt==1.17.2
21 changes: 15 additions & 6 deletions scripts/requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
# This file was autogenerated by uv via the following command:
# uv export -o scripts/requirements_dev.txt --only-dev --no-hashes
# uv export --frozen -o scripts/requirements_dev.txt --only-dev --no-hashes
cfgv==3.4.0
colorama==0.4.6 ; sys_platform == 'win32'
distlib==0.3.9
filelock==3.16.1
identify==2.6.5
exceptiongroup==1.2.2 ; python_full_version < '3.11'
execnet==2.1.1
filelock==3.17.0
identify==2.6.6
iniconfig==2.0.0
nodeenv==1.9.1
packaging==24.2
platformdirs==4.3.6
pre-commit==4.0.1
pluggy==1.5.0
pre-commit==4.1.0
pytest==8.3.4
pytest-xdist==3.6.1
pyyaml==6.0.2
ruff==0.8.4
virtualenv==20.28.1
ruff==0.9.3
tomli==2.2.1 ; python_full_version < '3.11'
virtualenv==20.29.1
34 changes: 34 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

import klippy.chelper
import pathlib
import os

# Ensure chelper is built
klippy.chelper.get_ffi()


ROOT = pathlib.Path(__file__).parent.parent
KLIPPY_PLUGINS = ROOT / "klippy" / "plugins"
TESTING_PLUGIN = ROOT / "test" / "klippy_testing_plugin.py"


def pytest_addoption(parser):
parser.addoption(
"--dictdir",
action="store",
default=os.environ.get("DICTDIR", "dict"),
help="Klipper build dictionary path",
)


def pytest_sessionstart(session):
link_path = KLIPPY_PLUGINS / "testing.py"
if link_path.exists():
return

os.symlink(TESTING_PLUGIN, link_path)

@session.config.add_cleanup
def clean_symlink():
os.unlink(link_path)
2 changes: 0 additions & 2 deletions test/klippy/bed_screws.test
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ ACCEPT
ACCEPT
ACCEPT

ACCEPT
ACCEPT
ACCEPT

# Start helper script and run with two readjusts
Expand Down
9 changes: 3 additions & 6 deletions test/klippy/commands.test
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ RESTORE_GCODE_STATE MOVE=1

# Update commands
SET_GCODE_OFFSET Z=.1
M206 Z-.2
SET_GCODE_OFFSET Z_ADJUST=-.1

SET_VELOCITY_LIMIT ACCEL=100 VELOCITY=20 SQUARE_CORNER_VELOCITY=1 ACCEL_TO_DECEL=200
Expand All @@ -45,8 +44,6 @@ SET_PRESSURE_ADVANCE EXTRUDER=extruder ADVANCE=.001
SET_PRESSURE_ADVANCE ADVANCE=.002 ADVANCE_LOOKAHEAD_TIME=.001

# test telemetry
TELEMETRY_ENABLE
TELEMETRY_EXAMPLE

# Restart command (must be last in test)
RESTART
ASSERT TEST='{"telemetry" not in printer.configfile.save_config_pending_items}'
ENABLE_TELEMETRY
ASSERT TEST='{printer.configfile.save_config_pending_items.telemetry.enabled}'
Loading

0 comments on commit fe89074

Please sign in to comment.