diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index f5850d9fd7..41ef0b1f4b 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -44,6 +44,14 @@ jobs: uses: actions/download-artifact@v8 with: name: ${{ inputs.artifact }} + # run_unit_tests.sh installs the plugin tests' numpy with the uv the build stages + # beside them; the Windows arm64 build bundles none, so put one on PATH there. + - name: Install uv + if: runner.os == 'Windows' && runner.arch == 'ARM64' + uses: astral-sh/setup-uv@v10.2.0 + with: + version: "0.11.21" # ORCA_UV_VERSION in CMakeLists.txt + enable-cache: false - uses: lukka/get-cmake@latest with: cmakeVersion: "~4.3.0" # use most recent 4.3.x version diff --git a/scripts/run_unit_tests.sh b/scripts/run_unit_tests.sh index e91342ba15..c4433df04d 100755 --- a/scripts/run_unit_tests.sh +++ b/scripts/run_unit_tests.sh @@ -2,7 +2,8 @@ # This file is made to support the unit tests workflow. # It should only require the directories build/tests, scripts/, and tests/ to function, -# and cmake (with ctest) installed. +# and cmake (with ctest) installed -- plus network access to PyPI whenever numpy has to +# be installed into a freshly built test tree (see below). # (otherwise, update the workflow too, but try to avoid to keep things self-contained) # # Usage: run_unit_tests.sh [TEST_DIR] [BUILD_CONFIG] @@ -19,52 +20,48 @@ TEST_DIR="${1:-build/tests}" BUILD_CONFIG="${2:-}" # The slic3rutils plugin-host tests build numpy arrays through the CPython copied next -# to the test binary (see tests/slic3rutils/CMakeLists.txt); that runtime ships no numpy, -# so those tests SKIP without it. Install it into that interpreter's own site-packages -- -# no PYTHONPATH needed, and re-checked every run because a rebuild of the test target -# wipes and re-copies the runtime. Needs network on the first run only; a failure here is -# not fatal, the tests just keep skipping. +# to the test binary (see tests/slic3rutils/CMakeLists.txt), which ships no numpy. +# Install it with the uv staged beside that runtime -- the tool the app installs plugin +# dependencies with -- straight into the interpreter's own site-packages: no pip needed +# in the runtime, no PYTHONPATH. Re-checked every run because a rebuild of the test +# target re-copies the runtime; needs network whenever it installs. Pinned so a numpy +# release cannot change results on its own. +NUMPY_VERSION="2.5.3" + +# Without numpy those tests assert the numpy-absent error path instead, so a local run +# only warns. Under CI it fails the run, which would otherwise stay green while silently +# dropping the array coverage. (The Flatpak leg runs this inside `flatpak build`, whose +# minimal environment has no CI, and its offline build stages no uv, so numpy stays +# best-effort there.) +numpy_unavailable() { + if [ -n "${CI:-}" ]; then + echo "error: $1" >&2 + exit 1 + fi + echo "warning: $1; the numpy-backed binding tests will cover only the numpy-absent path." +} + +has_pinned_numpy() { + "${python_exe}" -c "import sys, numpy; sys.exit(numpy.__version__ != '${NUMPY_VERSION}')" >/dev/null 2>&1 +} + find_args=("${TEST_DIR}" \( -path '*/python/bin/python3' -o -path '*/python/python.exe' \)) # Multi-config trees hold one copy per configuration; only bootstrap the one being run. [ -n "${BUILD_CONFIG}" ] && find_args+=(-path "*/${BUILD_CONFIG}/*") python_exe="$(find "${find_args[@]}" -print -quit 2>/dev/null)" -# Unix stages the runtime with `make install`, which runs ensurepip; the Windows layout -# (deps/python3/stage_windows.cmake) ships no pip, so drive the install from whatever pip -# the host has, resolving wheels for the embedded interpreter's tags and not the host's. -install_numpy_from_host_pip() { - local py_ver py_abi py_plat py_site host_py - # tr strips the CR that a Windows interpreter's print() puts on every line. - { read -r py_ver; read -r py_abi; read -r py_plat; read -r py_site; } < <( - "${python_exe}" -c 'import sys, sysconfig -v = sys.version_info -print("%d.%d" % v[:2]) -print("cp%d%d" % v[:2]) -print(sysconfig.get_platform().replace("-", "_").replace(".", "_")) -print(sysconfig.get_paths()["purelib"])' 2>/dev/null | tr -d '\r') - [ -n "${py_site}" ] || return 1 - for host_py in python3 python py; do - # Skips a Windows Store stub, which resolves but has no pip behind it. - "${host_py}" -m pip --version >/dev/null 2>&1 || continue - "${host_py}" -m pip install --quiet --disable-pip-version-check --retries 1 \ - --target "${py_site}" --only-binary=:all: --implementation cp \ - --python-version "${py_ver}" --abi "${py_abi}" --platform "${py_plat}" \ - "numpy<3" - return $? - done - return 1 -} - if [ -z "${python_exe}" ]; then - echo "No bundled Python under ${TEST_DIR}; numpy-backed binding tests will skip." -elif ! "${python_exe}" -c "import numpy" >/dev/null 2>&1; then - echo "Installing numpy into the embedded test interpreter (${python_exe})..." - if "${python_exe}" -m pip --version >/dev/null 2>&1; then - "${python_exe}" -m pip install --quiet --disable-pip-version-check --retries 1 "numpy<3" \ - || echo "numpy install failed; numpy-backed binding tests will skip." - else - install_numpy_from_host_pip \ - || echo "numpy install failed; numpy-backed binding tests will skip." + numpy_unavailable "no bundled Python under ${TEST_DIR}" +elif ! has_pinned_numpy; then + uv_exe="${python_exe%/python/*}/tools/uv/uv" + # Builds that bundle no uv (Windows arm64) fall back to one on PATH. + [ -x "${uv_exe}" ] || uv_exe="$(command -v uv)" + echo "Installing numpy ${NUMPY_VERSION} into the embedded test interpreter (${python_exe})..." + if [ -z "${uv_exe}" ]; then + numpy_unavailable "no uv staged beside the tests or on PATH" + elif ! "${uv_exe}" pip install --python "${python_exe}" --only-binary :all: "numpy==${NUMPY_VERSION}" \ + || ! has_pinned_numpy; then + numpy_unavailable "could not install numpy ${NUMPY_VERSION} into ${python_exe}" fi fi diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index 39eb56e4e4..6c9e695049 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -83,4 +83,16 @@ elseif (FLATPAK) ) endif() +# scripts/run_unit_tests.sh installs the tests' numpy into that runtime with this uv, +# staged where the app build tree keeps it (/tools/uv, see src/CMakeLists.txt). +if (ORCA_BUNDLED_UV_EXECUTABLE) + add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "$/tools/uv" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ORCA_BUNDLED_UV_EXECUTABLE}" + "$/tools/uv/${ORCA_BUNDLED_UV_FILENAME}" + COMMENT "Copying uv for the plugin host API tests" + VERBATIM + ) +endif() + orcaslicer_discover_tests(${_TEST_NAME}_tests)