Doc tests #579
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: Doc tests | |
| on: | |
| pull_request: | |
| paths: | |
| - 'assets/**' | |
| - 'content/docs/**' | |
| schedule: | |
| # Every day at 06:00 UTC | |
| - cron: '0 6 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| slack_notification: | |
| description: 'Send Slack notification' | |
| type: boolean | |
| default: false | |
| slack_channel: | |
| description: 'Slack channel to notify' | |
| type: string | |
| default: 'product-excellence-test-notifications' | |
| jobs: | |
| discover: | |
| name: Discover test cases | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.list.outputs.matrix }} | |
| has_tests: ${{ steps.list.outputs.has_tests }} | |
| steps: | |
| - name: Checkout website repo | |
| uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.x' | |
| - name: Install Python dependencies | |
| run: pip install pyyaml | |
| - name: Get changed markdown files | |
| id: changed-files | |
| if: github.event_name == 'pull_request' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| ALL_CHANGED=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files \ | |
| --paginate --jq '.[].filename' || true) | |
| # Start with directly changed content/*.md files | |
| FILES=$(echo "$ALL_CHANGED" | grep '^content/.*\.md$' | tr '\n' ' ' || true) | |
| # For each changed assets/agw-docs/pages/*.md file, check for corresponding | |
| # content files in the kubernetes/standalone main/latest directories | |
| ASSET_FILES=$(echo "$ALL_CHANGED" | grep '^assets/agw-docs/pages/.*\.md$' || true) | |
| if [ -n "$ASSET_FILES" ]; then | |
| while IFS= read -r asset_file; do | |
| rel_path="${asset_file#assets/agw-docs/pages/}" | |
| rel_path="${rel_path#agentgateway/}" | |
| found=0 | |
| for content_dir in "content/docs/kubernetes/main" "content/docs/kubernetes/latest" "content/docs/standalone/main" "content/docs/standalone/latest"; do | |
| candidate="${content_dir}/${rel_path}" | |
| if [ -f "$candidate" ] && ! echo "$FILES" | grep -qF "$candidate"; then | |
| FILES="$FILES $candidate" | |
| found=1 | |
| fi | |
| done | |
| if [ "$found" -eq 0 ]; then | |
| echo "No content candidates found for asset file: $asset_file" | |
| fi | |
| done <<< "$ASSET_FILES" | |
| fi | |
| FILES=$(echo "$FILES" | tr ' ' '\n' | sort -u | tr '\n' ' ' | xargs) | |
| echo "files=${FILES}" >> $GITHUB_OUTPUT | |
| echo "$FILES" | tr ' ' '\n' | |
| - name: List test cases | |
| id: list | |
| run: | | |
| if [ -n "$CHANGED_FILES" ]; then | |
| TESTS=$(python3 scripts/doc_test_run.py --repo-root . --list-tests --file $CHANGED_FILES) | |
| else | |
| TESTS=$(python3 scripts/doc_test_run.py --repo-root . --list-tests) | |
| fi | |
| COUNT=$(echo "$TESTS" | jq 'length') | |
| if [ "$COUNT" -gt 0 ]; then | |
| echo "has_tests=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_tests=false" >> $GITHUB_OUTPUT | |
| fi | |
| MATRIX=$(echo "$TESTS" | jq -c '(length | [., 20] | min) as $shards | . as $tests | {include: [range($shards) | . as $i | ([$tests | to_entries[] | select(.key % $shards == $i) | .value] as $shard | {shard_index: $i, test_count: ($shard | length), tests: ($shard | tojson), shard_name: ($shard | [.[].test] | join(", ") | if length > 80 then .[:77] + "..." else . end)})]}') | |
| echo "matrix=${MATRIX}" >> $GITHUB_OUTPUT | |
| echo "Discovered $COUNT test case(s)" | |
| env: | |
| CHANGED_FILES: ${{ steps.changed-files.outputs.files }} | |
| run-test: | |
| name: "${{ matrix.shard_name }}" | |
| needs: discover | |
| if: needs.discover.outputs.has_tests == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| matrix: ${{ fromJson(needs.discover.outputs.matrix) }} | |
| fail-fast: false | |
| steps: | |
| - name: Checkout website repo | |
| uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.x' | |
| - name: Set up Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: 'stable' | |
| cache: false | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 'lts/*' | |
| - name: Install Python dependencies | |
| run: pip install pyyaml | |
| - name: Install cloud-provider-kind | |
| run: go install sigs.k8s.io/cloud-provider-kind@latest | |
| - name: Install yamltest | |
| run: npm install -g yamltest@latest | |
| - name: Run doc tests | |
| env: | |
| SHARD_TESTS: ${{ matrix.tests }} | |
| DEBUG_MODE: true | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| PYTHONUNBUFFERED: '1' | |
| run: | | |
| FAILED=0 | |
| TEST_INDEX=0 | |
| while IFS=$'\t' read -r file test; do | |
| python3 scripts/doc_test_run.py --repo-root . --file "$file" --test "$test" \ | |
| --report-file "out/tests/generated/shard/${TEST_INDEX}/test-results.yaml" || FAILED=1 | |
| TEST_INDEX=$((TEST_INDEX + 1)) | |
| done < <(echo "$SHARD_TESTS" | jq -r '.[] | "\(.file)\t\(.test)"') | |
| python3 scripts/merge_test_results.py out/tests/generated/shard/ out/tests/generated/test-results.yaml | |
| exit $FAILED | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: test-result-${{ strategy.job-index }} | |
| path: out/tests/generated/test-results.yaml | |
| retention-days: 1 | |
| - name: Upload test context (on failure) | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: test-context-${{ strategy.job-index }} | |
| path: out/tests/generated/context/ | |
| retention-days: 1 | |
| if-no-files-found: ignore | |
| report: | |
| name: Aggregate results and report | |
| needs: [discover, run-test] | |
| if: always() && needs.discover.outputs.has_tests == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout website repo | |
| uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.x' | |
| - name: Install Python dependencies | |
| run: pip install pyyaml | |
| - name: Download all test results | |
| uses: actions/download-artifact@v7 | |
| with: | |
| pattern: test-result-* | |
| path: collected-results/ | |
| - name: Merge test results | |
| run: python3 scripts/merge_test_results.py collected-results/ out/tests/generated/test-results.yaml | |
| - name: Generate job summary | |
| id: summary | |
| run: | | |
| RESULTS_FILE=out/tests/generated/test-results.yaml | |
| # GitHub Step Summary (Markdown) | |
| python3 scripts/report_summary.py "$RESULTS_FILE" >> "$GITHUB_STEP_SUMMARY" | |
| # Slack Block Kit payload | |
| RUN_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" | |
| SLACK_PAYLOAD=$(python3 scripts/report_summary.py --slack --run-url "$RUN_URL" "$RESULTS_FILE") | |
| echo "slack_blocks<<EOF" >> "$GITHUB_OUTPUT" | |
| echo "$SLACK_PAYLOAD" | jq '.main' >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| echo "slack_thread_blocks<<EOF" >> "$GITHUB_OUTPUT" | |
| echo "$SLACK_PAYLOAD" | jq '.thread // empty' >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| - name: Notify Slack | |
| if: always() && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.slack_notification)) | |
| uses: ./.github/actions/notify-slack | |
| with: | |
| message: "Doc Test Results" | |
| blocks: ${{ steps.summary.outputs.slack_blocks }} | |
| thread_blocks: ${{ steps.summary.outputs.slack_thread_blocks }} | |
| channel: ${{ github.event_name == 'workflow_dispatch' && inputs.slack_channel || 'product-excellence-test-notifications' }} | |
| token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| - name: List untested docs | |
| if: always() | |
| run: | | |
| python3 scripts/list_untested_docs.py \ | |
| --docs-dir content/docs \ | |
| --output out/tests/generated/untested-docs.txt | |
| - name: Upload merged results | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: doc-test-results | |
| path: out/tests/generated/ | |
| retention-days: 14 | |
| - name: Fail if any test failed | |
| run: | | |
| python3 -c " | |
| import yaml, sys | |
| with open('out/tests/generated/test-results.yaml') as f: | |
| report = yaml.safe_load(f) or {} | |
| tests = report.get('tests', {}) | |
| failed = [k for k, v in tests.items() if v.get('status') != 'passed'] | |
| if failed: | |
| print(f'{len(failed)} test(s) failed:', file=sys.stderr) | |
| for name in failed: | |
| print(f' - {name}', file=sys.stderr) | |
| sys.exit(1) | |
| print(f'All {len(tests)} test(s) passed') | |
| " |