forked from VimPlug/jedi
Merge pull request #186 from tkf/versioned-cache
Remove old version of FS cache automatically
This commit is contained in:
+47
-9
@@ -25,6 +25,7 @@ try:
|
|||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
except:
|
except:
|
||||||
import pickle
|
import pickle
|
||||||
|
import shutil
|
||||||
|
|
||||||
from jedi._compatibility import json
|
from jedi._compatibility import json
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
@@ -219,13 +220,36 @@ def save_module(path, name, parser, pickling=True):
|
|||||||
|
|
||||||
|
|
||||||
class _ModulePickling(object):
|
class _ModulePickling(object):
|
||||||
|
|
||||||
|
version = 1
|
||||||
|
"""
|
||||||
|
Version number (integer) for file system cache.
|
||||||
|
|
||||||
|
Increment this number when there are any incompatible changes in
|
||||||
|
parser representation classes. For example, the following changes
|
||||||
|
are regarded as incompatible.
|
||||||
|
|
||||||
|
- Class name is changed.
|
||||||
|
- Class is moved to another module.
|
||||||
|
- Defined slot of the class is changed.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__index = None
|
self.__index = None
|
||||||
self.py_version = '%s.%s' % sys.version_info[:2]
|
self.py_tag = 'cpython-%s%s' % sys.version_info[:2]
|
||||||
|
"""
|
||||||
|
Short name for distinguish Python implementations and versions.
|
||||||
|
|
||||||
|
It's like `sys.implementation.cache_tag` but for Python < 3.3
|
||||||
|
we generate something similar. See:
|
||||||
|
http://docs.python.org/3/library/sys.html#sys.implementation
|
||||||
|
|
||||||
|
.. todo:: Detect interpreter (e.g., PyPy).
|
||||||
|
"""
|
||||||
|
|
||||||
def load_module(self, path, original_changed_time):
|
def load_module(self, path, original_changed_time):
|
||||||
try:
|
try:
|
||||||
pickle_changed_time = self._index[self.py_version][path]
|
pickle_changed_time = self._index[path]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
if original_changed_time is not None \
|
if original_changed_time is not None \
|
||||||
@@ -243,10 +267,10 @@ class _ModulePickling(object):
|
|||||||
def save_module(self, path, parser_cache_item):
|
def save_module(self, path, parser_cache_item):
|
||||||
self.__index = None
|
self.__index = None
|
||||||
try:
|
try:
|
||||||
files = self._index[self.py_version]
|
files = self._index
|
||||||
except KeyError:
|
except KeyError:
|
||||||
files = {}
|
files = {}
|
||||||
self._index[self.py_version] = files
|
self._index = files
|
||||||
|
|
||||||
with open(self._get_hashed_path(path), 'wb') as f:
|
with open(self._get_hashed_path(path), 'wb') as f:
|
||||||
pickle.dump(parser_cache_item, f, pickle.HIGHEST_PROTOCOL)
|
pickle.dump(parser_cache_item, f, pickle.HIGHEST_PROTOCOL)
|
||||||
@@ -259,9 +283,16 @@ class _ModulePickling(object):
|
|||||||
if self.__index is None:
|
if self.__index is None:
|
||||||
try:
|
try:
|
||||||
with open(self._get_path('index.json')) as f:
|
with open(self._get_path('index.json')) as f:
|
||||||
self.__index = json.load(f)
|
data = json.load(f)
|
||||||
except IOError:
|
except IOError:
|
||||||
self.__index = {}
|
self.__index = {}
|
||||||
|
else:
|
||||||
|
# 0 means version is not defined (= always delete cache):
|
||||||
|
if data.get('version', 0) != self.version:
|
||||||
|
self.delete_cache()
|
||||||
|
self.__index = {}
|
||||||
|
else:
|
||||||
|
self.__index = data['index']
|
||||||
return self.__index
|
return self.__index
|
||||||
|
|
||||||
def _remove_old_modules(self):
|
def _remove_old_modules(self):
|
||||||
@@ -272,18 +303,25 @@ class _ModulePickling(object):
|
|||||||
self._index # reload index
|
self._index # reload index
|
||||||
|
|
||||||
def _flush_index(self):
|
def _flush_index(self):
|
||||||
|
data = {'version': self.version, 'index': self._index}
|
||||||
with open(self._get_path('index.json'), 'w') as f:
|
with open(self._get_path('index.json'), 'w') as f:
|
||||||
json.dump(self._index, f)
|
json.dump(data, f)
|
||||||
self.__index = None
|
self.__index = None
|
||||||
|
|
||||||
|
def delete_cache(self):
|
||||||
|
shutil.rmtree(self._cache_directory())
|
||||||
|
|
||||||
def _get_hashed_path(self, path):
|
def _get_hashed_path(self, path):
|
||||||
return self._get_path('%s_%s.pkl' % (self.py_version, hash(path)))
|
return self._get_path('%s.pkl' % hash(path))
|
||||||
|
|
||||||
def _get_path(self, file):
|
def _get_path(self, file):
|
||||||
dir = settings.cache_directory
|
dir = self._cache_directory()
|
||||||
if not os.path.exists(dir):
|
if not os.path.exists(dir):
|
||||||
os.makedirs(dir)
|
os.makedirs(dir)
|
||||||
return dir + os.path.sep + file
|
return os.path.join(dir, file)
|
||||||
|
|
||||||
|
def _cache_directory(self):
|
||||||
|
return os.path.join(settings.cache_directory, self.py_tag)
|
||||||
|
|
||||||
|
|
||||||
# is a singleton
|
# is a singleton
|
||||||
|
|||||||
@@ -65,6 +65,18 @@ def pytest_generate_tests(metafunc):
|
|||||||
refactor.collect_dir_tests(base_dir, test_files))
|
refactor.collect_dir_tests(base_dir, test_files))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def isolated_jedi_cache(monkeypatch, tmpdir):
|
||||||
|
"""
|
||||||
|
Set `jedi.settings.cache_directory` to a temporary directory during test.
|
||||||
|
|
||||||
|
Same as `clean_jedi_cache`, but create the temporary directory for
|
||||||
|
each test case (scope='function').
|
||||||
|
"""
|
||||||
|
settings = base.jedi.settings
|
||||||
|
monkeypatch.setattr(settings, 'cache_directory', str(tmpdir))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def clean_jedi_cache(request):
|
def clean_jedi_cache(request):
|
||||||
"""
|
"""
|
||||||
|
|||||||
+26
-2
@@ -1,3 +1,5 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi.cache import ParserCacheItem, _ModulePickling
|
from jedi.cache import ParserCacheItem, _ModulePickling
|
||||||
|
|
||||||
@@ -21,10 +23,32 @@ def test_modulepickling_change_cache_dir(monkeypatch, tmpdir):
|
|||||||
|
|
||||||
monkeypatch.setattr(settings, 'cache_directory', dir_1)
|
monkeypatch.setattr(settings, 'cache_directory', dir_1)
|
||||||
ModulePickling.save_module(path_1, item_1)
|
ModulePickling.save_module(path_1, item_1)
|
||||||
cached = ModulePickling.load_module(path_1, item_1.change_time - 1)
|
cached = load_stored_item(ModulePickling, path_1, item_1)
|
||||||
assert cached == item_1.parser
|
assert cached == item_1.parser
|
||||||
|
|
||||||
monkeypatch.setattr(settings, 'cache_directory', dir_2)
|
monkeypatch.setattr(settings, 'cache_directory', dir_2)
|
||||||
ModulePickling.save_module(path_2, item_2)
|
ModulePickling.save_module(path_2, item_2)
|
||||||
cached = ModulePickling.load_module(path_1, item_1.change_time - 1)
|
cached = load_stored_item(ModulePickling, path_1, item_1)
|
||||||
assert cached is None
|
assert cached is None
|
||||||
|
|
||||||
|
|
||||||
|
def load_stored_item(cache, path, item):
|
||||||
|
"""Load `item` stored at `path` in `cache`."""
|
||||||
|
return cache.load_module(path, item.change_time - 1)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("isolated_jedi_cache")
|
||||||
|
def test_modulepickling_delete_incompatible_cache():
|
||||||
|
item = ParserCacheItem('fake parser')
|
||||||
|
path = 'fake path'
|
||||||
|
|
||||||
|
cache1 = _ModulePickling()
|
||||||
|
cache1.version = 1
|
||||||
|
cache1.save_module(path, item)
|
||||||
|
cached1 = load_stored_item(cache1, path, item)
|
||||||
|
assert cached1 == item.parser
|
||||||
|
|
||||||
|
cache2 = _ModulePickling()
|
||||||
|
cache2.version = 2
|
||||||
|
cached2 = load_stored_item(cache2, path, item)
|
||||||
|
assert cached2 is None
|
||||||
|
|||||||
Reference in New Issue
Block a user