name: Server Test Merge Template on: workflow_call: inputs: artifact-pattern: description: "Glob pattern to download shard artifacts" required: true type: string artifact-name: description: "Name for the merged output artifact" required: true type: string save-timing-cache: description: "Whether to save timing cache for future shard balancing" required: false type: boolean default: false all-shards-passed: description: "Whether every upstream shard succeeded. Used to gate the timing-cache save so a single shard failure doesn't poison the cache with missing-package data." required: false type: boolean default: false permissions: contents: read actions: write jobs: merge: name: Merge if: always() runs-on: ubuntu-22.04 steps: - name: Download all shard artifacts uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: pattern: ${{ inputs.artifact-pattern }} path: shards - name: Merge JUnit reports run: | python3 -c " import glob, sys from xml.etree import ElementTree as ET root = ET.Element('testsuites') for path in sorted(glob.glob('shards/*/report.xml')): tree = ET.parse(path) r = tree.getroot() if r.tag == 'testsuites': root.extend(r) else: root.append(r) ET.ElementTree(root).write('merged-report.xml', xml_declaration=True, encoding='UTF-8') " - name: Prepare merged artifact run: | mkdir -p merged cp merged-report.xml merged/report.xml for dir in shards/*/; do if [[ -f "${dir}test-name" ]]; then cp "${dir}test-name" merged/test-name cp "${dir}pr-number" merged/pr-number break fi done - name: Upload merged test logs uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: ${{ inputs.artifact-name }} path: merged/ - name: Prepare timing cache if: inputs.save-timing-cache id: timing-prep run: | mkdir -p server echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT" if [[ -f merged-report.xml && $(stat -c%s merged-report.xml) -gt 1024 ]]; then cp merged-report.xml server/prev-report.xml cat shards/*/gotestsum.json > server/prev-gotestsum.json 2>/dev/null || true echo "has_timing=true" >> "$GITHUB_OUTPUT" else echo "Skipping timing cache — merged report too small or missing" echo "has_timing=false" >> "$GITHUB_OUTPUT" fi # Only save when every upstream shard succeeded. If even one shard # failed/was killed, its gotestsum.json is missing and the merged report # has no timings for that shard's packages — saving that would poison # future shard splits (missing packages default to 1ms, all bin-pack # onto the lightest shard, overloading it and repeating the failure). - name: Save test timing cache if: inputs.save-timing-cache && inputs.all-shards-passed && steps.timing-prep.outputs.has_timing == 'true' && github.ref_name == github.event.repository.default_branch uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: | server/prev-report.xml server/prev-gotestsum.json # Bucket by UTC date (not run id) so we save at most one cache per day. key: server-test-timing-v2-master-${{ steps.timing-prep.outputs.date }}