From bf5342a6d7d0531a3d2eea1e074c44b893c76b3d Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 17 Apr 2026 18:13:34 +0100 Subject: [PATCH 1/3] add arm32 ci action --- .github/workflows/ci.yaml | 52 +++++++++-- ....Dockerfile => linux-container.Dockerfile} | 0 etc/test-linux-386.sh | 84 ++--------------- etc/test-linux-arm32.sh | 14 +++ etc/test-linux-container.sh | 89 +++++++++++++++++++ 5 files changed, 153 insertions(+), 86 deletions(-) rename etc/{linux-386.Dockerfile => linux-container.Dockerfile} (100%) mode change 100644 => 100755 etc/test-linux-386.sh create mode 100755 etc/test-linux-arm32.sh create mode 100755 etc/test-linux-container.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3c17124..8a82a43 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,34 +48,72 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Restore cached linux/386 docker image + - name: Restore cached test image (linux/386) id: cache-linux-386-image uses: actions/cache@v5 with: path: /tmp/gfloat-linux-386-image.tar - key: ${{ runner.os }}-linux-386-image-${{ hashFiles('etc/linux-386.Dockerfile', 'requirements.txt', 'requirements-dev.txt', 'pyproject.toml') }} + key: ${{ runner.os }}-linux-386-image-${{ hashFiles('etc/linux-container.Dockerfile', 'requirements.txt', 'requirements-dev.txt', 'pyproject.toml') }} - - name: Enable QEMU for 32-bit emulation + - name: Enable QEMU emulation (linux/386) uses: docker/setup-qemu-action@v4 with: platforms: 386 - - name: Load cached linux/386 docker image + - name: Load cached test image (linux/386) run: | if [ -f /tmp/gfloat-linux-386-image.tar ]; then docker load -i /tmp/gfloat-linux-386-image.tar fi - - name: Ensure linux/386 test image is available + - name: Ensure test image is available (linux/386) run: | bash etc/test-linux-386.sh load - - name: Run full unit tests on linux/386 + - name: Run unit tests (linux/386) run: | bash etc/test-linux-386.sh run -vv test - - name: Save linux/386 docker image to cache path + - name: Save test image to cache path (linux/386) if: always() run: | docker image inspect gfloat-linux-386:py310-uv >/dev/null 2>&1 docker save gfloat-linux-386:py310-uv -o /tmp/gfloat-linux-386-image.tar + + pytest-linux-arm32: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Restore cached test image (linux/arm32) + id: cache-linux-arm32-image + uses: actions/cache@v5 + with: + path: /tmp/gfloat-linux-arm32-image.tar + key: ${{ runner.os }}-linux-arm32-image-${{ hashFiles('etc/linux-container.Dockerfile', 'requirements.txt', 'requirements-dev.txt', 'pyproject.toml') }} + + - name: Enable QEMU emulation (linux/arm32) + uses: docker/setup-qemu-action@v4 + with: + platforms: arm + + - name: Load cached test image (linux/arm32) + run: | + if [ -f /tmp/gfloat-linux-arm32-image.tar ]; then + docker load -i /tmp/gfloat-linux-arm32-image.tar + fi + + - name: Ensure test image is available (linux/arm32) + run: | + bash etc/test-linux-arm32.sh load + + - name: Run unit tests (linux/arm32) + run: | + bash etc/test-linux-arm32.sh run -vv test + + - name: Save test image to cache path (linux/arm32) + if: always() + run: | + docker image inspect gfloat-linux-arm32:py310-uv >/dev/null 2>&1 + docker save gfloat-linux-arm32:py310-uv -o /tmp/gfloat-linux-arm32-image.tar diff --git a/etc/linux-386.Dockerfile b/etc/linux-container.Dockerfile similarity index 100% rename from etc/linux-386.Dockerfile rename to etc/linux-container.Dockerfile diff --git a/etc/test-linux-386.sh b/etc/test-linux-386.sh old mode 100644 new mode 100755 index d59e321..dea50c2 --- a/etc/test-linux-386.sh +++ b/etc/test-linux-386.sh @@ -4,85 +4,11 @@ [ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@" -set -euo pipefail - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" - -IMAGE="gfloat-linux-386:py310-uv" -PLATFORM="linux/386" -DOCKERFILE="etc/linux-386.Dockerfile" -MODE="all" - -if [[ "${1:-}" == "all" || "${1:-}" == "load" || "${1:-}" == "build" || "${1:-}" == "run" ]]; then - MODE="$1" - shift -fi - -usage() { - echo "Usage: bash etc/test-linux-386.sh [all|load|build|run] [pytest args...]" - echo " load Build image only if missing" - echo " build Force rebuild image" - echo " run Run tests (image must already exist)" - echo " (no arg) Build image if missing, then run tests" - echo "" - echo "Examples:" - echo " bash etc/test-linux-386.sh run test/test_encode.py::test_encode[binary32]" - echo " bash etc/test-linux-386.sh run -k stochastic -q" -} - -build_image() { - docker buildx build \ - --progress=plain \ - --platform "${PLATFORM}" \ - --load \ - -t "${IMAGE}" \ - -f "${REPO_DIR}/${DOCKERFILE}" \ - "${REPO_DIR}" -} - -run_tests() { - docker run --rm \ - --platform "${PLATFORM}" \ - -v "${REPO_DIR}:/work" \ - -w /work \ - "${IMAGE}" \ - bash -lc ' - set -euo pipefail - export PYTHONPATH="/work/src${PYTHONPATH:+:${PYTHONPATH}}" - python -m pytest "$@" - ' _ "$@" -} - -if [[ "${MODE}" != "all" && "${MODE}" != "load" && "${MODE}" != "build" && "${MODE}" != "run" ]]; then - usage - exit 2 -fi - -echo "Running unit tests in Docker (${PLATFORM})" - -if ! docker info >/dev/null 2>&1; then - echo "Docker daemon is not running; start Docker and rerun this script." >&2 - exit 1 -fi - -if [[ "${MODE}" == "build" ]]; then - echo "Force-building test image ${IMAGE} for ${PLATFORM}" - build_image -fi - -if [[ "${MODE}" == "load" || "${MODE}" == "all" ]]; then - if ! docker image inspect "${IMAGE}" >/dev/null 2>&1; then - echo "Building test image ${IMAGE} for ${PLATFORM} (cached after first build)" - build_image - fi -fi -if [[ "${MODE}" == "run" || "${MODE}" == "all" ]]; then - if ! docker image inspect "${IMAGE}" >/dev/null 2>&1; then - echo "Image ${IMAGE} not found. Run 'bash etc/test-linux-386.sh load' first." >&2 - exit 1 - fi +export GFLOAT_TEST_IMAGE="gfloat-linux-386:py310-uv" +export GFLOAT_TEST_PLATFORM="linux/386" +export GFLOAT_TEST_DOCKERFILE="etc/linux-container.Dockerfile" +export GFLOAT_TEST_SCRIPT_NAME="test-linux-386.sh" - run_tests "$@" -fi +exec bash "${SCRIPT_DIR}/test-linux-container.sh" "$@" diff --git a/etc/test-linux-arm32.sh b/etc/test-linux-arm32.sh new file mode 100755 index 0000000..caadac5 --- /dev/null +++ b/etc/test-linux-arm32.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Copyright (c) 2024 Graphcore Ltd. All rights reserved. + +[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +export GFLOAT_TEST_IMAGE="gfloat-linux-arm32:py310-uv" +export GFLOAT_TEST_PLATFORM="linux/arm/v7" +export GFLOAT_TEST_DOCKERFILE="etc/linux-container.Dockerfile" +export GFLOAT_TEST_SCRIPT_NAME="test-linux-arm32.sh" + +exec bash "${SCRIPT_DIR}/test-linux-container.sh" "$@" diff --git a/etc/test-linux-container.sh b/etc/test-linux-container.sh new file mode 100755 index 0000000..d441277 --- /dev/null +++ b/etc/test-linux-container.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +# Copyright (c) 2024 Graphcore Ltd. All rights reserved. + +[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@" + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +IMAGE="${GFLOAT_TEST_IMAGE:?GFLOAT_TEST_IMAGE is required}" +PLATFORM="${GFLOAT_TEST_PLATFORM:?GFLOAT_TEST_PLATFORM is required}" +DOCKERFILE="${GFLOAT_TEST_DOCKERFILE:?GFLOAT_TEST_DOCKERFILE is required}" +SCRIPT_NAME="${GFLOAT_TEST_SCRIPT_NAME:-$(basename "$0")}" + +MODE="all" +if [[ "${1:-}" == "all" || "${1:-}" == "load" || "${1:-}" == "build" || "${1:-}" == "run" ]]; then + MODE="$1" + shift +fi + +usage() { + echo "Usage: bash etc/${SCRIPT_NAME} [all|load|build|run] [pytest args...]" + echo " load Build image only if missing" + echo " build Force rebuild image" + echo " run Run tests (image must already exist)" + echo " (no arg) Build image if missing, then run tests" + echo "" + echo "Examples:" + echo " bash etc/${SCRIPT_NAME} run test/test_encode.py::test_encode[binary32]" + echo " bash etc/${SCRIPT_NAME} run -k stochastic -q" +} + +build_image() { + docker buildx build \ + --progress=plain \ + --platform "${PLATFORM}" \ + --load \ + -t "${IMAGE}" \ + -f "${REPO_DIR}/${DOCKERFILE}" \ + "${REPO_DIR}" +} + +run_tests() { + docker run --rm \ + --platform "${PLATFORM}" \ + -v "${REPO_DIR}:/work" \ + -w /work \ + "${IMAGE}" \ + bash -lc ' + set -euo pipefail + export PYTHONPATH="/work/src${PYTHONPATH:+:${PYTHONPATH}}" + python -m pytest "$@" + ' _ "$@" +} + +if [[ "${MODE}" != "all" && "${MODE}" != "load" && "${MODE}" != "build" && "${MODE}" != "run" ]]; then + usage + exit 2 +fi + +echo "Running unit tests in Docker (${PLATFORM})" + +if ! docker info >/dev/null 2>&1; then + echo "Docker daemon is not running; start Docker and rerun this script." >&2 + exit 1 +fi + +if [[ "${MODE}" == "build" ]]; then + echo "Force-building test image ${IMAGE} for ${PLATFORM}" + build_image +fi + +if [[ "${MODE}" == "load" || "${MODE}" == "all" ]]; then + if ! docker image inspect "${IMAGE}" >/dev/null 2>&1; then + echo "Building test image ${IMAGE} for ${PLATFORM} (cached after first build)" + build_image + fi +fi + +if [[ "${MODE}" == "run" || "${MODE}" == "all" ]]; then + if ! docker image inspect "${IMAGE}" >/dev/null 2>&1; then + echo "Image ${IMAGE} not found. Run 'bash etc/${SCRIPT_NAME} load' first." >&2 + exit 1 + fi + + run_tests "$@" +fi From 6052197dc43f0e708849f9bf6b16d7a87886f950 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 17 Apr 2026 18:32:16 +0100 Subject: [PATCH 2/3] docker: upgrade pip early --- etc/linux-container.Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/linux-container.Dockerfile b/etc/linux-container.Dockerfile index f6e96bc..f73743f 100644 --- a/etc/linux-container.Dockerfile +++ b/etc/linux-container.Dockerfile @@ -8,6 +8,8 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends build-essential \ && rm -rf /var/lib/apt/lists/* +RUN python -m pip install -U --no-cache-dir pip + RUN NINJAFLAGS='-v' python -m pip install -v --no-cache-dir \ "numpy<2" From 3a51231561eb8ab81ff920e0be09d7cff6af26f3 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 17 Apr 2026 18:32:20 +0100 Subject: [PATCH 3/3] ci: matrix linux container tests --- .github/workflows/ci.yaml | 88 +++++++++++++++------------------------ 1 file changed, 33 insertions(+), 55 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8a82a43..f603f09 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,78 +42,56 @@ jobs: run: | cd docs && make html - pytest-linux-386: + pytest-linux-container: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - arch_name: linux/386 + qemu_platform: 386 + image_name: gfloat-linux-386:py310-uv + image_tar: /tmp/gfloat-linux-386-image.tar + cache_prefix: linux-386-image + test_script: etc/test-linux-386.sh + - arch_name: linux/arm32 + qemu_platform: arm + image_name: gfloat-linux-arm32:py310-uv + image_tar: /tmp/gfloat-linux-arm32-image.tar + cache_prefix: linux-arm32-image + test_script: etc/test-linux-arm32.sh steps: - uses: actions/checkout@v6 - - name: Restore cached test image (linux/386) - id: cache-linux-386-image + - name: Restore cached test image (${{ matrix.arch_name }}) + id: cache-test-image uses: actions/cache@v5 with: - path: /tmp/gfloat-linux-386-image.tar - key: ${{ runner.os }}-linux-386-image-${{ hashFiles('etc/linux-container.Dockerfile', 'requirements.txt', 'requirements-dev.txt', 'pyproject.toml') }} + path: ${{ matrix.image_tar }} + key: ${{ runner.os }}-${{ matrix.cache_prefix }}-${{ hashFiles('etc/linux-container.Dockerfile', 'requirements.txt', 'requirements-dev.txt', 'pyproject.toml') }} - - name: Enable QEMU emulation (linux/386) + - name: Enable QEMU emulation (${{ matrix.arch_name }}) uses: docker/setup-qemu-action@v4 with: - platforms: 386 + platforms: ${{ matrix.qemu_platform }} - - name: Load cached test image (linux/386) + - name: Load cached test image (${{ matrix.arch_name }}) run: | - if [ -f /tmp/gfloat-linux-386-image.tar ]; then - docker load -i /tmp/gfloat-linux-386-image.tar + if [ -f "${{ matrix.image_tar }}" ]; then + docker load -i "${{ matrix.image_tar }}" fi - - name: Ensure test image is available (linux/386) + - name: Ensure test image is available (${{ matrix.arch_name }}) run: | - bash etc/test-linux-386.sh load + bash "${{ matrix.test_script }}" load - - name: Run unit tests (linux/386) + - name: Run unit tests (${{ matrix.arch_name }}) run: | - bash etc/test-linux-386.sh run -vv test + bash "${{ matrix.test_script }}" run -vv test - - name: Save test image to cache path (linux/386) + - name: Save test image to cache path (${{ matrix.arch_name }}) if: always() run: | - docker image inspect gfloat-linux-386:py310-uv >/dev/null 2>&1 - docker save gfloat-linux-386:py310-uv -o /tmp/gfloat-linux-386-image.tar - - pytest-linux-arm32: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - name: Restore cached test image (linux/arm32) - id: cache-linux-arm32-image - uses: actions/cache@v5 - with: - path: /tmp/gfloat-linux-arm32-image.tar - key: ${{ runner.os }}-linux-arm32-image-${{ hashFiles('etc/linux-container.Dockerfile', 'requirements.txt', 'requirements-dev.txt', 'pyproject.toml') }} - - - name: Enable QEMU emulation (linux/arm32) - uses: docker/setup-qemu-action@v4 - with: - platforms: arm - - - name: Load cached test image (linux/arm32) - run: | - if [ -f /tmp/gfloat-linux-arm32-image.tar ]; then - docker load -i /tmp/gfloat-linux-arm32-image.tar - fi - - - name: Ensure test image is available (linux/arm32) - run: | - bash etc/test-linux-arm32.sh load - - - name: Run unit tests (linux/arm32) - run: | - bash etc/test-linux-arm32.sh run -vv test - - - name: Save test image to cache path (linux/arm32) - if: always() - run: | - docker image inspect gfloat-linux-arm32:py310-uv >/dev/null 2>&1 - docker save gfloat-linux-arm32:py310-uv -o /tmp/gfloat-linux-arm32-image.tar + docker image inspect "${{ matrix.image_name }}" >/dev/null 2>&1 + docker save "${{ matrix.image_name }}" -o "${{ matrix.image_tar }}"