@@ -14,11 +14,19 @@ permissions:
1414
1515# Configurable global environment variables for batching
1616env :
17- BATCH_SIZE : 10
1817 TEST_ALL_PACKAGES : " true" # Set to "false" to only run tests for packages with a git diff
18+ ALL_PYTHON : ' ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]'
1919
2020jobs :
2121 # Dynamic package discovery job to calculate required matrix size automatically
22+ python_config :
23+ runs-on : ubuntu-latest
24+ outputs :
25+ all_python : ${{ steps.export.outputs.python_list }}
26+ steps :
27+ - id : export
28+ run : echo "python_list=${{ env.ALL_PYTHON }}" >> "$GITHUB_OUTPUT"
29+
2230 discover-packages :
2331 runs-on : ubuntu-latest
2432 outputs :
@@ -28,54 +36,23 @@ jobs:
2836 uses : actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
2937 with :
3038 persist-credentials : false
39+ - name : Setup Python
40+ uses : actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
41+ with :
42+ python-version : " 3.14"
3143 - name : Generate Batch Indices
3244 id : set-matrix
3345 run : |
34- # Testing all monorepo packages sequentially on a single runner node is
35- # too slow and creates a severe CI bottleneck as the repository expands.
36- #
37- # To scale efficiently, we chunk the workload into parallel batch slices.
38- # Instead of using a fixed, hardcoded matrix array (which risks silently
39- # skipping newly added packages if the repository outgrows the array size),
40- # this step dynamically audits the 'packages/' directory at runtime.
41- #
42- # It calculates exactly how many concurrent runners are required based on
43- # the current repository size and the configured BATCH_SIZE variable,
44- # ensuring 100% test coverage with zero manual YAML maintenance.
45-
46- # 1. Count the number of total directories matching the 'packages/*' pattern.
47- # Redirect stderr to /dev/null so empty repos do not print unneeded errors.
48- TOTAL_PACKAGES=$(ls -d packages/*/ 2>/dev/null | wc -l | tr -d ' ')
49-
50- # 2. Safety fallback: If no packages are detected, assign a single slice index [0]
51- # so subsequent matrix-dependent jobs do not break or fail validation on an empty matrix.
52- if [ "$TOTAL_PACKAGES" -eq 0 ]; then
53- echo "indices=[0]" >> "$GITHUB_OUTPUT"
54- exit 0
55- fi
56-
57- # 3. Calculate the number of batches required using ceiling division: ceil(TOTAL_PACKAGES / BATCH_SIZE).
58- # The formula ((A + B - 1) / B) ensures integer division rounds up if there's any remaining package leftover.
59- # Example: 251 packages with a batch size of 10 gives ((251 + 10 - 1) / 10) = 260 / 10 = 26 batches.
60- NUM_BATCHES=$(( (TOTAL_PACKAGES + ${{ env.BATCH_SIZE }} - 1) / ${{ env.BATCH_SIZE }} ))
61-
62- # 4. Generate a zero-indexed sequence from 0 to (NUM_BATCHES - 1).
63- # Use jq to securely parse the raw numbers and compile them into a compacted JSON array string.
64- # Example output format: [0,1,2,3,...,25]
65- INDICES=$(seq 0 $((NUM_BATCHES - 1)) | jq -R . | jq -s -c .)
66-
67- # 5. Output the finished JSON string to the GitHub environment outputs pipeline.
68- # This will safely feed directly into the execution matrix downstream.
46+ INDICES=$(python -c 'import sys; sys.path.append("ci"); import get_batches; print(get_batches.get_batch_indices())')
6947 echo "indices=${INDICES}" >> "$GITHUB_OUTPUT"
7048
7149 unit :
7250 name : " unit-run (${{ matrix.python }}, Batch ${{ matrix.batch-index }})"
7351 runs-on : ubuntu-22.04
74- needs : discover-packages
52+ needs : [python_config, discover-packages]
7553 strategy :
7654 matrix :
77- python : ['3.9', '3.10', "3.11", "3.12", "3.13", "3.14"]
78- # Dynamically scales to fit every package perfectly without hardcoding array indices
55+ python : ${{ fromJSON(needs.python_config.outputs.all_python) }}
7956 batch-index : ${{ fromJson(needs.discover-packages.outputs.batch-indices) }}
8057 steps :
8158 - name : Checkout
@@ -95,43 +72,47 @@ jobs:
9572 python -m pip install --upgrade setuptools pip wheel
9673 python -m pip install nox
9774 - name : Run unit tests
75+ id : run-tests
9876 env :
99- COVERAGE_FILE : ${{ github.workspace }}/.coverage-${{ matrix.python }}
77+ COVERAGE_FILE : ${{ github.workspace }}/.coverage-${{ matrix.python }}-${{ matrix.batch-index }}
10078 # Dynamically set BUILD_TYPE to an empty string to skip the diff calculation if TEST_ALL_PACKAGES is true
10179 BUILD_TYPE : ${{ env.TEST_ALL_PACKAGES == 'true' && '' || 'presubmit' }}
10280 TARGET_BRANCH : ${{ github.base_ref || github.event.merge_group.base_ref }}
10381 TEST_TYPE : unit
10482 PY_VERSION : ${{ matrix.python }}
10583 run : |
106- # Gather all packages in alphabetical order
107- ALL_PACKAGES=($(ls -d packages/*/ | sort))
108- TOTAL_PACKAGES=${#ALL_PACKAGES[@]}
84+ UNIQUE_BATCH_PACKAGES=$(python -c 'import sys; sys.path.append("ci"); import get_batches; print(get_batches.get_batch_slice(${{ matrix.batch-index }}))')
10985
110- # Determine this runner's slice window
111- START_INDEX=$(( ${{ matrix.batch-index }} * ${{ env.BATCH_SIZE }} ))
112-
113- if [ $START_INDEX -ge $TOTAL_PACKAGES ]; then
86+ if [ -z "$UNIQUE_BATCH_PACKAGES" ]; then
87+ echo "No structural units allocated to this matrix slice."
11488 exit 0
11589 fi
11690
117- BATCH_PACKAGES=("${ALL_PACKAGES[@]:$START_INDEX:${{ env.BATCH_SIZE }}}")
91+ echo "Running tests for packages in this weighted batch: ${UNIQUE_BATCH_PACKAGES}"
92+ ci/run_conditional_tests.sh ${UNIQUE_BATCH_PACKAGES}
11893
119- # Strip trailing slashes to pass down directly into ci/run_conditional_tests.sh
120- subdirs=("${BATCH_PACKAGES[@]%/}")
94+ - name : Save Status Footprint
95+ run : |
96+ mkdir -p footprints
97+ echo "${{ steps.run-tests.outcome }}" > footprints/status.txt
98+
99+ - name : Upload Status Footprint
100+ uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
101+ with :
102+ name : footprint-${{ matrix.python }}-${{ matrix.batch-index }}
103+ path : footprints/
121104
122- ci/run_conditional_tests.sh "${subdirs[@]}"
123105 - name : Upload coverage results
124106 uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
125107 with :
126108 # Appended batch-index to separate parallel coverage uploads cleanly
127109 name : coverage-artifact-${{ matrix.python }}-${{ matrix.batch-index }}
128- path : .coverage-${{ matrix.python }}
110+ path : .coverage-${{ matrix.python }}-${{ matrix.batch-index }}
129111 include-hidden-files : true
130112
131113 cover :
132114 runs-on : ubuntu-latest
133- needs :
134- - unit
115+ needs : [unit]
135116 steps :
136117 - name : Checkout
137118 uses : actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
@@ -246,19 +227,49 @@ jobs:
246227
247228 unittest-runtime-result :
248229 name : " unit (${{ matrix.python }})"
249- needs : unit
230+ needs : [python_config, discover-packages, unit]
231+ if : always()
250232 strategy :
233+ fail-fast : false
251234 matrix :
252- python : ['3.9', '3.10', "3.11", "3.12", "3.13", "3.14"]
235+ python : ${{ fromJSON(needs.python_config.outputs.all_python) }}
253236 runs-on : ubuntu-latest
254237 steps :
255- - name : Check unit tests results
256- run : |
257- UNIT_STATUS="${{ needs.unit.result }}"
238+ - name : Download all status footprints for this runtime
239+ uses : actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
240+ with :
241+ pattern : footprint-${{ matrix.python }}-*
242+ path : footprint-results/
258243
259- if [[ "$UNIT_STATUS" == "success" ]]; then
260- echo "Python ${{ matrix.python }} tests passed."
244+ - name : Validate complete batch execution footprint status
245+ run : |
246+ EXPECTED_BATCHES=$(echo '${{ needs.discover-packages.outputs.batch-indices }}' | jq '. | length')
247+
248+ if [ -d "footprint-results" ]; then
249+ ACTUAL_BATCHES=$(find footprint-results -type f -name 'status.txt' | wc -l | tr -d ' ')
261250 else
262- echo "Error: Python ${{ matrix.python }} status is '$UNIT_STATUS'."
251+ ACTUAL_BATCHES=0
252+ fi
253+
254+ echo "Validation metrics for Python ${{ matrix.python }}:"
255+ echo " -> Expected footprint files: $EXPECTED_BATCHES"
256+ echo " -> Downloaded footprint files: $ACTUAL_BATCHES"
257+
258+ if [ "$ACTUAL_BATCHES" -ne "$EXPECTED_BATCHES" ]; then
259+ echo "Error: Footprint count mismatch! Expected $EXPECTED_BATCHES files, but found $ACTUAL_BATCHES."
260+ exit 1
261+ fi
262+
263+ FAILED_RUNS=0
264+ while read -r STATUS_FILE; do
265+ RUN_STATUS=$(cat "$STATUS_FILE" | tr -d '[:space:]')
266+ if [[ "$RUN_STATUS" != "success" ]]; then
267+ echo "Failure detected in batch profile path: $STATUS_FILE (Status: $RUN_STATUS)"
268+ FAILED_RUNS=$((FAILED_RUNS + 1))
269+ fi
270+ done < <(find footprint-results -type f -name 'status.txt' 2>/dev/null)
271+
272+ if [ "$FAILED_RUNS" -gt 0 ]; then
273+ echo "Error: Validation failed. Found $FAILED_RUNS failing test batches."
263274 exit 1
264275 fi
0 commit comments