From 0c129bef152c4ab14f66a44a71fd0b22259b71ee Mon Sep 17 00:00:00 2001 From: Eric Pinzur <2641606+epinzur@users.noreply.github.com> Date: Thu, 31 Jul 2025 13:25:50 +0200 Subject: [PATCH] bug: simplified cross-platform test logic (#9256) --- .../workflows/cross-platform-test-manual.yml | 59 --- .../workflows/cross-platform-test-shared.yml | 305 ---------------- .github/workflows/cross-platform-test.md | 102 ++++-- .github/workflows/cross-platform-test.yml | 337 +++++++++++++++++- 4 files changed, 390 insertions(+), 413 deletions(-) delete mode 100644 .github/workflows/cross-platform-test-manual.yml delete mode 100644 .github/workflows/cross-platform-test-shared.yml diff --git a/.github/workflows/cross-platform-test-manual.yml b/.github/workflows/cross-platform-test-manual.yml deleted file mode 100644 index f3894802c..000000000 --- a/.github/workflows/cross-platform-test-manual.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Manual Cross-Platform Test - -on: - workflow_dispatch: - inputs: - test-from-pypi: - description: "Test from PyPI instead of building from source" - type: boolean - default: false - langflow-version: - description: "Langflow version to test from PyPI (leave empty for latest)" - required: false - type: string - default: "" - test-timeout: - description: "Timeout for langflow server startup test (minutes)" - required: false - type: number - default: 5 - -jobs: - test-pypi-installation: - if: inputs.test-from-pypi == true - name: Test PyPI Installation - uses: ./.github/workflows/cross-platform-test-shared.yml - with: - install-method: "pypi" - test-timeout: ${{ inputs.test-timeout }} - langflow-version: ${{ inputs.langflow-version }} - base-artifact-name: "" - main-artifact-name: "" - run-id: "" - - test-source-build: - if: inputs.test-from-pypi == false - name: Test Source Build and Install - uses: ./.github/workflows/cross-platform-test.yml - with: - base-artifact-name: "" - main-artifact-name: "" - test-timeout: ${{ inputs.test-timeout }} - run-id: "" - - test-summary: - name: Test Summary - needs: [test-pypi-installation, test-source-build] - runs-on: ubuntu-latest - if: always() - steps: - - name: Check test results - run: | - if [ "${{ needs.test-pypi-installation.result }}" = "failure" ] || [ "${{ needs.test-source-build.result }}" = "failure" ]; then - echo "❌ Cross-platform tests failed" - exit 1 - elif [ "${{ needs.test-pypi-installation.result }}" = "success" ] || [ "${{ needs.test-source-build.result }}" = "success" ]; then - echo "✅ Cross-platform tests passed" - else - echo "ℹ️ No tests were run" - fi \ No newline at end of file diff --git a/.github/workflows/cross-platform-test-shared.yml b/.github/workflows/cross-platform-test-shared.yml deleted file mode 100644 index 695aa84c1..000000000 --- a/.github/workflows/cross-platform-test-shared.yml +++ /dev/null @@ -1,305 +0,0 @@ -name: Shared Cross-Platform Test Logic - -on: - workflow_call: - inputs: - test-timeout: - description: "Timeout for langflow server startup test (minutes)" - required: false - type: number - default: 5 - install-method: - description: "Installation method: 'wheel' for local wheels, 'pypi' for PyPI packages" - required: true - type: string - langflow-version: - description: "Langflow version to install from PyPI (only used if install-method is 'pypi')" - required: false - type: string - default: "" - base-artifact-name: - description: "Name of the base package artifact (only used if install-method is 'wheel')" - required: false - type: string - default: "" - main-artifact-name: - description: "Name of the main package artifact (only used if install-method is 'wheel')" - required: false - type: string - default: "" - run-id: - description: "GitHub run ID to download artifacts from (leave empty for current run)" - required: false - type: string - default: "" - -jobs: - test-installation: - name: Install & Run - ${{ matrix.os }} ${{ matrix.arch }} ${{ matrix.python-version }} - runs-on: ${{ matrix.runner }} - strategy: - fail-fast: false - matrix: - include: - # Linux AMD64 - - os: linux - arch: amd64 - runner: ubuntu-latest - python-version: "3.10" - - os: linux - arch: amd64 - runner: ubuntu-latest - python-version: "3.13" - # macOS AMD64 - - os: macos - arch: amd64 - runner: macos-13 - python-version: "3.10" - - os: macos - arch: amd64 - runner: macos-13 - python-version: "3.13" - # macOS ARM64 (Apple Silicon) - - os: macos - arch: arm64 - runner: macos-latest - python-version: "3.10" - - os: macos - arch: arm64 - runner: macos-latest - python-version: "3.13" - # Windows AMD64 - - os: windows - arch: amd64 - runner: windows-latest - python-version: "3.10" - - os: windows - arch: amd64 - runner: windows-latest - python-version: "3.12" - - steps: - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - architecture: ${{ matrix.arch == 'amd64' && 'x64' || matrix.arch }} - - - name: Setup UV - uses: astral-sh/setup-uv@v6 - with: - enable-cache: false - - # Download artifacts for wheel installation - - name: Download base package artifact - if: inputs.install-method == 'wheel' - uses: actions/download-artifact@v4 - with: - name: ${{ inputs.base-artifact-name }} - path: ./base-dist - - - name: Download main package artifact - if: inputs.install-method == 'wheel' - uses: actions/download-artifact@v4 - with: - name: ${{ inputs.main-artifact-name }} - path: ./main-dist - - - name: Create fresh virtual environment - run: | - uv venv test-env --seed - shell: bash - - # Wheel installation steps - - name: Install base package from wheel (Windows) - if: inputs.install-method == 'wheel' && matrix.os == 'windows' - run: | - ls -la ./base-dist/ - find ./base-dist -name "*.whl" -type f - WHEEL_FILE=$(find ./base-dist -name "*.whl" -type f | head -1) - if [ -n "$WHEEL_FILE" ]; then - uv pip install --python ./test-env/Scripts/python.exe "$WHEEL_FILE" - else - echo "No wheel file found in ./base-dist/" - exit 1 - fi - shell: bash - - - name: Install main package from wheel (Windows) - if: inputs.install-method == 'wheel' && matrix.os == 'windows' - run: | - ls -la ./main-dist/ - find ./main-dist -name "*.whl" -type f - WHEEL_FILE=$(find ./main-dist -name "*.whl" -type f | head -1) - if [ -n "$WHEEL_FILE" ]; then - uv pip install --python ./test-env/Scripts/python.exe "$WHEEL_FILE" - else - echo "No wheel file found in ./main-dist/" - exit 1 - fi - shell: bash - - - name: Install base package from wheel (Unix) - if: inputs.install-method == 'wheel' && matrix.os != 'windows' - run: | - ls -la ./base-dist/ - find ./base-dist -name "*.whl" -type f - WHEEL_FILE=$(find ./base-dist -name "*.whl" -type f | head -1) - if [ -n "$WHEEL_FILE" ]; then - uv pip install --python ./test-env/bin/python "$WHEEL_FILE" - else - echo "No wheel file found in ./base-dist/" - exit 1 - fi - shell: bash - - - name: Install main package from wheel (Unix) - if: inputs.install-method == 'wheel' && matrix.os != 'windows' - run: | - ls -la ./main-dist/ - find ./main-dist -name "*.whl" -type f - WHEEL_FILE=$(find ./main-dist -name "*.whl" -type f | head -1) - if [ -n "$WHEEL_FILE" ]; then - uv pip install --python ./test-env/bin/python "$WHEEL_FILE" - else - echo "No wheel file found in ./main-dist/" - exit 1 - fi - shell: bash - - # PyPI installation steps - - name: Install langflow from PyPI (Windows) - if: inputs.install-method == 'pypi' && matrix.os == 'windows' - run: | - if [ -n "${{ inputs.langflow-version }}" ]; then - uv pip install --python ./test-env/Scripts/python.exe langflow==${{ inputs.langflow-version }} - else - uv pip install --python ./test-env/Scripts/python.exe langflow - fi - shell: bash - - - name: Install langflow from PyPI (Unix) - if: inputs.install-method == 'pypi' && matrix.os != 'windows' - run: | - if [ -n "${{ inputs.langflow-version }}" ]; then - uv pip install --python ./test-env/bin/python langflow==${{ inputs.langflow-version }} - else - uv pip install --python ./test-env/bin/python langflow - fi - shell: bash - - # Install additional dependencies - - name: Install additional dependencies (Windows) - if: matrix.os == 'windows' - run: | - uv pip install --python ./test-env/Scripts/python.exe openai - shell: bash - - - name: Install additional dependencies (Unix) - if: matrix.os != 'windows' - run: | - uv pip install --python ./test-env/bin/python openai - shell: bash - - # Test steps - - name: Test CLI help command (Windows) - if: matrix.os == 'windows' - run: | - test-env\Scripts\python.exe -m langflow --help - shell: cmd - - - name: Test CLI help command (Unix) - if: matrix.os != 'windows' - run: | - ./test-env/bin/python -m langflow --help - shell: bash - - - name: Test server startup (Windows) - if: matrix.os == 'windows' - timeout-minutes: ${{ inputs.test-timeout }} - run: | - # Start server in background - $serverProcess = Start-Process -FilePath ".\test-env\Scripts\python.exe" -ArgumentList "-m", "langflow", "run", "--host", "localhost", "--port", "7860", "--backend-only" -PassThru -WindowStyle Hidden - - # Wait for server to be ready - $timeoutSeconds = ${{ inputs.test-timeout }} * 60 - $elapsed = 0 - do { - try { - $response = Invoke-WebRequest -Uri "http://localhost:7860/health_check" -UseBasicParsing -TimeoutSec 5 - if ($response.StatusCode -eq 200) { - Write-Host "✅ Server is ready on ${{ matrix.os }}-${{ matrix.arch }}" - break - } - } catch { - Start-Sleep -Seconds 5 - $elapsed += 5 - } - } while ($elapsed -lt $timeoutSeconds) - - if ($elapsed -ge $timeoutSeconds) { - Write-Host "❌ Server failed to start within timeout on ${{ matrix.os }}-${{ matrix.arch }}" - exit 1 - } - - # Stop the server process - Stop-Process -Id $serverProcess.Id -Force -ErrorAction SilentlyContinue - shell: powershell - - - name: Test server startup (Unix) - if: matrix.os != 'windows' - timeout-minutes: ${{ inputs.test-timeout }} - run: | - # Start server in background - ./test-env/bin/python -m langflow run --host localhost --port 7860 --backend-only & - SERVER_PID=$! - - # Wait for server to be ready (using bash loop instead of timeout command) - TIMEOUT_SECONDS=$((${{ inputs.test-timeout }} * 60)) - ELAPSED=0 - while [ $ELAPSED -lt $TIMEOUT_SECONDS ]; do - if curl -f http://localhost:7860/health_check >/dev/null 2>&1; then - echo "✅ Server is ready on ${{ matrix.os }}-${{ matrix.arch }}" - break - fi - sleep 5 - ELAPSED=$((ELAPSED + 5)) - done - - if [ $ELAPSED -ge $TIMEOUT_SECONDS ]; then - echo "❌ Server failed to start within timeout on ${{ matrix.os }}-${{ matrix.arch }}" - kill $SERVER_PID 2>/dev/null || true - exit 1 - fi - - # Clean shutdown - kill $SERVER_PID 2>/dev/null || true - sleep 5 - shell: bash - - - name: Test import in Python (Windows) - if: matrix.os == 'windows' - run: | - test-env\Scripts\python.exe -c " - try: - import langflow - print('✅ langflow import successful on ${{ matrix.os }}-${{ matrix.arch }}') - except Exception as e: - print(f'❌ langflow import failed on ${{ matrix.os }}-${{ matrix.arch }}: {e}') - exit(1) - " - shell: cmd - - - name: Test import in Python (Unix) - if: matrix.os != 'windows' - run: | - ./test-env/bin/python -c " - try: - import langflow - print('✅ langflow import successful on ${{ matrix.os }}-${{ matrix.arch }}') - except Exception as e: - print(f'❌ langflow import failed on ${{ matrix.os }}-${{ matrix.arch }}: {e}') - exit(1) - " - shell: bash - diff --git a/.github/workflows/cross-platform-test.md b/.github/workflows/cross-platform-test.md index 3759e5248..f1f22f738 100644 --- a/.github/workflows/cross-platform-test.md +++ b/.github/workflows/cross-platform-test.md @@ -1,25 +1,25 @@ # Cross-Platform Install Tests -Guide for running cross-platform installation tests manually and programmatically. +Unified workflow for testing langflow installation across multiple platforms, supporting both manual and programmatic execution. -## Available Tests +## Manual Testing ### 1. Test from PyPI Tests published langflow packages from PyPI across all platforms. **Via GitHub UI:** -1. Go to **Actions** → **Manual Cross-Platform Test** -2. Check **"Test from PyPI"** +1. Go to **Actions** → **Cross-Platform Installation Test** +2. Check **"Test from PyPI instead of building from source"** 3. Optionally specify a version (leave empty for latest) 4. Click **"Run workflow"** **Via CLI:** ```bash # Test latest version -gh workflow run cross-platform-test-manual.yml -f test-from-pypi=true +gh workflow run cross-platform-test.yml -f test-from-pypi=true # Test specific version -gh workflow run cross-platform-test-manual.yml \ +gh workflow run cross-platform-test.yml \ -f test-from-pypi=true \ -f langflow-version="1.0.18" ``` @@ -28,14 +28,28 @@ gh workflow run cross-platform-test-manual.yml \ Builds and tests langflow from current branch source code. **Via GitHub UI:** -1. Go to **Actions** → **Manual Cross-Platform Test** -2. Leave **"Test from PyPI"** unchecked +1. Go to **Actions** → **Cross-Platform Installation Test** +2. Leave **"Test from PyPI instead of building from source"** unchecked 3. Click **"Run workflow"** **Via CLI:** ```bash # Test current branch -gh workflow run cross-platform-test-manual.yml -f test-from-pypi=false +gh workflow run cross-platform-test.yml -f test-from-pypi=false +``` + +## Programmatic Testing + +For CI, releases, and other automated workflows that test wheel installation: + +```yaml +jobs: + test-cross-platform: + uses: ./.github/workflows/cross-platform-test.yml + with: + base-artifact-name: "dist-base" + main-artifact-name: "dist-main" + test-timeout: 120 # optional, defaults to 5 ``` ## Platforms Tested @@ -59,11 +73,11 @@ gh workflow run cross-platform-test-manual.yml -f test-from-pypi=false ```bash # Extended timeout (10 minutes instead of default 5) -gh workflow run cross-platform-test-manual.yml \ +gh workflow run cross-platform-test.yml \ -f test-timeout=10 # Test specific PyPI version -gh workflow run cross-platform-test-manual.yml \ +gh workflow run cross-platform-test.yml \ -f test-from-pypi=true \ -f langflow-version="1.0.18" ``` @@ -94,36 +108,56 @@ gh workflow run cross-platform-test-manual.yml \ ### Workflow Architecture -``` -Manual Entry Point: -└── cross-platform-test-manual.yml (workflow_dispatch) - ├── PyPI Mode → cross-platform-test-shared.yml - └── Source Mode → cross-platform-test.yml → cross-platform-test-shared.yml +**Unified Single-File Design:** -Programmatic Entry Point: -└── cross-platform-test.yml (workflow_call only) - └── cross-platform-test-shared.yml +``` +cross-platform-test.yml +├── workflow_dispatch (Manual UI) +│ ├── PyPI Testing (test-from-pypi=true) +│ └── Source Testing (test-from-pypi=false) +└── workflow_call (Programmatic API) + └── Wheel Testing (always uses wheel method) ``` -- **Manual Workflow**: User-facing interface with PyPI/source options -- **Main Workflow**: Internal workflow for programmatic calls (CI, releases) -- **Shared Workflow**: Core test execution logic (matrix jobs) -- **Single Entry Point**: Use `cross-platform-test-manual.yml` for all manual testing +**Key Benefits:** +- **Single File**: No complex workflow chains or parameter passing issues +- **Unified Logic**: Same test matrix for all use cases +- **Smart Routing**: Automatically determines install method based on trigger type +- **Context-Aware**: Summary messages adapt to manual vs programmatic usage -### Parameter Requirements +### Trigger Types -⚠️ **Important**: When calling reusable workflows from `workflow_dispatch` triggers, **all parameters must be explicitly provided**, even optional ones with defaults. Missing optional parameters can cause workflows to be silently skipped. +**Manual (`workflow_dispatch`):** +- Simple boolean toggle: "Test from PyPI" vs "Test from Source" +- User-friendly parameter names +- Context-specific success/failure messages + +**Programmatic (`workflow_call`):** +- Full parameter control for CI/releases +- Backward compatible with existing workflows +- Always uses wheel installation method (tests built artifacts) + +### Implementation Details + +The workflow uses dynamic job conditions to route execution: -**Example of correct parameter passing:** ```yaml -uses: ./.github/workflows/cross-platform-test-shared.yml -with: - install-method: "wheel" - test-timeout: 5 - langflow-version: "" # ← Required even though optional - base-artifact-name: "dist" - main-artifact-name: "dist" - run-id: "" # ← Required even though optional +# Build only runs for source testing or when no artifacts provided +build-if-needed: + if: | + (github.event_name == 'workflow_dispatch' && inputs.test-from-pypi == false) || + (github.event_name == 'workflow_call' && (inputs.base-artifact-name == '' || inputs.main-artifact-name == '')) + +# Test matrix adapts install method based on trigger +test-installation: + steps: + - name: Determine install method + # workflow_dispatch: maps boolean to install method + # workflow_call: always uses wheel method + - name: Install from PyPI + if: steps.install-method.outputs.method == 'pypi' + - name: Install from wheels + if: steps.install-method.outputs.method == 'wheel' ``` ## Results diff --git a/.github/workflows/cross-platform-test.yml b/.github/workflows/cross-platform-test.yml index c03857352..62b62da59 100644 --- a/.github/workflows/cross-platform-test.yml +++ b/.github/workflows/cross-platform-test.yml @@ -1,6 +1,22 @@ name: Cross-Platform Installation Test on: + workflow_dispatch: + inputs: + test-from-pypi: + description: "Test from PyPI instead of building from source" + type: boolean + default: false + langflow-version: + description: "Langflow version to test from PyPI (leave empty for latest)" + required: false + type: string + default: "" + test-timeout: + description: "Timeout for langflow server startup test (minutes)" + required: false + type: number + default: 5 workflow_call: inputs: base-artifact-name: @@ -21,7 +37,9 @@ jobs: build-if-needed: name: Build Packages (if no artifacts provided) runs-on: ubuntu-latest - if: inputs.base-artifact-name == '' || inputs.main-artifact-name == '' + if: | + (github.event_name == 'workflow_dispatch' && inputs.test-from-pypi == false) || + (github.event_name == 'workflow_call' && (inputs.base-artifact-name == '' || inputs.main-artifact-name == '')) outputs: base-artifact-name: ${{ steps.set-names.outputs.base-artifact-name }} main-artifact-name: ${{ steps.set-names.outputs.main-artifact-name }} @@ -60,30 +78,319 @@ jobs: echo "base-artifact-name=adhoc-dist-base" >> $GITHUB_OUTPUT echo "main-artifact-name=adhoc-dist-main" >> $GITHUB_OUTPUT - test-wheel-installation: - name: Test Wheel Installation + test-installation: + name: Install & Run - ${{ matrix.os }} ${{ matrix.arch }} ${{ matrix.python-version }} needs: [build-if-needed] if: always() && (needs.build-if-needed.result == 'success' || needs.build-if-needed.result == 'skipped') - uses: ./.github/workflows/cross-platform-test-shared.yml - with: - install-method: "wheel" - test-timeout: ${{ inputs.test-timeout }} - langflow-version: "" - base-artifact-name: ${{ inputs.base-artifact-name || needs.build-if-needed.outputs.base-artifact-name }} - main-artifact-name: ${{ inputs.main-artifact-name || needs.build-if-needed.outputs.main-artifact-name }} - run-id: "" + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + # Linux AMD64 + - os: linux + arch: amd64 + runner: ubuntu-latest + python-version: "3.10" + - os: linux + arch: amd64 + runner: ubuntu-latest + python-version: "3.13" + # macOS AMD64 + - os: macos + arch: amd64 + runner: macos-13 + python-version: "3.10" + - os: macos + arch: amd64 + runner: macos-13 + python-version: "3.13" + # macOS ARM64 (Apple Silicon) + - os: macos + arch: arm64 + runner: macos-latest + python-version: "3.10" + - os: macos + arch: arm64 + runner: macos-latest + python-version: "3.13" + # Windows AMD64 + - os: windows + arch: amd64 + runner: windows-latest + python-version: "3.10" + - os: windows + arch: amd64 + runner: windows-latest + python-version: "3.12" + + steps: + - name: Determine install method + id: install-method + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + if [ "${{ inputs.test-from-pypi }}" = "true" ]; then + echo "method=pypi" >> $GITHUB_OUTPUT + else + echo "method=wheel" >> $GITHUB_OUTPUT + fi + else + # workflow_call always uses wheel method (backward compatibility) + echo "method=wheel" >> $GITHUB_OUTPUT + fi + shell: bash + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + architecture: ${{ matrix.arch == 'amd64' && 'x64' || matrix.arch }} + + - name: Setup UV + uses: astral-sh/setup-uv@v6 + with: + enable-cache: false + + # Download artifacts for wheel installation + - name: Download base package artifact + if: steps.install-method.outputs.method == 'wheel' + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.base-artifact-name || needs.build-if-needed.outputs.base-artifact-name || 'adhoc-dist-base' }} + path: ./base-dist + + - name: Download main package artifact + if: steps.install-method.outputs.method == 'wheel' + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.main-artifact-name || needs.build-if-needed.outputs.main-artifact-name || 'adhoc-dist-main' }} + path: ./main-dist + + - name: Create fresh virtual environment + run: | + uv venv test-env --seed + shell: bash + + # Wheel installation steps + - name: Install base package from wheel (Windows) + if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows' + run: | + ls -la ./base-dist/ + find ./base-dist -name "*.whl" -type f + WHEEL_FILE=$(find ./base-dist -name "*.whl" -type f | head -1) + if [ -n "$WHEEL_FILE" ]; then + uv pip install --python ./test-env/Scripts/python.exe "$WHEEL_FILE" + else + echo "No wheel file found in ./base-dist/" + exit 1 + fi + shell: bash + + - name: Install main package from wheel (Windows) + if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows' + run: | + ls -la ./main-dist/ + find ./main-dist -name "*.whl" -type f + WHEEL_FILE=$(find ./main-dist -name "*.whl" -type f | head -1) + if [ -n "$WHEEL_FILE" ]; then + uv pip install --python ./test-env/Scripts/python.exe "$WHEEL_FILE" + else + echo "No wheel file found in ./main-dist/" + exit 1 + fi + shell: bash + + - name: Install base package from wheel (Unix) + if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows' + run: | + ls -la ./base-dist/ + find ./base-dist -name "*.whl" -type f + WHEEL_FILE=$(find ./base-dist -name "*.whl" -type f | head -1) + if [ -n "$WHEEL_FILE" ]; then + uv pip install --python ./test-env/bin/python "$WHEEL_FILE" + else + echo "No wheel file found in ./base-dist/" + exit 1 + fi + shell: bash + + - name: Install main package from wheel (Unix) + if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows' + run: | + ls -la ./main-dist/ + find ./main-dist -name "*.whl" -type f + WHEEL_FILE=$(find ./main-dist -name "*.whl" -type f | head -1) + if [ -n "$WHEEL_FILE" ]; then + uv pip install --python ./test-env/bin/python "$WHEEL_FILE" + else + echo "No wheel file found in ./main-dist/" + exit 1 + fi + shell: bash + + # PyPI installation steps + - name: Install langflow from PyPI (Windows) + if: steps.install-method.outputs.method == 'pypi' && matrix.os == 'windows' + run: | + if [ -n "${{ inputs.langflow-version }}" ]; then + uv pip install --python ./test-env/Scripts/python.exe langflow==${{ inputs.langflow-version }} + else + uv pip install --python ./test-env/Scripts/python.exe langflow + fi + shell: bash + + - name: Install langflow from PyPI (Unix) + if: steps.install-method.outputs.method == 'pypi' && matrix.os != 'windows' + run: | + if [ -n "${{ inputs.langflow-version }}" ]; then + uv pip install --python ./test-env/bin/python langflow==${{ inputs.langflow-version }} + else + uv pip install --python ./test-env/bin/python langflow + fi + shell: bash + + # Install additional dependencies + - name: Install additional dependencies (Windows) + if: matrix.os == 'windows' + run: | + uv pip install --python ./test-env/Scripts/python.exe openai + shell: bash + + - name: Install additional dependencies (Unix) + if: matrix.os != 'windows' + run: | + uv pip install --python ./test-env/bin/python openai + shell: bash + + # Test steps + - name: Test CLI help command (Windows) + if: matrix.os == 'windows' + run: | + test-env\Scripts\python.exe -m langflow --help + shell: cmd + + - name: Test CLI help command (Unix) + if: matrix.os != 'windows' + run: | + ./test-env/bin/python -m langflow --help + shell: bash + + - name: Test server startup (Windows) + if: matrix.os == 'windows' + timeout-minutes: ${{ inputs.test-timeout }} + run: | + # Start server in background + $serverProcess = Start-Process -FilePath ".\test-env\Scripts\python.exe" -ArgumentList "-m", "langflow", "run", "--host", "localhost", "--port", "7860", "--backend-only" -PassThru -WindowStyle Hidden + + # Wait for server to be ready + $timeoutSeconds = ${{ inputs.test-timeout }} * 60 + $elapsed = 0 + do { + try { + $response = Invoke-WebRequest -Uri "http://localhost:7860/health_check" -UseBasicParsing -TimeoutSec 5 + if ($response.StatusCode -eq 200) { + Write-Host "✅ Server is ready on ${{ matrix.os }}-${{ matrix.arch }}" + break + } + } catch { + Start-Sleep -Seconds 5 + $elapsed += 5 + } + } while ($elapsed -lt $timeoutSeconds) + + if ($elapsed -ge $timeoutSeconds) { + Write-Host "❌ Server failed to start within timeout on ${{ matrix.os }}-${{ matrix.arch }}" + exit 1 + } + + # Stop the server process + Stop-Process -Id $serverProcess.Id -Force -ErrorAction SilentlyContinue + shell: powershell + + - name: Test server startup (Unix) + if: matrix.os != 'windows' + timeout-minutes: ${{ inputs.test-timeout }} + run: | + # Start server in background + ./test-env/bin/python -m langflow run --host localhost --port 7860 --backend-only & + SERVER_PID=$! + + # Wait for server to be ready (using bash loop instead of timeout command) + TIMEOUT_SECONDS=$((${{ inputs.test-timeout }} * 60)) + ELAPSED=0 + while [ $ELAPSED -lt $TIMEOUT_SECONDS ]; do + if curl -f http://localhost:7860/health_check >/dev/null 2>&1; then + echo "✅ Server is ready on ${{ matrix.os }}-${{ matrix.arch }}" + break + fi + sleep 5 + ELAPSED=$((ELAPSED + 5)) + done + + if [ $ELAPSED -ge $TIMEOUT_SECONDS ]; then + echo "❌ Server failed to start within timeout on ${{ matrix.os }}-${{ matrix.arch }}" + kill $SERVER_PID 2>/dev/null || true + exit 1 + fi + + # Clean shutdown + kill $SERVER_PID 2>/dev/null || true + sleep 5 + shell: bash + + - name: Test import in Python (Windows) + if: matrix.os == 'windows' + run: | + test-env\Scripts\python.exe -c " + try: + import langflow + print('✅ langflow import successful on ${{ matrix.os }}-${{ matrix.arch }}') + except Exception as e: + print(f'❌ langflow import failed on ${{ matrix.os }}-${{ matrix.arch }}: {e}') + exit(1) + " + shell: cmd + + - name: Test import in Python (Unix) + if: matrix.os != 'windows' + run: | + ./test-env/bin/python -c " + try: + import langflow + print('✅ langflow import successful on ${{ matrix.os }}-${{ matrix.arch }}') + except Exception as e: + print(f'❌ langflow import failed on ${{ matrix.os }}-${{ matrix.arch }}: {e}') + exit(1) + " + shell: bash test-summary: name: Cross-Platform Test Summary - needs: test-wheel-installation + needs: test-installation runs-on: ubuntu-latest if: always() steps: - name: Check test results run: | - if [ "${{ needs.test-wheel-installation.result }}" != "success" ]; then - echo "❌ Cross-platform tests failed - PyPI upload blocked" + if [ "${{ needs.test-installation.result }}" != "success" ]; then + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + if [ "${{ inputs.test-from-pypi }}" = "true" ]; then + echo "❌ PyPI installation tests failed" + else + echo "❌ Source build and installation tests failed" + fi + else + echo "❌ Cross-platform tests failed - PyPI upload blocked" + fi exit 1 else - echo "✅ All cross-platform tests passed - PyPI upload can proceed" + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + if [ "${{ inputs.test-from-pypi }}" = "true" ]; then + echo "✅ PyPI installation tests passed" + else + echo "✅ Source build and installation tests passed" + fi + else + echo "✅ All cross-platform tests passed - PyPI upload can proceed" + fi fi \ No newline at end of file