scikit-learn/sklearn/externals/joblib/test/test_memory.py

443 lines
12 KiB
Python

"""
Test the memory module.
"""
# Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>
# Copyright (c) 2009 Gael Varoquaux
# License: BSD Style, 3 clauses.
from __future__ import with_statement
import shutil
import os
from tempfile import mkdtemp
import pickle
import warnings
import nose
from ..memory import Memory, MemorizedFunc
from .common import with_numpy, np
###############################################################################
# Module-level variables for the tests
def f(x, y=1):
""" A module-level function for testing purposes.
"""
return x ** 2 + y
###############################################################################
# Test fixtures
env = dict()
def setup_module():
""" Test setup.
"""
cachedir = mkdtemp()
#cachedir = 'foobar'
env['dir'] = cachedir
if os.path.exists(cachedir):
shutil.rmtree(cachedir)
# Don't make the cachedir, Memory should be able to do that on the fly
print 80 * '_'
print 'test_memory setup'
print 80 * '_'
def _rmtree_onerror(func, path, excinfo):
print '!' * 79
print 'os function failed:', repr(func)
print 'file to be removed:', path
print 'exception was:', excinfo[1]
print '!' * 79
def teardown_module():
""" Test teardown.
"""
shutil.rmtree(env['dir'], False, _rmtree_onerror)
print 80 * '_'
print 'test_memory teardown'
print 80 * '_'
###############################################################################
# Helper function for the tests
def check_identity_lazy(func, accumulator):
""" Given a function and an accumulator (a list that grows every
time the function is called, check that the function can be
decorated by memory to be a lazy identity.
"""
# Call each function with several arguments, and check that it is
# evaluated only once per argument.
memory = Memory(cachedir=env['dir'], verbose=0)
memory.clear(warn=False)
func = memory.cache(func)
for i in range(3):
for _ in range(2):
yield nose.tools.assert_equal, func(i), i
yield nose.tools.assert_equal, len(accumulator), i + 1
###############################################################################
# Tests
def test_memory_integration():
""" Simple test of memory lazy evaluation.
"""
accumulator = list()
# Rmk: this function has the same name than a module-level function,
# thus it serves as a test to see that both are identified
# as different.
def f(l):
accumulator.append(1)
return l
for test in check_identity_lazy(f, accumulator):
yield test
# Now test clearing
for compress in (False, True):
memory = Memory(cachedir=env['dir'], verbose=0, compress=compress)
# First clear the cache directory, to check that our code can
# handle that
# NOTE: this line would raise an exception, as the database file is
# still open; we ignore the error since we want to test what happens if
# the directory disappears
shutil.rmtree(env['dir'], ignore_errors=True)
g = memory.cache(f)
g(1)
g.clear(warn=False)
current_accumulator = len(accumulator)
out = g(1)
yield nose.tools.assert_equal, len(accumulator), \
current_accumulator + 1
# Also, check that Memory.eval works similarly
yield nose.tools.assert_equal, memory.eval(f, 1), out
yield nose.tools.assert_equal, len(accumulator), \
current_accumulator + 1
# Now do a smoke test with a function defined in __main__, as the name
# mangling rules are more complex
f.__module__ = '__main__'
memory = Memory(cachedir=env['dir'], verbose=0)
memory.cache(f)(1)
def test_no_memory():
""" Test memory with cachedir=None: no memoize """
accumulator = list()
def ff(l):
accumulator.append(1)
return l
mem = Memory(cachedir=None, verbose=0)
gg = mem.cache(ff)
for _ in range(4):
current_accumulator = len(accumulator)
gg(1)
yield nose.tools.assert_equal, len(accumulator), \
current_accumulator + 1
def test_memory_kwarg():
" Test memory with a function with keyword arguments."
accumulator = list()
def g(l=None, m=1):
accumulator.append(1)
return l
for test in check_identity_lazy(g, accumulator):
yield test
memory = Memory(cachedir=env['dir'], verbose=0)
g = memory.cache(g)
# Smoke test with an explicit keyword argument:
nose.tools.assert_equal(g(l=30, m=2), 30)
def test_memory_lambda():
" Test memory with a function with a lambda."
accumulator = list()
def helper(x):
""" A helper function to define l as a lambda.
"""
accumulator.append(1)
return x
l = lambda x: helper(x)
for test in check_identity_lazy(l, accumulator):
yield test
def test_memory_name_collision():
" Check that name collisions with functions will raise warnings"
memory = Memory(cachedir=env['dir'], verbose=0)
@memory.cache
def name_collision(x):
""" A first function called name_collision
"""
return x
a = name_collision
@memory.cache
def name_collision(x):
""" A second function called name_collision
"""
return x
b = name_collision
if not hasattr(warnings, 'catch_warnings'):
# catch_warnings is new in Python 2.6
return
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
a(1)
b(1)
yield nose.tools.assert_equal, len(w), 1
yield nose.tools.assert_true, "collision" in str(w[-1].message)
def test_memory_warning_lambda_collisions():
" Check that multiple use of lambda will raise collisions"
memory = Memory(cachedir=env['dir'], verbose=0)
a = lambda x: x
a = memory.cache(a)
b = lambda x: x + 1
b = memory.cache(b)
if not hasattr(warnings, 'catch_warnings'):
# catch_warnings is new in Python 2.6
return
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
a(1)
b(1)
yield nose.tools.assert_equal, len(w), 2
yield nose.tools.assert_true, "collision" in str(w[-1].message)
yield nose.tools.assert_true, "collision" in str(w[-2].message)
def test_memory_warning_collision_detection():
""" Check that collisions impossible to detect will raise appropriate
warnings.
"""
memory = Memory(cachedir=env['dir'], verbose=0)
a = eval('lambda x: x')
a = memory.cache(a)
b = eval('lambda x: x+1')
b = memory.cache(b)
if not hasattr(warnings, 'catch_warnings'):
# catch_warnings is new in Python 2.6
return
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
a(1)
b(1)
yield nose.tools.assert_equal, len(w), 1
yield nose.tools.assert_true, \
"cannot detect" in str(w[-1].message).lower()
def test_memory_partial():
" Test memory with functools.partial."
accumulator = list()
def func(x, y):
""" A helper function to define l as a lambda.
"""
accumulator.append(1)
return y
import functools
function = functools.partial(func, 1)
for test in check_identity_lazy(function, accumulator):
yield test
def test_memory_eval():
" Smoke test memory with a function with a function defined in an eval."
memory = Memory(cachedir=env['dir'], verbose=0)
m = eval('lambda x: x')
mm = memory.cache(m)
yield nose.tools.assert_equal, 1, mm(1)
def count_and_append(x=[]):
""" A function with a side effect in its arguments.
Return the lenght of its argument and append one element.
"""
len_x = len(x)
x.append(None)
return len_x
def test_argument_change():
""" Check that if a function has a side effect in its arguments, it
should use the hash of changing arguments.
"""
mem = Memory(cachedir=env['dir'], verbose=0)
func = mem.cache(count_and_append)
# call the function for the first time, is should cache it with
# argument x=[]
assert func() == 0
# the second time the argument is x=[None], which is not cached
# yet, so the functions should be called a second time
assert func() == 1
@with_numpy
def test_memory_numpy():
" Test memory with a function with numpy arrays."
# Check with memmapping and without.
for mmap_mode in (None, 'r'):
accumulator = list()
def n(l=None):
accumulator.append(1)
return l
memory = Memory(cachedir=env['dir'], mmap_mode=mmap_mode,
verbose=0)
memory.clear(warn=False)
cached_n = memory.cache(n)
rnd = np.random.RandomState(0)
for i in range(3):
a = rnd.random_sample((10, 10))
for _ in range(3):
yield nose.tools.assert_true, np.all(cached_n(a) == a)
yield nose.tools.assert_equal, len(accumulator), i + 1
def test_memory_exception():
""" Smoketest the exception handling of Memory.
"""
memory = Memory(cachedir=env['dir'], verbose=0)
class MyException(Exception):
pass
@memory.cache
def h(exc=0):
if exc:
raise MyException
# Call once, to initialise the cache
h()
for _ in range(3):
# Call 3 times, to be sure that the Exception is always raised
yield nose.tools.assert_raises, MyException, h, 1
def test_memory_ignore():
" Test the ignore feature of memory "
memory = Memory(cachedir=env['dir'], verbose=0)
accumulator = list()
@memory.cache(ignore=['y'])
def z(x, y=1):
accumulator.append(1)
yield nose.tools.assert_equal, z.ignore, ['y']
z(0, y=1)
yield nose.tools.assert_equal, len(accumulator), 1
z(0, y=1)
yield nose.tools.assert_equal, len(accumulator), 1
z(0, y=2)
yield nose.tools.assert_equal, len(accumulator), 1
def test_func_dir():
""" Test the creation of the memory cache directory for the function.
"""
memory = Memory(cachedir=env['dir'], verbose=0)
path = __name__.split('.')
path.append('f')
path = os.path.join(env['dir'], 'joblib', *path)
g = memory.cache(f)
# Test that the function directory is created on demand
yield nose.tools.assert_equal, g._get_func_dir(), path
yield nose.tools.assert_true, os.path.exists(path)
# Test that the code is stored.
yield nose.tools.assert_false, \
g._check_previous_func_code()
yield nose.tools.assert_true, \
os.path.exists(os.path.join(path, 'func_code.py'))
yield nose.tools.assert_true, \
g._check_previous_func_code()
# Test the robustness to failure of loading previous results.
dir, _ = g.get_output_dir(1)
a = g(1)
yield nose.tools.assert_true, os.path.exists(dir)
os.remove(os.path.join(dir, 'output.pkl'))
yield nose.tools.assert_equal, a, g(1)
def test_persistence():
""" Test the memorized functions can be pickled and restored.
"""
memory = Memory(cachedir=env['dir'], verbose=0)
g = memory.cache(f)
output = g(1)
h = pickle.loads(pickle.dumps(g))
output_dir, _ = g.get_output_dir(1)
yield nose.tools.assert_equal, output, h.load_output(output_dir)
memory2 = pickle.loads(pickle.dumps(memory))
yield nose.tools.assert_equal, memory.cachedir, memory2.cachedir
# Smoke test that pickling a memory with cachedir=None works
memory = Memory(cachedir=None, verbose=0)
pickle.loads(pickle.dumps(memory))
def test_format_signature():
""" Test the signature formatting.
"""
func = MemorizedFunc(f, cachedir=env['dir'])
path, sgn = func.format_signature(f, range(10))
yield nose.tools.assert_equal, \
sgn, \
'f([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])'
path, sgn = func.format_signature(f, range(10), y=range(10))
yield nose.tools.assert_equal, \
sgn, \
'f([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], y=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])'
@with_numpy
def test_format_signature_numpy():
""" Test the format signature formatting with numpy.
"""