Integration Tests #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Integration Tests | |
| on: | |
| # Run after CI or Release workflow completes | |
| # - CI on main: catches issues before release (uses 'latest' image) | |
| # - Release on tags: tests the actual released image (uses tag like 'v0.6.0') | |
| workflow_run: | |
| workflows: ["CI", "Release"] | |
| types: | |
| - completed | |
| # Manual trigger for testing specific protocols/tests | |
| workflow_dispatch: | |
| inputs: | |
| protocol: | |
| description: 'Protocol to test' | |
| required: false | |
| default: 'all' | |
| type: choice | |
| options: | |
| - all | |
| - nfs | |
| - nvmeof | |
| - iscsi | |
| - smb | |
| test: | |
| description: 'Specific test to run (leave empty for all tests of selected protocol)' | |
| required: false | |
| default: '' | |
| type: choice | |
| options: | |
| - '' | |
| - basic | |
| - snapshot | |
| - detached-snapshot | |
| - concurrent | |
| - persistence | |
| - statefulset | |
| - volume-expansion | |
| - zfsprops | |
| - name-templating | |
| - clone-volume | |
| - delete-strategy-retain | |
| - block-mode | |
| - snapshot-stress | |
| - dual-mount | |
| - connection-resilience | |
| - pod-restart | |
| - access-modes | |
| - snapshot-restore | |
| - volume-stress | |
| - error-handling | |
| - encryption | |
| pr: | |
| description: 'PR number to test (builds image from PR code, leave empty for normal run)' | |
| required: false | |
| default: '' | |
| type: string | |
| # Restrict permissions to minimum required | |
| permissions: | |
| contents: read | |
| # Ensure only one integration test runs at a time on the new runner | |
| concurrency: | |
| group: integration-tests | |
| cancel-in-progress: false | |
| jobs: | |
| # ========================================== | |
| # Setup Phase | |
| # ========================================== | |
| # Check if driver files changed (skip integration tests for plugin-only changes) | |
| check-driver-changes: | |
| name: Check Driver Changes | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'workflow_run' | |
| outputs: | |
| should_run: ${{ steps.check.outputs.should_run }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| fetch-depth: 2 | |
| - name: Check for driver changes | |
| id: check | |
| run: | | |
| # Get changed files in the commit | |
| CHANGED=$(git diff --name-only HEAD^..HEAD 2>/dev/null || echo "") | |
| echo "Changed files: $CHANGED" | |
| # Check if any driver-related files changed (excludes docs/, README, tests/, etc.) | |
| if echo "$CHANGED" | grep -qE '^(cmd/nasty-csi-driver/|pkg/|Dockerfile|go\.(mod|sum)|charts/)'; then | |
| echo "Driver files changed - will run integration tests" | |
| echo "should_run=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No driver files changed - skipping integration tests" | |
| echo "should_run=false" >> $GITHUB_OUTPUT | |
| fi | |
| compute-tag: | |
| name: Compute Image Tag | |
| runs-on: new | |
| needs: check-driver-changes | |
| # Skip if: | |
| # - triggered by workflow_run and the workflow failed | |
| # - triggered by CI workflow but not on main branch (only run integration tests for main) | |
| # - no driver files changed (plugin-only changes) | |
| # Note: always() allows this job to run even when check-driver-changes is skipped (manual dispatch) | |
| if: | | |
| always() && | |
| (github.event_name != 'workflow_run' || | |
| (github.event.workflow_run.conclusion == 'success' && | |
| (github.event.workflow_run.name == 'Release' || github.event.workflow_run.head_branch == 'main'))) && | |
| (needs.check-driver-changes.outputs.should_run != 'false') | |
| outputs: | |
| image_tag: ${{ steps.tag.outputs.tag }} | |
| steps: | |
| # PR mode: checkout PR code and build image locally | |
| - name: Checkout PR code | |
| if: inputs.pr != '' | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: refs/pull/${{ inputs.pr }}/merge | |
| - name: Set up Docker Buildx | |
| if: inputs.pr != '' | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build PR image | |
| if: inputs.pr != '' | |
| run: | | |
| PR_TAG="pr-${{ inputs.pr }}" | |
| IMAGE="ghcr.io/fenio/tns-csi:${PR_TAG}" | |
| echo "=== Building image from PR #${{ inputs.pr }} ===" | |
| docker buildx build \ | |
| --load \ | |
| --build-arg VERSION="0.0.0-${PR_TAG}" \ | |
| --build-arg GIT_COMMIT="$(git rev-parse --short HEAD)" \ | |
| --build-arg BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ | |
| -t "${IMAGE}" . | |
| docker save "${IMAGE}" -o /tmp/tns-csi-pr-image.tar | |
| echo "Image saved to /tmp/tns-csi-pr-image.tar" | |
| - name: Upload PR image artifact | |
| if: inputs.pr != '' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: pr-image | |
| path: /tmp/tns-csi-pr-image.tar | |
| retention-days: 1 | |
| - name: Compute image tag from ref | |
| id: tag | |
| env: | |
| # Use environment variables to safely pass workflow_run context | |
| # This prevents shell injection via malicious branch names | |
| WORKFLOW_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} | |
| WORKFLOW_EVENT: ${{ github.event.workflow_run.event }} | |
| WORKFLOW_NAME: ${{ github.event.workflow_run.name }} | |
| run: | | |
| # PR mode: use the PR-specific tag | |
| if [[ -n "${{ inputs.pr }}" ]]; then | |
| TAG="pr-${{ inputs.pr }}" | |
| echo "Using PR image tag: $TAG" | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # For workflow_run events, get ref from the triggering workflow | |
| if [ "${{ github.event_name }}" = "workflow_run" ]; then | |
| REF_NAME="$WORKFLOW_HEAD_BRANCH" | |
| # Check if it was triggered by a tag (Release workflow) | |
| if [ "$WORKFLOW_EVENT" = "push" ] && [[ "$WORKFLOW_HEAD_BRANCH" == v* ]]; then | |
| REF_TYPE="tag" | |
| else | |
| REF_TYPE="branch" | |
| fi | |
| echo "Triggered by workflow: $WORKFLOW_NAME" | |
| else | |
| # For manual dispatch or other events | |
| REF_NAME="${{ github.ref_name }}" | |
| REF_TYPE="${{ github.ref_type }}" | |
| fi | |
| echo "REF_NAME: $REF_NAME, REF_TYPE: $REF_TYPE" | |
| if [ "$REF_TYPE" = "tag" ]; then | |
| # For tags like v0.5.0, strip the 'v' prefix to match Docker image tags | |
| # Release workflow uses semver pattern which produces tags like 0.5.0 (without v) | |
| TAG="${REF_NAME#v}" | |
| echo "Using image tag: $TAG (from tag: $REF_NAME)" | |
| elif [ "$REF_NAME" = "main" ]; then | |
| TAG="latest" | |
| echo "Using image tag: $TAG (from branch: $REF_NAME)" | |
| else | |
| TAG=$(echo "$REF_NAME" | sed 's/\//-/g') | |
| echo "Using image tag: $TAG (from branch: $REF_NAME)" | |
| fi | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| # ========================================== | |
| # E2E Tests | |
| # ========================================== | |
| ginkgo-e2e-nfs: | |
| name: "E2E: NFS" | |
| runs-on: new | |
| timeout-minutes: 60 | |
| needs: compute-tag | |
| if: | | |
| !cancelled() && needs.compute-tag.result == 'success' && | |
| (github.event.inputs.protocol == 'all' || github.event.inputs.protocol == 'nfs' || github.event.inputs.protocol == '') | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ inputs.pr != '' && format('refs/pull/{0}/merge', inputs.pr) || '' }} | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| - uses: fenio/setup-k3s@v5 | |
| with: | |
| k3s-args: '--write-kubeconfig-mode 644 --disable=traefik --disable=local-storage' | |
| - uses: ./.github/actions/setup-snapshots | |
| - name: Download PR image artifact | |
| if: inputs.pr != '' | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: pr-image | |
| path: /tmp | |
| - name: Import PR image into k3s | |
| if: inputs.pr != '' | |
| run: | | |
| sudo k3s ctr images import /tmp/tns-csi-pr-image.tar | |
| echo "Imported PR image into k3s containerd" | |
| sudo k3s crictl images | grep tns-csi || true | |
| - name: Install Ginkgo | |
| run: go install github.com/onsi/ginkgo/v2/ginkgo@latest | |
| - name: Run NFS E2E Tests | |
| env: | |
| NASTY_HOST: ${{ secrets.NASTY_HOST }} | |
| NASTY_API_KEY: ${{ secrets.NASTY_API_KEY }} | |
| NASTY_POOL: ${{ secrets.NASTY_POOL }} | |
| CSI_IMAGE_TAG: ${{ needs.compute-tag.outputs.image_tag }} | |
| KUBECONFIG: /etc/rancher/k3s/k3s.yaml | |
| run: | | |
| echo "=== Running NFS E2E Tests ===" | |
| # Run only NFS-specific tests from the nfs/ directory | |
| ginkgo -v --timeout=55m --flake-attempts=2 ./tests/e2e/nfs/... | |
| - name: Upload diagnostic logs | |
| if: failure() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: e2e-nfs-diagnostics | |
| path: /tmp/test-logs/ | |
| retention-days: 7 | |
| ginkgo-e2e-nvmeof: | |
| name: "E2E: NVMe-oF" | |
| runs-on: new | |
| timeout-minutes: 75 | |
| needs: compute-tag | |
| if: | | |
| !cancelled() && needs.compute-tag.result == 'success' && | |
| (github.event.inputs.protocol == 'all' || github.event.inputs.protocol == 'nvmeof' || github.event.inputs.protocol == '') | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ inputs.pr != '' && format('refs/pull/{0}/merge', inputs.pr) || '' }} | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| - uses: fenio/setup-k3s@v5 | |
| with: | |
| k3s-args: '--write-kubeconfig-mode 644 --disable=traefik --disable=local-storage' | |
| - uses: ./.github/actions/setup-snapshots | |
| - name: Download PR image artifact | |
| if: inputs.pr != '' | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: pr-image | |
| path: /tmp | |
| - name: Import PR image into k3s | |
| if: inputs.pr != '' | |
| run: | | |
| sudo k3s ctr images import /tmp/tns-csi-pr-image.tar | |
| echo "Imported PR image into k3s containerd" | |
| sudo k3s crictl images | grep tns-csi || true | |
| - name: Install Ginkgo | |
| run: go install github.com/onsi/ginkgo/v2/ginkgo@latest | |
| - name: Run NVMe-oF E2E Tests | |
| env: | |
| NASTY_HOST: ${{ secrets.NASTY_HOST }} | |
| NASTY_API_KEY: ${{ secrets.NASTY_API_KEY }} | |
| NASTY_POOL: ${{ secrets.NASTY_POOL }} | |
| CSI_IMAGE_TAG: ${{ needs.compute-tag.outputs.image_tag }} | |
| KUBECONFIG: /etc/rancher/k3s/k3s.yaml | |
| run: | | |
| echo "=== Running NVMe-oF E2E Tests ===" | |
| # Run only NVMe-oF-specific tests from the nvmeof/ directory | |
| ginkgo -v --timeout=70m --flake-attempts=2 ./tests/e2e/nvmeof/... | |
| - name: Upload diagnostic logs | |
| if: failure() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: e2e-nvmeof-diagnostics | |
| path: /tmp/test-logs/ | |
| retention-days: 7 | |
| ginkgo-e2e-iscsi: | |
| name: "E2E: iSCSI" | |
| runs-on: new | |
| timeout-minutes: 75 | |
| needs: compute-tag | |
| if: | | |
| !cancelled() && needs.compute-tag.result == 'success' && | |
| (github.event.inputs.protocol == 'all' || github.event.inputs.protocol == 'iscsi' || github.event.inputs.protocol == '') | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ inputs.pr != '' && format('refs/pull/{0}/merge', inputs.pr) || '' }} | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| - uses: fenio/setup-k3s@v5 | |
| with: | |
| k3s-args: '--write-kubeconfig-mode 644 --disable=traefik --disable=local-storage' | |
| - uses: ./.github/actions/setup-snapshots | |
| - name: Download PR image artifact | |
| if: inputs.pr != '' | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: pr-image | |
| path: /tmp | |
| - name: Import PR image into k3s | |
| if: inputs.pr != '' | |
| run: | | |
| sudo k3s ctr images import /tmp/tns-csi-pr-image.tar | |
| echo "Imported PR image into k3s containerd" | |
| sudo k3s crictl images | grep tns-csi || true | |
| - name: Install Ginkgo | |
| run: go install github.com/onsi/ginkgo/v2/ginkgo@latest | |
| - name: Run iSCSI E2E Tests | |
| env: | |
| NASTY_HOST: ${{ secrets.NASTY_HOST }} | |
| NASTY_API_KEY: ${{ secrets.NASTY_API_KEY }} | |
| NASTY_POOL: ${{ secrets.NASTY_POOL }} | |
| CSI_IMAGE_TAG: ${{ needs.compute-tag.outputs.image_tag }} | |
| KUBECONFIG: /etc/rancher/k3s/k3s.yaml | |
| run: | | |
| echo "=== Running iSCSI E2E Tests ===" | |
| # Run only iSCSI-specific tests from the iscsi/ directory | |
| ginkgo -v --timeout=70m --flake-attempts=2 ./tests/e2e/iscsi/... | |
| - name: Upload diagnostic logs | |
| if: failure() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: e2e-iscsi-diagnostics | |
| path: /tmp/test-logs/ | |
| retention-days: 7 | |
| ginkgo-e2e-smb: | |
| name: "E2E: SMB" | |
| runs-on: new | |
| timeout-minutes: 60 | |
| needs: compute-tag | |
| if: | | |
| !cancelled() && needs.compute-tag.result == 'success' && | |
| (github.event.inputs.protocol == 'all' || github.event.inputs.protocol == 'smb' || github.event.inputs.protocol == '') | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ inputs.pr != '' && format('refs/pull/{0}/merge', inputs.pr) || '' }} | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| - uses: fenio/setup-k3s@v5 | |
| with: | |
| k3s-args: '--write-kubeconfig-mode 644 --disable=traefik --disable=local-storage' | |
| - uses: ./.github/actions/setup-snapshots | |
| - name: Download PR image artifact | |
| if: inputs.pr != '' | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: pr-image | |
| path: /tmp | |
| - name: Import PR image into k3s | |
| if: inputs.pr != '' | |
| run: | | |
| sudo k3s ctr images import /tmp/tns-csi-pr-image.tar | |
| echo "Imported PR image into k3s containerd" | |
| sudo k3s crictl images | grep tns-csi || true | |
| - name: Install Ginkgo and smbclient | |
| run: | | |
| go install github.com/onsi/ginkgo/v2/ginkgo@latest | |
| sudo apt-get install -y smbclient 2>/dev/null || true | |
| - name: Run SMB E2E Tests | |
| env: | |
| NASTY_HOST: ${{ secrets.NASTY_HOST }} | |
| NASTY_API_KEY: ${{ secrets.NASTY_API_KEY }} | |
| NASTY_POOL: ${{ secrets.NASTY_POOL }} | |
| CSI_IMAGE_TAG: ${{ needs.compute-tag.outputs.image_tag }} | |
| KUBECONFIG: /etc/rancher/k3s/k3s.yaml | |
| SMB_USERNAME: ${{ secrets.SMB_USERNAME }} | |
| SMB_PASSWORD: ${{ secrets.SMB_PASSWORD }} | |
| run: | | |
| echo "=== Running SMB E2E Tests ===" | |
| # Run only SMB-specific tests from the smb/ directory | |
| ginkgo -v --timeout=55m --flake-attempts=2 ./tests/e2e/smb/... | |
| - name: Collect diagnostic logs | |
| if: failure() | |
| env: | |
| NASTY_HOST: ${{ secrets.NASTY_HOST }} | |
| NASTY_API_KEY: ${{ secrets.NASTY_API_KEY }} | |
| SMB_USERNAME: ${{ secrets.SMB_USERNAME }} | |
| SMB_PASSWORD: ${{ secrets.SMB_PASSWORD }} | |
| run: | | |
| mkdir -p /tmp/test-logs | |
| echo "=== CSI Controller Logs ===" > /tmp/test-logs/controller.log | |
| kubectl logs -n kube-system -l app.kubernetes.io/component=controller -c nasty-csi-plugin --tail=500 >> /tmp/test-logs/controller.log 2>&1 || true | |
| echo "=== CSI Node Logs ===" > /tmp/test-logs/node.log | |
| kubectl logs -n kube-system -l app.kubernetes.io/component=node -c nasty-csi-plugin --tail=500 >> /tmp/test-logs/node.log 2>&1 || true | |
| echo "=== Pod Events ===" > /tmp/test-logs/events.log | |
| kubectl get events --all-namespaces --sort-by='.lastTimestamp' >> /tmp/test-logs/events.log 2>&1 || true | |
| echo "=== PVCs ===" >> /tmp/test-logs/events.log | |
| kubectl get pvc --all-namespaces >> /tmp/test-logs/events.log 2>&1 || true | |
| echo "=== CSI Driver Pods ===" >> /tmp/test-logs/events.log | |
| kubectl get pods -n kube-system -l app.kubernetes.io/name=nasty-csi-driver -o wide >> /tmp/test-logs/events.log 2>&1 || true | |
| # SMB-specific diagnostics (note: runs after test cleanup, so shares may be gone) | |
| echo "=== SMB Share Listing (smbclient -L) ===" > /tmp/test-logs/smb-diag.log | |
| smbclient -L "//${NASTY_HOST}" -U "${SMB_USERNAME}%${SMB_PASSWORD}" --option="client min protocol=SMB3" 2>&1 >> /tmp/test-logs/smb-diag.log || true | |
| echo "" >> /tmp/test-logs/smb-diag.log | |
| echo "=== NOTE: Real-time share diagnostics are in controller.log [SMB clone diag] lines ===" >> /tmp/test-logs/smb-diag.log | |
| - name: Upload diagnostic logs | |
| if: failure() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: e2e-smb-diagnostics | |
| path: /tmp/test-logs/ | |
| retention-days: 7 | |
| ginkgo-e2e-shared: | |
| name: "E2E: Shared" | |
| runs-on: new | |
| timeout-minutes: 75 | |
| needs: compute-tag | |
| if: | | |
| !cancelled() && needs.compute-tag.result == 'success' | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ inputs.pr != '' && format('refs/pull/{0}/merge', inputs.pr) || '' }} | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: go.mod | |
| - uses: fenio/setup-k3s@v5 | |
| with: | |
| k3s-args: '--write-kubeconfig-mode 644 --disable=traefik --disable=local-storage' | |
| - uses: ./.github/actions/setup-snapshots | |
| - name: Download PR image artifact | |
| if: inputs.pr != '' | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: pr-image | |
| path: /tmp | |
| - name: Import PR image into k3s | |
| if: inputs.pr != '' | |
| run: | | |
| sudo k3s ctr images import /tmp/tns-csi-pr-image.tar | |
| echo "Imported PR image into k3s containerd" | |
| sudo k3s crictl images | grep tns-csi || true | |
| - name: Install Ginkgo | |
| run: go install github.com/onsi/ginkgo/v2/ginkgo@latest | |
| - name: Run Shared E2E Tests | |
| env: | |
| NASTY_HOST: ${{ secrets.NASTY_HOST }} | |
| NASTY_API_KEY: ${{ secrets.NASTY_API_KEY }} | |
| NASTY_POOL: ${{ secrets.NASTY_POOL }} | |
| CSI_IMAGE_TAG: ${{ needs.compute-tag.outputs.image_tag }} | |
| KUBECONFIG: /etc/rancher/k3s/k3s.yaml | |
| SMB_USERNAME: ${{ secrets.SMB_USERNAME }} | |
| SMB_PASSWORD: ${{ secrets.SMB_PASSWORD }} | |
| run: | | |
| echo "=== Running Shared E2E Tests ===" | |
| # Run shared tests from the root e2e/ directory (not nfs/ or nvmeof/) | |
| # These tests use "both" protocol setup (NFS, NVMe-oF, and iSCSI) | |
| ginkgo -v --timeout=70m --flake-attempts=2 ./tests/e2e/ | |
| - name: Upload diagnostic logs | |
| if: failure() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: e2e-shared-diagnostics | |
| path: /tmp/test-logs/ | |
| retention-days: 7 | |
| # ========================================== | |
| # Summary | |
| # ========================================== | |
| # Note: All integration tests have been migrated to Ginkgo E2E tests. | |
| # - Protocol-specific tests: tests/e2e/nfs/, tests/e2e/nvmeof/, tests/e2e/iscsi/, tests/e2e/smb/ | |
| # - Shared tests (all protocols): tests/e2e/*.go (snapshot_restore, stress, name_templating, etc.) | |
| test-summary: | |
| name: "Test Summary" | |
| runs-on: new | |
| timeout-minutes: 5 | |
| needs: [ginkgo-e2e-nfs, ginkgo-e2e-nvmeof, ginkgo-e2e-iscsi, ginkgo-e2e-smb, ginkgo-e2e-shared] | |
| if: always() | |
| steps: | |
| - name: Generate Test Summary | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const e2eResults = { | |
| 'NFS E2E': '${{ needs.ginkgo-e2e-nfs.result }}', | |
| 'NVMe-oF E2E': '${{ needs.ginkgo-e2e-nvmeof.result }}', | |
| 'iSCSI E2E': '${{ needs.ginkgo-e2e-iscsi.result }}', | |
| 'SMB E2E': '${{ needs.ginkgo-e2e-smb.result }}', | |
| 'Shared E2E': '${{ needs.ginkgo-e2e-shared.result }}', | |
| }; | |
| const getIcon = (result) => { | |
| switch(result) { | |
| case 'success': return '✅'; | |
| case 'failure': return '❌'; | |
| case 'skipped': return '⏭️'; | |
| case 'cancelled': return '🚫'; | |
| default: return '⚪'; | |
| } | |
| }; | |
| let passed = 0, failed = 0, skipped = 0; | |
| for (const result of Object.values(e2eResults)) { | |
| if (result === 'success') passed++; | |
| else if (result === 'failure') failed++; | |
| else if (result === 'skipped') skipped++; | |
| } | |
| const total = Object.keys(e2eResults).length; | |
| let summary = `# Integration Test Results\n\n`; | |
| // Overall status banner | |
| if (failed > 0) { | |
| summary += `> ### ${failed} test(s) failed\n\n`; | |
| } else if (passed === total) { | |
| summary += `> ### All ${total} tests passed!\n\n`; | |
| } else { | |
| summary += `> ### ${passed}/${passed + failed} tests passed (${skipped} skipped)\n\n`; | |
| } | |
| // Quick stats | |
| summary += `| Passed | Failed | Skipped | Total |\n`; | |
| summary += `|:------:|:------:|:-------:|:-----:|\n`; | |
| summary += `| ${passed} | ${failed} | ${skipped} | ${total} |\n\n`; | |
| // E2E Tests | |
| summary += `## E2E Tests\n\n`; | |
| summary += `| Test Suite | Status |\n`; | |
| summary += `|------------|:------:|\n`; | |
| for (const [test, result] of Object.entries(e2eResults)) { | |
| summary += `| ${test} | ${getIcon(result)} |\n`; | |
| } | |
| summary += `\n_Note: Shared E2E includes Snapshot Restore, Volume Stress, and Snapshot Stress tests for NFS, NVMe-oF, iSCSI, and SMB._\n`; | |
| // Write to job summary | |
| await core.summary.addRaw(summary).write(); | |
| // Fail if any test failed | |
| if (failed > 0) { | |
| core.setFailed(`${failed} test(s) failed`); | |
| } |