debian-suds-jurko/tests/test_cache.py

1053 lines
39 KiB
Python

# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify it under
# the terms of the (LGPL) GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License
# for more details at ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr )
"""
Suds Python library document caching unit tests.
Implemented using the 'pytest' testing framework.
"""
import testutils
if __name__ == "__main__":
testutils.run_using_pytest(globals())
import suds
import suds.cache
import suds.sax.parser
import pytest
from six import b, next, u
import datetime
import os
import os.path
import sys
class MyException(Exception):
"""Local exception class used in the tests in this module."""
pass
class InvisibleMan:
"""Dummy class used for pickling related tests."""
def __init__(self, x):
self.x = x
class MockDateTime(datetime.datetime):
"""
MockDateTime class monkeypatched to replace datetime.datetime.
Allows us to control the exact built-in datetime.datetime.now() return
value. Note that Python does not allow us to monkeypatch
datetime.datetime.now() directly as it is a built-in function.
"""
mock_counter = 0
@staticmethod
def now(*args, **kwargs):
MockDateTime.mock_counter += 1
return MockDateTime.mock_value
class MockFile:
"""
Wrapper around a regular file object allowing controlled file operation
failures.
"""
def __init__(self, opener, file, fail_read):
self.__opener = opener
self.__file = file
self.__fail_read = fail_read
def __getattr__(self, *args, **kwargs):
return getattr(self.__file, *args, **kwargs)
def read(self, *args, **kwargs):
self.__opener.read_counter += 1
if self.__fail_read:
raise MyException
return self.__file.read(*args, **kwargs)
class MockFileOpener:
"""
Mock open() function for the suds.cache module.
May cause such calls to fail or to return our MockFile objects prepared so
some of their functions fail in a controlled manner.
"""
def __init__(self, fail_open=False, fail_read=False):
self.__previous = None
self.__fail_open = fail_open
self.__fail_read = fail_read
self.counter = 0
self.read_counter = 0
def __call__(self, *args, **kwargs):
self.counter += 1
if self.__fail_open:
raise MyException
file = self.__previous(*args, **kwargs)
return MockFile(self, file, fail_read=self.__fail_read)
def apply(self, monkeypatch):
"""Monkeypatch suds.cache module's open() global."""
try:
self.__previous = suds.cache.open
except AttributeError:
self.__previous = open
monkeypatch.setitem(suds.cache.__dict__, "open", self)
def reset(self):
self.counter = 0
self.read_counter = 0
class MockParse:
"""Mock object causing suds.sax.parser.Parser.parse() failures."""
def __init__(self):
self.counter = 0
def __call__(self, *args, **kwargs):
self.counter += 1
raise MyException
def apply(self, monkeypatch):
"""Monkeypatch suds SAX Parser's parse() method."""
monkeypatch.setattr(suds.sax.parser.Parser, "parse", self)
def reset(self):
self.counter = 0
class MockPickleLoad:
"""Mock object causing suds.cache module's pickle load failures."""
def __init__(self):
self.counter = 0
def __call__(self, *args, **kwargs):
self.counter += 1
raise MyException
def apply(self, monkeypatch):
"""Monkeypatch suds.cache module's pickle.load()."""
monkeypatch.setattr(suds.cache.pickle, "load", self)
def reset(self):
self.counter = 0
# Hardcoded values used in different caching test cases.
value_empty = b("")
value_f2 = b("fifi2")
value_f22 = b("fifi22")
value_f3 = b("fifi3")
value_p1 = b("pero1")
value_p11 = b("pero11")
value_p111 = b("pero111")
value_p2 = b("pero2")
value_p22 = b("pero22")
value_unicode = u("\u20AC \u7684 "
"\u010D\u0107\u017E\u0161\u0111"
"\u010C\u0106\u017D\u0160\u0110").encode("utf-8")
# FileCache item expiration test data - duration, current_time, expect_remove.
# Reused for different testing different FileCache derived classes.
file_cache_item_expiration_test_data = ([
# Infinite cache entry durations.
({}, datetime.datetime.min, False),
({}, datetime.timedelta(days=-21), False),
({}, -datetime.datetime.resolution, False),
({}, datetime.timedelta(), False),
({}, datetime.datetime.resolution, False),
({}, datetime.timedelta(days=7), False),
({}, datetime.datetime.max, False)] +
# Finite cache entry durations.
[(duration, current_time, expect_remove)
for duration in (
{"minutes": 7},
{"microseconds": 1},
{"microseconds": -1},
{"hours": -7})
for current_time, expect_remove in (
(datetime.datetime.min, False),
(datetime.timedelta(days=-21), False),
(-datetime.datetime.resolution, False),
(datetime.timedelta(), False),
(datetime.datetime.resolution, True),
(datetime.timedelta(days=7), True),
(datetime.datetime.max, True))])
@pytest.mark.parametrize(("method_name", "params"), (
("clear", []),
("get", ["id"]),
("purge", ["id"]),
("put", ["id", "object"])))
def test_Cache_methods_abstract(monkeypatch, method_name, params):
monkeypatch.delitem(locals(), "e", False)
cache = suds.cache.Cache()
f = getattr(cache, method_name)
e = pytest.raises(Exception, f, *params).value
try:
assert e.__class__ is Exception
assert str(e) == "not-implemented"
finally:
del e # explicitly break circular reference chain in Python 3
class TestDefaultFileCacheLocation:
"""Default FileCache cache location handling tests."""
@pytest.mark.parametrize("cache_class", (
suds.cache.DocumentCache,
suds.cache.FileCache,
suds.cache.ObjectCache))
def test_basic(self, tmpdir, cache_class):
"""
Test default FileCache folder usage.
Initial DocumentCache/FileCache/ObjectCache instantiation with no
explicitly specified location in a process should use
tempfile.mkdtemp() and that folder should be used as its location.
After a single DocumentCache/FileCache/ObjectCache instantiation with
no explicitly specified location, all later DocumentCache/FileCache/
ObjectCache instantiations with no explicitly specified location in the
same process should use that same location folder without additional
tempfile.mkdtemp() calls.
Both initial & non-initial DocumentCache/FileCache/ObjectCache
instantiation with an explicitly specified location should use that
folder as its default location and not make any tempfile.mkdtemp()
calls.
"""
cache_folder_name = "my test cache-%s" % (cache_class.__name__,)
cache_folder = tmpdir.join(cache_folder_name).strpath
fake_cache_folder_name = "my fake cache-%s" % (cache_class.__name__,)
fake_cache_folder = tmpdir.join(fake_cache_folder_name).strpath
test_file = tmpdir.join("test_file.py")
test_file.write("""\
import os.path
import tempfile
original_mkdtemp = tempfile.mkdtemp
mock_mkdtemp_counter = 0
def mock_mkdtemp(*args, **kwargs):
global mock_mkdtemp_counter
mock_mkdtemp_counter += 1
return cache_folder
tempfile.mkdtemp = mock_mkdtemp
def check_cache_folder(expected_exists, expected_mkdtemp_counter, comment):
if os.path.exists(cache_folder) != expected_exists:
if expected_exists:
message = "does not exist when expected"
else:
message = "exists when not expected"
print("Cache folder %%s (%%s)." %% (message, comment))
sys.exit(-2)
if mock_mkdtemp_counter != expected_mkdtemp_counter:
if mock_mkdtemp_counter < expected_mkdtemp_counter:
message = "less"
else:
message = "more"
print("tempfile.mkdtemp() called %%s times then expected (%%s)" %%
(message, comment,))
cache_folder = %(cache_folder)r
fake_cache_folder = %(fake_cache_folder)r
def fake_cache(n):
return fake_cache_folder + str(n)
from suds.cache import DocumentCache, FileCache, ObjectCache
check_cache_folder(False, 0, "import")
assert DocumentCache(fake_cache(1)).location == fake_cache(1)
assert FileCache(fake_cache(2)).location == fake_cache(2)
assert ObjectCache(fake_cache(3)).location == fake_cache(3)
check_cache_folder(False, 0, "initial caches with non-default location")
assert %(cache_class_name)s().location == cache_folder
check_cache_folder(True, 1, "initial cache with default location")
assert DocumentCache().location == cache_folder
assert FileCache().location == cache_folder
assert ObjectCache().location == cache_folder
check_cache_folder(True, 1, "non-initial caches with default location")
assert DocumentCache(fake_cache(4)).location == fake_cache(4)
assert FileCache(fake_cache(5)).location == fake_cache(5)
assert ObjectCache(fake_cache(6)).location == fake_cache(6)
check_cache_folder(True, 1, "non-initial caches with non-default location")
assert DocumentCache().location == cache_folder
assert FileCache().location == cache_folder
assert ObjectCache().location == cache_folder
check_cache_folder(True, 1, "final caches with default location")
""" % {"cache_class_name": cache_class.__name__,
"cache_folder": cache_folder,
"fake_cache_folder": fake_cache_folder})
assert not os.path.exists(cache_folder)
testutils.run_test_process(test_file)
@pytest.mark.parametrize("removal_enabled", (True, False))
def test_remove_on_exit(self, tmpdir, removal_enabled):
"""
Test removing the default cache folder on process exit.
The folder should be removed by default on process exit, but this
behaviour may be disabled by the user.
"""
cache_folder_name = "my test cache-%s" % (removal_enabled,)
cache_folder = tmpdir.join(cache_folder_name).strpath
test_file = tmpdir.join("test_file.py")
test_file.write("""\
import os.path
import tempfile
original_mkdtemp = tempfile.mkdtemp
mock_mkdtemp_counter = 0
def mock_mkdtemp(*args, **kwargs):
global mock_mkdtemp_counter
mock_mkdtemp_counter += 1
return cache_folder
tempfile.mkdtemp = mock_mkdtemp
import suds.cache
if not suds.cache.FileCache.remove_default_location_on_exit:
print("Default FileCache folder removal not enabled by default.")
sys.exit(-2)
suds.cache.FileCache.remove_default_location_on_exit = %(removal_enabled)s
cache_folder = %(cache_folder)r
if os.path.exists(cache_folder):
print("Cache folder exists too early.")
sys.exit(-2)
suds.cache.FileCache()
if not mock_mkdtemp_counter == 1:
print("tempfile.mkdtemp() not called as expected (%%d)." %%
(mock_mkdtemp_counter,))
sys.exit(-2)
if not os.path.isdir(cache_folder):
print("Cache folder not created when expected.")
sys.exit(-2)
""" % {"cache_folder": cache_folder, "removal_enabled": removal_enabled})
assert not os.path.exists(cache_folder)
testutils.run_test_process(test_file)
if removal_enabled:
assert not os.path.exists(cache_folder)
else:
assert os.path.isdir(cache_folder)
class TestDocumentCache:
def compare_document_to_content(self, document, content):
"""Assert that the given XML document and content match."""
assert document.__class__ is suds.sax.document.Document
elements = document.getChildren()
assert len(elements) == 1
element = elements[0]
assert element.__class__ is suds.sax.element.Element
assert suds.byte_str(str(element)) == content
@staticmethod
def construct_XML(element_name="Elemento"):
"""
Construct XML content and a Document wrapping it.
The XML contains a single Element (may be parametrized with the given
element name) and possibly additional sub-elements under it.
"""
#TODO: Update the tests in this group to no longer depend on the exact
# input XML data formatting. They currently expect it to be formatted
# exactly as what gets read back from their DocumentCache.
content = suds.byte_str("""\
<xsd:element name="%s">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="alfa"/>
<xsd:enumeration value="beta"/>
<xsd:enumeration value="gamma"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>""" % (element_name,))
xml = suds.sax.parser.Parser().parse(suds.BytesIO(content))
assert xml.__class__ is suds.sax.document.Document
return content, xml
def test_cache_document(self, tmpdir):
cache_item_id = "unga1"
cache = suds.cache.DocumentCache(tmpdir.strpath)
assert isinstance(cache, suds.cache.FileCache)
assert cache.get(cache_item_id) is None
content, document = self.construct_XML()
cache.put(cache_item_id, document)
self.compare_document_to_content(cache.get(cache_item_id), content)
def test_cache_element(self, tmpdir):
cache_item_id = "unga1"
cache = suds.cache.DocumentCache(tmpdir.strpath)
assert isinstance(cache, suds.cache.FileCache)
assert cache.get(cache_item_id) is None
content, document = self.construct_XML()
cache.put(cache_item_id, document.root())
self.compare_document_to_content(cache.get(cache_item_id), content)
def test_file_open_failure(self, tmpdir, monkeypatch):
"""
File open failure should cause no cached object to be found, but any
existing underlying cache file should be kept around.
"""
mock_open = MockFileOpener(fail_open=True)
cache_folder = tmpdir.strpath
cache = suds.cache.DocumentCache(cache_folder)
content1, document1 = self.construct_XML("One")
content2, document2 = self.construct_XML("Two")
assert content1 != content2
cache.put("unga1", document1)
mock_open.apply(monkeypatch)
assert cache.get("unga1") is None
monkeypatch.undo()
assert mock_open.counter == 1
_assert_empty_cache_folder(cache_folder, expected=False)
self.compare_document_to_content(cache.get("unga1"), content1)
mock_open.apply(monkeypatch)
assert cache.get("unga2") is None
monkeypatch.undo()
assert mock_open.counter == 2
_assert_empty_cache_folder(cache_folder, expected=False)
self.compare_document_to_content(cache.get("unga1"), content1)
assert cache.get("unga2") is None
cache.put("unga2", document2)
assert mock_open.counter == 2
mock_open.apply(monkeypatch)
assert cache.get("unga1") is None
monkeypatch.undo()
assert mock_open.counter == 3
_assert_empty_cache_folder(cache_folder, expected=False)
self.compare_document_to_content(cache.get("unga1"), content1)
self.compare_document_to_content(cache.get("unga2"), content2)
assert mock_open.counter == 3
@pytest.mark.parametrize(("mock", "extra_checks"), (
(MockParse(), [lambda x: True] * 4),
(MockFileOpener(fail_read=True), [
lambda x: x.read_counter != 0,
lambda x: x.read_counter == 0,
lambda x: x.read_counter != 0,
lambda x: x.read_counter == 0])))
def test_file_operation_failure(self, tmpdir, monkeypatch, mock,
extra_checks):
"""
File operation failures such as reading failures or failing to parse
data read from such a file should cause no cached object to be found
and the related cache file to be removed.
"""
cache_folder = tmpdir.strpath
cache = suds.cache.DocumentCache(cache_folder)
content1, document1 = self.construct_XML("Eins")
content2, document2 = self.construct_XML("Zwei")
cache.put("unga1", document1)
mock.apply(monkeypatch)
assert cache.get("unga1") is None
monkeypatch.undo()
assert mock.counter == 1
assert extra_checks[0](mock)
_assert_empty_cache_folder(cache_folder)
mock.reset()
assert cache.get("unga1") is None
cache.put("unga1", document1)
cache.put("unga2", document2)
assert mock.counter == 0
assert extra_checks[1](mock)
mock.reset()
mock.apply(monkeypatch)
assert cache.get("unga1") is None
monkeypatch.undo()
assert mock.counter == 1
assert extra_checks[2](mock)
_assert_empty_cache_folder(cache_folder, expected=False)
mock.reset()
assert cache.get("unga1") is None
self.compare_document_to_content(cache.get("unga2"), content2)
assert mock.counter == 0
assert extra_checks[3](mock)
@pytest.mark.parametrize(("duration", "current_time", "expect_remove"),
file_cache_item_expiration_test_data)
def test_item_expiration(self, tmpdir, monkeypatch, duration, current_time,
expect_remove):
"""See TestFileCache.item_expiration_test_worker() for more info."""
cache = suds.cache.DocumentCache(tmpdir.strpath, **duration)
content, document = self.construct_XML()
cache.put("willy", document)
TestFileCache.item_expiration_test_worker(cache, "willy", monkeypatch,
current_time, expect_remove)
def test_repeated_reads(self, tmpdir):
cache = suds.cache.DocumentCache(tmpdir.strpath)
content, document = self.construct_XML()
cache.put("unga1", document)
read_XML = cache.get("unga1").str()
assert read_XML == cache.get("unga1").str()
assert cache.get(None) is None
assert cache.get("") is None
assert cache.get("unga2") is None
assert read_XML == cache.get("unga1").str()
class TestFileCache:
@staticmethod
def item_expiration_test_worker(cache, id, monkeypatch, current_time,
expect_remove):
"""
Test how a FileCache & its derived classes expire their item entries.
Facts tested:
* 0 duration should cause cache items never to expire.
* Expired item files should be automatically removed from the cache
folder.
* Negative durations should be treated the same as positive ones.
Requirements on the passed cache object:
* Configures with the correct duration for this test.
* Contains a valid cached item with the given id and its ctime
timestamp + cache.duration must fall into the valid datetime.datetime
value range.
* Must use only public & protected FileCache interfaces to access its
cache item data files.
'current_time' values are expected to be either datetime.datetime or
datetime.timedelta instances with the latter interpreted relative to
the test file's expected expiration time.
"""
assert isinstance(cache, suds.cache.FileCache)
filepath = cache._FileCache__filename(id)
assert os.path.isfile(filepath)
file_timestamp = os.path.getctime(filepath)
file_time = datetime.datetime.fromtimestamp(file_timestamp)
MockDateTime.mock_counter = 0
if isinstance(current_time, datetime.timedelta):
expire_time = file_time + cache.duration
MockDateTime.mock_value = expire_time + current_time
else:
MockDateTime.mock_value = current_time
monkeypatch.setattr(datetime, "datetime", MockDateTime)
assert (cache._getf(id) is None) == expect_remove
monkeypatch.undo()
if cache.duration:
assert MockDateTime.mock_counter == 1
else:
assert MockDateTime.mock_counter == 0
assert os.path.isfile(filepath) == (not expect_remove)
def test_basic_construction(self):
cache = suds.cache.FileCache()
assert isinstance(cache, suds.cache.Cache)
assert cache.duration.__class__ is datetime.timedelta
def test_cached_content_empty(self, tmpdir):
cache_folder = tmpdir.strpath
cache = suds.cache.FileCache(cache_folder)
cache.put("unga1", value_empty)
assert cache.get("unga1") == value_empty
_assert_empty_cache_folder(cache_folder, expected=False)
def test_cached_content_unicode(self, tmpdir):
cache_folder = tmpdir.strpath
cache = suds.cache.FileCache(cache_folder)
cache.put("unga1", value_unicode)
assert cache.get("unga1") == value_unicode
_assert_empty_cache_folder(cache_folder, expected=False)
def test_clear(self, tmpdir):
cache_folder1 = tmpdir.join("fungus").strpath
cache1 = suds.cache.FileCache(cache_folder1)
cache1.put("unga1", value_p1)
_assert_empty_cache_folder(cache_folder1, expected=False)
cache1.put("unga2", value_p2)
_assert_empty_cache_folder(cache_folder1, expected=False)
assert cache1.get("unga1") == value_p1
assert cache1.get("unga2") == value_p2
_assert_empty_cache_folder(cache_folder1, expected=False)
cache1.clear()
_assert_empty_cache_folder(cache_folder1)
assert cache1.get("unga1") is None
assert cache1.get("unga2") is None
_assert_empty_cache_folder(cache_folder1)
cache1.put("unga1", value_p11)
cache1.put("unga2", value_p2)
_assert_empty_cache_folder(cache_folder1, expected=False)
assert cache1.get("unga1") == value_p11
assert cache1.get("unga2") == value_p2
_assert_empty_cache_folder(cache_folder1, expected=False)
cache_folder2 = tmpdir.join("broccoli").strpath
cache2 = suds.cache.FileCache(cache_folder2)
cache2.put("unga2", value_f2)
assert cache2.get("unga2") == value_f2
assert cache1.get("unga2") == value_p2
cache2.clear()
_assert_empty_cache_folder(cache_folder1, expected=False)
_assert_empty_cache_folder(cache_folder2)
assert cache2.get("unga2") is None
assert cache1.get("unga1") == value_p11
assert cache1.get("unga2") == value_p2
cache2.put("unga2", value_p22)
assert cache2.get("unga2") == value_p22
def test_close_leaves_cached_files_behind(self, tmpdir):
cache_folder1 = tmpdir.join("ana").strpath
cache1 = suds.cache.FileCache(cache_folder1)
cache1.put("unga1", value_p1)
cache1.put("unga2", value_p2)
cache_folder2 = tmpdir.join("nan").strpath
cache2 = suds.cache.FileCache(cache_folder2)
cache2.put("unga2", value_f2)
cache2.put("unga3", value_f3)
del cache1
cache11 = suds.cache.FileCache(cache_folder1)
assert cache11.get("unga1") == value_p1
assert cache11.get("unga2") == value_p2
assert cache2.get("unga2") == value_f2
assert cache2.get("unga3") == value_f3
def test_get_put(self, tmpdir):
cache_folder1 = tmpdir.join("firefly").strpath
cache1 = suds.cache.FileCache(cache_folder1)
_assert_empty_cache_folder(cache_folder1)
assert cache1.get("unga1") is None
cache1.put("unga1", value_p1)
_assert_empty_cache_folder(cache_folder1, expected=False)
assert cache1.get("unga1") == value_p1
assert cache1.get("unga2") is None
cache1.put("unga1", value_p11)
assert cache1.get("unga1") == value_p11
assert cache1.get("unga2") is None
cache1.put("unga2", value_p2)
assert cache1.get("unga1") == value_p11
assert cache1.get("unga2") == value_p2
cache_folder2 = tmpdir.join("semper fi").strpath
cache2 = suds.cache.FileCache(cache_folder2)
_assert_empty_cache_folder(cache_folder2)
assert cache2.get("unga2") is None
cache2.put("unga2", value_f2)
_assert_empty_cache_folder(cache_folder2, expected=False)
assert cache2.get("unga2") == value_f2
assert cache2.get("unga3") is None
cache2.put("unga2", value_f22)
assert cache2.get("unga2") == value_f22
assert cache2.get("unga3") is None
cache2.put("unga3", value_f3)
assert cache2.get("unga2") == value_f22
assert cache2.get("unga3") == value_f3
_assert_empty_cache_folder(cache_folder1, expected=False)
_assert_empty_cache_folder(cache_folder2, expected=False)
assert cache1.get("unga1") == value_p11
assert cache1.get("unga2") == value_p2
assert cache1.get("unga3") is None
assert cache2.get("unga1") is None
assert cache2.get("unga2") == value_f22
assert cache2.get("unga3") == value_f3
def test_independent_item_expirations(self, tmpdir, monkeypatch):
cache = suds.cache.FileCache(tmpdir.strpath, days=1)
cache.put("unga1", value_p1)
cache.put("unga2", value_p2)
cache.put("unga3", value_f2)
filepath1 = cache._FileCache__filename("unga1")
filepath2 = cache._FileCache__filename("unga2")
filepath3 = cache._FileCache__filename("unga3")
file_timestamp1 = os.path.getctime(filepath1)
file_timestamp2 = file_timestamp1 + 10 * 60 # in seconds
file_timestamp3 = file_timestamp1 + 20 * 60 # in seconds
file_time1 = datetime.datetime.fromtimestamp(file_timestamp1)
file_time1_expiration = file_time1 + cache.duration
original_getctime = os.path.getctime
def mock_getctime(path):
if path == filepath2:
return file_timestamp2
if path == filepath3:
return file_timestamp3
return original_getctime(path)
timedelta = datetime.timedelta
monkeypatch.setattr(os.path, "getctime", mock_getctime)
monkeypatch.setattr(datetime, "datetime", MockDateTime)
MockDateTime.mock_value = file_time1_expiration + timedelta(minutes=15)
assert cache._getf("unga2") is None
assert os.path.isfile(filepath1)
assert not os.path.isfile(filepath2)
assert os.path.isfile(filepath3)
cache._getf("unga3").close()
assert os.path.isfile(filepath1)
assert not os.path.isfile(filepath2)
assert os.path.isfile(filepath3)
MockDateTime.mock_value = file_time1_expiration + timedelta(minutes=25)
assert cache._getf("unga1") is None
assert not os.path.isfile(filepath1)
assert not os.path.isfile(filepath2)
assert os.path.isfile(filepath3)
assert cache._getf("unga3") is None
assert not os.path.isfile(filepath1)
assert not os.path.isfile(filepath2)
assert not os.path.isfile(filepath3)
@pytest.mark.parametrize(("duration", "current_time", "expect_remove"),
file_cache_item_expiration_test_data)
def test_item_expiration(self, tmpdir, monkeypatch, duration, current_time,
expect_remove):
"""See TestFileCache.item_expiration_test_worker() for more info."""
cache = suds.cache.FileCache(tmpdir.strpath, **duration)
cache.put("unga1", value_p1)
TestFileCache.item_expiration_test_worker(cache, "unga1", monkeypatch,
current_time, expect_remove)
def test_non_default_location(self, tmpdir):
FileCache = suds.cache.FileCache
cache_folder1 = tmpdir.join("flip-flop1").strpath
assert not os.path.isdir(cache_folder1)
assert FileCache(location=cache_folder1).location == cache_folder1
_assert_empty_cache_folder(cache_folder1)
cache_folder2 = tmpdir.join("flip-flop2").strpath
assert not os.path.isdir(cache_folder2)
assert FileCache(cache_folder2).location == cache_folder2
_assert_empty_cache_folder(cache_folder2)
def test_purge(self, tmpdir):
cache_folder1 = tmpdir.join("flamenco").strpath
cache1 = suds.cache.FileCache(cache_folder1)
cache1.put("unga1", value_p1)
assert cache1.get("unga1") == value_p1
cache1.purge("unga1")
_assert_empty_cache_folder(cache_folder1)
assert cache1.get("unga1") is None
cache1.put("unga1", value_p11)
cache1.put("unga2", value_p2)
assert cache1.get("unga1") == value_p11
assert cache1.get("unga2") == value_p2
cache1.purge("unga1")
assert cache1.get("unga1") is None
assert cache1.get("unga2") == value_p2
cache1.put("unga1", value_p111)
cache_folder2 = tmpdir.join("shadow").strpath
cache2 = suds.cache.FileCache(cache_folder2)
cache2.put("unga2", value_f2)
cache2.purge("unga2")
_assert_empty_cache_folder(cache_folder2)
assert cache1.get("unga1") == value_p111
assert cache1.get("unga2") == value_p2
assert cache2.get("unga2") is None
def test_reused_cache_folder(self, tmpdir):
cache_folder = tmpdir.strpath
cache1 = suds.cache.FileCache(cache_folder)
_assert_empty_cache_folder(cache_folder)
assert cache1.get("unga1") is None
cache1.put("unga1", value_p1)
assert cache1.get("unga1") == value_p1
assert cache1.get("unga2") is None
cache1.put("unga1", value_p11)
assert cache1.get("unga1") == value_p11
assert cache1.get("unga2") is None
cache1.put("unga2", value_p2)
assert cache1.get("unga1") == value_p11
assert cache1.get("unga2") == value_p2
cache2 = suds.cache.FileCache(cache_folder)
assert cache2.get("unga1") == value_p11
assert cache2.get("unga2") == value_p2
cache2.put("unga2", value_f2)
cache2.put("unga3", value_f3)
assert cache1.get("unga2") == value_f2
assert cache1.get("unga3") == value_f3
cache1.purge("unga2")
assert cache2.get("unga2") is None
cache1.clear()
assert cache2.get("unga1") is None
assert cache2.get("unga3") is None
@pytest.mark.parametrize("params", (
{},
{"microseconds": 1},
{"milliseconds": 1},
{"seconds": 1},
{"minutes": 1},
{"hours": 1},
{"days": 1},
{"weeks": 1},
{"microseconds": -1},
{"milliseconds": -1},
{"seconds": -1},
{"minutes": -1},
{"hours": -1},
{"days": -1},
{"weeks": -1},
{"weeks": 1, "days": 2, "hours": 7, "minutes": 0, "seconds": -712}))
def test_set_durations(self, tmpdir, params):
cache = suds.cache.FileCache(tmpdir.strpath, **params)
assert cache.duration == datetime.timedelta(**params)
def test_version(self, tmpdir):
fake_version_info = "--- fake version info ---"
assert suds.__version__ != fake_version_info
version_file = tmpdir.join("version")
cache_folder = tmpdir.strpath
cache = suds.cache.FileCache(cache_folder)
assert version_file.read() == suds.__version__
cache.put("unga1", value_p1)
version_file.write(fake_version_info)
assert cache.get("unga1") == value_p1
cache2 = suds.cache.FileCache(cache_folder)
_assert_empty_cache_folder(cache_folder)
assert cache.get("unga1") is None
assert cache2.get("unga1") is None
assert version_file.read() == suds.__version__
cache.put("unga1", value_p11)
cache.put("unga2", value_p22)
version_file.remove()
assert cache.get("unga1") == value_p11
assert cache.get("unga2") == value_p22
cache3 = suds.cache.FileCache(cache_folder)
_assert_empty_cache_folder(cache_folder)
assert cache.get("unga1") is None
assert cache.get("unga2") is None
assert cache2.get("unga1") is None
assert cache3.get("unga1") is None
assert version_file.read() == suds.__version__
def test_NoCache(monkeypatch):
cache = suds.cache.NoCache()
assert isinstance(cache, suds.cache.Cache)
assert cache.get("id") == None
cache.put("id", "something")
assert cache.get("id") == None
#TODO: It should not be an error to call clear() or purge() on a NoCache
# instance.
monkeypatch.delitem(locals(), "e", False)
e = pytest.raises(Exception, cache.purge, "id").value
try:
assert str(e) == "not-implemented"
finally:
del e # explicitly break circular reference chain in Python 3
e = pytest.raises(Exception, cache.clear).value
try:
assert str(e) == "not-implemented"
finally:
del e # explicitly break circular reference chain in Python 3
class TestObjectCache:
def test_basic(self, tmpdir):
cache = suds.cache.ObjectCache(tmpdir.strpath)
assert isinstance(cache, suds.cache.FileCache)
assert cache.get("unga1") is None
assert cache.get("unga2") is None
cache.put("unga1", InvisibleMan(1))
cache.put("unga2", InvisibleMan(2))
read1 = cache.get("unga1")
read2 = cache.get("unga2")
assert read1.__class__ is InvisibleMan
assert read2.__class__ is InvisibleMan
assert read1.x == 1
assert read2.x == 2
def test_file_open_failure(self, tmpdir, monkeypatch):
"""
File open failure should cause no cached object to be found, but any
existing underlying cache file should be kept around.
"""
mock_open = MockFileOpener(fail_open=True)
cache_folder = tmpdir.strpath
cache = suds.cache.ObjectCache(cache_folder)
cache.put("unga1", InvisibleMan(1))
mock_open.apply(monkeypatch)
assert cache.get("unga1") is None
monkeypatch.undo()
assert mock_open.counter == 1
_assert_empty_cache_folder(cache_folder, expected=False)
assert cache.get("unga1").x == 1
mock_open.apply(monkeypatch)
assert cache.get("unga2") is None
monkeypatch.undo()
assert mock_open.counter == 2
_assert_empty_cache_folder(cache_folder, expected=False)
assert cache.get("unga1").x == 1
assert cache.get("unga2") is None
cache.put("unga2", InvisibleMan(2))
assert mock_open.counter == 2
mock_open.apply(monkeypatch)
assert cache.get("unga1") is None
monkeypatch.undo()
assert mock_open.counter == 3
_assert_empty_cache_folder(cache_folder, expected=False)
assert cache.get("unga1").x == 1
assert cache.get("unga2").x == 2
assert mock_open.counter == 3
@pytest.mark.parametrize(("mock", "extra_checks"), (
(MockPickleLoad(), [lambda x: True] * 4),
(MockFileOpener(fail_read=True), [
lambda x: x.read_counter != 0,
lambda x: x.read_counter == 0,
lambda x: x.read_counter != 0,
lambda x: x.read_counter == 0])))
def test_file_operation_failure(self, tmpdir, monkeypatch, mock,
extra_checks):
"""
Open file operation failures such as reading failures or failing to
unpickle the data read from such a file should cause no cached object
to be found and the related cache file to be removed.
"""
cache_folder = tmpdir.strpath
cache = suds.cache.ObjectCache(cache_folder)
cache.put("unga1", InvisibleMan(1))
mock.apply(monkeypatch)
assert cache.get("unga1") is None
monkeypatch.undo()
assert mock.counter == 1
assert extra_checks[0](mock)
_assert_empty_cache_folder(cache_folder)
mock.reset()
assert cache.get("unga1") is None
cache.put("unga1", InvisibleMan(1))
cache.put("unga2", InvisibleMan(2))
assert mock.counter == 0
assert extra_checks[1](mock)
mock.reset()
mock.apply(monkeypatch)
assert cache.get("unga1") is None
monkeypatch.undo()
assert mock.counter == 1
assert extra_checks[2](mock)
_assert_empty_cache_folder(cache_folder, expected=False)
mock.reset()
assert cache.get("unga1") is None
assert cache.get("unga2").x == 2
assert mock.counter == 0
assert extra_checks[3](mock)
@pytest.mark.parametrize(("duration", "current_time", "expect_remove"),
file_cache_item_expiration_test_data)
def test_item_expiration(self, tmpdir, monkeypatch, duration, current_time,
expect_remove):
"""See TestFileCache.item_expiration_test_worker() for more info."""
cache = suds.cache.ObjectCache(tmpdir.strpath, **duration)
cache.put("silly", InvisibleMan(666))
TestFileCache.item_expiration_test_worker(cache, "silly", monkeypatch,
current_time, expect_remove)
def _assert_empty_cache_folder(folder, expected=True):
"""Test utility asserting that a cache folder is or is not empty."""
if not _is_assert_enabled():
return
assert os.path.isdir(folder)
def walk_error(error):
pytest.fail("Error walking through cache folder content.")
root, folders, files = next(os.walk(folder, onerror=walk_error))
assert root == folder
empty = len(folders) == 0 and len(files) == 1 and files[0] == 'version'
if expected:
assert len(folders) == 0
assert len(files) == 1
assert files[0] == 'version'
assert empty, "bad test code"
else:
assert not empty, "unexpected empty cache folder"
def _is_assert_enabled():
"""Return whether Python assertions have been enabled in this module."""
try:
assert False
except AssertionError:
return True
return False