diff --git a/.github/workflows/experiment.yaml b/.github/workflows/experiment.yaml new file mode 100644 index 000000000000..f1c77af19ba6 --- /dev/null +++ b/.github/workflows/experiment.yaml @@ -0,0 +1,194 @@ +name: Next-Gen CI Pipeline + +on: + pull_request: + branches: [ main, preview ] + # Native Merge Queue support for exhaustive batching + merge_group: + types: [checks_requested] + +# Stop burning money on abandoned iterative commits +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + # ========================================== + # 1. DISCOVERY ENGINE (The Router) + # ========================================== + discover: + runs-on: ubuntu-latest + outputs: + packages: ${{ steps.changes.outputs.all_changed_files }} + # Expose the dynamic Python matrix to downstream jobs + python_versions: ${{ steps.set-python.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect Changed Packages + id: changes + uses: tj-actions/changed-files@v44 + with: + files: packages/** + dir_names: true + dir_names_max_depth: 2 + json: true + escape_json: false + + - name: Determine Python Matrix (Risk-Tiering) + id: set-python + run: | + if [[ "${{ github.event_name }}" == "merge_group" ]]; then + echo "Merge Queue detected. Deploying exhaustive matrix." + echo 'matrix=["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]' >> $GITHUB_OUTPUT + else + echo "Pull Request detected. Deploying Min/Max Boundary matrix." + echo 'matrix=["3.9", "3.14"]' >> $GITHUB_OUTPUT + fi + + # ========================================== + # 2. STATIC ANALYSIS (Grouped for Speed) + # ========================================== + static-checks: + needs: discover + if: ${{ needs.discover.outputs.packages != '[]' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package: ${{ fromJSON(needs.discover.outputs.packages) }} + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: "3.14" + enable-cache: true + cache-dependency-glob: "${{ matrix.package }}/setup.py" + + - name: Run Lint and MyPy + run: | + cd ${{ matrix.package }} + export NOX_DEFAULT_VENV_BACKEND=uv + uvx --with 'nox[uv]' nox -s lint mypy lint_setup_py + + # ========================================== + # 3. DOCUMENTATION BUILD + # ========================================== + docs-build: + needs: discover + if: ${{ needs.discover.outputs.packages != '[]' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package: ${{ fromJSON(needs.discover.outputs.packages) }} + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: "3.10" + enable-cache: true + cache-dependency-glob: "${{ matrix.package }}/setup.py" + + - name: Build Docs and DocFX + run: | + cd ${{ matrix.package }} + export NOX_DEFAULT_VENV_BACKEND=uv + uvx --with 'nox[uv]' nox -s docs docfx + + # ========================================== + # 4. UNIT TESTS (Dynamic 2D Matrix + Retries) + # ========================================== + unit-tests: + needs: discover + if: ${{ needs.discover.outputs.packages != '[]' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package: ${{ fromJSON(needs.discover.outputs.packages) }} + # Reads the array generated by the Discovery job + python: ${{ fromJSON(needs.discover.outputs.python_versions) }} + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python }} + enable-cache: true + cache-dependency-glob: "${{ matrix.package }}/setup.py" + + - name: Execute Unit Tests (With Shock Absorbers) + run: | + cd ${{ matrix.package }} + export NOX_DEFAULT_VENV_BACKEND=uv + + # 3-Attempt retry loop to mask legacy flaky tests + for i in 1 2 3; do + echo "Attempt $i of 3 for Python ${{ matrix.python }}..." + if uvx --with 'nox[uv]' nox -s unit-${{ matrix.python }}; then + echo "Tests passed successfully!" + exit 0 + fi + echo "Tests failed. Waiting 5 seconds before retrying..." + sleep 5 + done + + echo "::error::Tests failed after 3 attempts. This is a hard failure." + exit 1 + + # ========================================== + # 5. SYSTEM TESTS + # ========================================== + system-tests: + needs: discover + if: ${{ needs.discover.outputs.packages != '[]' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package: ${{ fromJSON(needs.discover.outputs.packages) }} + python: ["3.11"] + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python }} + enable-cache: true + cache-dependency-glob: "${{ matrix.package }}/setup.py" + + - name: Execute System Tests + env: + RUN_SYSTEM_TESTS: "true" + run: | + cd ${{ matrix.package }} + export NOX_DEFAULT_VENV_BACKEND=uv + uvx --with 'nox[uv]' nox -s system-${{ matrix.python }} + + # ========================================== + # 6. THE GATEKEEPER (Status Check Rollup) + # ========================================== + presubmit-passed: + if: always() + needs: + - discover + - static-checks + - docs-build + - unit-tests + - system-tests + runs-on: ubuntu-latest + steps: + - name: Evaluate Pipeline Status + run: | + if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" || "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then + echo "::error::One or more required CI jobs failed or were cancelled." + exit 1 + fi + + if [[ "${{ needs.discover.outputs.packages }}" == "[]" ]]; then + echo "No Python packages changed. Safely bypassing execution." + exit 0 + fi + + echo "All dynamically generated CI jobs completed successfully." diff --git a/.kokoro/system-single.sh b/.kokoro/system-single.sh index 0ec5ae7ebf1b..6d8b87cd5ec5 100755 --- a/.kokoro/system-single.sh +++ b/.kokoro/system-single.sh @@ -1,35 +1,32 @@ #!/bin/bash # Copyright 2022 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. - -# `-e` enables the script to automatically fail when a command fails -# `-o pipefail` sets the exit code to non-zero if any command fails, -# or zero if all commands in the pipeline exit successfully. +# Licensed under the Apache License, Version 2.0 (the "License"); ... set -eo pipefail pwd -# If NOX_SESSION is set, it only runs the specified session, -# otherwise run all the sessions. NOX_SESSION_ARG="" - -# IF NOX_FILE is set, it runs the specific nox file, -# otherwise it runs noxfile.py in the package directory. NOX_FILE_ARG="" [[ -z "${NOX_SESSION}" ]] || NOX_SESSION_ARG="-s ${NOX_SESSION}" - [[ -z "${NOX_FILE}" ]] || NOX_FILE_ARG="-f ${NOX_FILE}" -python3 -m nox ${NOX_SESSION_ARG} $NOX_FILE_ARG +# 3-Attempt retry loop to absorb GCP quota limits and network blips +for attempt in 1 2 3; do + echo "============================================" + echo "Execution attempt $attempt of 3..." + echo "============================================" + + if uvx --with 'nox[uv]' nox ${NOX_SESSION_ARG} ${NOX_FILE_ARG}; then + echo "Tests passed successfully!" + exit 0 + fi + + if [[ $attempt -lt 3 ]]; then + echo "Tests failed. Backing off for 15 seconds to absorb quota limits..." + sleep 15 + fi +done + +echo "Tests failed after 3 attempts. Hard failure." +exit 1 \ No newline at end of file diff --git a/.kokoro/system.sh b/.kokoro/system.sh index 67215245bcf1..140cc25d26f4 100755 --- a/.kokoro/system.sh +++ b/.kokoro/system.sh @@ -1,45 +1,26 @@ #!/bin/bash # Copyright 2020 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 -# -# https://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. - -# `-e` enables the script to automatically fail when a command fails -# `-o pipefail` sets the exit code to non-zero if any command fails, -# or zero if all commands in the pipeline exit successfully. +# Licensed under the Apache License, Version 2.0 (the "License"); ... set -eo pipefail -# Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 - -# Setup firestore account credentials export FIRESTORE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/firebase-credentials.json - export PROJECT_ROOT=$(realpath $(dirname "${BASH_SOURCE[0]}")/..) cd "$PROJECT_ROOT" - -# This is needed in order for `git diff` to succeed git config --global --add safe.directory $(realpath .) -RETVAL=0 +# HOISTED: Install uv exactly once globally before we fan out +echo "Installing uv globally..." +python3 -m pip install uv +RETVAL=0 pwd run_package_test() { local package_name=$1 local package_path="packages/${package_name}" - # Declare local overrides to prevent bleeding into the next loop iteration local PROJECT_ID local GOOGLE_APPLICATION_CREDENTIALS local NOX_FILE @@ -52,7 +33,6 @@ run_package_test() { case "${package_name}" in "google-auth") - # Copy files needed for google-auth system tests mkdir -p "${package_path}/system_tests/data" cp "${KOKORO_GFILE_DIR}/google-auth-service-account.json" "${package_path}/system_tests/data/service_account.json" cp "${KOKORO_GFILE_DIR}/google-auth-authorized-user.json" "${package_path}/system_tests/data/authorized_user.json" @@ -72,14 +52,13 @@ run_package_test() { ;; esac - # Export variables for the duration of this function's sub-processes export PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS NOX_FILE NOX_SESSION export GOOGLE_CLOUD_PROJECT="${PROJECT_ID}" + + # NEW: Subshell-isolated GCP auth. Never modify the global gcloud config! + export CLOUDSDK_CORE_PROJECT="${PROJECT_ID}" + export CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE="${GOOGLE_APPLICATION_CREDENTIALS}" - gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" - gcloud config set project "$PROJECT_ID" - - # Run the actual test pushd "${package_path}" > /dev/null set +e "${system_test_script}" @@ -110,42 +89,58 @@ packages_with_system_tests=( "sqlalchemy-spanner" ) -# A file for running system tests system_test_script="${PROJECT_ROOT}/.kokoro/system-single.sh" - -# Join array elements with | for the pattern match packages_with_system_tests_pattern=$(printf "|*%s*" "${packages_with_system_tests[@]}") -packages_with_system_tests_pattern="${packages_with_system_tests_pattern:1}" # Remove the leading pipe +packages_with_system_tests_pattern="${packages_with_system_tests_pattern:1}" + +declare -A pids -# Run system tests for each package with directory packages/*/tests/system for path in `find 'packages' \ \( -type d -wholename 'packages/*/tests/system' \) -o \ \( -type d -wholename 'packages/*/system_tests' \) -o \ \( -type f -wholename 'packages/*/tests/system.py' \)`; do - # Extract the package name and define the relative package path - # 1. Remove the 'packages/' prefix - # 2. Remove everything after the first '/' package_name=${path#packages/} package_name=${package_name%%/*} package_path="packages/${package_name}" - # Determine if we should skip based on git diff files_to_check="${package_path}/CHANGELOG.md" if [[ $package_name == @($packages_with_system_tests_pattern) ]]; then files_to_check="${package_path}" fi - echo "checking changes with 'git diff "${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH}...${KOKORO_GITHUB_PULL_REQUEST_COMMIT}" -- ${files_to_check}'" + echo "checking changes with 'git diff ${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH}...${KOKORO_GITHUB_PULL_REQUEST_COMMIT} -- ${files_to_check}'" set +e package_modified=$(git diff "${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH}...${KOKORO_GITHUB_PULL_REQUEST_COMMIT}" -- ${files_to_check} | wc -l) set -e - if [[ "${package_modified}" -gt 0 || "$KOKORO_BUILD_ARTIFACTS_SUBDIR" == *"continuous"* ]]; then - # Call the function - its internal exports won't affect the next loop - run_package_test "$package_name" || RETVAL=$? +if [[ "${package_modified}" -gt 0 || "$KOKORO_BUILD_ARTIFACTS_SUBDIR" == *"continuous"* ]]; then + echo ">>> Dispatching ${package_name} in the background <<<" + + # Execute inside an isolated subshell ( ) to prevent GCP credential collisions + ( + run_package_test "$package_name" + ) & + + # Capture the PID of the subshell + pids["$package_name"]=$! else echo "No changes in ${package_name} and not a continuous build, skipping." fi done -exit ${RETVAL} + +echo "============================================================" +echo "All affected packages dispatched. Waiting for completion..." +echo "============================================================" + +for package in "${!pids[@]}"; do + wait ${pids[$package]} || { + echo "============================================================" + echo "ERROR: System tests failed for $package" + echo "============================================================" + RETVAL=1 + } +done + +echo "All concurrent tests completed." +exit ${RETVAL} \ No newline at end of file diff --git a/packages/google-analytics-admin/tests/unit/test_scale_validation.py b/packages/google-analytics-admin/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-analytics-admin/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-automl/tests/unit/test_scale_validation.py b/packages/google-cloud-automl/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-automl/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-cloudsecuritycompliance/tests/unit/test_scale_validation.py b/packages/google-cloud-cloudsecuritycompliance/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-cloudsecuritycompliance/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-discoveryengine/tests/unit/test_scale_validation.py b/packages/google-cloud-discoveryengine/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-discoveryengine/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-domains/tests/unit/test_scale_validation.py b/packages/google-cloud-domains/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-domains/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-edgenetwork/tests/unit/test_scale_validation.py b/packages/google-cloud-edgenetwork/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-edgenetwork/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-hypercomputecluster/tests/unit/test_scale_validation.py b/packages/google-cloud-hypercomputecluster/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-hypercomputecluster/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-kms/tests/unit/test_dummy.py b/packages/google-cloud-kms/tests/unit/test_dummy.py new file mode 100644 index 000000000000..2127e58f9386 --- /dev/null +++ b/packages/google-cloud-kms/tests/unit/test_dummy.py @@ -0,0 +1 @@ +# CI Prototype Verification diff --git a/packages/google-cloud-ndb/tests/unit/test_scale_validation.py b/packages/google-cloud-ndb/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-ndb/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-network-management/tests/unit/test_scale_validation.py b/packages/google-cloud-network-management/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-network-management/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-network-security/tests/unit/test_scale_validation.py b/packages/google-cloud-network-security/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-network-security/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-org-policy/tests/unit/test_scale_validation.py b/packages/google-cloud-org-policy/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-org-policy/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-os-login/tests/unit/test_scale_validation.py b/packages/google-cloud-os-login/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-os-login/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-pubsub/tests/unit/test_scale_validation.py b/packages/google-cloud-pubsub/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-pubsub/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-securesourcemanager/tests/unit/test_scale_validation.py b/packages/google-cloud-securesourcemanager/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/google-cloud-securesourcemanager/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/packages/google-cloud-storage/tests/unit/test_dummy.py b/packages/google-cloud-storage/tests/unit/test_dummy.py new file mode 100644 index 000000000000..2127e58f9386 --- /dev/null +++ b/packages/google-cloud-storage/tests/unit/test_dummy.py @@ -0,0 +1 @@ +# CI Prototype Verification diff --git a/packages/googleapis-common-protos/tests/unit/test_scale_validation.py b/packages/googleapis-common-protos/tests/unit/test_scale_validation.py new file mode 100644 index 000000000000..64bb4375d915 --- /dev/null +++ b/packages/googleapis-common-protos/tests/unit/test_scale_validation.py @@ -0,0 +1,3 @@ +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True diff --git a/test.py b/test.py new file mode 100755 index 000000000000..44b41cb2e540 --- /dev/null +++ b/test.py @@ -0,0 +1,20 @@ +# Ensure you are on the sandbox branch +git checkout experiment-testing-infra + +# Loop through the first 15 packages and inject an actual test file +for dir in $(find packages -mindepth 1 -maxdepth 1 -type d | head -n 15); do + # Ensure the target directory exists + mkdir -p "${dir}/tests/unit" + + # Inject the passing test + cat << 'EOF' > "${dir}/tests/unit/test_scale_validation.py" +def test_fan_out_execution(): + """Verify L5 Fan-Out architecture executes successfully.""" + assert True +EOF +done + +# Commit and push the massive execution test +git add packages/ +git commit -m "test: inject dummy unit tests across 15 packages to verify concurrent execution" +git push origin ci-architecture-prototype \ No newline at end of file