google-cloud-testutils 1.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
test_utils/retry.py ADDED
@@ -0,0 +1,228 @@
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import functools
16
+ import time
17
+
18
+ MAX_TRIES = 4
19
+ DELAY = 1
20
+ BACKOFF = 2
21
+
22
+
23
+ def _retry_all(_):
24
+ """Retry all caught exceptions."""
25
+ return True
26
+
27
+
28
+ class BackoffFailed(Exception):
29
+ """Retry w/ backoffs did not complete successfully."""
30
+
31
+
32
+ class RetryBase(object):
33
+ """Base for retrying calling a decorated function w/ exponential backoff.
34
+
35
+ :type max_tries: int
36
+ :param max_tries: Number of times to try (not retry) before giving up.
37
+
38
+ :type delay: int
39
+ :param delay: Initial delay between retries in seconds.
40
+
41
+ :type backoff: int
42
+ :param backoff: Backoff multiplier e.g. value of 2 will double the
43
+ delay each retry.
44
+
45
+ :type logger: logging.Logger instance
46
+ :param logger: Logger to use. If None, print.
47
+ """
48
+
49
+ def __init__(self, max_tries=MAX_TRIES, delay=DELAY, backoff=BACKOFF, logger=None):
50
+ self.max_tries = max_tries
51
+ self.delay = delay
52
+ self.backoff = backoff
53
+ self.logger = logger.warning if logger else print
54
+
55
+
56
+ class RetryErrors(RetryBase):
57
+ """Decorator for retrying given exceptions in testing.
58
+
59
+ :type exception: Exception or tuple of Exceptions
60
+ :param exception: The exception to check or may be a tuple of
61
+ exceptions to check.
62
+
63
+ :type error_predicate: function, takes caught exception, returns bool
64
+ :param error_predicate: Predicate evaluating whether to retry after a
65
+ caught exception.
66
+
67
+ :type max_tries: int
68
+ :param max_tries: Number of times to try (not retry) before giving up.
69
+
70
+ :type delay: int
71
+ :param delay: Initial delay between retries in seconds.
72
+
73
+ :type backoff: int
74
+ :param backoff: Backoff multiplier e.g. value of 2 will double the
75
+ delay each retry.
76
+
77
+ :type logger: logging.Logger instance
78
+ :param logger: Logger to use. If None, print.
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ exception,
84
+ error_predicate=_retry_all,
85
+ max_tries=MAX_TRIES,
86
+ delay=DELAY,
87
+ backoff=BACKOFF,
88
+ logger=None,
89
+ ):
90
+ super(RetryErrors, self).__init__(max_tries, delay, backoff, logger)
91
+ self.exception = exception
92
+ self.error_predicate = error_predicate
93
+
94
+ def __call__(self, to_wrap):
95
+ @functools.wraps(to_wrap)
96
+ def wrapped_function(*args, **kwargs):
97
+ tries = 0
98
+ while tries < self.max_tries:
99
+ try:
100
+ return to_wrap(*args, **kwargs)
101
+ except self.exception as caught_exception:
102
+ if not self.error_predicate(caught_exception):
103
+ raise
104
+
105
+ delay = self.delay * self.backoff**tries
106
+ msg = "%s, Trying again in %d seconds..." % (
107
+ caught_exception,
108
+ delay,
109
+ )
110
+ self.logger(msg)
111
+
112
+ time.sleep(delay)
113
+ tries += 1
114
+ return to_wrap(*args, **kwargs)
115
+
116
+ return wrapped_function
117
+
118
+
119
+ class RetryResult(RetryBase):
120
+ """Decorator for retrying based on non-error result.
121
+
122
+ :type result_predicate: function, takes result, returns bool
123
+ :param result_predicate: Predicate evaluating whether to retry after a
124
+ result is returned.
125
+
126
+ :type max_tries: int
127
+ :param max_tries: Number of times to try (not retry) before giving up.
128
+
129
+ :type delay: int
130
+ :param delay: Initial delay between retries in seconds.
131
+
132
+ :type backoff: int
133
+ :param backoff: Backoff multiplier e.g. value of 2 will double the
134
+ delay each retry.
135
+
136
+ :type logger: logging.Logger instance
137
+ :param logger: Logger to use. If None, print.
138
+ """
139
+
140
+ def __init__(
141
+ self,
142
+ result_predicate,
143
+ max_tries=MAX_TRIES,
144
+ delay=DELAY,
145
+ backoff=BACKOFF,
146
+ logger=None,
147
+ ):
148
+ super(RetryResult, self).__init__(max_tries, delay, backoff, logger)
149
+ self.result_predicate = result_predicate
150
+
151
+ def __call__(self, to_wrap):
152
+ @functools.wraps(to_wrap)
153
+ def wrapped_function(*args, **kwargs):
154
+ tries = 0
155
+ while tries < self.max_tries:
156
+ result = to_wrap(*args, **kwargs)
157
+ if self.result_predicate(result):
158
+ return result
159
+
160
+ delay = self.delay * self.backoff**tries
161
+ msg = "%s. Trying again in %d seconds..." % (
162
+ self.result_predicate.__name__,
163
+ delay,
164
+ )
165
+ self.logger(msg)
166
+
167
+ time.sleep(delay)
168
+ tries += 1
169
+ raise BackoffFailed()
170
+
171
+ return wrapped_function
172
+
173
+
174
+ class RetryInstanceState(RetryBase):
175
+ """Decorator for retrying based on instance state.
176
+
177
+ :type instance_predicate: function, takes instance, returns bool
178
+ :param instance_predicate: Predicate evaluating whether to retry after an
179
+ API-invoking method is called.
180
+
181
+ :type max_tries: int
182
+ :param max_tries: Number of times to try (not retry) before giving up.
183
+
184
+ :type delay: int
185
+ :param delay: Initial delay between retries in seconds.
186
+
187
+ :type backoff: int
188
+ :param backoff: Backoff multiplier e.g. value of 2 will double the
189
+ delay each retry.
190
+
191
+ :type logger: logging.Logger instance
192
+ :param logger: Logger to use. If None, print.
193
+ """
194
+
195
+ def __init__(
196
+ self,
197
+ instance_predicate,
198
+ max_tries=MAX_TRIES,
199
+ delay=DELAY,
200
+ backoff=BACKOFF,
201
+ logger=None,
202
+ ):
203
+ super(RetryInstanceState, self).__init__(max_tries, delay, backoff, logger)
204
+ self.instance_predicate = instance_predicate
205
+
206
+ def __call__(self, to_wrap):
207
+ instance = to_wrap.__self__ # only instance methods allowed
208
+
209
+ @functools.wraps(to_wrap)
210
+ def wrapped_function(*args, **kwargs):
211
+ tries = 0
212
+ while tries < self.max_tries:
213
+ result = to_wrap(*args, **kwargs)
214
+ if self.instance_predicate(instance):
215
+ return result
216
+
217
+ delay = self.delay * self.backoff**tries
218
+ msg = "%s. Trying again in %d seconds..." % (
219
+ self.instance_predicate.__name__,
220
+ delay,
221
+ )
222
+ self.logger(msg)
223
+
224
+ time.sleep(delay)
225
+ tries += 1
226
+ raise BackoffFailed()
227
+
228
+ return wrapped_function
test_utils/system.py ADDED
@@ -0,0 +1,80 @@
1
+ # Copyright 2014 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import print_function
16
+ import os
17
+ import sys
18
+ import time
19
+
20
+ import google.auth.credentials # type: ignore
21
+ from google.auth.environment_vars import CREDENTIALS as TEST_CREDENTIALS # type: ignore
22
+
23
+
24
+ # From shell environ. May be None.
25
+ CREDENTIALS = os.getenv(TEST_CREDENTIALS)
26
+
27
+ ENVIRON_ERROR_MSG = """\
28
+ To run the system tests, you need to set some environment variables.
29
+ Please check the CONTRIBUTING guide for instructions.
30
+ """
31
+
32
+
33
+ class EmulatorCreds(google.auth.credentials.Credentials):
34
+ """A mock credential object.
35
+
36
+ Used to avoid unnecessary token refreshing or reliance on the network
37
+ while an emulator is running.
38
+ """
39
+
40
+ def __init__(self): # pylint: disable=super-init-not-called
41
+ self.token = b"seekrit"
42
+ self.expiry = None
43
+
44
+ @property
45
+ def valid(self):
46
+ """Would-be validity check of the credentials.
47
+
48
+ Always is :data:`True`.
49
+ """
50
+ return True
51
+
52
+ def refresh(self, unused_request): # pylint: disable=unused-argument
53
+ """Off-limits implementation for abstract method."""
54
+ raise RuntimeError("Should never be refreshed.")
55
+
56
+
57
+ def check_environ():
58
+ err_msg = None
59
+ if CREDENTIALS is None:
60
+ err_msg = "\nMissing variables: " + TEST_CREDENTIALS
61
+ elif not os.path.isfile(CREDENTIALS):
62
+ err_msg = "\nThe %s path %r is not a file." % (TEST_CREDENTIALS, CREDENTIALS)
63
+
64
+ if err_msg is not None:
65
+ msg = ENVIRON_ERROR_MSG + err_msg
66
+ print(msg, file=sys.stderr)
67
+ sys.exit(1)
68
+
69
+
70
+ def unique_resource_id(delimiter="_"):
71
+ """A unique identifier for a resource.
72
+
73
+ Intended to help locate resources created in particular
74
+ testing environments and at particular times.
75
+ """
76
+ build_id = os.getenv("CIRCLE_BUILD_NUM", "")
77
+ if build_id == "":
78
+ return "%s%d" % (delimiter, 1000 * time.time())
79
+ else:
80
+ return "%s%s%s%d" % (delimiter, build_id, delimiter, time.time())
test_utils/version.py ADDED
@@ -0,0 +1,15 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ __version__ = "1.6.1"
@@ -0,0 +1,118 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2019 Google LLC
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import os
18
+
19
+ import pytest # type: ignore
20
+
21
+
22
+ INSIDE_VPCSC_ENVVAR = "GOOGLE_CLOUD_TESTS_IN_VPCSC"
23
+ PROJECT_INSIDE_ENVVAR = "PROJECT_ID"
24
+ PROJECT_OUTSIDE_ENVVAR = "GOOGLE_CLOUD_TESTS_VPCSC_OUTSIDE_PERIMETER_PROJECT"
25
+ BUCKET_OUTSIDE_ENVVAR = "GOOGLE_CLOUD_TESTS_VPCSC_OUTSIDE_PERIMETER_BUCKET"
26
+
27
+
28
+ class VPCSCTestConfig(object):
29
+ """System test utility for VPCSC detection.
30
+
31
+ See: https://cloud.google.com/vpc-service-controls/docs/
32
+ """
33
+
34
+ @property
35
+ def inside_vpcsc(self):
36
+ """Test whether the test environment is configured to run inside VPCSC.
37
+
38
+ Returns:
39
+ bool:
40
+ true if the environment is configured to run inside VPCSC,
41
+ else false.
42
+ """
43
+ return INSIDE_VPCSC_ENVVAR in os.environ
44
+
45
+ @property
46
+ def project_inside(self):
47
+ """Project ID for testing outside access.
48
+
49
+ Returns:
50
+ str: project ID used for testing outside access; None if undefined.
51
+ """
52
+ return os.environ.get(PROJECT_INSIDE_ENVVAR, None)
53
+
54
+ @property
55
+ def project_outside(self):
56
+ """Project ID for testing inside access.
57
+
58
+ Returns:
59
+ str: project ID used for testing inside access; None if undefined.
60
+ """
61
+ return os.environ.get(PROJECT_OUTSIDE_ENVVAR, None)
62
+
63
+ @property
64
+ def bucket_outside(self):
65
+ """GCS bucket for testing inside access.
66
+
67
+ Returns:
68
+ str: bucket ID used for testing inside access; None if undefined.
69
+ """
70
+ return os.environ.get(BUCKET_OUTSIDE_ENVVAR, None)
71
+
72
+ def skip_if_inside_vpcsc(self, testcase):
73
+ """Test decorator: skip if running inside VPCSC."""
74
+ reason = (
75
+ "Running inside VPCSC. "
76
+ "Unset the {} environment variable to enable this test."
77
+ ).format(INSIDE_VPCSC_ENVVAR)
78
+ skip = pytest.mark.skipif(self.inside_vpcsc, reason=reason)
79
+ return skip(testcase)
80
+
81
+ def skip_unless_inside_vpcsc(self, testcase):
82
+ """Test decorator: skip if running outside VPCSC."""
83
+ reason = (
84
+ "Running outside VPCSC. "
85
+ "Set the {} environment variable to enable this test."
86
+ ).format(INSIDE_VPCSC_ENVVAR)
87
+ skip = pytest.mark.skipif(not self.inside_vpcsc, reason=reason)
88
+ return skip(testcase)
89
+
90
+ def skip_unless_inside_project(self, testcase):
91
+ """Test decorator: skip if inside project env var not set."""
92
+ reason = (
93
+ "Project ID for running inside VPCSC not set. "
94
+ "Set the {} environment variable to enable this test."
95
+ ).format(PROJECT_INSIDE_ENVVAR)
96
+ skip = pytest.mark.skipif(self.project_inside is None, reason=reason)
97
+ return skip(testcase)
98
+
99
+ def skip_unless_outside_project(self, testcase):
100
+ """Test decorator: skip if outside project env var not set."""
101
+ reason = (
102
+ "Project ID for running outside VPCSC not set. "
103
+ "Set the {} environment variable to enable this test."
104
+ ).format(PROJECT_OUTSIDE_ENVVAR)
105
+ skip = pytest.mark.skipif(self.project_outside is None, reason=reason)
106
+ return skip(testcase)
107
+
108
+ def skip_unless_outside_bucket(self, testcase):
109
+ """Test decorator: skip if outside bucket env var not set."""
110
+ reason = (
111
+ "Bucket ID for running outside VPCSC not set. "
112
+ "Set the {} environment variable to enable this test."
113
+ ).format(BUCKET_OUTSIDE_ENVVAR)
114
+ skip = pytest.mark.skipif(self.bucket_outside is None, reason=reason)
115
+ return skip(testcase)
116
+
117
+
118
+ vpcsc_config = VPCSCTestConfig()