From 3994296d3024d14b824715a761f5ebcd0af10391 Mon Sep 17 00:00:00 2001 From: Jakub Kocka Date: Mon, 26 Jan 2026 10:34:25 +0100 Subject: [PATCH 1/4] feat: Added test workflow to install builded wheels before the upload --- .github/workflows/build-wheels-defined.yml | 77 ++++- .github/workflows/build-wheels-platforms.yml | 10 +- .github/workflows/test-wheels-install.yml | 288 +++++++++++++++++++ 3 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/test-wheels-install.yml diff --git a/.github/workflows/build-wheels-defined.yml b/.github/workflows/build-wheels-defined.yml index 0c43309..b94ba7a 100644 --- a/.github/workflows/build-wheels-defined.yml +++ b/.github/workflows/build-wheels-defined.yml @@ -40,6 +40,11 @@ on: type: boolean required: false default: true + os_linux_armv7_legacy: + description: Build on linux armv7 legacy (bullseye, glibc 2.31) + type: boolean + required: false + default: false env: GH_TOKEN : ${{ secrets.GITHUB_TOKEN }} @@ -86,7 +91,7 @@ jobs: - name: Upload artifacts of downloaded_wheels directory uses: actions/upload-artifact@v4 with: - name: wheels-download-directory-ubuntu-${{ matrix.python-version }} + name: wheels-download-directory-linux-x86_64-${{ matrix.python-version }} path: ./downloaded_wheels @@ -123,7 +128,7 @@ jobs: - name: Upload artifacts of downloaded_wheels directory uses: actions/upload-artifact@v4 with: - name: wheels-download-directory-windows-${{ matrix.python-version }} + name: wheels-download-directory-windows-x86_64-${{ matrix.python-version }} path: ./downloaded_wheels @@ -165,7 +170,7 @@ jobs: - name: Upload artifacts of downloaded_wheels directory uses: actions/upload-artifact@v4 with: - name: wheels-download-directory-macos-x86-${{ matrix.python-version }} + name: wheels-download-directory-macos-x86_64-${{ matrix.python-version }} path: ./downloaded_wheels @@ -262,7 +267,7 @@ jobs: - name: Upload artifacts of downloaded_wheels directory uses: actions/upload-artifact@v4 with: - name: wheels-download-directory-linux-arm7-${{ matrix.python-version }} + name: wheels-download-directory-linux-armv7-${{ matrix.python-version }} path: ./downloaded_wheels @@ -301,17 +306,75 @@ jobs: name: wheels-download-directory-linux-arm64-${{ matrix.python-version }} path: ./downloaded_wheels + + linux-armv7-legacy: + needs: get-supported-versions + name: linux aarch32 (armv7 legacy) + if: ${{ inputs.os_linux_armv7_legacy }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJson(needs.get-supported-versions.outputs.supported_python) }} + exclude: + # Python 3.14 doesn't have bullseye images for ARM + - python-version: '3.14' + steps: + - name: Set up QEMU for ARMv7 + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/arm/v7 + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build wheels - ARMv7 Legacy (in Docker) + # Build on Bullseye (glibc 2.31) for compatibility with older systems + run: | + docker run --rm --platform linux/arm/v7 \ + -v $(pwd):/work \ + -w /work \ + -e GH_TOKEN="${GH_TOKEN}" \ + -e PIP_NO_CACHE_DIR=1 \ + python:${{ matrix.python-version }}-bullseye \ + bash -c " + set -e + python --version + # Install pip packages without cache to reduce memory usage + python -m pip install --no-cache-dir --upgrade pip + python -m pip install --no-cache-dir -r build_requirements.txt + bash os_dependencies/linux_arm.sh + # Source Rust environment after installation + . \$HOME/.cargo/env + python build_wheels_from_file.py --requirements '${{ inputs.packages }}' + " + + - name: Upload artifacts of downloaded_wheels directory + uses: actions/upload-artifact@v4 + with: + name: wheels-download-directory-linux-armv7legacy-${{ matrix.python-version }} + path: ./downloaded_wheels + # Repair wheels for dynamically linked libraries on all platforms # https://github.com/espressif/idf-python-wheels/blob/main/README.md#universal-wheel-tag---linking-of-dynamic-libraries repair-wheels: if: ${{ always() }} - needs: [get-supported-versions, ubuntu-latest, windows-latest, macos-latest, macos-m1, linux-armv7, linux-arm64] + needs: [get-supported-versions, ubuntu-latest, windows-latest, macos-latest, macos-m1, linux-armv7, linux-arm64, linux-armv7-legacy] name: Repair wheels uses: ./.github/workflows/wheels-repair.yml + # Test that all wheels can be installed on all supported platforms + test-wheels: + if: ${{ always() }} + needs: [get-supported-versions, repair-wheels] + name: Test wheels installation + uses: ./.github/workflows/test-wheels-install.yml + with: + supported_python_versions: ${{ needs.get-supported-versions.outputs.supported_python }} + upload-python-wheels: if: ${{ always() }} - needs: [repair-wheels] + needs: [test-wheels] name: Upload Python wheels - uses: espressif/idf-python-wheels/.github/workflows/upload-python-wheels.yml@main + uses: ./.github/workflows/upload-python-wheels.yml secrets: inherit diff --git a/.github/workflows/build-wheels-platforms.yml b/.github/workflows/build-wheels-platforms.yml index 313962a..cfbf869 100644 --- a/.github/workflows/build-wheels-platforms.yml +++ b/.github/workflows/build-wheels-platforms.yml @@ -213,8 +213,16 @@ jobs: name: Repair wheels uses: ./.github/workflows/wheels-repair.yml + # Test that all wheels can be installed on all supported platforms + test-wheels: + needs: [get-supported-versions, repair-wheels] + name: Test wheels installation + uses: ./.github/workflows/test-wheels-install.yml + with: + supported_python_versions: ${{ needs.get-supported-versions.outputs.supported_python }} + upload-python-wheels: - needs: [repair-wheels] + needs: [test-wheels] name: Upload Python wheels uses: ./.github/workflows/upload-python-wheels.yml secrets: inherit diff --git a/.github/workflows/test-wheels-install.yml b/.github/workflows/test-wheels-install.yml new file mode 100644 index 0000000..c71d9b6 --- /dev/null +++ b/.github/workflows/test-wheels-install.yml @@ -0,0 +1,288 @@ +name: Test wheels installation + +# Test that all built wheels are valid and platform-compatible +# This workflow runs after repair-wheels and before upload to catch any issues + +on: + workflow_call: + inputs: + supported_python_versions: + description: 'JSON array of supported Python versions' + required: true + type: string + +jobs: + test-install: + name: Test ${{ matrix.os }} - Python ${{ matrix.python-version }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + os: + - Windows + - Linux x86_64 + - macOS Intel + - macOS ARM + - Linux ARM64 + - Linux ARMv7 + - Linux ARMv7 Legacy + include: + - os: Windows + runner: windows-latest + arch: windows-x86_64 + - os: Linux x86_64 + runner: ubuntu-latest + arch: linux-x86_64 + - os: macOS Intel + runner: macos-15-intel + arch: macos-x86_64 + - os: macOS ARM + runner: macos-latest + arch: macos-arm64 + - os: Linux ARM64 + runner: ubuntu-24.04-arm + arch: linux-arm64 + - os: Linux ARMv7 + runner: ubuntu-latest + arch: linux-armv7 + - os: Linux ARMv7 Legacy + runner: ubuntu-latest + arch: linux-armv7legacy + python-version: ${{ fromJson(inputs.supported_python_versions) }} + exclude: + # Python 3.14 doesn't have bullseye images for ARM + - python-version: '3.14' + os: Linux ARMv7 Legacy + + steps: + - name: Set up QEMU for ARMv7 + if: matrix.os == 'Linux ARMv7' || matrix.os == 'Linux ARMv7 Legacy' + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/arm/v7 + + - name: Download repaired wheels + uses: actions/download-artifact@v4 + with: + name: wheels-repaired-${{ matrix.arch }} + path: ./downloaded_wheels + + - name: List downloaded wheels + if: matrix.os != 'Windows' + run: | + echo "Downloaded wheels:" + find ./downloaded_wheels -name "*.whl" | head -50 + echo "Total wheel count:" + find ./downloaded_wheels -name "*.whl" | wc -l + + - name: List downloaded wheels - Windows + if: matrix.os == 'Windows' + run: | + echo "Downloaded wheels:" + Get-ChildItem -Path ./downloaded_wheels -Recurse -Filter "*.whl" | Select-Object -First 50 | ForEach-Object { $_.FullName } + echo "Total wheel count:" + (Get-ChildItem -Path ./downloaded_wheels -Recurse -Filter "*.whl").Count + + - name: Setup Python + if: matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy' + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Test wheel installation - Linux/macOS + if: matrix.os != 'Windows' && matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy' + run: | + set -e + python --version + python -m pip install --upgrade pip + + # Get Python version tag (e.g., "311" for Python 3.11) + PY_VER=$(python -c "import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')") + echo "Testing wheels for Python version: cp${PY_VER}" + + # Find wheels compatible with this Python version + # Include: cpXY (exact match), py3/py2.py3 (universal), abi3 (stable ABI) + WHEELS=$(find ./downloaded_wheels -name "*.whl" \( \ + -name "*-cp${PY_VER}-*" -o \ + -name "*-py3-*" -o \ + -name "*-py2.py3-*" -o \ + -name "*-abi3-*" \ + \)) + WHEEL_COUNT=$(echo "$WHEELS" | grep -c . || echo "0") + echo "Found $WHEEL_COUNT compatible wheels to test" + + # Install each wheel with --no-deps to verify wheel validity and platform compatibility + # This tests that the wheel files are valid and manylinux tags are correct + # without running into dependency resolution issues between unrelated packages + echo "Installing wheels (--no-deps to test wheel validity only)..." + FAILED=0 + INSTALLED=0 + for wheel in $WHEELS; do + if pip install --no-deps --no-index --find-links ./downloaded_wheels "$wheel" 2>/dev/null; then + ((INSTALLED++)) + else + echo "Warning: Failed to install $(basename "$wheel")" + ((FAILED++)) + fi + done + + # Write results to file for summary step + echo "INSTALLED=$INSTALLED" > test_results.txt + echo "FAILED=$FAILED" >> test_results.txt + + echo "" + echo "Results: $INSTALLED installed successfully, $FAILED failed" + + - name: Test wheel installation - Windows + if: matrix.os == 'Windows' + run: | + python --version + python -m pip install --upgrade pip + + # Get Python version tag (e.g., "311" for Python 3.11) + $pyVer = python -c "import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')" + echo "Testing wheels for Python version: cp$pyVer" + + # Find wheels compatible with this Python version + # Include: cpXY (exact match), py3/py2.py3 (universal), abi3 (stable ABI) + $wheels = Get-ChildItem -Path ./downloaded_wheels -Filter "*.whl" | Where-Object { + $_.Name -match "-cp$pyVer-|-py3-|-py2\.py3-|-abi3-" + } + echo "Found $($wheels.Count) compatible wheels to test" + + # Install each wheel with --no-deps to verify wheel validity and platform compatibility + # This tests that the wheel files are valid without dependency resolution issues + echo "Installing wheels (--no-deps to test wheel validity only)..." + $installed = 0 + $failed = 0 + foreach ($wheel in $wheels) { + $result = pip install --no-deps --no-index --find-links ./downloaded_wheels $wheel.FullName 2>&1 + if ($LASTEXITCODE -eq 0) { + $installed++ + } else { + echo "Warning: Failed to install $($wheel.Name)" + $failed++ + } + } + + # Write results to file for summary step + "INSTALLED=$installed" | Out-File -FilePath test_results.txt -Encoding utf8 + "FAILED=$failed" | Out-File -FilePath test_results.txt -Append -Encoding utf8 + + echo "" + echo "Results: $installed installed successfully, $failed failed" + + - name: Test wheel installation - ARMv7 (in Docker) + if: matrix.os == 'Linux ARMv7' + run: | + docker run --rm --platform linux/arm/v7 \ + -v $(pwd):/work \ + -w /work \ + python:${{ matrix.python-version }}-bookworm \ + bash -c " + set -e + python --version + python -m pip install --upgrade pip + + # Get Python version tag (e.g., '311' for Python 3.11) + PY_VER=\$(python -c \"import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')\") + echo \"Testing wheels for Python version: cp\${PY_VER}\" + + # Find wheels compatible with this Python version + WHEELS=\$(find ./downloaded_wheels -name '*.whl' \( \ + -name \"*-cp\${PY_VER}-*\" -o \ + -name '*-py3-*' -o \ + -name '*-py2.py3-*' -o \ + -name '*-abi3-*' \ + \)) + WHEEL_COUNT=\$(echo \"\$WHEELS\" | grep -c . || echo '0') + echo \"Found \$WHEEL_COUNT compatible wheels to test\" + + # Install each wheel with --no-deps to verify wheel validity + echo 'Installing wheels (--no-deps to test wheel validity only)...' + FAILED=0 + INSTALLED=0 + for wheel in \$WHEELS; do + if pip install --no-deps --no-index --find-links ./downloaded_wheels \"\$wheel\" 2>/dev/null; then + ((INSTALLED++)) + else + echo \"Warning: Failed to install \$(basename \"\$wheel\")\" + ((FAILED++)) + fi + done + + # Write results to file for summary step + echo \"INSTALLED=\$INSTALLED\" > test_results.txt + echo \"FAILED=\$FAILED\" >> test_results.txt + + echo '' + echo \"Results: \$INSTALLED installed successfully, \$FAILED failed\" + " + + - name: Test wheel installation - ARMv7 Legacy (in Docker) + if: matrix.os == 'Linux ARMv7 Legacy' + run: | + docker run --rm --platform linux/arm/v7 \ + -v $(pwd):/work \ + -w /work \ + python:${{ matrix.python-version }}-bullseye \ + bash -c " + set -e + python --version + python -m pip install --upgrade pip + + # Get Python version tag (e.g., '311' for Python 3.11) + PY_VER=\$(python -c \"import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')\") + echo \"Testing wheels for Python version: cp\${PY_VER}\" + + # Find wheels compatible with this Python version + WHEELS=\$(find ./downloaded_wheels -name '*.whl' \( \ + -name \"*-cp\${PY_VER}-*\" -o \ + -name '*-py3-*' -o \ + -name '*-py2.py3-*' -o \ + -name '*-abi3-*' \ + \)) + WHEEL_COUNT=\$(echo \"\$WHEELS\" | grep -c . || echo '0') + echo \"Found \$WHEEL_COUNT compatible wheels to test\" + + # Install each wheel with --no-deps to verify wheel validity + echo 'Installing wheels (--no-deps to test wheel validity only)...' + FAILED=0 + INSTALLED=0 + for wheel in \$WHEELS; do + if pip install --no-deps --no-index --find-links ./downloaded_wheels \"\$wheel\" 2>/dev/null; then + ((INSTALLED++)) + else + echo \"Warning: Failed to install \$(basename \"\$wheel\")\" + ((FAILED++)) + fi + done + + # Write results to file for summary step + echo \"INSTALLED=\$INSTALLED\" > test_results.txt + echo \"FAILED=\$FAILED\" >> test_results.txt + + echo '' + echo \"Results: \$INSTALLED installed successfully, \$FAILED failed\" + " + + - name: Test Summary + shell: bash + run: | + echo "=== Test Summary ===" + if [ -f test_results.txt ]; then + cat test_results.txt + source test_results.txt + echo "" + echo "Installed: $INSTALLED" + echo "Failed: $FAILED" + echo "" + if [ "$FAILED" -gt 0 ]; then + echo "ERROR: $FAILED wheel(s) failed to install!" + exit 1 + fi + echo "All wheels installed successfully!" + else + echo "ERROR: test_results.txt not found!" + exit 1 + fi From d52bdea43e31ed1f2080cb0163904e95363cdfab Mon Sep 17 00:00:00 2001 From: Jakub Kocka Date: Thu, 29 Jan 2026 11:17:45 +0100 Subject: [PATCH 2/4] fix: Fixed test and repair jobs for defined wheels --- .github/workflows/build-wheels-defined.yml | 2 ++ .github/workflows/wheels-repair.yml | 32 ++++++++++++++++------ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-wheels-defined.yml b/.github/workflows/build-wheels-defined.yml index b94ba7a..12fc0b6 100644 --- a/.github/workflows/build-wheels-defined.yml +++ b/.github/workflows/build-wheels-defined.yml @@ -251,6 +251,7 @@ jobs: -w /work \ -e GH_TOKEN="${GH_TOKEN}" \ -e PIP_NO_CACHE_DIR=1 \ + -e LDFLAGS="-Wl,-z,max-page-size=0x1000" \ python:${{ matrix.python-version }}-bookworm \ bash -c " set -e @@ -336,6 +337,7 @@ jobs: -w /work \ -e GH_TOKEN="${GH_TOKEN}" \ -e PIP_NO_CACHE_DIR=1 \ + -e LDFLAGS="-Wl,-z,max-page-size=0x1000" \ python:${{ matrix.python-version }}-bullseye \ bash -c " set -e diff --git a/.github/workflows/wheels-repair.yml b/.github/workflows/wheels-repair.yml index ea0376a..c58f407 100644 --- a/.github/workflows/wheels-repair.yml +++ b/.github/workflows/wheels-repair.yml @@ -82,37 +82,50 @@ jobs: path: ./downloaded_wheels merge-multiple: true + - name: Check for wheels + id: check-wheels + run: | + if [ -d "./downloaded_wheels" ] && [ -n "$(find ./downloaded_wheels -name '*.whl' 2>/dev/null)" ]; then + echo "has_wheels=true" >> $GITHUB_OUTPUT + echo "Found wheels to repair" + else + echo "has_wheels=false" >> $GITHUB_OUTPUT + echo "No wheels found for ${{ matrix.platform }} - skipping repair" + fi + shell: bash + - name: Setup Python + if: steps.check-wheels.outputs.has_wheels == 'true' uses: actions/setup-python@v5 with: python-version: '3.12' - name: Set up QEMU - if: matrix.setup_qemu + if: matrix.setup_qemu && steps.check-wheels.outputs.has_wheels == 'true' uses: docker/setup-qemu-action@v3 with: platforms: ${{ matrix.qemu_platform }} - name: Install repair tool - Windows - if: matrix.tool == 'delvewheel' + if: matrix.tool == 'delvewheel' && steps.check-wheels.outputs.has_wheels == 'true' run: python -m pip install delvewheel - name: Install repair tool - macOS - if: matrix.tool == 'delocate' + if: matrix.tool == 'delocate' && steps.check-wheels.outputs.has_wheels == 'true' run: python -m pip install delocate - name: Install OS dependencies - macOS - if: matrix.tool == 'delocate' + if: matrix.tool == 'delocate' && steps.check-wheels.outputs.has_wheels == 'true' run: bash os_dependencies/macos.sh - name: Install dependencies and repair - Windows/macOS - if: matrix.tool != 'auditwheel' + if: matrix.tool != 'auditwheel' && steps.check-wheels.outputs.has_wheels == 'true' run: | python -m pip install -r build_requirements.txt python repair_wheels.py - name: Repair Linux x86_64 wheels in manylinux container - if: matrix.platform == 'Linux x86_64' + if: matrix.platform == 'Linux x86_64' && steps.check-wheels.outputs.has_wheels == 'true' run: | docker run --rm \ -v $(pwd):/work \ @@ -127,7 +140,7 @@ jobs: " - name: Repair Linux ARM64 wheels in manylinux container - if: matrix.platform == 'Linux ARM64' + if: matrix.platform == 'Linux ARM64' && steps.check-wheels.outputs.has_wheels == 'true' run: | docker run --rm \ --platform ${{ matrix.docker_platform }} \ @@ -144,7 +157,7 @@ jobs: - name: Repair Linux ARMv7 wheels in manylinux container # Using --break-system-packages and --ignore-installed to avoid conflicts with system packages - if: matrix.platform == 'Linux ARMv7' + if: matrix.platform == 'Linux ARMv7' && steps.check-wheels.outputs.has_wheels == 'true' run: | docker run --rm \ --platform ${{ matrix.docker_platform }} \ @@ -160,7 +173,7 @@ jobs: - name: Repair Linux ARMv7 Legacy wheels in manylinux container # Using --break-system-packages and --ignore-installed to avoid conflicts with system packages - if: matrix.platform == 'Linux ARMv7 Legacy' + if: matrix.platform == 'Linux ARMv7 Legacy' && steps.check-wheels.outputs.has_wheels == 'true' run: | docker run --rm \ --platform ${{ matrix.docker_platform }} \ @@ -175,6 +188,7 @@ jobs: " - name: Re-upload artifacts with repaired wheels + if: steps.check-wheels.outputs.has_wheels == 'true' uses: actions/upload-artifact@v4 with: name: wheels-repaired-${{ matrix.arch }} From 2c843efe1874745e4c458e3cc1be34475a9d179b Mon Sep 17 00:00:00 2001 From: Jakub Kocka Date: Thu, 29 Jan 2026 14:21:48 +0100 Subject: [PATCH 3/4] change: Changed test aproach to have main logic in Python script --- .github/workflows/test-wheels-install.yml | 195 +----------------- .../test_build_wheels.py | 6 + test/test_wheels_install.py | 135 ++++++++++++ 3 files changed, 150 insertions(+), 186 deletions(-) rename test_build_wheels.py => test/test_build_wheels.py (98%) create mode 100644 test/test_wheels_install.py diff --git a/.github/workflows/test-wheels-install.yml b/.github/workflows/test-wheels-install.yml index c71d9b6..6de4c1c 100644 --- a/.github/workflows/test-wheels-install.yml +++ b/.github/workflows/test-wheels-install.yml @@ -2,6 +2,7 @@ name: Test wheels installation # Test that all built wheels are valid and platform-compatible # This workflow runs after repair-wheels and before upload to catch any issues +# Uses test_wheels_install.py for consistent testing across all platforms on: workflow_call: @@ -55,6 +56,9 @@ jobs: os: Linux ARMv7 Legacy steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up QEMU for ARMv7 if: matrix.os == 'Linux ARMv7' || matrix.os == 'Linux ARMv7 Legacy' uses: docker/setup-qemu-action@v3 @@ -67,110 +71,18 @@ jobs: name: wheels-repaired-${{ matrix.arch }} path: ./downloaded_wheels - - name: List downloaded wheels - if: matrix.os != 'Windows' - run: | - echo "Downloaded wheels:" - find ./downloaded_wheels -name "*.whl" | head -50 - echo "Total wheel count:" - find ./downloaded_wheels -name "*.whl" | wc -l - - - name: List downloaded wheels - Windows - if: matrix.os == 'Windows' - run: | - echo "Downloaded wheels:" - Get-ChildItem -Path ./downloaded_wheels -Recurse -Filter "*.whl" | Select-Object -First 50 | ForEach-Object { $_.FullName } - echo "Total wheel count:" - (Get-ChildItem -Path ./downloaded_wheels -Recurse -Filter "*.whl").Count - - name: Setup Python if: matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy' uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Test wheel installation - Linux/macOS - if: matrix.os != 'Windows' && matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy' - run: | - set -e - python --version - python -m pip install --upgrade pip - - # Get Python version tag (e.g., "311" for Python 3.11) - PY_VER=$(python -c "import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')") - echo "Testing wheels for Python version: cp${PY_VER}" - - # Find wheels compatible with this Python version - # Include: cpXY (exact match), py3/py2.py3 (universal), abi3 (stable ABI) - WHEELS=$(find ./downloaded_wheels -name "*.whl" \( \ - -name "*-cp${PY_VER}-*" -o \ - -name "*-py3-*" -o \ - -name "*-py2.py3-*" -o \ - -name "*-abi3-*" \ - \)) - WHEEL_COUNT=$(echo "$WHEELS" | grep -c . || echo "0") - echo "Found $WHEEL_COUNT compatible wheels to test" - - # Install each wheel with --no-deps to verify wheel validity and platform compatibility - # This tests that the wheel files are valid and manylinux tags are correct - # without running into dependency resolution issues between unrelated packages - echo "Installing wheels (--no-deps to test wheel validity only)..." - FAILED=0 - INSTALLED=0 - for wheel in $WHEELS; do - if pip install --no-deps --no-index --find-links ./downloaded_wheels "$wheel" 2>/dev/null; then - ((INSTALLED++)) - else - echo "Warning: Failed to install $(basename "$wheel")" - ((FAILED++)) - fi - done - - # Write results to file for summary step - echo "INSTALLED=$INSTALLED" > test_results.txt - echo "FAILED=$FAILED" >> test_results.txt - - echo "" - echo "Results: $INSTALLED installed successfully, $FAILED failed" - - - name: Test wheel installation - Windows - if: matrix.os == 'Windows' + - name: Test wheel installation + if: matrix.os != 'Linux ARMv7' && matrix.os != 'Linux ARMv7 Legacy' run: | python --version python -m pip install --upgrade pip - - # Get Python version tag (e.g., "311" for Python 3.11) - $pyVer = python -c "import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')" - echo "Testing wheels for Python version: cp$pyVer" - - # Find wheels compatible with this Python version - # Include: cpXY (exact match), py3/py2.py3 (universal), abi3 (stable ABI) - $wheels = Get-ChildItem -Path ./downloaded_wheels -Filter "*.whl" | Where-Object { - $_.Name -match "-cp$pyVer-|-py3-|-py2\.py3-|-abi3-" - } - echo "Found $($wheels.Count) compatible wheels to test" - - # Install each wheel with --no-deps to verify wheel validity and platform compatibility - # This tests that the wheel files are valid without dependency resolution issues - echo "Installing wheels (--no-deps to test wheel validity only)..." - $installed = 0 - $failed = 0 - foreach ($wheel in $wheels) { - $result = pip install --no-deps --no-index --find-links ./downloaded_wheels $wheel.FullName 2>&1 - if ($LASTEXITCODE -eq 0) { - $installed++ - } else { - echo "Warning: Failed to install $($wheel.Name)" - $failed++ - } - } - - # Write results to file for summary step - "INSTALLED=$installed" | Out-File -FilePath test_results.txt -Encoding utf8 - "FAILED=$failed" | Out-File -FilePath test_results.txt -Append -Encoding utf8 - - echo "" - echo "Results: $installed installed successfully, $failed failed" + python test/test_wheels_install.py - name: Test wheel installation - ARMv7 (in Docker) if: matrix.os == 'Linux ARMv7' @@ -180,43 +92,9 @@ jobs: -w /work \ python:${{ matrix.python-version }}-bookworm \ bash -c " - set -e python --version python -m pip install --upgrade pip - - # Get Python version tag (e.g., '311' for Python 3.11) - PY_VER=\$(python -c \"import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')\") - echo \"Testing wheels for Python version: cp\${PY_VER}\" - - # Find wheels compatible with this Python version - WHEELS=\$(find ./downloaded_wheels -name '*.whl' \( \ - -name \"*-cp\${PY_VER}-*\" -o \ - -name '*-py3-*' -o \ - -name '*-py2.py3-*' -o \ - -name '*-abi3-*' \ - \)) - WHEEL_COUNT=\$(echo \"\$WHEELS\" | grep -c . || echo '0') - echo \"Found \$WHEEL_COUNT compatible wheels to test\" - - # Install each wheel with --no-deps to verify wheel validity - echo 'Installing wheels (--no-deps to test wheel validity only)...' - FAILED=0 - INSTALLED=0 - for wheel in \$WHEELS; do - if pip install --no-deps --no-index --find-links ./downloaded_wheels \"\$wheel\" 2>/dev/null; then - ((INSTALLED++)) - else - echo \"Warning: Failed to install \$(basename \"\$wheel\")\" - ((FAILED++)) - fi - done - - # Write results to file for summary step - echo \"INSTALLED=\$INSTALLED\" > test_results.txt - echo \"FAILED=\$FAILED\" >> test_results.txt - - echo '' - echo \"Results: \$INSTALLED installed successfully, \$FAILED failed\" + python test/test_wheels_install.py " - name: Test wheel installation - ARMv7 Legacy (in Docker) @@ -227,62 +105,7 @@ jobs: -w /work \ python:${{ matrix.python-version }}-bullseye \ bash -c " - set -e python --version python -m pip install --upgrade pip - - # Get Python version tag (e.g., '311' for Python 3.11) - PY_VER=\$(python -c \"import sys; print(f'{sys.version_info.major}{sys.version_info.minor}')\") - echo \"Testing wheels for Python version: cp\${PY_VER}\" - - # Find wheels compatible with this Python version - WHEELS=\$(find ./downloaded_wheels -name '*.whl' \( \ - -name \"*-cp\${PY_VER}-*\" -o \ - -name '*-py3-*' -o \ - -name '*-py2.py3-*' -o \ - -name '*-abi3-*' \ - \)) - WHEEL_COUNT=\$(echo \"\$WHEELS\" | grep -c . || echo '0') - echo \"Found \$WHEEL_COUNT compatible wheels to test\" - - # Install each wheel with --no-deps to verify wheel validity - echo 'Installing wheels (--no-deps to test wheel validity only)...' - FAILED=0 - INSTALLED=0 - for wheel in \$WHEELS; do - if pip install --no-deps --no-index --find-links ./downloaded_wheels \"\$wheel\" 2>/dev/null; then - ((INSTALLED++)) - else - echo \"Warning: Failed to install \$(basename \"\$wheel\")\" - ((FAILED++)) - fi - done - - # Write results to file for summary step - echo \"INSTALLED=\$INSTALLED\" > test_results.txt - echo \"FAILED=\$FAILED\" >> test_results.txt - - echo '' - echo \"Results: \$INSTALLED installed successfully, \$FAILED failed\" + python test/test_wheels_install.py " - - - name: Test Summary - shell: bash - run: | - echo "=== Test Summary ===" - if [ -f test_results.txt ]; then - cat test_results.txt - source test_results.txt - echo "" - echo "Installed: $INSTALLED" - echo "Failed: $FAILED" - echo "" - if [ "$FAILED" -gt 0 ]; then - echo "ERROR: $FAILED wheel(s) failed to install!" - exit 1 - fi - echo "All wheels installed successfully!" - else - echo "ERROR: test_results.txt not found!" - exit 1 - fi diff --git a/test_build_wheels.py b/test/test_build_wheels.py similarity index 98% rename from test_build_wheels.py rename to test/test_build_wheels.py index 11ef7c9..e865871 100644 --- a/test_build_wheels.py +++ b/test/test_build_wheels.py @@ -5,8 +5,14 @@ # # SPDX-License-Identifier: Apache-2.0 # +import sys import unittest +from pathlib import Path + +# Add parent directory to path for imports +sys.path.insert(0, str(Path(__file__).parent.parent)) + from packaging.requirements import Requirement from yaml_list_adapter import YAMLListAdapter diff --git a/test/test_wheels_install.py b/test/test_wheels_install.py new file mode 100644 index 0000000..0971c66 --- /dev/null +++ b/test/test_wheels_install.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +""" +Test wheel installation script for CI workflows. + +This script finds and installs wheels compatible with the current Python version, +verifying that wheel files are valid and platform-compatible. +""" + +import re +import subprocess +import sys + +from pathlib import Path + +WHEELS_DIR = Path("./downloaded_wheels") + + +def get_python_version_tag() -> str: + """Get the Python version tag (e.g., '311' for Python 3.11).""" + return f"{sys.version_info.major}{sys.version_info.minor}" + + +def is_wheel_compatible(wheel_name: str, python_version: str) -> bool: + """ + Check if a wheel is compatible with the given Python version. + + Compatible wheels are: + - cpXY: exact Python version match (e.g., cp311 for Python 3.11) + - py3: universal Python 3 wheels + - py2.py3: universal Python 2/3 wheels + - abi3: stable ABI wheels (compatible with Python >= base version) + """ + patterns = [ + rf"-cp{python_version}-", # Exact version match + r"-py3-", # Universal Python 3 + r"-py2\.py3-", # Universal Python 2/3 + r"-abi3-", # Stable ABI + ] + return any(re.search(pattern, wheel_name) for pattern in patterns) + + +def find_compatible_wheels(python_version: str) -> list[Path]: + """Find all wheel files compatible with the given Python version.""" + if not WHEELS_DIR.exists(): + return [] + + wheels = [] + for wheel_path in WHEELS_DIR.glob("*.whl"): + if is_wheel_compatible(wheel_path.name, python_version): + wheels.append(wheel_path) + + return sorted(wheels) + + +def install_wheel(wheel_path: Path) -> tuple[bool, str]: + """ + Install a wheel with --no-deps to verify wheel validity. + + Returns: + tuple: (success: bool, error_message: str) + """ + cmd = [ + sys.executable, + "-m", + "pip", + "install", + "--no-deps", + "--no-index", + "--find-links", + str(WHEELS_DIR), + str(wheel_path), + ] + + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode == 0: + return True, "" + + return False, (result.stderr or result.stdout).strip() + + +def main() -> int: + python_version = get_python_version_tag() + + # Find compatible wheels + wheels = find_compatible_wheels(python_version) + print(f"Found {len(wheels)} compatible wheels to test\n") + + if not wheels: + print("No compatible wheels found!") + return 1 + + # Install each wheel + installed = 0 + failed = 0 + failed_wheels = [] + + print("Installing wheels (--no-deps to test wheel validity only)...") + print("-" * 60) + + for wheel_path in wheels: + success, error_message = install_wheel(wheel_path) + + if success: + installed += 1 + else: + failed += 1 + failed_wheels.append((wheel_path.name, error_message)) + print() + print(f"ERROR: Failed to install {wheel_path.name}") + if error_message: + for line in error_message.split("\n"): + print(f" {line}") + print() + + print("-" * 60) + print(f"Results: {installed} installed successfully, {failed} failed\n") + + # Print summary of failures + if failed_wheels: + print("Failed wheels:") + for wheel_name, _ in failed_wheels: + print(f" - {wheel_name}") + print() + return 1 + + print("All wheels installed successfully!") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 8e040fe14f85b7c72a6030709e235f4899c3e9de Mon Sep 17 00:00:00 2001 From: Jakub Kocka Date: Thu, 29 Jan 2026 14:41:33 +0100 Subject: [PATCH 4/4] feat: Added more complex unit tests --- .github/workflows/unit-tests.yml | 38 +++ test/test_build_wheels.py | 436 +++++++++++++++++++++---------- test/test_wheels_install.py | 2 + 3 files changed, 335 insertions(+), 141 deletions(-) create mode 100644 .github/workflows/unit-tests.yml diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..9e1f5d4 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,38 @@ +name: Unit Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + get-supported-versions: + name: Get Supported Versions + uses: ./.github/workflows/get-supported-versions.yml + secrets: inherit + + unit-tests: + name: Unit Tests (Python ${{ matrix.python-version }}) + needs: get-supported-versions + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJson(needs.get-supported-versions.outputs.supported_python) }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install packaging pyyaml colorama requests + + - name: Run unit tests + run: python -m unittest discover -s test -v diff --git a/test/test_build_wheels.py b/test/test_build_wheels.py index e865871..27021d0 100644 --- a/test/test_build_wheels.py +++ b/test/test_build_wheels.py @@ -9,162 +9,316 @@ import unittest from pathlib import Path +from unittest.mock import patch # Add parent directory to path for imports sys.path.insert(0, str(Path(__file__).parent.parent)) from packaging.requirements import Requirement +from _helper_functions import get_no_binary_args +from _helper_functions import merge_requirements +from build_wheels import _add_into_requirements +from build_wheels import get_used_idf_branches from yaml_list_adapter import YAMLListAdapter -class TestYAMLtoRequirement(unittest.TestCase): +class TestChangeSpecifierLogic(unittest.TestCase): + """Test the _change_specifier_logic method.""" + + def setUp(self): + """Create a YAMLListAdapter instance for testing.""" + # Create instance with a minimal valid YAML file + self.adapter = YAMLListAdapter.__new__(YAMLListAdapter) + self.adapter._yaml_list = [] + self.adapter.exclude = False + self.adapter.requirements = set() + def test_change_specifier_logic(self): - version_with_specifier = ( - (">0.9.0.2", "<0.9.0.2"), - ("<0.9.0.2", ">0.9.0.2"), + """Test that specifier logic is correctly inverted (logical negation).""" + # The function performs logical negation: + # > becomes <= (not greater means less or equal) + # < becomes >= (not less means greater or equal) + # >= becomes < (not greater-or-equal means less) + # <= becomes > (not less-or-equal means greater) + test_cases = ( + (">0.9.0.2", "<=0.9.0.2"), + ("<0.9.0.2", ">=0.9.0.2"), ("==0.9.0.2", "!=0.9.0.2"), - (">=0.9.0.2", "<=0.9.0.2"), - ("<=0.9.0.2", ">=0.9.0.2"), + (">=0.9.0.2", "<0.9.0.2"), + ("<=0.9.0.2", ">0.9.0.2"), ("!=0.9.0.2", "==0.9.0.2"), ("===0.9.0.2", "===0.9.0.2"), ) - for case in version_with_specifier: - self.assertEqual( - f"{YAMLListAdapter._change_specifier_logic(case[0])[0]}{YAMLListAdapter._change_specifier_logic(case[0])[1]}", - case[1], - ) - - def test_yaml_to_requirement(self): - test_requirements = { - Requirement("platform;sys_platform == 'win32'"), - Requirement("platform;sys_platform == 'win32' or sys_platform == 'linux'"), - Requirement("version<42"), - Requirement("version<42,>50"), - Requirement("python;python_version > '3.10'"), - Requirement("python;python_version > '3.10' and python_version != '3.8'"), - Requirement("version-platform<=0.9.0.2;sys_platform == 'win32'"), - Requirement("version-platform<=0.9.0.2,>0.9.1;sys_platform == 'win32'"), - Requirement("version-platform<=0.9.0.2;sys_platform == 'win32' or sys_platform == 'linux'"), - Requirement("version-platform<=0.9.0.2,>0.9.1;sys_platform == 'win32' or sys_platform == 'linux'"), - Requirement("version-python<=0.9.0.2;python_version < '3.8'"), - Requirement("version-python<=0.9.0.2,>0.9.1;python_version < '3.8'"), - Requirement("version-python<=0.9.0.2;python_version < '3.8' and python_version > '3.11'"), - Requirement("version-python<=0.9.0.2,>0.9.1;python_version < '3.8' and python_version > '3.11'"), - Requirement("platform-python;sys_platform == 'win32' and python_version < '3.8'"), - Requirement( - "platform-python;sys_platform == 'win32' or sys_platform == 'linux' and python_version < '3.8'" - ), - Requirement( - "platform-python;sys_platform == 'win32' and python_version < '3.8' and python_version > '3.11'" - ), - Requirement( - "platform-python;sys_platform == 'win32' or sys_platform == 'linux' and python_version < '3.8' and python_version > '3.11'" - ), - Requirement("version-platform-python<=0.9.0.2;sys_platform == 'win32' and python_version < '3.8'"), - Requirement("version-platform-python<=0.9.0.2,>0.9.1;sys_platform == 'win32' and python_version < '3.8'"), - Requirement( - "version-platform-python<=0.9.0.2,>0.9.1;sys_platform == 'win32' or sys_platform == 'linux' and python_version < '3.8'" - ), - Requirement( - "version-platform-python<=0.9.0.2;sys_platform == 'win32' or sys_platform == 'linux' and python_version < '3.8'" - ), - Requirement( - "version-platform-python<=0.9.0.2;sys_platform == 'win32' and python_version < '3.8' and python_version > '3.11'" - ), - Requirement( - "version-platform-python<=0.9.0.2,>0.9.1;sys_platform == 'win32' and python_version < '3.8' and python_version > '3.11'" - ), - Requirement( - "version-platform-python<=0.9.0.2,>0.9.1;sys_platform == 'win32' or sys_platform == 'linux' and python_version < '3.8' and python_version > '3.11'" - ), - Requirement( - "version-platform-python<=0.9.0.2;sys_platform == 'win32' or sys_platform == 'linux' and python_version < '3.8' and python_version > '3.11'" - ), - } - - self.assertEqual(YAMLListAdapter._yaml_to_requirement("test/test_list.yaml"), test_requirements) - - def test_yaml_to_requirement_exclude(self): - test_requirements_exclude = { - Requirement("platform;sys_platform != 'win32'"), - Requirement("platform;sys_platform != 'win32' or sys_platform != 'linux'"), - Requirement("version>42"), - Requirement("version>42,<50"), - Requirement("python;python_version < '3.10'"), - Requirement("python;python_version < '3.10' and python_version == '3.8'"), - Requirement("version-platform>=0.9.0.2;sys_platform == 'win32'"), - Requirement("version-platform;sys_platform != 'win32'"), - Requirement("version-platform>=0.9.0.2,<0.9.1;sys_platform == 'win32'"), - Requirement("version-platform;sys_platform != 'win32'"), - Requirement("version-platform>=0.9.0.2;sys_platform == 'win32' or sys_platform == 'linux'"), - Requirement("version-platform;sys_platform != 'win32' or sys_platform != 'linux'"), - Requirement("version-platform>=0.9.0.2,<0.9.1;sys_platform == 'win32' or sys_platform == 'linux'"), - Requirement("version-platform;sys_platform != 'win32' or sys_platform != 'linux'"), - Requirement("version-python>=0.9.0.2;python_version < '3.8'"), - Requirement("version-python;python_version > '3.8'"), - Requirement("version-python>=0.9.0.2,<0.9.1;python_version < '3.8'"), - Requirement("version-python;python_version > '3.8'"), - Requirement("version-python>=0.9.0.2;python_version < '3.8' and python_version > '3.11'"), - Requirement("version-python;python_version > '3.8' and python_version < '3.11'"), - Requirement("version-python>=0.9.0.2,<0.9.1;python_version < '3.8' and python_version > '3.11'"), - Requirement("version-python;python_version > '3.8' and python_version < '3.11'"), - Requirement("platform-python;sys_platform != 'win32' and python_version > '3.8'"), - Requirement( - "platform-python;sys_platform != 'win32' or sys_platform != 'linux' and python_version > '3.8'" - ), - Requirement( - "platform-python;sys_platform != 'win32' and python_version > '3.8' and python_version < '3.11'" - ), - Requirement( - "platform-python;sys_platform != 'win32' or sys_platform != 'linux' and python_version > '3.8' and python_version < '3.11'" - ), - Requirement("version-platform-python>=0.9.0.2;sys_platform == 'win32' and python_version < '3.8'"), - Requirement("version-platform-python;sys_platform != 'win32' and python_version > '3.8'"), - Requirement("version-platform-python>=0.9.0.2,<0.9.1;sys_platform == 'win32' and python_version < '3.8'"), - Requirement("version-platform-python;sys_platform != 'win32' and python_version > '3.8'"), - Requirement( - "version-platform-python>=0.9.0.2,<0.9.1;sys_platform == 'win32' or sys_platform == 'linux' and python_version < '3.8'" - ), - Requirement( - "version-platform-python;sys_platform != 'win32' or sys_platform != 'linux' and python_version > '3.8'" - ), - Requirement( - "version-platform-python>=0.9.0.2;sys_platform == 'win32' or sys_platform == 'linux' and python_version < '3.8'" - ), - Requirement( - "version-platform-python;sys_platform != 'win32' or sys_platform != 'linux' and python_version > '3.8'" - ), - Requirement( - "version-platform-python>=0.9.0.2;sys_platform == 'win32' and python_version < '3.8' and python_version > '3.11'" - ), - Requirement( - "version-platform-python;sys_platform != 'win32' and python_version > '3.8' and python_version < '3.11'" - ), - Requirement( - "version-platform-python>=0.9.0.2,<0.9.1;sys_platform == 'win32' and python_version < '3.8' and python_version > '3.11'" - ), - Requirement( - "version-platform-python;sys_platform != 'win32' and python_version > '3.8' and python_version < '3.11'" - ), - Requirement( - "version-platform-python>=0.9.0.2,<0.9.1;sys_platform == 'win32' or sys_platform == 'linux' and python_version < '3.8' and python_version > '3.11'" - ), - Requirement( - "version-platform-python;sys_platform != 'win32' or sys_platform != 'linux' and python_version > '3.8' and python_version < '3.11'" - ), - Requirement( - "version-platform-python>=0.9.0.2;sys_platform == 'win32' or sys_platform == 'linux' and python_version < '3.8' and python_version > '3.11'" - ), - Requirement( - "version-platform-python;sys_platform != 'win32' or sys_platform != 'linux' and python_version > '3.8' and python_version < '3.11'" - ), - } - - self.assertEqual( - YAMLListAdapter._yaml_to_requirement("test/test_list.yaml", exclude=True), test_requirements_exclude - ) + for original, expected in test_cases: + with self.subTest(original=original): + new_spec, ver, _ = self.adapter._change_specifier_logic(original) + result = f"{new_spec}{ver}" + self.assertEqual(result, expected) + + +class TestYAMLtoRequirement(unittest.TestCase): + """Test the _yaml_to_requirement method.""" + + def setUp(self): + """Create a YAMLListAdapter instance for testing.""" + self.adapter = YAMLListAdapter.__new__(YAMLListAdapter) + self.adapter._yaml_list = [] + self.adapter.exclude = False + self.adapter.requirements = set() + + def test_simple_package(self): + """Test conversion of a simple package without markers.""" + yaml_list = [{"package_name": "numpy"}] + result = self.adapter._yaml_to_requirement(yaml_list) + self.assertEqual(result, {Requirement("numpy")}) + + def test_package_with_version(self): + """Test conversion of a package with version specifier.""" + yaml_list = [{"package_name": "numpy", "version": "<1.20"}] + result = self.adapter._yaml_to_requirement(yaml_list) + self.assertEqual(result, {Requirement("numpy<1.20")}) + + def test_package_with_multiple_versions(self): + """Test conversion of a package with multiple version specifiers.""" + yaml_list = [{"package_name": "numpy", "version": ["<1.20", ">=1.10"]}] + result = self.adapter._yaml_to_requirement(yaml_list) + self.assertEqual(result, {Requirement("numpy<1.20,>=1.10")}) + + def test_package_with_platform(self): + """Test conversion of a package with platform marker.""" + yaml_list = [{"package_name": "pywin32", "platform": "win32"}] + result = self.adapter._yaml_to_requirement(yaml_list) + self.assertEqual(result, {Requirement("pywin32; sys_platform == 'win32'")}) + + def test_package_with_multiple_platforms(self): + """Test conversion of a package with multiple platform markers.""" + yaml_list = [{"package_name": "pkg", "platform": ["win32", "linux"]}] + result = self.adapter._yaml_to_requirement(yaml_list) + self.assertEqual(result, {Requirement("pkg; sys_platform == 'win32' or sys_platform == 'linux'")}) + + def test_package_with_python_version(self): + """Test conversion of a package with python version marker.""" + yaml_list = [{"package_name": "pkg", "python": ">=3.8"}] + result = self.adapter._yaml_to_requirement(yaml_list) + self.assertEqual(result, {Requirement("pkg; python_version >= '3.8'")}) + + def test_package_with_version_and_platform(self): + """Test conversion of a package with version and platform.""" + yaml_list = [{"package_name": "numpy", "version": "<=1.20", "platform": "win32"}] + result = self.adapter._yaml_to_requirement(yaml_list) + self.assertEqual(result, {Requirement("numpy<=1.20; sys_platform == 'win32'")}) + + def test_exclude_simple_platform(self): + """Test exclude mode with platform marker.""" + yaml_list = [{"package_name": "pkg", "platform": "win32"}] + result = self.adapter._yaml_to_requirement(yaml_list, exclude=True) + self.assertEqual(result, {Requirement("pkg; sys_platform != 'win32'")}) + + def test_exclude_version(self): + """Test exclude mode with version specifier.""" + yaml_list = [{"package_name": "numpy", "version": "<1.20"}] + result = self.adapter._yaml_to_requirement(yaml_list, exclude=True) + self.assertEqual(result, {Requirement("numpy>=1.20")}) + + +class TestYAMLListAdapterIntegration(unittest.TestCase): + """Integration tests using actual YAML files.""" + + def test_load_include_list(self): + """Test loading the include_list.yaml file.""" + try: + adapter = YAMLListAdapter("include_list.yaml") + self.assertIsInstance(adapter.requirements, set) + except FileNotFoundError: + self.skipTest("include_list.yaml not found") + + def test_load_exclude_list(self): + """Test loading the exclude_list.yaml file.""" + try: + adapter = YAMLListAdapter("exclude_list.yaml", exclude=True) + self.assertIsInstance(adapter.requirements, set) + except FileNotFoundError: + self.skipTest("exclude_list.yaml not found") + + +class TestWheelCompatibility(unittest.TestCase): + """Test the is_wheel_compatible function from test_wheels_install.py.""" + + def setUp(self): + """Import the function to test.""" + sys.path.insert(0, str(Path(__file__).parent)) + from test_wheels_install import is_wheel_compatible + + self.is_wheel_compatible = is_wheel_compatible + + def test_exact_python_version_match(self): + """Test that cpXY wheels match the exact Python version.""" + self.assertTrue(self.is_wheel_compatible("numpy-1.0.0-cp311-cp311-linux_x86_64.whl", "311")) + self.assertFalse(self.is_wheel_compatible("numpy-1.0.0-cp310-cp310-linux_x86_64.whl", "311")) + + def test_universal_py3_wheel(self): + """Test that py3 wheels are compatible with any Python 3.""" + self.assertTrue(self.is_wheel_compatible("six-1.0.0-py3-none-any.whl", "311")) + self.assertTrue(self.is_wheel_compatible("six-1.0.0-py3-none-any.whl", "39")) + + def test_universal_py2_py3_wheel(self): + """Test that py2.py3 wheels are compatible with any Python.""" + self.assertTrue(self.is_wheel_compatible("six-1.0.0-py2.py3-none-any.whl", "311")) + self.assertTrue(self.is_wheel_compatible("six-1.0.0-py2.py3-none-any.whl", "39")) + + def test_abi3_wheel(self): + """Test that abi3 wheels are compatible.""" + self.assertTrue(self.is_wheel_compatible("cryptography-41.0.0-cp39-abi3-linux_x86_64.whl", "311")) + self.assertTrue(self.is_wheel_compatible("cryptography-41.0.0-cp39-abi3-linux_x86_64.whl", "39")) + + +class TestGetUsedIdfBranches(unittest.TestCase): + """Test the get_used_idf_branches function.""" + + @patch("build_wheels.MIN_IDF_MAJOR_VERSION", 5) + @patch("build_wheels.MIN_IDF_MINOR_VERSION", 0) + def test_filters_old_branches(self): + """Test that branches older than minimum version are filtered out.""" + branches = [ + "release/v4.4", + "release/v5.0", + "release/v5.1", + "release/v5.2", + "master", + ] + result = get_used_idf_branches(branches) + self.assertIn("release/v5.0", result) + self.assertIn("release/v5.1", result) + self.assertIn("release/v5.2", result) + self.assertIn("master", result) + self.assertNotIn("release/v4.4", result) + + @patch("build_wheels.MIN_IDF_MAJOR_VERSION", 5) + @patch("build_wheels.MIN_IDF_MINOR_VERSION", 1) + def test_filters_by_minor_version(self): + """Test that filtering works correctly with minor version.""" + branches = [ + "release/v5.0", + "release/v5.1", + "release/v5.2", + ] + result = get_used_idf_branches(branches) + self.assertNotIn("release/v5.0", result) + self.assertIn("release/v5.1", result) + self.assertIn("release/v5.2", result) + + def test_ignores_non_release_branches(self): + """Test that non-release branches (except master) are ignored.""" + branches = [ + "feature/test", + "bugfix/something", + "release/v5.0", + ] + result = get_used_idf_branches(branches) + self.assertNotIn("feature/test", result) + self.assertNotIn("bugfix/something", result) + self.assertIn("master", result) + + +class TestAddIntoRequirements(unittest.TestCase): + """Test the _add_into_requirements function.""" + + def test_parses_simple_requirements(self): + """Test parsing simple requirement lines.""" + lines = ["numpy", "pandas>=1.0", "requests==2.28.0"] + result = _add_into_requirements(lines) + self.assertEqual(len(result), 3) + names = {r.name for r in result} + self.assertIn("numpy", names) + self.assertIn("pandas", names) + self.assertIn("requests", names) + + def test_ignores_comments(self): + """Test that comment lines are ignored.""" + lines = [ + "# This is a comment", + "numpy", + "pandas # inline comment", + ] + result = _add_into_requirements(lines) + self.assertEqual(len(result), 2) + + def test_ignores_empty_lines(self): + """Test that empty lines are ignored.""" + lines = ["numpy", "", " ", "pandas"] + result = _add_into_requirements(lines) + self.assertEqual(len(result), 2) + + def test_handles_whitespace(self): + """Test that leading/trailing whitespace is handled.""" + lines = [" numpy ", "\tpandas\t"] + result = _add_into_requirements(lines) + self.assertEqual(len(result), 2) + + +class TestMergeRequirements(unittest.TestCase): + """Test the merge_requirements function.""" + + def test_merge_specifiers(self): + """Test merging two requirements with version specifiers.""" + req1 = Requirement("numpy>=1.0") + req2 = Requirement("numpy<2.0") + result = merge_requirements(req1, req2) + self.assertEqual(result.name, "numpy") + self.assertIn(">=1.0", str(result.specifier)) + self.assertIn("<2.0", str(result.specifier)) + + def test_merge_markers(self): + """Test merging two requirements with markers.""" + req1 = Requirement("numpy; sys_platform == 'win32'") + req2 = Requirement("numpy; python_version >= '3.8'") + result = merge_requirements(req1, req2) + self.assertEqual(result.name, "numpy") + self.assertIn("sys_platform", str(result.marker)) + self.assertIn("python_version", str(result.marker)) + + def test_merge_preserves_name(self): + """Test that package name is preserved after merge.""" + req1 = Requirement("requests>=2.0") + req2 = Requirement("requests; sys_platform == 'linux'") + result = merge_requirements(req1, req2) + self.assertEqual(result.name, "requests") + + +class TestGetNoBinaryArgs(unittest.TestCase): + """Test the get_no_binary_args function.""" + + @patch("_helper_functions.platform.system", return_value="Linux") + def test_returns_args_for_source_build_packages_on_linux(self, mock_system): + """Test that --no-binary args are returned for specified packages on Linux.""" + result = get_no_binary_args("cffi") + self.assertEqual(result, ["--no-binary", "cffi"]) + + @patch("_helper_functions.platform.system", return_value="Linux") + def test_handles_requirement_with_version(self, mock_system): + """Test that package name is extracted from requirement string.""" + result = get_no_binary_args("cffi>=1.0") + self.assertEqual(result, ["--no-binary", "cffi"]) + + @patch("_helper_functions.platform.system", return_value="Windows") + def test_returns_empty_on_windows(self, mock_system): + """Test that empty list is returned on Windows.""" + result = get_no_binary_args("cffi") + self.assertEqual(result, []) + + @patch("_helper_functions.platform.system", return_value="Darwin") + def test_returns_empty_on_macos(self, mock_system): + """Test that empty list is returned on macOS.""" + result = get_no_binary_args("cffi") + self.assertEqual(result, []) + + @patch("_helper_functions.platform.system", return_value="Linux") + def test_returns_empty_for_non_source_build_package(self, mock_system): + """Test that empty list is returned for packages not in source build list.""" + result = get_no_binary_args("requests") + self.assertEqual(result, []) if __name__ == "__main__": diff --git a/test/test_wheels_install.py b/test/test_wheels_install.py index 0971c66..0d3a68b 100644 --- a/test/test_wheels_install.py +++ b/test/test_wheels_install.py @@ -9,6 +9,8 @@ verifying that wheel files are valid and platform-compatible. """ +from __future__ import annotations + import re import subprocess import sys