Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b890da6
chore: update librarian.yaml; post processing; use root mypy.ini
parthea Jun 26, 2026
f838a04
chore: run generation for all libraries
parthea Jun 26, 2026
5f62a2d
chore: add 3.15 to remaining libraries; add 3.15 testing
parthea Jun 26, 2026
44ed114
chore: allow prerelease testing
parthea Jun 26, 2026
142dd59
manual changes
parthea Jun 30, 2026
f2ce4a0
tests: parallel batching for packages when running unit tests
parthea Jul 2, 2026
8cbf92e
Update ci/run_conditional_tests.sh
parthea Jul 2, 2026
ef70039
Update ci/run_conditional_tests.sh
parthea Jul 2, 2026
e8b90af
Typo
parthea Jul 2, 2026
47e915c
remove 3.15
parthea Jul 2, 2026
492ee5f
Typo
parthea Jul 2, 2026
08933fd
Update ci/run_conditional_tests.sh
parthea Jul 2, 2026
c85a266
Update ci/run_conditional_tests.sh
parthea Jul 2, 2026
75e5657
fix TEST_ALL_PACKAGES override
parthea Jul 2, 2026
d3a042e
fix presubmit
parthea Jul 2, 2026
4898748
update --cov-fail-under
parthea Jul 2, 2026
bbd8144
add weighting
parthea Jul 6, 2026
5ef6f91
syntax
parthea Jul 6, 2026
7b1b6b7
address review feedback
parthea Jul 6, 2026
1aab0d5
Update ci/run_conditional_tests.sh
parthea Jul 6, 2026
4d5e55d
remove weighting
parthea Jul 6, 2026
4e39eeb
set TEST_ALL_PACKAGES to false
parthea Jul 6, 2026
90370ad
Merge branch 'ci-improvements-20260702' into chore-update-librarian-2…
parthea Jul 6, 2026
8e473c8
skip 3.15 for packages which depend on geopandas
parthea Jul 6, 2026
a803ac7
chore: typo
parthea Jul 6, 2026
5e833ae
Merge branch 'main' into chore-update-librarian-2026-06-26
parthea Jul 13, 2026
882bec9
fix build
parthea Jul 13, 2026
e34b439
revert
parthea Jul 13, 2026
c2e32bf
revert
parthea Jul 13, 2026
0d3dc9f
revert
parthea Jul 13, 2026
3dff0e4
fix build
parthea Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
159 changes: 148 additions & 11 deletions .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,48 @@ name: unittest
permissions:
contents: read

# Configurable global environment variables for batching
env:
TEST_ALL_PACKAGES: "false" # Set to "false" to only run tests for packages with a git diff
ALL_PYTHON: "['3.9', '3.10', '3.11', '3.12', '3.13', '3.14', '3.15']"

jobs:
# Dynamic package discovery job to calculate required matrix size automatically
python_config:
runs-on: ubuntu-latest
outputs:
all_python: ${{ steps.export.outputs.python_list }}
steps:
- id: export
run: echo "python_list=${{ env.ALL_PYTHON }}" >> "$GITHUB_OUTPUT"

discover-packages:
runs-on: ubuntu-latest
outputs:
batch-indices: ${{ steps.set-matrix.outputs.indices }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
- name: Setup Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.14"
- name: Generate Batch Indices
id: set-matrix
run: |
INDICES=$(python -c 'import sys; sys.path.append("ci"); import get_batches; print(get_batches.get_batch_indices())')
echo "indices=${INDICES}" >> "$GITHUB_OUTPUT"

unit:
name: "unit-run (${{ matrix.python }}, Batch ${{ matrix.batch-index }})"
runs-on: ubuntu-22.04
needs: [python_config, discover-packages]
strategy:
fail-fast: true
matrix:
python: ['3.9', '3.10', "3.11", "3.12", "3.13", "3.14"]
python: ${{ fromJSON(needs.python_config.outputs.all_python) }}
batch-index: ${{ fromJson(needs.discover-packages.outputs.batch-indices) }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
Expand All @@ -32,30 +67,83 @@ jobs:
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: ${{ matrix.python }}
allow-prereleases: true
- name: Setup uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
cache-dependency-glob: 'packages/**/testing/constraints*.txt'
- name: Install nox
run: |
python -m pip install --upgrade setuptools pip wheel
python -m pip install nox
uv pip install --system nox nox-uv
# Caches compiled wheels locally to prevent building heavy libraries
# such as grpcio, which we build from scratch on every run for Python 3.15+.
# Follow https://github.com/grpc/grpc/issues/41010 for updates.
- name: Cache Built Python 3.15 Wheels
if: matrix.python == '3.15'
uses: actions/cache@v4
with:
path: .uv-wheel-cache
key: ${{ runner.os }}-uv-wheels-3.15-${{ hashFiles('packages/**/testing/constraints*.txt') }}
restore-keys: |
${{ runner.os }}-uv-wheels-3.15-
- name: Run unit tests
id: run-tests
env:
COVERAGE_FILE: ${{ github.workspace }}/.coverage-${{ matrix.python }}
BUILD_TYPE: presubmit
COVERAGE_FILE: ${{ github.workspace }}/.coverage-${{ matrix.python }}-${{ matrix.batch-index }}
# Dynamically set BUILD_TYPE to an empty string to skip the diff calculation if TEST_ALL_PACKAGES is true
BUILD_TYPE: ${{ env.TEST_ALL_PACKAGES == 'true' && '' || 'presubmit' }}
TARGET_BRANCH: ${{ github.base_ref || github.event.merge_group.base_ref }}
TEST_TYPE: unit
PY_VERSION: ${{ matrix.python }}
# Workaround: Allows libcst to compile on Python 3.15+ while PyO3 catches up
# Can be removed once libcst releases a version with native Python 3.15 wheels
# Follow https://github.com/Instagram/LibCST/issues/1445 for updates.
PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1"
run: |
UNIQUE_BATCH_PACKAGES=$(python -c 'import sys; sys.path.append("ci"); import get_batches; print(get_batches.get_batch_slice(${{ matrix.batch-index }}))')

if [ -z "$UNIQUE_BATCH_PACKAGES" ]; then
echo "No structural units allocated to this matrix slice."
exit 0
fi

echo "Running tests for packages in this weighted batch: ${UNIQUE_BATCH_PACKAGES}"

# Only inject overrides if we are strictly on the 3.15 pre-release
# This is needed to speed up builds
if [ "${{ matrix.python }}" = "3.15" ]; then
# Tell uv it is allowed to use the pre-release 3.15 toolchain
export UV_PRERELEASE="allow"

# Route uv's wheel and environment cache to our persistent workspace directory
export UV_CACHE_DIR="${{ github.workspace }}/.uv-wheel-cache"
fi

ci/run_conditional_tests.sh ${UNIQUE_BATCH_PACKAGES}

- name: Save Status Footprint
run: |
ci/run_conditional_tests.sh
mkdir -p footprints
echo "${{ steps.run-tests.outcome }}" > footprints/status.txt

- name: Upload Status Footprint
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: footprint-${{ matrix.python }}-${{ matrix.batch-index }}
path: footprints/

- name: Upload coverage results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage-artifact-${{ matrix.python }}
path: .coverage-${{ matrix.python }}
# Appended batch-index to separate parallel coverage uploads cleanly
name: coverage-artifact-${{ matrix.python }}-${{ matrix.batch-index }}
path: .coverage-${{ matrix.python }}-${{ matrix.batch-index }}
include-hidden-files: true

cover:
runs-on: ubuntu-latest
needs:
- unit
needs: [unit]
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
Expand Down Expand Up @@ -167,3 +255,52 @@ jobs:
echo "This usually means the unit tests did not run or failed to upload their coverage files."
exit 1
fi

unittest-runtime-result:
name: "unit (${{ matrix.python }})"
needs: [python_config, discover-packages, unit]
if: always()
strategy:
fail-fast: false
matrix:
python: ${{ fromJSON(needs.python_config.outputs.all_python) }}
runs-on: ubuntu-latest
steps:
- name: Download all status footprints for this runtime
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
pattern: footprint-${{ matrix.python }}-*
path: footprint-results/

- name: Validate complete batch execution footprint status
run: |
EXPECTED_BATCHES=$(echo '${{ needs.discover-packages.outputs.batch-indices }}' | jq '. | length')

if [ -d "footprint-results" ]; then
ACTUAL_BATCHES=$(find footprint-results -type f -name 'status.txt' | wc -l | tr -d ' ')
else
ACTUAL_BATCHES=0
fi

echo "Validation metrics for Python ${{ matrix.python }}:"
echo " -> Expected footprint files: $EXPECTED_BATCHES"
echo " -> Downloaded footprint files: $ACTUAL_BATCHES"

if [ "$ACTUAL_BATCHES" -ne "$EXPECTED_BATCHES" ]; then
echo "Error: Footprint count mismatch! Expected $EXPECTED_BATCHES files, but found $ACTUAL_BATCHES."
exit 1
fi

FAILED_RUNS=0
while read -r STATUS_FILE; do
RUN_STATUS=$(cat "$STATUS_FILE" | tr -d '[:space:]')
if [[ "$RUN_STATUS" != "success" ]]; then
echo "Failure detected in batch profile path: $STATUS_FILE (Status: $RUN_STATUS)"
FAILED_RUNS=$((FAILED_RUNS + 1))
fi
done < <(find footprint-results -type f -name 'status.txt' 2>/dev/null)

if [ "$FAILED_RUNS" -gt 0 ]; then
echo "Error: Validation failed. Found $FAILED_RUNS failing test batches."
exit 1
fi
2 changes: 2 additions & 0 deletions .kokoro/presubmit/system.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ env_vars: {
key: "BIGFRAMES_TEST_PROJECT"
value: "bigframes-testing"
}

timeout_mins: 360
Original file line number Diff line number Diff line change
Expand Up @@ -357,52 +357,6 @@ replacements:

Quick Start
count: 1
- paths: [
packages/google-cloud-bigtable/mypy.ini
]
before: |
\[mypy\]
[\s\S]*?incremental = True
after: |
[mypy]
python_version = 3.13
namespace_packages = True
check_untyped_defs = True
warn_unreachable = True
disallow_any_generics = True
exclude = tests/unit/gapic/

[mypy-grpc.*]
ignore_missing_imports = True

[mypy-google.auth.*]
ignore_missing_imports = True

[mypy-google.iam.*]
ignore_missing_imports = True

[mypy-google.longrunning.*]
ignore_missing_imports = True

[mypy-google.oauth2.*]
ignore_missing_imports = True

[mypy-google.rpc.*]
ignore_missing_imports = True

[mypy-proto.*]
ignore_missing_imports = True

[mypy-pytest]
ignore_missing_imports = True

[mypy-google.cloud.*]
ignore_errors = True

# only verify data client
[mypy-google.cloud.bigtable.data.*]
ignore_errors = False
count: 1
# Note: noxfile.py is heavily customized so we clobber the whole file.
- paths: [
packages/google-cloud-bigtable/noxfile.py
Expand Down Expand Up @@ -431,6 +385,7 @@ replacements:
"3.12",
"3.13",
"3.14",
"3.15",
]

UNIT_TEST_STANDARD_DEPENDENCIES = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,42 +39,6 @@ replacements:
"google-cloud-core >= 2.0.0, <3.0.0",
"grpcio >= 1.59.0, < 2.0.0",
count: 1
- paths: [
"packages/google-cloud-datastore/mypy.ini",
]
before: |-
# Performance: reuse results from previous runs to speed up 'nox'
incremental = True
after: |-
# Performance: reuse results from previous runs to speed up "nox"
incremental = True

[mypy-google.cloud.datastore._app_engine_key_pb2]
ignore_errors = True

# TODO(https://github.com/googleapis/gapic-generator-python/issues/2410):
# Remove once this generator bug is fixed
[mypy-google.cloud.datastore_v1.services.datastore.async_client]
ignore_errors = True

# TODO(https://github.com/googleapis/gapic-generator-python/issues/2410):
# Remove once this generator bug is fixed
[mypy-google.cloud.datastore_v1.services.datastore.client]
ignore_errors = True
count: 1
- paths: [
"packages/google-cloud-datastore/mypy.ini",
]
before: |
ignore_missing_imports = False

# TODO\(https://github.com/googleapis/gapic-generator-python/issues/2563\):
# Dependencies that historically lacks py.typed markers
\[mypy-google\.iam\.\*\]
ignore_missing_imports = True
after: |
ignore_missing_imports = True
count: 1
- paths: [
"packages/google-cloud-datastore/docs/index.rst",
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ replacements:
"3.12",
"3.13",
"3.14",
"3.15",
]
UNIT_TEST_STANDARD_DEPENDENCIES = [
"mock",
Expand Down
81 changes: 81 additions & 0 deletions ci/get_batches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
import os
import glob
import math
import json
import sys

BATCH_SIZE = 10

# Packages that take significantly longer to run tests.
# These will always be assigned to their own dedicated runner.
ISOLATED_PACKAGES = [
"google-cloud-compute",
"google-cloud-compute-v1beta",
"google-cloud-dialogflow",
"google-cloud-dialogflow-cx",
"google-cloud-retail",
]

def get_batches():
"""Splits packages into dedicated isolated batches and evenly chunked standard batches."""
raw_paths = sorted(glob.glob("packages/*"))
package_paths = [p for p in raw_paths if os.path.isdir(p)]

batches = []
standard_packages = []

for path in package_paths:
pkg_name = os.path.basename(path)

if pkg_name in ISOLATED_PACKAGES:
# Put heavy packages into their own standalone batches immediately
batches.append([path])
else:
standard_packages.append(path)

# Chunk the remaining standard packages by BATCH_SIZE
num_standard_packages = len(standard_packages)
num_standard_batches = math.ceil(num_standard_packages / BATCH_SIZE)

if num_standard_batches == 0 and not batches:
num_standard_batches = 1

for i in range(num_standard_batches):
start_idx = i * BATCH_SIZE
chunk = standard_packages[start_idx : start_idx + BATCH_SIZE]
if chunk:
batches.append(chunk)

return batches

def get_batch_indices():
"""Returns a JSON string of the array of batch indices for GitHub Actions matrix."""
batches = get_batches()
return json.dumps(list(range(len(batches))))

def get_batch_slice(batch_index):
"""Returns a space-separated string of unique package paths for a specific batch index."""
batches = get_batches()
if batch_index < 0 or batch_index >= len(batches):
return ""
return " ".join(batches[batch_index])

if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--count":
print(get_batch_indices())
elif len(sys.argv) > 2 and sys.argv[1] == "--slice":
print(get_batch_slice(int(sys.argv[2])))
Loading
Loading