diff --git a/botocore-1.17.56.tar.gz b/botocore-1.17.56.tar.gz deleted file mode 100644 index 7a1a5dd..0000000 --- a/botocore-1.17.56.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5a72e1758f3c89c663d74eb733d313f69d059ab4fd571ad41829d666e3367392 -size 6813626 diff --git a/botocore-1.18.15.tar.gz b/botocore-1.18.15.tar.gz new file mode 100644 index 0000000..8cbbeb5 --- /dev/null +++ b/botocore-1.18.15.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b9179edbba61c96f5d1eaa4328c9cda686bd461e102c5878c4880479c24e268 +size 6926102 diff --git a/hide_py_pckgmgmt.patch b/hide_py_pckgmgmt.patch deleted file mode 100644 index cb03a66..0000000 --- a/hide_py_pckgmgmt.patch +++ /dev/null @@ -1,43 +0,0 @@ -diff -Nru botocore-1.15.0.orig/setup.py botocore-1.15.0/setup.py ---- botocore-1.15.0.orig/setup.py 2020-02-14 20:04:34.000000000 +0100 -+++ botocore-1.15.0/setup.py 2020-02-17 18:15:41.860693280 +0100 -@@ -23,18 +23,18 @@ - raise RuntimeError("Unable to find version string.") - - --requires = [ -- 'jmespath>=0.7.1,<1.0.0', -- 'docutils>=0.10,<0.16', -- 'python-dateutil>=2.1,<3.0.0', --] -+# requires = [ -+# 'jmespath>=0.7.1,<1.0.0', -+# 'docutils>=0.10,<0.16', -+# 'python-dateutil>=2.1,<3.0.0', -+# ] - - --if sys.version_info[:2] == (3, 4): -- # urllib3 dropped support for python 3.4 in point release 1.25.8 -- requires.append('urllib3>=1.20,<1.25.8') --else: -- requires.append('urllib3>=1.20,<1.26') -+# if sys.version_info[:2] == (3, 4): -+# # urllib3 dropped support for python 3.4 in point release 1.25.8 -+# requires.append('urllib3>=1.20,<1.25.8') -+# else: -+# requires.append('urllib3>=1.20,<1.26') - - - -@@ -50,8 +50,8 @@ - package_data={'botocore': ['cacert.pem', 'data/*.json', 'data/*/*.json'], - 'botocore.vendored.requests': ['*.pem']}, - include_package_data=True, -- install_requires=requires, -- extras_require={}, -+ # install_requires=requires, -+ # extras_require={}, - license="Apache License 2.0", - classifiers=[ - 'Development Status :: 5 - Production/Stable', diff --git a/python-botocore.changes b/python-botocore.changes index eb25b02..9043e72 100644 --- a/python-botocore.changes +++ b/python-botocore.changes @@ -1,3 +1,153 @@ +------------------------------------------------------------------- +Fri Oct 9 10:09:20 UTC 2020 - John Paul Adrian Glaubitz + +- Version update to 1.18.15 + * api-change:``ec2``: Update ec2 client to latest version + * api-change:``events``: Update events client to latest version + * api-change:``sns``: Update sns client to latest version + * api-change:``ce``: Update ce client to latest version + * api-change:``sagemaker``: Update sagemaker client to latest version + * api-change:``rds``: Update rds client to latest version + * api-change:``rekognition``: Update rekognition client to latest version +- from version 1.18.14 + * api-change:``mediapackage``: Update mediapackage client to latest version + * api-change:``ce``: Update ce client to latest version + * api-change:``compute-optimizer``: Update compute-optimizer client to latest version + * api-change:``elasticache``: Update elasticache client to latest version +- from version 1.18.13 + * api-change:``dms``: Update dms client to latest version + * api-change:``kinesisanalyticsv2``: Update kinesisanalyticsv2 client to latest version + * api-change:``marketplace-catalog``: Update marketplace-catalog client to latest version + * api-change:``ec2``: Update ec2 client to latest version +- from version 1.18.12 + * api-change:``dynamodbstreams``: Update dynamodbstreams client to latest version + * api-change:``sagemaker``: Update sagemaker client to latest version + * api-change:``mediaconvert``: Update mediaconvert client to latest version + * api-change:``dynamodb``: Update dynamodb client to latest version + * api-change:``glue``: Update glue client to latest version +- from version 1.18.11 + * api-change:``batch``: Update batch client to latest version + * api-change:``personalize-events``: Update personalize-events client to latest version + * api-change:``rds``: Update rds client to latest version + * api-change:``elbv2``: Update elbv2 client to latest version + * api-change:``servicediscovery``: Update servicediscovery client to latest version + * api-change:``s3``: Update s3 client to latest version +- from version 1.18.10 + * api-change:``glue``: Update glue client to latest version + * api-change:``kafka``: Update kafka client to latest version + * api-change:``appsync``: Update appsync client to latest version + * api-change:``emr``: Update emr client to latest version + * api-change:``wafv2``: Update wafv2 client to latest version + * api-change:``quicksight``: Update quicksight client to latest version +- from version 1.18.9 + * api-change:``datasync``: Update datasync client to latest version + * api-change:``s3control``: Update s3control client to latest version + * api-change:``imagebuilder``: Update imagebuilder client to latest version + * api-change:``securityhub``: Update securityhub client to latest version + * api-change:``iot``: Update iot client to latest version + * api-change:``emr``: Update emr client to latest version + * api-change:``s3outposts``: Update s3outposts client to latest version + * api-change:``application-autoscaling``: Update application-autoscaling client to latest version + * api-change:``directconnect``: Update directconnect client to latest version + * api-change:``s3``: Update s3 client to latest version + * api-change:``mediaconnect``: Update mediaconnect client to latest version + * api-change:``pinpoint``: Update pinpoint client to latest version +- from version 1.18.8 + * api-change:``timestream-write``: Update timestream-write client to latest version + * api-change:``connect``: Update connect client to latest version + * api-change:``ssm``: Update ssm client to latest version + * api-change:``ec2``: Update ec2 client to latest version + * api-change:``schemas``: Update schemas client to latest version + * api-change:``timestream-query``: Update timestream-query client to latest version +- from version 1.18.7 + * api-change:``application-autoscaling``: Update application-autoscaling client to latest version + * api-change:``rds``: Update rds client to latest version +- from version 1.18.6 + * api-change:``frauddetector``: Update frauddetector client to latest version + * api-change:``config``: Update config client to latest version + * api-change:``batch``: Update batch client to latest version + * api-change:``docdb``: Update docdb client to latest version + * api-change:``ec2``: Update ec2 client to latest version + * api-change:``sts``: Update sts client to latest version +- from version 1.18.5 + * api-change:``transcribe``: Update transcribe client to latest version + * api-change:``textract``: Update textract client to latest version + * api-change:``amplify``: Update amplify client to latest version + * api-change:``eks``: Update eks client to latest version + * api-change:``savingsplans``: Update savingsplans client to latest version + * api-change:``synthetics``: Update synthetics client to latest version +- from version 1.18.4 + * api-change:``translate``: Update translate client to latest version + * api-change:``ce``: Update ce client to latest version + * api-change:``quicksight``: Update quicksight client to latest version + * api-change:``backup``: Update backup client to latest version +- from version 1.18.3 + * api-change:``comprehend``: Update comprehend client to latest version + * api-change:``dynamodbstreams``: Update dynamodbstreams client to latest version + * api-change:``workmail``: Update workmail client to latest version + * api-change:``lex-models``: Update lex-models client to latest version +- from version 1.18.2 + * api-change:``glue``: Update glue client to latest version + * api-change:``resourcegroupstaggingapi``: Update resourcegroupstaggingapi client to latest version + * api-change:``iotsitewise``: Update iotsitewise client to latest version + * api-change:``events``: Update events client to latest version + * api-change:``resource-groups``: Update resource-groups client to latest version + * api-change:``rds``: Update rds client to latest version +- from version 1.18.1 + * api-change:``medialive``: Update medialive client to latest version + * api-change:``sso-admin``: Update sso-admin client to latest version + * api-change:``codestar-connections``: Update codestar-connections client to latest version +- from version 1.18.0 + * api-change:``kendra``: Update kendra client to latest version + * api-change:``cloudfront``: Update cloudfront client to latest version + * api-change:``comprehend``: Update comprehend client to latest version + * api-change:``apigateway``: Update apigateway client to latest version + * api-change:``es``: Update es client to latest version + * api-change:``apigatewayv2``: Update apigatewayv2 client to latest version + * feature:dependency: botocore has removed docutils as a required dependency +- from version 1.17.63 + * api-change:``servicecatalog``: Update servicecatalog client to latest version + * api-change:``dlm``: Update dlm client to latest version + * api-change:``greengrass``: Update greengrass client to latest version + * api-change:``connect``: Update connect client to latest version + * api-change:``ssm``: Update ssm client to latest version +- from version 1.17.62 + * api-change:``transcribe``: Update transcribe client to latest version + * api-change:``ec2``: Update ec2 client to latest version + * api-change:``sagemaker``: Update sagemaker client to latest version + * api-change:``medialive``: Update medialive client to latest version + * api-change:``budgets``: Update budgets client to latest version + * api-change:``kafka``: Update kafka client to latest version + * api-change:``kendra``: Update kendra client to latest version + * api-change:``organizations``: Update organizations client to latest version +- from version 1.17.61 + * api-change:``ec2``: Update ec2 client to latest version + * api-change:``managedblockchain``: Update managedblockchain client to latest version + * api-change:``stepfunctions``: Update stepfunctions client to latest version + * api-change:``docdb``: Update docdb client to latest version +- from version 1.17.60 + * api-change:``workspaces``: Update workspaces client to latest version +- from version 1.17.59 + * api-change:``cloudfront``: Update cloudfront client to latest version + * api-change:``ebs``: Update ebs client to latest version + * api-change:``sso-admin``: Update sso-admin client to latest version + * api-change:``s3``: Update s3 client to latest version +- from version 1.17.58 + * api-change:``kinesisanalyticsv2``: Update kinesisanalyticsv2 client to latest version + * api-change:``glue``: Update glue client to latest version + * api-change:``redshift-data``: Update redshift-data client to latest version +- from version 1.17.57 + * api-change:``lex-models``: Update lex-models client to latest version + * api-change:``apigatewayv2``: Update apigatewayv2 client to latest version + * api-change:``codebuild``: Update codebuild client to latest version + * api-change:``quicksight``: Update quicksight client to latest version + * api-change:``elbv2``: Update elbv2 client to latest version +- Drop patches no longer required + * hide_py_pckgmgmt.patch +- Refresh patches for new version + * remove_nose.patch +- Update BuildRequires and Requires from requirements.txt and setup.py + ------------------------------------------------------------------- Thu Sep 24 17:50:34 UTC 2020 - Hans-Peter Jansen diff --git a/python-botocore.spec b/python-botocore.spec index bc9bfbf..d184573 100644 --- a/python-botocore.spec +++ b/python-botocore.spec @@ -18,17 +18,15 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-botocore -Version: 1.17.56 +Version: 1.18.15 Release: 0 Summary: Python interface for AWS License: Apache-2.0 URL: https://github.com/boto/botocore Source: https://files.pythonhosted.org/packages/source/b/botocore/botocore-%{version}.tar.gz -Patch0: hide_py_pckgmgmt.patch # PATCH-FEATURE-UPSTREAM remove_nose.patch gh#boto/botocore#2134 mcepl@suse.com # Port test suite from nose to pytest (mostly just plain unittest) -Patch1: remove_nose.patch -BuildRequires: %{python_module docutils >= 0.10} +Patch0: remove_nose.patch BuildRequires: %{python_module jmespath < 1.0.0} BuildRequires: %{python_module jmespath >= 0.7.1} BuildRequires: %{python_module python-dateutil <= 3.0.0} @@ -38,7 +36,6 @@ BuildRequires: %{python_module urllib3 < 1.26} BuildRequires: %{python_module urllib3 >= 1.20} BuildRequires: fdupes BuildRequires: python-rpm-macros -Requires: python-docutils >= 0.10 Requires: python-jmespath < 1.0.0 Requires: python-jmespath >= 0.7.1 Requires: python-python-dateutil <= 3.0.0 @@ -53,6 +50,7 @@ BuildArch: noarch BuildRequires: python %endif # SECTION Testing requirements +BuildRequires: %{python_module mock >= 1.3.0} BuildRequires: %{python_module pluggy >= 0.7} BuildRequires: %{python_module py >= 1.5.0} BuildRequires: %{python_module pytest >= 4.6} diff --git a/remove_nose.patch b/remove_nose.patch index a6cc26a..4cdeb1a 100644 --- a/remove_nose.patch +++ b/remove_nose.patch @@ -1,124 +1,49 @@ ---- - requirements.txt | 2 - setup.cfg | 2 - tests/__init__.py | 22 +- - tests/acceptance/features/steps/base.py | 3 - tests/functional/csm/test_monitoring.py | 4 - tests/functional/test_client_class_names.py | 21 +- - tests/functional/test_cognito_idp.py | 6 - tests/functional/test_credentials.py | 4 - tests/functional/test_endpoints.py | 9 - - tests/functional/test_history.py | 4 - tests/functional/test_model_backcompat.py | 9 - - tests/functional/test_paginate.py | 44 ++--- - tests/functional/test_regions.py | 115 +++++++------- - tests/functional/test_response_shadowing.py | 13 - - tests/functional/test_retry.py | 3 - tests/functional/test_s3.py | 9 - - tests/functional/test_service_names.py | 21 +- - tests/functional/test_stub.py | 12 - - tests/integration/test_client.py | 6 - tests/integration/test_ec2.py | 2 - tests/integration/test_emr.py | 4 - tests/integration/test_s3.py | 17 +- - tests/integration/test_smoke.py | 12 - - tests/integration/test_sts.py | 4 - tests/integration/test_waiters.py | 4 - tests/unit/auth/test_sigv4.py | 20 +- - tests/unit/docs/test_utils.py | 2 - tests/unit/response_parsing/README.rst | 12 - - tests/unit/response_parsing/test_response_parsing.py | 8 - - tests/unit/retries/test_special.py | 2 - tests/unit/retries/test_standard.py | 34 +--- - tests/unit/test_awsrequest.py | 4 - tests/unit/test_client.py | 18 +- - tests/unit/test_compat.py | 134 ++++++++-------- - tests/unit/test_config_provider.py | 11 - - tests/unit/test_credentials.py | 20 +- - tests/unit/test_errorfactory.py | 3 - tests/unit/test_eventstream.py | 152 ++++++++++--------- - tests/unit/test_exceptions.py | 6 - tests/unit/test_handlers.py | 2 - tests/unit/test_http_client_exception_mapping.py | 20 -- - tests/unit/test_http_session.py | 13 - - tests/unit/test_loaders.py | 9 - - tests/unit/test_model.py | 12 - - tests/unit/test_paginate.py | 2 - tests/unit/test_parsers.py | 19 +- - tests/unit/test_protocols.py | 30 +-- - tests/unit/test_s3_addressing.py | 3 - tests/unit/test_utils.py | 2 - tests/unit/test_waiters.py | 2 - 50 files changed, 427 insertions(+), 465 deletions(-) - -Index: b/setup.cfg -=================================================================== ---- a/setup.cfg -+++ b/setup.cfg -@@ -13,3 +13,5 @@ requires-dist = +diff -Nru botocore-1.18.15.orig/requirements.txt botocore-1.18.15/requirements.txt +--- botocore-1.18.15.orig/requirements.txt 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/requirements.txt 2020-10-09 10:18:32.608259889 +0200 +@@ -1,6 +1,7 @@ + tox>=2.5.0,<3.0.0 +-nose==1.3.7 ++pytest>=4.6 ++pluggy>=0.7 ++py>=1.5.0 ++pytest-cov + mock==1.3.0 + wheel==0.24.0 +-behave==1.2.5 +-jsonschema==2.5.1 +diff -Nru botocore-1.18.15.orig/setup.cfg botocore-1.18.15/setup.cfg +--- botocore-1.18.15.orig/setup.cfg 2020-10-08 20:10:09.000000000 +0200 ++++ botocore-1.18.15/setup.cfg 2020-10-09 10:13:49.504471764 +0200 +@@ -12,3 +12,5 @@ tag_build = tag_date = 0 +[tool:pytest] +markers = slow: marks tests as slow \ No newline at end of file -Index: b/tests/__init__.py -=================================================================== ---- a/tests/__init__.py -+++ b/tests/__init__.py -@@ -13,7 +13,10 @@ - - import os - import sys --import mock -+try: -+ import mock -+except ImportError: -+ from unittest import mock - import time - import random - import shutil -@@ -29,8 +32,6 @@ from subprocess import Popen, PIPE - from dateutil.tz import tzlocal - import unittest - --from nose.tools import assert_equal -- - import botocore.loaders - import botocore.session - from botocore.awsrequest import AWSResponse -@@ -346,16 +347,16 @@ def assert_url_equal(url1, url2): - - # Because the query string ordering isn't relevant, we have to parse - # every single part manually and then handle the query string. -- assert_equal(parts1.scheme, parts2.scheme) -- assert_equal(parts1.netloc, parts2.netloc) -- assert_equal(parts1.path, parts2.path) -- assert_equal(parts1.params, parts2.params) -- assert_equal(parts1.fragment, parts2.fragment) -- assert_equal(parts1.username, parts2.username) -- assert_equal(parts1.password, parts2.password) -- assert_equal(parts1.hostname, parts2.hostname) -- assert_equal(parts1.port, parts2.port) -- assert_equal(parse_qs(parts1.query), parse_qs(parts2.query)) -+ assert parts1.scheme == parts2.scheme -+ assert parts1.netloc == parts2.netloc -+ assert parts1.path == parts2.path -+ assert parts1.params == parts2.params -+ assert parts1.fragment == parts2.fragment -+ assert parts1.username == parts2.username -+ assert parts1.password == parts2.password -+ assert parts1.hostname == parts2.hostname -+ assert parts1.port == parts2.port -+ assert parse_qs(parts1.query) == parse_qs(parts2.query) - - - class HTTPStubberException(Exception): -Index: b/tests/acceptance/features/steps/base.py -=================================================================== ---- a/tests/acceptance/features/steps/base.py -+++ b/tests/acceptance/features/steps/base.py -@@ -4,7 +4,6 @@ from botocore import xform_name +diff -Nru botocore-1.18.15.orig/setup.cfg.orig botocore-1.18.15/setup.cfg.orig +--- botocore-1.18.15.orig/setup.cfg.orig 1970-01-01 01:00:00.000000000 +0100 ++++ botocore-1.18.15/setup.cfg.orig 2020-10-08 20:10:09.000000000 +0200 +@@ -0,0 +1,14 @@ ++[bdist_wheel] ++universal = 1 ++ ++[metadata] ++requires-dist = ++ python-dateutil>=2.1,<3.0.0 ++ jmespath>=0.7.1,<1.0.0 ++ urllib3>=1.20,<1.25.8; python_version=='3.4' ++ urllib3>=1.20,<1.26; python_version!='3.4' ++ ++[egg_info] ++tag_build = ++tag_date = 0 ++ +diff -Nru botocore-1.18.15.orig/tests/acceptance/features/steps/base.py botocore-1.18.15/tests/acceptance/features/steps/base.py +--- botocore-1.18.15.orig/tests/acceptance/features/steps/base.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/acceptance/features/steps/base.py 2020-10-09 10:13:49.504471764 +0200 +@@ -4,7 +4,6 @@ from botocore.exceptions import ClientError from behave import when, then @@ -126,7 +51,7 @@ Index: b/tests/acceptance/features/steps/base.py def _params_from_table(table): -@@ -72,7 +71,7 @@ def api_call_with_json_and_error(context +@@ -72,7 +71,7 @@ @then(u'I expect the response error code to be "{}"') def then_expected_error(context, code): @@ -135,11 +60,10 @@ Index: b/tests/acceptance/features/steps/base.py @then(u'the value at "{}" should be a list') -Index: b/tests/functional/csm/test_monitoring.py -=================================================================== ---- a/tests/functional/csm/test_monitoring.py -+++ b/tests/functional/csm/test_monitoring.py -@@ -18,8 +18,7 @@ import os +diff -Nru botocore-1.18.15.orig/tests/functional/csm/test_monitoring.py botocore-1.18.15/tests/functional/csm/test_monitoring.py +--- botocore-1.18.15.orig/tests/functional/csm/test_monitoring.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/csm/test_monitoring.py 2020-10-09 10:13:49.504471764 +0200 +@@ -18,8 +18,7 @@ import socket import threading @@ -149,7 +73,7 @@ Index: b/tests/functional/csm/test_monitoring.py from tests import temporary_file from tests import ClientHTTPStubber -@@ -50,7 +49,7 @@ EXPECTED_EXCEPTIONS_THROWN = ( +@@ -50,7 +49,7 @@ def test_client_monitoring(): test_cases = _load_test_cases() for case in test_cases: @@ -158,7 +82,7 @@ Index: b/tests/functional/csm/test_monitoring.py def _load_test_cases(): -@@ -121,8 +120,7 @@ def _run_test_case(case): +@@ -121,8 +120,7 @@ case['configuration'], listener.port) as session: for api_call in case['apiCalls']: _make_api_call(session, api_call) @@ -168,10 +92,52 @@ Index: b/tests/functional/csm/test_monitoring.py def _make_api_call(session, api_call): -Index: b/tests/functional/test_client_class_names.py -=================================================================== ---- a/tests/functional/test_client_class_names.py -+++ b/tests/functional/test_client_class_names.py +diff -Nru botocore-1.18.15.orig/tests/functional/docs/test_shared_example_config.py botocore-1.18.15/tests/functional/docs/test_shared_example_config.py +--- botocore-1.18.15.orig/tests/functional/docs/test_shared_example_config.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/docs/test_shared_example_config.py 2020-10-09 10:13:49.544472317 +0200 +@@ -27,7 +27,7 @@ + examples = example_config.get("examples", {}) + for operation, operation_examples in examples.items(): + for example in operation_examples: +- yield _lint_single_example, operation, example, service_model ++ _lint_single_example(operation, example, service_model) + + + def _lint_single_example(operation_name, example_config, service_model): +diff -Nru botocore-1.18.15.orig/tests/functional/test_alias.py botocore-1.18.15/tests/functional/test_alias.py +--- botocore-1.18.15.orig/tests/functional/test_alias.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_alias.py 2020-10-09 10:13:49.544472317 +0200 +@@ -49,13 +49,13 @@ + def test_can_use_alias(): + session = botocore.session.get_session() + for case in ALIAS_CASES: +- yield _can_use_parameter_in_client_call, session, case ++ _can_use_parameter_in_client_call(session, case) + + + def test_can_use_original_name(): + session = botocore.session.get_session() + for case in ALIAS_CASES: +- yield _can_use_parameter_in_client_call, session, case, False ++ _can_use_parameter_in_client_call(session, case, False) + + + def _can_use_parameter_in_client_call(session, case, use_alias=True): +diff -Nru botocore-1.18.15.orig/tests/functional/test_apigateway.py botocore-1.18.15/tests/functional/test_apigateway.py +--- botocore-1.18.15.orig/tests/functional/test_apigateway.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_apigateway.py 2020-10-09 10:13:49.548472372 +0200 +@@ -10,7 +10,7 @@ + # 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. +-import mock ++from tests import mock + + from tests import BaseSessionTest, ClientHTTPStubber + +diff -Nru botocore-1.18.15.orig/tests/functional/test_client_class_names.py botocore-1.18.15/tests/functional/test_client_class_names.py +--- botocore-1.18.15.orig/tests/functional/test_client_class_names.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_client_class_names.py 2020-10-09 10:13:49.504471764 +0200 @@ -10,11 +10,9 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific @@ -185,7 +151,7 @@ Index: b/tests/functional/test_client_class_names.py REGION = 'us-east-1' SERVICE_TO_CLASS_NAME = { -@@ -69,13 +67,10 @@ SERVICE_TO_CLASS_NAME = { +@@ -69,13 +67,10 @@ } @@ -206,10 +172,21 @@ Index: b/tests/functional/test_client_class_names.py + client = session.create_client(service_name, REGION) + self.assertEqual(client.__class__.__name__, + SERVICE_TO_CLASS_NAME[service_name]) -Index: b/tests/functional/test_cognito_idp.py -=================================================================== ---- a/tests/functional/test_cognito_idp.py -+++ b/tests/functional/test_cognito_idp.py +diff -Nru botocore-1.18.15.orig/tests/functional/test_cloudsearchdomain.py botocore-1.18.15/tests/functional/test_cloudsearchdomain.py +--- botocore-1.18.15.orig/tests/functional/test_cloudsearchdomain.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_cloudsearchdomain.py 2020-10-09 10:13:49.548472372 +0200 +@@ -10,7 +10,7 @@ + # 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. +-import mock ++from tests import mock + + from tests import BaseSessionTest, ClientHTTPStubber + +diff -Nru botocore-1.18.15.orig/tests/functional/test_cognito_idp.py botocore-1.18.15/tests/functional/test_cognito_idp.py +--- botocore-1.18.15.orig/tests/functional/test_cognito_idp.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_cognito_idp.py 2020-10-09 10:13:49.504471764 +0200 @@ -10,9 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific @@ -221,7 +198,7 @@ Index: b/tests/functional/test_cognito_idp.py from tests import create_session, ClientHTTPStubber -@@ -95,8 +93,7 @@ def test_unsigned_operations(): +@@ -95,8 +93,7 @@ client = session.create_client('cognito-idp', 'us-west-2') for operation, params in operation_params.items(): @@ -231,7 +208,7 @@ Index: b/tests/functional/test_cognito_idp.py class UnsignedOperationTestCase(object): -@@ -114,7 +111,5 @@ class UnsignedOperationTestCase(object): +@@ -114,7 +111,5 @@ operation(**self._parameters) request = self._http_stubber.requests[0] @@ -240,10 +217,63 @@ Index: b/tests/functional/test_cognito_idp.py + assert 'authorization' not in request.headers, \ 'authorization header found in unsigned operation' - ) -Index: b/tests/functional/test_endpoints.py -=================================================================== ---- a/tests/functional/test_endpoints.py -+++ b/tests/functional/test_endpoints.py +diff -Nru botocore-1.18.15.orig/tests/functional/test_credentials.py botocore-1.18.15/tests/functional/test_credentials.py +--- botocore-1.18.15.orig/tests/functional/test_credentials.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_credentials.py 2020-10-09 10:13:49.528472096 +0200 +@@ -15,7 +15,7 @@ + import os + import math + import time +-import mock ++from tests import mock + import tempfile + import shutil + from datetime import datetime, timedelta +@@ -41,7 +41,7 @@ + from botocore.exceptions import InvalidConfigError, InfiniteLoopConfigError + from botocore.stub import Stubber + from botocore.utils import datetime2timestamp +- ++from botocore.compat import six + + class TestCredentialRefreshRaces(unittest.TestCase): + def assert_consistent_credentials_seen(self, creds, func): +@@ -826,7 +826,7 @@ + # Finally `(?s)` at the beginning makes dots match newlines so + # we can handle a multi-line string. + reg = r"(?s)^((?!b').)*$" +- with self.assertRaisesRegexp(CredentialRetrievalError, reg): ++ with six.assertRaisesRegex(self, CredentialRetrievalError, reg): + session.get_credentials() + + +diff -Nru botocore-1.18.15.orig/tests/functional/test_docdb.py botocore-1.18.15/tests/functional/test_docdb.py +--- botocore-1.18.15.orig/tests/functional/test_docdb.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_docdb.py 2020-10-09 10:13:49.548472372 +0200 +@@ -10,7 +10,7 @@ + # 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. +-import mock ++from tests import mock + from contextlib import contextmanager + + import botocore.session +diff -Nru botocore-1.18.15.orig/tests/functional/test_ec2.py botocore-1.18.15/tests/functional/test_ec2.py +--- botocore-1.18.15.orig/tests/functional/test_ec2.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_ec2.py 2020-10-09 10:13:49.548472372 +0200 +@@ -11,7 +11,7 @@ + # ANY KIND, either express or implied. See the License for the specific + # language governing permissions and limitations under the License. + import datetime +-import mock ++from tests import mock + + from tests import unittest, ClientHTTPStubber, BaseSessionTest + from botocore.compat import parse_qs, urlparse +diff -Nru botocore-1.18.15.orig/tests/functional/test_endpoints.py botocore-1.18.15/tests/functional/test_endpoints.py +--- botocore-1.18.15.orig/tests/functional/test_endpoints.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_endpoints.py 2020-10-09 10:13:49.504471764 +0200 @@ -10,7 +10,6 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific @@ -252,7 +282,7 @@ Index: b/tests/functional/test_endpoints.py from botocore.session import get_session -@@ -134,9 +133,7 @@ def test_endpoint_matches_service(): +@@ -134,9 +133,7 @@ # prefix. endpoint_prefix = ENDPOINT_PREFIX_OVERRIDE.get(endpoint_prefix, endpoint_prefix) @@ -263,7 +293,7 @@ Index: b/tests/functional/test_endpoints.py def _assert_known_endpoint_prefix(endpoint_prefix, known_endpoint_prefixes): -@@ -156,7 +153,7 @@ def test_service_name_matches_endpoint_p +@@ -156,7 +153,7 @@ services = loader.list_available_services('service-2') for service in services: @@ -272,7 +302,7 @@ Index: b/tests/functional/test_endpoints.py def _assert_service_name_matches_endpoint_prefix(session, service_name): -@@ -166,8 +163,6 @@ def _assert_service_name_matches_endpoin +@@ -166,8 +163,6 @@ # Handle known exceptions where we have renamed the service directory # for one reason or another. actual_service_name = SERVICE_RENAMES.get(service_name, service_name) @@ -284,10 +314,89 @@ Index: b/tests/functional/test_endpoints.py + assert computed_name == actual_service_name, \ + ("Actual service name `%s` does not match expected service name " + + "we computed: `%s`") % (actual_service_name, computed_name) -Index: b/tests/functional/test_model_backcompat.py -=================================================================== ---- a/tests/functional/test_model_backcompat.py -+++ b/tests/functional/test_model_backcompat.py +diff -Nru botocore-1.18.15.orig/tests/functional/test_event_alias.py botocore-1.18.15/tests/functional/test_event_alias.py +--- botocore-1.18.15.orig/tests/functional/test_event_alias.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_event_alias.py 2020-10-09 10:13:49.544472317 +0200 +@@ -584,8 +584,8 @@ + service_id = SERVICES[client_name]['service_id'] + if endpoint_prefix is not None: + yield _assert_handler_called, client_name, endpoint_prefix +- yield _assert_handler_called, client_name, service_id +- yield _assert_handler_called, client_name, client_name ++ _assert_handler_called(client_name, service_id) ++ _assert_handler_called(client_name, client_name) + + + def _assert_handler_called(client_name, event_part): +diff -Nru botocore-1.18.15.orig/tests/functional/test_h2_required.py botocore-1.18.15/tests/functional/test_h2_required.py +--- botocore-1.18.15.orig/tests/functional/test_h2_required.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_h2_required.py 2020-10-09 10:13:49.544472317 +0200 +@@ -29,12 +29,12 @@ + service_model = session.get_service_model(service) + h2_config = service_model.metadata.get('protocolSettings', {}).get('h2') + if h2_config == 'required': +- yield _assert_h2_service_is_known, service ++ _assert_h2_service_is_known(service) + elif h2_config == 'eventstream': + for operation in service_model.operation_names: + operation_model = service_model.operation_model(operation) + if operation_model.has_event_stream_output: +- yield _assert_h2_operation_is_known, service, operation ++ _assert_h2_operation_is_known(service, operation) + + + def _assert_h2_service_is_known(service): +diff -Nru botocore-1.18.15.orig/tests/functional/test_history.py botocore-1.18.15/tests/functional/test_history.py +--- botocore-1.18.15.orig/tests/functional/test_history.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_history.py 2020-10-09 10:13:49.528472096 +0200 +@@ -1,6 +1,6 @@ + from contextlib import contextmanager + +-import mock ++from tests import mock + + from tests import BaseSessionTest, ClientHTTPStubber + from botocore.history import BaseHistoryHandler +@@ -87,10 +87,10 @@ + self.assertIsNone(body) + + streaming = payload['streaming'] +- self.assertEquals(streaming, False) ++ self.assertEqual(streaming, False) + + url = payload['url'] +- self.assertEquals(url, 'https://s3.us-west-2.amazonaws.com/') ++ self.assertEqual(url, 'https://s3.us-west-2.amazonaws.com/') + + self.assertEqual(source, 'BOTOCORE') + +diff -Nru botocore-1.18.15.orig/tests/functional/test_lex.py botocore-1.18.15/tests/functional/test_lex.py +--- botocore-1.18.15.orig/tests/functional/test_lex.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_lex.py 2020-10-09 10:13:49.548472372 +0200 +@@ -10,7 +10,7 @@ + # 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. +-import mock ++from tests import mock + from datetime import datetime + + from tests import BaseSessionTest, ClientHTTPStubber +diff -Nru botocore-1.18.15.orig/tests/functional/test_machinelearning.py botocore-1.18.15/tests/functional/test_machinelearning.py +--- botocore-1.18.15.orig/tests/functional/test_machinelearning.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_machinelearning.py 2020-10-09 10:13:49.548472372 +0200 +@@ -10,7 +10,7 @@ + # 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. +-import mock ++from tests import mock + + from tests import BaseSessionTest, ClientHTTPStubber + +diff -Nru botocore-1.18.15.orig/tests/functional/test_model_backcompat.py botocore-1.18.15/tests/functional/test_model_backcompat.py +--- botocore-1.18.15.orig/tests/functional/test_model_backcompat.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_model_backcompat.py 2020-10-09 10:13:49.508471818 +0200 @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. import os @@ -296,7 +405,7 @@ Index: b/tests/functional/test_model_backcompat.py from botocore.session import Session from tests import ClientHTTPStubber from tests.functional import TEST_MODELS_DIR -@@ -56,8 +55,7 @@ def test_old_model_continues_to_work(): +@@ -56,8 +55,7 @@ 'Content-Type': 'application/x-amz-json-1.1'}, body=b'{"CertificateSummaryList":[]}') response = client.list_certificates() @@ -306,7 +415,7 @@ Index: b/tests/functional/test_model_backcompat.py {'CertificateSummaryList': [], 'ResponseMetadata': { 'HTTPHeaders': { -@@ -69,8 +67,7 @@ def test_old_model_continues_to_work(): +@@ -69,8 +67,7 @@ 'RequestId': 'abcd', 'RetryAttempts': 0} } @@ -317,11 +426,34 @@ Index: b/tests/functional/test_model_backcompat.py - assert_equal(client.waiter_names, ['certificate_validated']) + assert client.can_paginate('list_certificates') + assert client.waiter_names == ['certificate_validated'] -Index: b/tests/functional/test_paginate.py -=================================================================== ---- a/tests/functional/test_paginate.py -+++ b/tests/functional/test_paginate.py -@@ -14,9 +14,7 @@ from __future__ import division +diff -Nru botocore-1.18.15.orig/tests/functional/test_model_completeness.py botocore-1.18.15/tests/functional/test_model_completeness.py +--- botocore-1.18.15.orig/tests/functional/test_model_completeness.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_model_completeness.py 2020-10-09 10:13:49.544472317 +0200 +@@ -38,5 +38,6 @@ + versions = Loader().list_api_versions(service_name, 'service-2') + if len(versions) > 1: + for type_name in ['paginators-1', 'waiters-2']: +- yield (_test_model_is_not_lost, service_name, +- type_name, versions[-2], versions[-1]) ++ _test_model_is_not_lost(service_name, ++ type_name, ++ versions[-2], versions[-1]) +diff -Nru botocore-1.18.15.orig/tests/functional/test_neptune.py botocore-1.18.15/tests/functional/test_neptune.py +--- botocore-1.18.15.orig/tests/functional/test_neptune.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_neptune.py 2020-10-09 10:13:49.548472372 +0200 +@@ -10,7 +10,7 @@ + # 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. +-import mock ++from tests import mock + from contextlib import contextmanager + + import botocore.session +diff -Nru botocore-1.18.15.orig/tests/functional/test_paginate.py botocore-1.18.15/tests/functional/test_paginate.py +--- botocore-1.18.15.orig/tests/functional/test_paginate.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_paginate.py 2020-10-09 10:13:49.508471818 +0200 +@@ -14,9 +14,7 @@ from math import ceil from datetime import datetime @@ -332,7 +464,7 @@ Index: b/tests/functional/test_paginate.py from tests import BaseSessionTest from botocore.stub import Stubber, StubAssertionError from botocore.paginate import TokenDecoder, TokenEncoder -@@ -79,7 +77,7 @@ class TestAutoscalingPagination(BaseSess +@@ -79,7 +77,7 @@ self.stubber.activate() def _setup_scaling_pagination(self, page_size=200, max_items=100, @@ -341,7 +473,7 @@ Index: b/tests/functional/test_paginate.py """ Add to the stubber to test paginating describe_scaling_activities. -@@ -217,22 +215,22 @@ class TestCloudwatchLogsPagination(BaseS +@@ -217,22 +215,22 @@ self.assertEqual(len(result['events']), 1) @@ -383,10 +515,56 @@ Index: b/tests/functional/test_paginate.py + assert isinstance(encoded, six.string_types) + decoded = TokenDecoder().decode(encoded) + self.assertEqual(decoded, token_dict) -Index: b/tests/functional/test_regions.py -=================================================================== ---- a/tests/functional/test_regions.py -+++ b/tests/functional/test_regions.py +diff -Nru botocore-1.18.15.orig/tests/functional/test_paginator_config.py botocore-1.18.15/tests/functional/test_paginator_config.py +--- botocore-1.18.15.orig/tests/functional/test_paginator_config.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_paginator_config.py 2020-10-09 10:13:49.544472317 +0200 +@@ -140,12 +140,7 @@ + 'paginators-1', + service_model.api_version) + for op_name, single_config in page_config['pagination'].items(): +- yield ( +- _lint_single_paginator, +- op_name, +- single_config, +- service_model +- ) ++ _lint_single_paginator(op_name, single_config, service_model) + + + def _lint_single_paginator(operation_name, page_config, +diff -Nru botocore-1.18.15.orig/tests/functional/test_public_apis.py botocore-1.18.15/tests/functional/test_public_apis.py +--- botocore-1.18.15.orig/tests/functional/test_public_apis.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_public_apis.py 2020-10-09 10:13:49.544472317 +0200 +@@ -12,7 +12,7 @@ + # language governing permissions and limitations under the License. + from collections import defaultdict + +-import mock ++from tests import mock + + from tests import ClientHTTPStubber + from botocore.session import Session +@@ -73,4 +73,4 @@ + for operation_name in PUBLIC_API_TESTS[service_name]: + kwargs = PUBLIC_API_TESTS[service_name][operation_name] + method = getattr(client, xform_name(operation_name)) +- yield _test_public_apis_will_not_be_signed, client, method, kwargs ++ _test_public_apis_will_not_be_signed(client, method, kwargs) +diff -Nru botocore-1.18.15.orig/tests/functional/test_rds.py botocore-1.18.15/tests/functional/test_rds.py +--- botocore-1.18.15.orig/tests/functional/test_rds.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_rds.py 2020-10-09 10:13:49.552472426 +0200 +@@ -10,7 +10,7 @@ + # 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. +-import mock ++from tests import mock + from contextlib import contextmanager + + import botocore.session +diff -Nru botocore-1.18.15.orig/tests/functional/test_regions.py botocore-1.18.15/tests/functional/test_regions.py +--- botocore-1.18.15.orig/tests/functional/test_regions.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_regions.py 2020-10-09 10:13:49.508471818 +0200 @@ -10,10 +10,9 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific @@ -400,7 +578,7 @@ Index: b/tests/functional/test_regions.py from botocore.client import ClientEndpointBridge from botocore.exceptions import NoRegionError -@@ -448,64 +447,62 @@ def _get_patched_session(): +@@ -448,64 +447,62 @@ return session @@ -518,7 +696,7 @@ Index: b/tests/functional/test_regions.py def setUp(self): super(TestEndpointResolution, self).setUp() self.xml_response = ( -@@ -526,7 +523,7 @@ class TestEndpointResolution(BaseSession +@@ -526,7 +523,7 @@ client, stubber = self.create_stubbed_client('s3', 'us-east-2') stubber.add_response() client.list_buckets() @@ -527,7 +705,7 @@ Index: b/tests/functional/test_regions.py stubber.requests[0].url, 'https://s3.us-east-2.amazonaws.com/' ) -@@ -537,7 +534,7 @@ class TestEndpointResolution(BaseSession +@@ -537,7 +534,7 @@ client.list_buckets() # Validate we don't fall back to partition endpoint for # regionalized services. @@ -536,10 +714,9 @@ Index: b/tests/functional/test_regions.py stubber.requests[0].url, 'https://s3.not-real.amazonaws.com/' ) -Index: b/tests/functional/test_response_shadowing.py -=================================================================== ---- a/tests/functional/test_response_shadowing.py -+++ b/tests/functional/test_response_shadowing.py +diff -Nru botocore-1.18.15.orig/tests/functional/test_response_shadowing.py botocore-1.18.15/tests/functional/test_response_shadowing.py +--- botocore-1.18.15.orig/tests/functional/test_response_shadowing.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_response_shadowing.py 2020-10-09 10:13:49.508471818 +0200 @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. @@ -548,7 +725,7 @@ Index: b/tests/functional/test_response_shadowing.py def _all_services(): -@@ -33,17 +32,17 @@ def _assert_not_shadowed(key, shape): +@@ -33,17 +32,17 @@ msg = ( 'Found shape "%s" that shadows the botocore response key "%s"' ) @@ -570,11 +747,30 @@ Index: b/tests/functional/test_response_shadowing.py - yield _assert_not_shadowed, 'Error', shape + _assert_not_shadowed('ResponseMetadata', shape) + _assert_not_shadowed('Error', shape) -Index: b/tests/functional/test_s3.py -=================================================================== ---- a/tests/functional/test_s3.py -+++ b/tests/functional/test_s3.py -@@ -14,7 +14,6 @@ import re +diff -Nru botocore-1.18.15.orig/tests/functional/test_retry.py botocore-1.18.15/tests/functional/test_retry.py +--- botocore-1.18.15.orig/tests/functional/test_retry.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_retry.py 2020-10-09 10:13:49.528472096 +0200 +@@ -16,6 +16,7 @@ + + from botocore.exceptions import ClientError + from botocore.config import Config ++from botocore.compat import six + + + class BaseRetryTest(BaseSessionTest): +@@ -38,7 +39,7 @@ + with ClientHTTPStubber(client) as http_stubber: + for _ in range(num_responses): + http_stubber.add_response(status=status, body=body) +- with self.assertRaisesRegexp( ++ with six.assertRaisesRegex(self, + ClientError, 'reached max retries: %s' % num_retries): + yield + self.assertEqual(len(http_stubber.requests), num_responses) +diff -Nru botocore-1.18.15.orig/tests/functional/test_s3.py botocore-1.18.15/tests/functional/test_s3.py +--- botocore-1.18.15.orig/tests/functional/test_s3.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_s3.py 2020-10-09 11:13:26.998182410 +0200 +@@ -14,7 +14,6 @@ from tests import temporary_file from tests import unittest, mock, BaseSessionTest, create_session, ClientHTTPStubber @@ -582,7 +778,7 @@ Index: b/tests/functional/test_s3.py import botocore.session from botocore.config import Config -@@ -447,8 +446,8 @@ class TestS3Copy(BaseS3OperationTest): +@@ -447,8 +446,8 @@ ) # Validate we retried and got second body @@ -593,7 +789,7 @@ Index: b/tests/functional/test_s3.py self.assertTrue('CopyObjectResult' in response) def test_s3_copy_object_with_incomplete_response(self): -@@ -1193,48 +1192,49 @@ class TestGeneratePresigned(BaseS3Operat +@@ -1284,48 +1283,49 @@ 'get_object', {'Bucket': 'mybucket', 'Key': 'mykey'}) self.assert_is_v2_presigned_url(url) @@ -681,7 +877,7 @@ Index: b/tests/functional/test_s3.py def _verify_checksum_in_headers(operation, operation_kwargs): -@@ -1259,36 +1259,36 @@ def test_correct_url_used_for_s3(): +@@ -1350,36 +1350,36 @@ t = S3AddressingCases(_verify_expected_endpoint_url) # The default behavior for sigv2. DNS compatible buckets @@ -746,72 +942,52 @@ Index: b/tests/functional/test_s3.py region='us-west-1', bucket='bucket-with-num-1', key='key', signature_version='s3v4', is_secure=False, expected_url='http://bucket-with-num-1.s3.us-west-1.amazonaws.com/key') -@@ -1296,189 +1296,188 @@ def test_correct_url_used_for_s3(): +@@ -1387,121 +1387,121 @@ # Regions outside of the 'aws' partition. # These should still default to virtual hosted addressing # unless explicitly configured otherwise. - yield t.case(region='cn-north-1', bucket='bucket', key='key', -- signature_version='s3v4', -- expected_url=( -- 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) + t.case(region='cn-north-1', bucket='bucket', key='key', -+ signature_version='s3v4', -+ expected_url=( -+ 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) + signature_version='s3v4', + expected_url=( + 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) # This isn't actually supported because cn-north-1 is sigv4 only, # but we'll still double check that our internal logic is correct # when building the expected url. - yield t.case(region='cn-north-1', bucket='bucket', key='key', -- signature_version='s3', -- expected_url=( -- 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) + t.case(region='cn-north-1', bucket='bucket', key='key', -+ signature_version='s3', -+ expected_url=( -+ 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) + signature_version='s3', + expected_url=( + 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) # If the request is unsigned, we should have the default # fix_s3_host behavior which is to use virtual hosting where # possible but fall back to path style when needed. - yield t.case(region='cn-north-1', bucket='bucket', key='key', -- signature_version=UNSIGNED, -- expected_url=( -- 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) -- yield t.case(region='cn-north-1', bucket='bucket.dot', key='key', -- signature_version=UNSIGNED, -- expected_url=( -- 'https://s3.cn-north-1.amazonaws.com.cn/bucket.dot/key')) + t.case(region='cn-north-1', bucket='bucket', key='key', -+ signature_version=UNSIGNED, -+ expected_url=( -+ 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) + signature_version=UNSIGNED, + expected_url=( + 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) +- yield t.case(region='cn-north-1', bucket='bucket.dot', key='key', + t.case(region='cn-north-1', bucket='bucket.dot', key='key', -+ signature_version=UNSIGNED, -+ expected_url=( -+ 'https://s3.cn-north-1.amazonaws.com.cn/bucket.dot/key')) + signature_version=UNSIGNED, + expected_url=( + 'https://s3.cn-north-1.amazonaws.com.cn/bucket.dot/key')) # And of course you can explicitly specify which style to use. virtual_hosting = {'addressing_style': 'virtual'} - yield t.case(region='cn-north-1', bucket='bucket', key='key', -- signature_version=UNSIGNED, -- s3_config=virtual_hosting, -- expected_url=( -- 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) + t.case(region='cn-north-1', bucket='bucket', key='key', -+ signature_version=UNSIGNED, -+ s3_config=virtual_hosting, -+ expected_url=( -+ 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) + signature_version=UNSIGNED, + s3_config=virtual_hosting, + expected_url=( + 'https://bucket.s3.cn-north-1.amazonaws.com.cn/key')) path_style = {'addressing_style': 'path'} - yield t.case(region='cn-north-1', bucket='bucket', key='key', -- signature_version=UNSIGNED, -- s3_config=path_style, -- expected_url=( -- 'https://s3.cn-north-1.amazonaws.com.cn/bucket/key')) + t.case(region='cn-north-1', bucket='bucket', key='key', -+ signature_version=UNSIGNED, -+ s3_config=path_style, -+ expected_url=( -+ 'https://s3.cn-north-1.amazonaws.com.cn/bucket/key')) + signature_version=UNSIGNED, + s3_config=path_style, + expected_url=( + 'https://s3.cn-north-1.amazonaws.com.cn/bucket/key')) # If you don't have a DNS compatible bucket, we use path style. - yield t.case( @@ -912,9 +1088,9 @@ Index: b/tests/functional/test_s3.py + t.case( region='fips-us-gov-west-1', bucket='bucket', key='key', signature_version='s3', - expected_url='https://bucket.s3-fips-us-gov-west-1.amazonaws.com/key') + expected_url='https://bucket.s3-fips.us-gov-west-1.amazonaws.com/key') +@@ -1509,67 +1509,67 @@ -- # Test path style addressing. path_style = {'addressing_style': 'path'} - yield t.case( @@ -994,7 +1170,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket='bucket', key='key', s3_config={'use_accelerate_endpoint': True, 'addressing_style': 'path'}, -@@ -1486,17 +1485,17 @@ def test_correct_url_used_for_s3(): +@@ -1577,17 +1577,17 @@ # S3 dual stack endpoints. use_dualstack = {'use_dualstack_endpoint': True} @@ -1015,7 +1191,7 @@ Index: b/tests/functional/test_s3.py region='aws-global', bucket='bucket', key='key', s3_config=use_dualstack, # Pseudo-regions should not have any special resolving logic even when -@@ -1505,32 +1504,32 @@ def test_correct_url_used_for_s3(): +@@ -1596,32 +1596,32 @@ # region name. expected_url=( 'https://bucket.s3.dualstack.aws-global.amazonaws.com/key')) @@ -1054,7 +1230,7 @@ Index: b/tests/functional/test_s3.py region='us-west-2', bucket='bucket.dot', key='key', is_secure=False, s3_config=use_dualstack, # Still default to virtual hosted when possible. -@@ -1543,7 +1542,7 @@ def test_correct_url_used_for_s3(): +@@ -1634,7 +1634,7 @@ 'use_dualstack_endpoint': True, 'addressing_style': 'path', } @@ -1063,7 +1239,7 @@ Index: b/tests/functional/test_s3.py region='us-west-2', bucket='bucket', key='key', s3_config=force_path_style, # Still default to virtual hosted when possible. -@@ -1554,32 +1553,32 @@ def test_correct_url_used_for_s3(): +@@ -1645,32 +1645,32 @@ 'use_accelerate_endpoint': True, 'use_dualstack_endpoint': True, } @@ -1101,7 +1277,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket='bucket', key='key', # Even whitelisted parts cannot be duplicated. customer_provided_endpoint=( -@@ -1587,7 +1586,7 @@ def test_correct_url_used_for_s3(): +@@ -1678,7 +1678,7 @@ expected_url=( 'https://s3-accelerate.dualstack.dualstack' '.amazonaws.com/bucket/key')) @@ -1110,7 +1286,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket='bucket', key='key', # More than two extra parts is not allowed. customer_provided_endpoint=( -@@ -1596,12 +1595,12 @@ def test_correct_url_used_for_s3(): +@@ -1687,12 +1687,12 @@ expected_url=( 'https://s3-accelerate.dualstack.dualstack.dualstack.amazonaws.com' '/bucket/key')) @@ -1125,7 +1301,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket='bucket', key='key', s3_config=use_accelerate_dualstack, is_secure=False, # Note we're using http:// because is_secure=False. -@@ -1610,7 +1609,7 @@ def test_correct_url_used_for_s3(): +@@ -1701,7 +1701,7 @@ # Use virtual even if path is specified for s3 accelerate because # path style will not work with S3 accelerate. use_accelerate_dualstack['addressing_style'] = 'path' @@ -1134,7 +1310,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket='bucket', key='key', s3_config=use_accelerate_dualstack, expected_url=( -@@ -1620,14 +1619,14 @@ def test_correct_url_used_for_s3(): +@@ -1711,14 +1711,14 @@ accesspoint_arn = ( 'arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint' ) @@ -1151,7 +1327,7 @@ Index: b/tests/functional/test_s3.py region='us-west-2', bucket=accesspoint_arn, key='key', s3_config={'use_arn_region': True}, expected_url=( -@@ -1635,21 +1634,21 @@ def test_correct_url_used_for_s3(): +@@ -1726,21 +1726,21 @@ 'us-west-2.amazonaws.com/key' ) ) @@ -1176,7 +1352,7 @@ Index: b/tests/functional/test_s3.py # Note: The access-point arn has us-west-2 and the client's region is # us-east-1, for the default case the access-point arn region is used. region='us-east-1', bucket=accesspoint_arn, key='key', -@@ -1658,7 +1657,7 @@ def test_correct_url_used_for_s3(): +@@ -1749,7 +1749,7 @@ 'us-west-2.amazonaws.com/key' ) ) @@ -1185,7 +1361,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket=accesspoint_arn, key='key', s3_config={'use_arn_region': False}, expected_url=( -@@ -1666,14 +1665,14 @@ def test_correct_url_used_for_s3(): +@@ -1757,14 +1757,14 @@ 'us-east-1.amazonaws.com/key' ) ) @@ -1202,7 +1378,7 @@ Index: b/tests/functional/test_s3.py region='s3-external-1', bucket=accesspoint_arn, key='key', s3_config={'use_arn_region': False}, expected_url=( -@@ -1681,14 +1680,14 @@ def test_correct_url_used_for_s3(): +@@ -1772,14 +1772,14 @@ 's3-external-1.amazonaws.com/key' ) ) @@ -1219,7 +1395,7 @@ Index: b/tests/functional/test_s3.py region='aws-global', bucket=accesspoint_arn, key='key', s3_config={'use_arn_region': False}, expected_url=( -@@ -1696,7 +1695,7 @@ def test_correct_url_used_for_s3(): +@@ -1787,7 +1787,7 @@ 'aws-global.amazonaws.com/key' ) ) @@ -1228,7 +1404,7 @@ Index: b/tests/functional/test_s3.py region='unknown', bucket=accesspoint_arn, key='key', s3_config={'use_arn_region': False}, expected_url=( -@@ -1704,7 +1703,7 @@ def test_correct_url_used_for_s3(): +@@ -1795,7 +1795,7 @@ 'unknown.amazonaws.com/key' ) ) @@ -1237,7 +1413,7 @@ Index: b/tests/functional/test_s3.py region='unknown', bucket=accesspoint_arn, key='key', s3_config={'use_arn_region': True}, expected_url=( -@@ -1715,21 +1714,21 @@ def test_correct_url_used_for_s3(): +@@ -1806,21 +1806,21 @@ accesspoint_arn_cn = ( 'arn:aws-cn:s3:cn-north-1:123456789012:accesspoint:myendpoint' ) @@ -1262,7 +1438,7 @@ Index: b/tests/functional/test_s3.py region='cn-northwest-1', bucket=accesspoint_arn_cn, key='key', s3_config={'use_arn_region': False}, expected_url=( -@@ -1740,21 +1739,21 @@ def test_correct_url_used_for_s3(): +@@ -1831,21 +1831,21 @@ accesspoint_arn_gov = ( 'arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:myendpoint' ) @@ -1287,7 +1463,7 @@ Index: b/tests/functional/test_s3.py region='fips-us-gov-west-1', bucket=accesspoint_arn_gov, key='key', s3_config={'use_arn_region': False}, expected_url=( -@@ -1763,7 +1762,7 @@ def test_correct_url_used_for_s3(): +@@ -1854,7 +1854,7 @@ ) ) @@ -1296,7 +1472,7 @@ Index: b/tests/functional/test_s3.py region='us-west-2', bucket=accesspoint_arn, key='key', is_secure=False, expected_url=( 'http://myendpoint-123456789012.s3-accesspoint.' -@@ -1771,7 +1770,7 @@ def test_correct_url_used_for_s3(): +@@ -1862,7 +1862,7 @@ ) ) # Dual-stack with access-point arn @@ -1305,7 +1481,7 @@ Index: b/tests/functional/test_s3.py # Note: The access-point arn has us-west-2 and the client's region is # us-east-1, for the default case the access-point arn region is used. region='us-east-1', bucket=accesspoint_arn, key='key', -@@ -1783,7 +1782,7 @@ def test_correct_url_used_for_s3(): +@@ -1874,7 +1874,7 @@ 'us-west-2.amazonaws.com/key' ) ) @@ -1314,7 +1490,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket=accesspoint_arn, key='key', s3_config={ 'use_dualstack_endpoint': True, -@@ -1794,7 +1793,7 @@ def test_correct_url_used_for_s3(): +@@ -1885,7 +1885,7 @@ 'us-east-1.amazonaws.com/key' ) ) @@ -1323,7 +1499,7 @@ Index: b/tests/functional/test_s3.py region='us-gov-east-1', bucket=accesspoint_arn_gov, key='key', s3_config={ 'use_dualstack_endpoint': True, -@@ -1807,7 +1806,7 @@ def test_correct_url_used_for_s3(): +@@ -1898,7 +1898,7 @@ # None of the various s3 settings related to paths should affect what # endpoint to use when an access-point is provided. @@ -1332,7 +1508,7 @@ Index: b/tests/functional/test_s3.py region='us-west-2', bucket=accesspoint_arn, key='key', s3_config={'adressing_style': 'auto'}, expected_url=( -@@ -1815,7 +1814,7 @@ def test_correct_url_used_for_s3(): +@@ -1906,7 +1906,7 @@ 'us-west-2.amazonaws.com/key' ) ) @@ -1341,7 +1517,7 @@ Index: b/tests/functional/test_s3.py region='us-west-2', bucket=accesspoint_arn, key='key', s3_config={'adressing_style': 'virtual'}, expected_url=( -@@ -1823,7 +1822,7 @@ def test_correct_url_used_for_s3(): +@@ -1914,7 +1914,7 @@ 'us-west-2.amazonaws.com/key' ) ) @@ -1350,7 +1526,7 @@ Index: b/tests/functional/test_s3.py region='us-west-2', bucket=accesspoint_arn, key='key', s3_config={'adressing_style': 'path'}, expected_url=( -@@ -1836,27 +1835,27 @@ def test_correct_url_used_for_s3(): +@@ -1927,27 +1927,27 @@ us_east_1_regional_endpoint = { 'us_east_1_regional_endpoint': 'regional' } @@ -1383,7 +1559,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket='bucket', key='key', s3_config={ 'us_east_1_regional_endpoint': 'regional', -@@ -1864,7 +1863,7 @@ def test_correct_url_used_for_s3(): +@@ -1955,7 +1955,7 @@ }, expected_url=( 'https://bucket.s3.dualstack.us-east-1.amazonaws.com/key')) @@ -1392,7 +1568,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket='bucket', key='key', s3_config={ 'us_east_1_regional_endpoint': 'regional', -@@ -1872,7 +1871,7 @@ def test_correct_url_used_for_s3(): +@@ -1963,7 +1963,7 @@ }, expected_url=( 'https://bucket.s3-accelerate.amazonaws.com/key')) @@ -1401,7 +1577,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket='bucket', key='key', s3_config={ 'us_east_1_regional_endpoint': 'regional', -@@ -1886,19 +1885,19 @@ def test_correct_url_used_for_s3(): +@@ -1977,19 +1977,19 @@ us_east_1_regional_endpoint_legacy = { 'us_east_1_regional_endpoint': 'legacy' } @@ -1424,7 +1600,7 @@ Index: b/tests/functional/test_s3.py region='unknown', bucket='bucket', key='key', s3_config=us_east_1_regional_endpoint_legacy, expected_url=( -@@ -1950,7 +1949,7 @@ def _verify_expected_endpoint_url(region +@@ -2041,7 +2041,7 @@ with ClientHTTPStubber(s3) as http_stubber: http_stubber.add_response() s3.put_object(Bucket=bucket, Key=key, Body=b'bar') @@ -1433,7 +1609,7 @@ Index: b/tests/functional/test_s3.py def _create_s3_client(region, is_secure, endpoint_url, s3_config, -@@ -1983,96 +1982,96 @@ def test_addressing_for_presigned_urls() +@@ -2074,96 +2074,96 @@ # us-east-1, or the "global" endpoint. A signature version of # None means the user doesn't have signature version configured. @@ -1585,7 +1761,7 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket=accesspoint_arn, key='key', s3_config={'use_arn_region': False}, expected_url=( -@@ -2085,12 +2084,12 @@ def test_addressing_for_presigned_urls() +@@ -2176,12 +2176,12 @@ us_east_1_regional_endpoint = { 'us_east_1_regional_endpoint': 'regional' } @@ -1600,16 +1776,27 @@ Index: b/tests/functional/test_s3.py region='us-east-1', bucket='bucket', key='key', s3_config=us_east_1_regional_endpoint, signature_version='s3v4', expected_url=( -@@ -2112,4 +2111,4 @@ def _verify_presigned_url_addressing(reg +@@ -2203,4 +2203,4 @@ # those are tested elsewhere. We just care about the hostname/path. parts = urlsplit(url) actual = '%s://%s%s' % parts[:3] - assert_equal(actual, expected_url) + assert actual == expected_url -Index: b/tests/functional/test_service_names.py -=================================================================== ---- a/tests/functional/test_service_names.py -+++ b/tests/functional/test_service_names.py +diff -Nru botocore-1.18.15.orig/tests/functional/test_service_alias.py botocore-1.18.15/tests/functional/test_service_alias.py +--- botocore-1.18.15.orig/tests/functional/test_service_alias.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_service_alias.py 2020-10-09 10:13:49.548472372 +0200 +@@ -17,7 +17,7 @@ + def test_can_use_service_alias(): + session = botocore.session.get_session() + for (alias, name) in SERVICE_NAME_ALIASES.items(): +- yield _instantiates_the_same_client, session, name, alias ++ _instantiates_the_same_client(session, name, alias) + + + def _instantiates_the_same_client(session, service_name, service_alias): +diff -Nru botocore-1.18.15.orig/tests/functional/test_service_names.py botocore-1.18.15/tests/functional/test_service_names.py +--- botocore-1.18.15.orig/tests/functional/test_service_names.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_service_names.py 2020-10-09 10:13:49.512471875 +0200 @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. import re @@ -1618,7 +1805,7 @@ Index: b/tests/functional/test_service_names.py from botocore.session import get_session BLACKLIST = [ -@@ -41,18 +40,18 @@ MAX_SERVICE_NAME_LENGTH = 50 +@@ -41,18 +40,18 @@ def _assert_name_length(service_name): if service_name not in BLACKLIST: service_name_length = len(service_name) @@ -1645,7 +1832,7 @@ Index: b/tests/functional/test_service_names.py def test_service_names_are_valid(): -@@ -60,5 +59,5 @@ def test_service_names_are_valid(): +@@ -60,5 +59,5 @@ loader = session.get_component('data_loader') service_names = loader.list_available_services('service-2') for service_name in service_names: @@ -1653,10 +1840,180 @@ Index: b/tests/functional/test_service_names.py - yield _assert_name_pattern, service_name + _assert_name_length(service_name) + _assert_name_pattern(service_name) -Index: b/tests/integration/test_ec2.py -=================================================================== ---- a/tests/integration/test_ec2.py -+++ b/tests/integration/test_ec2.py +diff -Nru botocore-1.18.15.orig/tests/functional/test_session.py botocore-1.18.15/tests/functional/test_session.py +--- botocore-1.18.15.orig/tests/functional/test_session.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_session.py 2020-10-09 10:13:49.552472426 +0200 +@@ -12,7 +12,7 @@ + # language governing permissions and limitations under the License. + from tests import unittest, temporary_file + +-import mock ++from tests import mock + + import botocore.session + from botocore.exceptions import ProfileNotFound +diff -Nru botocore-1.18.15.orig/tests/functional/test_six_imports.py botocore-1.18.15/tests/functional/test_six_imports.py +--- botocore-1.18.15.orig/tests/functional/test_six_imports.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_six_imports.py 2020-10-09 10:13:49.548472372 +0200 +@@ -15,7 +15,7 @@ + if not filename.endswith('.py'): + continue + fullname = os.path.join(rootdir, filename) +- yield _assert_no_bare_six_imports, fullname ++ _assert_no_bare_six_imports(fullname) + + + def _assert_no_bare_six_imports(filename): +diff -Nru botocore-1.18.15.orig/tests/functional/test_sts.py botocore-1.18.15/tests/functional/test_sts.py +--- botocore-1.18.15.orig/tests/functional/test_sts.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_sts.py 2020-10-09 10:13:49.552472426 +0200 +@@ -13,7 +13,7 @@ + from datetime import datetime + import re + +-import mock ++from tests import mock + + from tests import BaseSessionTest + from tests import temporary_file +diff -Nru botocore-1.18.15.orig/tests/functional/test_stub.py botocore-1.18.15/tests/functional/test_stub.py +--- botocore-1.18.15.orig/tests/functional/test_stub.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_stub.py 2020-10-09 10:13:49.528472096 +0200 +@@ -16,6 +16,7 @@ + import botocore + import botocore.session + import botocore.stub as stub ++from botocore.compat import six + from botocore.stub import Stubber + from botocore.exceptions import StubResponseError, ClientError, \ + StubAssertionError, UnStubbedResponseError +@@ -54,8 +55,8 @@ + def test_activated_stubber_errors_with_no_registered_stubs(self): + self.stubber.activate() + # Params one per line for readability. +- with self.assertRaisesRegexp(UnStubbedResponseError, +- "Unexpected API Call"): ++ with six.assertRaisesRegex(self, UnStubbedResponseError, ++ "Unexpected API Call"): + self.client.list_objects( + Bucket='asdfasdfasdfasdf', + Delimiter='asdfasdfasdfasdf', +@@ -119,8 +120,8 @@ + 'list_objects', service_response, expected_params) + self.stubber.activate() + # This should call should raise an for mismatching expected params. +- with self.assertRaisesRegexp(StubResponseError, +- "{'Bucket': 'bar'},\n"): ++ with six.assertRaisesRegex(self, StubResponseError, ++ "{'Bucket': 'bar'},\n"): + self.client.list_objects(Bucket='foo') + + def test_expected_params_mixed_with_errors_responses(self): +@@ -143,7 +144,8 @@ + self.client.list_objects(Bucket='foo') + + # The second call should throw an error for unexpected parameters +- with self.assertRaisesRegexp(StubResponseError, 'Expected parameters'): ++ with six.assertRaisesRegex(self, StubResponseError, ++ 'Expected parameters'): + self.client.list_objects(Bucket='foo') + + def test_can_continue_to_call_after_expected_params_fail(self): +diff -Nru botocore-1.18.15.orig/tests/functional/test_waiter_config.py botocore-1.18.15/tests/functional/test_waiter_config.py +--- botocore-1.18.15.orig/tests/functional/test_waiter_config.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/functional/test_waiter_config.py 2020-10-09 10:13:49.548472372 +0200 +@@ -98,9 +98,9 @@ + except UnknownServiceError: + # The service doesn't have waiters + continue +- yield _validate_schema, validator, waiter_model ++ _validate_schema(validator, waiter_model) + for waiter_name in client.waiter_names: +- yield _lint_single_waiter, client, waiter_name, service_model ++ _lint_single_waiter(client, waiter_name, service_model) + + + def _lint_single_waiter(client, waiter_name, service_model): +diff -Nru botocore-1.18.15.orig/tests/__init__.py botocore-1.18.15/tests/__init__.py +--- botocore-1.18.15.orig/tests/__init__.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/__init__.py 2020-10-09 10:13:49.504471764 +0200 +@@ -13,7 +13,10 @@ + + import os + import sys +-import mock ++try: ++ import mock ++except ImportError: ++ from unittest import mock + import time + import random + import shutil +@@ -29,8 +32,6 @@ + from dateutil.tz import tzlocal + import unittest + +-from nose.tools import assert_equal +- + import botocore.loaders + import botocore.session + from botocore.awsrequest import AWSResponse +@@ -346,16 +347,16 @@ + + # Because the query string ordering isn't relevant, we have to parse + # every single part manually and then handle the query string. +- assert_equal(parts1.scheme, parts2.scheme) +- assert_equal(parts1.netloc, parts2.netloc) +- assert_equal(parts1.path, parts2.path) +- assert_equal(parts1.params, parts2.params) +- assert_equal(parts1.fragment, parts2.fragment) +- assert_equal(parts1.username, parts2.username) +- assert_equal(parts1.password, parts2.password) +- assert_equal(parts1.hostname, parts2.hostname) +- assert_equal(parts1.port, parts2.port) +- assert_equal(parse_qs(parts1.query), parse_qs(parts2.query)) ++ assert parts1.scheme == parts2.scheme ++ assert parts1.netloc == parts2.netloc ++ assert parts1.path == parts2.path ++ assert parts1.params == parts2.params ++ assert parts1.fragment == parts2.fragment ++ assert parts1.username == parts2.username ++ assert parts1.password == parts2.password ++ assert parts1.hostname == parts2.hostname ++ assert parts1.port == parts2.port ++ assert parse_qs(parts1.query) == parse_qs(parts2.query) + + + class HTTPStubberException(Exception): +diff -Nru botocore-1.18.15.orig/tests/integration/test_client.py botocore-1.18.15/tests/integration/test_client.py +--- botocore-1.18.15.orig/tests/integration/test_client.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/integration/test_client.py 2020-10-09 10:13:49.532472151 +0200 +@@ -79,8 +79,8 @@ + def test_region_mentioned_in_invalid_region(self): + client = self.session.create_client( + 'cloudformation', region_name='us-east-999') +- with self.assertRaisesRegexp(EndpointConnectionError, +- 'Could not connect to the endpoint URL'): ++ with six.assertRaisesRegex(self, EndpointConnectionError, ++ 'Could not connect to the endpoint URL'): + client.list_stacks() + + def test_client_modeled_exception(self): +diff -Nru botocore-1.18.15.orig/tests/integration/test_credentials.py botocore-1.18.15/tests/integration/test_credentials.py +--- botocore-1.18.15.orig/tests/integration/test_credentials.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/integration/test_credentials.py 2020-10-09 10:13:49.552472426 +0200 +@@ -11,7 +11,7 @@ + # ANY KIND, either express or implied. See the License for the specific + # language governing permissions and limitations under the License. + import os +-import mock ++from tests import mock + import tempfile + import shutil + import json +diff -Nru botocore-1.18.15.orig/tests/integration/test_ec2.py botocore-1.18.15/tests/integration/test_ec2.py +--- botocore-1.18.15.orig/tests/integration/test_ec2.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/integration/test_ec2.py 2020-10-09 10:13:49.512471875 +0200 @@ -13,8 +13,6 @@ from tests import unittest import itertools @@ -1666,10 +2023,9 @@ Index: b/tests/integration/test_ec2.py import botocore.session from botocore.exceptions import ClientError -Index: b/tests/integration/test_emr.py -=================================================================== ---- a/tests/integration/test_emr.py -+++ b/tests/integration/test_emr.py +diff -Nru botocore-1.18.15.orig/tests/integration/test_emr.py botocore-1.18.15/tests/integration/test_emr.py +--- botocore-1.18.15.orig/tests/integration/test_emr.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/integration/test_emr.py 2020-10-09 10:13:49.516471930 +0200 @@ -12,8 +12,6 @@ # language governing permissions and limitations under the License. from tests import unittest @@ -1679,7 +2035,7 @@ Index: b/tests/integration/test_emr.py import botocore.session from botocore.paginate import PageIterator from botocore.exceptions import OperationNotPageableError -@@ -34,7 +32,7 @@ def test_emr_endpoints_work_with_py26(): +@@ -34,7 +32,7 @@ def _test_can_list_clusters_in_region(session, region): client = session.create_client('emr', region_name=region) response = client.list_clusters() @@ -1688,11 +2044,22 @@ Index: b/tests/integration/test_emr.py # I consider these integration tests because they're -Index: b/tests/integration/test_s3.py -=================================================================== ---- a/tests/integration/test_s3.py -+++ b/tests/integration/test_s3.py -@@ -22,11 +22,10 @@ import tempfile +diff -Nru botocore-1.18.15.orig/tests/integration/test_loaders.py botocore-1.18.15/tests/integration/test_loaders.py +--- botocore-1.18.15.orig/tests/integration/test_loaders.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/integration/test_loaders.py 2020-10-09 10:13:49.552472426 +0200 +@@ -13,7 +13,7 @@ + import os + from tests import unittest + +-import mock ++from tests import mock + + import botocore.session + +diff -Nru botocore-1.18.15.orig/tests/integration/test_s3.py botocore-1.18.15/tests/integration/test_s3.py +--- botocore-1.18.15.orig/tests/integration/test_s3.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/integration/test_s3.py 2020-10-09 10:13:49.516471930 +0200 +@@ -22,11 +22,10 @@ import shutil import threading import logging @@ -1705,7 +2072,7 @@ Index: b/tests/integration/test_s3.py import urllib3 from botocore.endpoint import Endpoint -@@ -324,7 +323,7 @@ class TestS3Objects(TestS3BaseWithBucket +@@ -324,7 +323,7 @@ Bucket=self.bucket_name, Key=key_name) self.assert_status_code(response, 204) @@ -1714,7 +2081,7 @@ Index: b/tests/integration/test_s3.py def test_can_paginate(self): for i in range(5): key_name = 'key%s' % i -@@ -340,7 +339,7 @@ class TestS3Objects(TestS3BaseWithBucket +@@ -340,7 +339,7 @@ for el in responses] self.assertEqual(key_names, ['key0', 'key1', 'key2', 'key3', 'key4']) @@ -1723,7 +2090,7 @@ Index: b/tests/integration/test_s3.py def test_can_paginate_with_page_size(self): for i in range(5): key_name = 'key%s' % i -@@ -357,7 +356,7 @@ class TestS3Objects(TestS3BaseWithBucket +@@ -357,7 +356,7 @@ for el in data] self.assertEqual(key_names, ['key0', 'key1', 'key2', 'key3', 'key4']) @@ -1732,7 +2099,7 @@ Index: b/tests/integration/test_s3.py def test_result_key_iters(self): for i in range(5): key_name = 'key/%s/%s' % (i, i) -@@ -380,7 +379,7 @@ class TestS3Objects(TestS3BaseWithBucket +@@ -380,7 +379,7 @@ self.assertIn('Contents', response) self.assertIn('CommonPrefixes', response) @@ -1741,7 +2108,7 @@ Index: b/tests/integration/test_s3.py def test_can_get_and_put_object(self): self.create_object('foobarbaz', body='body contents') time.sleep(3) -@@ -930,7 +929,7 @@ class TestS3SigV4Client(BaseS3ClientTest +@@ -930,7 +929,7 @@ Key='foo.txt', Body=body) self.assert_status_code(response, 200) @@ -1750,7 +2117,7 @@ Index: b/tests/integration/test_s3.py def test_paginate_list_objects_unicode(self): key_names = [ u'non-ascii-key-\xe4\xf6\xfc-01.txt', -@@ -953,7 +952,7 @@ class TestS3SigV4Client(BaseS3ClientTest +@@ -953,7 +952,7 @@ self.assertEqual(key_names, key_refs) @@ -1759,7 +2126,7 @@ Index: b/tests/integration/test_s3.py def test_paginate_list_objects_safe_chars(self): key_names = [ u'-._~safe-chars-key-01.txt', -@@ -1247,7 +1246,7 @@ class TestRegionRedirect(BaseS3ClientTes +@@ -1247,7 +1246,7 @@ eu_bucket = self.create_bucket(self.bucket_region) msg = 'The authorization mechanism you have provided is not supported.' @@ -1768,11 +2135,10 @@ Index: b/tests/integration/test_s3.py sigv2_client.list_objects(Bucket=eu_bucket) def test_region_redirects_multiple_requests(self): -Index: b/tests/integration/test_smoke.py -=================================================================== ---- a/tests/integration/test_smoke.py -+++ b/tests/integration/test_smoke.py -@@ -11,17 +11,14 @@ to use and all the services in SMOKE_TES +diff -Nru botocore-1.18.15.orig/tests/integration/test_smoke.py botocore-1.18.15/tests/integration/test_smoke.py +--- botocore-1.18.15.orig/tests/integration/test_smoke.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/integration/test_smoke.py 2020-10-09 10:13:49.516471930 +0200 +@@ -11,17 +11,14 @@ """ import os @@ -1790,7 +2156,7 @@ Index: b/tests/integration/test_smoke.py from botocore.exceptions import ConnectionClosedError -@@ -262,10 +259,9 @@ def _make_client_call(client, operation_ +@@ -262,10 +259,9 @@ method = getattr(client, operation_name) with warnings.catch_warnings(record=True) as caught_warnings: response = method(**kwargs) @@ -1804,7 +2170,7 @@ Index: b/tests/integration/test_smoke.py def test_can_make_request_and_understand_errors_with_client(): -@@ -275,7 +271,7 @@ def test_can_make_request_and_understand +@@ -275,7 +271,7 @@ for operation_name in ERROR_TESTS[service_name]: kwargs = ERROR_TESTS[service_name][operation_name] method_name = xform_name(operation_name) @@ -1813,10 +2179,28 @@ Index: b/tests/integration/test_smoke.py def _make_error_client_call(client, operation_name, kwargs): -Index: b/tests/integration/test_waiters.py -=================================================================== ---- a/tests/integration/test_waiters.py -+++ b/tests/integration/test_waiters.py +diff -Nru botocore-1.18.15.orig/tests/integration/test_sts.py botocore-1.18.15/tests/integration/test_sts.py +--- botocore-1.18.15.orig/tests/integration/test_sts.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/integration/test_sts.py 2020-10-09 10:13:49.532472151 +0200 +@@ -13,6 +13,8 @@ + from tests import unittest + + import botocore.session ++ ++from botocore.compat import six + from botocore.exceptions import ClientError + + class TestSTS(unittest.TestCase): +@@ -38,5 +40,5 @@ + self.assertEqual(sts.meta.endpoint_url, + 'https://sts.us-west-2.amazonaws.com') + # Signing error will be thrown with the incorrect region name included. +- with self.assertRaisesRegexp(ClientError, 'ap-southeast-1') as e: ++ with six.assertRaisesRegex(self, ClientError, 'ap-southeast-1'): + sts.get_session_token() +diff -Nru botocore-1.18.15.orig/tests/integration/test_waiters.py botocore-1.18.15/tests/integration/test_waiters.py +--- botocore-1.18.15.orig/tests/integration/test_waiters.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/integration/test_waiters.py 2020-10-09 10:13:49.516471930 +0200 @@ -12,14 +12,14 @@ # language governing permissions and limitations under the License. from tests import unittest, random_chars @@ -1834,11 +2218,22 @@ Index: b/tests/integration/test_waiters.py class TestWaiterForDynamoDB(unittest.TestCase): def setUp(self): self.session = botocore.session.get_session() -Index: b/tests/unit/auth/test_sigv4.py -=================================================================== ---- a/tests/unit/auth/test_sigv4.py -+++ b/tests/unit/auth/test_sigv4.py -@@ -18,8 +18,7 @@ AWS provides a test suite for signature +diff -Nru botocore-1.18.15.orig/tests/unit/auth/test_signers.py botocore-1.18.15/tests/unit/auth/test_signers.py +--- botocore-1.18.15.orig/tests/unit/auth/test_signers.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/unit/auth/test_signers.py 2020-10-09 10:13:49.552472426 +0200 +@@ -18,7 +18,7 @@ + import base64 + import json + +-import mock ++from tests import mock + + import botocore.auth + import botocore.credentials +diff -Nru botocore-1.18.15.orig/tests/unit/auth/test_sigv4.py botocore-1.18.15/tests/unit/auth/test_sigv4.py +--- botocore-1.18.15.orig/tests/unit/auth/test_sigv4.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/unit/auth/test_sigv4.py 2020-10-09 10:13:49.516471930 +0200 +@@ -18,8 +18,7 @@ http://docs.aws.amazon.com/general/latest/gr/signature-v4-test-suite.html This module contains logic to run these tests. The test files were @@ -1848,7 +2243,7 @@ Index: b/tests/unit/auth/test_sigv4.py """ import os -@@ -28,7 +27,7 @@ import io +@@ -28,7 +27,7 @@ import datetime from botocore.compat import six @@ -1857,7 +2252,7 @@ Index: b/tests/unit/auth/test_sigv4.py import botocore.auth from botocore.awsrequest import AWSRequest -@@ -106,7 +105,7 @@ def test_generator(): +@@ -106,7 +105,7 @@ if test_case in TESTS_TO_IGNORE: log.debug("Skipping test: %s", test_case) continue @@ -1866,7 +2261,7 @@ Index: b/tests/unit/auth/test_sigv4.py datetime_patcher.stop() formatdate_patcher.stop() -@@ -147,21 +146,22 @@ def _test_signature_version_4(test_case) +@@ -147,21 +146,22 @@ auth = botocore.auth.SigV4Auth(test_case.credentials, 'host', 'us-east-1') actual_canonical_request = auth.canonical_request(request) @@ -1896,10 +2291,149 @@ Index: b/tests/unit/auth/test_sigv4.py if actual != expected: message = "The %s did not match" % part message += "\nACTUAL:%r !=\nEXPECT:%r" % (actual, expected) -Index: b/tests/unit/retries/test_special.py -=================================================================== ---- a/tests/unit/retries/test_special.py -+++ b/tests/unit/retries/test_special.py +diff -Nru botocore-1.18.15.orig/tests/unit/docs/bcdoc/test_docstringparser.py botocore-1.18.15/tests/unit/docs/bcdoc/test_docstringparser.py +--- botocore-1.18.15.orig/tests/unit/docs/bcdoc/test_docstringparser.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/unit/docs/bcdoc/test_docstringparser.py 2020-10-09 10:13:49.556472483 +0200 +@@ -18,7 +18,7 @@ + # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + # IN THE SOFTWARE. +-import mock ++from tests import mock + from tests import unittest + + import botocore.docs.bcdoc.docstringparser as parser +diff -Nru botocore-1.18.15.orig/tests/unit/docs/__init__.py botocore-1.18.15/tests/unit/docs/__init__.py +--- botocore-1.18.15.orig/tests/unit/docs/__init__.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/unit/docs/__init__.py 2020-10-09 10:13:49.552472426 +0200 +@@ -16,7 +16,7 @@ + import shutil + + from botocore.docs.bcdoc.restdoc import DocumentStructure +-import mock ++from tests import mock + + from tests import unittest + from botocore.compat import OrderedDict +diff -Nru botocore-1.18.15.orig/tests/unit/docs/test_docs.py botocore-1.18.15/tests/unit/docs/test_docs.py +--- botocore-1.18.15.orig/tests/unit/docs/test_docs.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/unit/docs/test_docs.py 2020-10-09 10:13:49.556472483 +0200 +@@ -14,7 +14,7 @@ + import shutil + import tempfile + +-import mock ++from tests import mock + + from tests.unit.docs import BaseDocsTest + from botocore.session import get_session +diff -Nru botocore-1.18.15.orig/tests/unit/docs/test_example.py botocore-1.18.15/tests/unit/docs/test_example.py +--- botocore-1.18.15.orig/tests/unit/docs/test_example.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/unit/docs/test_example.py 2020-10-09 10:13:49.556472483 +0200 +@@ -10,7 +10,7 @@ + # 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. +-import mock ++from tests import mock + + from tests.unit.docs import BaseDocsTest + from botocore.hooks import HierarchicalEmitter +diff -Nru botocore-1.18.15.orig/tests/unit/docs/test_params.py botocore-1.18.15/tests/unit/docs/test_params.py +--- botocore-1.18.15.orig/tests/unit/docs/test_params.py 2020-10-08 20:05:10.000000000 +0200 ++++ botocore-1.18.15/tests/unit/docs/test_params.py 2020-10-09 10:13:49.556472483 +0200 +@@ -10,7 +10,7 @@ + # 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. +-import mock ++from tests import mock + + from tests.unit.docs import BaseDocsTest + from botocore.hooks import HierarchicalEmitter +diff -Nru botocore-1.18.15.orig/tests/unit/docs/test_service.py botocore-1.18.15/tests/unit/docs/test_service.py +--- botocore-1.18.15.orig/tests/unit/docs/test_service.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/docs/test_service.py 2020-10-09 10:13:49.556472483 +0200 +@@ -12,7 +12,7 @@ + # language governing permissions and limitations under the License. + import os + +-import mock ++from tests import mock + + from tests.unit.docs import BaseDocsTest + from botocore.session import get_session +diff -Nru botocore-1.18.15.orig/tests/unit/docs/test_utils.py botocore-1.18.15/tests/unit/docs/test_utils.py +--- botocore-1.18.15.orig/tests/unit/docs/test_utils.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/docs/test_utils.py 2020-10-09 10:13:49.532472151 +0200 +@@ -223,5 +223,5 @@ + class TestEscapeControls(unittest.TestCase): + def test_escapes_controls(self): + escaped = escape_controls('\na\rb\tc\fd\be') +- self.assertEquals(escaped, '\\na\\rb\\tc\\fd\\be') ++ self.assertEqual(escaped, '\\na\\rb\\tc\\fd\\be') + +diff -Nru botocore-1.18.15.orig/tests/unit/response_parsing/README.rst botocore-1.18.15/tests/unit/response_parsing/README.rst +--- botocore-1.18.15.orig/tests/unit/response_parsing/README.rst 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/response_parsing/README.rst 2020-10-09 10:13:49.528472096 +0200 +@@ -16,12 +16,12 @@ + file contains the expected Python data structure created from the XML + response. + +-The main test is contained in ``test_response_parser.py`` and is +-implemented as a nose generator. Each time through the loop an XML +-file is read and passed to a ``botocore.response.XmlResponse`` +-object. The corresponding JSON file is then parsed and compared to +-the value created by the parser. If the are equal, the test passes. If +-they are not equal, both the expected result and the actual result are ++The main test is contained in ``test_response_parser.py``. Each ++time through the loop an XML file is read and passed to ++a ``botocore.response.XmlResponse`` object. The corresponding ++JSON file is then parsed and compared to the value created by the ++parser. If the are equal, the test passes. If they are not ++equal, both the expected result and the actual result are + pretty-printed to stdout and the tests continue. + + ----------------- +diff -Nru botocore-1.18.15.orig/tests/unit/response_parsing/test_response_parsing.py botocore-1.18.15/tests/unit/response_parsing/test_response_parsing.py +--- botocore-1.18.15.orig/tests/unit/response_parsing/test_response_parsing.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/response_parsing/test_response_parsing.py 2020-10-09 10:13:49.532472151 +0200 +@@ -119,8 +119,8 @@ + expected = _get_expected_parsed_result(xmlfile) + operation_model = _get_operation_model(service_model, xmlfile) + raw_response_body = _get_raw_response_body(xmlfile) +- yield _test_parsed_response, xmlfile, raw_response_body, \ +- operation_model, expected ++ _test_parsed_response(xmlfile, raw_response_body, ++ operation_model, expected) + + + def _get_raw_response_body(xmlfile): +@@ -179,8 +179,8 @@ + operation_model = service_model.operation_model(op_name) + with open(raw_response_file, 'rb') as f: + raw_response_body = f.read() +- yield _test_parsed_response, raw_response_file, \ +- raw_response_body, operation_model, expected ++ _test_parsed_response(raw_response_file, ++ raw_response_body, operation_model, expected) + + + def _uhg_test_json_parsing(): +diff -Nru botocore-1.18.15.orig/tests/unit/retries/test_adaptive.py botocore-1.18.15/tests/unit/retries/test_adaptive.py +--- botocore-1.18.15.orig/tests/unit/retries/test_adaptive.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/retries/test_adaptive.py 2020-10-09 10:13:49.556472483 +0200 +@@ -1,6 +1,6 @@ + from tests import unittest + +-import mock ++from tests import mock + + from botocore.retries import adaptive + from botocore.retries import standard +diff -Nru botocore-1.18.15.orig/tests/unit/retries/test_special.py botocore-1.18.15/tests/unit/retries/test_special.py +--- botocore-1.18.15.orig/tests/unit/retries/test_special.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/retries/test_special.py 2020-10-09 10:13:49.516471930 +0200 @@ -1,9 +1,7 @@ from tests import unittest @@ -1911,10 +2445,9 @@ Index: b/tests/unit/retries/test_special.py from botocore.awsrequest import AWSResponse from botocore.retries import standard, special -Index: b/tests/unit/retries/test_standard.py -=================================================================== ---- a/tests/unit/retries/test_standard.py -+++ b/tests/unit/retries/test_standard.py +diff -Nru botocore-1.18.15.orig/tests/unit/retries/test_standard.py botocore-1.18.15/tests/unit/retries/test_standard.py +--- botocore-1.18.15.orig/tests/unit/retries/test_standard.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/retries/test_standard.py 2020-10-09 10:13:49.520471985 +0200 @@ -1,7 +1,6 @@ from tests import unittest @@ -1924,7 +2457,7 @@ Index: b/tests/unit/retries/test_standard.py from botocore.retries import standard from botocore.retries import quota -@@ -154,22 +153,20 @@ SERVICE_DESCRIPTION_WITH_RETRIES = { +@@ -154,22 +153,20 @@ def test_can_detect_retryable_transient_errors(): transient_checker = standard.TransientRetryableChecker() for case in RETRYABLE_TRANSIENT_ERRORS: @@ -1951,7 +2484,7 @@ Index: b/tests/unit/retries/test_standard.py def test_standard_retry_conditions(): -@@ -184,9 +181,8 @@ def test_standard_retry_conditions(): +@@ -184,9 +181,8 @@ # are retryable for a different checker. We need to filter out all # the False cases. all_cases = [c for c in all_cases if c[2]] @@ -1962,7 +2495,7 @@ Index: b/tests/unit/retries/test_standard.py def get_operation_model_with_retries(): -@@ -213,7 +209,7 @@ def _verify_retryable(checker, operation +@@ -213,7 +209,7 @@ http_response=http_response, caught_exception=caught_exception, ) @@ -1971,7 +2504,7 @@ Index: b/tests/unit/retries/test_standard.py def arbitrary_retry_context(): -@@ -233,36 +229,36 @@ def test_can_honor_max_attempts(): +@@ -233,36 +229,36 @@ checker = standard.MaxAttemptsChecker(max_attempts=3) context = arbitrary_retry_context() context.attempt_number = 1 @@ -2017,10 +2550,114 @@ Index: b/tests/unit/retries/test_standard.py class TestRetryHandler(unittest.TestCase): -Index: b/tests/unit/test_compat.py -=================================================================== ---- a/tests/unit/test_compat.py -+++ b/tests/unit/test_compat.py +diff -Nru botocore-1.18.15.orig/tests/unit/test_args.py botocore-1.18.15/tests/unit/test_args.py +--- botocore-1.18.15.orig/tests/unit/test_args.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_args.py 2020-10-09 10:13:49.556472483 +0200 +@@ -15,7 +15,7 @@ + + import botocore.config + from tests import unittest +-import mock ++from tests import mock + + from botocore import args + from botocore import exceptions +diff -Nru botocore-1.18.15.orig/tests/unit/test_awsrequest.py botocore-1.18.15/tests/unit/test_awsrequest.py +--- botocore-1.18.15.orig/tests/unit/test_awsrequest.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_awsrequest.py 2020-10-09 10:13:49.532472151 +0200 +@@ -18,13 +18,15 @@ + import shutil + import io + import socket +-import sys + +-from mock import Mock, patch ++try: ++ from mock import Mock, patch ++except ImportError: ++ from unittest.mock import Mock, patch + from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool + + from botocore.exceptions import UnseekableStreamError +-from botocore.awsrequest import AWSRequest, AWSPreparedRequest, AWSResponse ++from botocore.awsrequest import AWSRequest, AWSResponse + from botocore.awsrequest import AWSHTTPConnection, AWSHTTPSConnection, HeadersDict + from botocore.awsrequest import prepare_request_dict, create_request_object + from botocore.compat import file_type, six +@@ -271,11 +273,11 @@ + def test_text_property(self): + self.set_raw_stream([b'\xe3\x82\xb8\xe3\x83\xa7\xe3\x82\xb0']) + self.response.headers['content-type'] = 'text/plain; charset=utf-8' +- self.assertEquals(self.response.text, u'\u30b8\u30e7\u30b0') ++ self.assertEqual(self.response.text, u'\u30b8\u30e7\u30b0') + + def test_text_property_defaults_utf8(self): + self.set_raw_stream([b'\xe3\x82\xb8\xe3\x83\xa7\xe3\x82\xb0']) +- self.assertEquals(self.response.text, u'\u30b8\u30e7\u30b0') ++ self.assertEqual(self.response.text, u'\u30b8\u30e7\u30b0') + + + class TestAWSHTTPConnection(unittest.TestCase): +diff -Nru botocore-1.18.15.orig/tests/unit/test_client.py botocore-1.18.15/tests/unit/test_client.py +--- botocore-1.18.15.orig/tests/unit/test_client.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_client.py 2020-10-09 10:13:49.532472151 +0200 +@@ -12,7 +12,7 @@ + # language governing permissions and limitations under the License. + import botocore.config + from tests import unittest +-import mock ++from tests import mock + + import botocore + from botocore import utils +@@ -554,8 +554,8 @@ + creator = self.create_client_creator() + service_client = creator.create_client( + 'myservice', 'us-west-2', credentials=self.credentials) +- with self.assertRaisesRegexp( +- TypeError, 'only accepts keyword arguments'): ++ with six.assertRaisesRegex(self, TypeError, ++ 'only accepts keyword arguments'): + service_client.test_operation('foo') + + @mock.patch('botocore.args.RequestSigner.sign') +@@ -1550,15 +1550,15 @@ + self.assertEqual(config.read_timeout, 50) + + def test_invalid_kwargs(self): +- with self.assertRaisesRegexp(TypeError, 'Got unexpected keyword'): ++ with six.assertRaisesRegex(self, TypeError, 'Got unexpected keyword'): + botocore.config.Config(foo='foo') + + def test_pass_invalid_length_of_args(self): +- with self.assertRaisesRegexp(TypeError, 'Takes at most'): ++ with six.assertRaisesRegex(self, TypeError, 'Takes at most'): + botocore.config.Config('foo', *botocore.config.Config.OPTION_DEFAULTS.values()) + + def test_create_with_multiple_kwargs(self): +- with self.assertRaisesRegexp(TypeError, 'Got multiple values'): ++ with six.assertRaisesRegex(self, TypeError, 'Got multiple values'): + botocore.config.Config('us-east-1', region_name='us-east-1') + + def test_merge_returns_new_config_object(self): +@@ -1610,10 +1610,10 @@ + self.assertEqual(config.retries['max_attempts'], 15) + + def test_validates_retry_config(self): +- with self.assertRaisesRegexp( +- InvalidRetryConfigurationError, +- 'Cannot provide retry configuration for "not-allowed"'): +- botocore.config.Config(retries={'not-allowed': True}) ++ with six.assertRaisesRegex( ++ self, InvalidRetryConfigurationError, ++ 'Cannot provide retry configuration for "not-allowed"'): ++ botocore.config.Config(retries={'not-allowed': True}) + + def test_validates_max_retry_attempts(self): + with self.assertRaises(InvalidMaxRetryAttemptsError): +diff -Nru botocore-1.18.15.orig/tests/unit/test_compat.py botocore-1.18.15/tests/unit/test_compat.py +--- botocore-1.18.15.orig/tests/unit/test_compat.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_compat.py 2020-10-09 10:13:49.520471985 +0200 @@ -11,9 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. @@ -2032,7 +2669,7 @@ Index: b/tests/unit/test_compat.py from botocore.exceptions import MD5UnavailableError from botocore.compat import ( -@@ -98,80 +96,76 @@ class TestGetMD5(unittest.TestCase): +@@ -98,80 +96,76 @@ get_md5() @@ -2177,10 +2814,21 @@ Index: b/tests/unit/test_compat.py for tzinfo in options: self.assertIsInstance(tzinfo(), datetime.tzinfo) -Index: b/tests/unit/test_config_provider.py -=================================================================== ---- a/tests/unit/test_config_provider.py -+++ b/tests/unit/test_config_provider.py +diff -Nru botocore-1.18.15.orig/tests/unit/test_configloader.py botocore-1.18.15/tests/unit/test_configloader.py +--- botocore-1.18.15.orig/tests/unit/test_configloader.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_configloader.py 2020-10-09 10:13:49.556472483 +0200 +@@ -14,7 +14,7 @@ + # language governing permissions and limitations under the License. + from tests import unittest, BaseEnvVar + import os +-import mock ++from tests import mock + import tempfile + import shutil + +diff -Nru botocore-1.18.15.orig/tests/unit/test_config_provider.py botocore-1.18.15/tests/unit/test_config_provider.py +--- botocore-1.18.15.orig/tests/unit/test_config_provider.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_config_provider.py 2020-10-09 10:13:49.520471985 +0200 @@ -11,8 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. @@ -2191,7 +2839,7 @@ Index: b/tests/unit/test_config_provider.py import botocore import botocore.session as session -@@ -308,7 +307,7 @@ class TestConfigValueStore(unittest.Test +@@ -308,7 +307,7 @@ provider = ConfigValueStore() provider.set_config_variable('fake_variable', 'foo') value = provider.get_config_variable('fake_variable') @@ -2200,7 +2848,7 @@ Index: b/tests/unit/test_config_provider.py def test_can_set_config_provider(self): foo_value_provider = mock.Mock(spec=BaseProvider) -@@ -448,7 +447,7 @@ def assert_chain_does_provide(providers, +@@ -448,7 +447,7 @@ providers=providers, ) value = provider.provide() @@ -2209,7 +2857,7 @@ Index: b/tests/unit/test_config_provider.py def test_chain_provider(): -@@ -468,9 +467,9 @@ def test_chain_provider(): +@@ -468,9 +467,9 @@ ('foo', ['foo', 'bar', 'baz']), ] for case in cases: @@ -2222,10 +2870,159 @@ Index: b/tests/unit/test_config_provider.py class TestChainProvider(unittest.TestCase): -Index: b/tests/unit/test_eventstream.py -=================================================================== ---- a/tests/unit/test_eventstream.py -+++ b/tests/unit/test_eventstream.py +diff -Nru botocore-1.18.15.orig/tests/unit/test_credentials.py botocore-1.18.15/tests/unit/test_credentials.py +--- botocore-1.18.15.orig/tests/unit/test_credentials.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_credentials.py 2020-10-09 10:13:49.536472205 +0200 +@@ -13,7 +13,7 @@ + # language governing permissions and limitations under the License. + from datetime import datetime, timedelta + import subprocess +-import mock ++from tests import mock + import os + import tempfile + import shutil +@@ -1083,7 +1083,7 @@ + "Credentials were refreshed, but the refreshed credentials are " + "still expired." + ) +- with self.assertRaisesRegexp(RuntimeError, error_message): ++ with six.assertRaisesRegex(self, RuntimeError, error_message): + creds.get_frozen_credentials() + + def test_partial_creds_is_an_error(self): +@@ -1149,7 +1149,7 @@ + "Credentials were refreshed, but the refreshed credentials are " + "still expired." + ) +- with self.assertRaisesRegexp(RuntimeError, error_message): ++ with six.assertRaisesRegex(self, RuntimeError, error_message): + creds.get_frozen_credentials() + + # Now we update the environment with non-expired credentials, +@@ -2745,7 +2745,7 @@ + mandatory_refresh=7, + refresh_function=fail_refresh + ) +- with self.assertRaisesRegexp(Exception, 'refresh failed'): ++ with six.assertRaisesRegex(self, Exception, 'refresh failed'): + creds.get_frozen_credentials() + + def test_exception_propogated_on_expired_credentials(self): +@@ -2758,7 +2758,7 @@ + mandatory_refresh=7, + refresh_function=fail_refresh + ) +- with self.assertRaisesRegexp(Exception, 'refresh failed'): ++ with six.assertRaisesRegex(self, Exception, 'refresh failed'): + # Because credentials are actually expired, any + # failure to refresh should be propagated. + creds.get_frozen_credentials() +@@ -2779,7 +2779,7 @@ + creds_last_for=-2, + ) + err_msg = 'refreshed credentials are still expired' +- with self.assertRaisesRegexp(RuntimeError, err_msg): ++ with six.assertRaisesRegex(self, RuntimeError, err_msg): + # Because credentials are actually expired, any + # failure to refresh should be propagated. + creds.get_frozen_credentials() +@@ -3067,7 +3067,7 @@ + + provider = self.create_process_provider() + exception = botocore.exceptions.CredentialRetrievalError +- with self.assertRaisesRegexp(exception, 'Error Message'): ++ with six.assertRaisesRegex(self, exception, 'Error Message'): + provider.load() + + def test_unsupported_version_raises_mismatch(self): +@@ -3085,7 +3085,7 @@ + + provider = self.create_process_provider() + exception = botocore.exceptions.CredentialRetrievalError +- with self.assertRaisesRegexp(exception, 'Unsupported version'): ++ with six.assertRaisesRegex(self, exception, 'Unsupported version'): + provider.load() + + def test_missing_version_in_payload_returned_raises_exception(self): +@@ -3102,7 +3102,7 @@ + + provider = self.create_process_provider() + exception = botocore.exceptions.CredentialRetrievalError +- with self.assertRaisesRegexp(exception, 'Unsupported version'): ++ with six.assertRaisesRegex(self, exception, 'Unsupported version'): + provider.load() + + def test_missing_access_key_raises_exception(self): +@@ -3119,7 +3119,7 @@ + + provider = self.create_process_provider() + exception = botocore.exceptions.CredentialRetrievalError +- with self.assertRaisesRegexp(exception, 'Missing required key'): ++ with six.assertRaisesRegex(self, exception, 'Missing required key'): + provider.load() + + def test_missing_secret_key_raises_exception(self): +@@ -3136,7 +3136,7 @@ + + provider = self.create_process_provider() + exception = botocore.exceptions.CredentialRetrievalError +- with self.assertRaisesRegexp(exception, 'Missing required key'): ++ with six.assertRaisesRegex(self, exception, 'Missing required key'): + provider.load() + + def test_missing_session_token(self): +diff -Nru botocore-1.18.15.orig/tests/unit/test_discovery.py botocore-1.18.15/tests/unit/test_discovery.py +--- botocore-1.18.15.orig/tests/unit/test_discovery.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_discovery.py 2020-10-09 10:13:49.560472538 +0200 +@@ -1,5 +1,8 @@ + import time +-from mock import Mock, call ++try: ++ from mock import Mock, call ++except ImportError: ++ from unittest.mock import Mock, call + from tests import unittest + + from botocore.awsrequest import AWSRequest +diff -Nru botocore-1.18.15.orig/tests/unit/test_endpoint.py botocore-1.18.15/tests/unit/test_endpoint.py +--- botocore-1.18.15.orig/tests/unit/test_endpoint.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_endpoint.py 2020-10-09 10:13:49.560472538 +0200 +@@ -13,7 +13,10 @@ + import socket + from tests import unittest + +-from mock import Mock, patch, sentinel ++try: ++ from mock import Mock, patch, sentinel ++except ImportError: ++ from unittest.mock import Mock, patch, sentinel + + from botocore.compat import six + from botocore.awsrequest import AWSRequest +diff -Nru botocore-1.18.15.orig/tests/unit/test_errorfactory.py botocore-1.18.15/tests/unit/test_errorfactory.py +--- botocore-1.18.15.orig/tests/unit/test_errorfactory.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_errorfactory.py 2020-10-09 10:13:49.536472205 +0200 +@@ -12,6 +12,7 @@ + # language governing permissions and limitations under the License. + from tests import unittest + ++from botocore.compat import six + from botocore.exceptions import ClientError + from botocore.errorfactory import BaseClientExceptions + from botocore.errorfactory import ClientExceptionsFactory +@@ -39,7 +40,7 @@ + def test_gettattr_message(self): + exception_cls = type('MyException', (ClientError,), {}) + self.code_to_exception['MyExceptionCode'] = exception_cls +- with self.assertRaisesRegexp( ++ with six.assertRaisesRegex(self, + AttributeError, 'Valid exceptions are: MyException'): + self.exceptions.SomeUnmodeledError + +diff -Nru botocore-1.18.15.orig/tests/unit/test_eventstream.py botocore-1.18.15/tests/unit/test_eventstream.py +--- botocore-1.18.15.orig/tests/unit/test_eventstream.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_eventstream.py 2020-10-09 10:13:49.520471985 +0200 @@ -12,8 +12,10 @@ # language governing permissions and limitations under the License. """Unit tests for the binary event stream decoder. """ @@ -2239,7 +3036,7 @@ Index: b/tests/unit/test_eventstream.py from botocore.parsers import EventStreamXMLParser from botocore.eventstream import ( -@@ -240,18 +242,12 @@ NEGATIVE_CASES = [ +@@ -240,18 +242,12 @@ def assert_message_equal(message_a, message_b): """Asserts all fields for two messages are equal. """ @@ -2264,7 +3061,7 @@ Index: b/tests/unit/test_eventstream.py def test_partial_message(): -@@ -262,7 +258,7 @@ def test_partial_message(): +@@ -262,7 +258,7 @@ mid_point = 15 event_buffer.add_data(data[:mid_point]) messages = list(event_buffer) @@ -2273,7 +3070,7 @@ Index: b/tests/unit/test_eventstream.py event_buffer.add_data(data[mid_point:len(data)]) for message in event_buffer: assert_message_equal(message, EMPTY_MESSAGE[1]) -@@ -280,7 +276,7 @@ def check_message_decodes(encoded, decod +@@ -280,7 +276,7 @@ def test_positive_cases(): """Test that all positive cases decode how we expect. """ for (encoded, decoded) in POSITIVE_CASES: @@ -2282,7 +3079,7 @@ Index: b/tests/unit/test_eventstream.py def test_all_positive_cases(): -@@ -301,8 +297,13 @@ def test_all_positive_cases(): +@@ -301,8 +297,13 @@ def test_negative_cases(): """Test that all negative cases raise the expected exception. """ for (encoded, exception) in NEGATIVE_CASES: @@ -2298,7 +3095,7 @@ Index: b/tests/unit/test_eventstream.py def test_header_parser(): -@@ -329,87 +330,87 @@ def test_header_parser(): +@@ -329,87 +330,87 @@ parser = EventStreamHeaderParser() headers = parser.parse(headers_data) @@ -2412,7 +3209,7 @@ Index: b/tests/unit/test_eventstream.py def test_unpack_utf8_string(): -@@ -417,18 +418,19 @@ def test_unpack_utf8_string(): +@@ -417,18 +418,19 @@ utf8_string = b'\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e' encoded = length + utf8_string (value, bytes_consumed) = DecodeUtils.unpack_utf8_string(encoded) @@ -2435,7 +3232,7 @@ Index: b/tests/unit/test_eventstream.py def generator(): for chunk in data: yield chunk -@@ -445,7 +447,7 @@ def test_event_stream_wrapper_iteration( +@@ -445,7 +447,7 @@ output_shape = Mock() event_stream = EventStream(raw_stream, output_shape, parser, '') events = list(event_stream) @@ -2444,7 +3241,7 @@ Index: b/tests/unit/test_eventstream.py response_dict = { 'headers': {'event-id': 0x0000a00c}, -@@ -455,14 +457,19 @@ def test_event_stream_wrapper_iteration( +@@ -455,14 +457,19 @@ parser.parse.assert_called_with(response_dict, output_shape) @@ -2471,7 +3268,7 @@ Index: b/tests/unit/test_eventstream.py def test_event_stream_wrapper_close(): -@@ -492,22 +499,32 @@ def test_event_stream_initial_response() +@@ -492,22 +499,32 @@ assert event.payload == payload @@ -2519,10 +3316,9 @@ Index: b/tests/unit/test_eventstream.py + else: + raise AssertionError( + 'Expected exception NoInitialResponseError has not been raised.') -Index: b/tests/unit/test_exceptions.py -=================================================================== ---- a/tests/unit/test_exceptions.py -+++ b/tests/unit/test_exceptions.py +diff -Nru botocore-1.18.15.orig/tests/unit/test_exceptions.py botocore-1.18.15/tests/unit/test_exceptions.py +--- botocore-1.18.15.orig/tests/unit/test_exceptions.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_exceptions.py 2020-10-09 10:13:49.520471985 +0200 @@ -14,8 +14,6 @@ import pickle from tests import unittest @@ -2532,7 +3328,7 @@ Index: b/tests/unit/test_exceptions.py import botocore.awsrequest import botocore.session from botocore import exceptions -@@ -24,7 +22,7 @@ from botocore import exceptions +@@ -24,7 +22,7 @@ def test_client_error_can_handle_missing_code_or_message(): response = {'Error': {}} expect = 'An error occurred (Unknown) when calling the blackhole operation: Unknown' @@ -2541,7 +3337,7 @@ Index: b/tests/unit/test_exceptions.py def test_client_error_has_operation_name_set(): -@@ -36,7 +34,7 @@ def test_client_error_has_operation_name +@@ -36,7 +34,7 @@ def test_client_error_set_correct_operation_name(): response = {'Error': {}} exception = exceptions.ClientError(response, 'blackhole') @@ -2550,17 +3346,48 @@ Index: b/tests/unit/test_exceptions.py def test_retry_info_added_when_present(): -Index: b/tests/unit/test_http_client_exception_mapping.py -=================================================================== ---- a/tests/unit/test_http_client_exception_mapping.py -+++ b/tests/unit/test_http_client_exception_mapping.py +diff -Nru botocore-1.18.15.orig/tests/unit/test_handlers.py botocore-1.18.15/tests/unit/test_handlers.py +--- botocore-1.18.15.orig/tests/unit/test_handlers.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_handlers.py 2020-10-09 10:13:49.536472205 +0200 +@@ -14,7 +14,7 @@ + from tests import unittest, BaseSessionTest + + import base64 +-import mock ++from tests import mock + import copy + import os + import json +@@ -126,7 +126,7 @@ + 'foo/keyname%2B?versionId=asdf+') + + def test_copy_source_has_validation_failure(self): +- with self.assertRaisesRegexp(ParamValidationError, 'Key'): ++ with six.assertRaisesRegex(self, ParamValidationError, 'Key'): + handlers.handle_copy_source_param( + {'CopySource': {'Bucket': 'foo'}}) + +diff -Nru botocore-1.18.15.orig/tests/unit/test_history.py botocore-1.18.15/tests/unit/test_history.py +--- botocore-1.18.15.orig/tests/unit/test_history.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_history.py 2020-10-09 10:13:49.556472483 +0200 +@@ -1,6 +1,6 @@ + from tests import unittest + +-import mock ++from tests import mock + + from botocore.history import HistoryRecorder + from botocore.history import BaseHistoryHandler +diff -Nru botocore-1.18.15.orig/tests/unit/test_http_client_exception_mapping.py botocore-1.18.15/tests/unit/test_http_client_exception_mapping.py +--- botocore-1.18.15.orig/tests/unit/test_http_client_exception_mapping.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_http_client_exception_mapping.py 2020-10-09 10:13:49.524472039 +0200 @@ -1,4 +1,4 @@ -from nose.tools import assert_raises +import unittest from botocore import exceptions as botocore_exceptions from botocore.vendored.requests import exceptions as requests_exceptions -@@ -13,15 +13,9 @@ EXCEPTION_MAPPING = [ +@@ -13,15 +13,9 @@ ] @@ -2582,10 +3409,9 @@ Index: b/tests/unit/test_http_client_exception_mapping.py + with self.assertRaises(old_exception): + raise new_exception(endpoint_url=None, proxy_url=None, + error=None) -Index: b/tests/unit/test_http_session.py -=================================================================== ---- a/tests/unit/test_http_session.py -+++ b/tests/unit/test_http_session.py +diff -Nru botocore-1.18.15.orig/tests/unit/test_http_session.py botocore-1.18.15/tests/unit/test_http_session.py +--- botocore-1.18.15.orig/tests/unit/test_http_session.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_http_session.py 2020-10-09 10:13:49.524472039 +0200 @@ -1,11 +1,12 @@ import socket @@ -2602,7 +3428,7 @@ Index: b/tests/unit/test_http_session.py from botocore.awsrequest import AWSRequest from botocore.awsrequest import AWSHTTPConnectionPool, AWSHTTPSConnectionPool from botocore.httpsession import get_cert_path -@@ -250,15 +251,15 @@ class TestURLLib3Session(unittest.TestCa +@@ -250,15 +251,15 @@ session = URLLib3Session() session.send(self.request.prepare()) @@ -2624,11 +3450,62 @@ Index: b/tests/unit/test_http_session.py def test_aws_connection_classes_are_used(self): session = URLLib3Session() -Index: b/tests/unit/test_model.py -=================================================================== ---- a/tests/unit/test_model.py -+++ b/tests/unit/test_model.py -@@ -2,11 +2,11 @@ from tests import unittest +diff -Nru botocore-1.18.15.orig/tests/unit/test_idempotency.py botocore-1.18.15/tests/unit/test_idempotency.py +--- botocore-1.18.15.orig/tests/unit/test_idempotency.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_idempotency.py 2020-10-09 10:13:49.556472483 +0200 +@@ -13,7 +13,7 @@ + + from tests import unittest + import re +-import mock ++from tests import mock + from botocore.handlers import generate_idempotent_uuid + + +diff -Nru botocore-1.18.15.orig/tests/unit/test_loaders.py botocore-1.18.15/tests/unit/test_loaders.py +--- botocore-1.18.15.orig/tests/unit/test_loaders.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_loaders.py 2020-10-09 10:13:49.540472262 +0200 +@@ -22,12 +22,13 @@ + import os + import contextlib + import copy +-import mock ++from tests import mock + + from botocore.exceptions import DataNotFoundError, UnknownServiceError + from botocore.loaders import JSONFileLoader + from botocore.loaders import Loader, create_loader + from botocore.loaders import ExtrasProcessor ++from botocore.compat import six + + from tests import BaseEnvVar + +@@ -156,8 +157,8 @@ + + # Should have a) the unknown service name and b) list of valid + # service names. +- with self.assertRaisesRegexp(UnknownServiceError, +- 'Unknown service.*BAZ.*baz'): ++ with six.assertRaisesRegex(self, UnknownServiceError, ++ 'Unknown service.*BAZ.*baz'): + loader.load_service_model('BAZ', type_name='service-2') + + def test_load_service_model_uses_provided_type_name(self): +@@ -169,8 +170,8 @@ + # Should have a) the unknown service name and b) list of valid + # service names. + provided_type_name = 'not-service-2' +- with self.assertRaisesRegexp(UnknownServiceError, +- 'Unknown service.*BAZ.*baz'): ++ with six.assertRaisesRegex(self, UnknownServiceError, ++ 'Unknown service.*BAZ.*baz'): + loader.load_service_model( + 'BAZ', type_name=provided_type_name) + +diff -Nru botocore-1.18.15.orig/tests/unit/test_model.py botocore-1.18.15/tests/unit/test_model.py +--- botocore-1.18.15.orig/tests/unit/test_model.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_model.py 2020-10-09 10:13:49.524472039 +0200 +@@ -2,11 +2,11 @@ from botocore import model from botocore.compat import OrderedDict @@ -2642,7 +3519,7 @@ Index: b/tests/unit/test_model.py # on the duplication. The property names below # all have the same test logic. service_model = model.ServiceModel({'metadata': {'endpointPrefix': 'foo'}}) -@@ -28,7 +28,7 @@ def test_missing_model_attribute_raises_ +@@ -28,7 +28,7 @@ "be raised, but no exception was raised for: %s" % attr_name) for name in property_names: @@ -2651,7 +3528,7 @@ Index: b/tests/unit/test_model.py class TestServiceId(unittest.TestCase): -@@ -105,9 +105,9 @@ class TestServiceModel(unittest.TestCase +@@ -105,9 +105,9 @@ } service_name = 'myservice' service_model = model.ServiceModel(service_model, service_name) @@ -2664,11 +3541,31 @@ Index: b/tests/unit/test_model.py def test_operation_does_not_exist(self): with self.assertRaises(model.OperationNotFoundError): -Index: b/tests/unit/test_parsers.py -=================================================================== ---- a/tests/unit/test_parsers.py -+++ b/tests/unit/test_parsers.py -@@ -14,11 +14,11 @@ from tests import unittest, RawResponse +diff -Nru botocore-1.18.15.orig/tests/unit/test_paginate.py botocore-1.18.15/tests/unit/test_paginate.py +--- botocore-1.18.15.orig/tests/unit/test_paginate.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_paginate.py 2020-10-09 10:13:49.540472262 +0200 +@@ -20,7 +20,7 @@ + from botocore.exceptions import PaginationError + from botocore.compat import six + +-import mock ++from tests import mock + + + def encode_token(token): +@@ -823,7 +823,7 @@ + {"Users": ["User3"]}, + ] + self.method.side_effect = responses +- with self.assertRaisesRegexp(ValueError, 'Bad starting token'): ++ with six.assertRaisesRegex(self, ValueError, 'Bad starting token'): + pagination_config = {'StartingToken': 'does___not___work'} + self.paginator.paginate( + PaginationConfig=pagination_config).build_full_result() +diff -Nru botocore-1.18.15.orig/tests/unit/test_parsers.py botocore-1.18.15/tests/unit/test_parsers.py +--- botocore-1.18.15.orig/tests/unit/test_parsers.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_parsers.py 2020-10-09 10:13:49.524472039 +0200 +@@ -14,11 +14,11 @@ import datetime from dateutil.tz import tzutc @@ -2681,7 +3578,7 @@ Index: b/tests/unit/test_parsers.py # HTTP responses will typically return a custom HTTP -@@ -597,8 +597,8 @@ class TestHandlesInvalidXMLResponses(uni +@@ -597,8 +597,8 @@ parser = parsers.QueryParser() output_shape = None # The XML body should be in the error message. @@ -2692,7 +3589,7 @@ Index: b/tests/unit/test_parsers.py parser.parse( {'body': invalid_xml, 'headers': {}, 'status_code': 200}, output_shape) -@@ -1310,9 +1310,9 @@ def test_can_handle_generic_error_messag +@@ -1310,9 +1310,9 @@ ).encode('utf-8') empty_body = b'' none_body = None @@ -2705,7 +3602,7 @@ Index: b/tests/unit/test_parsers.py def _assert_parses_generic_error(parser, body): -@@ -1320,7 +1320,6 @@ def _assert_parses_generic_error(parser, +@@ -1320,7 +1320,6 @@ # html error page. We should be able to handle this case. parsed = parser.parse({ 'body': body, 'headers': {}, 'status_code': 503}, None) @@ -2716,10 +3613,9 @@ Index: b/tests/unit/test_parsers.py + assert parsed['Error'] == \ + {'Code': '503', 'Message': 'Service Unavailable'} + assert parsed['ResponseMetadata']['HTTPStatusCode'] == 503 -Index: b/tests/unit/test_protocols.py -=================================================================== ---- a/tests/unit/test_protocols.py -+++ b/tests/unit/test_protocols.py +diff -Nru botocore-1.18.15.orig/tests/unit/test_protocols.py botocore-1.18.15/tests/unit/test_protocols.py +--- botocore-1.18.15.orig/tests/unit/test_protocols.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_protocols.py 2020-10-09 10:13:49.528472096 +0200 @@ -16,7 +16,7 @@ This is a test runner for all the JSON tests defined in ``tests/unit/protocols/``, including both the input/output tests. @@ -2729,7 +3625,7 @@ Index: b/tests/unit/test_protocols.py this test. In addition, there are several env vars you can use during development. -@@ -37,17 +37,17 @@ failed test. +@@ -37,17 +37,17 @@ To run tests from only a single file, you can set the BOTOCORE_TEST env var:: @@ -2750,7 +3646,7 @@ Index: b/tests/unit/test_protocols.py """ import os -@@ -69,8 +69,6 @@ from botocore.awsrequest import prepare_ +@@ -69,8 +69,6 @@ from calendar import timegm from botocore.model import NoShapeFoundError @@ -2759,7 +3655,7 @@ Index: b/tests/unit/test_protocols.py TEST_DIR = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'protocols') -@@ -101,9 +99,9 @@ def test_compliance(): +@@ -101,9 +99,9 @@ if model.get('description') in PROTOCOL_TEST_BLACKLIST: continue if 'params' in case: @@ -2771,7 +3667,7 @@ Index: b/tests/unit/test_protocols.py def _test_input(json_description, case, basename): -@@ -142,7 +140,7 @@ def _assert_endpoints_equal(actual, expe +@@ -142,7 +140,7 @@ return prepare_request_dict(actual, endpoint) actual_host = urlsplit(actual['url']).netloc @@ -2780,7 +3676,7 @@ Index: b/tests/unit/test_protocols.py class MockRawResponse(object): -@@ -208,7 +206,7 @@ def _test_output(json_description, case, +@@ -208,7 +206,7 @@ expected_result.update(case['error']) else: expected_result = case['result'] @@ -2789,7 +3685,7 @@ Index: b/tests/unit/test_protocols.py except Exception as e: _output_failure_message(model.metadata['protocol'], case, parsed, expected_result, e) -@@ -318,11 +316,11 @@ def _try_json_dump(obj): +@@ -318,11 +316,11 @@ return str(obj) @@ -2803,7 +3699,7 @@ Index: b/tests/unit/test_protocols.py except Exception: try: better = "%s (actual != expected)\n%s !=\n%s" % ( -@@ -353,14 +351,14 @@ def _serialize_request_description(reque +@@ -353,14 +351,14 @@ def _assert_requests_equal(actual, expected): @@ -2822,546 +3718,21 @@ Index: b/tests/unit/test_protocols.py def _walk_files(): -Index: b/requirements.txt -=================================================================== ---- a/requirements.txt -+++ b/requirements.txt -@@ -1,7 +1,7 @@ - tox>=2.5.0,<3.0.0 --nose==1.3.7 -+pytest>=4.6 -+pluggy>=0.7 -+py>=1.5.0 -+pytest-cov - mock==1.3.0 - wheel==0.24.0 --docutils>=0.10,<0.16 --behave==1.2.5 --jsonschema==2.5.1 -Index: b/tests/unit/response_parsing/README.rst -=================================================================== ---- a/tests/unit/response_parsing/README.rst -+++ b/tests/unit/response_parsing/README.rst -@@ -16,12 +16,12 @@ response sent from the server for that p - file contains the expected Python data structure created from the XML - response. +diff -Nru botocore-1.18.15.orig/tests/unit/test_retryhandler.py botocore-1.18.15/tests/unit/test_retryhandler.py +--- botocore-1.18.15.orig/tests/unit/test_retryhandler.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_retryhandler.py 2020-10-09 10:13:49.560472538 +0200 +@@ -15,7 +15,7 @@ --The main test is contained in ``test_response_parser.py`` and is --implemented as a nose generator. Each time through the loop an XML --file is read and passed to a ``botocore.response.XmlResponse`` --object. The corresponding JSON file is then parsed and compared to --the value created by the parser. If the are equal, the test passes. If --they are not equal, both the expected result and the actual result are -+The main test is contained in ``test_response_parser.py``. Each -+time through the loop an XML file is read and passed to -+a ``botocore.response.XmlResponse`` object. The corresponding -+JSON file is then parsed and compared to the value created by the -+parser. If the are equal, the test passes. If they are not -+equal, both the expected result and the actual result are - pretty-printed to stdout and the tests continue. - - ----------------- -Index: b/tests/functional/test_credentials.py -=================================================================== ---- a/tests/functional/test_credentials.py -+++ b/tests/functional/test_credentials.py -@@ -15,7 +15,7 @@ import threading - import os - import math - import time --import mock -+from tests import mock - import tempfile - import shutil - from datetime import datetime, timedelta -@@ -41,7 +41,7 @@ from botocore.session import Session - from botocore.exceptions import InvalidConfigError, InfiniteLoopConfigError - from botocore.stub import Stubber - from botocore.utils import datetime2timestamp -- -+from botocore.compat import six - - class TestCredentialRefreshRaces(unittest.TestCase): - def assert_consistent_credentials_seen(self, creds, func): -@@ -826,7 +826,7 @@ class TestProcessProvider(unittest.TestC - # Finally `(?s)` at the beginning makes dots match newlines so - # we can handle a multi-line string. - reg = r"(?s)^((?!b').)*$" -- with self.assertRaisesRegexp(CredentialRetrievalError, reg): -+ with six.assertRaisesRegex(self, CredentialRetrievalError, reg): - session.get_credentials() - - -Index: b/tests/functional/test_history.py -=================================================================== ---- a/tests/functional/test_history.py -+++ b/tests/functional/test_history.py -@@ -1,6 +1,6 @@ - from contextlib import contextmanager - --import mock -+from tests import mock - - from tests import BaseSessionTest, ClientHTTPStubber - from botocore.history import BaseHistoryHandler -@@ -87,10 +87,10 @@ class TestRecordStatementsInjections(Bas - self.assertIsNone(body) - - streaming = payload['streaming'] -- self.assertEquals(streaming, False) -+ self.assertEqual(streaming, False) - - url = payload['url'] -- self.assertEquals(url, 'https://s3.us-west-2.amazonaws.com/') -+ self.assertEqual(url, 'https://s3.us-west-2.amazonaws.com/') - - self.assertEqual(source, 'BOTOCORE') - -Index: b/tests/functional/test_retry.py -=================================================================== ---- a/tests/functional/test_retry.py -+++ b/tests/functional/test_retry.py -@@ -16,6 +16,7 @@ from tests import BaseSessionTest, mock, - - from botocore.exceptions import ClientError - from botocore.config import Config -+from botocore.compat import six - - - class BaseRetryTest(BaseSessionTest): -@@ -38,7 +39,7 @@ class BaseRetryTest(BaseSessionTest): - with ClientHTTPStubber(client) as http_stubber: - for _ in range(num_responses): - http_stubber.add_response(status=status, body=body) -- with self.assertRaisesRegexp( -+ with six.assertRaisesRegex(self, - ClientError, 'reached max retries: %s' % num_retries): - yield - self.assertEqual(len(http_stubber.requests), num_responses) -Index: b/tests/functional/test_stub.py -=================================================================== ---- a/tests/functional/test_stub.py -+++ b/tests/functional/test_stub.py -@@ -16,6 +16,7 @@ from tests import unittest - import botocore - import botocore.session - import botocore.stub as stub -+from botocore.compat import six - from botocore.stub import Stubber - from botocore.exceptions import StubResponseError, ClientError, \ - StubAssertionError, UnStubbedResponseError -@@ -54,8 +55,8 @@ class TestStubber(unittest.TestCase): - def test_activated_stubber_errors_with_no_registered_stubs(self): - self.stubber.activate() - # Params one per line for readability. -- with self.assertRaisesRegexp(UnStubbedResponseError, -- "Unexpected API Call"): -+ with six.assertRaisesRegex(self, UnStubbedResponseError, -+ "Unexpected API Call"): - self.client.list_objects( - Bucket='asdfasdfasdfasdf', - Delimiter='asdfasdfasdfasdf', -@@ -119,8 +120,8 @@ class TestStubber(unittest.TestCase): - 'list_objects', service_response, expected_params) - self.stubber.activate() - # This should call should raise an for mismatching expected params. -- with self.assertRaisesRegexp(StubResponseError, -- "{'Bucket': 'bar'},\n"): -+ with six.assertRaisesRegex(self, StubResponseError, -+ "{'Bucket': 'bar'},\n"): - self.client.list_objects(Bucket='foo') - - def test_expected_params_mixed_with_errors_responses(self): -@@ -143,7 +144,8 @@ class TestStubber(unittest.TestCase): - self.client.list_objects(Bucket='foo') - - # The second call should throw an error for unexpected parameters -- with self.assertRaisesRegexp(StubResponseError, 'Expected parameters'): -+ with six.assertRaisesRegex(self, StubResponseError, -+ 'Expected parameters'): - self.client.list_objects(Bucket='foo') - - def test_can_continue_to_call_after_expected_params_fail(self): -Index: b/tests/integration/test_client.py -=================================================================== ---- a/tests/integration/test_client.py -+++ b/tests/integration/test_client.py -@@ -79,8 +79,8 @@ class TestClientErrors(unittest.TestCase - def test_region_mentioned_in_invalid_region(self): - client = self.session.create_client( - 'cloudformation', region_name='us-east-999') -- with self.assertRaisesRegexp(EndpointConnectionError, -- 'Could not connect to the endpoint URL'): -+ with six.assertRaisesRegex(self, EndpointConnectionError, -+ 'Could not connect to the endpoint URL'): - client.list_stacks() - - def test_client_modeled_exception(self): -Index: b/tests/integration/test_sts.py -=================================================================== ---- a/tests/integration/test_sts.py -+++ b/tests/integration/test_sts.py -@@ -13,6 +13,8 @@ from tests import unittest - import botocore.session -+ -+from botocore.compat import six - from botocore.exceptions import ClientError - - class TestSTS(unittest.TestCase): -@@ -38,5 +40,5 @@ class TestSTS(unittest.TestCase): - self.assertEqual(sts.meta.endpoint_url, - 'https://sts.us-west-2.amazonaws.com') - # Signing error will be thrown with the incorrect region name included. -- with self.assertRaisesRegexp(ClientError, 'ap-southeast-1') as e: -+ with six.assertRaisesRegex(self, ClientError, 'ap-southeast-1'): - sts.get_session_token() -Index: b/tests/unit/docs/test_utils.py -=================================================================== ---- a/tests/unit/docs/test_utils.py -+++ b/tests/unit/docs/test_utils.py -@@ -223,5 +223,5 @@ class TestAppendParamDocumentation(BaseD - class TestEscapeControls(unittest.TestCase): - def test_escapes_controls(self): - escaped = escape_controls('\na\rb\tc\fd\be') -- self.assertEquals(escaped, '\\na\\rb\\tc\\fd\\be') -+ self.assertEqual(escaped, '\\na\\rb\\tc\\fd\\be') - -Index: b/tests/unit/response_parsing/test_response_parsing.py -=================================================================== ---- a/tests/unit/response_parsing/test_response_parsing.py -+++ b/tests/unit/response_parsing/test_response_parsing.py -@@ -119,8 +119,8 @@ def test_xml_parsing(): - expected = _get_expected_parsed_result(xmlfile) - operation_model = _get_operation_model(service_model, xmlfile) - raw_response_body = _get_raw_response_body(xmlfile) -- yield _test_parsed_response, xmlfile, raw_response_body, \ -- operation_model, expected -+ _test_parsed_response(xmlfile, raw_response_body, -+ operation_model, expected) - - - def _get_raw_response_body(xmlfile): -@@ -179,8 +179,8 @@ def test_json_errors_parsing(): - operation_model = service_model.operation_model(op_name) - with open(raw_response_file, 'rb') as f: - raw_response_body = f.read() -- yield _test_parsed_response, raw_response_file, \ -- raw_response_body, operation_model, expected -+ _test_parsed_response(raw_response_file, -+ raw_response_body, operation_model, expected) - - - def _uhg_test_json_parsing(): -Index: b/tests/unit/test_awsrequest.py -=================================================================== ---- a/tests/unit/test_awsrequest.py -+++ b/tests/unit/test_awsrequest.py -@@ -18,13 +18,15 @@ import tempfile - import shutil - import io - import socket --import sys - --from mock import Mock, patch -+try: -+ from mock import Mock, patch -+except ImportError: -+ from unittest.mock import Mock, patch - from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool - - from botocore.exceptions import UnseekableStreamError --from botocore.awsrequest import AWSRequest, AWSPreparedRequest, AWSResponse -+from botocore.awsrequest import AWSRequest, AWSResponse - from botocore.awsrequest import AWSHTTPConnection, AWSHTTPSConnection, HeadersDict - from botocore.awsrequest import prepare_request_dict, create_request_object - from botocore.compat import file_type, six -@@ -271,11 +273,11 @@ class TestAWSResponse(unittest.TestCase) - def test_text_property(self): - self.set_raw_stream([b'\xe3\x82\xb8\xe3\x83\xa7\xe3\x82\xb0']) - self.response.headers['content-type'] = 'text/plain; charset=utf-8' -- self.assertEquals(self.response.text, u'\u30b8\u30e7\u30b0') -+ self.assertEqual(self.response.text, u'\u30b8\u30e7\u30b0') - - def test_text_property_defaults_utf8(self): - self.set_raw_stream([b'\xe3\x82\xb8\xe3\x83\xa7\xe3\x82\xb0']) -- self.assertEquals(self.response.text, u'\u30b8\u30e7\u30b0') -+ self.assertEqual(self.response.text, u'\u30b8\u30e7\u30b0') - - - class TestAWSHTTPConnection(unittest.TestCase): -Index: b/tests/unit/test_client.py -=================================================================== ---- a/tests/unit/test_client.py -+++ b/tests/unit/test_client.py -@@ -12,7 +12,7 @@ - # language governing permissions and limitations under the License. - import botocore.config - from tests import unittest -import mock +from tests import mock - import botocore - from botocore import utils -@@ -554,8 +554,8 @@ class TestAutoGeneratedClient(unittest.T - creator = self.create_client_creator() - service_client = creator.create_client( - 'myservice', 'us-west-2', credentials=self.credentials) -- with self.assertRaisesRegexp( -- TypeError, 'only accepts keyword arguments'): -+ with six.assertRaisesRegex(self, TypeError, -+ 'only accepts keyword arguments'): - service_client.test_operation('foo') - - @mock.patch('botocore.args.RequestSigner.sign') -@@ -1550,15 +1550,15 @@ class TestConfig(unittest.TestCase): - self.assertEqual(config.read_timeout, 50) - - def test_invalid_kwargs(self): -- with self.assertRaisesRegexp(TypeError, 'Got unexpected keyword'): -+ with six.assertRaisesRegex(self, TypeError, 'Got unexpected keyword'): - botocore.config.Config(foo='foo') - - def test_pass_invalid_length_of_args(self): -- with self.assertRaisesRegexp(TypeError, 'Takes at most'): -+ with six.assertRaisesRegex(self, TypeError, 'Takes at most'): - botocore.config.Config('foo', *botocore.config.Config.OPTION_DEFAULTS.values()) - - def test_create_with_multiple_kwargs(self): -- with self.assertRaisesRegexp(TypeError, 'Got multiple values'): -+ with six.assertRaisesRegex(self, TypeError, 'Got multiple values'): - botocore.config.Config('us-east-1', region_name='us-east-1') - - def test_merge_returns_new_config_object(self): -@@ -1610,10 +1610,10 @@ class TestConfig(unittest.TestCase): - self.assertEqual(config.retries['max_attempts'], 15) - - def test_validates_retry_config(self): -- with self.assertRaisesRegexp( -- InvalidRetryConfigurationError, -- 'Cannot provide retry configuration for "not-allowed"'): -- botocore.config.Config(retries={'not-allowed': True}) -+ with six.assertRaisesRegex( -+ self, InvalidRetryConfigurationError, -+ 'Cannot provide retry configuration for "not-allowed"'): -+ botocore.config.Config(retries={'not-allowed': True}) - - def test_validates_max_retry_attempts(self): - with self.assertRaises(InvalidMaxRetryAttemptsError): -Index: b/tests/unit/test_credentials.py -=================================================================== ---- a/tests/unit/test_credentials.py -+++ b/tests/unit/test_credentials.py -@@ -13,7 +13,7 @@ - # language governing permissions and limitations under the License. - from datetime import datetime, timedelta - import subprocess --import mock -+from tests import mock - import os - import tempfile - import shutil -@@ -1083,7 +1083,7 @@ class TestEnvVar(BaseEnvVar): - "Credentials were refreshed, but the refreshed credentials are " - "still expired." - ) -- with self.assertRaisesRegexp(RuntimeError, error_message): -+ with six.assertRaisesRegex(self, RuntimeError, error_message): - creds.get_frozen_credentials() - - def test_partial_creds_is_an_error(self): -@@ -1149,7 +1149,7 @@ class TestEnvVar(BaseEnvVar): - "Credentials were refreshed, but the refreshed credentials are " - "still expired." - ) -- with self.assertRaisesRegexp(RuntimeError, error_message): -+ with six.assertRaisesRegex(self, RuntimeError, error_message): - creds.get_frozen_credentials() - - # Now we update the environment with non-expired credentials, -@@ -2745,7 +2745,7 @@ class TestRefreshLogic(unittest.TestCase - mandatory_refresh=7, - refresh_function=fail_refresh - ) -- with self.assertRaisesRegexp(Exception, 'refresh failed'): -+ with six.assertRaisesRegex(self, Exception, 'refresh failed'): - creds.get_frozen_credentials() - - def test_exception_propogated_on_expired_credentials(self): -@@ -2758,7 +2758,7 @@ class TestRefreshLogic(unittest.TestCase - mandatory_refresh=7, - refresh_function=fail_refresh - ) -- with self.assertRaisesRegexp(Exception, 'refresh failed'): -+ with six.assertRaisesRegex(self, Exception, 'refresh failed'): - # Because credentials are actually expired, any - # failure to refresh should be propagated. - creds.get_frozen_credentials() -@@ -2779,7 +2779,7 @@ class TestRefreshLogic(unittest.TestCase - creds_last_for=-2, - ) - err_msg = 'refreshed credentials are still expired' -- with self.assertRaisesRegexp(RuntimeError, err_msg): -+ with six.assertRaisesRegex(self, RuntimeError, err_msg): - # Because credentials are actually expired, any - # failure to refresh should be propagated. - creds.get_frozen_credentials() -@@ -3067,7 +3067,7 @@ class TestProcessProvider(BaseEnvVar): - - provider = self.create_process_provider() - exception = botocore.exceptions.CredentialRetrievalError -- with self.assertRaisesRegexp(exception, 'Error Message'): -+ with six.assertRaisesRegex(self, exception, 'Error Message'): - provider.load() - - def test_unsupported_version_raises_mismatch(self): -@@ -3085,7 +3085,7 @@ class TestProcessProvider(BaseEnvVar): - - provider = self.create_process_provider() - exception = botocore.exceptions.CredentialRetrievalError -- with self.assertRaisesRegexp(exception, 'Unsupported version'): -+ with six.assertRaisesRegex(self, exception, 'Unsupported version'): - provider.load() - - def test_missing_version_in_payload_returned_raises_exception(self): -@@ -3102,7 +3102,7 @@ class TestProcessProvider(BaseEnvVar): - - provider = self.create_process_provider() - exception = botocore.exceptions.CredentialRetrievalError -- with self.assertRaisesRegexp(exception, 'Unsupported version'): -+ with six.assertRaisesRegex(self, exception, 'Unsupported version'): - provider.load() - - def test_missing_access_key_raises_exception(self): -@@ -3119,7 +3119,7 @@ class TestProcessProvider(BaseEnvVar): - - provider = self.create_process_provider() - exception = botocore.exceptions.CredentialRetrievalError -- with self.assertRaisesRegexp(exception, 'Missing required key'): -+ with six.assertRaisesRegex(self, exception, 'Missing required key'): - provider.load() - - def test_missing_secret_key_raises_exception(self): -@@ -3136,7 +3136,7 @@ class TestProcessProvider(BaseEnvVar): - - provider = self.create_process_provider() - exception = botocore.exceptions.CredentialRetrievalError -- with self.assertRaisesRegexp(exception, 'Missing required key'): -+ with six.assertRaisesRegex(self, exception, 'Missing required key'): - provider.load() - - def test_missing_session_token(self): -Index: b/tests/unit/test_errorfactory.py -=================================================================== ---- a/tests/unit/test_errorfactory.py -+++ b/tests/unit/test_errorfactory.py -@@ -12,6 +12,7 @@ - # language governing permissions and limitations under the License. - from tests import unittest - -+from botocore.compat import six - from botocore.exceptions import ClientError - from botocore.errorfactory import BaseClientExceptions - from botocore.errorfactory import ClientExceptionsFactory -@@ -39,7 +40,7 @@ class TestBaseClientExceptions(unittest. - def test_gettattr_message(self): - exception_cls = type('MyException', (ClientError,), {}) - self.code_to_exception['MyExceptionCode'] = exception_cls -- with self.assertRaisesRegexp( -+ with six.assertRaisesRegex(self, - AttributeError, 'Valid exceptions are: MyException'): - self.exceptions.SomeUnmodeledError - -Index: b/tests/unit/test_handlers.py -=================================================================== ---- a/tests/unit/test_handlers.py -+++ b/tests/unit/test_handlers.py -@@ -14,7 +14,7 @@ - from tests import unittest, BaseSessionTest - - import base64 --import mock -+from tests import mock - import copy - import os - import json -@@ -126,7 +126,7 @@ class TestHandlers(BaseSessionTest): - 'foo/keyname%2B?versionId=asdf+') - - def test_copy_source_has_validation_failure(self): -- with self.assertRaisesRegexp(ParamValidationError, 'Key'): -+ with six.assertRaisesRegex(self, ParamValidationError, 'Key'): - handlers.handle_copy_source_param( - {'CopySource': {'Bucket': 'foo'}}) - -Index: b/tests/unit/test_loaders.py -=================================================================== ---- a/tests/unit/test_loaders.py -+++ b/tests/unit/test_loaders.py -@@ -22,12 +22,13 @@ - import os - import contextlib - import copy --import mock -+from tests import mock - - from botocore.exceptions import DataNotFoundError, UnknownServiceError - from botocore.loaders import JSONFileLoader - from botocore.loaders import Loader, create_loader - from botocore.loaders import ExtrasProcessor -+from botocore.compat import six - - from tests import BaseEnvVar - -@@ -156,8 +157,8 @@ class TestLoader(BaseEnvVar): - - # Should have a) the unknown service name and b) list of valid - # service names. -- with self.assertRaisesRegexp(UnknownServiceError, -- 'Unknown service.*BAZ.*baz'): -+ with six.assertRaisesRegex(self, UnknownServiceError, -+ 'Unknown service.*BAZ.*baz'): - loader.load_service_model('BAZ', type_name='service-2') - - def test_load_service_model_uses_provided_type_name(self): -@@ -169,8 +170,8 @@ class TestLoader(BaseEnvVar): - # Should have a) the unknown service name and b) list of valid - # service names. - provided_type_name = 'not-service-2' -- with self.assertRaisesRegexp(UnknownServiceError, -- 'Unknown service.*BAZ.*baz'): -+ with six.assertRaisesRegex(self, UnknownServiceError, -+ 'Unknown service.*BAZ.*baz'): - loader.load_service_model( - 'BAZ', type_name=provided_type_name) - -Index: b/tests/unit/test_paginate.py -=================================================================== ---- a/tests/unit/test_paginate.py -+++ b/tests/unit/test_paginate.py -@@ -20,7 +20,7 @@ from botocore.paginate import TokenEncod - from botocore.exceptions import PaginationError - from botocore.compat import six - --import mock -+from tests import mock - - - def encode_token(token): -@@ -823,7 +823,7 @@ class TestKeyIterators(unittest.TestCase - {"Users": ["User3"]}, - ] - self.method.side_effect = responses -- with self.assertRaisesRegexp(ValueError, 'Bad starting token'): -+ with six.assertRaisesRegex(self, ValueError, 'Bad starting token'): - pagination_config = {'StartingToken': 'does___not___work'} - self.paginator.paginate( - PaginationConfig=pagination_config).build_full_result() -Index: b/tests/unit/test_s3_addressing.py -=================================================================== ---- a/tests/unit/test_s3_addressing.py -+++ b/tests/unit/test_s3_addressing.py + from botocore import retryhandler + from botocore.exceptions import ( +diff -Nru botocore-1.18.15.orig/tests/unit/test_s3_addressing.py botocore-1.18.15/tests/unit/test_s3_addressing.py +--- botocore-1.18.15.orig/tests/unit/test_s3_addressing.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_s3_addressing.py 2020-10-09 10:13:49.540472262 +0200 @@ -16,9 +16,13 @@ import os @@ -3377,7 +3748,7 @@ Index: b/tests/unit/test_s3_addressing.py from botocore.handlers import set_list_objects_encoding_type_url -@@ -198,7 +202,7 @@ class TestS3Addressing(BaseSessionTest): +@@ -198,7 +202,7 @@ 'https://s3.us-west-2.amazonaws.com/192.168.5.256/mykeyname') def test_invalid_endpoint_raises_exception(self): @@ -3386,537 +3757,10 @@ Index: b/tests/unit/test_s3_addressing.py self.session.create_client('s3', 'Invalid region') def test_non_existent_region(self): -Index: b/tests/unit/test_utils.py -=================================================================== ---- a/tests/unit/test_utils.py -+++ b/tests/unit/test_utils.py -@@ -15,7 +15,7 @@ from tests import RawResponse - from dateutil.tz import tzutc, tzoffset - import datetime - import copy --import mock -+from tests import mock - - import botocore - from botocore import xform_name -@@ -2003,7 +2003,7 @@ class TestContainerMetadataFetcher(unitt - response_body = {'foo': 'bar'} - self.set_http_responses_to(response_body) - fetcher = self.create_fetcher() -- with self.assertRaisesRegexp(ValueError, 'Unsupported host'): -+ with six.assertRaisesRegex(self, ValueError, 'Unsupported host'): - fetcher.retrieve_full_uri(full_uri) - self.assertFalse(self.http.send.called) - -Index: b/tests/unit/test_waiters.py -=================================================================== ---- a/tests/unit/test_waiters.py -+++ b/tests/unit/test_waiters.py -@@ -13,7 +13,7 @@ - import os - from tests import unittest, BaseEnvVar - --import mock -+from tests import mock - - import botocore - from botocore.compat import six -@@ -389,7 +389,7 @@ class TestWaitersObjects(unittest.TestCa - ) - waiter = Waiter('MyWaiter', config, operation_method) - -- with self.assertRaisesRegexp(WaiterError, error_message): -+ with six.assertRaisesRegex(self, WaiterError, error_message): - waiter.wait() - - def test_waiter_transitions_to_failure_state(self): -Index: b/tests/functional/docs/test_shared_example_config.py -=================================================================== ---- a/tests/functional/docs/test_shared_example_config.py -+++ b/tests/functional/docs/test_shared_example_config.py -@@ -27,7 +27,7 @@ def test_lint_shared_example_configs(): - examples = example_config.get("examples", {}) - for operation, operation_examples in examples.items(): - for example in operation_examples: -- yield _lint_single_example, operation, example, service_model -+ _lint_single_example(operation, example, service_model) - - - def _lint_single_example(operation_name, example_config, service_model): -Index: b/tests/functional/test_alias.py -=================================================================== ---- a/tests/functional/test_alias.py -+++ b/tests/functional/test_alias.py -@@ -49,13 +49,13 @@ ALIAS_CASES = [ - def test_can_use_alias(): - session = botocore.session.get_session() - for case in ALIAS_CASES: -- yield _can_use_parameter_in_client_call, session, case -+ _can_use_parameter_in_client_call(session, case) - - - def test_can_use_original_name(): - session = botocore.session.get_session() - for case in ALIAS_CASES: -- yield _can_use_parameter_in_client_call, session, case, False -+ _can_use_parameter_in_client_call(session, case, False) - - - def _can_use_parameter_in_client_call(session, case, use_alias=True): -Index: b/tests/functional/test_event_alias.py -=================================================================== ---- a/tests/functional/test_event_alias.py -+++ b/tests/functional/test_event_alias.py -@@ -584,8 +584,8 @@ def test_event_alias(): - service_id = SERVICES[client_name]['service_id'] - if endpoint_prefix is not None: - yield _assert_handler_called, client_name, endpoint_prefix -- yield _assert_handler_called, client_name, service_id -- yield _assert_handler_called, client_name, client_name -+ _assert_handler_called(client_name, service_id) -+ _assert_handler_called(client_name, client_name) - - - def _assert_handler_called(client_name, event_part): -Index: b/tests/functional/test_h2_required.py -=================================================================== ---- a/tests/functional/test_h2_required.py -+++ b/tests/functional/test_h2_required.py -@@ -29,12 +29,12 @@ def test_all_uses_of_h2_are_known(): - service_model = session.get_service_model(service) - h2_config = service_model.metadata.get('protocolSettings', {}).get('h2') - if h2_config == 'required': -- yield _assert_h2_service_is_known, service -+ _assert_h2_service_is_known(service) - elif h2_config == 'eventstream': - for operation in service_model.operation_names: - operation_model = service_model.operation_model(operation) - if operation_model.has_event_stream_output: -- yield _assert_h2_operation_is_known, service, operation -+ _assert_h2_operation_is_known(service, operation) - - - def _assert_h2_service_is_known(service): -Index: b/tests/functional/test_model_completeness.py -=================================================================== ---- a/tests/functional/test_model_completeness.py -+++ b/tests/functional/test_model_completeness.py -@@ -38,5 +38,6 @@ def test_paginators_and_waiters_are_not_ - versions = Loader().list_api_versions(service_name, 'service-2') - if len(versions) > 1: - for type_name in ['paginators-1', 'waiters-2']: -- yield (_test_model_is_not_lost, service_name, -- type_name, versions[-2], versions[-1]) -+ _test_model_is_not_lost(service_name, -+ type_name, -+ versions[-2], versions[-1]) -Index: b/tests/functional/test_paginator_config.py -=================================================================== ---- a/tests/functional/test_paginator_config.py -+++ b/tests/functional/test_paginator_config.py -@@ -140,12 +140,7 @@ def test_lint_pagination_configs(): - 'paginators-1', - service_model.api_version) - for op_name, single_config in page_config['pagination'].items(): -- yield ( -- _lint_single_paginator, -- op_name, -- single_config, -- service_model -- ) -+ _lint_single_paginator(op_name, single_config, service_model) - - - def _lint_single_paginator(operation_name, page_config, -Index: b/tests/functional/test_public_apis.py -=================================================================== ---- a/tests/functional/test_public_apis.py -+++ b/tests/functional/test_public_apis.py -@@ -12,7 +12,7 @@ - # language governing permissions and limitations under the License. - from collections import defaultdict - --import mock -+from tests import mock - - from tests import ClientHTTPStubber - from botocore.session import Session -@@ -73,4 +73,4 @@ def test_public_apis_will_not_be_signed( - for operation_name in PUBLIC_API_TESTS[service_name]: - kwargs = PUBLIC_API_TESTS[service_name][operation_name] - method = getattr(client, xform_name(operation_name)) -- yield _test_public_apis_will_not_be_signed, client, method, kwargs -+ _test_public_apis_will_not_be_signed(client, method, kwargs) -Index: b/tests/functional/test_service_alias.py -=================================================================== ---- a/tests/functional/test_service_alias.py -+++ b/tests/functional/test_service_alias.py -@@ -17,7 +17,7 @@ from botocore.handlers import SERVICE_NA - def test_can_use_service_alias(): - session = botocore.session.get_session() - for (alias, name) in SERVICE_NAME_ALIASES.items(): -- yield _instantiates_the_same_client, session, name, alias -+ _instantiates_the_same_client(session, name, alias) - - - def _instantiates_the_same_client(session, service_name, service_alias): -Index: b/tests/functional/test_six_imports.py -=================================================================== ---- a/tests/functional/test_six_imports.py -+++ b/tests/functional/test_six_imports.py -@@ -15,7 +15,7 @@ def test_no_bare_six_imports(): - if not filename.endswith('.py'): - continue - fullname = os.path.join(rootdir, filename) -- yield _assert_no_bare_six_imports, fullname -+ _assert_no_bare_six_imports(fullname) - - - def _assert_no_bare_six_imports(filename): -Index: b/tests/functional/test_waiter_config.py -=================================================================== ---- a/tests/functional/test_waiter_config.py -+++ b/tests/functional/test_waiter_config.py -@@ -98,9 +98,9 @@ def test_lint_waiter_configs(): - except UnknownServiceError: - # The service doesn't have waiters - continue -- yield _validate_schema, validator, waiter_model -+ _validate_schema(validator, waiter_model) - for waiter_name in client.waiter_names: -- yield _lint_single_waiter, client, waiter_name, service_model -+ _lint_single_waiter(client, waiter_name, service_model) - - - def _lint_single_waiter(client, waiter_name, service_model): -Index: b/tests/functional/test_apigateway.py -=================================================================== ---- a/tests/functional/test_apigateway.py -+++ b/tests/functional/test_apigateway.py -@@ -10,7 +10,7 @@ - # 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. --import mock -+from tests import mock - - from tests import BaseSessionTest, ClientHTTPStubber - -Index: b/tests/functional/test_cloudsearchdomain.py -=================================================================== ---- a/tests/functional/test_cloudsearchdomain.py -+++ b/tests/functional/test_cloudsearchdomain.py -@@ -10,7 +10,7 @@ - # 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. --import mock -+from tests import mock - - from tests import BaseSessionTest, ClientHTTPStubber - -Index: b/tests/functional/test_docdb.py -=================================================================== ---- a/tests/functional/test_docdb.py -+++ b/tests/functional/test_docdb.py -@@ -10,7 +10,7 @@ - # 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. --import mock -+from tests import mock - from contextlib import contextmanager - - import botocore.session -Index: b/tests/functional/test_ec2.py -=================================================================== ---- a/tests/functional/test_ec2.py -+++ b/tests/functional/test_ec2.py -@@ -11,7 +11,7 @@ - # ANY KIND, either express or implied. See the License for the specific - # language governing permissions and limitations under the License. - import datetime --import mock -+from tests import mock - - from tests import unittest, ClientHTTPStubber, BaseSessionTest - from botocore.compat import parse_qs, urlparse -Index: b/tests/functional/test_lex.py -=================================================================== ---- a/tests/functional/test_lex.py -+++ b/tests/functional/test_lex.py -@@ -10,7 +10,7 @@ - # 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. --import mock -+from tests import mock - from datetime import datetime - - from tests import BaseSessionTest, ClientHTTPStubber -Index: b/tests/functional/test_machinelearning.py -=================================================================== ---- a/tests/functional/test_machinelearning.py -+++ b/tests/functional/test_machinelearning.py -@@ -10,7 +10,7 @@ - # 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. --import mock -+from tests import mock - - from tests import BaseSessionTest, ClientHTTPStubber - -Index: b/tests/functional/test_neptune.py -=================================================================== ---- a/tests/functional/test_neptune.py -+++ b/tests/functional/test_neptune.py -@@ -10,7 +10,7 @@ - # 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. --import mock -+from tests import mock - from contextlib import contextmanager - - import botocore.session -Index: b/tests/functional/test_rds.py -=================================================================== ---- a/tests/functional/test_rds.py -+++ b/tests/functional/test_rds.py -@@ -10,7 +10,7 @@ - # 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. --import mock -+from tests import mock - from contextlib import contextmanager - - import botocore.session -Index: b/tests/functional/test_session.py -=================================================================== ---- a/tests/functional/test_session.py -+++ b/tests/functional/test_session.py -@@ -12,7 +12,7 @@ - # language governing permissions and limitations under the License. - from tests import unittest, temporary_file - --import mock -+from tests import mock - - import botocore.session - from botocore.exceptions import ProfileNotFound -Index: b/tests/functional/test_sts.py -=================================================================== ---- a/tests/functional/test_sts.py -+++ b/tests/functional/test_sts.py -@@ -13,7 +13,7 @@ - from datetime import datetime - import re - --import mock -+from tests import mock - - from tests import BaseSessionTest - from tests import temporary_file -Index: b/tests/integration/test_credentials.py -=================================================================== ---- a/tests/integration/test_credentials.py -+++ b/tests/integration/test_credentials.py -@@ -11,7 +11,7 @@ - # ANY KIND, either express or implied. See the License for the specific - # language governing permissions and limitations under the License. - import os --import mock -+from tests import mock - import tempfile - import shutil - import json -Index: b/tests/integration/test_loaders.py -=================================================================== ---- a/tests/integration/test_loaders.py -+++ b/tests/integration/test_loaders.py -@@ -13,7 +13,7 @@ - import os - from tests import unittest - --import mock -+from tests import mock - - import botocore.session - -Index: b/tests/unit/auth/test_signers.py -=================================================================== ---- a/tests/unit/auth/test_signers.py -+++ b/tests/unit/auth/test_signers.py -@@ -18,7 +18,7 @@ import time - import base64 - import json - --import mock -+from tests import mock - - import botocore.auth - import botocore.credentials -Index: b/tests/unit/docs/__init__.py -=================================================================== ---- a/tests/unit/docs/__init__.py -+++ b/tests/unit/docs/__init__.py -@@ -16,7 +16,7 @@ import tempfile - import shutil - - from botocore.docs.bcdoc.restdoc import DocumentStructure --import mock -+from tests import mock - - from tests import unittest - from botocore.compat import OrderedDict -Index: b/tests/unit/docs/bcdoc/test_docstringparser.py -=================================================================== ---- a/tests/unit/docs/bcdoc/test_docstringparser.py -+++ b/tests/unit/docs/bcdoc/test_docstringparser.py -@@ -18,7 +18,7 @@ - # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - # IN THE SOFTWARE. --import mock -+from tests import mock - from tests import unittest - - import botocore.docs.bcdoc.docstringparser as parser -Index: b/tests/unit/docs/test_docs.py -=================================================================== ---- a/tests/unit/docs/test_docs.py -+++ b/tests/unit/docs/test_docs.py -@@ -14,7 +14,7 @@ import os - import shutil - import tempfile - --import mock -+from tests import mock - - from tests.unit.docs import BaseDocsTest - from botocore.session import get_session -Index: b/tests/unit/docs/test_example.py -=================================================================== ---- a/tests/unit/docs/test_example.py -+++ b/tests/unit/docs/test_example.py -@@ -10,7 +10,7 @@ - # 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. --import mock -+from tests import mock - - from tests.unit.docs import BaseDocsTest - from botocore.hooks import HierarchicalEmitter -Index: b/tests/unit/docs/test_params.py -=================================================================== ---- a/tests/unit/docs/test_params.py -+++ b/tests/unit/docs/test_params.py -@@ -10,7 +10,7 @@ - # 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. --import mock -+from tests import mock - - from tests.unit.docs import BaseDocsTest - from botocore.hooks import HierarchicalEmitter -Index: b/tests/unit/docs/test_service.py -=================================================================== ---- a/tests/unit/docs/test_service.py -+++ b/tests/unit/docs/test_service.py -@@ -12,7 +12,7 @@ - # language governing permissions and limitations under the License. - import os - --import mock -+from tests import mock - - from tests.unit.docs import BaseDocsTest - from botocore.session import get_session -Index: b/tests/unit/retries/test_adaptive.py -=================================================================== ---- a/tests/unit/retries/test_adaptive.py -+++ b/tests/unit/retries/test_adaptive.py -@@ -1,6 +1,6 @@ - from tests import unittest - --import mock -+from tests import mock - - from botocore.retries import adaptive - from botocore.retries import standard -Index: b/tests/unit/test_args.py -=================================================================== ---- a/tests/unit/test_args.py -+++ b/tests/unit/test_args.py -@@ -15,7 +15,7 @@ import socket - - import botocore.config - from tests import unittest --import mock -+from tests import mock - - from botocore import args - from botocore import exceptions -Index: b/tests/unit/test_configloader.py -=================================================================== ---- a/tests/unit/test_configloader.py -+++ b/tests/unit/test_configloader.py -@@ -14,7 +14,7 @@ - # language governing permissions and limitations under the License. - from tests import unittest, BaseEnvVar - import os --import mock -+from tests import mock - import tempfile - import shutil - -Index: b/tests/unit/test_history.py -=================================================================== ---- a/tests/unit/test_history.py -+++ b/tests/unit/test_history.py -@@ -1,6 +1,6 @@ - from tests import unittest - --import mock -+from tests import mock - - from botocore.history import HistoryRecorder - from botocore.history import BaseHistoryHandler -Index: b/tests/unit/test_idempotency.py -=================================================================== ---- a/tests/unit/test_idempotency.py -+++ b/tests/unit/test_idempotency.py -@@ -13,7 +13,7 @@ - - from tests import unittest - import re --import mock -+from tests import mock - from botocore.handlers import generate_idempotent_uuid - - -Index: b/tests/unit/test_retryhandler.py -=================================================================== ---- a/tests/unit/test_retryhandler.py -+++ b/tests/unit/test_retryhandler.py -@@ -15,7 +15,7 @@ - - from tests import unittest - --import mock -+from tests import mock - - from botocore import retryhandler - from botocore.exceptions import ( -Index: b/tests/unit/test_session.py -=================================================================== ---- a/tests/unit/test_session.py -+++ b/tests/unit/test_session.py -@@ -19,7 +19,7 @@ import logging +diff -Nru botocore-1.18.15.orig/tests/unit/test_session_legacy.py botocore-1.18.15/tests/unit/test_session_legacy.py +--- botocore-1.18.15.orig/tests/unit/test_session_legacy.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_session_legacy.py 2020-10-09 10:13:49.560472538 +0200 +@@ -19,7 +19,7 @@ import tempfile import shutil @@ -3925,11 +3769,10 @@ Index: b/tests/unit/test_session.py import botocore.session import botocore.exceptions -Index: b/tests/unit/test_session_legacy.py -=================================================================== ---- a/tests/unit/test_session_legacy.py -+++ b/tests/unit/test_session_legacy.py -@@ -19,7 +19,7 @@ import logging +diff -Nru botocore-1.18.15.orig/tests/unit/test_session.py botocore-1.18.15/tests/unit/test_session.py +--- botocore-1.18.15.orig/tests/unit/test_session.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_session.py 2020-10-09 10:13:49.560472538 +0200 +@@ -19,7 +19,7 @@ import tempfile import shutil @@ -3938,10 +3781,9 @@ Index: b/tests/unit/test_session_legacy.py import botocore.session import botocore.exceptions -Index: b/tests/unit/test_signers.py -=================================================================== ---- a/tests/unit/test_signers.py -+++ b/tests/unit/test_signers.py +diff -Nru botocore-1.18.15.orig/tests/unit/test_signers.py botocore-1.18.15/tests/unit/test_signers.py +--- botocore-1.18.15.orig/tests/unit/test_signers.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_signers.py 2020-10-09 10:13:49.560472538 +0200 @@ -10,7 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific @@ -3951,10 +3793,9 @@ Index: b/tests/unit/test_signers.py import datetime import json -Index: b/tests/unit/test_stub.py -=================================================================== ---- a/tests/unit/test_stub.py -+++ b/tests/unit/test_stub.py +diff -Nru botocore-1.18.15.orig/tests/unit/test_stub.py botocore-1.18.15/tests/unit/test_stub.py +--- botocore-1.18.15.orig/tests/unit/test_stub.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_stub.py 2020-10-09 10:13:49.560472538 +0200 @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. @@ -3964,33 +3805,2605 @@ Index: b/tests/unit/test_stub.py from botocore.stub import Stubber from botocore.exceptions import ParamValidationError, StubResponseError, UnStubbedResponseError -Index: b/tests/unit/test_discovery.py -=================================================================== ---- a/tests/unit/test_discovery.py -+++ b/tests/unit/test_discovery.py -@@ -1,5 +1,8 @@ - import time --from mock import Mock, call -+try: -+ from mock import Mock, call -+except ImportError: -+ from unittest.mock import Mock, call - from tests import unittest +diff -Nru botocore-1.18.15.orig/tests/unit/test_utils.py botocore-1.18.15/tests/unit/test_utils.py +--- botocore-1.18.15.orig/tests/unit/test_utils.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_utils.py 2020-10-09 10:13:49.540472262 +0200 +@@ -15,7 +15,7 @@ + from dateutil.tz import tzutc, tzoffset + import datetime + import copy +-import mock ++from tests import mock - from botocore.awsrequest import AWSRequest -Index: b/tests/unit/test_endpoint.py -=================================================================== ---- a/tests/unit/test_endpoint.py -+++ b/tests/unit/test_endpoint.py -@@ -13,7 +13,10 @@ - import socket - from tests import unittest + import botocore + from botocore import xform_name +@@ -2099,7 +2099,7 @@ + response_body = {'foo': 'bar'} + self.set_http_responses_to(response_body) + fetcher = self.create_fetcher() +- with self.assertRaisesRegexp(ValueError, 'Unsupported host'): ++ with six.assertRaisesRegex(self, ValueError, 'Unsupported host'): + fetcher.retrieve_full_uri(full_uri) + self.assertFalse(self.http.send.called) --from mock import Mock, patch, sentinel -+try: -+ from mock import Mock, patch, sentinel -+except ImportError: -+ from unittest.mock import Mock, patch, sentinel +diff -Nru botocore-1.18.15.orig/tests/unit/test_utils.py.orig botocore-1.18.15/tests/unit/test_utils.py.orig +--- botocore-1.18.15.orig/tests/unit/test_utils.py.orig 1970-01-01 01:00:00.000000000 +0100 ++++ botocore-1.18.15/tests/unit/test_utils.py.orig 2020-10-08 20:05:12.000000000 +0200 +@@ -0,0 +1,2556 @@ ++# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. ++# ++# Licensed under the Apache License, Version 2.0 (the "License"). You ++# may not use this file except in compliance with the License. A copy of ++# the License is located at ++# ++# http://aws.amazon.com/apache2.0/ ++# ++# or in the "license" file accompanying this file. This file 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. ++from tests import unittest ++from tests import RawResponse ++from dateutil.tz import tzutc, tzoffset ++import datetime ++import copy ++import mock ++ ++import botocore ++from botocore import xform_name ++from botocore.compat import OrderedDict, json ++from botocore.compat import six ++from botocore.awsrequest import AWSRequest ++from botocore.awsrequest import AWSResponse ++from botocore.exceptions import InvalidExpressionError, ConfigNotFound ++from botocore.exceptions import ClientError, ConnectionClosedError ++from botocore.exceptions import InvalidDNSNameError, MetadataRetrievalError ++from botocore.exceptions import ReadTimeoutError ++from botocore.exceptions import ConnectTimeoutError ++from botocore.exceptions import UnsupportedS3ArnError ++from botocore.exceptions import UnsupportedS3AccesspointConfigurationError ++from botocore.exceptions import UnsupportedOutpostResourceError ++from botocore.model import ServiceModel ++from botocore.model import OperationModel ++from botocore.regions import EndpointResolver ++from botocore.utils import ensure_boolean ++from botocore.utils import is_json_value_header ++from botocore.utils import remove_dot_segments ++from botocore.utils import normalize_url_path ++from botocore.utils import validate_jmespath_for_set ++from botocore.utils import set_value_from_jmespath ++from botocore.utils import parse_key_val_file_contents ++from botocore.utils import parse_key_val_file ++from botocore.utils import parse_timestamp ++from botocore.utils import parse_to_aware_datetime ++from botocore.utils import datetime2timestamp ++from botocore.utils import CachedProperty ++from botocore.utils import ArgumentGenerator ++from botocore.utils import calculate_tree_hash ++from botocore.utils import calculate_sha256 ++from botocore.utils import is_valid_endpoint_url ++from botocore.utils import fix_s3_host ++from botocore.utils import switch_to_virtual_host_style ++from botocore.utils import instance_cache ++from botocore.utils import merge_dicts ++from botocore.utils import lowercase_dict ++from botocore.utils import get_service_module_name ++from botocore.utils import percent_encode_sequence ++from botocore.utils import percent_encode ++from botocore.utils import switch_host_s3_accelerate ++from botocore.utils import deep_merge ++from botocore.utils import S3RegionRedirector ++from botocore.utils import InvalidArnException ++from botocore.utils import ArnParser ++from botocore.utils import S3ArnParamHandler ++from botocore.utils import S3EndpointSetter ++from botocore.utils import ContainerMetadataFetcher ++from botocore.utils import InstanceMetadataFetcher ++from botocore.utils import SSOTokenLoader ++from botocore.exceptions import SSOTokenLoadError ++from botocore.utils import IMDSFetcher ++from botocore.utils import BadIMDSRequestError ++from botocore.model import DenormalizedStructureBuilder ++from botocore.model import ShapeResolver ++from botocore.config import Config ++ ++ ++class TestEnsureBoolean(unittest.TestCase): ++ def test_boolean_true(self): ++ self.assertEqual(ensure_boolean(True), True) ++ ++ def test_boolean_false(self): ++ self.assertEqual(ensure_boolean(False), False) ++ ++ def test_string_true(self): ++ self.assertEqual(ensure_boolean('True'), True) ++ ++ def test_string_false(self): ++ self.assertEqual(ensure_boolean('False'), False) ++ ++ def test_string_lowercase_true(self): ++ self.assertEqual(ensure_boolean('true'), True) ++ ++ ++class TestIsJSONValueHeader(unittest.TestCase): ++ def test_no_serialization_section(self): ++ shape = mock.Mock() ++ shape.type_name = 'string' ++ self.assertFalse(is_json_value_header(shape)) ++ ++ def test_non_jsonvalue_shape(self): ++ shape = mock.Mock() ++ shape.serialization = { ++ 'location': 'header' ++ } ++ shape.type_name = 'string' ++ self.assertFalse(is_json_value_header(shape)) ++ ++ def test_non_header_jsonvalue_shape(self): ++ shape = mock.Mock() ++ shape.serialization = { ++ 'jsonvalue': True ++ } ++ shape.type_name = 'string' ++ self.assertFalse(is_json_value_header(shape)) ++ ++ def test_non_string_jsonvalue_shape(self): ++ shape = mock.Mock() ++ shape.serialization = { ++ 'location': 'header', ++ 'jsonvalue': True ++ } ++ shape.type_name = 'integer' ++ self.assertFalse(is_json_value_header(shape)) ++ ++ def test_json_value_header(self): ++ shape = mock.Mock() ++ shape.serialization = { ++ 'jsonvalue': True, ++ 'location': 'header' ++ } ++ shape.type_name = 'string' ++ self.assertTrue(is_json_value_header(shape)) ++ ++ ++ ++class TestURINormalization(unittest.TestCase): ++ def test_remove_dot_segments(self): ++ self.assertEqual(remove_dot_segments('../foo'), 'foo') ++ self.assertEqual(remove_dot_segments('../../foo'), 'foo') ++ self.assertEqual(remove_dot_segments('./foo'), 'foo') ++ self.assertEqual(remove_dot_segments('/./'), '/') ++ self.assertEqual(remove_dot_segments('/../'), '/') ++ self.assertEqual(remove_dot_segments('/foo/bar/baz/../qux'), ++ '/foo/bar/qux') ++ self.assertEqual(remove_dot_segments('/foo/..'), '/') ++ self.assertEqual(remove_dot_segments('foo/bar/baz'), 'foo/bar/baz') ++ self.assertEqual(remove_dot_segments('..'), '') ++ self.assertEqual(remove_dot_segments('.'), '') ++ self.assertEqual(remove_dot_segments('/.'), '/') ++ self.assertEqual(remove_dot_segments('/.foo'), '/.foo') ++ self.assertEqual(remove_dot_segments('/..foo'), '/..foo') ++ self.assertEqual(remove_dot_segments(''), '') ++ self.assertEqual(remove_dot_segments('/a/b/c/./../../g'), '/a/g') ++ self.assertEqual(remove_dot_segments('mid/content=5/../6'), 'mid/6') ++ # I don't think this is RFC compliant... ++ self.assertEqual(remove_dot_segments('//foo//'), '/foo/') ++ ++ def test_empty_url_normalization(self): ++ self.assertEqual(normalize_url_path(''), '/') ++ ++ ++class TestTransformName(unittest.TestCase): ++ def test_upper_camel_case(self): ++ self.assertEqual(xform_name('UpperCamelCase'), 'upper_camel_case') ++ self.assertEqual(xform_name('UpperCamelCase', '-'), 'upper-camel-case') ++ ++ def test_lower_camel_case(self): ++ self.assertEqual(xform_name('lowerCamelCase'), 'lower_camel_case') ++ self.assertEqual(xform_name('lowerCamelCase', '-'), 'lower-camel-case') ++ ++ def test_consecutive_upper_case(self): ++ self.assertEqual(xform_name('HTTPHeaders'), 'http_headers') ++ self.assertEqual(xform_name('HTTPHeaders', '-'), 'http-headers') ++ ++ def test_consecutive_upper_case_middle_string(self): ++ self.assertEqual(xform_name('MainHTTPHeaders'), 'main_http_headers') ++ self.assertEqual(xform_name('MainHTTPHeaders', '-'), ++ 'main-http-headers') ++ ++ def test_s3_prefix(self): ++ self.assertEqual(xform_name('S3BucketName'), 's3_bucket_name') ++ ++ def test_already_snake_cased(self): ++ self.assertEqual(xform_name('leave_alone'), 'leave_alone') ++ self.assertEqual(xform_name('s3_bucket_name'), 's3_bucket_name') ++ self.assertEqual(xform_name('bucket_s3_name'), 'bucket_s3_name') ++ ++ def test_special_cases(self): ++ # Some patterns don't actually match the rules we expect. ++ self.assertEqual(xform_name('SwapEnvironmentCNAMEs'), ++ 'swap_environment_cnames') ++ self.assertEqual(xform_name('SwapEnvironmentCNAMEs', '-'), ++ 'swap-environment-cnames') ++ self.assertEqual(xform_name('CreateCachediSCSIVolume', '-'), ++ 'create-cached-iscsi-volume') ++ self.assertEqual(xform_name('DescribeCachediSCSIVolumes', '-'), ++ 'describe-cached-iscsi-volumes') ++ self.assertEqual(xform_name('DescribeStorediSCSIVolumes', '-'), ++ 'describe-stored-iscsi-volumes') ++ self.assertEqual(xform_name('CreateStorediSCSIVolume', '-'), ++ 'create-stored-iscsi-volume') ++ ++ def test_special_case_ends_with_s(self): ++ self.assertEqual(xform_name('GatewayARNs', '-'), 'gateway-arns') ++ ++ def test_partial_rename(self): ++ transformed = xform_name('IPV6', '-') ++ self.assertEqual(transformed, 'ipv6') ++ transformed = xform_name('IPV6', '_') ++ self.assertEqual(transformed, 'ipv6') ++ ++ def test_s3_partial_rename(self): ++ transformed = xform_name('s3Resources', '-') ++ self.assertEqual(transformed, 's3-resources') ++ transformed = xform_name('s3Resources', '_') ++ self.assertEqual(transformed, 's3_resources') ++ ++ ++class TestValidateJMESPathForSet(unittest.TestCase): ++ def setUp(self): ++ super(TestValidateJMESPathForSet, self).setUp() ++ self.data = { ++ 'Response': { ++ 'Thing': { ++ 'Id': 1, ++ 'Name': 'Thing #1', ++ } ++ }, ++ 'Marker': 'some-token' ++ } ++ ++ def test_invalid_exp(self): ++ with self.assertRaises(InvalidExpressionError): ++ validate_jmespath_for_set('Response.*.Name') ++ ++ with self.assertRaises(InvalidExpressionError): ++ validate_jmespath_for_set('Response.Things[0]') ++ ++ with self.assertRaises(InvalidExpressionError): ++ validate_jmespath_for_set('') ++ ++ with self.assertRaises(InvalidExpressionError): ++ validate_jmespath_for_set('.') ++ ++ ++class TestSetValueFromJMESPath(unittest.TestCase): ++ def setUp(self): ++ super(TestSetValueFromJMESPath, self).setUp() ++ self.data = { ++ 'Response': { ++ 'Thing': { ++ 'Id': 1, ++ 'Name': 'Thing #1', ++ } ++ }, ++ 'Marker': 'some-token' ++ } ++ ++ def test_single_depth_existing(self): ++ set_value_from_jmespath(self.data, 'Marker', 'new-token') ++ self.assertEqual(self.data['Marker'], 'new-token') ++ ++ def test_single_depth_new(self): ++ self.assertFalse('Limit' in self.data) ++ set_value_from_jmespath(self.data, 'Limit', 100) ++ self.assertEqual(self.data['Limit'], 100) ++ ++ def test_multiple_depth_existing(self): ++ set_value_from_jmespath(self.data, 'Response.Thing.Name', 'New Name') ++ self.assertEqual(self.data['Response']['Thing']['Name'], 'New Name') ++ ++ def test_multiple_depth_new(self): ++ self.assertFalse('Brand' in self.data) ++ set_value_from_jmespath(self.data, 'Brand.New', {'abc': 123}) ++ self.assertEqual(self.data['Brand']['New']['abc'], 123) ++ ++ ++class TestParseEC2CredentialsFile(unittest.TestCase): ++ def test_parse_ec2_content(self): ++ contents = "AWSAccessKeyId=a\nAWSSecretKey=b\n" ++ self.assertEqual(parse_key_val_file_contents(contents), ++ {'AWSAccessKeyId': 'a', ++ 'AWSSecretKey': 'b'}) ++ ++ def test_parse_ec2_content_empty(self): ++ contents = "" ++ self.assertEqual(parse_key_val_file_contents(contents), {}) ++ ++ def test_key_val_pair_with_blank_lines(self): ++ # The \n\n has an extra blank between the access/secret keys. ++ contents = "AWSAccessKeyId=a\n\nAWSSecretKey=b\n" ++ self.assertEqual(parse_key_val_file_contents(contents), ++ {'AWSAccessKeyId': 'a', ++ 'AWSSecretKey': 'b'}) ++ ++ def test_key_val_parser_lenient(self): ++ # Ignore any line that does not have a '=' char in it. ++ contents = "AWSAccessKeyId=a\nNOTKEYVALLINE\nAWSSecretKey=b\n" ++ self.assertEqual(parse_key_val_file_contents(contents), ++ {'AWSAccessKeyId': 'a', ++ 'AWSSecretKey': 'b'}) ++ ++ def test_multiple_equals_on_line(self): ++ contents = "AWSAccessKeyId=a\nAWSSecretKey=secret_key_with_equals=b\n" ++ self.assertEqual(parse_key_val_file_contents(contents), ++ {'AWSAccessKeyId': 'a', ++ 'AWSSecretKey': 'secret_key_with_equals=b'}) ++ ++ def test_os_error_raises_config_not_found(self): ++ mock_open = mock.Mock() ++ mock_open.side_effect = OSError() ++ with self.assertRaises(ConfigNotFound): ++ parse_key_val_file('badfile', _open=mock_open) ++ ++ ++class TestParseTimestamps(unittest.TestCase): ++ def test_parse_iso8601(self): ++ self.assertEqual( ++ parse_timestamp('1970-01-01T00:10:00.000Z'), ++ datetime.datetime(1970, 1, 1, 0, 10, tzinfo=tzutc())) ++ ++ def test_parse_epoch(self): ++ self.assertEqual( ++ parse_timestamp(1222172800), ++ datetime.datetime(2008, 9, 23, 12, 26, 40, tzinfo=tzutc())) ++ ++ def test_parse_epoch_zero_time(self): ++ self.assertEqual( ++ parse_timestamp(0), ++ datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=tzutc())) ++ ++ def test_parse_epoch_as_string(self): ++ self.assertEqual( ++ parse_timestamp('1222172800'), ++ datetime.datetime(2008, 9, 23, 12, 26, 40, tzinfo=tzutc())) ++ ++ def test_parse_rfc822(self): ++ self.assertEqual( ++ parse_timestamp('Wed, 02 Oct 2002 13:00:00 GMT'), ++ datetime.datetime(2002, 10, 2, 13, 0, tzinfo=tzutc())) ++ ++ def test_parse_gmt_in_uk_time(self): ++ # In the UK the time switches from GMT to BST and back as part of ++ # their daylight savings time. time.tzname will therefore report ++ # both time zones. dateutil sees that the time zone is a local time ++ # zone and so parses it as local time, but it ends up being BST ++ # instead of GMT. To remedy this issue we can provide a time zone ++ # context which will enforce GMT == UTC. ++ with mock.patch('time.tzname', ('GMT', 'BST')): ++ self.assertEqual( ++ parse_timestamp('Wed, 02 Oct 2002 13:00:00 GMT'), ++ datetime.datetime(2002, 10, 2, 13, 0, tzinfo=tzutc())) ++ ++ def test_parse_invalid_timestamp(self): ++ with self.assertRaises(ValueError): ++ parse_timestamp('invalid date') ++ ++ def test_parse_timestamp_fails_with_bad_tzinfo(self): ++ mock_tzinfo = mock.Mock() ++ mock_tzinfo.__name__ = 'tzinfo' ++ mock_tzinfo.side_effect = OSError() ++ mock_get_tzinfo_options = mock.MagicMock(return_value=(mock_tzinfo,)) ++ ++ with mock.patch('botocore.utils.get_tzinfo_options', mock_get_tzinfo_options): ++ with self.assertRaises(RuntimeError): ++ parse_timestamp(0) ++ ++ ++class TestDatetime2Timestamp(unittest.TestCase): ++ def test_datetime2timestamp_naive(self): ++ self.assertEqual( ++ datetime2timestamp(datetime.datetime(1970, 1, 2)), 86400) ++ ++ def test_datetime2timestamp_aware(self): ++ tzinfo = tzoffset("BRST", -10800) ++ self.assertEqual( ++ datetime2timestamp(datetime.datetime(1970, 1, 2, tzinfo=tzinfo)), ++ 97200) ++ ++ ++class TestParseToUTCDatetime(unittest.TestCase): ++ def test_handles_utc_time(self): ++ original = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=tzutc()) ++ self.assertEqual(parse_to_aware_datetime(original), original) ++ ++ def test_handles_other_timezone(self): ++ tzinfo = tzoffset("BRST", -10800) ++ original = datetime.datetime(2014, 1, 1, 0, 0, 0, tzinfo=tzinfo) ++ self.assertEqual(parse_to_aware_datetime(original), original) ++ ++ def test_handles_naive_datetime(self): ++ original = datetime.datetime(1970, 1, 1, 0, 0, 0) ++ expected = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=tzutc()) ++ self.assertEqual(parse_to_aware_datetime(original), expected) ++ ++ def test_handles_string_epoch(self): ++ expected = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=tzutc()) ++ self.assertEqual(parse_to_aware_datetime('0'), expected) ++ ++ def test_handles_int_epoch(self): ++ expected = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=tzutc()) ++ self.assertEqual(parse_to_aware_datetime(0), expected) ++ ++ def test_handles_full_iso_8601(self): ++ expected = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=tzutc()) ++ self.assertEqual( ++ parse_to_aware_datetime('1970-01-01T00:00:00Z'), ++ expected) ++ ++ def test_year_only_iso_8601(self): ++ expected = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=tzutc()) ++ self.assertEqual(parse_to_aware_datetime('1970-01-01'), expected) ++ ++ ++class TestCachedProperty(unittest.TestCase): ++ def test_cached_property_same_value(self): ++ class CacheMe(object): ++ @CachedProperty ++ def foo(self): ++ return 'foo' ++ ++ c = CacheMe() ++ self.assertEqual(c.foo, 'foo') ++ self.assertEqual(c.foo, 'foo') ++ ++ def test_cached_property_only_called_once(self): ++ # Note: you would normally never want to cache ++ # a property that returns a new value each time, ++ # but this is done to demonstrate the caching behavior. ++ ++ class NoIncrement(object): ++ def __init__(self): ++ self.counter = 0 ++ ++ @CachedProperty ++ def current_value(self): ++ self.counter += 1 ++ return self.counter ++ ++ c = NoIncrement() ++ self.assertEqual(c.current_value, 1) ++ # If the property wasn't cached, the next value should be ++ # be 2, but because it's cached, we know the value will be 1. ++ self.assertEqual(c.current_value, 1) ++ ++ ++class TestArgumentGenerator(unittest.TestCase): ++ def setUp(self): ++ self.arg_generator = ArgumentGenerator() ++ ++ def assert_skeleton_from_model_is(self, model, generated_skeleton): ++ shape = DenormalizedStructureBuilder().with_members( ++ model).build_model() ++ actual = self.arg_generator.generate_skeleton(shape) ++ self.assertEqual(actual, generated_skeleton) ++ ++ def test_generate_string(self): ++ self.assert_skeleton_from_model_is( ++ model={ ++ 'A': {'type': 'string'} ++ }, ++ generated_skeleton={ ++ 'A': '' ++ } ++ ) ++ ++ def test_generate_string_enum(self): ++ enum_values = ['A', 'B', 'C'] ++ model = { ++ 'A': {'type': 'string', 'enum': enum_values} ++ } ++ shape = DenormalizedStructureBuilder().with_members( ++ model).build_model() ++ actual = self.arg_generator.generate_skeleton(shape) ++ ++ self.assertIn(actual['A'], enum_values) ++ ++ def test_generate_scalars(self): ++ self.assert_skeleton_from_model_is( ++ model={ ++ 'A': {'type': 'string'}, ++ 'B': {'type': 'integer'}, ++ 'C': {'type': 'float'}, ++ 'D': {'type': 'boolean'}, ++ 'E': {'type': 'timestamp'} ++ }, ++ generated_skeleton={ ++ 'A': '', ++ 'B': 0, ++ 'C': 0.0, ++ 'D': True, ++ 'E': datetime.datetime(1970, 1, 1, 0, 0, 0) ++ } ++ ) ++ ++ def test_will_use_member_names_for_string_values(self): ++ self.arg_generator = ArgumentGenerator(use_member_names=True) ++ self.assert_skeleton_from_model_is( ++ model={ ++ 'A': {'type': 'string'}, ++ 'B': {'type': 'integer'}, ++ 'C': {'type': 'float'}, ++ 'D': {'type': 'boolean'}, ++ }, ++ generated_skeleton={ ++ 'A': 'A', ++ 'B': 0, ++ 'C': 0.0, ++ 'D': True, ++ } ++ ) ++ ++ def test_will_use_member_names_for_string_values_of_list(self): ++ self.arg_generator = ArgumentGenerator(use_member_names=True) ++ # We're not using assert_skeleton_from_model_is ++ # because we can't really control the name of strings shapes ++ # being used in the DenormalizedStructureBuilder. We can only ++ # control the name of structures and list shapes. ++ shape_map = ShapeResolver({ ++ 'InputShape': { ++ 'type': 'structure', ++ 'members': { ++ 'StringList': {'shape': 'StringList'}, ++ } ++ }, ++ 'StringList': { ++ 'type': 'list', ++ 'member': {'shape': 'StringType'}, ++ }, ++ 'StringType': { ++ 'type': 'string', ++ } ++ }) ++ shape = shape_map.get_shape_by_name('InputShape') ++ actual = self.arg_generator.generate_skeleton(shape) ++ ++ expected = {'StringList': ['StringType']} ++ self.assertEqual(actual, expected) ++ ++ def test_generate_nested_structure(self): ++ self.assert_skeleton_from_model_is( ++ model={ ++ 'A': { ++ 'type': 'structure', ++ 'members': { ++ 'B': {'type': 'string'}, ++ } ++ } ++ }, ++ generated_skeleton={ ++ 'A': {'B': ''} ++ } ++ ) ++ ++ def test_generate_scalar_list(self): ++ self.assert_skeleton_from_model_is( ++ model={ ++ 'A': { ++ 'type': 'list', ++ 'member': { ++ 'type': 'string' ++ } ++ }, ++ }, ++ generated_skeleton={ ++ 'A': [''], ++ } ++ ) ++ ++ def test_generate_scalar_map(self): ++ self.assert_skeleton_from_model_is( ++ model={ ++ 'A': { ++ 'type': 'map', ++ 'key': {'type': 'string'}, ++ 'value': {'type': 'string'}, ++ } ++ }, ++ generated_skeleton={ ++ 'A': { ++ 'KeyName': '', ++ } ++ } ++ ) ++ ++ def test_handles_recursive_shapes(self): ++ # We're not using assert_skeleton_from_model_is ++ # because we can't use a DenormalizedStructureBuilder, ++ # we need a normalized model to represent recursive ++ # shapes. ++ shape_map = ShapeResolver({ ++ 'InputShape': { ++ 'type': 'structure', ++ 'members': { ++ 'A': {'shape': 'RecursiveStruct'}, ++ 'B': {'shape': 'StringType'}, ++ } ++ }, ++ 'RecursiveStruct': { ++ 'type': 'structure', ++ 'members': { ++ 'C': {'shape': 'RecursiveStruct'}, ++ 'D': {'shape': 'StringType'}, ++ } ++ }, ++ 'StringType': { ++ 'type': 'string', ++ } ++ }) ++ shape = shape_map.get_shape_by_name('InputShape') ++ actual = self.arg_generator.generate_skeleton(shape) ++ expected = { ++ 'A': { ++ 'C': { ++ # For recurisve shapes, we'll just show ++ # an empty dict. ++ }, ++ 'D': '' ++ }, ++ 'B': '' ++ } ++ self.assertEqual(actual, expected) ++ ++ ++class TestChecksums(unittest.TestCase): ++ def test_empty_hash(self): ++ self.assertEqual( ++ calculate_sha256(six.BytesIO(b''), as_hex=True), ++ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') ++ ++ def test_as_hex(self): ++ self.assertEqual( ++ calculate_sha256(six.BytesIO(b'hello world'), as_hex=True), ++ 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9') ++ ++ def test_as_binary(self): ++ self.assertEqual( ++ calculate_sha256(six.BytesIO(b'hello world'), as_hex=False), ++ (b"\xb9M'\xb9\x93M>\x08\xa5.R\xd7\xda}\xab\xfa\xc4\x84\xef" ++ b"\xe3zS\x80\xee\x90\x88\xf7\xac\xe2\xef\xcd\xe9")) ++ ++ ++class TestTreeHash(unittest.TestCase): ++ # Note that for these tests I've independently verified ++ # what the expected tree hashes should be from other ++ # SDK implementations. ++ ++ def test_empty_tree_hash(self): ++ self.assertEqual( ++ calculate_tree_hash(six.BytesIO(b'')), ++ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') ++ ++ def test_tree_hash_less_than_one_mb(self): ++ one_k = six.BytesIO(b'a' * 1024) ++ self.assertEqual( ++ calculate_tree_hash(one_k), ++ '2edc986847e209b4016e141a6dc8716d3207350f416969382d431539bf292e4a') ++ ++ def test_tree_hash_exactly_one_mb(self): ++ one_meg_bytestring = b'a' * (1 * 1024 * 1024) ++ one_meg = six.BytesIO(one_meg_bytestring) ++ self.assertEqual( ++ calculate_tree_hash(one_meg), ++ '9bc1b2a288b26af7257a36277ae3816a7d4f16e89c1e7e77d0a5c48bad62b360') ++ ++ def test_tree_hash_multiple_of_one_mb(self): ++ four_mb = six.BytesIO(b'a' * (4 * 1024 * 1024)) ++ self.assertEqual( ++ calculate_tree_hash(four_mb), ++ '9491cb2ed1d4e7cd53215f4017c23ec4ad21d7050a1e6bb636c4f67e8cddb844') ++ ++ def test_tree_hash_offset_of_one_mb_multiple(self): ++ offset_four_mb = six.BytesIO(b'a' * (4 * 1024 * 1024) + b'a' * 20) ++ self.assertEqual( ++ calculate_tree_hash(offset_four_mb), ++ '12f3cbd6101b981cde074039f6f728071da8879d6f632de8afc7cdf00661b08f') ++ ++ ++class TestIsValidEndpointURL(unittest.TestCase): ++ def test_dns_name_is_valid(self): ++ self.assertTrue(is_valid_endpoint_url('https://s3.amazonaws.com/')) ++ ++ def test_ip_address_is_allowed(self): ++ self.assertTrue(is_valid_endpoint_url('https://10.10.10.10/')) ++ ++ def test_path_component_ignored(self): ++ self.assertTrue( ++ is_valid_endpoint_url('https://foo.bar.com/other/path/')) ++ ++ def test_can_have_port(self): ++ self.assertTrue(is_valid_endpoint_url('https://foo.bar.com:12345/')) ++ ++ def test_ip_can_have_port(self): ++ self.assertTrue(is_valid_endpoint_url('https://10.10.10.10:12345/')) ++ ++ def test_cannot_have_spaces(self): ++ self.assertFalse(is_valid_endpoint_url('https://my invalid name/')) ++ ++ def test_missing_scheme(self): ++ self.assertFalse(is_valid_endpoint_url('foo.bar.com')) ++ ++ def test_no_new_lines(self): ++ self.assertFalse(is_valid_endpoint_url('https://foo.bar.com\nbar/')) ++ ++ def test_long_hostname(self): ++ long_hostname = 'htps://%s.com' % ('a' * 256) ++ self.assertFalse(is_valid_endpoint_url(long_hostname)) ++ ++ def test_hostname_can_end_with_dot(self): ++ self.assertTrue(is_valid_endpoint_url('https://foo.bar.com./')) ++ ++ def test_hostname_no_dots(self): ++ self.assertTrue(is_valid_endpoint_url('https://foo/')) ++ ++ ++class TestFixS3Host(unittest.TestCase): ++ def test_fix_s3_host_initial(self): ++ request = AWSRequest( ++ method='PUT', headers={}, ++ url='https://s3-us-west-2.amazonaws.com/bucket/key.txt' ++ ) ++ region_name = 'us-west-2' ++ signature_version = 's3' ++ fix_s3_host( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ self.assertEqual(request.url, ++ 'https://bucket.s3-us-west-2.amazonaws.com/key.txt') ++ self.assertEqual(request.auth_path, '/bucket/key.txt') ++ ++ def test_fix_s3_host_only_applied_once(self): ++ request = AWSRequest( ++ method='PUT', headers={}, ++ url='https://s3.us-west-2.amazonaws.com/bucket/key.txt' ++ ) ++ region_name = 'us-west-2' ++ signature_version = 's3' ++ fix_s3_host( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ # Calling the handler again should not affect the end result: ++ fix_s3_host( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ self.assertEqual(request.url, ++ 'https://bucket.s3.us-west-2.amazonaws.com/key.txt') ++ # This was a bug previously. We want to make sure that ++ # calling fix_s3_host() again does not alter the auth_path. ++ # Otherwise we'll get signature errors. ++ self.assertEqual(request.auth_path, '/bucket/key.txt') ++ ++ def test_dns_style_not_used_for_get_bucket_location(self): ++ original_url = 'https://s3-us-west-2.amazonaws.com/bucket?location' ++ request = AWSRequest( ++ method='GET', headers={}, ++ url=original_url, ++ ) ++ signature_version = 's3' ++ region_name = 'us-west-2' ++ fix_s3_host( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ # The request url should not have been modified because this is ++ # a request for GetBucketLocation. ++ self.assertEqual(request.url, original_url) ++ ++ def test_can_provide_default_endpoint_url(self): ++ request = AWSRequest( ++ method='PUT', headers={}, ++ url='https://s3-us-west-2.amazonaws.com/bucket/key.txt' ++ ) ++ region_name = 'us-west-2' ++ signature_version = 's3' ++ fix_s3_host( ++ request=request, signature_version=signature_version, ++ region_name=region_name, ++ default_endpoint_url='foo.s3.amazonaws.com') ++ self.assertEqual(request.url, ++ 'https://bucket.foo.s3.amazonaws.com/key.txt') ++ ++ def test_no_endpoint_url_uses_request_url(self): ++ request = AWSRequest( ++ method='PUT', headers={}, ++ url='https://s3-us-west-2.amazonaws.com/bucket/key.txt' ++ ) ++ region_name = 'us-west-2' ++ signature_version = 's3' ++ fix_s3_host( ++ request=request, signature_version=signature_version, ++ region_name=region_name, ++ # A value of None means use the url in the current request. ++ default_endpoint_url=None, ++ ) ++ self.assertEqual(request.url, ++ 'https://bucket.s3-us-west-2.amazonaws.com/key.txt') ++ ++ ++class TestSwitchToVirtualHostStyle(unittest.TestCase): ++ def test_switch_to_virtual_host_style(self): ++ request = AWSRequest( ++ method='PUT', headers={}, ++ url='https://foo.amazonaws.com/bucket/key.txt' ++ ) ++ region_name = 'us-west-2' ++ signature_version = 's3' ++ switch_to_virtual_host_style( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ self.assertEqual(request.url, ++ 'https://bucket.foo.amazonaws.com/key.txt') ++ self.assertEqual(request.auth_path, '/bucket/key.txt') ++ ++ def test_uses_default_endpoint(self): ++ request = AWSRequest( ++ method='PUT', headers={}, ++ url='https://foo.amazonaws.com/bucket/key.txt' ++ ) ++ region_name = 'us-west-2' ++ signature_version = 's3' ++ switch_to_virtual_host_style( ++ request=request, signature_version=signature_version, ++ region_name=region_name, default_endpoint_url='s3.amazonaws.com') ++ self.assertEqual(request.url, ++ 'https://bucket.s3.amazonaws.com/key.txt') ++ self.assertEqual(request.auth_path, '/bucket/key.txt') ++ ++ def test_throws_invalid_dns_name_error(self): ++ request = AWSRequest( ++ method='PUT', headers={}, ++ url='https://foo.amazonaws.com/mybucket.foo/key.txt' ++ ) ++ region_name = 'us-west-2' ++ signature_version = 's3' ++ with self.assertRaises(InvalidDNSNameError): ++ switch_to_virtual_host_style( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ ++ def test_fix_s3_host_only_applied_once(self): ++ request = AWSRequest( ++ method='PUT', headers={}, ++ url='https://foo.amazonaws.com/bucket/key.txt' ++ ) ++ region_name = 'us-west-2' ++ signature_version = 's3' ++ switch_to_virtual_host_style( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ # Calling the handler again should not affect the end result: ++ switch_to_virtual_host_style( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ self.assertEqual(request.url, ++ 'https://bucket.foo.amazonaws.com/key.txt') ++ # This was a bug previously. We want to make sure that ++ # calling fix_s3_host() again does not alter the auth_path. ++ # Otherwise we'll get signature errors. ++ self.assertEqual(request.auth_path, '/bucket/key.txt') ++ ++ def test_virtual_host_style_for_make_bucket(self): ++ request = AWSRequest( ++ method='PUT', headers={}, ++ url='https://foo.amazonaws.com/bucket' ++ ) ++ region_name = 'us-west-2' ++ signature_version = 's3' ++ switch_to_virtual_host_style( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ self.assertEqual(request.url, ++ 'https://bucket.foo.amazonaws.com/') ++ ++ def test_virtual_host_style_not_used_for_get_bucket_location(self): ++ original_url = 'https://foo.amazonaws.com/bucket?location' ++ request = AWSRequest( ++ method='GET', headers={}, ++ url=original_url, ++ ) ++ signature_version = 's3' ++ region_name = 'us-west-2' ++ switch_to_virtual_host_style( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ # The request url should not have been modified because this is ++ # a request for GetBucketLocation. ++ self.assertEqual(request.url, original_url) ++ ++ def test_virtual_host_style_not_used_for_list_buckets(self): ++ original_url = 'https://foo.amazonaws.com/' ++ request = AWSRequest( ++ method='GET', headers={}, ++ url=original_url, ++ ) ++ signature_version = 's3' ++ region_name = 'us-west-2' ++ switch_to_virtual_host_style( ++ request=request, signature_version=signature_version, ++ region_name=region_name) ++ # The request url should not have been modified because this is ++ # a request for GetBucketLocation. ++ self.assertEqual(request.url, original_url) ++ ++ def test_is_unaffected_by_sigv4(self): ++ request = AWSRequest( ++ method='PUT', headers={}, ++ url='https://foo.amazonaws.com/bucket/key.txt' ++ ) ++ region_name = 'us-west-2' ++ signature_version = 's3v4' ++ switch_to_virtual_host_style( ++ request=request, signature_version=signature_version, ++ region_name=region_name, default_endpoint_url='s3.amazonaws.com') ++ self.assertEqual(request.url, ++ 'https://bucket.s3.amazonaws.com/key.txt') ++ ++ ++class TestInstanceCache(unittest.TestCase): ++ class DummyClass(object): ++ def __init__(self, cache): ++ self._instance_cache = cache ++ ++ @instance_cache ++ def add(self, x, y): ++ return x + y ++ ++ @instance_cache ++ def sub(self, x, y): ++ return x - y ++ ++ def setUp(self): ++ self.cache = {} ++ ++ def test_cache_single_method_call(self): ++ adder = self.DummyClass(self.cache) ++ self.assertEqual(adder.add(2, 1), 3) ++ # This should result in one entry in the cache. ++ self.assertEqual(len(self.cache), 1) ++ # When we call the method with the same args, ++ # we should reuse the same entry in the cache. ++ self.assertEqual(adder.add(2, 1), 3) ++ self.assertEqual(len(self.cache), 1) ++ ++ def test_can_cache_multiple_methods(self): ++ adder = self.DummyClass(self.cache) ++ adder.add(2, 1) ++ ++ # A different method results in a new cache entry, ++ # so now there should be two elements in the cache. ++ self.assertEqual(adder.sub(2, 1), 1) ++ self.assertEqual(len(self.cache), 2) ++ self.assertEqual(adder.sub(2, 1), 1) ++ ++ def test_can_cache_kwargs(self): ++ adder = self.DummyClass(self.cache) ++ adder.add(x=2, y=1) ++ self.assertEqual(adder.add(x=2, y=1), 3) ++ self.assertEqual(len(self.cache), 1) ++ ++ ++class TestMergeDicts(unittest.TestCase): ++ def test_merge_dicts_overrides(self): ++ first = { ++ 'foo': {'bar': {'baz': {'one': 'ORIGINAL', 'two': 'ORIGINAL'}}}} ++ second = {'foo': {'bar': {'baz': {'one': 'UPDATE'}}}} ++ ++ merge_dicts(first, second) ++ # The value from the second dict wins. ++ self.assertEqual(first['foo']['bar']['baz']['one'], 'UPDATE') ++ # And we still preserve the other attributes. ++ self.assertEqual(first['foo']['bar']['baz']['two'], 'ORIGINAL') ++ ++ def test_merge_dicts_new_keys(self): ++ first = { ++ 'foo': {'bar': {'baz': {'one': 'ORIGINAL', 'two': 'ORIGINAL'}}}} ++ second = {'foo': {'bar': {'baz': {'three': 'UPDATE'}}}} ++ ++ merge_dicts(first, second) ++ self.assertEqual(first['foo']['bar']['baz']['one'], 'ORIGINAL') ++ self.assertEqual(first['foo']['bar']['baz']['two'], 'ORIGINAL') ++ self.assertEqual(first['foo']['bar']['baz']['three'], 'UPDATE') ++ ++ def test_merge_empty_dict_does_nothing(self): ++ first = {'foo': {'bar': 'baz'}} ++ merge_dicts(first, {}) ++ self.assertEqual(first, {'foo': {'bar': 'baz'}}) ++ ++ def test_more_than_one_sub_dict(self): ++ first = {'one': {'inner': 'ORIGINAL', 'inner2': 'ORIGINAL'}, ++ 'two': {'inner': 'ORIGINAL', 'inner2': 'ORIGINAL'}} ++ second = {'one': {'inner': 'UPDATE'}, 'two': {'inner': 'UPDATE'}} ++ ++ merge_dicts(first, second) ++ self.assertEqual(first['one']['inner'], 'UPDATE') ++ self.assertEqual(first['one']['inner2'], 'ORIGINAL') ++ ++ self.assertEqual(first['two']['inner'], 'UPDATE') ++ self.assertEqual(first['two']['inner2'], 'ORIGINAL') ++ ++ def test_new_keys(self): ++ first = {'one': {'inner': 'ORIGINAL'}, 'two': {'inner': 'ORIGINAL'}} ++ second = {'three': {'foo': {'bar': 'baz'}}} ++ # In this case, second has no keys in common, but we'd still expect ++ # this to get merged. ++ merge_dicts(first, second) ++ self.assertEqual(first['three']['foo']['bar'], 'baz') ++ ++ def test_list_values_no_append(self): ++ dict1 = {'Foo': ['old_foo_value']} ++ dict2 = {'Foo': ['new_foo_value']} ++ merge_dicts(dict1, dict2) ++ self.assertEqual( ++ dict1, {'Foo': ['new_foo_value']}) ++ ++ def test_list_values_append(self): ++ dict1 = {'Foo': ['old_foo_value']} ++ dict2 = {'Foo': ['new_foo_value']} ++ merge_dicts(dict1, dict2, append_lists=True) ++ self.assertEqual( ++ dict1, {'Foo': ['old_foo_value', 'new_foo_value']}) ++ ++ def test_list_values_mismatching_types(self): ++ dict1 = {'Foo': 'old_foo_value'} ++ dict2 = {'Foo': ['new_foo_value']} ++ merge_dicts(dict1, dict2, append_lists=True) ++ self.assertEqual( ++ dict1, {'Foo': ['new_foo_value']}) ++ ++ def test_list_values_missing_key(self): ++ dict1 = {} ++ dict2 = {'Foo': ['foo_value']} ++ merge_dicts(dict1, dict2, append_lists=True) ++ self.assertEqual( ++ dict1, {'Foo': ['foo_value']}) ++ ++ ++class TestLowercaseDict(unittest.TestCase): ++ def test_lowercase_dict_empty(self): ++ original = {} ++ copy = lowercase_dict(original) ++ self.assertEqual(original, copy) ++ ++ def test_lowercase_dict_original_keys_lower(self): ++ original = { ++ 'lower_key1': 1, ++ 'lower_key2': 2, ++ } ++ copy = lowercase_dict(original) ++ self.assertEqual(original, copy) ++ ++ def test_lowercase_dict_original_keys_mixed(self): ++ original = { ++ 'SOME_KEY': 'value', ++ 'AnOTher_OnE': 'anothervalue', ++ } ++ copy = lowercase_dict(original) ++ expected = { ++ 'some_key': 'value', ++ 'another_one': 'anothervalue', ++ } ++ self.assertEqual(expected, copy) ++ ++ ++class TestGetServiceModuleName(unittest.TestCase): ++ def setUp(self): ++ self.service_description = { ++ 'metadata': { ++ 'serviceFullName': 'AWS MyService', ++ 'apiVersion': '2014-01-01', ++ 'endpointPrefix': 'myservice', ++ 'signatureVersion': 'v4', ++ 'protocol': 'query' ++ }, ++ 'operations': {}, ++ 'shapes': {}, ++ } ++ self.service_model = ServiceModel( ++ self.service_description, 'myservice') ++ ++ def test_default(self): ++ self.assertEqual( ++ get_service_module_name(self.service_model), ++ 'MyService' ++ ) ++ ++ def test_client_name_with_amazon(self): ++ self.service_description['metadata']['serviceFullName'] = ( ++ 'Amazon MyService') ++ self.assertEqual( ++ get_service_module_name(self.service_model), ++ 'MyService' ++ ) ++ ++ def test_client_name_using_abreviation(self): ++ self.service_description['metadata']['serviceAbbreviation'] = ( ++ 'Abbreviation') ++ self.assertEqual( ++ get_service_module_name(self.service_model), ++ 'Abbreviation' ++ ) ++ ++ def test_client_name_with_non_alphabet_characters(self): ++ self.service_description['metadata']['serviceFullName'] = ( ++ 'Amazon My-Service') ++ self.assertEqual( ++ get_service_module_name(self.service_model), ++ 'MyService' ++ ) ++ ++ def test_client_name_with_no_full_name_or_abbreviation(self): ++ del self.service_description['metadata']['serviceFullName'] ++ self.assertEqual( ++ get_service_module_name(self.service_model), ++ 'myservice' ++ ) ++ ++ ++class TestPercentEncodeSequence(unittest.TestCase): ++ def test_percent_encode_empty(self): ++ self.assertEqual(percent_encode_sequence({}), '') ++ ++ def test_percent_encode_special_chars(self): ++ self.assertEqual( ++ percent_encode_sequence({'k1': 'with spaces++/'}), ++ 'k1=with%20spaces%2B%2B%2F') ++ ++ def test_percent_encode_string_string_tuples(self): ++ self.assertEqual(percent_encode_sequence([('k1', 'v1'), ('k2', 'v2')]), ++ 'k1=v1&k2=v2') ++ ++ def test_percent_encode_dict_single_pair(self): ++ self.assertEqual(percent_encode_sequence({'k1': 'v1'}), 'k1=v1') ++ ++ def test_percent_encode_dict_string_string(self): ++ self.assertEqual( ++ percent_encode_sequence(OrderedDict([('k1', 'v1'), ('k2', 'v2')])), ++ 'k1=v1&k2=v2') ++ ++ def test_percent_encode_single_list_of_values(self): ++ self.assertEqual(percent_encode_sequence({'k1': ['a', 'b', 'c']}), ++ 'k1=a&k1=b&k1=c') ++ ++ def test_percent_encode_list_values_of_string(self): ++ self.assertEqual( ++ percent_encode_sequence( ++ OrderedDict([('k1', ['a', 'list']), ++ ('k2', ['another', 'list'])])), ++ 'k1=a&k1=list&k2=another&k2=list') ++ ++class TestPercentEncode(unittest.TestCase): ++ def test_percent_encode_obj(self): ++ self.assertEqual(percent_encode(1), '1') ++ ++ def test_percent_encode_text(self): ++ self.assertEqual(percent_encode(u''), '') ++ self.assertEqual(percent_encode(u'a'), 'a') ++ self.assertEqual(percent_encode(u'\u0000'), '%00') ++ # Codepoint > 0x7f ++ self.assertEqual(percent_encode(u'\u2603'), '%E2%98%83') ++ # Codepoint > 0xffff ++ self.assertEqual(percent_encode(u'\U0001f32e'), '%F0%9F%8C%AE') ++ ++ def test_percent_encode_bytes(self): ++ self.assertEqual(percent_encode(b''), '') ++ self.assertEqual(percent_encode(b'a'), u'a') ++ self.assertEqual(percent_encode(b'\x00'), u'%00') ++ # UTF-8 Snowman ++ self.assertEqual(percent_encode(b'\xe2\x98\x83'), '%E2%98%83') ++ # Arbitrary bytes (not valid UTF-8). ++ self.assertEqual(percent_encode(b'\x80\x00'), '%80%00') ++ ++class TestSwitchHostS3Accelerate(unittest.TestCase): ++ def setUp(self): ++ self.original_url = 'https://s3.amazonaws.com/foo/key.txt' ++ self.request = AWSRequest( ++ method='PUT', headers={}, ++ url=self.original_url ++ ) ++ self.client_config = Config() ++ self.request.context['client_config'] = self.client_config ++ ++ def test_switch_host(self): ++ switch_host_s3_accelerate(self.request, 'PutObject') ++ self.assertEqual( ++ self.request.url, ++ 'https://s3-accelerate.amazonaws.com/foo/key.txt') ++ ++ def test_do_not_switch_black_listed_operations(self): ++ # It should not get switched for ListBuckets, DeleteBucket, and ++ # CreateBucket ++ blacklist_ops = [ ++ 'ListBuckets', ++ 'DeleteBucket', ++ 'CreateBucket' ++ ] ++ for op_name in blacklist_ops: ++ switch_host_s3_accelerate(self.request, op_name) ++ self.assertEqual(self.request.url, self.original_url) ++ ++ def test_uses_original_endpoint_scheme(self): ++ self.request.url = 'http://s3.amazonaws.com/foo/key.txt' ++ switch_host_s3_accelerate(self.request, 'PutObject') ++ self.assertEqual( ++ self.request.url, ++ 'http://s3-accelerate.amazonaws.com/foo/key.txt') ++ ++ def test_uses_dualstack(self): ++ self.client_config.s3 = {'use_dualstack_endpoint': True} ++ self.original_url = 'https://s3.dualstack.amazonaws.com/foo/key.txt' ++ self.request = AWSRequest( ++ method='PUT', headers={}, ++ url=self.original_url ++ ) ++ self.request.context['client_config'] = self.client_config ++ switch_host_s3_accelerate(self.request, 'PutObject') ++ self.assertEqual( ++ self.request.url, ++ 'https://s3-accelerate.dualstack.amazonaws.com/foo/key.txt') ++ ++ ++class TestDeepMerge(unittest.TestCase): ++ def test_simple_merge(self): ++ a = {'key': 'value'} ++ b = {'otherkey': 'othervalue'} ++ deep_merge(a, b) ++ ++ expected = {'key': 'value', 'otherkey': 'othervalue'} ++ self.assertEqual(a, expected) ++ ++ def test_merge_list(self): ++ # Lists are treated as opaque data and so no effort should be made to ++ # combine them. ++ a = {'key': ['original']} ++ b = {'key': ['new']} ++ deep_merge(a, b) ++ self.assertEqual(a, {'key': ['new']}) ++ ++ def test_merge_number(self): ++ # The value from b is always taken ++ a = {'key': 10} ++ b = {'key': 45} ++ deep_merge(a, b) ++ self.assertEqual(a, {'key': 45}) ++ ++ a = {'key': 45} ++ b = {'key': 10} ++ deep_merge(a, b) ++ self.assertEqual(a, {'key': 10}) ++ ++ def test_merge_boolean(self): ++ # The value from b is always taken ++ a = {'key': False} ++ b = {'key': True} ++ deep_merge(a, b) ++ self.assertEqual(a, {'key': True}) ++ ++ a = {'key': True} ++ b = {'key': False} ++ deep_merge(a, b) ++ self.assertEqual(a, {'key': False}) ++ ++ def test_merge_string(self): ++ a = {'key': 'value'} ++ b = {'key': 'othervalue'} ++ deep_merge(a, b) ++ self.assertEqual(a, {'key': 'othervalue'}) ++ ++ def test_merge_overrides_value(self): ++ # The value from b is always taken, even when it's a different type ++ a = {'key': 'original'} ++ b = {'key': {'newkey': 'newvalue'}} ++ deep_merge(a, b) ++ self.assertEqual(a, {'key': {'newkey': 'newvalue'}}) ++ ++ a = {'key': {'anotherkey': 'value'}} ++ b = {'key': 'newvalue'} ++ deep_merge(a, b) ++ self.assertEqual(a, {'key': 'newvalue'}) ++ ++ def test_deep_merge(self): ++ a = { ++ 'first': { ++ 'second': { ++ 'key': 'value', ++ 'otherkey': 'othervalue' ++ }, ++ 'key': 'value' ++ } ++ } ++ b = { ++ 'first': { ++ 'second': { ++ 'otherkey': 'newvalue', ++ 'yetanotherkey': 'yetanothervalue' ++ } ++ } ++ } ++ deep_merge(a, b) ++ ++ expected = { ++ 'first': { ++ 'second': { ++ 'key': 'value', ++ 'otherkey': 'newvalue', ++ 'yetanotherkey': 'yetanothervalue' ++ }, ++ 'key': 'value' ++ } ++ } ++ self.assertEqual(a, expected) ++ ++ ++class TestS3RegionRedirector(unittest.TestCase): ++ def setUp(self): ++ self.endpoint_bridge = mock.Mock() ++ self.endpoint_bridge.resolve.return_value = { ++ 'endpoint_url': 'https://eu-central-1.amazonaws.com' ++ } ++ self.client = mock.Mock() ++ self.cache = {} ++ self.redirector = S3RegionRedirector(self.endpoint_bridge, self.client) ++ self.set_client_response_headers({}) ++ self.operation = mock.Mock() ++ self.operation.name = 'foo' ++ ++ def set_client_response_headers(self, headers): ++ error_response = ClientError({ ++ 'Error': { ++ 'Code': '', ++ 'Message': '' ++ }, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': headers ++ } ++ }, 'HeadBucket') ++ success_response = { ++ 'ResponseMetadata': { ++ 'HTTPHeaders': headers ++ } ++ } ++ self.client.head_bucket.side_effect = [ ++ error_response, success_response] ++ ++ def test_set_request_url(self): ++ params = {'url': 'https://us-west-2.amazonaws.com/foo'} ++ context = {'signing': { ++ 'endpoint': 'https://eu-central-1.amazonaws.com' ++ }} ++ self.redirector.set_request_url(params, context) ++ self.assertEqual( ++ params['url'], 'https://eu-central-1.amazonaws.com/foo') ++ ++ def test_only_changes_request_url_if_endpoint_present(self): ++ params = {'url': 'https://us-west-2.amazonaws.com/foo'} ++ context = {} ++ self.redirector.set_request_url(params, context) ++ self.assertEqual( ++ params['url'], 'https://us-west-2.amazonaws.com/foo') ++ ++ def test_set_request_url_keeps_old_scheme(self): ++ params = {'url': 'http://us-west-2.amazonaws.com/foo'} ++ context = {'signing': { ++ 'endpoint': 'https://eu-central-1.amazonaws.com' ++ }} ++ self.redirector.set_request_url(params, context) ++ self.assertEqual( ++ params['url'], 'http://eu-central-1.amazonaws.com/foo') ++ ++ def test_sets_signing_context_from_cache(self): ++ signing_context = {'endpoint': 'bar'} ++ self.cache['foo'] = signing_context ++ self.redirector = S3RegionRedirector( ++ self.endpoint_bridge, self.client, cache=self.cache) ++ params = {'Bucket': 'foo'} ++ context = {} ++ self.redirector.redirect_from_cache(params, context) ++ self.assertEqual(context.get('signing'), signing_context) ++ ++ def test_only_changes_context_if_bucket_in_cache(self): ++ signing_context = {'endpoint': 'bar'} ++ self.cache['bar'] = signing_context ++ self.redirector = S3RegionRedirector( ++ self.endpoint_bridge, self.client, cache=self.cache) ++ params = {'Bucket': 'foo'} ++ context = {} ++ self.redirector.redirect_from_cache(params, context) ++ self.assertNotEqual(context.get('signing'), signing_context) ++ ++ def test_redirect_from_error(self): ++ request_dict = { ++ 'context': {'signing': {'bucket': 'foo'}}, ++ 'url': 'https://us-west-2.amazonaws.com/foo' ++ } ++ response = (None, { ++ 'Error': { ++ 'Code': 'PermanentRedirect', ++ 'Endpoint': 'foo.eu-central-1.amazonaws.com', ++ 'Bucket': 'foo' ++ }, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {'x-amz-bucket-region': 'eu-central-1'} ++ } ++ }) ++ ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ ++ # The response needs to be 0 so that there is no retry delay ++ self.assertEqual(redirect_response, 0) ++ ++ self.assertEqual( ++ request_dict['url'], 'https://eu-central-1.amazonaws.com/foo') ++ ++ expected_signing_context = { ++ 'endpoint': 'https://eu-central-1.amazonaws.com', ++ 'bucket': 'foo', ++ 'region': 'eu-central-1' ++ } ++ signing_context = request_dict['context'].get('signing') ++ self.assertEqual(signing_context, expected_signing_context) ++ self.assertTrue(request_dict['context'].get('s3_redirected')) ++ ++ def test_does_not_redirect_if_previously_redirected(self): ++ request_dict = { ++ 'context': { ++ 'signing': {'bucket': 'foo', 'region': 'us-west-2'}, ++ 's3_redirected': True, ++ }, ++ 'url': 'https://us-west-2.amazonaws.com/foo' ++ } ++ response = (None, { ++ 'Error': { ++ 'Code': '400', ++ 'Message': 'Bad Request', ++ }, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {'x-amz-bucket-region': 'us-west-2'} ++ } ++ }) ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ self.assertIsNone(redirect_response) ++ ++ def test_does_not_redirect_unless_permanentredirect_recieved(self): ++ request_dict = {} ++ response = (None, {}) ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ self.assertIsNone(redirect_response) ++ self.assertEqual(request_dict, {}) ++ ++ def test_does_not_redirect_if_region_cannot_be_found(self): ++ request_dict = {'url': 'https://us-west-2.amazonaws.com/foo', ++ 'context': {'signing': {'bucket': 'foo'}}} ++ response = (None, { ++ 'Error': { ++ 'Code': 'PermanentRedirect', ++ 'Endpoint': 'foo.eu-central-1.amazonaws.com', ++ 'Bucket': 'foo' ++ }, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {} ++ } ++ }) ++ ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ ++ self.assertIsNone(redirect_response) ++ ++ def test_redirects_301(self): ++ request_dict = {'url': 'https://us-west-2.amazonaws.com/foo', ++ 'context': {'signing': {'bucket': 'foo'}}} ++ response = (None, { ++ 'Error': { ++ 'Code': '301', ++ 'Message': 'Moved Permanently' ++ }, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {'x-amz-bucket-region': 'eu-central-1'} ++ } ++ }) ++ ++ self.operation.name = 'HeadObject' ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ self.assertEqual(redirect_response, 0) ++ ++ self.operation.name = 'ListObjects' ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ self.assertIsNone(redirect_response) ++ ++ def test_redirects_400_head_bucket(self): ++ request_dict = {'url': 'https://us-west-2.amazonaws.com/foo', ++ 'context': {'signing': {'bucket': 'foo'}}} ++ response = (None, { ++ 'Error': {'Code': '400', 'Message': 'Bad Request'}, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {'x-amz-bucket-region': 'eu-central-1'} ++ } ++ }) ++ ++ self.operation.name = 'HeadObject' ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ self.assertEqual(redirect_response, 0) ++ ++ self.operation.name = 'ListObjects' ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ self.assertIsNone(redirect_response) ++ ++ def test_does_not_redirect_400_head_bucket_no_region_header(self): ++ # We should not redirect a 400 Head* if the region header is not ++ # present as this will lead to infinitely calling HeadBucket. ++ request_dict = {'url': 'https://us-west-2.amazonaws.com/foo', ++ 'context': {'signing': {'bucket': 'foo'}}} ++ response = (None, { ++ 'Error': {'Code': '400', 'Message': 'Bad Request'}, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {} ++ } ++ }) ++ ++ self.operation.name = 'HeadBucket' ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ head_bucket_calls = self.client.head_bucket.call_count ++ self.assertIsNone(redirect_response) ++ # We should not have made an additional head bucket call ++ self.assertEqual(head_bucket_calls, 0) ++ ++ def test_does_not_redirect_if_None_response(self): ++ request_dict = {'url': 'https://us-west-2.amazonaws.com/foo', ++ 'context': {'signing': {'bucket': 'foo'}}} ++ response = None ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ self.assertIsNone(redirect_response) ++ ++ def test_get_region_from_response(self): ++ response = (None, { ++ 'Error': { ++ 'Code': 'PermanentRedirect', ++ 'Endpoint': 'foo.eu-central-1.amazonaws.com', ++ 'Bucket': 'foo' ++ }, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {'x-amz-bucket-region': 'eu-central-1'} ++ } ++ }) ++ region = self.redirector.get_bucket_region('foo', response) ++ self.assertEqual(region, 'eu-central-1') ++ ++ def test_get_region_from_response_error_body(self): ++ response = (None, { ++ 'Error': { ++ 'Code': 'PermanentRedirect', ++ 'Endpoint': 'foo.eu-central-1.amazonaws.com', ++ 'Bucket': 'foo', ++ 'Region': 'eu-central-1' ++ }, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {} ++ } ++ }) ++ region = self.redirector.get_bucket_region('foo', response) ++ self.assertEqual(region, 'eu-central-1') ++ ++ def test_get_region_from_head_bucket_error(self): ++ self.set_client_response_headers( ++ {'x-amz-bucket-region': 'eu-central-1'}) ++ response = (None, { ++ 'Error': { ++ 'Code': 'PermanentRedirect', ++ 'Endpoint': 'foo.eu-central-1.amazonaws.com', ++ 'Bucket': 'foo', ++ }, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {} ++ } ++ }) ++ region = self.redirector.get_bucket_region('foo', response) ++ self.assertEqual(region, 'eu-central-1') ++ ++ def test_get_region_from_head_bucket_success(self): ++ success_response = { ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {'x-amz-bucket-region': 'eu-central-1'} ++ } ++ } ++ self.client.head_bucket.side_effect = None ++ self.client.head_bucket.return_value = success_response ++ response = (None, { ++ 'Error': { ++ 'Code': 'PermanentRedirect', ++ 'Endpoint': 'foo.eu-central-1.amazonaws.com', ++ 'Bucket': 'foo', ++ }, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {} ++ } ++ }) ++ region = self.redirector.get_bucket_region('foo', response) ++ self.assertEqual(region, 'eu-central-1') ++ ++ def test_no_redirect_from_error_for_accesspoint(self): ++ request_dict = { ++ 'url': ( ++ 'https://myendpoint-123456789012.s3-accesspoint.' ++ 'us-west-2.amazonaws.com/key' ++ ), ++ 'context': { ++ 's3_accesspoint': {} ++ } ++ } ++ response = (None, { ++ 'Error': {'Code': '400', 'Message': 'Bad Request'}, ++ 'ResponseMetadata': { ++ 'HTTPHeaders': {'x-amz-bucket-region': 'eu-central-1'} ++ } ++ }) ++ ++ self.operation.name = 'HeadObject' ++ redirect_response = self.redirector.redirect_from_error( ++ request_dict, response, self.operation) ++ self.assertEqual(redirect_response, None) ++ ++ def test_no_redirect_from_cache_for_accesspoint(self): ++ self.cache['foo'] = {'endpoint': 'foo-endpoint'} ++ self.redirector = S3RegionRedirector( ++ self.endpoint_bridge, self.client, cache=self.cache) ++ params = {'Bucket': 'foo'} ++ context = {'s3_accesspoint': {}} ++ self.redirector.redirect_from_cache(params, context) ++ self.assertNotIn('signing', context) ++ ++ ++class TestArnParser(unittest.TestCase): ++ def setUp(self): ++ self.parser = ArnParser() ++ ++ def test_parse(self): ++ arn = 'arn:aws:s3:us-west-2:1023456789012:myresource' ++ self.assertEqual( ++ self.parser.parse_arn(arn), ++ { ++ 'partition': 'aws', ++ 'service': 's3', ++ 'region': 'us-west-2', ++ 'account': '1023456789012', ++ 'resource': 'myresource', ++ } ++ ) ++ ++ def test_parse_invalid_arn(self): ++ with self.assertRaises(InvalidArnException): ++ self.parser.parse_arn('arn:aws:s3') ++ ++ def test_parse_arn_with_resource_type(self): ++ arn = 'arn:aws:s3:us-west-2:1023456789012:bucket_name:mybucket' ++ self.assertEqual( ++ self.parser.parse_arn(arn), ++ { ++ 'partition': 'aws', ++ 'service': 's3', ++ 'region': 'us-west-2', ++ 'account': '1023456789012', ++ 'resource': 'bucket_name:mybucket', ++ } ++ ) ++ ++ def test_parse_arn_with_empty_elements(self): ++ arn = 'arn:aws:s3:::mybucket' ++ self.assertEqual( ++ self.parser.parse_arn(arn), ++ { ++ 'partition': 'aws', ++ 'service': 's3', ++ 'region': '', ++ 'account': '', ++ 'resource': 'mybucket', ++ } ++ ) ++ ++ ++class TestS3ArnParamHandler(unittest.TestCase): ++ def setUp(self): ++ self.arn_handler = S3ArnParamHandler() ++ self.model = mock.Mock(OperationModel) ++ self.model.name = 'GetObject' ++ ++ def test_register(self): ++ event_emitter = mock.Mock() ++ self.arn_handler.register(event_emitter) ++ event_emitter.register.assert_called_with( ++ 'before-parameter-build.s3', self.arn_handler.handle_arn) ++ ++ def test_accesspoint_arn(self): ++ params = { ++ 'Bucket': 'arn:aws:s3:us-west-2:123456789012:accesspoint/endpoint' ++ } ++ context = {} ++ self.arn_handler.handle_arn(params, self.model, context) ++ self.assertEqual(params, {'Bucket': 'endpoint'}) ++ self.assertEqual( ++ context, ++ { ++ 's3_accesspoint': { ++ 'name': 'endpoint', ++ 'account': '123456789012', ++ 'region': 'us-west-2', ++ 'partition': 'aws', ++ 'service': 's3', ++ } ++ } ++ ) ++ ++ def test_accesspoint_arn_with_colon(self): ++ params = { ++ 'Bucket': 'arn:aws:s3:us-west-2:123456789012:accesspoint:endpoint' ++ } ++ context = {} ++ self.arn_handler.handle_arn(params, self.model, context) ++ self.assertEqual(params, {'Bucket': 'endpoint'}) ++ self.assertEqual( ++ context, ++ { ++ 's3_accesspoint': { ++ 'name': 'endpoint', ++ 'account': '123456789012', ++ 'region': 'us-west-2', ++ 'partition': 'aws', ++ 'service': 's3', ++ } ++ } ++ ) ++ ++ def test_errors_for_non_accesspoint_arn(self): ++ params = { ++ 'Bucket': 'arn:aws:s3:us-west-2:123456789012:unsupported:resource' ++ } ++ context = {} ++ with self.assertRaises(UnsupportedS3ArnError): ++ self.arn_handler.handle_arn(params, self.model, context) ++ ++ def test_outpost_arn_with_colon(self): ++ params = { ++ 'Bucket': ( ++ 'arn:aws:s3-outposts:us-west-2:123456789012:outpost:' ++ 'op-01234567890123456:accesspoint:myaccesspoint' ++ ) ++ } ++ context = {} ++ self.arn_handler.handle_arn(params, self.model, context) ++ self.assertEqual(params, {'Bucket': 'myaccesspoint'}) ++ self.assertEqual( ++ context, ++ { ++ 's3_accesspoint': { ++ 'name': 'myaccesspoint', ++ 'outpost_name': 'op-01234567890123456', ++ 'account': '123456789012', ++ 'region': 'us-west-2', ++ 'partition': 'aws', ++ 'service': 's3-outposts', ++ } ++ } ++ ) ++ ++ def test_outpost_arn_with_slash(self): ++ params = { ++ 'Bucket': ( ++ 'arn:aws:s3-outposts:us-west-2:123456789012:outpost/' ++ 'op-01234567890123456/accesspoint/myaccesspoint' ++ ) ++ } ++ context = {} ++ self.arn_handler.handle_arn(params, self.model, context) ++ self.assertEqual(params, {'Bucket': 'myaccesspoint'}) ++ self.assertEqual( ++ context, ++ { ++ 's3_accesspoint': { ++ 'name': 'myaccesspoint', ++ 'outpost_name': 'op-01234567890123456', ++ 'account': '123456789012', ++ 'region': 'us-west-2', ++ 'partition': 'aws', ++ 'service': 's3-outposts', ++ } ++ } ++ ) ++ ++ def test_outpost_arn_errors_for_missing_fields(self): ++ params = { ++ 'Bucket': 'arn:aws:s3-outposts:us-west-2:123456789012:outpost/' ++ 'op-01234567890123456/accesspoint' ++ } ++ with self.assertRaises(UnsupportedOutpostResourceError): ++ self.arn_handler.handle_arn(params, self.model, {}) ++ ++ def test_outpost_arn_errors_for_empty_fields(self): ++ params = { ++ 'Bucket': 'arn:aws:s3-outposts:us-west-2:123456789012:outpost/' ++ '/accesspoint/myaccesspoint' ++ } ++ with self.assertRaises(UnsupportedOutpostResourceError): ++ self.arn_handler.handle_arn(params, self.model, {}) ++ ++ def test_ignores_bucket_names(self): ++ params = {'Bucket': 'mybucket'} ++ context = {} ++ self.arn_handler.handle_arn(params, self.model, context) ++ self.assertEqual(params, {'Bucket': 'mybucket'}) ++ self.assertEqual(context, {}) ++ ++ def test_ignores_create_bucket(self): ++ arn = 'arn:aws:s3:us-west-2:123456789012:accesspoint/endpoint' ++ params = {'Bucket': arn} ++ context = {} ++ self.model.name = 'CreateBucket' ++ self.arn_handler.handle_arn(params, self.model, context) ++ self.assertEqual(params, {'Bucket': arn}) ++ self.assertEqual(context, {}) ++ ++ ++class TestS3EndpointSetter(unittest.TestCase): ++ def setUp(self): ++ self.operation_name = 'GetObject' ++ self.signature_version = 's3v4' ++ self.region_name = 'us-west-2' ++ self.service = 's3' ++ self.account = '123456789012' ++ self.bucket = 'mybucket' ++ self.key = 'key.txt' ++ self.accesspoint_name = 'myaccesspoint' ++ self.outpost_name = 'op-123456789012' ++ self.partition = 'aws' ++ self.endpoint_resolver = mock.Mock() ++ self.dns_suffix = 'amazonaws.com' ++ self.endpoint_resolver.construct_endpoint.return_value = { ++ 'dnsSuffix': self.dns_suffix ++ } ++ self.endpoint_setter = self.get_endpoint_setter() ++ ++ def get_endpoint_setter(self, **kwargs): ++ setter_kwargs = { ++ 'endpoint_resolver': self.endpoint_resolver, ++ 'region': self.region_name, ++ } ++ setter_kwargs.update(kwargs) ++ return S3EndpointSetter(**setter_kwargs) ++ ++ def get_s3_request(self, bucket=None, key=None, scheme='https://', ++ querystring=None): ++ url = scheme + 's3.us-west-2.amazonaws.com/' ++ if bucket: ++ url += bucket ++ if key: ++ url += '/%s' % key ++ if querystring: ++ url += '?%s' % querystring ++ return AWSRequest(method='GET', headers={}, url=url) ++ ++ def get_s3_outpost_request(self, **s3_request_kwargs): ++ request = self.get_s3_request( ++ self.accesspoint_name, **s3_request_kwargs) ++ accesspoint_context = self.get_s3_accesspoint_context( ++ name=self.accesspoint_name, outpost_name=self.outpost_name) ++ request.context['s3_accesspoint'] = accesspoint_context ++ return request ++ ++ def get_s3_accesspoint_request(self, accesspoint_name=None, ++ accesspoint_context=None, ++ **s3_request_kwargs): ++ if not accesspoint_name: ++ accesspoint_name = self.accesspoint_name ++ request = self.get_s3_request(accesspoint_name, **s3_request_kwargs) ++ if accesspoint_context is None: ++ accesspoint_context = self.get_s3_accesspoint_context( ++ name=accesspoint_name) ++ request.context['s3_accesspoint'] = accesspoint_context ++ return request ++ ++ def get_s3_accesspoint_context(self, **overrides): ++ accesspoint_context = { ++ 'name': self.accesspoint_name, ++ 'account': self.account, ++ 'region': self.region_name, ++ 'partition': self.partition, ++ 'service': self.service, ++ } ++ accesspoint_context.update(overrides) ++ return accesspoint_context ++ ++ def call_set_endpoint(self, endpoint_setter, request, **kwargs): ++ set_endpoint_kwargs = { ++ 'request': request, ++ 'operation_name': self.operation_name, ++ 'signature_version': self.signature_version, ++ 'region_name': self.region_name, ++ } ++ set_endpoint_kwargs.update(kwargs) ++ endpoint_setter.set_endpoint(**set_endpoint_kwargs) ++ ++ def test_register(self): ++ event_emitter = mock.Mock() ++ self.endpoint_setter.register(event_emitter) ++ event_emitter.register.assert_called_with( ++ 'before-sign.s3', self.endpoint_setter.set_endpoint) ++ ++ def test_outpost_endpoint(self): ++ request = self.get_s3_outpost_request() ++ self.call_set_endpoint(self.endpoint_setter, request=request) ++ expected_url = 'https://%s-%s.%s.s3-outposts.%s.amazonaws.com/' % ( ++ self.accesspoint_name, self.account, self.outpost_name, ++ self.region_name, ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_outpost_endpoint_preserves_key_in_path(self): ++ request = self.get_s3_outpost_request(key=self.key) ++ self.call_set_endpoint(self.endpoint_setter, request=request) ++ expected_url = 'https://%s-%s.%s.s3-outposts.%s.amazonaws.com/%s' % ( ++ self.accesspoint_name, self.account, self.outpost_name, ++ self.region_name, self.key ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_accesspoint_endpoint(self): ++ request = self.get_s3_accesspoint_request() ++ self.call_set_endpoint(self.endpoint_setter, request=request) ++ expected_url = 'https://%s-%s.s3-accesspoint.%s.amazonaws.com/' % ( ++ self.accesspoint_name, self.account, self.region_name ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_accesspoint_preserves_key_in_path(self): ++ request = self.get_s3_accesspoint_request(key=self.key) ++ self.call_set_endpoint(self.endpoint_setter, request=request) ++ expected_url = 'https://%s-%s.s3-accesspoint.%s.amazonaws.com/%s' % ( ++ self.accesspoint_name, self.account, self.region_name, ++ self.key ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_accesspoint_preserves_scheme(self): ++ request = self.get_s3_accesspoint_request(scheme='http://') ++ self.call_set_endpoint(self.endpoint_setter, request=request) ++ expected_url = 'http://%s-%s.s3-accesspoint.%s.amazonaws.com/' % ( ++ self.accesspoint_name, self.account, self.region_name, ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_accesspoint_preserves_query_string(self): ++ request = self.get_s3_accesspoint_request(querystring='acl') ++ self.call_set_endpoint(self.endpoint_setter, request=request) ++ expected_url = 'https://%s-%s.s3-accesspoint.%s.amazonaws.com/?acl' % ( ++ self.accesspoint_name, self.account, self.region_name, ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_uses_resolved_dns_suffix(self): ++ self.endpoint_resolver.construct_endpoint.return_value = { ++ 'dnsSuffix': 'mysuffix.com' ++ } ++ request = self.get_s3_accesspoint_request() ++ self.call_set_endpoint(self.endpoint_setter, request=request) ++ expected_url = 'https://%s-%s.s3-accesspoint.%s.mysuffix.com/' % ( ++ self.accesspoint_name, self.account, self.region_name, ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_uses_region_of_client_if_use_arn_disabled(self): ++ client_region = 'client-region' ++ self.endpoint_setter = self.get_endpoint_setter( ++ region=client_region, s3_config={'use_arn_region': False}) ++ request = self.get_s3_accesspoint_request() ++ self.call_set_endpoint(self.endpoint_setter, request=request) ++ expected_url = 'https://%s-%s.s3-accesspoint.%s.amazonaws.com/' % ( ++ self.accesspoint_name, self.account, client_region, ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_accesspoint_errors_for_custom_endpoint(self): ++ endpoint_setter = self.get_endpoint_setter( ++ endpoint_url='https://custom.com') ++ request = self.get_s3_accesspoint_request() ++ with self.assertRaises(UnsupportedS3AccesspointConfigurationError): ++ self.call_set_endpoint(endpoint_setter, request=request) ++ ++ def test_errors_for_mismatching_partition(self): ++ endpoint_setter = self.get_endpoint_setter(partition='aws-cn') ++ accesspoint_context = self.get_s3_accesspoint_context(partition='aws') ++ request = self.get_s3_accesspoint_request( ++ accesspoint_context=accesspoint_context) ++ with self.assertRaises(UnsupportedS3AccesspointConfigurationError): ++ self.call_set_endpoint(endpoint_setter, request=request) ++ ++ def test_errors_for_mismatching_partition_when_using_client_region(self): ++ endpoint_setter = self.get_endpoint_setter( ++ s3_config={'use_arn_region': False}, partition='aws-cn' ++ ) ++ accesspoint_context = self.get_s3_accesspoint_context(partition='aws') ++ request = self.get_s3_accesspoint_request( ++ accesspoint_context=accesspoint_context) ++ with self.assertRaises(UnsupportedS3AccesspointConfigurationError): ++ self.call_set_endpoint(endpoint_setter, request=request) ++ ++ def test_set_endpoint_for_auto(self): ++ endpoint_setter = self.get_endpoint_setter( ++ s3_config={'addressing_style': 'auto'}) ++ request = self.get_s3_request(self.bucket, self.key) ++ self.call_set_endpoint(endpoint_setter, request) ++ expected_url = 'https://%s.s3.us-west-2.amazonaws.com/%s' % ( ++ self.bucket, self.key ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_set_endpoint_for_virtual(self): ++ endpoint_setter = self.get_endpoint_setter( ++ s3_config={'addressing_style': 'virtual'}) ++ request = self.get_s3_request(self.bucket, self.key) ++ self.call_set_endpoint(endpoint_setter, request) ++ expected_url = 'https://%s.s3.us-west-2.amazonaws.com/%s' % ( ++ self.bucket, self.key ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_set_endpoint_for_path(self): ++ endpoint_setter = self.get_endpoint_setter( ++ s3_config={'addressing_style': 'path'}) ++ request = self.get_s3_request(self.bucket, self.key) ++ self.call_set_endpoint(endpoint_setter, request) ++ expected_url = 'https://s3.us-west-2.amazonaws.com/%s/%s' % ( ++ self.bucket, self.key ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ def test_set_endpoint_for_accelerate(self): ++ endpoint_setter = self.get_endpoint_setter( ++ s3_config={'use_accelerate_endpoint': True}) ++ request = self.get_s3_request(self.bucket, self.key) ++ self.call_set_endpoint(endpoint_setter, request) ++ expected_url = 'https://%s.s3-accelerate.amazonaws.com/%s' % ( ++ self.bucket, self.key ++ ) ++ self.assertEqual(request.url, expected_url) ++ ++ ++class TestContainerMetadataFetcher(unittest.TestCase): ++ def setUp(self): ++ self.responses = [] ++ self.http = mock.Mock() ++ self.sleep = mock.Mock() ++ ++ def create_fetcher(self): ++ return ContainerMetadataFetcher(self.http, sleep=self.sleep) ++ ++ def fake_response(self, status_code, body): ++ response = mock.Mock() ++ response.status_code = status_code ++ response.content = body ++ return response ++ ++ def set_http_responses_to(self, *responses): ++ http_responses = [] ++ for response in responses: ++ if isinstance(response, Exception): ++ # Simulating an error condition. ++ http_response = response ++ elif hasattr(response, 'status_code'): ++ # It's a precreated fake_response. ++ http_response = response ++ else: ++ http_response = self.fake_response( ++ status_code=200, body=json.dumps(response).encode('utf-8')) ++ http_responses.append(http_response) ++ self.http.send.side_effect = http_responses ++ ++ def assert_request(self, method, url, headers): ++ request = self.http.send.call_args[0][0] ++ self.assertEqual(request.method, method) ++ self.assertEqual(request.url, url) ++ self.assertEqual(request.headers, headers) ++ ++ def assert_can_retrieve_metadata_from(self, full_uri): ++ response_body = {'foo': 'bar'} ++ self.set_http_responses_to(response_body) ++ fetcher = self.create_fetcher() ++ response = fetcher.retrieve_full_uri(full_uri) ++ self.assertEqual(response, response_body) ++ self.assert_request('GET', full_uri, {'Accept': 'application/json'}) ++ ++ def assert_host_is_not_allowed(self, full_uri): ++ response_body = {'foo': 'bar'} ++ self.set_http_responses_to(response_body) ++ fetcher = self.create_fetcher() ++ with self.assertRaisesRegexp(ValueError, 'Unsupported host'): ++ fetcher.retrieve_full_uri(full_uri) ++ self.assertFalse(self.http.send.called) ++ ++ def test_can_specify_extra_headers_are_merged(self): ++ headers = { ++ # The 'Accept' header will override the ++ # default Accept header of application/json. ++ 'Accept': 'application/not-json', ++ 'X-Other-Header': 'foo', ++ } ++ self.set_http_responses_to({'foo': 'bar'}) ++ fetcher = self.create_fetcher() ++ response = fetcher.retrieve_full_uri( ++ 'http://localhost', headers) ++ self.assert_request('GET', 'http://localhost', headers) ++ ++ def test_can_retrieve_uri(self): ++ json_body = { ++ "AccessKeyId" : "a", ++ "SecretAccessKey" : "b", ++ "Token" : "c", ++ "Expiration" : "d" ++ } ++ self.set_http_responses_to(json_body) ++ ++ fetcher = self.create_fetcher() ++ response = fetcher.retrieve_uri('/foo?id=1') ++ ++ self.assertEqual(response, json_body) ++ # Ensure we made calls to the right endpoint. ++ headers = {'Accept': 'application/json'} ++ self.assert_request('GET', 'http://169.254.170.2/foo?id=1', headers) ++ ++ def test_can_retry_requests(self): ++ success_response = { ++ "AccessKeyId" : "a", ++ "SecretAccessKey" : "b", ++ "Token" : "c", ++ "Expiration" : "d" ++ } ++ self.set_http_responses_to( ++ # First response is a connection error, should ++ # be retried. ++ ConnectionClosedError(endpoint_url=''), ++ # Second response is the successful JSON response ++ # with credentials. ++ success_response, ++ ) ++ fetcher = self.create_fetcher() ++ response = fetcher.retrieve_uri('/foo?id=1') ++ self.assertEqual(response, success_response) ++ ++ def test_propagates_credential_error_on_http_errors(self): ++ self.set_http_responses_to( ++ # In this scenario, we never get a successful response. ++ ConnectionClosedError(endpoint_url=''), ++ ConnectionClosedError(endpoint_url=''), ++ ConnectionClosedError(endpoint_url=''), ++ ConnectionClosedError(endpoint_url=''), ++ ConnectionClosedError(endpoint_url=''), ++ ) ++ # As a result, we expect an appropriate error to be raised. ++ fetcher = self.create_fetcher() ++ with self.assertRaises(MetadataRetrievalError): ++ fetcher.retrieve_uri('/foo?id=1') ++ self.assertEqual(self.http.send.call_count, fetcher.RETRY_ATTEMPTS) ++ ++ def test_error_raised_on_non_200_response(self): ++ self.set_http_responses_to( ++ self.fake_response(status_code=404, body=b'Error not found'), ++ self.fake_response(status_code=404, body=b'Error not found'), ++ self.fake_response(status_code=404, body=b'Error not found'), ++ ) ++ fetcher = self.create_fetcher() ++ with self.assertRaises(MetadataRetrievalError): ++ fetcher.retrieve_uri('/foo?id=1') ++ # Should have tried up to RETRY_ATTEMPTS. ++ self.assertEqual(self.http.send.call_count, fetcher.RETRY_ATTEMPTS) ++ ++ def test_error_raised_on_no_json_response(self): ++ # If the service returns a sucess response but with a body that ++ # does not contain JSON, we should still retry up to RETRY_ATTEMPTS, ++ # but after exhausting retries we propagate the exception. ++ self.set_http_responses_to( ++ self.fake_response(status_code=200, body=b'Not JSON'), ++ self.fake_response(status_code=200, body=b'Not JSON'), ++ self.fake_response(status_code=200, body=b'Not JSON'), ++ ) ++ fetcher = self.create_fetcher() ++ with self.assertRaises(MetadataRetrievalError) as e: ++ fetcher.retrieve_uri('/foo?id=1') ++ self.assertNotIn('Not JSON', str(e.exception)) ++ # Should have tried up to RETRY_ATTEMPTS. ++ self.assertEqual(self.http.send.call_count, fetcher.RETRY_ATTEMPTS) ++ ++ def test_can_retrieve_full_uri_with_fixed_ip(self): ++ self.assert_can_retrieve_metadata_from( ++ 'http://%s/foo?id=1' % ContainerMetadataFetcher.IP_ADDRESS) ++ ++ def test_localhost_http_is_allowed(self): ++ self.assert_can_retrieve_metadata_from('http://localhost/foo') ++ ++ def test_localhost_with_port_http_is_allowed(self): ++ self.assert_can_retrieve_metadata_from('http://localhost:8000/foo') ++ ++ def test_localhost_https_is_allowed(self): ++ self.assert_can_retrieve_metadata_from('https://localhost/foo') ++ ++ def test_can_use_127_ip_addr(self): ++ self.assert_can_retrieve_metadata_from('https://127.0.0.1/foo') ++ ++ def test_can_use_127_ip_addr_with_port(self): ++ self.assert_can_retrieve_metadata_from('https://127.0.0.1:8080/foo') ++ ++ def test_link_local_http_is_not_allowed(self): ++ self.assert_host_is_not_allowed('http://169.254.0.1/foo') ++ ++ def test_link_local_https_is_not_allowed(self): ++ self.assert_host_is_not_allowed('https://169.254.0.1/foo') ++ ++ def test_non_link_local_nonallowed_url(self): ++ self.assert_host_is_not_allowed('http://169.1.2.3/foo') ++ ++ def test_error_raised_on_nonallowed_url(self): ++ self.assert_host_is_not_allowed('http://somewhere.com/foo') ++ ++ def test_external_host_not_allowed_if_https(self): ++ self.assert_host_is_not_allowed('https://somewhere.com/foo') ++ ++ ++class TestUnsigned(unittest.TestCase): ++ def test_copy_returns_same_object(self): ++ self.assertIs(botocore.UNSIGNED, copy.copy(botocore.UNSIGNED)) ++ ++ def test_deepcopy_returns_same_object(self): ++ self.assertIs(botocore.UNSIGNED, copy.deepcopy(botocore.UNSIGNED)) ++ ++ ++class TestInstanceMetadataFetcher(unittest.TestCase): ++ def setUp(self): ++ urllib3_session_send = 'botocore.httpsession.URLLib3Session.send' ++ self._urllib3_patch = mock.patch(urllib3_session_send) ++ self._send = self._urllib3_patch.start() ++ self._imds_responses = [] ++ self._send.side_effect = self.get_imds_response ++ self._role_name = 'role-name' ++ self._creds = { ++ 'AccessKeyId': 'spam', ++ 'SecretAccessKey': 'eggs', ++ 'Token': 'spam-token', ++ 'Expiration': 'something', ++ } ++ self._expected_creds = { ++ 'access_key': self._creds['AccessKeyId'], ++ 'secret_key': self._creds['SecretAccessKey'], ++ 'token': self._creds['Token'], ++ 'expiry_time': self._creds['Expiration'], ++ 'role_name': self._role_name ++ } ++ ++ def tearDown(self): ++ self._urllib3_patch.stop() ++ ++ def add_imds_response(self, body, status_code=200): ++ response = botocore.awsrequest.AWSResponse( ++ url='http://169.254.169.254/', ++ status_code=status_code, ++ headers={}, ++ raw=RawResponse(body) ++ ) ++ self._imds_responses.append(response) ++ ++ def add_get_role_name_imds_response(self, role_name=None): ++ if role_name is None: ++ role_name = self._role_name ++ self.add_imds_response(body=role_name.encode('utf-8')) ++ ++ def add_get_credentials_imds_response(self, creds=None): ++ if creds is None: ++ creds = self._creds ++ self.add_imds_response(body=json.dumps(creds).encode('utf-8')) ++ ++ def add_get_token_imds_response(self, token, status_code=200): ++ self.add_imds_response(body=token.encode('utf-8'), ++ status_code=status_code) ++ ++ def add_metadata_token_not_supported_response(self): ++ self.add_imds_response(b'', status_code=404) ++ ++ def add_imds_connection_error(self, exception): ++ self._imds_responses.append(exception) ++ ++ def get_imds_response(self, request): ++ response = self._imds_responses.pop(0) ++ if isinstance(response, Exception): ++ raise response ++ return response ++ ++ def test_disabled_by_environment(self): ++ env = {'AWS_EC2_METADATA_DISABLED': 'true'} ++ fetcher = InstanceMetadataFetcher(env=env) ++ result = fetcher.retrieve_iam_role_credentials() ++ self.assertEqual(result, {}) ++ self._send.assert_not_called() ++ ++ def test_disabled_by_environment_mixed_case(self): ++ env = {'AWS_EC2_METADATA_DISABLED': 'tRuE'} ++ fetcher = InstanceMetadataFetcher(env=env) ++ result = fetcher.retrieve_iam_role_credentials() ++ self.assertEqual(result, {}) ++ self._send.assert_not_called() ++ ++ def test_disabling_env_var_not_true(self): ++ url = 'https://example.com/' ++ env = {'AWS_EC2_METADATA_DISABLED': 'false'} ++ ++ self.add_get_token_imds_response(token='token') ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ ++ fetcher = InstanceMetadataFetcher(base_url=url, env=env) ++ result = fetcher.retrieve_iam_role_credentials() ++ ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_includes_user_agent_header(self): ++ user_agent = 'my-user-agent' ++ self.add_get_token_imds_response(token='token') ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ ++ InstanceMetadataFetcher( ++ user_agent=user_agent).retrieve_iam_role_credentials() ++ ++ self.assertEqual(self._send.call_count, 3) ++ for call in self._send.calls: ++ self.assertTrue(call[0][0].headers['User-Agent'], user_agent) ++ ++ def test_non_200_response_for_role_name_is_retried(self): ++ # Response for role name that have a non 200 status code should ++ # be retried. ++ self.add_get_token_imds_response(token='token') ++ self.add_imds_response( ++ status_code=429, body=b'{"message": "Slow down"}') ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ result = InstanceMetadataFetcher( ++ num_attempts=2).retrieve_iam_role_credentials() ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_http_connection_error_for_role_name_is_retried(self): ++ # Connection related errors should be retried ++ self.add_get_token_imds_response(token='token') ++ self.add_imds_connection_error(ConnectionClosedError(endpoint_url='')) ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ result = InstanceMetadataFetcher( ++ num_attempts=2).retrieve_iam_role_credentials() ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_empty_response_for_role_name_is_retried(self): ++ # Response for role name that have a non 200 status code should ++ # be retried. ++ self.add_get_token_imds_response(token='token') ++ self.add_imds_response(body=b'') ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ result = InstanceMetadataFetcher( ++ num_attempts=2).retrieve_iam_role_credentials() ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_non_200_response_is_retried(self): ++ self.add_get_token_imds_response(token='token') ++ self.add_get_role_name_imds_response() ++ # Response for creds that has a 200 status code but has an empty ++ # body should be retried. ++ self.add_imds_response( ++ status_code=429, body=b'{"message": "Slow down"}') ++ self.add_get_credentials_imds_response() ++ result = InstanceMetadataFetcher( ++ num_attempts=2).retrieve_iam_role_credentials() ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_http_connection_errors_is_retried(self): ++ self.add_get_token_imds_response(token='token') ++ self.add_get_role_name_imds_response() ++ # Connection related errors should be retried ++ self.add_imds_connection_error(ConnectionClosedError(endpoint_url='')) ++ self.add_get_credentials_imds_response() ++ result = InstanceMetadataFetcher( ++ num_attempts=2).retrieve_iam_role_credentials() ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_empty_response_is_retried(self): ++ self.add_get_token_imds_response(token='token') ++ self.add_get_role_name_imds_response() ++ # Response for creds that has a 200 status code but is empty. ++ # This should be retried. ++ self.add_imds_response(body=b'') ++ self.add_get_credentials_imds_response() ++ result = InstanceMetadataFetcher( ++ num_attempts=2).retrieve_iam_role_credentials() ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_invalid_json_is_retried(self): ++ self.add_get_token_imds_response(token='token') ++ self.add_get_role_name_imds_response() ++ # Response for creds that has a 200 status code but is invalid JSON. ++ # This should be retried. ++ self.add_imds_response(body=b'{"AccessKey":') ++ self.add_get_credentials_imds_response() ++ result = InstanceMetadataFetcher( ++ num_attempts=2).retrieve_iam_role_credentials() ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_exhaust_retries_on_role_name_request(self): ++ self.add_get_token_imds_response(token='token') ++ self.add_imds_response(status_code=400, body=b'') ++ result = InstanceMetadataFetcher( ++ num_attempts=1).retrieve_iam_role_credentials() ++ self.assertEqual(result, {}) ++ ++ def test_exhaust_retries_on_credentials_request(self): ++ self.add_get_token_imds_response(token='token') ++ self.add_get_role_name_imds_response() ++ self.add_imds_response(status_code=400, body=b'') ++ result = InstanceMetadataFetcher( ++ num_attempts=1).retrieve_iam_role_credentials() ++ self.assertEqual(result, {}) ++ ++ def test_missing_fields_in_credentials_response(self): ++ self.add_get_token_imds_response(token='token') ++ self.add_get_role_name_imds_response() ++ # Response for creds that has a 200 status code and a JSON body ++ # representing an error. We do not necessarily want to retry this. ++ self.add_imds_response( ++ body=b'{"Code":"AssumeRoleUnauthorizedAccess","Message":"error"}') ++ result = InstanceMetadataFetcher().retrieve_iam_role_credentials() ++ self.assertEqual(result, {}) ++ ++ def test_token_is_included(self): ++ user_agent = 'my-user-agent' ++ self.add_get_token_imds_response(token='token') ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ ++ result = InstanceMetadataFetcher( ++ user_agent=user_agent).retrieve_iam_role_credentials() ++ ++ # Check that subsequent calls after getting the token include the token. ++ self.assertEqual(self._send.call_count, 3) ++ for call in self._send.call_args_list[1:]: ++ self.assertEqual(call[0][0].headers['x-aws-ec2-metadata-token'], 'token') ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_metadata_token_not_supported_404(self): ++ user_agent = 'my-user-agent' ++ self.add_imds_response(b'', status_code=404) ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ ++ result = InstanceMetadataFetcher( ++ user_agent=user_agent).retrieve_iam_role_credentials() ++ ++ for call in self._send.call_args_list[1:]: ++ self.assertNotIn('x-aws-ec2-metadata-token', call[0][0].headers) ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_metadata_token_not_supported_403(self): ++ user_agent = 'my-user-agent' ++ self.add_imds_response(b'', status_code=403) ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ ++ result = InstanceMetadataFetcher( ++ user_agent=user_agent).retrieve_iam_role_credentials() ++ ++ for call in self._send.call_args_list[1:]: ++ self.assertNotIn('x-aws-ec2-metadata-token', call[0][0].headers) ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_metadata_token_not_supported_405(self): ++ user_agent = 'my-user-agent' ++ self.add_imds_response(b'', status_code=405) ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ ++ result = InstanceMetadataFetcher( ++ user_agent=user_agent).retrieve_iam_role_credentials() ++ ++ for call in self._send.call_args_list[1:]: ++ self.assertNotIn('x-aws-ec2-metadata-token', call[0][0].headers) ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_metadata_token_not_supported_timeout(self): ++ user_agent = 'my-user-agent' ++ self.add_imds_connection_error(ReadTimeoutError(endpoint_url='url')) ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ ++ result = InstanceMetadataFetcher( ++ user_agent=user_agent).retrieve_iam_role_credentials() ++ ++ for call in self._send.call_args_list[1:]: ++ self.assertNotIn('x-aws-ec2-metadata-token', call[0][0].headers) ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_token_not_supported_exhaust_retries(self): ++ user_agent = 'my-user-agent' ++ self.add_imds_connection_error(ConnectTimeoutError(endpoint_url='url')) ++ self.add_get_role_name_imds_response() ++ self.add_get_credentials_imds_response() ++ ++ result = InstanceMetadataFetcher( ++ user_agent=user_agent).retrieve_iam_role_credentials() ++ ++ for call in self._send.call_args_list[1:]: ++ self.assertNotIn('x-aws-ec2-metadata-token', call[0][0].headers) ++ self.assertEqual(result, self._expected_creds) ++ ++ def test_metadata_token_bad_request_yields_no_credentials(self): ++ user_agent = 'my-user-agent' ++ self.add_imds_response(b'', status_code=400) ++ result = InstanceMetadataFetcher( ++ user_agent=user_agent).retrieve_iam_role_credentials() ++ self.assertEqual(result, {}) ++ ++ ++class TestSSOTokenLoader(unittest.TestCase): ++ def setUp(self): ++ super(TestSSOTokenLoader, self).setUp() ++ self.start_url = 'https://d-abc123.awsapps.com/start' ++ self.cache_key = '40a89917e3175433e361b710a9d43528d7f1890a' ++ self.access_token = 'totally.a.token' ++ self.cached_token = { ++ 'accessToken': self.access_token, ++ 'expiresAt': '2002-10-18T03:52:38UTC' ++ } ++ self.cache = {} ++ self.loader = SSOTokenLoader(cache=self.cache) ++ ++ def test_can_load_token_exists(self): ++ self.cache[self.cache_key] = self.cached_token ++ access_token = self.loader(self.start_url) ++ self.assertEqual(self.access_token, access_token) ++ ++ def test_can_handle_does_not_exist(self): ++ with self.assertRaises(SSOTokenLoadError): ++ access_token = self.loader(self.start_url) ++ ++ def test_can_handle_invalid_cache(self): ++ self.cache[self.cache_key] = {} ++ with self.assertRaises(SSOTokenLoadError): ++ access_token = self.loader(self.start_url) +diff -Nru botocore-1.18.15.orig/tests/unit/test_waiters.py botocore-1.18.15/tests/unit/test_waiters.py +--- botocore-1.18.15.orig/tests/unit/test_waiters.py 2020-10-08 20:05:12.000000000 +0200 ++++ botocore-1.18.15/tests/unit/test_waiters.py 2020-10-09 10:13:49.544472317 +0200 +@@ -13,7 +13,7 @@ + import os + from tests import unittest, BaseEnvVar +-import mock ++from tests import mock + + import botocore from botocore.compat import six - from botocore.awsrequest import AWSRequest +@@ -389,7 +389,7 @@ + ) + waiter = Waiter('MyWaiter', config, operation_method) + +- with self.assertRaisesRegexp(WaiterError, error_message): ++ with six.assertRaisesRegex(self, WaiterError, error_message): + waiter.wait() + + def test_waiter_transitions_to_failure_state(self):