diff options
author | Jordan Cook <jordan.cook@pioneer.com> | 2021-04-22 17:16:42 -0500 |
---|---|---|
committer | Jordan Cook <jordan.cook@pioneer.com> | 2021-04-22 20:55:23 -0500 |
commit | 6e1679f7fb53a7edd2133f3a5ab2d8ca40ddccdb (patch) | |
tree | 32f3626643cc6b1e5102a6e627e8142d5dfd912a | |
parent | f2bf9bbfd8d711e9dfc8ccce83f439d765d484cb (diff) | |
download | requests-cache-6e1679f7fb53a7edd2133f3a5ab2d8ca40ddccdb.tar.gz |
Combine test_cache.py with BaseCacheTest to run these tests for all backends
-rw-r--r-- | .github/workflows/build.yml | 2 | ||||
-rw-r--r-- | tests/conftest.py | 17 | ||||
-rw-r--r-- | tests/integration/base_cache_test.py | 133 | ||||
-rw-r--r-- | tests/integration/base_storage_test.py (renamed from tests/integration/test_backends.py) | 48 | ||||
-rw-r--r-- | tests/integration/test_cache.py | 96 | ||||
-rw-r--r-- | tests/integration/test_dynamodb.py | 3 | ||||
-rw-r--r-- | tests/integration/test_filesystem.py | 3 | ||||
-rw-r--r-- | tests/integration/test_gridfs.py | 3 | ||||
-rw-r--r-- | tests/integration/test_mongodb.py | 3 | ||||
-rw-r--r-- | tests/integration/test_redis.py | 3 | ||||
-rw-r--r-- | tests/integration/test_sqlite.py | 3 |
11 files changed, 166 insertions, 148 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 36e2423..10f778d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,7 +72,7 @@ jobs: # Run longer stress tests if this is a release or merge to master - name: Run stress tests if: startsWith(github.ref, 'refs/tags/v') || endsWith(github.ref, '/master') - run: STRESS_TEST_MULTIPLIER=5 pytest tests/integration/test_thread_safety.py + run: STRESS_TEST_MULTIPLIER=5 pytest tests/integration/ -k 'multithreaded' # Run code analysis checks analyze: diff --git a/tests/conftest.py b/tests/conftest.py index a918bc7..c01e066 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,6 +29,23 @@ STRESS_TEST_MULTIPLIER = int(os.getenv('STRESS_TEST_MULTIPLIER', '1')) N_THREADS = 2 * STRESS_TEST_MULTIPLIER N_ITERATIONS = 4 * STRESS_TEST_MULTIPLIER +HTTPBIN_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] +HTTPBIN_FORMATS = [ + 'brotli', + 'deflate', + 'deny', + 'encoding/utf8', + 'gzip', + 'html', + 'image/jpeg', + 'image/png', + 'image/svg', + 'image/webp', + 'json', + 'robots.txt', + 'xml', +] + MOCKED_URL = 'http+mock://requests-cache.com/text' MOCKED_URL_HTTPS = 'https+mock://requests-cache.com/text' MOCKED_URL_JSON = 'http+mock://requests-cache.com/json' diff --git a/tests/integration/base_cache_test.py b/tests/integration/base_cache_test.py new file mode 100644 index 0000000..7745cc4 --- /dev/null +++ b/tests/integration/base_cache_test.py @@ -0,0 +1,133 @@ +"""Common tests to run for all backends (BaseCache subclasses)""" +import json +import pytest +from threading import Thread +from time import time +from typing import Dict, Type + +from requests_cache import ALL_METHODS, CachedResponse, CachedSession +from requests_cache.backends.base import BaseCache +from tests.conftest import ( + CACHE_NAME, + HTTPBIN_FORMATS, + HTTPBIN_METHODS, + N_ITERATIONS, + N_THREADS, + USE_PYTEST_HTTPBIN, + httpbin, +) + + +class BaseCacheTest: + """Base class for testing cache backend classes""" + + backend_class: Type[BaseCache] = None + init_kwargs: Dict = {} + + def init_session(self, clear=True, **kwargs) -> CachedSession: + kwargs.update({'allowable_methods': ALL_METHODS, 'suppress_warnings': True}) + backend = self.backend_class(CACHE_NAME, **self.init_kwargs, **kwargs) + if clear: + backend.redirects.clear() + backend.responses.clear() + + return CachedSession(backend=backend, **self.init_kwargs, **kwargs) + + @pytest.mark.parametrize('method', HTTPBIN_METHODS) + @pytest.mark.parametrize('field', ['params', 'data', 'json']) + def test_all_methods(self, field, method): + """Test all relevant combinations of methods and data fields. Requests with different request + params, data, or json should be cached under different keys. + """ + url = httpbin(method.lower()) + session = self.init_session() + for params in [{'param_1': 1}, {'param_1': 2}, {'param_2': 2}]: + assert session.request(method, url, **{field: params}).from_cache is False + assert session.request(method, url, **{field: params}).from_cache is True + + @pytest.mark.parametrize('response_format', HTTPBIN_FORMATS) + def test_all_response_formats(self, response_format): + """Test that all relevant response formats are cached correctly""" + session = self.init_session() + # Temporary workaround for this issue: https://github.com/kevin1024/pytest-httpbin/issues/60 + if response_format == 'json' and USE_PYTEST_HTTPBIN: + session.allowable_codes = (200, 404) + + r1 = session.get(httpbin(response_format)) + r2 = session.get(httpbin(response_format)) + assert r1.from_cache is False + assert r2.from_cache is True + assert r1.content == r2.content + + @pytest.mark.parametrize('n_redirects', range(1, 5)) + @pytest.mark.parametrize('endpoint', ['redirect', 'absolute-redirect', 'relative-redirect']) + def test_redirects(self, endpoint, n_redirects): + """Test all types of redirect endpoints with different numbers of consecutive redirects""" + session = self.init_session() + session.get(httpbin(f'redirect/{n_redirects}')) + r2 = session.get(httpbin('get')) + + assert r2.from_cache is True + assert len(session.cache.redirects) == n_redirects + + def test_cookies(self): + session = self.init_session() + + def get_json(url): + return json.loads(session.get(url).text) + + response_1 = get_json(httpbin('cookies/set/test1/test2')) + with session.cache_disabled(): + assert get_json(httpbin('cookies')) == response_1 + # From cache + response_2 = get_json(httpbin('cookies')) + assert response_2 == get_json(httpbin('cookies')) + # Not from cache + with session.cache_disabled(): + response_3 = get_json(httpbin('cookies/set/test3/test4')) + assert response_3 == get_json(httpbin('cookies')) + + def test_response_decode(self): + """Test that a gzip-compressed raw response can be manually uncompressed with decode_content""" + session = self.init_session() + response = session.get(httpbin('gzip')) + assert b'gzipped' in response.content + + cached = CachedResponse(response) + assert b'gzipped' in cached.content + assert b'gzipped' in cached.raw.read(None, decode_content=True) + + def test_response_decode_stream(self): + """Test that streamed gzip-compressed responses can be uncompressed with decode_content""" + session = self.init_session() + response_uncached = session.get(httpbin('gzip'), stream=True) + response_cached = session.get(httpbin('gzip'), stream=True) + + for res in (response_uncached, response_cached): + assert b'gzipped' in res.content + assert b'gzipped' in res.raw.read(None, decode_content=True) + + @pytest.mark.parametrize('iteration', range(N_ITERATIONS)) + def test_multithreaded(self, iteration): + """Run a multi-threaded stress test for each backend""" + session = self.init_session() + start = time() + url = httpbin('anything') + + def send_requests(): + for i in range(N_ITERATIONS): + session.get(url, params={f'key_{i}': f'value_{i}'}) + + threads = [Thread(target=send_requests) for i in range(N_THREADS)] + for t in threads: + t.start() + for t in threads: + t.join() + + elapsed = time() - start + average = (elapsed * 1000) / (N_ITERATIONS * N_THREADS) + print(f'{self.backend_class}: Ran {N_ITERATIONS} iterations with {N_THREADS} threads each in {elapsed} s') + print(f'Average time per request: {average} ms') + + for i in range(N_ITERATIONS): + assert session.cache.has_url(f'{url}?key_{i}=value_{i}') diff --git a/tests/integration/test_backends.py b/tests/integration/base_storage_test.py index 9849a6b..0679e9c 100644 --- a/tests/integration/test_backends.py +++ b/tests/integration/base_storage_test.py @@ -1,11 +1,9 @@ +"""Common tests to run for all backends (BaseStorage subclasses)""" import pytest -from threading import Thread -from time import time from typing import Dict, Type -from requests_cache.backends.base import BaseCache, BaseStorage -from requests_cache.session import CachedSession -from tests.conftest import CACHE_NAME, N_ITERATIONS, N_THREADS, httpbin +from requests_cache.backends.base import BaseStorage +from tests.conftest import CACHE_NAME class BaseStorageTest: @@ -124,43 +122,3 @@ class BaseStorageTest: class Picklable: attr_1 = 'value_1' attr_2 = 'value_2' - - -class BaseCacheTest: - """Base class for testing cache backend classes""" - - backend_class: Type[BaseCache] = None - init_kwargs: Dict = {} - - def init_backend(self, clear=True, **kwargs): - kwargs['suppress_warnings'] = True - backend = self.backend_class(CACHE_NAME, **self.init_kwargs, **kwargs) - if clear: - backend.redirects.clear() - backend.responses.clear() - return backend - - @pytest.mark.parametrize('iteration', range(N_ITERATIONS)) - def test_caching_with_threads(self, iteration): - """Run a multi-threaded stress test for each backend""" - start = time() - session = CachedSession(backend=self.init_backend()) - url = httpbin('anything') - - def send_requests(): - for i in range(N_ITERATIONS): - session.get(url, params={f'key_{i}': f'value_{i}'}) - - threads = [Thread(target=send_requests) for i in range(N_THREADS)] - for t in threads: - t.start() - for t in threads: - t.join() - - elapsed = time() - start - average = (elapsed * 1000) / (N_ITERATIONS * N_THREADS) - print(f'{self.backend_class}: Ran {N_ITERATIONS} iterations with {N_THREADS} threads each in {elapsed} s') - print(f'Average time per request: {average} ms') - - for i in range(N_ITERATIONS): - assert session.cache.has_url(f'{url}?key_{i}=value_{i}') diff --git a/tests/integration/test_cache.py b/tests/integration/test_cache.py deleted file mode 100644 index 4b2fb33..0000000 --- a/tests/integration/test_cache.py +++ /dev/null @@ -1,96 +0,0 @@ -"""CachedSession tests that hit a containerized httpbin service""" -import json -import pytest - -from requests_cache import CachedResponse -from tests.conftest import USE_PYTEST_HTTPBIN, httpbin - -HTTPBIN_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] -HTTPBIN_FORMATS = [ - 'brotli', - 'deflate', - 'deny', - 'encoding/utf8', - 'gzip', - 'html', - 'image/jpeg', - 'image/png', - 'image/svg', - 'image/webp', - 'json', - 'robots.txt', - 'xml', -] - - -@pytest.mark.parametrize('method', HTTPBIN_METHODS) -@pytest.mark.parametrize('field', ['params', 'data', 'json']) -def test_all_methods(field, method, tempfile_session): - """Test all relevant combinations of methods and data fields. Requests with different request - params, data, or json should be cached under different keys. - """ - url = httpbin(method.lower()) - for params in [{'param_1': 1}, {'param_1': 2}, {'param_2': 2}]: - assert tempfile_session.request(method, url, **{field: params}).from_cache is False - assert tempfile_session.request(method, url, **{field: params}).from_cache is True - - -@pytest.mark.parametrize('response_format', HTTPBIN_FORMATS) -def test_all_response_formats(response_format, tempfile_session): - """Test that all relevant response formats are cached correctly""" - # Temporary workaround for this issue: https://github.com/kevin1024/pytest-httpbin/issues/60 - if response_format == 'json' and USE_PYTEST_HTTPBIN: - tempfile_session.allowable_codes = (200, 404) - - r1 = tempfile_session.get(httpbin(response_format)) - r2 = tempfile_session.get(httpbin(response_format)) - assert r1.from_cache is False - assert r2.from_cache is True - assert r1.content == r2.content - - -@pytest.mark.parametrize('n_redirects', range(1, 5)) -@pytest.mark.parametrize('endpoint', ['redirect', 'absolute-redirect', 'relative-redirect']) -def test_redirects(endpoint, n_redirects, mock_session): - """Test all types of redirect endpoints with different numbers of consecutive redirects""" - mock_session.get(httpbin(f'redirect/{n_redirects}')) - r2 = mock_session.get(httpbin('get')) - - assert r2.from_cache is True - assert len(mock_session.cache.redirects) == n_redirects - - -def test_cookies(tempfile_session): - def get_json(url): - return json.loads(tempfile_session.get(url).text) - - response_1 = get_json(httpbin('cookies/set/test1/test2')) - with tempfile_session.cache_disabled(): - assert get_json(httpbin('cookies')) == response_1 - # From cache - response_2 = get_json(httpbin('cookies')) - assert response_2 == get_json(httpbin('cookies')) - # Not from cache - with tempfile_session.cache_disabled(): - response_3 = get_json(httpbin('cookies/set/test3/test4')) - assert response_3 == get_json(httpbin('cookies')) - - -def test_response_decode(tempfile_session): - """Test that a gzip-compressed raw response can be manually uncompressed with decode_content""" - response = tempfile_session.get(httpbin('gzip')) - assert b'gzipped' in response.content - - cached = CachedResponse(response) - assert b'gzipped' in cached.content - assert b'gzipped' in cached.raw.read(None, decode_content=True) - - -def test_response_decode_stream(tempfile_session): - """Test that streamed gzip-compressed responses can be uncompressed with decode_content""" - response_uncached = tempfile_session.get(httpbin('gzip'), stream=True) - response_cached = tempfile_session.get(httpbin('gzip'), stream=True) - - for res in (response_uncached, response_cached): - assert b'gzipped' in res.content - assert b'gzipped' in res.raw.read(None, decode_content=True) diff --git a/tests/integration/test_dynamodb.py b/tests/integration/test_dynamodb.py index 951d9c8..c6a175e 100644 --- a/tests/integration/test_dynamodb.py +++ b/tests/integration/test_dynamodb.py @@ -3,7 +3,8 @@ from unittest.mock import patch from requests_cache.backends import DynamoDbCache, DynamoDbDict from tests.conftest import AWS_OPTIONS, fail_if_no_connection -from tests.integration.test_backends import BaseCacheTest, BaseStorageTest +from tests.integration.base_cache_test import BaseCacheTest +from tests.integration.base_storage_test import BaseStorageTest # Run this test module last, since the DynamoDB container takes the longest to initialize pytestmark = pytest.mark.order(-1) diff --git a/tests/integration/test_filesystem.py b/tests/integration/test_filesystem.py index 0a3caec..7c329a4 100644 --- a/tests/integration/test_filesystem.py +++ b/tests/integration/test_filesystem.py @@ -2,7 +2,8 @@ from os.path import isfile from shutil import rmtree from requests_cache.backends import FileCache, FileDict -from tests.integration.test_backends import CACHE_NAME, BaseCacheTest, BaseStorageTest +from tests.integration.base_cache_test import BaseCacheTest +from tests.integration.base_storage_test import CACHE_NAME, BaseStorageTest class TestFileDict(BaseStorageTest): diff --git a/tests/integration/test_gridfs.py b/tests/integration/test_gridfs.py index d463824..c628337 100644 --- a/tests/integration/test_gridfs.py +++ b/tests/integration/test_gridfs.py @@ -5,7 +5,8 @@ from pymongo import MongoClient from requests_cache.backends import GridFSCache, GridFSPickleDict, get_valid_kwargs from tests.conftest import fail_if_no_connection -from tests.integration.test_backends import BaseCacheTest, BaseStorageTest +from tests.integration.base_cache_test import BaseCacheTest +from tests.integration.base_storage_test import BaseStorageTest @pytest.fixture(scope='module', autouse=True) diff --git a/tests/integration/test_mongodb.py b/tests/integration/test_mongodb.py index a186ee7..8e9cafe 100644 --- a/tests/integration/test_mongodb.py +++ b/tests/integration/test_mongodb.py @@ -5,7 +5,8 @@ from pymongo import MongoClient from requests_cache.backends import MongoCache, MongoDict, MongoPickleDict, get_valid_kwargs from tests.conftest import fail_if_no_connection -from tests.integration.test_backends import BaseCacheTest, BaseStorageTest +from tests.integration.base_cache_test import BaseCacheTest +from tests.integration.base_storage_test import BaseStorageTest @pytest.fixture(scope='module', autouse=True) diff --git a/tests/integration/test_redis.py b/tests/integration/test_redis.py index 899d2b6..8154b29 100644 --- a/tests/integration/test_redis.py +++ b/tests/integration/test_redis.py @@ -3,7 +3,8 @@ from unittest.mock import patch from requests_cache.backends.redis import RedisCache, RedisDict from tests.conftest import fail_if_no_connection -from tests.integration.test_backends import BaseCacheTest, BaseStorageTest +from tests.integration.base_cache_test import BaseCacheTest +from tests.integration.base_storage_test import BaseStorageTest @pytest.fixture(scope='module', autouse=True) diff --git a/tests/integration/test_sqlite.py b/tests/integration/test_sqlite.py index 63c67ec..fd463ac 100644 --- a/tests/integration/test_sqlite.py +++ b/tests/integration/test_sqlite.py @@ -3,7 +3,8 @@ from threading import Thread from unittest.mock import patch from requests_cache.backends.sqlite import DbCache, DbDict, DbPickleDict -from tests.integration.test_backends import CACHE_NAME, BaseCacheTest, BaseStorageTest +from tests.integration.base_cache_test import BaseCacheTest +from tests.integration.base_storage_test import CACHE_NAME, BaseStorageTest class SQLiteTestCase(BaseStorageTest): |