mirror of
https://github.com/davidhalter/jedi.git
synced 2026-01-09 05:22:19 +08:00
Merge pull request #186 from tkf/versioned-cache
Remove old version of FS cache automatically
This commit is contained in:
@@ -25,6 +25,7 @@ try:
|
||||
import cPickle as pickle
|
||||
except:
|
||||
import pickle
|
||||
import shutil
|
||||
|
||||
from jedi._compatibility import json
|
||||
from jedi import settings
|
||||
@@ -219,13 +220,36 @@ def save_module(path, name, parser, pickling=True):
|
||||
|
||||
|
||||
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):
|
||||
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):
|
||||
try:
|
||||
pickle_changed_time = self._index[self.py_version][path]
|
||||
pickle_changed_time = self._index[path]
|
||||
except KeyError:
|
||||
return None
|
||||
if original_changed_time is not None \
|
||||
@@ -243,10 +267,10 @@ class _ModulePickling(object):
|
||||
def save_module(self, path, parser_cache_item):
|
||||
self.__index = None
|
||||
try:
|
||||
files = self._index[self.py_version]
|
||||
files = self._index
|
||||
except KeyError:
|
||||
files = {}
|
||||
self._index[self.py_version] = files
|
||||
self._index = files
|
||||
|
||||
with open(self._get_hashed_path(path), 'wb') as f:
|
||||
pickle.dump(parser_cache_item, f, pickle.HIGHEST_PROTOCOL)
|
||||
@@ -259,9 +283,16 @@ class _ModulePickling(object):
|
||||
if self.__index is None:
|
||||
try:
|
||||
with open(self._get_path('index.json')) as f:
|
||||
self.__index = json.load(f)
|
||||
data = json.load(f)
|
||||
except IOError:
|
||||
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
|
||||
|
||||
def _remove_old_modules(self):
|
||||
@@ -272,18 +303,25 @@ class _ModulePickling(object):
|
||||
self._index # reload index
|
||||
|
||||
def _flush_index(self):
|
||||
data = {'version': self.version, 'index': self._index}
|
||||
with open(self._get_path('index.json'), 'w') as f:
|
||||
json.dump(self._index, f)
|
||||
json.dump(data, f)
|
||||
self.__index = None
|
||||
|
||||
def delete_cache(self):
|
||||
shutil.rmtree(self._cache_directory())
|
||||
|
||||
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):
|
||||
dir = settings.cache_directory
|
||||
dir = self._cache_directory()
|
||||
if not os.path.exists(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
|
||||
|
||||
@@ -65,6 +65,18 @@ def pytest_generate_tests(metafunc):
|
||||
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')
|
||||
def clean_jedi_cache(request):
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from jedi import settings
|
||||
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)
|
||||
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
|
||||
|
||||
monkeypatch.setattr(settings, 'cache_directory', dir_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
|
||||
|
||||
|
||||
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