diff options
author | Jordan Cook <jordan.cook.git@proton.me> | 2023-03-24 20:58:33 -0500 |
---|---|---|
committer | Jordan Cook <jordan.cook.git@proton.me> | 2023-04-02 21:42:25 -0500 |
commit | b5d7d1546d953157ca7eb70838c6cb6f6f7651e3 (patch) | |
tree | 11ebbe68b4a7e29b8e6ee35381531ef8e185d3f3 /tests | |
parent | 8e4de3c35ebb8028d24dd1a874ec3e3348ed0b6a (diff) | |
download | requests-cache-b5d7d1546d953157ca7eb70838c6cb6f6f7651e3.tar.gz |
Use time-machine instead of sleep for timing-based tests
Diffstat (limited to 'tests')
-rw-r--r-- | tests/conftest.py | 11 | ||||
-rw-r--r-- | tests/unit/test_base_cache.py | 97 | ||||
-rw-r--r-- | tests/unit/test_session.py | 90 |
3 files changed, 121 insertions, 77 deletions
diff --git a/tests/conftest.py b/tests/conftest.py index c53d866..953ddb3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,7 +11,7 @@ https://requests-mock.readthedocs.io/en/latest/adapter.html import os import platform import warnings -from contextlib import contextmanager +from contextlib import contextmanager, nullcontext from datetime import datetime, timedelta from functools import wraps from importlib import import_module @@ -30,6 +30,13 @@ from timeout_decorator import timeout from requests_cache import ALL_METHODS, CachedSession, install_cache, uninstall_cache +# ignore missing time-travel library on PyPy +try: + from time_machine import travel as time_travel +except ImportError: + time_travel = nullcontext + + # Configure logging to show log output when tests fail (or with pytest -s) basicConfig( level='INFO', @@ -68,6 +75,8 @@ HTTPDATE_DATETIME = datetime(2021, 4, 16, 21, 13) EXPIRED_DT = datetime.utcnow() - timedelta(1) ETAG = '"644b5b0155e6404a9cc4bd9d8b1ae730"' LAST_MODIFIED = 'Thu, 05 Jul 2012 15:31:30 GMT' +START_DT = datetime.utcnow() +YESTERDAY = datetime.utcnow() - timedelta(days=1) MOCKED_URL = 'http+mock://requests-cache.com/text' MOCKED_URL_ETAG = 'http+mock://requests-cache.com/etag' diff --git a/tests/unit/test_base_cache.py b/tests/unit/test_base_cache.py index b57ed09..774ba8c 100644 --- a/tests/unit/test_base_cache.py +++ b/tests/unit/test_base_cache.py @@ -3,7 +3,6 @@ import pickle from datetime import datetime, timedelta from logging import getLogger from pickle import PickleError -from time import sleep from unittest.mock import patch import pytest @@ -19,12 +18,15 @@ from tests.conftest import ( MOCKED_URL_HTTPS, MOCKED_URL_JSON, MOCKED_URL_REDIRECT, + START_DT, + YESTERDAY, ignore_deprecation, mount_mock_adapter, patch_normalize_url, + skip_pypy, + time_travel, ) -YESTERDAY = datetime.utcnow() - timedelta(days=1) logger = getLogger(__name__) @@ -59,6 +61,7 @@ def test_contains__url(mock_session): assert not mock_session.cache.contains(url=f'{MOCKED_URL}?foo=bar') +@skip_pypy # time-machine doesn't work on PyPy @patch_normalize_url def test_delete__expired(mock_normalize_url, mock_session): unexpired_url = f'{MOCKED_URL}?x=1' @@ -66,49 +69,58 @@ def test_delete__expired(mock_normalize_url, mock_session): 'GET', unexpired_url, status_code=200, text='mock response' ) mock_session.settings.expire_after = 1 - mock_session.get(MOCKED_URL) - mock_session.get(MOCKED_URL_JSON) - sleep(1.1) + + with time_travel(START_DT): + mock_session.get(MOCKED_URL) + mock_session.get(MOCKED_URL_JSON) + mock_session.settings.expire_after = 2 - mock_session.get(unexpired_url) + with time_travel(START_DT + timedelta(seconds=1.1)): + mock_session.get(unexpired_url) # At this point we should have 1 unexpired response and 2 expired responses assert len(mock_session.cache.responses) == 3 # Use the generic BaseCache implementation, not the SQLite-specific one - BaseCache.delete(mock_session.cache, expired=True) + with time_travel(START_DT + timedelta(seconds=2)): + BaseCache.delete(mock_session.cache, expired=True) assert len(mock_session.cache.responses) == 1 + cached_response = list(mock_session.cache.responses.values())[0] assert cached_response.url == unexpired_url # Now the last response should be expired as well - sleep(2) - BaseCache.delete(mock_session.cache, expired=True) + with time_travel(START_DT + timedelta(seconds=4)): + BaseCache.delete(mock_session.cache, expired=True) assert len(mock_session.cache.responses) == 0 +@skip_pypy def test_delete__expired__per_request(mock_session): - # Cache 3 responses with different expiration times second_url = f'{MOCKED_URL}/endpoint_2' third_url = f'{MOCKED_URL}/endpoint_3' mock_session.mock_adapter.register_uri('GET', second_url, status_code=200) mock_session.mock_adapter.register_uri('GET', third_url, status_code=200) - mock_session.get(MOCKED_URL) - mock_session.get(second_url, expire_after=2) - mock_session.get(third_url, expire_after=4) - # All 3 responses should still be cached - mock_session.cache.delete(expired=True) - for response in mock_session.cache.responses.values(): - logger.info(f'Expires in {response.expires_delta} seconds') + # Cache 3 responses with different expiration times + with time_travel(START_DT): + mock_session.get(MOCKED_URL) + mock_session.get(second_url, expire_after=2) + mock_session.get(third_url, expire_after=4) + + # All 3 responses should still be cached + mock_session.cache.delete(expired=True) + for response in mock_session.cache.responses.values(): + logger.info(f'Expires in {response.expires_delta} seconds') + assert len(mock_session.cache.responses) == 3 # One should be expired after 2s, and another should be expired after 4s - sleep(2) - mock_session.cache.delete(expired=True) + with time_travel(START_DT + timedelta(seconds=2)): + mock_session.cache.delete(expired=True) assert len(mock_session.cache.responses) == 2 - sleep(2) - mock_session.cache.delete(expired=True) + with time_travel(START_DT + timedelta(seconds=4)): + mock_session.cache.delete(expired=True) assert len(mock_session.cache.responses) == 1 @@ -140,32 +152,35 @@ def test_delete__invalid(tempfile_path): assert mock_session.get(MOCKED_URL_JSON).from_cache is False +@skip_pypy def test_delete__older_than(mock_session): # Cache 4 responses with different creation times - response_0 = CachedResponse(request=CachedRequest(method='GET', url='https://test.com/test_0')) - mock_session.cache.save_response(response_0) - response_1 = CachedResponse(request=CachedRequest(method='GET', url='https://test.com/test_1')) - response_1.created_at -= timedelta(seconds=1) - mock_session.cache.save_response(response_1) - response_2 = CachedResponse(request=CachedRequest(method='GET', url='https://test.com/test_2')) - response_2.created_at -= timedelta(seconds=2) - mock_session.cache.save_response(response_2) - response_3 = CachedResponse(request=CachedRequest(method='GET', url='https://test.com/test_3')) - response_3.created_at -= timedelta(seconds=3) - mock_session.cache.save_response(response_3) + with time_travel(START_DT): + request = CachedRequest(method='GET', url='https://test.com/test_0') + mock_session.cache.save_response(CachedResponse(request=request)) + with time_travel(START_DT - timedelta(seconds=1)): + request = CachedRequest(method='GET', url='https://test.com/test_1') + mock_session.cache.save_response(CachedResponse(request=request)) + with time_travel(START_DT - timedelta(seconds=2)): + request = CachedRequest(method='GET', url='https://test.com/test_2') + mock_session.cache.save_response(CachedResponse(request=request)) + with time_travel(START_DT - timedelta(seconds=3)): + request = CachedRequest(method='GET', url='https://test.com/test_3') + mock_session.cache.save_response(CachedResponse(request=request)) # Incrementally remove responses older than 3, 2, and 1 seconds - assert len(mock_session.cache.responses) == 4 - mock_session.cache.delete(older_than=timedelta(seconds=3)) - assert len(mock_session.cache.responses) == 3 - mock_session.cache.delete(older_than=timedelta(seconds=2)) - assert len(mock_session.cache.responses) == 2 - mock_session.cache.delete(older_than=timedelta(seconds=1)) - assert len(mock_session.cache.responses) == 1 + with time_travel(START_DT): + assert len(mock_session.cache.responses) == 4 + mock_session.cache.delete(older_than=timedelta(seconds=3)) + assert len(mock_session.cache.responses) == 3 + mock_session.cache.delete(older_than=timedelta(seconds=2)) + assert len(mock_session.cache.responses) == 2 + mock_session.cache.delete(older_than=timedelta(seconds=1)) + assert len(mock_session.cache.responses) == 1 # Remove the last response after it's 1 second old - sleep(1) - mock_session.cache.delete(older_than=timedelta(seconds=1)) + with time_travel(START_DT + timedelta(seconds=1.1)): + mock_session.cache.delete(older_than=timedelta(seconds=1)) assert len(mock_session.cache.responses) == 0 diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py index 7ed2c1b..0c5b736 100644 --- a/tests/unit/test_session.py +++ b/tests/unit/test_session.py @@ -31,8 +31,11 @@ from tests.conftest import ( MOCKED_URL_REDIRECT, MOCKED_URL_REDIRECT_TARGET, MOCKED_URL_VARY, + START_DT, ignore_deprecation, patch_normalize_url, + skip_pypy, + time_travel, ) logger = getLogger(__name__) @@ -357,33 +360,39 @@ def test_cache_error(exception_cls, mock_session): assert mock_session.get(MOCKED_URL).from_cache is False +@skip_pypy def test_expired_request_error(mock_session): """Without stale_if_error (default), if there is an error while re-fetching an expired response, the request should be re-raised """ mock_session.settings.stale_if_error = False mock_session.settings.expire_after = 1 - mock_session.get(MOCKED_URL) - sleep(1) + + with time_travel(START_DT): + mock_session.get(MOCKED_URL) with patch.object(mock_session.cache, 'save_response', side_effect=ValueError): - with pytest.raises(ValueError): - mock_session.get(MOCKED_URL) + with time_travel(START_DT + timedelta(seconds=1.1)): + with pytest.raises(ValueError): + mock_session.get(MOCKED_URL) +@skip_pypy def test_stale_if_error__exception(mock_session): """With stale_if_error, expect to get old cache data if there is an exception during a request""" mock_session.settings.stale_if_error = True mock_session.settings.expire_after = 1 + with time_travel(START_DT): + assert mock_session.get(MOCKED_URL).from_cache is False + assert mock_session.get(MOCKED_URL).from_cache is True - assert mock_session.get(MOCKED_URL).from_cache is False - assert mock_session.get(MOCKED_URL).from_cache is True - sleep(1) with patch.object(mock_session.cache, 'save_response', side_effect=RequestException): - response = mock_session.get(MOCKED_URL) - assert response.from_cache is True and response.is_expired is True + with time_travel(START_DT + timedelta(seconds=1.1)): + response = mock_session.get(MOCKED_URL) + assert response.from_cache is True and response.is_expired is True +@skip_pypy def test_stale_if_error__error_code(mock_session): """With stale_if_error, expect to get old cache data if a response has an error status code, that is not in allowable_codes. @@ -392,16 +401,18 @@ def test_stale_if_error__error_code(mock_session): mock_session.settings.expire_after = 1 mock_session.settings.allowable_codes = (200,) - assert mock_session.get(MOCKED_URL_200_404).status_code == 200 + with time_travel(START_DT): + assert mock_session.get(MOCKED_URL_200_404).status_code == 200 - sleep(1) + with time_travel(START_DT + timedelta(seconds=1.1)): + response = mock_session.get(MOCKED_URL_200_404) - response = mock_session.get(MOCKED_URL_200_404) assert response.status_code == 200 assert response.from_cache is True assert response.is_expired is True +@skip_pypy def test_stale_if_error__error_code_in_allowable_codes(mock_session): """With stale_if_error, expect to get the failed response if a response has an error status code, that is in allowable_codes. @@ -410,11 +421,12 @@ def test_stale_if_error__error_code_in_allowable_codes(mock_session): mock_session.settings.expire_after = 1 mock_session.settings.allowable_codes = (200, 404) - assert mock_session.get(MOCKED_URL_200_404).status_code == 200 + with time_travel(START_DT): + assert mock_session.get(MOCKED_URL_200_404).status_code == 200 - sleep(1) + with time_travel(START_DT + timedelta(seconds=1.1)): + response = mock_session.get(MOCKED_URL_200_404) - response = mock_session.get(MOCKED_URL_200_404) assert response.status_code == 404 assert response.from_cache is False assert response.is_expired is False @@ -769,16 +781,18 @@ def test_stale_while_revalidate__refresh(mock_session): # ----------------------------------------------------- +@skip_pypy # time-machine doesn't work on PyPy def test_request_expire_after__enable_expiration(mock_session): """No per-session expiration is set, but then overridden for a single request""" mock_session.settings.expire_after = None - response = mock_session.get(MOCKED_URL, expire_after=1) - assert response.from_cache is False - assert mock_session.get(MOCKED_URL).from_cache is True + with time_travel(START_DT): + response = mock_session.get(MOCKED_URL, expire_after=1) + assert response.from_cache is False + assert mock_session.get(MOCKED_URL).from_cache is True - sleep(1) - response = mock_session.get(MOCKED_URL) - assert response.from_cache is False + with time_travel(START_DT + timedelta(seconds=1.1)): + response = mock_session.get(MOCKED_URL) + assert response.from_cache is False def test_request_expire_after__disable_expiration(mock_session): @@ -790,17 +804,19 @@ def test_request_expire_after__disable_expiration(mock_session): assert response.expires is None +@skip_pypy def test_request_expire_after__prepared_request(mock_session): """Pre-request expiration should also work for PreparedRequests with CachedSession.send()""" mock_session.settings.expire_after = None - request = Request('GET', MOCKED_URL, headers={}, data=None).prepare() - response = mock_session.send(request, expire_after=1) - assert response.from_cache is False - assert mock_session.send(request).from_cache is True + with time_travel(START_DT): + request = Request('GET', MOCKED_URL, headers={}, data=None).prepare() + response = mock_session.send(request, expire_after=1) + assert response.from_cache is False + assert mock_session.send(request).from_cache is True - sleep(1) - response = mock_session.get(MOCKED_URL) - assert response.from_cache is False + with time_travel(START_DT + timedelta(seconds=1.1)): + response = mock_session.get(MOCKED_URL) + assert response.from_cache is False def test_request_only_if_cached__cached(mock_session): @@ -819,22 +835,26 @@ def test_request_only_if_cached__uncached(mock_session): response.raise_for_status() +@skip_pypy def test_request_only_if_cached__expired(mock_session): """By default, only_if_cached will not return an expired response""" - mock_session.get(MOCKED_URL, expire_after=1) - sleep(1) + with time_travel(START_DT): + mock_session.get(MOCKED_URL, expire_after=1) - response = mock_session.get(MOCKED_URL, only_if_cached=True) + with time_travel(START_DT + timedelta(seconds=1.1)): + response = mock_session.get(MOCKED_URL, only_if_cached=True) assert response.status_code == 504 +@skip_pypy def test_request_only_if_cached__stale_if_error__expired(mock_session): """only_if_cached *will* return an expired response if stale_if_error is also set""" - mock_session.get(MOCKED_URL, expire_after=1) - sleep(1) + with time_travel(START_DT): + mock_session.get(MOCKED_URL, expire_after=1) - mock_session.settings.stale_if_error = True - response = mock_session.get(MOCKED_URL, only_if_cached=True) + with time_travel(START_DT + timedelta(seconds=1.1)): + mock_session.settings.stale_if_error = True + response = mock_session.get(MOCKED_URL, only_if_cached=True) assert response.status_code == 200 assert response.from_cache is True assert response.is_expired is True |