summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorJordan Cook <jordan.cook.git@proton.me>2023-03-24 20:58:33 -0500
committerJordan Cook <jordan.cook.git@proton.me>2023-04-02 21:42:25 -0500
commitb5d7d1546d953157ca7eb70838c6cb6f6f7651e3 (patch)
tree11ebbe68b4a7e29b8e6ee35381531ef8e185d3f3 /tests
parent8e4de3c35ebb8028d24dd1a874ec3e3348ed0b6a (diff)
downloadrequests-cache-b5d7d1546d953157ca7eb70838c6cb6f6f7651e3.tar.gz
Use time-machine instead of sleep for timing-based tests
Diffstat (limited to 'tests')
-rw-r--r--tests/conftest.py11
-rw-r--r--tests/unit/test_base_cache.py97
-rw-r--r--tests/unit/test_session.py90
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