-
Notifications
You must be signed in to change notification settings - Fork 1.7k
chore: add test sharding to unit tests #17438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 41 commits
01d4753
a7502d4
9ad0f0c
02c7653
3c8d057
73ab091
74f1ef7
a450254
af4d10c
15916df
49cac0f
d8dd522
de74a68
46d1583
6e3531d
e6167bd
fd5792d
8b825d5
32968a9
ff1274f
c49b06d
1cd8c1f
a0f5c9a
71f444a
cf1a1f1
b00967f
7bb323c
3633089
980d8eb
a5cc497
b36b559
c3cf49e
acdfb18
9c80a51
cf554ca
886d6d9
83ca04a
ae05642
7b565a4
72503de
e34b654
6c1be8b
8fb68d3
fb90860
6123e7a
c99a693
ee7cabc
40d31e3
89e5499
a1fab95
92626bd
8c4d2dd
7ccca59
2e9bafc
f14d489
e4495b8
80f0061
964e142
b4733da
8ea10ec
bdc977a
5cb0e26
deaabe7
b20bb1a
15421a7
b4f9d9d
b9f750d
073dc07
da2f41e
e611fe7
e453d2c
45b5e68
fa06713
cd33370
5bfe2d1
6c0566f
e1bedf1
60a3e74
a4566cf
010e1ad
e35ea34
d04104d
cf3de20
f1eb3a4
c70613a
eaf75e9
fb71278
a05d1fb
04fb332
4382fcc
4e8892c
7ca0171
eefea36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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))] | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to filter out hidden packages?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a brief comment here explaining the fallback behaviour?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using Using a contiguous even distribution formula ensures that the difference in package count between any two shards is at most 1.
Suggested change
|
||||||||||||||||||||||||||||||
| 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)) | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| branch = True | ||
|
|
||
| [report] | ||
| fail_under = 99 | ||
| show_missing = True | ||
| omit = | ||
| google/cloud/firestore/__init__.py | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? https://github.com/googleapis/google-cloud-python/tree/main/packages/sqlalchemy-spanner/tests/unit
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test change for sharding |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
os.path.joinand 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 thestartswithcheck inget_packages_to_testto fail on Windows environments.Using a consistent forward slash format for package paths avoids this issue.