Skip to content
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
01d4753
added unit test sharding
daniel-sanche Jun 12, 2026
a7502d4
run system tests concurrently
daniel-sanche Jun 12, 2026
9ad0f0c
added changes to many packages for testing
daniel-sanche Jun 12, 2026
02c7653
added sharding test file
daniel-sanche Jun 12, 2026
3c8d057
added unit-complete to gather all shards
daniel-sanche Jun 12, 2026
73ab091
changed shard params
daniel-sanche Jun 12, 2026
74f1ef7
fixed tests
daniel-sanche Jun 12, 2026
a450254
fix coverage
daniel-sanche Jun 12, 2026
af4d10c
fix system tests
daniel-sanche Jun 12, 2026
15916df
split out system test logs
daniel-sanche Jun 12, 2026
49cac0f
attempt fix for lint
daniel-sanche Jun 12, 2026
d8dd522
update system tests to show logs for each target
daniel-sanche Jun 12, 2026
de74a68
update sharding logic
daniel-sanche Jun 12, 2026
46d1583
fixed lint/mypy runs
daniel-sanche Jun 12, 2026
6e3531d
removed sqlalchemy-spanner from touched packages
daniel-sanche Jun 12, 2026
e6167bd
system tests print all logs in main build log
daniel-sanche Jun 12, 2026
fd5792d
removed many SHARD_TEST files
daniel-sanche Jun 12, 2026
8b825d5
updated lint and mypy logic
daniel-sanche Jun 12, 2026
32968a9
add shard descriptions
daniel-sanche Jun 12, 2026
ff1274f
remove global run on ci/ change
daniel-sanche Jun 12, 2026
c49b06d
attempt cover fix
daniel-sanche Jun 12, 2026
1cd8c1f
attempt fix for cover
daniel-sanche Jun 12, 2026
a0f5c9a
allow hidden files for cover
daniel-sanche Jun 12, 2026
71f444a
fail fast
daniel-sanche Jun 12, 2026
cf1a1f1
use individual coverage checks
daniel-sanche Jun 12, 2026
b00967f
loosen firestore coverage requirement
daniel-sanche Jun 12, 2026
7bb323c
activated more packages
daniel-sanche Jun 12, 2026
3633089
10 packages total
daniel-sanche Jun 13, 2026
980d8eb
change default for coverage percent
daniel-sanche Jun 13, 2026
a5cc497
activated 11th package (enable sharding)
daniel-sanche Jun 13, 2026
b36b559
fixed typo
daniel-sanche Jun 13, 2026
c3cf49e
change shard logic
daniel-sanche Jun 13, 2026
acdfb18
iterating on shard logic
daniel-sanche Jun 13, 2026
9c80a51
rename tests
daniel-sanche Jun 13, 2026
cf554ca
add label to number in shard
daniel-sanche Jun 13, 2026
886d6d9
unit tests fail if initialize fails
daniel-sanche Jun 13, 2026
83ca04a
added known-bad package
daniel-sanche Jun 13, 2026
ae05642
added summary to cover step
daniel-sanche Jun 13, 2026
7b565a4
added no-fail .coveragerc to sqlalchemy-spanner
daniel-sanche Jun 13, 2026
72503de
Merge branch 'main' into ci_sharding
daniel-sanche Jun 18, 2026
e34b654
reverted systen test changes
daniel-sanche Jun 19, 2026
6c1be8b
added github tag to test all packages
daniel-sanche Jul 6, 2026
8fb68d3
re-run when label added
daniel-sanche Jul 6, 2026
fb90860
don't trigger on label changes
daniel-sanche Jul 6, 2026
6123e7a
revered unrelated changes
daniel-sanche Jul 6, 2026
c99a693
increased shard count
daniel-sanche Jul 6, 2026
ee7cabc
isolate specific packages
daniel-sanche Jul 6, 2026
40d31e3
isolated shards should be included in max_shard count
daniel-sanche Jul 6, 2026
89e5499
changed shard names
daniel-sanche Jul 6, 2026
a1fab95
use pacakge weights instead of isolation
daniel-sanche Jul 6, 2026
92626bd
parallelize final cover check
daniel-sanche Jul 6, 2026
8c4d2dd
resolve merge conflict
daniel-sanche Jul 6, 2026
7ccca59
Merge branch 'main' into ci_sharding
daniel-sanche Jul 6, 2026
2e9bafc
added additional weights
daniel-sanche Jul 6, 2026
f14d489
pack shards alphabetically
daniel-sanche Jul 6, 2026
e4495b8
fixed typo
daniel-sanche Jul 6, 2026
80f0061
avoid combining all coverage files
daniel-sanche Jul 6, 2026
964e142
optimized coverage step
daniel-sanche Jul 7, 2026
b4733da
fix coverage files
daniel-sanche Jul 7, 2026
8ea10ec
change MAX_JOBS
daniel-sanche Jul 7, 2026
bdc977a
end early if fail_under = 0
daniel-sanche Jul 7, 2026
5cb0e26
added pip cache
daniel-sanche Jul 7, 2026
deaabe7
fixing bug in coverage search
daniel-sanche Jul 7, 2026
b20bb1a
enable test against ces
daniel-sanche Jul 7, 2026
15421a7
removed empty file
daniel-sanche Jul 7, 2026
b4f9d9d
updated find call
daniel-sanche Jul 7, 2026
b9f750d
combine coverage in shards
daniel-sanche Jul 7, 2026
073dc07
Revert "combine coverage in shards"
daniel-sanche Jul 7, 2026
da2f41e
use RAM disk for coverage results
daniel-sanche Jul 7, 2026
e611fe7
made max shards configurable
daniel-sanche Jul 7, 2026
e453d2c
added test files
daniel-sanche Jul 7, 2026
45b5e68
define package weights in yaml
daniel-sanche Jul 7, 2026
fa06713
added fast path for coverage check
daniel-sanche Jul 7, 2026
cd33370
undo fast-path optimization
daniel-sanche Jul 7, 2026
5bfe2d1
only upload coverage for first and last supported version
daniel-sanche Jul 7, 2026
6c0566f
use 3.10 as lower bound
daniel-sanche Jul 7, 2026
e1bedf1
avoid going over max_shards
daniel-sanche Jul 7, 2026
60a3e74
fixed coverage condition
daniel-sanche Jul 7, 2026
a4566cf
use all runtimes in coverage
daniel-sanche Jul 7, 2026
010e1ad
remove empty files
daniel-sanche Jul 7, 2026
e35ea34
removed duplication
daniel-sanche Jul 7, 2026
d04104d
cleaned up conditional tests
daniel-sanche Jul 7, 2026
cf3de20
added back subdirs
daniel-sanche Jul 7, 2026
f1eb3a4
added comments to sharding script
daniel-sanche Jul 7, 2026
c70613a
moved script out of yaml
daniel-sanche Jul 7, 2026
eaf75e9
print only count for successes
daniel-sanche Jul 7, 2026
fb71278
Merge branch 'main' into ci_sharding
daniel-sanche Jul 17, 2026
a05d1fb
update spanner coveragerc
daniel-sanche Jul 17, 2026
04fb332
changed sqlalchemy-spanner coverage value
daniel-sanche Jul 17, 2026
4382fcc
added comment
daniel-sanche Jul 22, 2026
4e8892c
fixed truncation issues
daniel-sanche Jul 22, 2026
7ca0171
Merge branch 'main' into ci_sharding
daniel-sanche Jul 24, 2026
eefea36
Merge branch 'main' into ci_sharding
daniel-sanche Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 52 additions & 4 deletions .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,40 @@ permissions:
contents: read

jobs:
initialize:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Get package shards
id: set-matrix
env:
BUILD_TYPE: presubmit
TARGET_BRANCH: ${{ github.base_ref || github.event.merge_group.base_ref }}
run: |
if [ -n "$TARGET_BRANCH" ]; then
git fetch origin "$TARGET_BRANCH" --deepen=200 || true
fi
echo "matrix=$(python3 ci/get_package_shards.py)" >> $GITHUB_OUTPUT

unit:
needs: initialize
if: needs.initialize.outputs.matrix != '[]' && needs.initialize.outputs.matrix != ''
runs-on: ubuntu-22.04
strategy:
fail-fast: true
matrix:
python: ['3.9', '3.10', "3.11", "3.12", "3.13", "3.14"]
package_shard: ${{ fromJson(needs.initialize.outputs.matrix) }}
name: ${{ matrix.package_shard.is_sharded && format('unit ({0}, {1})', matrix.python, matrix.package_shard.name) || format('unit ({0})', matrix.python) }}
steps:
- name: Checkout
uses: actions/checkout@v6
Expand All @@ -34,22 +63,41 @@ jobs:
run: |
python -m pip install --upgrade setuptools pip wheel
python -m pip install nox
- name: Run unit tests
- name: Run unit tests for ${{ matrix.package_shard.description }}
env:
COVERAGE_FILE: ${{ github.workspace }}/.coverage-${{ matrix.python }}
BUILD_TYPE: presubmit
TARGET_BRANCH: ${{ github.base_ref || github.event.merge_group.base_ref }}
TEST_TYPE: unit
PY_VERSION: ${{ matrix.python }}
PACKAGE_LIST: ${{ matrix.package_shard.packages }}
run: |
ci/run_conditional_tests.sh
- name: Upload coverage results
uses: actions/upload-artifact@v4
with:
name: coverage-artifact-${{ matrix.python }}
path: .coverage-${{ matrix.python }}
name: coverage-artifact-${{ matrix.python }}-${{ matrix.package_shard.index }}
path: .coverage.${{ matrix.python }}.*
include-hidden-files: true

all-tests:
needs: [initialize, unit]
if: always()
runs-on: ubuntu-latest
steps:
- name: Check unit test results
run: |
# 1. Check initialize job
if [[ "${{ needs.initialize.result }}" != "success" ]]; then
echo "Error: The initialize job status was: ${{ needs.initialize.result }}"
exit 1
fi
# 2. Check unit test shards
if [[ "${{ needs.unit.result }}" != "success" && "${{ needs.unit.result }}" != "skipped" ]]; then
echo "Unit tests failed"
exit 1
fi
echo "All unit tests passed or were skipped"

cover:
runs-on: ubuntu-latest
needs:
Expand Down
94 changes: 94 additions & 0 deletions ci/get_package_shards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import os
import subprocess
import json
import math
import sys

def get_packages():
subdirs = ['packages']
packages = []
for subdir in subdirs:
if not os.path.exists(subdir):
continue
# Use the same sorting as the shell script
pkg_dirs = [os.path.join(subdir, d) + '/' for d in os.listdir(subdir) if os.path.isdir(os.path.join(subdir, d))]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using os.path.join and then appending a hardcoded forward slash (/) can result in mixed path separators on Windows (e.g., packages\\google-cloud-bigtable/). Since git diff outputs always use forward slashes, this mismatch will cause the startswith check in get_packages_to_test to fail on Windows environments.

Using a consistent forward slash format for package paths avoids this issue.

Suggested change
pkg_dirs = [os.path.join(subdir, d) + '/' for d in os.listdir(subdir) if os.path.isdir(os.path.join(subdir, d))]
pkg_dirs = [f"{subdir}/{d}/" for d in os.listdir(subdir) if os.path.isdir(os.path.join(subdir, d))]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to filter out hidden packages?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, do we? I thought all packages have unit tests. Which ones would be candidates to filter out?

packages.extend(sorted(pkg_dirs))
return packages

def get_packages_to_test():
build_type = os.environ.get('BUILD_TYPE', 'presubmit')
target_branch = os.environ.get('TARGET_BRANCH', 'main')

all_packages = get_packages()

if build_type == 'presubmit':
git_diff_arg = f"origin/{target_branch}..."
elif build_type == 'continuous':
git_diff_arg = "HEAD~.."
else:
return all_packages

try:
res = subprocess.check_output(['git', 'diff', '--name-only', git_diff_arg]).decode('utf-8')
changed_files = res.splitlines()
except subprocess.CalledProcessError:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a brief comment here explaining the fallback behaviour?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

return all_packages

to_test = []
for pkg in all_packages:
# Check if any changed file starts with the package path
if any(f.startswith(pkg) for f in changed_files):
to_test.append(pkg)

return to_test

def group_packages(packages):
if not packages:
return []

num_packages = len(packages)

# 1. Only shard if > 10 packages are being touched
# 2. Only add a new shard if any shard would have > 10 packages.
# To guarantee that no shard contains more than 10 packages (when distributed evenly),
# we need S >= N / 10, which means S = ceil(N / 10).
num_shards = math.ceil(num_packages / 10)

# Ensure at least 1 shard if we have packages
num_shards = max(1, num_shards)

# 3. Top out at 10 shards
num_shards = min(10, num_shards)

# Distribute packages between them as evenly as possible
shard_size = math.ceil(num_packages / num_shards)

shards = []
for i in range(num_shards):
start = i * shard_size
end = min((i + 1) * shard_size, num_packages)
if start >= num_packages:
break
shard_packages = packages[start:end]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using math.ceil(num_packages / num_shards) as a fixed chunk size can lead to highly unbalanced shards when the number of shards is capped at 10. For example, if there are 101 packages, num_shards is capped at 10, and shard_size becomes ceil(101/10) = 11. This results in 9 shards of size 11 and 1 shard of size 2, which is highly unbalanced and defeats the purpose of efficient parallel sharding.

Using a contiguous even distribution formula ensures that the difference in package count between any two shards is at most 1.

Suggested change
shard_size = math.ceil(num_packages / num_shards)
shards = []
for i in range(num_shards):
start = i * shard_size
end = min((i + 1) * shard_size, num_packages)
if start >= num_packages:
break
shard_packages = packages[start:end]
shards = []
for i in range(num_shards):
start = (i * num_packages) // num_shards
end = ((i + 1) * num_packages) // num_shards
shard_packages = packages[start:end]

index = i + 1
name = f"Shard {index}"
num_in_shard = len(shard_packages)
# Calculate a descriptive range for step visibility
if len(shard_packages) == 1:
desc = shard_packages[0].strip('/').split('/')[-1]
else:
desc = f"{shard_packages[0].strip('/').split('/')[-1]}...{shard_packages[-1].strip('/').split('/')[-1]} ({num_in_shard} packages)"

shards.append({
"name": name,
"index": index,
"description": desc,
"packages": " ".join(shard_packages),
"is_sharded": num_shards > 1
})
return shards

if __name__ == "__main__":
packages = get_packages_to_test()
shards = group_packages(packages)
print(json.dumps(shards))
61 changes: 48 additions & 13 deletions ci/run_conditional_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,45 +47,73 @@ git config --global url."${PROJECT_ROOT}".insteadOf "https://github.com/googleap
# A script file for running the test in a sub project.
test_script="${PROJECT_ROOT}/ci/run_single_test.sh"

if [ -n "${PACKAGE_LIST}" ]; then
echo "Using provided PACKAGE_LIST"
to_test=(${PACKAGE_LIST})
RETVAL=0
for d in ${to_test[@]}; do
echo "running test in ${d}"
pushd ${d}
set +e

# Ensure unique coverage file per package to avoid DataError
# when combining statement and branch coverage.
# Strip trailing slash from directory name for the filename.
pkg_name_clean=$(echo ${d} | sed 's|/$||' | sed 's|/|_|g')
export COVERAGE_FILE="${PROJECT_ROOT}/.coverage.${PY_VERSION}.${pkg_name_clean}"

${test_script}
ret=$?
set -e
if [ ${ret} -ne 0 ]; then
RETVAL=${ret}
fi
popd
done
exit ${RETVAL}
fi

if [[ ${BUILD_TYPE} == "presubmit" ]]; then
# For presubmit build, we want to know the difference from the
# common commit in the target branch.
GIT_DIFF_ARG="origin/$TARGET_BRANCH..."

# Then fetch enough history for finding the common commit.
git fetch origin "$TARGET_BRANCH" --deepen=200
git fetch origin "$TARGET_BRANCH" --deepen=200 || true

elif [[ ${BUILD_TYPE} == "continuous" ]]; then
# For continuous build, we want to know the difference in the last
# commit. This assumes we use squash commit when merging PRs.
GIT_DIFF_ARG="HEAD~.."

# Then fetch one last commit for getting the diff.
git fetch origin "$TARGET_BRANCH" --deepen=1
git fetch origin "$TARGET_BRANCH" --deepen=1 || true

else
# Run everything.
GIT_DIFF_ARG=""
fi

# Then detect changes in the test scripts.

set +e
git diff --quiet ${GIT_DIFF_ARG} ci
changed=$?
set -e

# Now we have a fixed list, but we can change it to autodetect if
# necessary.

# Sharding logic (fallback for manual runs)
subdirs=(
packages
)
TOTAL_SHARDS="${TOTAL_SHARDS:-1}"
SHARD_INDEX="${SHARD_INDEX:-1}"
count=0

RETVAL=0

for subdir in ${subdirs[@]}; do
for d in `ls -d ${subdir}/*/`; do
# Sort the directories to ensure consistent sharding across jobs
for d in `ls -d ${subdir}/*/ | sort`; do
# Sharding logic: only process directories that belong to this shard
if (( count % TOTAL_SHARDS != SHARD_INDEX - 1 )); then
((++count))
continue
fi
((++count))

should_test=false
if [ -n "${GIT_DIFF_ARG}" ]; then
echo "checking changes with 'git diff --quiet ${GIT_DIFF_ARG} ${d}'"
Expand All @@ -108,6 +136,13 @@ for subdir in ${subdirs[@]}; do
pushd ${d}
# Temporarily allow failure.
set +e

# Ensure unique coverage file per package to avoid DataError
# when combining statement and branch coverage.
# Strip trailing slash from directory name for the filename.
pkg_name_clean=$(echo ${d} | sed 's|/$||' | sed 's|/|_|g')
export COVERAGE_FILE="${PROJECT_ROOT}/.coverage.${PY_VERSION}.${pkg_name_clean}"

${test_script}
ret=$?
set -e
Expand Down
1 change: 1 addition & 0 deletions packages/google-backstory/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
1 change: 1 addition & 0 deletions packages/google-cloud-apiregistry/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
1 change: 1 addition & 0 deletions packages/google-cloud-bigtable/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
1 change: 1 addition & 0 deletions packages/google-cloud-ces/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
1 change: 1 addition & 0 deletions packages/google-cloud-chronicle/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
1 change: 1 addition & 0 deletions packages/google-cloud-dataproc/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
1 change: 1 addition & 0 deletions packages/google-cloud-firestore/.coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
branch = True

[report]
fail_under = 99
show_missing = True
omit =
google/cloud/firestore/__init__.py
Expand Down
1 change: 1 addition & 0 deletions packages/google-cloud-firestore/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
1 change: 1 addition & 0 deletions packages/google-cloud-gke-multicloud/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
1 change: 1 addition & 0 deletions packages/google-cloud-managedkafka/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
1 change: 1 addition & 0 deletions packages/google-cloud-optimization/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
1 change: 1 addition & 0 deletions packages/google-cloud-retail/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
36 changes: 36 additions & 0 deletions packages/sqlalchemy-spanner/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
#
# 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
#
# 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.

[run]
branch = True
omit =
google/__init__.py
google/cloud/sqlalchemy_spanner/requirements.py

[report]
fail_under = 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? Is there really no code coverage with the existing tests?

Evaluating coverage for package: packages/sqlalchemy-spanner
============================================================
Package has fail_under = 0, passing instantly with 100% success (even without coverage files)

https://github.com/googleapis/google-cloud-python/tree/main/packages/sqlalchemy-spanner/tests/unit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage wasn't being checked by this library before now, so this is representing the status quo. This is owned by a partner team, so I was thinking we could let them decide if/when to raise the bar.

But yeah, maybe it makes more sense to at least add a floor, so coverage doesn't accidentally drop further. The current value is 36, so I set the target to 35

show_missing = True
exclude_lines =
# Re-enable the standard pragma
pragma: NO COVER
# Ignore debug-only repr
def __repr__
# Ignore abstract methods
raise NotImplementedError
omit =
*/site-packages/*.py
google/__init__.py
google/cloud/sqlalchemy_spanner/requirements.py
1 change: 1 addition & 0 deletions packages/sqlalchemy-spanner/SHARD_TEST.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test change for sharding
14 changes: 13 additions & 1 deletion packages/sqlalchemy-spanner/noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class = StreamHandler
UNIT_TEST_STANDARD_DEPENDENCIES = [
"mock",
"pytest",
"pytest-cov",
]

UNIT_TEST_EXTERNAL_DEPENDENCIES = [
Expand Down Expand Up @@ -368,7 +369,18 @@ def unit(session, test_type):
*UNIT_TEST_DEPENDENCIES,
)
session.install(".")
session.run("py.test", "--quiet", os.path.join("tests/unit"), *session.posargs)
session.run(
"py.test",
"--quiet",
"--cov=google.cloud.sqlalchemy_spanner",
"--cov=tests/unit",
"--cov-append",
"--cov-config=.coveragerc",
"--cov-report=",
"--cov-fail-under=0",
os.path.join("tests/unit"),
*session.posargs,
)
return


Expand Down
Loading