repoview: make propertycache.setcache compatible with repoview
Propertycache used standard attribute assignment. In the repoview case, this
assignment was forwarded to the unfiltered repo. This result in:
(1) unfiltered repo got a potentially wrong cache value,
(2) repoview never reused the cached value.
This patch replaces the standard attribute assignment by an assignment to
`objc.__dict__` which will bypass the `repoview.__setattr__`. This will not
affects other `propertycache` users and it is actually closer to the semantic we
need.
The interaction of `propertycache` and `repoview` are now tested in a python
test file.
--- a/mercurial/util.py Tue Oct 01 16:41:04 2013 -0300
+++ b/mercurial/util.py Mon Sep 30 14:36:11 2013 +0200
@@ -279,7 +279,8 @@
return result
def cachevalue(self, obj, value):
- setattr(obj, self.name, value)
+ # __dict__ assigment required to bypass __setattr__ (eg: repoview)
+ obj.__dict__[self.name] = value
def pipefilter(s, cmd):
'''filter string S through command CMD, returning its output'''
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-propertycache.py Mon Sep 30 14:36:11 2013 +0200
@@ -0,0 +1,94 @@
+"""test behavior of propertycache and unfiltered propertycache
+
+The repoview overlay is quite complexe. We test the behavior of
+property cache of both localrepo and repoview to prevent
+regression."""
+
+import os, subprocess
+import mercurial.localrepo
+import mercurial.repoview
+import mercurial.util
+import mercurial.hg
+import mercurial.ui as uimod
+
+
+# create some special property cache that trace they call
+
+calllog = []
+@mercurial.util.propertycache
+def testcachedfoobar(repo):
+ name = repo.filtername
+ if name is None:
+ name = ''
+ val = len(name)
+ calllog.append(val)
+ return val
+
+#plug them on repo
+mercurial.localrepo.localrepository.testcachedfoobar = testcachedfoobar
+
+
+# create an empty repo. and instanciate it. It is important to run
+# those test on the real object to detect regression.
+repopath = os.path.join(os.environ['TESTTMP'], 'repo')
+subprocess.check_call(['hg', 'init', repopath])
+ui = uimod.ui()
+repo = mercurial.hg.repository(ui, path=repopath).unfiltered()
+
+
+print ''
+print '=== property cache ==='
+print ''
+print 'calllog:', calllog
+print 'cached value (unfiltered):',
+print vars(repo).get('testcachedfoobar', 'NOCACHE')
+
+print ''
+print '= first access on unfiltered, should do a call'
+print 'access:', repo.testcachedfoobar
+print 'calllog:', calllog
+print 'cached value (unfiltered):',
+print vars(repo).get('testcachedfoobar', 'NOCACHE')
+
+print ''
+print '= second access on unfiltered, should not do call'
+print 'access', repo.testcachedfoobar
+print 'calllog:', calllog
+print 'cached value (unfiltered):',
+print vars(repo).get('testcachedfoobar', 'NOCACHE')
+
+print ''
+print '= first access on "visible" view, should do a call'
+visibleview = repo.filtered('visible')
+print 'cached value ("visible" view):',
+print vars(visibleview).get('testcachedfoobar', 'NOCACHE')
+print 'access:', visibleview.testcachedfoobar
+print 'calllog:', calllog
+print 'cached value (unfiltered):',
+print vars(repo).get('testcachedfoobar', 'NOCACHE')
+print 'cached value ("visible" view):',
+print vars(visibleview).get('testcachedfoobar', 'NOCACHE')
+
+print ''
+print '= second access on "visible view", should not do call'
+print 'access:', visibleview.testcachedfoobar
+print 'calllog:', calllog
+print 'cached value (unfiltered):',
+print vars(repo).get('testcachedfoobar', 'NOCACHE')
+print 'cached value ("visible" view):',
+print vars(visibleview).get('testcachedfoobar', 'NOCACHE')
+
+print ''
+print '= no effect on other view'
+immutableview = repo.filtered('immutable')
+print 'cached value ("immutable" view):',
+print vars(immutableview).get('testcachedfoobar', 'NOCACHE')
+print 'access:', immutableview.testcachedfoobar
+print 'calllog:', calllog
+print 'cached value (unfiltered):',
+print vars(repo).get('testcachedfoobar', 'NOCACHE')
+print 'cached value ("visible" view):',
+print vars(visibleview).get('testcachedfoobar', 'NOCACHE')
+print 'cached value ("immutable" view):',
+print vars(immutableview).get('testcachedfoobar', 'NOCACHE')
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-propertycache.py.out Mon Sep 30 14:36:11 2013 +0200
@@ -0,0 +1,36 @@
+
+=== property cache ===
+
+calllog: []
+cached value (unfiltered): NOCACHE
+
+= first access on unfiltered, should do a call
+access: 0
+calllog: [0]
+cached value (unfiltered): 0
+
+= second access on unfiltered, should not do call
+access 0
+calllog: [0]
+cached value (unfiltered): 0
+
+= first access on "visible" view, should do a call
+cached value ("visible" view): NOCACHE
+access: 7
+calllog: [0, 7]
+cached value (unfiltered): 0
+cached value ("visible" view): 7
+
+= second access on "visible view", should not do call
+access: 7
+calllog: [0, 7]
+cached value (unfiltered): 0
+cached value ("visible" view): 7
+
+= no effect on other view
+cached value ("immutable" view): NOCACHE
+access: 9
+calllog: [0, 7, 9]
+cached value (unfiltered): 0
+cached value ("visible" view): 7
+cached value ("immutable" view): 9