Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit 48db1e6

Browse files
authored
feat(tests): Add integration test framework, goldens for 4 APIs [gapic-generator-python] (#905)
* feat(tests): Add integration test framework, goldens for 4 APIs * fix: exclude generated sources from style checks * fix: add integration tests to CI * fix: split out integration tests * fix: bazel install * fix: Use 3.8 only for integration tests * fix: add integration test steps to docs
1 parent 592ec06 commit 48db1e6

173 files changed

Lines changed: 55916 additions & 6 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/tests.yaml

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,34 @@ jobs:
313313
- name: Submit coverage data to codecov.
314314
run: codecov
315315
if: always()
316+
integration:
317+
runs-on: ubuntu-latest
318+
steps:
319+
- name: Cancel Previous Runs
320+
uses: styfle/cancel-workflow-action@0.7.0
321+
with:
322+
access_token: ${{ github.token }}
323+
- uses: actions/checkout@v2
324+
- name: Set up Python 3.8
325+
uses: actions/setup-python@v2
326+
with:
327+
python-version: 3.8
328+
- name: Install system dependencies.
329+
run: |
330+
sudo apt-get update
331+
sudo apt-get install -y curl pandoc unzip gcc
332+
- name: Install Bazel
333+
run: |
334+
wget -q "https://github.com/bazelbuild/bazel/releases/download/$BAZEL_VERSION/$BAZEL_BINARY"
335+
wget -q "https://github.com/bazelbuild/bazel/releases/download/$BAZEL_VERSION/$BAZEL_BINARY.sha256"
336+
sha256sum -c "$BAZEL_BINARY.sha256"
337+
sudo dpkg -i "$BAZEL_BINARY"
338+
env:
339+
BAZEL_VERSION: 3.5.0
340+
BAZEL_BINARY: bazel_3.5.0-linux-x86_64.deb
341+
- name: Integration Tests
342+
run: bazel test tests/integration:asset tests/integration:credentials tests/integration:logging tests/integration:redis
343+
316344
style-check:
317345
runs-on: ubuntu-latest
318346
steps:
@@ -330,4 +358,4 @@ jobs:
330358
python -m pip install autopep8
331359
- name: Check diff
332360
run: |
333-
find gapic tests -name "*.py" | xargs autopep8 --diff --exit-code
361+
find gapic tests -name "*.py" -not -path 'tests/integration/goldens/*' | xargs autopep8 --diff --exit-code

DEVELOPMENT.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
- Execute unit tests by running one of the sessions prefixed with `unit-`
66
- Example: `nox -s unit-3.8`
77
- Lint sources by running `autopep8`.
8+
9+
## Integration Tests
10+
- Running tests: `bazel test tests/integration:asset`. See the full list of targets in `tests/integration/BUILD.bazel`.
11+
- Updating golden files: `bazel run tests/integration:asset_update`

WORKSPACE

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,11 @@ apple_rules_dependencies()
5555
load("@build_bazel_apple_support//lib:repositories.bzl", "apple_support_dependencies")
5656

5757
apple_support_dependencies()
58+
59+
load("@com_google_googleapis//:repository_rules.bzl", "switched_rules_by_language")
60+
61+
switched_rules_by_language(
62+
name = "com_google_googleapis_imports",
63+
gapic = True,
64+
grpc = True,
65+
)

repositories.bzl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ def gapic_generator_python():
6161
urls = ["https://github.com/googleapis/gapic-generator/archive/03abac35ec0716c6f426ffc1532f9a62f1c9e6a2.zip"],
6262
)
6363

64+
_rules_gapic_version = "0.5.3"
65+
_maybe(
66+
http_archive,
67+
name = "rules_gapic",
68+
strip_prefix = "rules_gapic-%s" % _rules_gapic_version,
69+
urls = ["https://github.com/googleapis/rules_gapic/archive/v%s.tar.gz" % _rules_gapic_version],
70+
)
71+
72+
_maybe(
73+
http_archive,
74+
name = "com_google_googleapis",
75+
strip_prefix = "googleapis-51fe6432d4076a4c101f561967df4bf1f27818e1",
76+
urls = ["https://github.com/googleapis/googleapis/archive/51fe6432d4076a4c101f561967df4bf1f27818e1.zip"],
77+
)
78+
6479
def gapic_generator_register_toolchains():
6580
native.register_toolchains(
6681
"@gapic_generator_python//:pandoc_toolchain_linux",

rules_python_gapic/py_gapic.bzl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
load("@com_google_api_codegen//rules_gapic:gapic.bzl", "proto_custom_library")
15+
load("@rules_gapic//:gapic.bzl", "proto_custom_library")
1616

1717
def py_gapic_library(
1818
name,
@@ -34,7 +34,7 @@ def py_gapic_library(
3434

3535
file_args = {}
3636
if grpc_service_config:
37-
file_args[grpc_service_config] = "retry-config"
37+
file_args[grpc_service_config] = "retry-config"
3838

3939
proto_custom_library(
4040
name = srcjar_target_name,

rules_python_gapic/py_gapic_pkg.bzl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
load("@com_google_api_codegen//rules_gapic:gapic_pkg.bzl", "construct_package_dir_paths")
15+
load("@rules_gapic//:gapic_pkg.bzl", "construct_package_dir_paths")
1616

1717
def _py_gapic_src_pkg_impl(ctx):
1818
srcjar_srcs = []
@@ -66,5 +66,3 @@ def py_gapic_assembly_pkg(name, deps, assembly_name = None, **kwargs):
6666
package_dir = package_dir,
6767
**kwargs
6868
)
69-
70-

rules_python_gapic/test/BUILD.bazel

Whitespace-only changes.
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
def _diff_integration_goldens_impl(ctx):
2+
# Extract the Python source files from the generated 3 srcjars from API bazel target,
3+
# and put them in the temporary folder `codegen_tmp`.
4+
# Compare the `codegen_tmp` with the goldens folder e.g `tests/integration/goldens/redis`
5+
# and save the differences in output file `diff_output.txt`.
6+
7+
diff_output = ctx.outputs.diff_output
8+
check_diff_script = ctx.outputs.check_diff_script
9+
gapic_library = ctx.attr.gapic_library
10+
srcs = ctx.files.srcs
11+
api_name = ctx.attr.name
12+
13+
script = """
14+
mkdir codegen_tmp
15+
unzip {input_srcs} -d codegen_tmp
16+
diff -r codegen_tmp $PWD/tests/integration/goldens/{api_name} > {diff_output}
17+
exit 0 # Avoid a build failure.
18+
""".format(
19+
diff_output = diff_output.path,
20+
input_srcs = gapic_library[DefaultInfo].files.to_list()[0].path,
21+
api_name = api_name,
22+
)
23+
ctx.actions.run_shell(
24+
inputs = srcs + [
25+
gapic_library[DefaultInfo].files.to_list()[0],
26+
],
27+
outputs = [diff_output],
28+
command = script,
29+
)
30+
31+
# Check the generated diff_output file, if it is empty, that means there is no difference
32+
# between generated source code and goldens files, test should pass. If it is not empty, then
33+
# test will fail by exiting 1.
34+
35+
check_diff_script_content = """
36+
# This will not print diff_output to the console unless `--test_output=all` option
37+
# is enabled, it only emits the comparison results to the test.log.
38+
# We could not copy the diff_output.txt to the test.log ($XML_OUTPUT_FILE) because that
39+
# file is not existing at the moment. It is generated once test is finished.
40+
cat $PWD/tests/integration/{api_name}_diff_output.txt
41+
if [ -s $PWD/tests/integration/{api_name}_diff_output.txt ]
42+
then
43+
exit 1
44+
fi
45+
""".format(
46+
api_name = api_name,
47+
)
48+
49+
ctx.actions.write(
50+
output = check_diff_script,
51+
content = check_diff_script_content,
52+
)
53+
runfiles = ctx.runfiles(files = [ctx.outputs.diff_output])
54+
return [DefaultInfo(executable = check_diff_script, runfiles = runfiles)]
55+
56+
diff_integration_goldens_test = rule(
57+
attrs = {
58+
"gapic_library": attr.label(),
59+
"srcs": attr.label_list(
60+
allow_files = True,
61+
mandatory = True,
62+
),
63+
},
64+
outputs = {
65+
"diff_output": "%{name}_diff_output.txt",
66+
"check_diff_script": "%{name}_check_diff_script.sh",
67+
},
68+
implementation = _diff_integration_goldens_impl,
69+
test = True,
70+
)
71+
72+
def integration_test(name, target, data):
73+
# Bazel target `py_gapic_library` will generate 1 source jar that holds the
74+
# Gapic_library's python sources.
75+
diff_integration_goldens_test(
76+
name = name,
77+
gapic_library = target,
78+
srcs = data,
79+
)
80+
81+
def _overwrite_golden_impl(ctx):
82+
# Extract the Java source files from the generated 3 srcjars from API bazel target,
83+
# and put them in the temporary folder `codegen_tmp`, zip as `goldens_output_zip`.
84+
# Overwrite the goldens folder e.g `tests/integration/goldens/redis` with the
85+
# code generation in `goldens_output_zip`.
86+
87+
gapic_library = ctx.attr.gapic_library
88+
srcs = ctx.files.srcs
89+
90+
# Convert the name of bazel rules e.g. `redis_update` to `redis`
91+
# because we will need to overwrite the goldens files in `redis` folder.
92+
api_name = "_".join(ctx.attr.name.split("_")[:-1])
93+
goldens_output_zip = ctx.outputs.goldens_output_zip
94+
95+
script = """
96+
mkdir codegen_tmp
97+
unzip {input_srcs} -d codegen_tmp
98+
cd codegen_tmp
99+
zip -r ../{goldens_output_zip} .
100+
""".format(
101+
goldens_output_zip = goldens_output_zip.path,
102+
input_srcs = gapic_library[DefaultInfo].files.to_list()[0].path,
103+
)
104+
105+
ctx.actions.run_shell(
106+
inputs = srcs + [
107+
gapic_library[DefaultInfo].files.to_list()[0],
108+
],
109+
outputs = [goldens_output_zip],
110+
command = script,
111+
)
112+
113+
# Overwrite the goldens.
114+
golden_update_script_content = """
115+
cd ${{BUILD_WORKSPACE_DIRECTORY}}
116+
# Filename pattern-based removal is needed to preserve the BUILD.bazel file.
117+
find tests/Integration/goldens/{api_name}/ -name \\*.py-type f -delete
118+
find tests/Integration/goldens/{api_name}/ -name \\*.json -type f -delete
119+
unzip -ao {goldens_output_zip} -d tests/integration/goldens/{api_name}
120+
""".format(
121+
goldens_output_zip = goldens_output_zip.path,
122+
api_name = api_name,
123+
)
124+
ctx.actions.write(
125+
output = ctx.outputs.golden_update_script,
126+
content = golden_update_script_content,
127+
is_executable = True,
128+
)
129+
return [DefaultInfo(executable = ctx.outputs.golden_update_script)]
130+
131+
overwrite_golden = rule(
132+
attrs = {
133+
"gapic_library": attr.label(),
134+
"srcs": attr.label_list(
135+
allow_files = True,
136+
mandatory = True,
137+
),
138+
},
139+
outputs = {
140+
"goldens_output_zip": "%{name}.zip",
141+
"golden_update_script": "%{name}.sh",
142+
},
143+
executable = True,
144+
implementation = _overwrite_golden_impl,
145+
)
146+
147+
def golden_update(name, target, data):
148+
overwrite_golden(
149+
name = name,
150+
gapic_library = target,
151+
srcs = data,
152+
)

tests/integration/BUILD.bazel

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
load(
2+
"@gapic_generator_python//rules_python_gapic:py_gapic.bzl",
3+
"py_gapic_library",
4+
)
5+
load(
6+
"@gapic_generator_python//rules_python_gapic:py_gapic_pkg.bzl",
7+
"py_gapic_assembly_pkg",
8+
)
9+
load(
10+
"@gapic_generator_python//rules_python_gapic/test:integration_test.bzl",
11+
"golden_update",
12+
"integration_test",
13+
)
14+
load("@rules_proto//proto:defs.bzl", "proto_library")
15+
16+
package(default_visibility = ["//visibility:public"])
17+
18+
####################################################
19+
# Integration Test Rules
20+
#
21+
# Usage:
22+
# Run tests: bazel test tests/integration:asset
23+
# Update goldens: bazel run tests/integration:asset_update
24+
####################################################
25+
26+
INTEGRATION_TEST_LIBRARIES = [
27+
"asset", # Basic case.
28+
"credentials", # Check that the capital name edge case is handled.
29+
"logging", # Java package remapping in gapic.yaml.
30+
"redis", # Has a gapic.yaml.
31+
]
32+
33+
[integration_test(
34+
name = lib_name,
35+
data = ["//tests/integration/goldens/%s:goldens_files" % lib_name],
36+
target = ":%s_py_gapic" % lib_name,
37+
) for lib_name in INTEGRATION_TEST_LIBRARIES]
38+
39+
[golden_update(
40+
name = "%s_update" % lib_name,
41+
data = ["//tests/integration/goldens/%s:goldens_files" % lib_name],
42+
target = ":%s_py_gapic" % lib_name,
43+
) for lib_name in INTEGRATION_TEST_LIBRARIES]
44+
45+
####################################################
46+
# API Library Rules
47+
####################################################
48+
49+
# Asset.
50+
py_gapic_library(
51+
name = "asset_py_gapic",
52+
srcs = ["@com_google_googleapis//google/cloud/asset/v1:asset_proto"],
53+
grpc_service_config = "cloudasset_grpc_service_config.json",
54+
)
55+
56+
# Credentials.
57+
py_gapic_library(
58+
name = "credentials_py_gapic",
59+
srcs = ["@com_google_googleapis//google/iam/credentials/v1:credentials_proto"],
60+
grpc_service_config = "iamcredentials_grpc_service_config.json",
61+
)
62+
63+
# Logging.
64+
py_gapic_library(
65+
name = "logging_py_gapic",
66+
srcs = ["@com_google_googleapis//google/logging/v2:logging_proto"],
67+
grpc_service_config = "logging_grpc_service_config.json",
68+
opt_args = [
69+
"python-gapic-namespace=google.cloud",
70+
"python-gapic-name=logging",
71+
],
72+
)
73+
74+
py_gapic_library(
75+
name = "redis_py_gapic",
76+
srcs = ["@com_google_googleapis//google/cloud/redis/v1:redis_proto"],
77+
grpc_service_config = "redis_grpc_service_config.json",
78+
)

0 commit comments

Comments
 (0)