From fb547a095613f0b5e0cb5010d34b1a0a21ec520a Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:16:06 +0000 Subject: [PATCH] ci: finish the lint, unit and smoke check layout (#45706) * ci: delete legacy required-checks shim workflow * ci: fold semgrep into lint and helm into unit, drop the duplicate UI build semgrep runs the repo's own rules over the source, so it becomes lint / semgrep behind lint passed. The helm chart tests become unit / helm behind unit passed. UI Build Check ran the same docker build --target ui-builder as smoke / dashboard-build, so it is deleted * ci: merge the alphabetical llms shards into one llms-providers shard The two shards were split by letter range, which says nothing about what they run. Every provider without its own shard now runs in llms-providers, with the same 137 paths as before --------- Co-authored-by: yuneng --- .github/workflows/helm_unit_test.yml | 54 --- .github/workflows/required-checks-legacy.yml | 397 ------------------- .github/workflows/test-linting.yml | 25 +- .github/workflows/test-litellm-ui-build.yml | 40 -- .github/workflows/test-semgrep.yml | 37 -- .github/workflows/test-unit.yml | 57 ++- 6 files changed, 69 insertions(+), 541 deletions(-) delete mode 100644 .github/workflows/helm_unit_test.yml delete mode 100644 .github/workflows/required-checks-legacy.yml delete mode 100644 .github/workflows/test-litellm-ui-build.yml delete mode 100644 .github/workflows/test-semgrep.yml diff --git a/.github/workflows/helm_unit_test.yml b/.github/workflows/helm_unit_test.yml deleted file mode 100644 index f95848945a0..00000000000 --- a/.github/workflows/helm_unit_test.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Helm unit test - -on: - pull_request: - push: - branches: - - main - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - unit-test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - persist-credentials: false - - - name: Set up Helm 3.11.1 - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 - with: - version: "3.11.1" - - - name: Download and verify Helm Unit Test Plugin - run: | - curl -fsSLo "$RUNNER_TEMP/helm-unittest.tgz" https://github.com/helm-unittest/helm-unittest/releases/download/v0.8.2/helm-unittest-linux-amd64-0.8.2.tgz - echo "56ab3091e6fa52a7c92ee951def9bed957f295d9ce98483aed404e748d7b3a94 $RUNNER_TEMP/helm-unittest.tgz" | sha256sum -c - - - - name: Install Helm Unit Test Plugin - run: | - PLUGIN_DIR="$(helm env HELM_PLUGINS)/helm-unittest" - mkdir -p "$PLUGIN_DIR" - tar -xzf "$RUNNER_TEMP/helm-unittest.tgz" -C "$PLUGIN_DIR" - helm plugin list - - - name: Run unit tests - run: | - for chart in helm/litellm-helm helm/litellm; do - declared="$(grep -h '^suite:' "$chart"/tests/*.yaml | wc -l | tr -d '[:space:]')" - output="$(mktemp)" - helm unittest -f 'tests/*.yaml' "$chart" | tee "$output" - executed="$(sed -n 's/^Test Suites:.*[[:space:]]\([0-9][0-9]*\) total$/\1/p' "$output")" - if [ "$declared" != "$executed" ]; then - echo "::error::$chart declares $declared test suites but helm-unittest ran $executed. Suites are being skipped silently, so their assertions never execute." - exit 1 - fi - echo "$chart: all $declared declared test suites ran" - done diff --git a/.github/workflows/required-checks-legacy.yml b/.github/workflows/required-checks-legacy.yml deleted file mode 100644 index 6bba690bd8a..00000000000 --- a/.github/workflows/required-checks-legacy.yml +++ /dev/null @@ -1,397 +0,0 @@ -name: Required checks (legacy) - -on: - pull_request: - branches: - - main - - "litellm_**" - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -jobs: - lint: - name: lint - permissions: - contents: read - pull-requests: read - actions: read - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 1 - clean: true - persist-credentials: false - - - name: Detect relevant changes - id: changes - uses: ./.github/actions/detect-changes - - - name: Fetch gate base (merge-base with target branch) - if: steps.changes.outputs.decision != 'skip' - env: - GH_TOKEN: ${{ github.token }} - BASE_SHA: ${{ github.event.pull_request.base.sha }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} - run: | - retry() { "$@" || { sleep 15; "$@"; } || { sleep 30; "$@"; }; } - MERGE_BASE=$(retry gh api "repos/${{ github.repository }}/compare/${BASE_SHA}...${HEAD_SHA}?per_page=1" --jq '.merge_base_commit.sha') - test -n "$MERGE_BASE" - retry git fetch --no-tags --depth=1 origin "$MERGE_BASE" - echo "GATE_BASE_SHA=$MERGE_BASE" >> "$GITHUB_ENV" - - - name: Set up Python - if: steps.changes.outputs.decision != 'skip' - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: "3.12" - - - name: Set up uv - if: steps.changes.outputs.decision != 'skip' - uses: ./.github/actions/setup-uv-with-retries - with: - version: "0.10.9" - - - name: Cache uv dependencies - if: steps.changes.outputs.decision != 'skip' - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: | - ~/.cache/uv - .venv - key: ${{ runner.os }}-uv-lint-${{ hashFiles('uv.lock') }} - restore-keys: | - ${{ runner.os }}-uv-lint- - - - name: Clean Python cache - if: steps.changes.outputs.decision != 'skip' - run: | - find . -type d -name "__pycache__" -exec rm -rf {} + || true - find . -name "*.pyc" -delete || true - - - name: Check uv.lock is up to date - if: steps.changes.outputs.decision != 'skip' - run: | - uv lock --check || (echo "❌ uv.lock is out of sync with pyproject.toml. Run 'uv lock' locally and commit the result." && exit 1) - - - name: Cache the Rust build - if: steps.changes.outputs.decision != 'skip' - uses: ./.github/actions/cache-cargo-build - - - name: Install dependencies - if: steps.changes.outputs.decision != 'skip' - run: | - uv sync --frozen --group proxy-dev --group e2e-dev - - - name: Cache Prisma binaries - if: steps.changes.outputs.decision != 'skip' - uses: ./.github/actions/cache-prisma-binaries - - - name: Generate Prisma client - if: steps.changes.outputs.decision != 'skip' - run: | - uv run --no-sync prisma generate --schema litellm/proxy/schema.prisma - - - name: Check ruff format - if: steps.changes.outputs.decision != 'skip' - run: | - git diff --name-only --diff-filter=ACMR "$GATE_BASE_SHA" HEAD -- ':(glob)litellm/**/*.py' | grep -v '^litellm/enterprise/' > "$RUNNER_TEMP/ruff_format_files.txt" || true - if [ ! -s "$RUNNER_TEMP/ruff_format_files.txt" ]; then - echo "No changed litellm Python files to check with ruff format." - exit 0 - fi - xargs uv run --no-sync ruff format --check --exclude '/enterprise/' < "$RUNNER_TEMP/ruff_format_files.txt" - - - name: Debug - Check file state - if: steps.changes.outputs.decision != 'skip' - run: | - echo "Current branch:" - git branch --show-current - echo "Last 3 commits:" - git log --oneline -3 - echo "File content around line 43:" - head -50 litellm/litellm_core_utils/custom_logger_registry.py | tail -10 - - - name: Check MCP operation boundary - if: steps.changes.outputs.decision != 'skip' - run: uv run --no-sync python scripts/check_mcp_operation_boundary.py - - - name: Run Ruff linting - if: steps.changes.outputs.decision != 'skip' - run: | - cd litellm - uv run --no-sync ruff check . - cd .. - - - name: Run Ruff linting (test tree) - if: steps.changes.outputs.decision != 'skip' - run: | - uv run --no-sync ruff check --config ruff-tests.toml tests - - - name: Check strict ruff rules (delta vs merge-base counts) - if: steps.changes.outputs.decision != 'skip' - env: - GH_TOKEN: ${{ github.token }} - run: | - uv run --no-sync python scripts/ruff_strict_gate.py --base "$GATE_BASE_SHA" - - - name: Check type discipline (mutable collections / casts / type guards / kwargs / unexplained suppressions, delta vs merge-base counts) - if: steps.changes.outputs.decision != 'skip' - env: - GH_TOKEN: ${{ github.token }} - run: | - uv run --no-sync python scripts/type_discipline_gate.py --base "$GATE_BASE_SHA" - - - name: Check test quality (zero-assert / mock-echo tests, sys.path.insert, raw env writes, litellm global mutation, credential-gated skips, conftest snapshot inventory, delta vs merge-base counts) - if: steps.changes.outputs.decision != 'skip' - env: - GH_TOKEN: ${{ github.token }} - run: | - uv run --no-sync python scripts/test_quality_gate.py --base "$GATE_BASE_SHA" - - - name: Print OpenAI version - if: steps.changes.outputs.decision != 'skip' - run: | - uv run --no-sync python -c "import openai; print(f'OpenAI version: {openai.__version__}')" - - - name: Check basedpyright (delta vs merge-base counts) - if: steps.changes.outputs.decision != 'skip' - env: - GH_TOKEN: ${{ github.token }} - run: | - uv run --no-sync python scripts/type_check_gate.py --base "$GATE_BASE_SHA" - - - name: Check tests/e2e basedpyright (zero errors) - if: steps.changes.outputs.decision != 'skip' - run: | - if git diff --name-only --diff-filter=ACMRD "$GATE_BASE_SHA" HEAD -- ':(glob)tests/e2e/**/*.py' ':(glob)tests/e2e_harness/**/*.py' pyrightconfig.json | grep -q .; then - uv run --no-sync basedpyright tests/e2e tests/e2e_harness - else - echo "No changed tests/e2e Python files; skipping." - fi - - - name: Run the e2e harness tests - if: steps.changes.outputs.decision != 'skip' - env: - LITELLM_MASTER_KEY: sk-e2e-harness-tests-reach-no-proxy - run: | - if ! git diff --name-only --diff-filter=ACMRD "$GATE_BASE_SHA" HEAD -- tests/e2e tests/e2e_harness ':(exclude)tests/e2e/ui' pyproject.toml uv.lock .github/workflows/test-linting.yml | grep -q .; then - echo "No changed e2e harness files; skipping." - exit 0 - fi - retry() { "$@" || { sleep 15; "$@"; } || { sleep 30; "$@"; }; } - CLAUDE_VERSION="$(retry uv run --no-sync python tests/e2e/claude_code/pr_gate_version_resolver.py)" - tests/e2e/claude_code/cron_vm/install_claude_code.sh "$CLAUDE_VERSION" "$RUNNER_TEMP/claude-cli" - PATH="$RUNNER_TEMP/claude-cli:$PATH" uv run --no-sync pytest -q tests/e2e_harness - - - name: Check for circular imports - if: steps.changes.outputs.decision != 'skip' - run: | - cd litellm - uv run --no-sync python ../tests/documentation_tests/test_circular_imports.py - cd .. - - - name: Check import safety - if: steps.changes.outputs.decision != 'skip' - run: | - uv run --no-sync python -c "from litellm import *" || (echo '🚨 import failed, this means you introduced unprotected imports! 🚨'; exit 1) - - frontend-lint: - name: frontend-lint - permissions: - contents: read - runs-on: ubuntu-latest - timeout-minutes: 8 - defaults: - run: - working-directory: ui/litellm-dashboard - - steps: - - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - fetch-depth: 1 - persist-credentials: false - - - name: Collect changed files - id: changed - env: - GH_TOKEN: ${{ github.token }} - BASE_SHA: ${{ github.event.pull_request.base.sha }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} - run: | - merge_base=$(gh api "repos/${{ github.repository }}/compare/${BASE_SHA}...${HEAD_SHA}?per_page=1" --jq '.merge_base_commit.sha') - test -n "$merge_base" - git fetch --no-tags --depth=1 origin "$merge_base" "$HEAD_SHA" - : > "$RUNNER_TEMP/prettier_files.txt" - : > "$RUNNER_TEMP/eslint_files.txt" - while IFS= read -r f; do - [ -f "$f" ] || continue - case "$f" in - *.js | *.jsx | *.ts | *.tsx | *.mjs | *.cjs) - printf '%s\n' "$f" >> "$RUNNER_TEMP/prettier_files.txt" - printf '%s\n' "$f" >> "$RUNNER_TEMP/eslint_files.txt" ;; - *.json | *.css | *.scss | *.md | *.mdx | *.yml | *.yaml | *.html) - printf '%s\n' "$f" >> "$RUNNER_TEMP/prettier_files.txt" ;; - esac - done < <(git diff --name-only --diff-filter=ACMR --relative "$merge_base" "$HEAD_SHA" -- .) - if [ -s "$RUNNER_TEMP/prettier_files.txt" ] || [ -s "$RUNNER_TEMP/eslint_files.txt" ]; then - echo "has_files=true" >> "$GITHUB_OUTPUT" - else - echo "has_files=false" >> "$GITHUB_OUTPUT" - echo "No lintable UI files changed in this PR; nothing to check." - fi - - - name: Setup Node.js - if: steps.changed.outputs.has_files == 'true' - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 - with: - node-version-file: ui/litellm-dashboard/.nvmrc - cache: "npm" - cache-dependency-path: ui/litellm-dashboard/package-lock.json - - - name: Install dependencies - if: steps.changed.outputs.has_files == 'true' - run: npm ci - - - name: Lint changed files (prettier + eslint) - if: steps.changed.outputs.has_files == 'true' - run: | - prettier_files=() - eslint_files=() - while IFS= read -r f; do prettier_files+=("$f"); done < "$RUNNER_TEMP/prettier_files.txt" - while IFS= read -r f; do eslint_files+=("$f"); done < "$RUNNER_TEMP/eslint_files.txt" - status=0 - if [ ${#prettier_files[@]} -gt 0 ]; then - echo "::group::Prettier (${#prettier_files[@]} files)" - npx prettier --check "${prettier_files[@]}" || { status=1; echo "::error::Unformatted files. Fix with: npm run format"; } - echo "::endgroup::" - fi - if [ ${#eslint_files[@]} -gt 0 ]; then - echo "::group::ESLint (${#eslint_files[@]} files)" - npx eslint --no-warn-ignored --pass-on-unpruned-suppressions "${eslint_files[@]}" || status=1 - echo "::endgroup::" - fi - exit $status - - - name: Check lint budgets - if: ${{ !cancelled() && steps.changed.outputs.has_files == 'true' }} - run: | - npx eslint . -f json -o "$RUNNER_TEMP/lint-report.json" || true - node scripts/check-lint-budgets.mjs "$RUNNER_TEMP/lint-report.json" eslint-budgets.json - - - name: Check for dead code (knip) - if: ${{ !cancelled() && steps.changed.outputs.has_files == 'true' }} - run: npm run knip:ci - - assert-ci-coverage: - name: assert-ci-coverage - permissions: - contents: read - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: "3.12" - - - name: Assert every test file and Dockerfile is invoked by a job - run: | - python -m pip install "pyyaml==6.0.3" - python .github/scripts/assert_ci_coverage.py - - - name: Assert no -k expression deselects a file from every job that globs it - run: python .github/scripts/assert_ci_coverage.py --slices - - - name: Assert .github/workflows/ holds only workflows, correctly named - run: python .github/scripts/assert_workflow_dir_hygiene.py - - dashboard-build: - name: Dashboard build - runs-on: ubuntu-24.04 - timeout-minutes: 30 - steps: - - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - persist-credentials: false - - - name: Build the dashboard stage - run: docker build --target ui-builder -f Dockerfile . - - core-checks: - name: Core checks (Python ${{ matrix.python-version }}) - runs-on: ubuntu-24.04 - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] - env: - LITELLM_LOCAL_MODEL_COST_MAP: "True" - steps: - - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: ${{ matrix.python-version }} - - - name: Set up uv - uses: ./.github/actions/setup-uv-with-retries - with: - version: "0.10.9" - - - name: Install dependencies - run: .github/scripts/uv_sync_with_retries.sh --frozen --extra proxy --extra cli --group dev --group proxy-dev --python ${{ matrix.python-version }} - - - name: Create the loopback-only network namespace - run: | - sudo ip netns add smoke - sudo ip netns exec smoke ip link set lo up - cat > "${RUNNER_TEMP}/in-netns" <<'WRAP' - #!/usr/bin/env bash - set -euo pipefail - exec sudo --preserve-env=LITELLM_LOCAL_MODEL_COST_MAP ip netns exec smoke setpriv --reuid "$(id -u)" --regid "$(id -g)" --init-groups -- env HOME="${HOME}" PATH="${PATH}" "$@" - WRAP - chmod +x "${RUNNER_TEMP}/in-netns" - echo "IN_NETNS=${RUNNER_TEMP}/in-netns" >> "${GITHUB_ENV}" - - - name: Verify namespace isolation - run: $IN_NETNS .venv/bin/python .github/scripts/run_merge_smoke.py verify-isolation - - - name: Verify interpreter version - run: $IN_NETNS .venv/bin/python .github/scripts/run_merge_smoke.py interpreter --expect ${{ matrix.python-version }} - - - name: Import and CLI checks - run: $IN_NETNS .venv/bin/python .github/scripts/run_merge_smoke.py cli - - - name: Proxy startup check - run: $IN_NETNS .venv/bin/python .github/scripts/run_merge_smoke.py proxy-startup --diagnostics-dir "${RUNNER_TEMP}/smoke-diagnostics" - - - name: Upload smoke diagnostics - if: always() - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 - with: - name: merge-smoke-diagnostics-py${{ matrix.python-version }} - path: ${{ runner.temp }}/smoke-diagnostics - if-no-files-found: ignore - - - name: Remove the network namespace - if: always() - run: sudo ip netns delete smoke diff --git a/.github/workflows/test-linting.yml b/.github/workflows/test-linting.yml index f00e9f28516..e85aaacd3f8 100644 --- a/.github/workflows/test-linting.yml +++ b/.github/workflows/test-linting.yml @@ -645,9 +645,32 @@ jobs: - name: Assert .github/workflows/ holds only workflows, correctly named run: python .github/scripts/assert_workflow_dir_hygiene.py + semgrep: + name: semgrep + permissions: + contents: read + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.12" + + - name: Set up uv + uses: ./.github/actions/setup-uv-with-retries + with: + version: "0.10.9" + + - name: Run Semgrep (custom rules) + run: uv tool run --from 'semgrep==1.157.0' semgrep scan --config .semgrep/rules . --error lint-passed: name: lint passed - needs: [python, secret-scan, ui, ui-api-types, code-quality, ci-coverage] + needs: [python, secret-scan, ui, ui-api-types, code-quality, ci-coverage, semgrep] if: always() runs-on: ubuntu-latest timeout-minutes: 2 diff --git a/.github/workflows/test-litellm-ui-build.yml b/.github/workflows/test-litellm-ui-build.yml deleted file mode 100644 index eace78fc2cb..00000000000 --- a/.github/workflows/test-litellm-ui-build.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: UI Build Check -permissions: - contents: read - pull-requests: read - -on: - pull_request: - branches: - - main - - "litellm_**" - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - build-ui: - runs-on: ubuntu-latest - timeout-minutes: 10 - - steps: - - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - persist-credentials: false - - - name: Detect relevant changes - id: changes - uses: ./.github/actions/detect-changes - with: - category: ui - - # Built through the image stage rather than the checkout, because the - # stage copies ui/litellm-dashboard/ alone: an import reaching above the - # dashboard root resolves in a checkout and fails in every image we ship. - # Dockerfile, docker/Dockerfile.non_root and ui/Dockerfile share this - # stage verbatim, so building one covers all three. - - name: Build the dashboard as the shipped images build it - if: steps.changes.outputs.decision != 'skip' - run: docker build --target ui-builder -f Dockerfile . diff --git a/.github/workflows/test-semgrep.yml b/.github/workflows/test-semgrep.yml deleted file mode 100644 index d375824e3c9..00000000000 --- a/.github/workflows/test-semgrep.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Semgrep - -on: - pull_request: - branches: - - main - - "litellm_**" - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - semgrep: - runs-on: ubuntu-latest - timeout-minutes: 10 - - steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: "3.12" - - - name: Set up uv - uses: ./.github/actions/setup-uv-with-retries - with: - version: "0.10.9" - - - name: Run Semgrep (custom rules) - run: uv tool run --from 'semgrep==1.157.0' semgrep scan --config .semgrep/rules . --error diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 7ea3ed691a9..1cf79049a66 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -214,7 +214,7 @@ jobs: job-timeout-minutes: 60 dist: loadscope - - shard: llms-a-to-g + - shard: llms-providers test-path: |- tests/unit/llms/test_cache_control_and_reasoning.py tests/unit/llms/test_custom_llm.py @@ -272,16 +272,6 @@ jobs: tests/unit/llms/github_copilot tests/unit/llms/gradient_ai tests/unit/llms/groq - --ignore=tests/unit/llms/base_llm/batches/base_batches_config_test.py - python-version: "3.12" - workers: 4 - reruns: 0 - timeout-minutes: 20 - job-timeout-minutes: 60 - dist: loadscope - - - shard: llms-h-to-z - test-path: |- tests/unit/llms/heroku tests/unit/llms/hosted_vllm tests/unit/llms/huggingface @@ -362,6 +352,7 @@ jobs: tests/unit/llms/xinference tests/unit/llms/you_com tests/unit/llms/zai + --ignore=tests/unit/llms/base_llm/batches/base_batches_config_test.py python-version: "3.12" workers: 4 reruns: 0 @@ -1235,6 +1226,48 @@ jobs: uv run --no-sync python ./tests/documentation_tests/test_router_settings.py uv run --no-sync python ./tests/documentation_tests/test_api_docs.py uv run --no-sync python ./tests/documentation_tests/test_circular_imports.py + helm: + name: helm + permissions: + contents: read + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + with: + persist-credentials: false + + - name: Set up Helm 3.11.1 + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 + with: + version: "3.11.1" + + - name: Download and verify Helm Unit Test Plugin + run: | + curl -fsSLo "$RUNNER_TEMP/helm-unittest.tgz" https://github.com/helm-unittest/helm-unittest/releases/download/v0.8.2/helm-unittest-linux-amd64-0.8.2.tgz + echo "56ab3091e6fa52a7c92ee951def9bed957f295d9ce98483aed404e748d7b3a94 $RUNNER_TEMP/helm-unittest.tgz" | sha256sum -c - + + - name: Install Helm Unit Test Plugin + run: | + PLUGIN_DIR="$(helm env HELM_PLUGINS)/helm-unittest" + mkdir -p "$PLUGIN_DIR" + tar -xzf "$RUNNER_TEMP/helm-unittest.tgz" -C "$PLUGIN_DIR" + helm plugin list + + - name: Run unit tests + run: | + for chart in helm/litellm-helm helm/litellm; do + declared="$(grep -h '^suite:' "$chart"/tests/*.yaml | wc -l | tr -d '[:space:]')" + output="$(mktemp)" + helm unittest -f 'tests/*.yaml' "$chart" | tee "$output" + executed="$(sed -n 's/^Test Suites:.*[[:space:]]\([0-9][0-9]*\) total$/\1/p' "$output")" + if [ "$declared" != "$executed" ]; then + echo "::error::$chart declares $declared test suites but helm-unittest ran $executed. Suites are being skipped silently, so their assertions never execute." + exit 1 + fi + echo "$chart: all $declared declared test suites ran" + done coverage: name: coverage needs: unit @@ -1283,7 +1316,7 @@ jobs: unit-passed: name: unit passed permissions: {} - needs: [rust-bridge, assert-shard-coverage, unit, ui-unit, docs] + needs: [rust-bridge, assert-shard-coverage, unit, ui-unit, docs, helm] if: always() runs-on: ubuntu-latest timeout-minutes: 2