Skip to content

Commit

Permalink
Redis: use a test database
Browse files Browse the repository at this point in the history
  • Loading branch information
rlmv committed Apr 26, 2018
1 parent 8927d7f commit acbc586
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 31 deletions.
24 changes: 19 additions & 5 deletions conftest.py
Expand Up @@ -8,7 +8,7 @@
import pytest

import pyphi
from pyphi import config, constants, db
from pyphi import cache, config, constants, db


log = logging.getLogger('pyphi.test')
Expand Down Expand Up @@ -82,9 +82,9 @@ def disable_progress_bars():


@pytest.fixture(scope="session", autouse=True)
def restore_filesystem_cache(request):
"""Temporarily backup, then restore, the user's joblib cache after each
testing session.
def protect_caches(request):
"""Temporarily backup, then restore, the user's joblib, mongo and redis
caches before and after the testing session.
This is called before flushcache, ensuring the cache is saved.
"""
Expand All @@ -98,13 +98,19 @@ def restore_filesystem_cache(request):
shutil.move(config.FS_CACHE_DIRECTORY, BACKUP_CACHE_DIR)
os.mkdir(config.FS_CACHE_DIRECTORY)

# Initialize a test Redis connection
original_redis_conn = cache.redis_conn
cache.redis_conn = cache.redis_init(config.REDIS_CONFIG['test_db'])

def fin():
if config.CACHING_BACKEND == constants.FILESYSTEM:
# Remove the tests' joblib cache directory.
shutil.rmtree(config.FS_CACHE_DIRECTORY)
# Restore the old joblib cache.
shutil.move(BACKUP_CACHE_DIR, config.FS_CACHE_DIRECTORY)

cache.redis_conn = original_redis_conn

# Restore the cache after the last test has run
request.addfinalizer(fin)

Expand All @@ -121,15 +127,23 @@ def _flush_database_cache():
return db.database.test.remove({})


def _flush_redis_cache():
if cache.redis_available():
cache.redis_conn.flushdb()
cache.redis_conn.config_resetstat()


# TODO: flush Redis cache
@pytest.fixture(scope="function", autouse=True)
def flushcache(request):
"""Flush the currently enabled cache.
This is called before every test case.
"""
log.info("Flushing cache...")
log.info("Flushing caches...")
if config.CACHING_BACKEND == constants.DATABASE:
_flush_database_cache()
elif config.CACHING_BACKEND == constants.FILESYSTEM:
_flush_joblib_cache()

_flush_redis_cache()
15 changes: 13 additions & 2 deletions pyphi/cache.py
Expand Up @@ -210,14 +210,25 @@ def key(self, *args, _prefix=None, **kwargs):
return (_prefix,) + tuple(args)


def redis_init(db):
return redis.StrictRedis(host=config.REDIS_CONFIG['host'],
port=config.REDIS_CONFIG['port'], db=db)

# Expose the StrictRedis API, maintaining one connection pool
# The connection pool is multi-process safe, and is reinitialized when the
# client detects a fork. See:
# https://github.com/andymccurdy/redis-py/blob/5109cb4f/redis/connection.py#L950
#
# TODO: rebuild connection after config changes?
redis_conn = redis.StrictRedis(host=config.REDIS_CONFIG['host'],
port=config.REDIS_CONFIG['port'], db=0)
redis_conn = redis_init(config.REDIS_CONFIG['db'])


def redis_available():
"""Check if the Redis server is connected."""
try:
return redis_conn.ping()
except redis.exceptions.ConnectionError:
return False


# TODO: use a cache prefix?
Expand Down
2 changes: 2 additions & 0 deletions pyphi/conf.py
Expand Up @@ -463,6 +463,8 @@ class PyphiConfig(Config):
REDIS_CONFIG = Option({
'host': 'localhost',
'port': 6379,
'db': 0,
'test_db': 1,
}, doc="""
Configure the Redis database backend. These are the defaults in the
provided ``redis.conf`` file.""")
Expand Down
2 changes: 2 additions & 0 deletions pyphi_config.yml
Expand Up @@ -91,6 +91,8 @@ REDIS_CACHE: false
REDIS_CONFIG:
host: "localhost"
port: 6379
db: 0
test_db: 1

# Logging
# ~~~~~~~
Expand Down
33 changes: 9 additions & 24 deletions test/test_cache.py
Expand Up @@ -106,10 +106,7 @@ def test_cache_repertoires_config_option():
# pytest fixture because they must be constructed with the correct cache
# config.

try:
redis_available = cache.redis_conn.ping()
except redis.exceptions.ConnectionError:
redis_available = False
redis_available = cache.redis_available()

# Decorator to skip a test if Redis is not available
require_redis = pytest.mark.skipif(not redis_available,
Expand Down Expand Up @@ -140,23 +137,13 @@ def wrapper(redis_cache, *args, **kwargs):
return functools.wraps(test_func)(wrapper)


@pytest.fixture
def flush_redis():
"""Fixture to flush and reset the Redis cache."""
try:
cache.redis_conn.flushall()
cache.redis_conn.config_resetstat()
except redis.exceptions.ConnectionError:
pass


@require_redis
def test_redis_singleton_connection():
assert cache.redis_conn.ping() is True
assert cache.redis_available()


@require_redis
def test_redis_cache_info(flush_redis):
def test_redis_cache_info():
c = cache.RedisCache()
assert c.info() == (0, 0, 0)
key = 'key'
Expand Down Expand Up @@ -207,7 +194,7 @@ def test_mice_cache_keys(s):


@all_caches
def test_mice_cache(redis_cache, flush_redis):
def test_mice_cache(redis_cache):
s = examples.basic_subsystem()
mechanism = (1,) # has a MIC
mice = s.find_mice(Direction.CAUSE, mechanism)
Expand All @@ -226,7 +213,7 @@ def test_do_not_cache_phi_zero_mice():


@all_caches
def test_only_cache_uncut_subsystem_mices(redis_cache, flush_redis, s):
def test_only_cache_uncut_subsystem_mices(redis_cache, s):
s = Subsystem(s.network, (1, 0, 0), s.node_indices,
cut=models.Cut((1,), (0, 2)))
mechanism = (1,) # has a MIC
Expand All @@ -236,7 +223,7 @@ def test_only_cache_uncut_subsystem_mices(redis_cache, flush_redis, s):


@all_caches
def test_split_mechanism_mice_is_not_reusable(redis_cache, flush_redis):
def test_split_mechanism_mice_is_not_reusable(redis_cache):
"""If mechanism is split, then cached mice are not usable
when a cache is built from a parent cache."""
s = examples.basic_subsystem()
Expand All @@ -254,8 +241,7 @@ def test_split_mechanism_mice_is_not_reusable(redis_cache, flush_redis):


@all_caches
def test_cut_relevant_connections_mice_is_not_reusable(redis_cache,
flush_redis):
def test_cut_relevant_connections_mice_is_not_reusable(redis_cache):
"""If relevant connections are cut, cached mice are not usable
when a cache is built from a parent cache."""
s = examples.basic_subsystem()
Expand All @@ -273,7 +259,7 @@ def test_cut_relevant_connections_mice_is_not_reusable(redis_cache,


@all_caches
def test_inherited_mice_cache_keeps_unaffected_mice(redis_cache, flush_redis):
def test_inherited_mice_cache_keeps_unaffected_mice(redis_cache):
"""Cached MICE are saved from the parent cache if both
the mechanism and the relevant connections are not cut."""
s = examples.basic_subsystem()
Expand All @@ -291,8 +277,7 @@ def test_inherited_mice_cache_keeps_unaffected_mice(redis_cache, flush_redis):


@all_caches
def test_inherited_cache_must_come_from_uncut_subsystem(redis_cache,
flush_redis):
def test_inherited_cache_must_come_from_uncut_subsystem(redis_cache):
s = examples.basic_subsystem()
cut_s = Subsystem(s.network, s.state, s.node_indices,
cut=models.Cut((0, 2), (1,)))
Expand Down

0 comments on commit acbc586

Please sign in to comment.