diff --git a/conftest.py b/conftest.py index 50d998425..3401b76e7 100644 --- a/conftest.py +++ b/conftest.py @@ -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') @@ -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. """ @@ -98,6 +98,10 @@ 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. @@ -105,6 +109,8 @@ def fin(): # 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) @@ -121,6 +127,12 @@ 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): @@ -128,8 +140,10 @@ def flushcache(request): 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() diff --git a/pyphi/cache.py b/pyphi/cache.py index 89858fcf0..bcc3cbb5e 100644 --- a/pyphi/cache.py +++ b/pyphi/cache.py @@ -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? diff --git a/pyphi/conf.py b/pyphi/conf.py index caaaf7022..ffd534950 100644 --- a/pyphi/conf.py +++ b/pyphi/conf.py @@ -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.""") diff --git a/pyphi_config.yml b/pyphi_config.yml index 7855a243f..5b99b4f7b 100644 --- a/pyphi_config.yml +++ b/pyphi_config.yml @@ -91,6 +91,8 @@ REDIS_CACHE: false REDIS_CONFIG: host: "localhost" port: 6379 + db: 0 + test_db: 1 # Logging # ~~~~~~~ diff --git a/test/test_cache.py b/test/test_cache.py index 528cc3fe6..3b6e42c24 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -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, @@ -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' @@ -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) @@ -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 @@ -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() @@ -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() @@ -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() @@ -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,)))